Read-Sensor-Resistances/serial_plotter.py

313 lines
17 KiB
Python
Raw Normal View History

2022-08-08 22:51:36 +00:00
from datetime import datetime
2022-08-18 09:56:58 +00:00
from matplotlib.animation import FuncAnimation
import os, json, traceback, wx
2022-08-08 22:51:36 +00:00
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import decimal
2022-12-15 23:22:33 +00:00
from itertools import islice
2022-08-18 09:56:58 +00:00
matplotlib.use("WXAgg") # for JetBrains IDE to force use wxPython as backend UI for plotting
2022-08-08 22:51:36 +00:00
class SerialPlotter:
2022-08-18 09:56:58 +00:00
def __init__(self, parent: wx.Frame = None) -> None:
"""
Dynamically plot a graph with moving window, if a finite window size set by the user. It reads the .csv file
read_arduino generated, use the last line as the latest data and update the graph accordingly. It will plot
multiple lines on the graph if there are more than one values on the last row of the .csv file
:param parent: optional, parent frame
"""
self.parent = parent
2022-08-08 22:51:36 +00:00
self.settings = json.load(open('settings.json', 'r'))
self.sensors = len(self.settings['sensor_ports'])
2022-08-18 09:56:58 +00:00
self.windowsize = self.settings['winSize']
2023-03-07 05:15:07 +00:00
self.fontSize = self.settings['fontSize']
2022-08-08 22:51:36 +00:00
self.delay = self.settings["delay"] / 1000
2022-08-18 09:56:58 +00:00
2022-08-08 22:51:36 +00:00
self.colors = ['blue', 'orange', 'green', 'yellow']
2022-08-18 09:56:58 +00:00
# TODO: make the figure size an UI option and pass into the settings.json
#self.fig, self.axs = plt.subplots(figsize=(9, 6))
# self.fig_1 = plt.figure(1)
# self.axs_1 = plt.axes()
# self.fig_2 = plt.figure(2)
# self.axs_2 = plt.axes()
if (self.sensors == 1) :
self.fig_1, self.axs_1 = plt.subplots()
self.fig_1.canvas.mpl_connect('close_event', self.event_close)
elif (self.sensors == 2) :
self.fig_1, self.axs_1 = plt.subplots()
self.fig_2, self.axs_2 = plt.subplots()
self.fig_1.canvas.mpl_connect('close_event', self.event_close)
self.fig_2.canvas.mpl_connect('close_event', self.event_close)
elif (self.sensors == 3) :
self.fig_1, self.axs_1 = plt.subplots()
self.fig_2, self.axs_2 = plt.subplots()
self.fig_3, self.axs_3 = plt.subplots()
self.fig_1.canvas.mpl_connect('close_event', self.event_close)
self.fig_2.canvas.mpl_connect('close_event', self.event_close)
self.fig_3.canvas.mpl_connect('close_event', self.event_close)
elif (self.sensors == 4) :
self.fig_1, self.axs_1 = plt.subplots()
self.fig_2, self.axs_2 = plt.subplots()
self.fig_3, self.axs_3 = plt.subplots()
self.fig_4, self.axs_4 = plt.subplots()
self.fig_1.canvas.mpl_connect('close_event', self.event_close)
self.fig_2.canvas.mpl_connect('close_event', self.event_close)
self.fig_3.canvas.mpl_connect('close_event', self.event_close)
self.fig_4.canvas.mpl_connect('close_event', self.event_close)
2022-08-08 22:51:36 +00:00
self.timeStamps = {}
self.sensorsData = {}
2022-08-18 09:56:58 +00:00
self.timeElapsed = 0 # time have passed since the graph started, in seconds
2022-08-08 22:51:36 +00:00
for i in range(self.sensors):
2022-08-18 09:56:58 +00:00
self.timeStamps[i] = ['']
self.sensorsData[i] = [0]
2022-08-08 22:51:36 +00:00
2022-08-18 09:56:58 +00:00
def event_close(self, event) -> None:
"""
Actions need to be executed when the graph has closed. Start a new .csv file to get read for new graph and
2022-08-18 09:56:58 +00:00
bring back the UI, if exist
"""
2022-08-08 22:51:36 +00:00
file = self.settings["file_name"]
wx.MessageBox(f"File has saved as {os.path.split(file)[1]} under {os.path.split(file)[0]} directory!\n")
2022-08-18 09:56:58 +00:00
if self.parent:
self.parent.Show()
2022-08-08 22:51:36 +00:00
def animation_1(self, t) -> None:
2022-08-18 09:56:58 +00:00
"""
2023-04-14 11:20:34 +00:00
render a frame of the animated graph.
2022-08-18 09:56:58 +00:00
"""
2023-04-14 11:20:34 +00:00
# Some of them copied from the old code, so even Eric may not 100% know what they do. ChatGPT may give a better explaination
2022-08-08 22:51:36 +00:00
try:
plt.cla() # clear previous frame
2022-08-18 09:56:58 +00:00
# read the last line from the .csv file, the data start from the second column so omit index #0
2022-08-08 22:51:36 +00:00
file = open(self.settings["file_name"], "r")
2022-12-15 23:22:33 +00:00
ndata = np.array([np.asarray(line.split(", ")[1:], dtype=np.float32) for line in islice(file, 1, None)])#read from the second row of the file ignoring headers
2022-08-08 22:51:36 +00:00
if len(ndata) > 0:
row = ndata[-1]
i = 0
if 0 < self.windowsize < len(self.timeStamps[i]):
self.timeStamps[i].pop(0)
self.sensorsData[i].pop(0)
2022-08-08 22:51:36 +00:00
2022-08-18 09:56:58 +00:00
# self.timeStamps[i].append(datetime.now().strftime('%H:%M:%S')) # version 1
# version 2, if we decide to go with this one change the list type to list[int] from list[str]
self.timeStamps[i].append(str(self.timeElapsed))
2022-08-18 09:56:58 +00:00
self.sensorsData[i].append(row[i])
2022-08-08 22:51:36 +00:00
# plot a line
# round the number to scientific notation
self.axs_1.plot(self.timeStamps[i], self.sensorsData[i], color=self.colors[i],
label=f'sensor {i + 1}, latest: {np.format_float_scientific(self.sensorsData[i][-1], precision = 2)} $\Omega$')
# TODO: make the font size an UI option from the settings.json
# Make the font size of values placed on x-axis and y-aixs equal to 16 (large enough)
#plt.xticks(fontsize = max(self.fontSize, 10))
#plt.yticks(fontsize = max(self.fontSize, 10))
self.axs_1.tick_params(axis='both', which='both', labelsize=max(self.fontSize, 10))
# make the label of two axes large enough
self.axs_1.set_xlabel('Time (seconds)', fontsize = max(self.fontSize, 10))
self.axs_1.set_ylabel(u'Resistance ($\Omega$)', fontsize = max(self.fontSize, 10))
#i += 1
self.timeElapsed += 1 # increment time
# Acknowledgement: https://stackoverflow.com/a/13589144 (can you imagine how long it took me to find this before ChatGPT?)
#handles, labels = self.axs.get_legend_handles_labels()
#by_label = dict(zip(labels, handles))
#self.axs.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10)) # Make the legend on graph large enough
# handles, labels = self.axs_1.get_legend_handles_labels()
# by_label = dict(zip(labels, handles))
# self.axs_1.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10))
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10))
except:
traceback.print_exc()
def animation_2(self, t: int) -> None:
"""
render a frame of the animated graph.
"""
# Some of them copied from the old code, so even Eric may not 100% know what they do. ChatGPT may give a better explaination
try:
plt.cla() # clear previous frame
# read the last line from the .csv file, the data start from the second column so omit index #0
file = open(self.settings["file_name"], "r")
ndata = np.array([np.asarray(line.split(", ")[1:], dtype=np.float32) for line in islice(file, 1, None)])#read from the second row of the file ignoring headers
if len(ndata) > 0:
row = ndata[-1]
i = 1
if 0 < self.windowsize < len(self.timeStamps[i]):
self.timeStamps[i].pop(0)
self.sensorsData[i].pop(0)
# self.timeStamps[i].append(datetime.now().strftime('%H:%M:%S')) # version 1
# version 2, if we decide to go with this one change the list type to list[int] from list[str]
self.timeStamps[i].append(str(self.timeElapsed))
self.sensorsData[i].append(row[i])
# plot a line
# round the number to scientific notation
self.axs_2.plot(self.timeStamps[i], self.sensorsData[i], color=self.colors[i],
label=f'sensor {i + 1}, latest: {np.format_float_scientific(self.sensorsData[i][-1], precision = 2)} $\Omega$')
2023-03-06 06:14:29 +00:00
# TODO: make the font size an UI option from the settings.json
# Make the font size of values placed on x-axis and y-aixs equal to 16 (large enough)
#self.axs_2.set_xticks(fontsize = max(self.fontSize, 10))
#self.axs_2.set_yticks(fontsize = max(self.fontSize, 10))
self.axs_2.tick_params(axis='both', which='both', labelsize=max(self.fontSize, 10))
# make the label of two axes large enough
self.axs_2.set_xlabel('Time (seconds)', fontsize = max(self.fontSize, 10))
self.axs_2.set_ylabel(u'Resistance ($\Omega$)', fontsize = max(self.fontSize, 10))
#i += 1
2022-08-18 09:56:58 +00:00
self.timeElapsed += 1 # increment time
2023-04-14 11:20:34 +00:00
# Acknowledgement: https://stackoverflow.com/a/13589144 (can you imagine how long it took me to find this before ChatGPT?)
#handles, labels = self.axs.get_legend_handles_labels()
#by_label = dict(zip(labels, handles))
#self.axs.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10)) # Make the legend on graph large enough
# handles, labels = self.axs_2.get_legend_handles_labels()
# by_label = dict(zip(labels, handles))
# self.axs_2.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10))
handles, labels = plt.gca().get_legend_handles_labels()
2022-08-08 22:51:36 +00:00
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10))
2022-08-08 22:51:36 +00:00
except:
traceback.print_exc()
def animation_3(self, t: int) -> None:
"""
render a frame of the animated graph.
"""
# Some of them copied from the old code, so even Eric may not 100% know what they do. ChatGPT may give a better explaination
try:
plt.cla() # clear previous frame
# read the last line from the .csv file, the data start from the second column so omit index #0
file = open(self.settings["file_name"], "r")
ndata = np.array([np.asarray(line.split(", ")[1:], dtype=np.float32) for line in islice(file, 1, None)])#read from the second row of the file ignoring headers
if len(ndata) > 0:
row = ndata[-1]
i = 2
if 0 < self.windowsize < len(self.timeStamps[i]):
self.timeStamps[i].pop(0)
self.sensorsData[i].pop(0)
2022-08-08 22:51:36 +00:00
# self.timeStamps[i].append(datetime.now().strftime('%H:%M:%S')) # version 1
# version 2, if we decide to go with this one change the list type to list[int] from list[str]
self.timeStamps[i].append(str(self.timeElapsed))
self.sensorsData[i].append(row[i])
# plot a line
# round the number to scientific notation
self.axs_3.plot(self.timeStamps[i], self.sensorsData[i], color=self.colors[i],
label=f'sensor {i + 1}, latest: {np.format_float_scientific(self.sensorsData[i][-1], precision = 2)} $\Omega$')
# TODO: make the font size an UI option from the settings.json
# Make the font size of values placed on x-axis and y-aixs equal to 16 (large enough)
#self.axs_2.set_xticks(fontsize = max(self.fontSize, 10))
#self.axs_2.set_yticks(fontsize = max(self.fontSize, 10))
self.axs_3.tick_params(axis='both', which='both', labelsize=max(self.fontSize, 10))
# make the label of two axes large enough
self.axs_3.set_xlabel('Time (seconds)', fontsize = max(self.fontSize, 10))
self.axs_3.set_ylabel(u'Resistance ($\Omega$)', fontsize = max(self.fontSize, 10))
#i += 1
self.timeElapsed += 1 # increment time
# Acknowledgement: https://stackoverflow.com/a/13589144 (can you imagine how long it took me to find this before ChatGPT?)
#handles, labels = self.axs.get_legend_handles_labels()
#by_label = dict(zip(labels, handles))
#self.axs.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10)) # Make the legend on graph large enough
# handles, labels = self.axs_2.get_legend_handles_labels()
# by_label = dict(zip(labels, handles))
# self.axs_2.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10))
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10))
except:
traceback.print_exc()
def animation_4(self, t: int) -> None:
"""
render a frame of the animated graph.
"""
# Some of them copied from the old code, so even Eric may not 100% know what they do. ChatGPT may give a better explaination
try:
plt.cla() # clear previous frame
# read the last line from the .csv file, the data start from the second column so omit index #0
file = open(self.settings["file_name"], "r")
ndata = np.array([np.asarray(line.split(", ")[1:], dtype=np.float32) for line in islice(file, 1, None)])#read from the second row of the file ignoring headers
if len(ndata) > 0:
row = ndata[-1]
i = 3
if 0 < self.windowsize < len(self.timeStamps[i]):
self.timeStamps[i].pop(0)
self.sensorsData[i].pop(0)
# self.timeStamps[i].append(datetime.now().strftime('%H:%M:%S')) # version 1
# version 2, if we decide to go with this one change the list type to list[int] from list[str]
self.timeStamps[i].append(str(self.timeElapsed))
self.sensorsData[i].append(row[i])
# plot a line
# round the number to scientific notation
self.axs_4.plot(self.timeStamps[i], self.sensorsData[i], color=self.colors[i],
label=f'sensor {i + 1}, latest: {np.format_float_scientific(self.sensorsData[i][-1], precision = 2)} $\Omega$')
# TODO: make the font size an UI option from the settings.json
# Make the font size of values placed on x-axis and y-aixs equal to 16 (large enough)
#self.axs_2.set_xticks(fontsize = max(self.fontSize, 10))
#self.axs_2.set_yticks(fontsize = max(self.fontSize, 10))
self.axs_4.tick_params(axis='both', which='both', labelsize=max(self.fontSize, 10))
# make the label of two axes large enough
self.axs_4.set_xlabel('Time (seconds)', fontsize = max(self.fontSize, 10))
self.axs_4.set_ylabel(u'Resistance ($\Omega$)', fontsize = max(self.fontSize, 10))
#i += 1
self.timeElapsed += 1 # increment time
# Acknowledgement: https://stackoverflow.com/a/13589144 (can you imagine how long it took me to find this before ChatGPT?)
#handles, labels = self.axs.get_legend_handles_labels()
#by_label = dict(zip(labels, handles))
#self.axs.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10)) # Make the legend on graph large enough
# handles, labels = self.axs_2.get_legend_handles_labels()
# by_label = dict(zip(labels, handles))
# self.axs_2.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10))
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc='best', fontsize = max(self.fontSize, 10))
except:
traceback.print_exc()
def plotting_1(self) -> FuncAnimation:
""" animate the dynamic plot """
ani_1 = FuncAnimation(self.fig_1, self.animation_1, blit=False, interval=self.delay * 1000, save_count=60)
return ani_1
def plotting_2(self) -> FuncAnimation:
""" animate the dynamic plot """
ani_2 = FuncAnimation(self.fig_2, self.animation_2, blit=False, interval=self.delay * 1000, save_count=60)
return ani_2
def plotting_3(self) -> FuncAnimation:
""" animate the dynamic plot """
ani_3 = FuncAnimation(self.fig_3, self.animation_3, blit=False, interval=self.delay * 1000, save_count=60)
return ani_3
def plotting_4(self) -> FuncAnimation:
2022-08-18 09:56:58 +00:00
""" animate the dynamic plot """
ani_4 = FuncAnimation(self.fig_3, self.animation_3, blit=False, interval=self.delay * 1000, save_count=60)
return ani_4