From 5353fed7bfe1819819fa3348ec93d2d0ba540628 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:50:47 +0200 Subject: [PATCH] feat: added qt_utils package with general Crosshair function * only emits int -> suitable for 2D coordinates but not for 1D plot --- bec_widgets/line_plot.py | 30 +++++----- bec_widgets/qt_utils/__init__.py | 0 bec_widgets/qt_utils/crosshair.py | 46 +++++++++++++++ bec_widgets/qt_utils/crosshair_example.py | 71 +++++++++++++++++++++++ 4 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 bec_widgets/qt_utils/__init__.py create mode 100644 bec_widgets/qt_utils/crosshair.py create mode 100644 bec_widgets/qt_utils/crosshair_example.py diff --git a/bec_widgets/line_plot.py b/bec_widgets/line_plot.py index 448efdb7..5fc7d830 100644 --- a/bec_widgets/line_plot.py +++ b/bec_widgets/line_plot.py @@ -61,7 +61,7 @@ class BasicPlot(QtWidgets.QWidget): "symbol": "o", "symbolSize": 10, } - color_list = ["#384c6b", "#e28a2b", "#5E3023", "#e41a1c", "#984e83", "#4daf4a"] + color_list = BasicPlot.golden_angle_color(colormap="CET-R2", num=len(self.y_value_list)) # setup plots - GraphicsLayoutWidget @@ -126,7 +126,7 @@ class BasicPlot(QtWidgets.QWidget): # Debug functions self.pushButton_debug.clicked.connect(self.generate_2D_data_update) - self.generate_2D_data() + # self.generate_2D_data() self._current_proj = None self._current_metadata_ep = "px_stream/projection_{}/metadata" @@ -187,18 +187,18 @@ class BasicPlot(QtWidgets.QWidget): closest_point = self.closest_x_y_value( mousePoint.x(), self.plotter_data_x[0], self.plotter_data_y[0] ) - self.precision = 3 - ii = 0 - y_value = self.y_value_list[ii] - x_data = f"{10**closest_point[0]:.{self.precision}f}" - y_data = f"{10**closest_point[1]:.{self.precision}f}" - - # Write coordinate to QTable - self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(y_value))) - self.mouse_table.setItem(ii, 2, QTableWidgetItem(str(x_data))) - self.mouse_table.setItem(ii, 3, QTableWidgetItem(str(y_data))) - - self.mouse_table.resizeColumnsToContents() + # self.precision = 3 + # ii = 0 + # y_value = self.y_value_list[ii] + # x_data = f"{10**closest_point[0]:.{self.precision}f}" + # y_data = f"{10**closest_point[1]:.{self.precision}f}" + # + # # Write coordinate to QTable + # self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(y_value))) + # self.mouse_table.setItem(ii, 2, QTableWidgetItem(str(x_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: """ @@ -382,7 +382,7 @@ class BasicPlot(QtWidgets.QWidget): time.sleep(0.1) continue endpoint = f"px_stream/projection_{self._current_proj}/data" - msgs = client.producer.lrange(topic=endpoint, start=0, end=0) + msgs = client.producer.lrange(topic=endpoint, start=-1, end=-1) data = [BECMessage.DeviceMessage.loads(msg) for msg in msgs] if not data: continue diff --git a/bec_widgets/qt_utils/__init__.py b/bec_widgets/qt_utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bec_widgets/qt_utils/crosshair.py b/bec_widgets/qt_utils/crosshair.py new file mode 100644 index 00000000..911c69d6 --- /dev/null +++ b/bec_widgets/qt_utils/crosshair.py @@ -0,0 +1,46 @@ +import pyqtgraph as pg +import numpy as np +from PyQt5.QtCore import pyqtSignal, QObject + + +class Crosshair(QObject): + coordinatesChanged = pyqtSignal(float, float) + dataPointClicked = pyqtSignal(float, float) + + def __init__(self, plot_item, is_image=False, parent=None): + super().__init__(parent) + self.plot_item = plot_item + self.data_shape = None + self.is_image = is_image + self.v_line = pg.InfiniteLine(angle=90, movable=False) + self.h_line = pg.InfiniteLine(angle=0, movable=False) + self.plot_item.addItem(self.v_line, ignoreBounds=True) + self.plot_item.addItem(self.h_line, ignoreBounds=True) + self.proxy = pg.SignalProxy( + self.plot_item.scene().sigMouseMoved, rateLimit=60, slot=self.mouse_moved + ) + self.plot_item.scene().sigMouseClicked.connect(self.mouse_clicked) + + def mouse_moved(self, event): + pos = event[0] + if self.data_shape is not None and self.plot_item.vb.sceneBoundingRect().contains(pos): + mouse_point = self.plot_item.vb.mapSceneToView(pos) + x = int(np.clip(np.round(mouse_point.x()), 0, self.data_shape[1] - 1)) + y = int(np.clip(np.round(mouse_point.y()), 0, self.data_shape[0] - 1)) + self.v_line.setPos(x) + self.h_line.setPos(y) + self.coordinatesChanged.emit(x, y) + + def mouse_clicked(self, event): + if self.data_shape is not None and self.plot_item.vb.sceneBoundingRect().contains( + event._scenePos + ): + mouse_point = self.plot_item.vb.mapSceneToView(event._scenePos) + x = int(np.clip(np.round(mouse_point.x()), 0, self.data_shape[1] - 1)) + y = int(np.clip(np.round(mouse_point.y()), 0, self.data_shape[0] - 1)) + self.v_line.setPos(x) + self.h_line.setPos(y) + self.dataPointClicked.emit(x, y) + + def set_data_shape(self, shape): + self.data_shape = shape diff --git a/bec_widgets/qt_utils/crosshair_example.py b/bec_widgets/qt_utils/crosshair_example.py new file mode 100644 index 00000000..7f6f761b --- /dev/null +++ b/bec_widgets/qt_utils/crosshair_example.py @@ -0,0 +1,71 @@ +import pyqtgraph as pg +import numpy as np + +from crosshair import Crosshair + + +def add_crosshair(plot_item, is_image=False): + return Crosshair(plot_item, is_image) + + +app = pg.mkQApp() +win = pg.GraphicsLayoutWidget(show=True) +win.resize(1000, 500) + +##################### +# 1D Plot with labels +##################### +label_1d_move = win.addLabel("1D move label", row=0, col=0) +label_1d_click = win.addLabel("1D click label", row=1, col=0) +plot_item_1d = win.addPlot(row=2, col=0) +x_data = np.linspace(0, 10, 1000) +y_data = np.sin(x_data) +plot_item_1d.plot(x_data, y_data) +# +crosshair_1d = add_crosshair(plot_item_1d) +crosshair_1d.set_data_shape((len(y_data), len(x_data))) + + +# +def on_coordinates_changed_1d(x, y): + label_1d_move.setText(f"1D Moved: ({x}, {y})") + + +def on_data_point_clicked_1d(x, y): + label_1d_click.setText(f"1D Clicked: ({x}, {y})") + + +# +crosshair_1d.coordinatesChanged.connect(on_coordinates_changed_1d) +crosshair_1d.dataPointClicked.connect(on_data_point_clicked_1d) + +##################### +# 2D Plot with labels +##################### +label_2d_move = win.addLabel("2D move label", row=0, col=1) +label_2d_click = win.addLabel("2D click label", row=1, col=1) +plot_item_2d = win.addPlot(row=2, col=1) + +img = np.random.normal(size=(100, 100)) +image_item = pg.ImageItem(img) +plot_item_2d.addItem(image_item) + +crosshair_2d = add_crosshair(plot_item_2d, is_image=True) +crosshair_2d.set_data_shape(img.shape) + + +# +def on_coordinates_changed_2d(x, y): + label_2d_move.setText(f"2D Moved: ({x}, {y})") + + +def on_data_point_clicked_2d(x, y): + label_2d_click.setText(f"2D Clicked: ({x}, {y})") + + +# +crosshair_2d.coordinatesChanged.connect(on_coordinates_changed_2d) +crosshair_2d.dataPointClicked.connect(on_data_point_clicked_2d) + +if __name__ == "__main__": + pg.exec()