From 98c0c64e8577f7e40eb0324dfe97d0ae4670c3a2 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Mon, 28 Aug 2023 14:07:07 +0200 Subject: [PATCH 01/11] feat: oneplot initialized as an example app for plotting motor vs monitor signals + dispatcher loop over msg --- bec_widgets/bec_dispatcher.py | 13 ++- bec_widgets/examples/oneplot/oneplot.py | 127 ++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 bec_widgets/examples/oneplot/oneplot.py diff --git a/bec_widgets/bec_dispatcher.py b/bec_widgets/bec_dispatcher.py index c97d085b..fbf5db7d 100644 --- a/bec_widgets/bec_dispatcher.py +++ b/bec_widgets/bec_dispatcher.py @@ -21,8 +21,7 @@ class _BECDap: # Adding a new pyqt signal requres a class factory, as they must be part of the class definition # and cannot be dynamically added as class attributes after the class has been defined. _signal_class_factory = ( - type(f"Signal{i}", (QObject,), dict(signal=pyqtSignal("PyQt_PyObject"))) - for i in itertools.count() + type(f"Signal{i}", (QObject,), dict(signal=pyqtSignal(dict, dict))) for i in itertools.count() ) @@ -99,7 +98,10 @@ class _BECDispatcher(QObject): def cb(msg): msg = BECMessage.MessageReader.loads(msg.value) - self._connections[topic].signal.emit(msg) + if not isinstance(msg, list): + msg = [msg] + for msg_i in msg: + self._connections[topic].signal.emit(msg_i.content, msg_i.metadata) consumer = self.client.connector.consumer(topics=topic, cb=cb) consumer.start() @@ -132,7 +134,10 @@ class _BECDispatcher(QObject): def _dap_cb(msg): msg = BECMessage.ProcessedDataMessage.loads(msg.value) - self.new_dap_data.emit(msg.content["data"], msg.metadata) + if not isinstance(msg, list): + msg = [msg] + for i in msg: + self.new_dap_data.emit(i.content["data"], i.metadata) dap_ep = MessageEndpoints.processed_data(dap_name) consumer = self.client.connector.consumer(topics=dap_ep, cb=_dap_cb) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py new file mode 100644 index 00000000..4528f215 --- /dev/null +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -0,0 +1,127 @@ +from PyQt5.QtCore import QThread, pyqtSignal +from PyQt5.QtWidgets import QApplication, QWidget +from bec_lib.core import MessageEndpoints, BECMessage +from pyqtgraph.Qt import QtWidgets, uic +from PyQt5.QtCore import pyqtSignal, Qt + +from threading import RLock + +import os + +import numpy as np +from enum import Enum +import pyqtgraph as pg +from PyQt5 import QtGui +from PyQt5.QtCore import QThread, pyqtSlot +from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtWidgets import QApplication, QWidget + + +class PlotApp(QWidget): + def __init__(self): + super().__init__() + self.motor_data = None + self.monitor_data = None + current_path = os.path.dirname(__file__) + uic.loadUi(os.path.join(current_path, "oneplot.ui"), self) + + self.init_ui() + + def init_ui(self): + self.plot = pg.PlotItem() + self.glw.addItem(self.plot) + self.plot.setLabel("bottom", "Motor") + self.plot.setLabel("left", "Monitor") + + def on_dap_update(self, msg, metadata): + ... + # print("on_dap_update") + # print(f'msg "on_dap_update" = {msg}') + # self.dap_x = msg["gaussian_fit_worker_3"]["x"] + # print(f"self.dap_x = {self.dap_x}") + # print(metadata) + + def on_scan_segment(self, msg, metadata): + # TODO x -> motor + # TODO y -> monitor._hints :list + print("on_scan_segment") + # scanMSG = BECMessage.ScanMessage.loads(msg.value) + self.motor_data = msg["samx"]["samx"]["value"] + self.monitor_data = msg["gauss_bpm"]["gauss_bpm"][ + "value" + ] # gaussbpm._hints -> implement logic with list + + # self.scan_x = + # scanMSG = msg.content["data"] + + # self.data_x = BECMessage.ScanMessage.loads(msg) + + # self.data_y = BECMessage.ScanMessage.loads(msg) + + # print(msg) + print(f'msg "on_scan_segment" = {msg}') + + def on_new_scan(self, msg, metadata): + ... + # print("on_new_scan") + # print(f'msg "on_new_scan" = {msg}') + # print(metadata) + + +# class Controller(QThread): +# new_scan = pyqtSignal(dict, dict) +# scan_segment = pyqtSignal(dict, dict) +# new_dap_data = pyqtSignal(dict, dict) +# +# def __init__(self): +# super().__init__() +# self.scan_lock = RLock() +# +# def _scan_segment_cb(msg, parent, **_kwargs): +# msg = BECMessage.ScanMessage.loads(msg.value) +# for i in msg: +# with parent.scan_lock: +# # TODO: use ScanStatusMessage instead? +# scan_id = msg.content["scanID"] +# if parent._scan_id != scan_id: +# parent._scan_id = scan_id +# parent.new_scan.emit(msg.content, msg.metadata) +# parent.scan_segment.emit(msg.content, msg.metadata) +# +# scan_segment_topic = MessageEndpoints.scan_segment() +# parent._scan_segment_thread = parent.client.connector.consumer( +# topics=scan_segment_topic, +# cb=_scan_segment_cb, +# ) +# parent._scan_segment_thread.start() +# +# @staticmethod +# def _scan_segment_callback(msg, *, parent, **_kwargs) -> None: +# scanMSG = BECMessage.ScanMessage.loads(msg.value) +# self.data_x + +if __name__ == "__main__": + # from bec_lib import BECClient + from bec_widgets import ctrl_c + from bec_widgets.bec_dispatcher import bec_dispatcher + + client = bec_dispatcher.client + client.start() + + dev = client.device_manager.devices + scans = client.scans + queue = client.queue + + app = QApplication([]) + + plotApp = PlotApp() + + bec_dispatcher.connect_dap_slot(plotApp.on_dap_update, "gaussian_fit_worker_3") + bec_dispatcher.connect_slot(plotApp.on_scan_segment, MessageEndpoints.scan_segment()) + bec_dispatcher.new_scan.connect(plotApp.on_new_scan) # TODO check if works! + # bec_dispatcher.connect_slot(plotApp.on_new_scan,) + ctrl_c.setup(app) + + window = plotApp + window.show() + app.exec_() From ff545bf5c9e707f2dd9b43f9d059aa8605f3916b Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:12:57 +0200 Subject: [PATCH 02/11] feat: oneplot can receive one motor and one monitor signal --- bec_widgets/examples/oneplot/oneplot.py | 68 +++++++++++++++++++------ 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py index 4528f215..17fdbeee 100644 --- a/bec_widgets/examples/oneplot/oneplot.py +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -15,24 +15,60 @@ from PyQt5 import QtGui from PyQt5.QtCore import QThread, pyqtSlot from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QApplication, QWidget +from pyqtgraph import mkPen +from pyqtgraph.Qt import QtCore +from pyqtgraph import mkBrush, mkColor, mkPen class PlotApp(QWidget): + update_plot = pyqtSignal() + init_plot = pyqtSignal() + def __init__(self): super().__init__() - self.motor_data = None - self.monitor_data = None + self.motor_data = [] + self.monitor_data = [] current_path = os.path.dirname(__file__) uic.loadUi(os.path.join(current_path, "oneplot.ui"), self) + self.monitor_names = ["gauss_bpm"] + self.init_ui() + self.init_plot.connect(self.init_curves) + self.update_plot.connect(self.update_curves) + def init_ui(self): self.plot = pg.PlotItem() self.glw.addItem(self.plot) self.plot.setLabel("bottom", "Motor") self.plot.setLabel("left", "Monitor") + def init_curves(self): + self.plot.clear() + + self.curves = [] + self.pens = [] + self.brushs = [] + + color_list = ["#384c6b", "#e28a2b", "#5E3023", "#e41a1c", "#984e83", "#4daf4a"] + + for ii, monitor in enumerate(self.monitor_names): + pen = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) + brush = mkBrush(color=color_list[ii]) + curve = pg.PlotDataItem(symbolBrush=brush, pen=pen, skipFiniteCheck=True, name=monitor) + self.plot.addItem(curve) + self.curves.append(curve) + self.pens.append(pen) + self.brushs.append(brush) + + self.plot.addLegend() + # TODO hook signals + # TODO hook crosshair + + def update_curves(self): + self.curves[0].setData(self.motor_data, self.monitor_data) + def on_dap_update(self, msg, metadata): ... # print("on_dap_update") @@ -46,25 +82,25 @@ class PlotApp(QWidget): # TODO y -> monitor._hints :list print("on_scan_segment") # scanMSG = BECMessage.ScanMessage.loads(msg.value) - self.motor_data = msg["samx"]["samx"]["value"] - self.monitor_data = msg["gauss_bpm"]["gauss_bpm"][ + # message = msg + # print(f"message = {message}") + motor_data = msg["data"]["samx"]["samx"]["value"] + monitor_data = msg["data"]["gauss_bpm"]["gauss_bpm"][ "value" ] # gaussbpm._hints -> implement logic with list + # + self.motor_data.append(motor_data) + self.monitor_data.append(monitor_data) - # self.scan_x = - # scanMSG = msg.content["data"] - - # self.data_x = BECMessage.ScanMessage.loads(msg) - - # self.data_y = BECMessage.ScanMessage.loads(msg) - - # print(msg) - print(f'msg "on_scan_segment" = {msg}') + self.update_plot.emit() def on_new_scan(self, msg, metadata): - ... - # print("on_new_scan") - # print(f'msg "on_new_scan" = {msg}') + print(30 * "#" + "NEW SCAN" + 30 * "#") + + self.motor_data = [msg["data"]["samx"]["samx"]["value"]] + self.monitor_data = [msg["data"]["gauss_bpm"]["gauss_bpm"]["value"]] + self.init_curves() + print(f'msg "on_new_scan" = {msg}') # print(metadata) From fc4b54239eaf3eb9608fb5f916ce61df5830f7c6 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:05:01 +0200 Subject: [PATCH 03/11] refactor: plot update via proxy --- bec_widgets/examples/oneplot/oneplot.py | 154 +++++++++++++----------- 1 file changed, 84 insertions(+), 70 deletions(-) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py index 17fdbeee..bf26f13f 100644 --- a/bec_widgets/examples/oneplot/oneplot.py +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -1,89 +1,125 @@ -from PyQt5.QtCore import QThread, pyqtSignal -from PyQt5.QtWidgets import QApplication, QWidget -from bec_lib.core import MessageEndpoints, BECMessage -from pyqtgraph.Qt import QtWidgets, uic -from PyQt5.QtCore import pyqtSignal, Qt - -from threading import RLock - +from bec_lib.core import MessageEndpoints import os -import numpy as np -from enum import Enum import pyqtgraph as pg -from PyQt5 import QtGui -from PyQt5.QtCore import QThread, pyqtSlot -from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QApplication, QWidget -from pyqtgraph import mkPen +from pyqtgraph import mkBrush, mkPen from pyqtgraph.Qt import QtCore -from pyqtgraph import mkBrush, mkColor, mkPen +from pyqtgraph.Qt import uic + +from bec_lib.core import MessageEndpoints + +# TODO implement: +# - add crosshair +# - add crosshair table +# - implement scanID database for visualizing previous scans +# - multiple signals for different monitors +# - user can choose what motor against what monitor to plot class PlotApp(QWidget): update_plot = pyqtSignal() + # update_scatters = pyqtSignal() init_plot = pyqtSignal() + update_signal = pyqtSignal() def __init__(self): super().__init__() + self.scanID = None self.motor_data = [] self.monitor_data = [] + + self.dap_x = [] + self.dap_y = [] + current_path = os.path.dirname(__file__) uic.loadUi(os.path.join(current_path, "oneplot.ui"), self) self.monitor_names = ["gauss_bpm"] self.init_ui() + self.init_curves() - self.init_plot.connect(self.init_curves) - self.update_plot.connect(self.update_curves) + self.proxy_update = pg.SignalProxy(self.update_signal, rateLimit=25, slot=self.update) def init_ui(self): self.plot = pg.PlotItem() self.glw.addItem(self.plot) self.plot.setLabel("bottom", "Motor") self.plot.setLabel("left", "Monitor") + self.plot.addLegend() def init_curves(self): self.plot.clear() self.curves = [] + self.scatters = [] self.pens = [] - self.brushs = [] + self.brushs = [] # todo check if needed color_list = ["#384c6b", "#e28a2b", "#5E3023", "#e41a1c", "#984e83", "#4daf4a"] for ii, monitor in enumerate(self.monitor_names): + print(self.monitor_names[ii]) pen = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) - brush = mkBrush(color=color_list[ii]) - curve = pg.PlotDataItem(symbolBrush=brush, pen=pen, skipFiniteCheck=True, name=monitor) - self.plot.addItem(curve) + # brush = mkBrush(color=color_list[ii]) + curve = pg.PlotDataItem( + pen=pen, skipFiniteCheck=True, name=monitor + " fit" + ) # ,symbolBrush=brush) + scatter = pg.ScatterPlotItem(pen=pen, size=10, name=monitor) # ,brush=brush,) self.curves.append(curve) + self.scatters.append(scatter) self.pens.append(pen) - self.brushs.append(brush) + # self.brushs.append(brush) + self.plot.addItem(curve) + self.plot.addItem(scatter) - self.plot.addLegend() + # self.plot.addLegend() # TODO check if needed # TODO hook signals # TODO hook crosshair - def update_curves(self): + def update(self): self.curves[0].setData(self.motor_data, self.monitor_data) + self.scatters[0].setData(self.motor_data, self.monitor_data) - def on_dap_update(self, msg, metadata): + @pyqtSlot(dict, dict) + def on_dap_update(self, msg, metadata) -> None: + """ + Getting processed data from DAP + + Args: + msg (dict): + metadata(dict): + """ ... - # print("on_dap_update") + print("on_dap_update") # print(f'msg "on_dap_update" = {msg}') - # self.dap_x = msg["gaussian_fit_worker_3"]["x"] - # print(f"self.dap_x = {self.dap_x}") + dap_x = msg["gaussian_fit_worker_3"]["x"] + dap_y = msg["gaussian_fit_worker_3"]["y"] + + self.dap_x.append(dap_x) + self.dap_y.append(dap_y) + # self.update_signal.emit() # print(metadata) + @pyqtSlot(dict, dict) def on_scan_segment(self, msg, metadata): # TODO x -> motor # TODO y -> monitor._hints :list print("on_scan_segment") - # scanMSG = BECMessage.ScanMessage.loads(msg.value) - # message = msg - # print(f"message = {message}") + + current_scanID = msg["scanID"] + print(f"current_scanID = {current_scanID}") + + # implement if condition that if scan id is different than last one init new scan variables + if current_scanID != self.scanID: + self.scanID = current_scanID + self.motor_data = [] + self.monitor_data = [] + self.init_curves() + motor_data = msg["data"]["samx"]["samx"]["value"] monitor_data = msg["data"]["gauss_bpm"]["gauss_bpm"][ "value" @@ -92,49 +128,27 @@ class PlotApp(QWidget): self.motor_data.append(motor_data) self.monitor_data.append(monitor_data) - self.update_plot.emit() + # self.update_plot.emit() + self.update_signal.emit() - def on_new_scan(self, msg, metadata): - print(30 * "#" + "NEW SCAN" + 30 * "#") + @pyqtSlot(dict, dict) + def on_new_scan(self, msg, metadata): # TODO probably not needed + """ + Initiate new scan and clear previous data + Args: + msg(dict): + metadata(dict): - self.motor_data = [msg["data"]["samx"]["samx"]["value"]] - self.monitor_data = [msg["data"]["gauss_bpm"]["gauss_bpm"]["value"]] - self.init_curves() - print(f'msg "on_new_scan" = {msg}') - # print(metadata) + Returns: + """ + + print(40 * "#" + "on_new_scan" + 40 * "#") + + # self.motor_data = [msg["data"]["samx"]["samx"]["value"]] + # self.monitor_data = [msg["data"]["gauss_bpm"]["gauss_bpm"]["value"]] + # self.init_curves() -# class Controller(QThread): -# new_scan = pyqtSignal(dict, dict) -# scan_segment = pyqtSignal(dict, dict) -# new_dap_data = pyqtSignal(dict, dict) -# -# def __init__(self): -# super().__init__() -# self.scan_lock = RLock() -# -# def _scan_segment_cb(msg, parent, **_kwargs): -# msg = BECMessage.ScanMessage.loads(msg.value) -# for i in msg: -# with parent.scan_lock: -# # TODO: use ScanStatusMessage instead? -# scan_id = msg.content["scanID"] -# if parent._scan_id != scan_id: -# parent._scan_id = scan_id -# parent.new_scan.emit(msg.content, msg.metadata) -# parent.scan_segment.emit(msg.content, msg.metadata) -# -# scan_segment_topic = MessageEndpoints.scan_segment() -# parent._scan_segment_thread = parent.client.connector.consumer( -# topics=scan_segment_topic, -# cb=_scan_segment_cb, -# ) -# parent._scan_segment_thread.start() -# -# @staticmethod -# def _scan_segment_callback(msg, *, parent, **_kwargs) -> None: -# scanMSG = BECMessage.ScanMessage.loads(msg.value) -# self.data_x if __name__ == "__main__": # from bec_lib import BECClient From 118f6af2b97188398a3dd0e2121f73328c53465b Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:22:53 +0200 Subject: [PATCH 04/11] feat: dap fit plotted as curve, data as scatter --- bec_widgets/examples/oneplot/oneplot.py | 51 +++++++++++++------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py index bf26f13f..eb9dff67 100644 --- a/bec_widgets/examples/oneplot/oneplot.py +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -1,3 +1,5 @@ +import numpy as np + from bec_lib.core import MessageEndpoints import os @@ -31,8 +33,8 @@ class PlotApp(QWidget): self.motor_data = [] self.monitor_data = [] - self.dap_x = [] - self.dap_y = [] + self.dap_x = np.array([]) + self.dap_y = np.array([]) current_path = os.path.dirname(__file__) uic.loadUi(os.path.join(current_path, "oneplot.ui"), self) @@ -81,7 +83,7 @@ class PlotApp(QWidget): # TODO hook crosshair def update(self): - self.curves[0].setData(self.motor_data, self.monitor_data) + self.curves[0].setData(self.dap_x, self.dap_y) self.scatters[0].setData(self.motor_data, self.monitor_data) @pyqtSlot(dict, dict) @@ -96,13 +98,12 @@ class PlotApp(QWidget): ... print("on_dap_update") # print(f'msg "on_dap_update" = {msg}') - dap_x = msg["gaussian_fit_worker_3"]["x"] - dap_y = msg["gaussian_fit_worker_3"]["y"] - self.dap_x.append(dap_x) - self.dap_y.append(dap_y) - # self.update_signal.emit() - # print(metadata) + dapMSG = msg + metaMSG = metadata + + self.dap_x = msg["gaussian_fit_worker_3"]["x"] + self.dap_y = msg["gaussian_fit_worker_3"]["y"] @pyqtSlot(dict, dict) def on_scan_segment(self, msg, metadata): @@ -111,7 +112,7 @@ class PlotApp(QWidget): print("on_scan_segment") current_scanID = msg["scanID"] - print(f"current_scanID = {current_scanID}") + # print(f"current_scanID = {current_scanID}") # implement if condition that if scan id is different than last one init new scan variables if current_scanID != self.scanID: @@ -131,23 +132,23 @@ class PlotApp(QWidget): # self.update_plot.emit() self.update_signal.emit() - @pyqtSlot(dict, dict) - def on_new_scan(self, msg, metadata): # TODO probably not needed - """ - Initiate new scan and clear previous data - Args: - msg(dict): - metadata(dict): + # @pyqtSlot(dict, dict) + # def on_new_scan(self, msg, metadata): # TODO probably not needed + # """ + # Initiate new scan and clear previous data + # Args: + # msg(dict): + # metadata(dict): + # + # Returns: + # + # """ - Returns: + # print(40 * "#" + "on_new_scan" + 40 * "#") - """ - - print(40 * "#" + "on_new_scan" + 40 * "#") - - # self.motor_data = [msg["data"]["samx"]["samx"]["value"]] - # self.monitor_data = [msg["data"]["gauss_bpm"]["gauss_bpm"]["value"]] - # self.init_curves() + # self.motor_data = [msg["data"]["samx"]["samx"]["value"]] + # self.monitor_data = [msg["data"]["gauss_bpm"]["gauss_bpm"]["value"]] + # self.init_curves() if __name__ == "__main__": From 223f102aa9f0e625fecef37c827c55f9062330d7 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:25:25 +0200 Subject: [PATCH 05/11] feat: crosshair snaps to data, but it is activated with button due to debug --- bec_widgets/examples/oneplot/oneplot.py | 41 +++++++++++++++++-------- bec_widgets/qt_utils/crosshair.py | 3 +- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py index eb9dff67..8dd4028f 100644 --- a/bec_widgets/examples/oneplot/oneplot.py +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -1,16 +1,14 @@ +import os import numpy as np -from bec_lib.core import MessageEndpoints -import os - import pyqtgraph as pg -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtCore import pyqtSlot +from PyQt5.QtCore import pyqtSignal, pyqtSlot from PyQt5.QtWidgets import QApplication, QWidget +from PyQt5.QtWidgets import QTableWidgetItem from pyqtgraph import mkBrush, mkPen -from pyqtgraph.Qt import QtCore -from pyqtgraph.Qt import uic +from pyqtgraph.Qt import QtCore, uic +from bec_widgets.qt_utils import Crosshair from bec_lib.core import MessageEndpoints # TODO implement: @@ -22,9 +20,6 @@ from bec_lib.core import MessageEndpoints class PlotApp(QWidget): - update_plot = pyqtSignal() - # update_scatters = pyqtSignal() - init_plot = pyqtSignal() update_signal = pyqtSignal() def __init__(self): @@ -44,6 +39,8 @@ class PlotApp(QWidget): self.init_ui() self.init_curves() + self.pushButton_hook.clicked.connect(self.hook_crosshair) + self.proxy_update = pg.SignalProxy(self.update_signal, rateLimit=25, slot=self.update) def init_ui(self): @@ -70,7 +67,10 @@ class PlotApp(QWidget): curve = pg.PlotDataItem( pen=pen, skipFiniteCheck=True, name=monitor + " fit" ) # ,symbolBrush=brush) - scatter = pg.ScatterPlotItem(pen=pen, size=10, name=monitor) # ,brush=brush,) + scatter = pg.ScatterPlotItem(pen=pen, size=5, name=monitor) # ,brush=brush,) + # scatter = pg.PlotDataItem( + # pen=None, symbol="o", symbolBrush=color_list[ii], name=monitor + # ) self.curves.append(curve) self.scatters.append(scatter) self.pens.append(pen) @@ -78,9 +78,24 @@ class PlotApp(QWidget): self.plot.addItem(curve) self.plot.addItem(scatter) - # self.plot.addLegend() # TODO check if needed # TODO hook signals # TODO hook crosshair + # self.hook_crosshair() + + def hook_crosshair(self): + self.crosshair_1d = Crosshair(self.plot, precision=10) + self.crosshair_1d.coordinatesChanged1D.connect( + lambda x, y: self.update_table(self.tableWidget_crosshair, x, y, column=0) + ) + self.crosshair_1d.coordinatesChanged1D.connect( + lambda x, y: print(f"crosshair 1d x = {x}, y = {y}") + ) + + def update_table(self, table_widget, x, y_values, column): + """Update the table with the new coordinates""" + for i, y in enumerate(y_values): + table_widget.setItem(i, column, QTableWidgetItem(f"({x}, {y})")) + table_widget.resizeColumnsToContents() def update(self): self.curves[0].setData(self.dap_x, self.dap_y) @@ -169,7 +184,7 @@ if __name__ == "__main__": bec_dispatcher.connect_dap_slot(plotApp.on_dap_update, "gaussian_fit_worker_3") bec_dispatcher.connect_slot(plotApp.on_scan_segment, MessageEndpoints.scan_segment()) - bec_dispatcher.new_scan.connect(plotApp.on_new_scan) # TODO check if works! + # bec_dispatcher.new_scan.connect(plotApp.on_new_scan) # TODO check if works! # bec_dispatcher.connect_slot(plotApp.on_new_scan,) ctrl_c.setup(app) diff --git a/bec_widgets/qt_utils/crosshair.py b/bec_widgets/qt_utils/crosshair.py index 82c80adf..768f8108 100644 --- a/bec_widgets/qt_utils/crosshair.py +++ b/bec_widgets/qt_utils/crosshair.py @@ -109,7 +109,8 @@ class Crosshair(QObject): if y_values_1d: if all(v is None for v in x_values_1d) or all(v is None for v in y_values_1d): return None, None - return x, y_values_1d + closest_x = min(x_values_1d, key=lambda xi: abs(xi - x)) # Snap x to closest data point + return closest_x, y_values_1d # Handle 2D plot if image_2d is not None: From 2ed5d7208c42f8a1175a49236d706ebf503875e4 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:47:24 +0200 Subject: [PATCH 06/11] fix: crosshair snaps correctly to x dataset --- bec_widgets/qt_utils/crosshair.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/bec_widgets/qt_utils/crosshair.py b/bec_widgets/qt_utils/crosshair.py index 768f8108..72cfe3a5 100644 --- a/bec_widgets/qt_utils/crosshair.py +++ b/bec_widgets/qt_utils/crosshair.py @@ -63,9 +63,19 @@ class Crosshair(QObject): size=10, pen=pg.mkPen(None), brush=pg.mkBrush(color) ) self.marker_moved_1d.append(marker_moved) - self.marker_clicked_1d.append(marker_clicked) self.plot_item.addItem(marker_moved) - self.plot_item.addItem(marker_clicked) + # Create glowing effect markers for clicked events + marker_clicked_list = [] + for size, alpha in [(18, 64), (14, 128), (10, 255)]: + marker_clicked = pg.ScatterPlotItem( + size=size, + pen=pg.mkPen(None), + brush=pg.mkBrush(color.red(), color.green(), color.blue(), alpha), + ) + marker_clicked_list.append(marker_clicked) + self.plot_item.addItem(marker_clicked) + + self.marker_clicked_1d.append(marker_clicked_list) elif isinstance(item, pg.ImageItem): # 2D plot self.marker_2d = pg.ROI( [0, 0], size=[1, 1], pen=pg.mkPen("r", width=2), movable=False @@ -200,10 +210,11 @@ class Crosshair(QObject): [round(y_val, self.precision) for y_val in y_values], ) for i, y_val in enumerate(y_values): - self.marker_clicked_1d[i].setData( - [x if not self.is_log_x else np.log10(x)], - [y_val if not self.is_log_y else np.log10(y_val)], - ) + for marker in self.marker_clicked_1d[i]: + marker.setData( + [x if not self.is_log_x else np.log10(x)], + [y_val if not self.is_log_y else np.log10(y_val)], + ) elif isinstance(item, pg.ImageItem): if x is None or y_values is None: return From 49ba6feb3a8494336c5772a06e9569d611fc240a Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:56:00 +0200 Subject: [PATCH 07/11] feat: crosshair snapped to x, y data automatically, clicked coordinates glows --- bec_widgets/examples/oneplot/oneplot.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py index 8dd4028f..37fcdb38 100644 --- a/bec_widgets/examples/oneplot/oneplot.py +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -17,6 +17,7 @@ from bec_lib.core import MessageEndpoints # - implement scanID database for visualizing previous scans # - multiple signals for different monitors # - user can choose what motor against what monitor to plot +# - crosshair snaps now just to fit, not to actual data class PlotApp(QWidget): @@ -38,6 +39,7 @@ class PlotApp(QWidget): self.init_ui() self.init_curves() + self.hook_crosshair() self.pushButton_hook.clicked.connect(self.hook_crosshair) @@ -80,15 +82,17 @@ class PlotApp(QWidget): # TODO hook signals # TODO hook crosshair - # self.hook_crosshair() + self.tableWidget_crosshair.setRowCount(len(self.monitor_names)) + self.tableWidget_crosshair.setVerticalHeaderLabels(self.monitor_names) + self.hook_crosshair() def hook_crosshair(self): - self.crosshair_1d = Crosshair(self.plot, precision=10) + self.crosshair_1d = Crosshair(self.plot, precision=3) self.crosshair_1d.coordinatesChanged1D.connect( lambda x, y: self.update_table(self.tableWidget_crosshair, x, y, column=0) ) - self.crosshair_1d.coordinatesChanged1D.connect( - lambda x, y: print(f"crosshair 1d x = {x}, y = {y}") + self.crosshair_1d.coordinatesClicked1D.connect( + lambda x, y: self.update_table(self.tableWidget_crosshair, x, y, column=1) ) def update_table(self, table_widget, x, y_values, column): From 3af57abc4888dfcd0224bf50708488dc8192be84 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Tue, 29 Aug 2023 10:10:03 +0200 Subject: [PATCH 08/11] feat: fit table hardcode to "gaussian_fit_worker_3" --- bec_widgets/examples/oneplot/oneplot.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py index 37fcdb38..3bb34932 100644 --- a/bec_widgets/examples/oneplot/oneplot.py +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -22,6 +22,7 @@ from bec_lib.core import MessageEndpoints class PlotApp(QWidget): update_signal = pyqtSignal() + update_dap_signal = pyqtSignal() def __init__(self): super().__init__() @@ -32,6 +33,8 @@ class PlotApp(QWidget): self.dap_x = np.array([]) self.dap_y = np.array([]) + self.fit = None + current_path = os.path.dirname(__file__) uic.loadUi(os.path.join(current_path, "oneplot.ui"), self) @@ -41,9 +44,12 @@ class PlotApp(QWidget): self.init_curves() self.hook_crosshair() - self.pushButton_hook.clicked.connect(self.hook_crosshair) - - self.proxy_update = pg.SignalProxy(self.update_signal, rateLimit=25, slot=self.update) + self.proxy_update_plot = pg.SignalProxy( + self.update_signal, rateLimit=25, slot=self.update_plot + ) + self.proxy_update_fit = pg.SignalProxy( + self.update_dap_signal, rateLimit=25, slot=self.update_fit_table + ) def init_ui(self): self.plot = pg.PlotItem() @@ -66,9 +72,7 @@ class PlotApp(QWidget): print(self.monitor_names[ii]) pen = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) # brush = mkBrush(color=color_list[ii]) - curve = pg.PlotDataItem( - pen=pen, skipFiniteCheck=True, name=monitor + " fit" - ) # ,symbolBrush=brush) + curve = pg.PlotDataItem(pen=pen, skipFiniteCheck=True) # ,symbolBrush=brush) scatter = pg.ScatterPlotItem(pen=pen, size=5, name=monitor) # ,brush=brush,) # scatter = pg.PlotDataItem( # pen=None, symbol="o", symbolBrush=color_list[ii], name=monitor @@ -101,10 +105,13 @@ class PlotApp(QWidget): table_widget.setItem(i, column, QTableWidgetItem(f"({x}, {y})")) table_widget.resizeColumnsToContents() - def update(self): + def update_plot(self): self.curves[0].setData(self.dap_x, self.dap_y) self.scatters[0].setData(self.motor_data, self.monitor_data) + def update_fit_table(self): + self.tableWidget_fit.setData(self.fit) + @pyqtSlot(dict, dict) def on_dap_update(self, msg, metadata) -> None: """ @@ -124,6 +131,10 @@ class PlotApp(QWidget): self.dap_x = msg["gaussian_fit_worker_3"]["x"] self.dap_y = msg["gaussian_fit_worker_3"]["y"] + self.fit = metadata["fit_parameters"] + + self.update_dap_signal.emit() + @pyqtSlot(dict, dict) def on_scan_segment(self, msg, metadata): # TODO x -> motor From 3344f1b92a7e4f4ecd2e63c66aa01d3a4c325070 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:57:28 +0200 Subject: [PATCH 09/11] feat: user can specify tuple of (x,y) devices which wants to plot --- bec_widgets/examples/oneplot/oneplot.py | 151 +++++++++++++----------- 1 file changed, 79 insertions(+), 72 deletions(-) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py index 3bb34932..c4d79880 100644 --- a/bec_widgets/examples/oneplot/oneplot.py +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -24,11 +24,17 @@ class PlotApp(QWidget): update_signal = pyqtSignal() update_dap_signal = pyqtSignal() - def __init__(self): - super().__init__() + def __init__(self, x_y_values=None, dap_worker=None, parent=None): + super(PlotApp, self).__init__(parent) + self.x_y_values = x_y_values if x_y_values is not None else [] + self.dap_worker = dap_worker if dap_worker is not None else "" + + self.x_values = [x for x, y in self.x_y_values] + self.y_values = [y for x, y in self.x_y_values] + self.scanID = None - self.motor_data = [] - self.monitor_data = [] + self.data_x = [] + self.data_y = [] self.dap_x = np.array([]) self.dap_y = np.array([]) @@ -54,38 +60,39 @@ class PlotApp(QWidget): def init_ui(self): self.plot = pg.PlotItem() self.glw.addItem(self.plot) - self.plot.setLabel("bottom", "Motor") - self.plot.setLabel("left", "Monitor") + self.plot.setLabel("bottom", self.x_values[0]) + self.plot.setLabel("left", self.y_values[0]) self.plot.addLegend() def init_curves(self): self.plot.clear() - self.curves = [] - self.scatters = [] + self.curves_data = [] + self.curves_dap = [] self.pens = [] self.brushs = [] # todo check if needed color_list = ["#384c6b", "#e28a2b", "#5E3023", "#e41a1c", "#984e83", "#4daf4a"] - for ii, monitor in enumerate(self.monitor_names): - print(self.monitor_names[ii]) - pen = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) - # brush = mkBrush(color=color_list[ii]) - curve = pg.PlotDataItem(pen=pen, skipFiniteCheck=True) # ,symbolBrush=brush) - scatter = pg.ScatterPlotItem(pen=pen, size=5, name=monitor) # ,brush=brush,) - # scatter = pg.PlotDataItem( - # pen=None, symbol="o", symbolBrush=color_list[ii], name=monitor - # ) - self.curves.append(curve) - self.scatters.append(scatter) - self.pens.append(pen) + for ii, monitor in enumerate(self.y_values): + pen_curve = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) + pen_dap = mkPen(color=color_list[ii + 1], width=2, style=QtCore.Qt.DashLine) + brush = mkBrush(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) + curve_data = pg.PlotDataItem( + pen=pen_curve, + skipFiniteCheck=True, + symbolBrush=brush, + symbolSize=5, + name=monitor + "_data", + ) + curve_dap = pg.PlotDataItem(pen=pen_dap, size=5, name=monitor + "_fit") + self.curves_data.append(curve_data) + self.curves_dap.append(curve_dap) + self.pens.append(pen_curve) # self.brushs.append(brush) - self.plot.addItem(curve) - self.plot.addItem(scatter) + self.plot.addItem(curve_data) + self.plot.addItem(curve_dap) - # TODO hook signals - # TODO hook crosshair self.tableWidget_crosshair.setRowCount(len(self.monitor_names)) self.tableWidget_crosshair.setVerticalHeaderLabels(self.monitor_names) self.hook_crosshair() @@ -106,8 +113,8 @@ class PlotApp(QWidget): table_widget.resizeColumnsToContents() def update_plot(self): - self.curves[0].setData(self.dap_x, self.dap_y) - self.scatters[0].setData(self.motor_data, self.monitor_data) + self.curves_data[0].setData(self.dap_x, self.dap_y) + self.curves_dap[0].setData(self.data_x, self.data_y) def update_fit_table(self): self.tableWidget_fit.setData(self.fit) @@ -121,15 +128,9 @@ class PlotApp(QWidget): msg (dict): metadata(dict): """ - ... - print("on_dap_update") - # print(f'msg "on_dap_update" = {msg}') - dapMSG = msg - metaMSG = metadata - - self.dap_x = msg["gaussian_fit_worker_3"]["x"] - self.dap_y = msg["gaussian_fit_worker_3"]["y"] + self.dap_x = msg[self.dap_worker]["x"] + self.dap_y = msg[self.dap_worker]["y"] self.fit = metadata["fit_parameters"] @@ -137,55 +138,63 @@ class PlotApp(QWidget): @pyqtSlot(dict, dict) def on_scan_segment(self, msg, metadata): - # TODO x -> motor - # TODO y -> monitor._hints :list - print("on_scan_segment") - current_scanID = msg["scanID"] # print(f"current_scanID = {current_scanID}") # implement if condition that if scan id is different than last one init new scan variables if current_scanID != self.scanID: self.scanID = current_scanID - self.motor_data = [] - self.monitor_data = [] + self.data_x = [] + self.data_y = [] self.init_curves() - motor_data = msg["data"]["samx"]["samx"]["value"] - monitor_data = msg["data"]["gauss_bpm"]["gauss_bpm"][ - "value" - ] # gaussbpm._hints -> implement logic with list - # - self.motor_data.append(motor_data) - self.monitor_data.append(monitor_data) + dev_x = self.x_values[0] + dev_y = self.y_values[0] + + # TODO put warning that I am putting 1st one + + data_x = msg["data"][dev_x][dev[dev_x]._hints[0]]["value"] + data_y = msg["data"][dev_y][dev[dev_y]._hints[0]]["value"] + + self.data_x.append(data_x) + self.data_y.append(data_y) - # self.update_plot.emit() self.update_signal.emit() - # @pyqtSlot(dict, dict) - # def on_new_scan(self, msg, metadata): # TODO probably not needed - # """ - # Initiate new scan and clear previous data - # Args: - # msg(dict): - # metadata(dict): - # - # Returns: - # - # """ - - # print(40 * "#" + "on_new_scan" + 40 * "#") - - # self.motor_data = [msg["data"]["samx"]["samx"]["value"]] - # self.monitor_data = [msg["data"]["gauss_bpm"]["gauss_bpm"]["value"]] - # self.init_curves() - if __name__ == "__main__": - # from bec_lib import BECClient + import argparse + import ast + from bec_widgets import ctrl_c from bec_widgets.bec_dispatcher import bec_dispatcher + parser = argparse.ArgumentParser() + + parser.add_argument( + "--x_y_values", + type=str, + default="[('samx', 'gauss_bpm')]", + help="Specify x y device/signals pairs for plotting as [tuple(str,str)]", + ) + + parser.add_argument( + "--dap_process", type=str, default="gaussian_fit_worker_3", help="Specify the DAP process" + ) + + args = parser.parse_args() + + try: + x_y_values = ast.literal_eval(args.x_y_values) + if not all(isinstance(item, tuple) and len(item) == 2 for item in x_y_values): + raise ValueError("Invalid format: All elements must be 2-tuples.") + except (ValueError, SyntaxError): + raise ValueError("Invalid input format. Expected a list of 2-tuples.") + + # Retrieve the dap_process value + dap_process = args.dap_process + + # BECclient global variables client = bec_dispatcher.client client.start() @@ -194,13 +203,11 @@ if __name__ == "__main__": queue = client.queue app = QApplication([]) + plotApp = PlotApp(x_y_values=x_y_values, dap_worker=dap_process) - plotApp = PlotApp() - - bec_dispatcher.connect_dap_slot(plotApp.on_dap_update, "gaussian_fit_worker_3") + # Connecting signals from bec_dispatcher + bec_dispatcher.connect_dap_slot(plotApp.on_dap_update, dap_process) bec_dispatcher.connect_slot(plotApp.on_scan_segment, MessageEndpoints.scan_segment()) - # bec_dispatcher.new_scan.connect(plotApp.on_new_scan) # TODO check if works! - # bec_dispatcher.connect_slot(plotApp.on_new_scan,) ctrl_c.setup(app) window = plotApp From cab53543e644921df69c57c70ad2b3a03bbafcc1 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Tue, 29 Aug 2023 14:41:23 +0200 Subject: [PATCH 10/11] fix: user can disable dap_worker and just choose signals to plot --- bec_widgets/examples/oneplot/oneplot.py | 52 ++++++++++++------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py index c4d79880..68f8cf1b 100644 --- a/bec_widgets/examples/oneplot/oneplot.py +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -12,12 +12,8 @@ from bec_widgets.qt_utils import Crosshair from bec_lib.core import MessageEndpoints # TODO implement: -# - add crosshair -# - add crosshair table # - implement scanID database for visualizing previous scans # - multiple signals for different monitors -# - user can choose what motor against what monitor to plot -# - crosshair snaps now just to fit, not to actual data class PlotApp(QWidget): @@ -26,8 +22,11 @@ class PlotApp(QWidget): def __init__(self, x_y_values=None, dap_worker=None, parent=None): super(PlotApp, self).__init__(parent) + current_path = os.path.dirname(__file__) + uic.loadUi(os.path.join(current_path, "oneplot.ui"), self) + self.x_y_values = x_y_values if x_y_values is not None else [] - self.dap_worker = dap_worker if dap_worker is not None else "" + self.dap_worker = dap_worker # if dap_worker is not None else "" self.x_values = [x for x, y in self.x_y_values] self.y_values = [y for x, y in self.x_y_values] @@ -41,11 +40,6 @@ class PlotApp(QWidget): self.fit = None - current_path = os.path.dirname(__file__) - uic.loadUi(os.path.join(current_path, "oneplot.ui"), self) - - self.monitor_names = ["gauss_bpm"] - self.init_ui() self.init_curves() self.hook_crosshair() @@ -58,13 +52,15 @@ class PlotApp(QWidget): ) def init_ui(self): - self.plot = pg.PlotItem() + """Initialize the UI""" + self.plot = pg.PlotItem(title=self.y_values[0]) self.glw.addItem(self.plot) self.plot.setLabel("bottom", self.x_values[0]) self.plot.setLabel("left", self.y_values[0]) self.plot.addLegend() def init_curves(self): + """Initialize the curves and hook crosshair""" self.plot.clear() self.curves_data = [] @@ -76,7 +72,6 @@ class PlotApp(QWidget): for ii, monitor in enumerate(self.y_values): pen_curve = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) - pen_dap = mkPen(color=color_list[ii + 1], width=2, style=QtCore.Qt.DashLine) brush = mkBrush(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) curve_data = pg.PlotDataItem( pen=pen_curve, @@ -85,19 +80,21 @@ class PlotApp(QWidget): symbolSize=5, name=monitor + "_data", ) - curve_dap = pg.PlotDataItem(pen=pen_dap, size=5, name=monitor + "_fit") self.curves_data.append(curve_data) - self.curves_dap.append(curve_dap) self.pens.append(pen_curve) - # self.brushs.append(brush) self.plot.addItem(curve_data) - self.plot.addItem(curve_dap) + if self.dap_worker is not None: + pen_dap = mkPen(color=color_list[ii + 1], width=2, style=QtCore.Qt.DashLine) + curve_dap = pg.PlotDataItem(pen=pen_dap, size=5, name=monitor + "_fit") + self.curves_dap.append(curve_dap) + self.plot.addItem(curve_dap) - self.tableWidget_crosshair.setRowCount(len(self.monitor_names)) - self.tableWidget_crosshair.setVerticalHeaderLabels(self.monitor_names) + self.tableWidget_crosshair.setRowCount(len(self.y_values)) + self.tableWidget_crosshair.setVerticalHeaderLabels(self.y_values) self.hook_crosshair() def hook_crosshair(self): + """Hook the crosshair to the plot""" self.crosshair_1d = Crosshair(self.plot, precision=3) self.crosshair_1d.coordinatesChanged1D.connect( lambda x, y: self.update_table(self.tableWidget_crosshair, x, y, column=0) @@ -107,14 +104,14 @@ class PlotApp(QWidget): ) def update_table(self, table_widget, x, y_values, column): - """Update the table with the new coordinates""" for i, y in enumerate(y_values): table_widget.setItem(i, column, QTableWidgetItem(f"({x}, {y})")) table_widget.resizeColumnsToContents() def update_plot(self): - self.curves_data[0].setData(self.dap_x, self.dap_y) - self.curves_dap[0].setData(self.data_x, self.data_y) + self.curves_data[0].setData(self.data_x, self.data_y) + if self.dap_worker is not None: + self.curves_dap[0].setData(self.dap_x, self.dap_y) def update_fit_table(self): self.tableWidget_fit.setData(self.fit) @@ -178,9 +175,7 @@ if __name__ == "__main__": help="Specify x y device/signals pairs for plotting as [tuple(str,str)]", ) - parser.add_argument( - "--dap_process", type=str, default="gaussian_fit_worker_3", help="Specify the DAP process" - ) + parser.add_argument("--dap_worker", type=str, default=None, help="Specify the DAP process") args = parser.parse_args() @@ -191,8 +186,11 @@ if __name__ == "__main__": except (ValueError, SyntaxError): raise ValueError("Invalid input format. Expected a list of 2-tuples.") + # Convert dap_worker to None if it's the string "None", for testing "gaussian_fit_worker_3" + dap_worker = None if args.dap_worker == "None" else args.dap_worker + # Retrieve the dap_process value - dap_process = args.dap_process + # dap_worker = args.dap_worker # BECclient global variables client = bec_dispatcher.client @@ -203,10 +201,10 @@ if __name__ == "__main__": queue = client.queue app = QApplication([]) - plotApp = PlotApp(x_y_values=x_y_values, dap_worker=dap_process) + plotApp = PlotApp(x_y_values=x_y_values, dap_worker=dap_worker) # Connecting signals from bec_dispatcher - bec_dispatcher.connect_dap_slot(plotApp.on_dap_update, dap_process) + bec_dispatcher.connect_dap_slot(plotApp.on_dap_update, dap_worker) bec_dispatcher.connect_slot(plotApp.on_scan_segment, MessageEndpoints.scan_segment()) ctrl_c.setup(app) From bdaeef831be37a0cc21a42056d911ce04fb27d2d Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Tue, 29 Aug 2023 14:48:21 +0200 Subject: [PATCH 11/11] doc: updated documentation for PlotApp --- bec_widgets/examples/oneplot/oneplot.py | 66 ++++++++++++++++++------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/bec_widgets/examples/oneplot/oneplot.py b/bec_widgets/examples/oneplot/oneplot.py index 68f8cf1b..6c93de9e 100644 --- a/bec_widgets/examples/oneplot/oneplot.py +++ b/bec_widgets/examples/oneplot/oneplot.py @@ -1,4 +1,6 @@ import os + +import PyQt5.QtWidgets import numpy as np import pyqtgraph as pg @@ -17,6 +19,19 @@ from bec_lib.core import MessageEndpoints class PlotApp(QWidget): + """ + Main class for the PlotApp used to plot two signals from the BEC. + + Attributes: + update_signal (pyqtSignal): Signal to trigger plot updates. + update_dap_signal (pyqtSignal): Signal to trigger DAP updates. + + Args: + x_y_values (list of tuple, optional): List of (x, y) device/signal pairs for plotting. + dap_worker (str, optional): DAP process to specify. Set to None to disable. + parent (QWidget, optional): Parent widget. + """ + update_signal = pyqtSignal() update_dap_signal = pyqtSignal() @@ -51,16 +66,16 @@ class PlotApp(QWidget): self.update_dap_signal, rateLimit=25, slot=self.update_fit_table ) - def init_ui(self): - """Initialize the UI""" + def init_ui(self) -> None: + """Initialize the UI components.""" self.plot = pg.PlotItem(title=self.y_values[0]) self.glw.addItem(self.plot) self.plot.setLabel("bottom", self.x_values[0]) self.plot.setLabel("left", self.y_values[0]) self.plot.addLegend() - def init_curves(self): - """Initialize the curves and hook crosshair""" + def init_curves(self) -> None: + """Initialize curve data and properties.""" self.plot.clear() self.curves_data = [] @@ -68,7 +83,14 @@ class PlotApp(QWidget): self.pens = [] self.brushs = [] # todo check if needed - color_list = ["#384c6b", "#e28a2b", "#5E3023", "#e41a1c", "#984e83", "#4daf4a"] + color_list = [ + "#384c6b", + "#e28a2b", + "#5E3023", + "#e41a1c", + "#984e83", + "#4daf4a", + ] # todo change to cmap for ii, monitor in enumerate(self.y_values): pen_curve = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) @@ -93,8 +115,8 @@ class PlotApp(QWidget): self.tableWidget_crosshair.setVerticalHeaderLabels(self.y_values) self.hook_crosshair() - def hook_crosshair(self): - """Hook the crosshair to the plot""" + def hook_crosshair(self) -> None: + """Attach the crosshair to the plot.""" self.crosshair_1d = Crosshair(self.plot, precision=3) self.crosshair_1d.coordinatesChanged1D.connect( lambda x, y: self.update_table(self.tableWidget_crosshair, x, y, column=0) @@ -103,27 +125,32 @@ class PlotApp(QWidget): lambda x, y: self.update_table(self.tableWidget_crosshair, x, y, column=1) ) - def update_table(self, table_widget, x, y_values, column): + def update_table( + self, table_widget: PyQt5.QtWidgets.QTableWidget, x: float, y_values: list, column: int + ) -> None: for i, y in enumerate(y_values): table_widget.setItem(i, column, QTableWidgetItem(f"({x}, {y})")) table_widget.resizeColumnsToContents() - def update_plot(self): + def update_plot(self) -> None: + """Update the plot data.""" self.curves_data[0].setData(self.data_x, self.data_y) if self.dap_worker is not None: self.curves_dap[0].setData(self.dap_x, self.dap_y) def update_fit_table(self): + """Update the table for fit data.""" + self.tableWidget_fit.setData(self.fit) @pyqtSlot(dict, dict) - def on_dap_update(self, msg, metadata) -> None: + def on_dap_update(self, msg: dict, metadata: dict) -> None: """ - Getting processed data from DAP + Update DAP related data. Args: - msg (dict): - metadata(dict): + msg (dict): Message received with data. + metadata (dict): Metadata of the DAP. """ self.dap_x = msg[self.dap_worker]["x"] @@ -134,11 +161,16 @@ class PlotApp(QWidget): self.update_dap_signal.emit() @pyqtSlot(dict, dict) - def on_scan_segment(self, msg, metadata): - current_scanID = msg["scanID"] - # print(f"current_scanID = {current_scanID}") + def on_scan_segment(self, msg: dict, metadata: dict): + """ + Handle new scan segments. + + Args: + msg (dict): Message received with scan data. + metadata (dict): Metadata of the scan. + """ + current_scanID = msg["scanID"] - # implement if condition that if scan id is different than last one init new scan variables if current_scanID != self.scanID: self.scanID = current_scanID self.data_x = []