From 6289bc52b5f11af2172315a3555f1b722f093c29 Mon Sep 17 00:00:00 2001 From: Dennis van Gils <37263907+Dennis-van-Gils@users.noreply.github.com> Date: Thu, 30 Jul 2020 22:14:27 +0200 Subject: [PATCH] Bump dependence dvg-pyqtgraph-threadsafe==1.0 --- DvG_pyqt_ChartHistory.py | 131 -------------------------- README.rst | 1 + demo_A_GUI_full.py | 80 ++++++++-------- demo_B_GUI_minimal.py | 27 +++--- demo_C_singlethread_for_comparison.py | 68 ++++++------- demo_D_no_GUI.py | 6 +- demo_E_no_GUI.py | 6 +- list_conda.txt | 1 + list_pip.txt | 1 + requirements.txt | 3 +- 10 files changed, 93 insertions(+), 231 deletions(-) delete mode 100644 DvG_pyqt_ChartHistory.py diff --git a/DvG_pyqt_ChartHistory.py b/DvG_pyqt_ChartHistory.py deleted file mode 100644 index 6ea4739..0000000 --- a/DvG_pyqt_ChartHistory.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -"""Class ChartHistory manages two data arrays `x` and `y` that serve as history -buffers for displaying e.g. a strip chart or Lissajous curve. It is -thread-safe and relies on the PyQtGraph library for fast plotting to screen. -Intended multithreaded operation: One thread does the data acquisition and -pushes new data points into the history buffers by calling `add_new_reading(s)`, -and another thread performs the GUI refresh and redraws the data behind the plot -by calling `update_curve`. - -New readings are stored in an history array of fixed size. Newest readings -are placed at the end of the array. Array full? -> FIFO. - -Class: - ChartHistory(chart_history_length, plot_data_item): - Args: - chart_history_length: - Number of data points to store in each history buffer. - plot_data_item: - Instance of `pyqtgraph.PlotDataItem` to plot out the buffered - data to. - - Methods: - apply_downsampling(...): - Calls the downsampling routines of PyQtGraph, but in the future - I will provide my own routines for downsampling here. - add_new_reading(...): - Add single data point (x, y) to the history buffers. - add_new_readings(...): - Add lists of data points (list_x, list_y) to the history - buffers. - update_curve(): - Update the data behind the curve and redraw. - clear(): - Clear buffers. - - Important member: - x_axis_divisor: - If the x-data is time, you can use this divisor value to - transform the x-axis units from e.g. milliseconds to seconds or - minutes. - y_axis_divisor: - Same functionality as x_axis_divisor -""" -__author__ = "Dennis van Gils" -__authoremail__ = "vangils.dennis@gmail.com" -__url__ = "https://github.com/Dennis-van-Gils/DvG_PyQt_misc" -__date__ = "14-09-2018" -__version__ = "1.0.0" - -import collections - -import numpy as np -from PyQt5 import QtCore -import pyqtgraph as pg - -class ChartHistory(object): - def __init__(self, - chart_history_length, - plot_data_item: pg.PlotDataItem=None): - self.chart_history_length = chart_history_length - self.curve = plot_data_item # Instance of [pyqtgraph.PlotDataItem] - self.mutex = QtCore.QMutex() # For the case of multithreaded access - - # If the x-data is time, you can use this divisor value to transform - # the x-axis units from e.g. milliseconds to seconds or minutes. - self.x_axis_divisor = 1 - self.y_axis_divisor = 1 - - self._x = collections.deque(maxlen=chart_history_length) - self._y = collections.deque(maxlen=chart_history_length) - self._x_snapshot = [0] - self._y_snapshot = [0] - - # Performance boost: Do not plot data outside of visible range - self.curve.clipToView = True - - # Default to no downsampling - self.curve.setDownsampling(ds=1, auto=False, method='mean') - - def apply_downsampling(self, do_apply=True, ds=4): - if do_apply: - # Speed up plotting, needed for keeping the GUI responsive when - # using large datasets - self.curve.setDownsampling(ds=ds, auto=False, method='mean') - else: - self.curve.setDownsampling(ds=1, auto=False, method='mean') - - def add_new_reading(self, x, y): - locker = QtCore.QMutexLocker(self.mutex) - self._x.append(x) - self._y.append(y) - locker.unlock() - - def add_new_readings(self, x_list, y_list): - locker = QtCore.QMutexLocker(self.mutex) - self._x.extend(x_list) - self._y.extend(y_list) - locker.unlock() - - def update_curve(self): - """Creates a snapshot of the buffered data, which is a fast operation, - followed by updating the data behind the curve and redrawing it, which - is a slow operation. Hence, the use of a snapshot creation, which is - locked my a mutex, followed by a the mutex unlocked redrawing. - """ - - # First create a snapshot of the buffered data. Fast. - locker = QtCore.QMutexLocker(self.mutex) - self._x_snapshot = np.copy(self._x) - self._y_snapshot = np.copy(self._y) - #print("numel x: %d, numel y: %d" % - # (self._x_snapshot.size, self._y_snapshot.size)) - locker.unlock() - - # Now update the data behind the curve and redraw the curve. Slow - if self.curve is not None: - if ((len(self._x_snapshot) == 0) or - (np.alltrue(np.isnan(self._y_snapshot)))): - self.curve.setData([0], [0]) - else: - self.curve.setData((self._x_snapshot - self._x_snapshot[-1]) - / float(self.x_axis_divisor), - self._y_snapshot - / float(self.y_axis_divisor)) - - def clear(self): - locker = QtCore.QMutexLocker(self.mutex) - self._x.clear() - self._y.clear() - locker.unlock() diff --git a/README.rst b/README.rst index 65f73d5..21055fb 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,7 @@ Other depencies you'll need for this demo can be installed by running:: * `dvg-debug-functions `_ * `dvg-qdeviceio `_ * `dvg-devices `_ +* `dvg-pyqtgraph-threadsafe `_ * `psutil `_ * `pySerial `_ * `NumPy `_ diff --git a/demo_A_GUI_full.py b/demo_A_GUI_full.py index 4d34cc9..5cf043a 100644 --- a/demo_A_GUI_full.py +++ b/demo_A_GUI_full.py @@ -6,8 +6,8 @@ __author__ = "Dennis van Gils" __authoremail__ = "vangils.dennis@gmail.com" __url__ = "https://github.com/Dennis-van-Gils/DvG_Arduino_PyQt_multithread_demo" -__date__ = "24-07-2020" -__version__ = "5.0" +__date__ = "30-07-2020" +__version__ = "6.0" # pylint: disable=bare-except, broad-except import os @@ -23,11 +23,11 @@ import pyqtgraph as pg from dvg_debug_functions import tprint, dprint, print_fancy_traceback as pft +from dvg_pyqtgraph_threadsafe import HistoryChartCurve from dvg_devices.Arduino_protocol_serial import Arduino from dvg_qdeviceio import QDeviceIO from DvG_pyqt_FileLogger import FileLogger -from DvG_pyqt_ChartHistory import ChartHistory from DvG_pyqt_controls import create_Toggle_button, SS_GROUP try: @@ -184,7 +184,7 @@ def __init__(self, parent=None, **kwargs): # Bottom frame # ------------------------- - # Create PlotItem + # GraphicsWindow self.gw_chart = pg.GraphicsWindow() self.gw_chart.setBackground([20, 20, 20]) self.pi_chart = self.gw_chart.addPlot() @@ -209,10 +209,12 @@ def __init__(self, parent=None, **kwargs): self.pi_chart.getAxis("left").setStyle(tickTextOffset=20) self.pi_chart.getAxis("left").setWidth(120) - # Create ChartHistory and PlotDataItem and link them together - PEN_01 = pg.mkPen(color=[255, 255, 90], width=3) - num_samples = round(CHART_HISTORY_TIME * 1e3 / DAQ_INTERVAL_MS) - self.CH_1 = ChartHistory(num_samples, self.pi_chart.plot(pen=PEN_01)) + self.history_chart_curve = HistoryChartCurve( + capacity=round(CHART_HISTORY_TIME * 1e3 / DAQ_INTERVAL_MS), + linked_curve=self.pi_chart.plot( + pen=pg.mkPen(color=[255, 255, 90], width=3) + ), + ) # 'Readings' p = {"readOnly": True} @@ -300,7 +302,7 @@ def process_qpbt_clear_chart(self): ) if reply == QtWid.QMessageBox.Yes: - self.CH_1.clear() + self.history_chart_curve.clear() @QtCore.pyqtSlot() def process_qpbt_record(self): @@ -309,6 +311,10 @@ def process_qpbt_record(self): else: file_logger.stopping = True + @QtCore.pyqtSlot(str) + def set_text_qpbt_record(self, text_str): + self.qpbt_record.setText(text_str) + @QtCore.pyqtSlot() def process_qpbt_wave_sine(self): qdev_ard.send(ard.write, "sine") @@ -321,37 +327,25 @@ def process_qpbt_wave_square(self): def process_qpbt_wave_sawtooth(self): qdev_ard.send(ard.write, "sawtooth") - @QtCore.pyqtSlot(str) - def set_text_qpbt_record(self, text_str): - self.qpbt_record.setText(text_str) - - -# ------------------------------------------------------------------------------ -# update_GUI -# ------------------------------------------------------------------------------ - - -@QtCore.pyqtSlot() -def update_GUI(): - str_cur_date, str_cur_time, _ = get_current_date_time() - window.qlbl_cur_date_time.setText("%s %s" % (str_cur_date, str_cur_time)) - window.qlbl_update_counter.setText("%i" % qdev_ard.update_counter_DAQ) - window.qlbl_DAQ_rate.setText("DAQ: %.1f Hz" % qdev_ard.obtained_DAQ_rate_Hz) - window.qlin_reading_t.setText("%.3f" % state.time) - window.qlin_reading_1.setText("%.4f" % state.reading_1) - - -# ------------------------------------------------------------------------------ -# update_chart -# ------------------------------------------------------------------------------ - + @QtCore.pyqtSlot() + def update_GUI(self): + str_cur_date, str_cur_time, _ = get_current_date_time() + self.qlbl_cur_date_time.setText( + "%s %s" % (str_cur_date, str_cur_time) + ) + self.qlbl_update_counter.setText("%i" % qdev_ard.update_counter_DAQ) + self.qlbl_DAQ_rate.setText( + "DAQ: %.1f Hz" % qdev_ard.obtained_DAQ_rate_Hz + ) + self.qlin_reading_t.setText("%.3f" % state.time) + self.qlin_reading_1.setText("%.4f" % state.reading_1) -@QtCore.pyqtSlot() -def update_chart(): - if DEBUG: - tprint("update_curve") + @QtCore.pyqtSlot() + def update_chart(self): + if DEBUG: + tprint("update_curve") - window.CH_1.update_curve() + self.history_chart_curve.update() # ------------------------------------------------------------------------------ @@ -430,8 +424,8 @@ def DAQ_function(): if use_PC_time: state.time = time.perf_counter() - # Add readings to chart histories - window.CH_1.add_new_reading(state.time, state.reading_1) + # Add readings to chart history + window.history_chart_curve.append_data(state.time, state.reading_1) # Logging to file if file_logger.starting: @@ -515,14 +509,14 @@ def DAQ_function(): qdev_ard.create_worker_DAQ( DAQ_function = DAQ_function, DAQ_interval_ms = DAQ_INTERVAL_MS, - critical_not_alive_count = 3, + critical_not_alive_count = 1, debug = DEBUG, ) # fmt: on qdev_ard.create_worker_jobs(debug=DEBUG) # Connect signals to slots - qdev_ard.signal_DAQ_updated.connect(update_GUI) + qdev_ard.signal_DAQ_updated.connect(window.update_GUI) qdev_ard.signal_connection_lost.connect(notify_connection_lost) # Start workers @@ -534,7 +528,7 @@ def DAQ_function(): timer_chart = QtCore.QTimer() # timer_chart.setTimerType(QtCore.Qt.PreciseTimer) - timer_chart.timeout.connect(update_chart) + timer_chart.timeout.connect(window.update_chart) timer_chart.start(CHART_INTERVAL_MS) # -------------------------------------------------------------------------- diff --git a/demo_B_GUI_minimal.py b/demo_B_GUI_minimal.py index 05c8c31..7b10593 100644 --- a/demo_B_GUI_minimal.py +++ b/demo_B_GUI_minimal.py @@ -6,8 +6,8 @@ __author__ = "Dennis van Gils" __authoremail__ = "vangils.dennis@gmail.com" __url__ = "https://github.com/Dennis-van-Gils/DvG_Arduino_PyQt_multithread_demo" -__date__ = "24-07-2020" -__version__ = "5.0" +__date__ = "30-07-2020" +__version__ = "6.0" # pylint: disable=bare-except, broad-except import os @@ -23,11 +23,10 @@ import pyqtgraph as pg from dvg_debug_functions import dprint, print_fancy_traceback as pft +from dvg_pyqtgraph_threadsafe import HistoryChartCurve from dvg_devices.Arduino_protocol_serial import Arduino from dvg_qdeviceio import QDeviceIO -from DvG_pyqt_ChartHistory import ChartHistory - try: import OpenGL.GL as gl # pylint: disable=unused-import except: @@ -98,7 +97,7 @@ def __init__(self, parent=None, **kwargs): self.setGeometry(350, 50, 800, 660) self.setWindowTitle("Arduino & PyQt multithread demo") - # Create PlotItem + # GraphicsWindow self.gw_chart = pg.GraphicsWindow() self.gw_chart.setBackground([20, 20, 20]) self.pi_chart = self.gw_chart.addPlot() @@ -113,10 +112,12 @@ def __init__(self, parent=None, **kwargs): disableAutoRange=True, ) - # Create ChartHistory and PlotDataItem and link them together - PEN_01 = pg.mkPen(color=[255, 255, 90], width=3) - num_samples = round(CHART_HISTORY_TIME * 1e3 / DAQ_INTERVAL_MS) - self.CH_1 = ChartHistory(num_samples, self.pi_chart.plot(pen=PEN_01)) + self.history_chart_curve = HistoryChartCurve( + capacity=round(CHART_HISTORY_TIME * 1e3 / DAQ_INTERVAL_MS), + linked_curve=self.pi_chart.plot( + pen=pg.mkPen(color=[255, 255, 90], width=3) + ), + ) vbox = QtWid.QVBoxLayout(self) vbox.addWidget(self.gw_chart, 1) @@ -175,8 +176,8 @@ def DAQ_function(): if use_PC_time: state.time = time.perf_counter() - # Add readings to chart histories - window.CH_1.add_new_reading(state.time, state.reading_1) + # Add readings to chart history + window.history_chart_curve.append_data(state.time, state.reading_1) return True @@ -234,7 +235,7 @@ def DAQ_function(): qdev_ard.create_worker_DAQ( DAQ_function = DAQ_function, DAQ_interval_ms = DAQ_INTERVAL_MS, - critical_not_alive_count = 3, + critical_not_alive_count = 1, debug = DEBUG, ) # fmt: on @@ -247,7 +248,7 @@ def DAQ_function(): # -------------------------------------------------------------------------- timer_chart = QtCore.QTimer() - timer_chart.timeout.connect(window.CH_1.update_curve) + timer_chart.timeout.connect(window.history_chart_curve.update) timer_chart.start(CHART_INTERVAL_MS) # -------------------------------------------------------------------------- diff --git a/demo_C_singlethread_for_comparison.py b/demo_C_singlethread_for_comparison.py index ce287a6..f05955a 100644 --- a/demo_C_singlethread_for_comparison.py +++ b/demo_C_singlethread_for_comparison.py @@ -12,8 +12,8 @@ __author__ = "Dennis van Gils" __authoremail__ = "vangils.dennis@gmail.com" __url__ = "https://github.com/Dennis-van-Gils/DvG_Arduino_PyQt_multithread_demo" -__date__ = "24-07-2020" -__version__ = "5.0" +__date__ = "30-07-2020" +__version__ = "6.0" # pylint: disable=bare-except, broad-except import os @@ -29,10 +29,10 @@ import pyqtgraph as pg from dvg_debug_functions import tprint, dprint, print_fancy_traceback as pft +from dvg_pyqtgraph_threadsafe import HistoryChartCurve from dvg_devices.Arduino_protocol_serial import Arduino from DvG_pyqt_FileLogger import FileLogger -from DvG_pyqt_ChartHistory import ChartHistory from DvG_pyqt_controls import create_Toggle_button, SS_GROUP try: @@ -158,7 +158,7 @@ def __init__(self, parent=None, **kwargs): # Bottom frame # ------------------------- - # Create PlotItem + # GraphicsWindow self.gw_chart = pg.GraphicsWindow() self.gw_chart.setBackground([20, 20, 20]) self.pi_chart = self.gw_chart.addPlot() @@ -173,10 +173,12 @@ def __init__(self, parent=None, **kwargs): disableAutoRange=True, ) - # Create ChartHistory and PlotDataItem and link them together - PEN_01 = pg.mkPen(color=[255, 255, 90], width=3) - num_samples = round(CHART_HISTORY_TIME * 1e3 / DAQ_INTERVAL_MS) - self.CH_1 = ChartHistory(num_samples, self.pi_chart.plot(pen=PEN_01)) + self.history_chart_curve = HistoryChartCurve( + capacity=round(CHART_HISTORY_TIME * 1e3 / DAQ_INTERVAL_MS), + linked_curve=self.pi_chart.plot( + pen=pg.mkPen(color=[255, 255, 90], width=3) + ), + ) # 'Readings' p = {"readOnly": True} @@ -264,7 +266,7 @@ def process_qpbt_clear_chart(self): ) if reply == QtWid.QMessageBox.Yes: - self.CH_1.clear() + self.history_chart_curve.clear() @QtCore.pyqtSlot() def process_qpbt_record(self): @@ -285,33 +287,23 @@ def process_qpbt_wave_square(self): def process_qpbt_wave_sawtooth(self): ard.write("sawtooth") + @QtCore.pyqtSlot() + def update_GUI(self): + str_cur_date, str_cur_time, _ = get_current_date_time() + self.qlbl_cur_date_time.setText( + "%s %s" % (str_cur_date, str_cur_time) + ) + self.qlbl_update_counter.setText("%i" % state.update_counter_DAQ) + self.qlbl_DAQ_rate.setText("DAQ: %.1f Hz" % state.obtained_DAQ_rate_Hz) + self.qlin_reading_t.setText("%.3f" % state.time) + self.qlin_reading_1.setText("%.4f" % state.reading_1) -# ------------------------------------------------------------------------------ -# update_GUI -# ------------------------------------------------------------------------------ - - -@QtCore.pyqtSlot() -def update_GUI(): - str_cur_date, str_cur_time, _ = get_current_date_time() - window.qlbl_cur_date_time.setText("%s %s" % (str_cur_date, str_cur_time)) - window.qlbl_update_counter.setText("%i" % state.update_counter_DAQ) - window.qlbl_DAQ_rate.setText("DAQ: %.1f Hz" % state.obtained_DAQ_rate_Hz) - window.qlin_reading_t.setText("%.3f" % state.time) - window.qlin_reading_1.setText("%.4f" % state.reading_1) - - -# ------------------------------------------------------------------------------ -# update_chart -# ------------------------------------------------------------------------------ - - -@QtCore.pyqtSlot() -def update_chart(): - if DEBUG: - tprint("update_curve") + @QtCore.pyqtSlot() + def update_chart(self): + if DEBUG: + tprint("update_curve") - window.CH_1.update_curve() + self.history_chart_curve.update() # ------------------------------------------------------------------------------ @@ -368,6 +360,7 @@ def DAQ_function(): "'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time) ) + sys.exit(0) # Parse readings into separate state variables try: @@ -379,6 +372,7 @@ def DAQ_function(): "'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time) ) + sys.exit(0) # Use Arduino time or PC time? use_PC_time = True @@ -386,7 +380,7 @@ def DAQ_function(): state.time = time.perf_counter() # Add readings to chart histories - window.CH_1.add_new_reading(state.time, state.reading_1) + window.history_chart_curve.append_data(state.time, state.reading_1) # Logging to file if file_logger.starting: @@ -404,7 +398,7 @@ def DAQ_function(): file_logger.write("%.3f\t%.4f\n" % (log_elapsed_time, state.reading_1)) # We update the GUI right now because this is a singlethread demo - update_GUI() + window.update_GUI() # ------------------------------------------------------------------------------ @@ -464,7 +458,7 @@ def DAQ_function(): timer_chart = QtCore.QTimer() # timer_chart.setTimerType(QtCore.Qt.PreciseTimer) - timer_chart.timeout.connect(update_chart) + timer_chart.timeout.connect(window.update_chart) timer_chart.start(CHART_INTERVAL_MS) # -------------------------------------------------------------------------- diff --git a/demo_D_no_GUI.py b/demo_D_no_GUI.py index 0f6d6d9..6889747 100644 --- a/demo_D_no_GUI.py +++ b/demo_D_no_GUI.py @@ -7,8 +7,8 @@ __author__ = "Dennis van Gils" __authoremail__ = "vangils.dennis@gmail.com" __url__ = "https://github.com/Dennis-van-Gils/DvG_Arduino_PyQt_multithread_demo" -__date__ = "24-07-2020" -__version__ = "5.0" +__date__ = "30-07-2020" +__version__ = "6.0" # pylint: disable=bare-except, broad-except import os @@ -172,7 +172,7 @@ def DAQ_function(): qdev_ard.create_worker_DAQ( DAQ_function = DAQ_function, DAQ_interval_ms = DAQ_INTERVAL_MS, - critical_not_alive_count = 3, + critical_not_alive_count = 1, debug = DEBUG, ) # fmt: on diff --git a/demo_E_no_GUI.py b/demo_E_no_GUI.py index 4fc6089..ff54c91 100644 --- a/demo_E_no_GUI.py +++ b/demo_E_no_GUI.py @@ -8,8 +8,8 @@ __author__ = "Dennis van Gils" __authoremail__ = "vangils.dennis@gmail.com" __url__ = "https://github.com/Dennis-van-Gils/DvG_Arduino_PyQt_multithread_demo" -__date__ = "24-07-2020" -__version__ = "5.0" +__date__ = "30-07-2020" +__version__ = "6.0" # pylint: disable=bare-except, broad-except import os @@ -192,7 +192,7 @@ def tick(self): DAQ_trigger = DAQ_TRIGGER.INTERNAL_TIMER, DAQ_function = sync.tick, DAQ_interval_ms = DAQ_INTERVAL_MS, - critical_not_alive_count = 3, + critical_not_alive_count = 1, debug = DEBUG, ) # fmt: on diff --git a/list_conda.txt b/list_conda.txt index a2bcd5e..812bb61 100644 --- a/list_conda.txt +++ b/list_conda.txt @@ -28,6 +28,7 @@ docutils 0.16 py38_1 dvg-debug-functions 2.1.1 pypi_0 pypi dvg-devices 0.1.0 pypi_0 pypi dvg-pid-controller 2.0.0 pypi_0 pypi +dvg-pyqtgraph-threadsafe 1.0.0 pypi_0 pypi dvg-qdeviceio 0.3.0 pypi_0 pypi dvg-ringbuffer 1.0.1 pypi_0 pypi fftw 3.3.8 nompi_h24e91a8_1111 conda-forge diff --git a/list_pip.txt b/list_pip.txt index 4dfbdfe..460bcdb 100644 --- a/list_pip.txt +++ b/list_pip.txt @@ -25,6 +25,7 @@ docutils 0.16 dvg-debug-functions 2.1.1 dvg-devices 0.1.0 dvg-pid-controller 2.0.0 +dvg-pyqtgraph-threadsafe 1.0.0 dvg-qdeviceio 0.3.0 dvg-ringbuffer 1.0.1 future 0.18.2 diff --git a/requirements.txt b/requirements.txt index 49afee4..26e1b5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,8 +2,9 @@ psutil~=5.6 pyserial~=3.4 numpy~=1.15 pyqt5~=5.12 -pyqtgraph~=0.10 +pyqtgraph~=0.11 dvg-debug-functions==2.1.1 dvg-qdeviceio==0.3.0 dvg-devices==0.1.0 +dvg-pyqtgraph-threadsafe==1.0.0