From f75554bd7b072207847956a8720b9a62c20ba2c8 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Thu, 10 Aug 2023 14:30:45 +0200 Subject: [PATCH] feat: cursor universal for 1D and 2D * in 1D snapping to the closest curve * 2D getting int coordinates --- bec_widgets/qt_utils/crosshair.py | 49 +++++++++++++++-------- bec_widgets/qt_utils/crosshair_example.py | 16 +++----- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/bec_widgets/qt_utils/crosshair.py b/bec_widgets/qt_utils/crosshair.py index 911c69d6..b39272ad 100644 --- a/bec_widgets/qt_utils/crosshair.py +++ b/bec_widgets/qt_utils/crosshair.py @@ -1,5 +1,5 @@ -import pyqtgraph as pg import numpy as np +import pyqtgraph as pg from PyQt5.QtCore import pyqtSignal, QObject @@ -7,11 +7,10 @@ class Crosshair(QObject): coordinatesChanged = pyqtSignal(float, float) dataPointClicked = pyqtSignal(float, float) - def __init__(self, plot_item, is_image=False, parent=None): + def __init__(self, plot_item, precision=None, parent=None): super().__init__(parent) self.plot_item = plot_item - self.data_shape = None - self.is_image = is_image + self.precision = precision 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) @@ -21,26 +20,44 @@ class Crosshair(QObject): ) self.plot_item.scene().sigMouseClicked.connect(self.mouse_clicked) + def get_data(self): + x_data, y_data = [], [] + for item in self.plot_item.items: + if isinstance(item, pg.ImageItem): + return item.image, None # Return image data for 2D plot + elif isinstance(item, pg.PlotDataItem): + x_data.extend(item.xData) + y_data.extend(item.yData) + if x_data and y_data: + return np.array(x_data), np.array(y_data) # Return x and y data for 1D plot + return None, None + def mouse_moved(self, event): pos = event[0] - if self.data_shape is not None and self.plot_item.vb.sceneBoundingRect().contains(pos): + if 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)) + x, y = self.snap_to_data(mouse_point.x(), mouse_point.y()) 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 - ): + if 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) + x, y = self.snap_to_data(mouse_point.x(), mouse_point.y()) self.dataPointClicked.emit(x, y) - def set_data_shape(self, shape): - self.data_shape = shape + def snap_to_data(self, x, y): + x_data, y_data = self.get_data() + if x_data is not None and y_data is not None: + distance = (x_data - x) ** 2 + (y_data - y) ** 2 + index = np.argmin(distance) + x, y = x_data[index], y_data[index] + if self.precision is not None: + x, y = round(x, self.precision), round(y, self.precision) + return x, y + elif x_data is not None and y_data is None: # For 2D plot (ImageItem) + x_idx = int(np.clip(x, 0, x_data.shape[1] - 1)) + y_idx = int(np.clip(y, 0, x_data.shape[0] - 1)) + return x_idx, y_idx + return x, y diff --git a/bec_widgets/qt_utils/crosshair_example.py b/bec_widgets/qt_utils/crosshair_example.py index 7f6f761b..3a478511 100644 --- a/bec_widgets/qt_utils/crosshair_example.py +++ b/bec_widgets/qt_utils/crosshair_example.py @@ -19,14 +19,13 @@ 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))) +y_data_sine = np.sin(x_data) +y_data_cosine = np.cos(x_data) +plot_item_1d.plot(x_data, y_data_sine) +plot_item_1d.plot(x_data, y_data_cosine) +crosshair_1d = Crosshair(plot_item_1d, precision=2) -# def on_coordinates_changed_1d(x, y): label_1d_move.setText(f"1D Moved: ({x}, {y})") @@ -35,7 +34,6 @@ 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) @@ -50,11 +48,9 @@ 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) +crosshair_2d = Crosshair(plot_item_2d, precision=2) -# def on_coordinates_changed_2d(x, y): label_2d_move.setText(f"2D Moved: ({x}, {y})")