0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 11:41:49 +02:00

refactor: changed from bec client to dispatcher

This commit is contained in:
wyzula-jan
2023-08-08 14:10:24 +02:00
parent 0feca4b157
commit 14e92e8d68
3 changed files with 99 additions and 46 deletions

View File

@ -5,7 +5,8 @@ from typing import Any
import numpy as np import numpy as np
import pyqtgraph import pyqtgraph
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5.QtWidgets import QTableWidgetItem from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QTableWidgetItem, QCheckBox
from bec_lib import BECClient from bec_lib import BECClient
from pyqtgraph import mkBrush, mkColor, mkPen from pyqtgraph import mkBrush, mkColor, mkPen
@ -33,6 +34,9 @@ class BasicPlot(QtWidgets.QWidget):
current_path = os.path.dirname(__file__) current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "line_plot.ui"), self) uic.loadUi(os.path.join(current_path, "line_plot.ui"), self)
# Set splitter distribution of widgets
self.splitter.setSizes([5, 2])
self._idle_time = 100 self._idle_time = 100
self.title = "" self.title = ""
self.label_bottom = "" self.label_bottom = ""
@ -40,6 +44,7 @@ class BasicPlot(QtWidgets.QWidget):
self.scan_motors = [] self.scan_motors = []
self.y_value_list = y_value_list self.y_value_list = y_value_list
self.previous_y_value_list = None
self.plotter_data_x = [] self.plotter_data_x = []
self.plotter_data_y = [] self.plotter_data_y = []
self.curves = [] self.curves = []
@ -138,9 +143,6 @@ class BasicPlot(QtWidgets.QWidget):
if not self.plotter_data_x: if not self.plotter_data_x:
return return
# Init number of rows in table according to n of devices
self.mouse_table.setRowCount(len(self.y_value_list))
for ii, y_value in enumerate(self.y_value_list): for ii, y_value in enumerate(self.y_value_list):
closest_point = self.closest_x_y_value( closest_point = self.closest_x_y_value(
mousePoint.x(), self.plotter_data_x, self.plotter_data_y[ii] mousePoint.x(), self.plotter_data_x, self.plotter_data_y[ii]
@ -150,9 +152,11 @@ class BasicPlot(QtWidgets.QWidget):
y_data = f"{closest_point[1]:.{self.precision}f}" y_data = f"{closest_point[1]:.{self.precision}f}"
# Write coordinate to QTable # Write coordinate to QTable
self.mouse_table.setItem(ii, 0, QTableWidgetItem(str(y_value))) self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(y_value)))
self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(x_data))) self.mouse_table.setItem(ii, 2, QTableWidgetItem(str(x_data)))
self.mouse_table.setItem(ii, 2, QTableWidgetItem(str(y_data))) self.mouse_table.setItem(ii, 3, QTableWidgetItem(str(y_data)))
self.mouse_table.resizeColumnsToContents()
def closest_x_y_value(self, input_value, list_x, list_y) -> tuple: def closest_x_y_value(self, input_value, list_x, list_y) -> tuple:
""" """
@ -176,6 +180,11 @@ class BasicPlot(QtWidgets.QWidget):
if self.roi_selector not in self.plot.items: if self.roi_selector not in self.plot.items:
self.plot.addItem(self.roi_selector) self.plot.addItem(self.roi_selector)
# check if QTable was initialised and if list of devices was changed
if self.y_value_list != self.previous_y_value_list:
self.setup_cursor_table()
self.previous_y_value_list = self.y_value_list.copy() if self.y_value_list else None
if len(self.plotter_data_x) <= 1: if len(self.plotter_data_x) <= 1:
return return
self.plot.setLabel("bottom", self.label_bottom) self.plot.setLabel("bottom", self.label_bottom)
@ -183,7 +192,8 @@ class BasicPlot(QtWidgets.QWidget):
for ii in range(len(self.y_value_list)): for ii in range(len(self.y_value_list)):
self.curves[ii].setData(self.plotter_data_x, self.plotter_data_y[ii]) self.curves[ii].setData(self.plotter_data_x, self.plotter_data_y[ii])
def __call__(self, data: dict, metadata: dict, **kwds: Any) -> None: @pyqtSlot(dict, dict)
def on_scan_segment(self, data: dict, metadata: dict) -> None:
"""Update function that is called during the scan callback. To avoid """Update function that is called during the scan callback. To avoid
too many renderings, the GUI is only processing events every <_idle_time> ms. too many renderings, the GUI is only processing events every <_idle_time> ms.
@ -199,7 +209,7 @@ class BasicPlot(QtWidgets.QWidget):
self.title = f"Scan {metadata['scan_number']}" self.title = f"Scan {metadata['scan_number']}"
self.scan_motors = scan_motors = metadata.get("scan_report_devices") self.scan_motors = scan_motors = metadata.get("scan_report_devices")
client = BECClient() # client = BECClient()
remove_y_value_index = [ remove_y_value_index = [
index index
for index, y_value in enumerate(self.y_value_list) for index, y_value in enumerate(self.y_value_list)
@ -219,6 +229,7 @@ class BasicPlot(QtWidgets.QWidget):
]["precision"] ]["precision"]
# TODO after update of bec_lib, this will be new way to access data # TODO after update of bec_lib, this will be new way to access data
# self.precision = client.device_manager.devices[scan_motors[0]].precision # self.precision = client.device_manager.devices[scan_motors[0]].precision
x = data["data"][scan_motors[0]][scan_motors[0]]["value"] x = data["data"][scan_motors[0]][scan_motors[0]]["value"]
self.plotter_data_x.append(x) self.plotter_data_x.append(x)
for ii, y_value in enumerate(self.y_value_list): for ii, y_value in enumerate(self.y_value_list):
@ -227,6 +238,9 @@ class BasicPlot(QtWidgets.QWidget):
self.label_bottom = scan_motors[0] self.label_bottom = scan_motors[0]
self.label_left = f"{', '.join(self.y_value_list)}" self.label_left = f"{', '.join(self.y_value_list)}"
# print(f'metadata scan N{metadata["scan_number"]}') #TODO put as label on top of plot
# print(f'Data point = {data["point_id"]}') #TODO can be used for progress bar
if len(self.plotter_data_x) <= 1: if len(self.plotter_data_x) <= 1:
return return
self.update_signal.emit() self.update_signal.emit()
@ -239,19 +253,41 @@ class BasicPlot(QtWidgets.QWidget):
self.curves[ii].setData([], []) self.curves[ii].setData([], [])
self.plotter_data_y.append([]) self.plotter_data_y.append([])
def setup_cursor_table(self):
"""QTable formatting according to N of devices displayed in plot."""
# Init number of rows in table according to n of devices
self.mouse_table.setRowCount(len(self.y_value_list))
for ii, y_value in enumerate(self.y_value_list):
checkbox = QCheckBox()
checkbox.setChecked(True)
# TODO just for testing, will be replaced by removing/adding curve
checkbox.stateChanged.connect(lambda: print("status Changed"))
# checkbox.stateChanged.connect(lambda: self.remove_curve_by_name(plot=self.plot, checkbox=checkbox, name=y_value))
self.mouse_table.setCellWidget(ii, 0, checkbox)
self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(y_value)))
self.mouse_table.resizeColumnsToContents()
@staticmethod @staticmethod
def remove_curve_by_name(plot: pyqtgraph.PlotItem, name: str) -> None: def remove_curve_by_name(plot: pyqtgraph.PlotItem, name: str) -> None:
# def remove_curve_by_name(plot: pyqtgraph.PlotItem, checkbox: QtWidgets.QCheckBox, name: str) -> None:
"""Removes a curve from the given plot by the specified name. """Removes a curve from the given plot by the specified name.
Args: Args:
plot (pyqtgraph.PlotItem): The plot from which to remove the curve. plot (pyqtgraph.PlotItem): The plot from which to remove the curve.
name (str): The name of the curve to remove. name (str): The name of the curve to remove.
""" """
# if checkbox.isChecked():
for item in plot.items: for item in plot.items:
if isinstance(item, pg.PlotDataItem) and getattr(item, "opts", {}).get("name") == name: if isinstance(item, pg.PlotDataItem) and getattr(item, "opts", {}).get("name") == name:
plot.removeItem(item) plot.removeItem(item)
return return
# else:
# return
@staticmethod @staticmethod
def golden_ratio(num: int) -> list: def golden_ratio(num: int) -> list:
"""Calculate the golden ratio for a given number of angles. """Calculate the golden ratio for a given number of angles.
@ -300,6 +336,7 @@ class BasicPlot(QtWidgets.QWidget):
if __name__ == "__main__": if __name__ == "__main__":
import argparse import argparse
from bec_widgets.bec_dispatcher import bec_dispatcher
from bec_widgets import ctrl_c from bec_widgets import ctrl_c
@ -312,11 +349,12 @@ if __name__ == "__main__":
) )
value = parser.parse_args() value = parser.parse_args()
print(f"Plotting signals for: {', '.join(value.signals)}") print(f"Plotting signals for: {', '.join(value.signals)}")
client = BECClient() client = bec_dispatcher.client
client.start() # client.start()
app = QtWidgets.QApplication([]) app = QtWidgets.QApplication([])
ctrl_c.setup(app) ctrl_c.setup(app)
plot = BasicPlot(y_value_list=value.signals) plot = BasicPlot(y_value_list=value.signals)
bec_dispatcher.connect(plot)
plot.show() plot.show()
client.callbacks.register("scan_segment", plot, sync=False) # client.callbacks.register("scan_segment", plot, sync=False)
app.exec_() app.exec_()

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>713</width> <width>737</width>
<height>294</height> <height>303</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -15,39 +15,52 @@
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <widget class="QSplitter" name="splitter">
<item> <property name="orientation">
<widget class="QPushButton" name="pushButton_debug"> <enum>Qt::Horizontal</enum>
<property name="text">
<string>Debug</string>
</property>
</widget>
</item>
<item>
<widget class="GraphicsLayoutWidget" name="glw"/>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="mouse_table">
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property> </property>
<column> <property name="opaqueResize">
<property name="text"> <bool>false</bool>
<string>Device</string> </property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="pushButton_debug">
<property name="text">
<string>Debug</string>
</property>
</widget>
</item>
<item>
<widget class="GraphicsLayoutWidget" name="glw"/>
</item>
</layout>
</widget>
<widget class="QTableWidget" name="mouse_table">
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property> </property>
</column> <column>
<column> <property name="text">
<property name="text"> <string>Display</string>
<string>X</string> </property>
</property> </column>
</column> <column>
<column> <property name="text">
<property name="text"> <string>Device</string>
<string>Y</string> </property>
</property> </column>
</column> <column>
<property name="text">
<string>X</string>
</property>
</column>
<column>
<property name="text">
<string>Y</string>
</property>
</column>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -143,7 +143,9 @@ def test_line_plot_mouse_moved(qtbot):
f"Y_data: {y_data:>{string_cap}}", f"Y_data: {y_data:>{string_cap}}",
] ]
) )
with mock.patch.object(plot, "plot") as mock_plot: with mock.patch.object(
plot, "plot"
) as mock_plot: # TODO change test to simulate QTable instead of QLabel
mock_plot.sceneBoundingRect.contains.return_value = True mock_plot.sceneBoundingRect.contains.return_value = True
mock_plot.vb.mapSceneToView((20, 10)).x.return_value = 2.8 mock_plot.vb.mapSceneToView((20, 10)).x.return_value = 2.8
plot.mouse_moved((20, 10)) plot.mouse_moved((20, 10))