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

fix(crosshair): fixed crosshair for image and waveforms

This commit is contained in:
2024-08-19 13:44:07 +02:00
committed by wakonig_k
parent e005be33d1
commit 37835cbf76
6 changed files with 230 additions and 118 deletions

View File

@ -3,8 +3,9 @@ import os
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections import defaultdict from collections import defaultdict
from bec_qthemes import material_icon
from qtpy.QtCore import QSize from qtpy.QtCore import QSize
from qtpy.QtGui import QAction, QIcon from qtpy.QtGui import QAction, QGuiApplication, QIcon
from qtpy.QtWidgets import QHBoxLayout, QLabel, QMenu, QToolBar, QToolButton, QWidget from qtpy.QtWidgets import QHBoxLayout, QLabel, QMenu, QToolBar, QToolButton, QWidget
import bec_widgets import bec_widgets
@ -70,6 +71,33 @@ class IconAction(ToolBarAction):
toolbar.addAction(self.action) toolbar.addAction(self.action)
class MaterialIconAction:
"""
Abstract base class for toolbar actions.
Args:
icon_path (str, optional): The name of the icon file from `assets/toolbar_icons`. Defaults to None.
tooltip (bool, optional): The tooltip for the action. Defaults to None.
checkable (bool, optional): Whether the action is checkable. Defaults to False.
"""
def __init__(self, icon_name: str = None, tooltip: str = None, checkable: bool = False):
self.icon_name = icon_name
self.tooltip = tooltip
self.checkable = checkable
self.action = None
def add_to_toolbar(self, toolbar: QToolBar, target: QWidget):
palette = QGuiApplication.palette()
color = "#FFFFFF" # FIXME: This should be a theme color but the toolbar doesn't respect the theme atm
# one fixed, change it to palette.toolTipBase().color()
icon = material_icon(self.icon_name, size=(20, 20), color=color)
self.action = QAction(QIcon(icon), self.tooltip, target)
self.action.setCheckable(self.checkable)
toolbar.addAction(self.action)
class DeviceSelectionAction(ToolBarAction): class DeviceSelectionAction(ToolBarAction):
""" """
Action for selecting a device in a combobox. Action for selecting a device in a combobox.

View File

@ -1,8 +1,10 @@
from collections import defaultdict
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
# from qtpy.QtCore import QObject, pyqtSignal # from qtpy.QtCore import QObject, pyqtSignal
from qtpy.QtCore import QObject from qtpy.QtCore import QObject, Qt
from qtpy.QtCore import Signal as pyqtSignal from qtpy.QtCore import Signal as pyqtSignal
@ -26,10 +28,13 @@ class Crosshair(QObject):
super().__init__(parent) super().__init__(parent)
self.is_log_y = None self.is_log_y = None
self.is_log_x = None self.is_log_x = None
self.is_derivative = None
self.plot_item = plot_item self.plot_item = plot_item
self.precision = precision self.precision = precision
self.v_line = pg.InfiniteLine(angle=90, movable=False) self.v_line = pg.InfiniteLine(angle=90, movable=False)
self.v_line.skip_auto_range = True
self.h_line = pg.InfiniteLine(angle=0, movable=False) self.h_line = pg.InfiniteLine(angle=0, movable=False)
self.h_line.skip_auto_range = True
self.plot_item.addItem(self.v_line, ignoreBounds=True) self.plot_item.addItem(self.v_line, ignoreBounds=True)
self.plot_item.addItem(self.h_line, ignoreBounds=True) self.plot_item.addItem(self.h_line, ignoreBounds=True)
self.proxy = pg.SignalProxy( self.proxy = pg.SignalProxy(
@ -37,6 +42,10 @@ class Crosshair(QObject):
) )
self.plot_item.scene().sigMouseClicked.connect(self.mouse_clicked) self.plot_item.scene().sigMouseClicked.connect(self.mouse_clicked)
self.plot_item.ctrl.derivativeCheck.checkStateChanged.connect(self.check_derivatives)
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
# Initialize markers # Initialize markers
self.marker_moved_1d = [] self.marker_moved_1d = []
self.marker_clicked_1d = [] self.marker_clicked_1d = []
@ -53,8 +62,8 @@ class Crosshair(QObject):
self.plot_item.removeItem(self.marker_2d) self.plot_item.removeItem(self.marker_2d)
# Create new markers # Create new markers
self.marker_moved_1d = [] self.marker_moved_1d = {}
self.marker_clicked_1d = [] self.marker_clicked_1d = {}
self.marker_2d = None self.marker_2d = None
for item in self.plot_item.items: for item in self.plot_item.items:
if isinstance(item, pg.PlotDataItem): # 1D plot if isinstance(item, pg.PlotDataItem): # 1D plot
@ -63,48 +72,48 @@ class Crosshair(QObject):
marker_moved = pg.ScatterPlotItem( marker_moved = pg.ScatterPlotItem(
size=10, pen=pg.mkPen(color), brush=pg.mkBrush(None) size=10, pen=pg.mkPen(color), brush=pg.mkBrush(None)
) )
marker_clicked = pg.ScatterPlotItem( marker_moved.skip_auto_range = True
size=10, pen=pg.mkPen(None), brush=pg.mkBrush(color) self.marker_moved_1d[item.name()] = marker_moved
)
self.marker_moved_1d.append(marker_moved)
self.plot_item.addItem(marker_moved) self.plot_item.addItem(marker_moved)
# Create glowing effect markers for clicked events # Create glowing effect markers for clicked events
marker_clicked_list = []
for size, alpha in [(18, 64), (14, 128), (10, 255)]: for size, alpha in [(18, 64), (14, 128), (10, 255)]:
marker_clicked = pg.ScatterPlotItem( marker_clicked = pg.ScatterPlotItem(
size=size, size=size,
pen=pg.mkPen(None), pen=pg.mkPen(None),
brush=pg.mkBrush(color.red(), color.green(), color.blue(), alpha), brush=pg.mkBrush(color.red(), color.green(), color.blue(), alpha),
) )
marker_clicked_list.append(marker_clicked) marker_clicked.skip_auto_range = True
self.marker_clicked_1d[item.name()] = marker_clicked
self.plot_item.addItem(marker_clicked) self.plot_item.addItem(marker_clicked)
self.marker_clicked_1d.append(marker_clicked_list)
elif isinstance(item, pg.ImageItem): # 2D plot elif isinstance(item, pg.ImageItem): # 2D plot
self.marker_2d = pg.ROI( self.marker_2d = pg.ROI(
[0, 0], size=[1, 1], pen=pg.mkPen("r", width=2), movable=False [0, 0], size=[1, 1], pen=pg.mkPen("r", width=2), movable=False
) )
self.plot_item.addItem(self.marker_2d) self.plot_item.addItem(self.marker_2d)
def snap_to_data(self, x, y) -> tuple: def snap_to_data(self, x, y) -> tuple[defaultdict[list], defaultdict[list]]:
""" """
Finds the nearest data points to the given x and y coordinates. Finds the nearest data points to the given x and y coordinates.
Args: Args:
x: The x-coordinate x: The x-coordinate of the mouse cursor
y: The y-coordinate y: The y-coordinate of the mouse cursor
Returns: Returns:
tuple: The nearest x and y values tuple: x and y values snapped to the nearest data
""" """
y_values_1d = [] y_values = defaultdict(list)
x_values_1d = [] x_values = defaultdict(list)
image_2d = None image_2d = None
# Iterate through items in the plot # Iterate through items in the plot
for item in self.plot_item.items: for item in self.plot_item.items:
if isinstance(item, pg.PlotDataItem): # 1D plot if isinstance(item, pg.PlotDataItem): # 1D plot
x_data, y_data = item.xData, item.yData name = item.name()
plot_data = item._getDisplayDataset()
x_data, y_data = plot_data.x, plot_data.y
if x_data is not None and y_data is not None: if x_data is not None and y_data is not None:
if self.is_log_x: if self.is_log_x:
min_x_data = np.min(x_data[x_data > 0]) min_x_data = np.min(x_data[x_data > 0])
@ -112,25 +121,25 @@ class Crosshair(QObject):
min_x_data = np.min(x_data) min_x_data = np.min(x_data)
max_x_data = np.max(x_data) max_x_data = np.max(x_data)
if x < min_x_data or x > max_x_data: if x < min_x_data or x > max_x_data:
return None, None y_values[name] = None
x_values[name] = None
continue
closest_x, closest_y = self.closest_x_y_value(x, x_data, y_data) closest_x, closest_y = self.closest_x_y_value(x, x_data, y_data)
y_values_1d.append(closest_y) y_values[name] = closest_y
x_values_1d.append(closest_x) x_values[name] = closest_x
elif isinstance(item, pg.ImageItem): # 2D plot elif isinstance(item, pg.ImageItem): # 2D plot
name = item.config.monitor
image_2d = item.image image_2d = item.image
# clip the x and y values to the image dimensions to avoid out of bounds errors
y_values[name] = int(np.clip(y, 0, image_2d.shape[1] - 1))
x_values[name] = int(np.clip(x, 0, image_2d.shape[0] - 1))
# Handle 1D plot if x_values and y_values:
if y_values_1d: if all(v is None for v in x_values.values()) or all(
if all(v is None for v in x_values_1d) or all(v is None for v in y_values_1d): v is None for v in y_values.values()
):
return None, None return None, None
closest_x = min(x_values_1d, key=lambda xi: abs(xi - x)) # Snap x to closest data point return x_values, y_values
return closest_x, y_values_1d
# Handle 2D plot
if image_2d is not None:
x_idx = int(np.clip(x, 0, image_2d.shape[0] - 1))
y_idx = int(np.clip(y, 0, image_2d.shape[1] - 1))
return x_idx, y_idx
return None, None return None, None
@ -156,7 +165,6 @@ class Crosshair(QObject):
Args: Args:
event: The mouse moved event event: The mouse moved event
""" """
self.check_log()
pos = event[0] pos = event[0]
if self.plot_item.vb.sceneBoundingRect().contains(pos): if self.plot_item.vb.sceneBoundingRect().contains(pos):
mouse_point = self.plot_item.vb.mapSceneToView(pos) mouse_point = self.plot_item.vb.mapSceneToView(pos)
@ -168,27 +176,34 @@ class Crosshair(QObject):
x = 10**x x = 10**x
if self.is_log_y: if self.is_log_y:
y = 10**y y = 10**y
x, y_values = self.snap_to_data(x, y) x_snap_values, y_snap_values = self.snap_to_data(x, y)
if x_snap_values is None or y_snap_values is None:
return
if all(v is None for v in x_snap_values.values()) or all(
v is None for v in y_snap_values.values()
):
# not sure how we got here, but just to be safe...
return
for item in self.plot_item.items: for item in self.plot_item.items:
if isinstance(item, pg.PlotDataItem): if isinstance(item, pg.PlotDataItem):
if x is None or all(v is None for v in y_values): name = item.name()
return x, y = x_snap_values[name], y_snap_values[name]
coordinate_to_emit = ( if x is None or y is None:
round(x, self.precision), continue
[round(y_val, self.precision) for y_val in y_values], self.marker_moved_1d[name].setData([x], [y])
) coordinate_to_emit = (name, round(x, self.precision), round(y, self.precision))
self.coordinatesChanged1D.emit(coordinate_to_emit) self.coordinatesChanged1D.emit(coordinate_to_emit)
for i, y_val in enumerate(y_values):
self.marker_moved_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)],
)
elif isinstance(item, pg.ImageItem): elif isinstance(item, pg.ImageItem):
if x is None or y_values is None: name = item.config.monitor
return x, y = x_snap_values[name], y_snap_values[name]
coordinate_to_emit = (x, y_values) if x is None or y is None:
continue
self.marker_2d.setPos([x, y])
coordinate_to_emit = (name, x, y)
self.coordinatesChanged2D.emit(coordinate_to_emit) self.coordinatesChanged2D.emit(coordinate_to_emit)
else:
continue
def mouse_clicked(self, event): def mouse_clicked(self, event):
"""Handles the mouse clicked event, updating the crosshair position and emitting signals. """Handles the mouse clicked event, updating the crosshair position and emitting signals.
@ -196,7 +211,11 @@ class Crosshair(QObject):
Args: Args:
event: The mouse clicked event event: The mouse clicked event
""" """
self.check_log()
# we only accept left mouse clicks
if event.button() != Qt.MouseButton.LeftButton:
return
if 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) mouse_point = self.plot_item.vb.mapSceneToView(event._scenePos)
x, y = mouse_point.x(), mouse_point.y() x, y = mouse_point.x(), mouse_point.y()
@ -205,31 +224,57 @@ class Crosshair(QObject):
x = 10**x x = 10**x
if self.is_log_y: if self.is_log_y:
y = 10**y y = 10**y
x, y_values = self.snap_to_data(x, y) x_snap_values, y_snap_values = self.snap_to_data(x, y)
if x_snap_values is None or y_snap_values is None:
return
if all(v is None for v in x_snap_values.values()) or all(
v is None for v in y_snap_values.values()
):
# not sure how we got here, but just to be safe...
return
for item in self.plot_item.items: for item in self.plot_item.items:
if isinstance(item, pg.PlotDataItem): if isinstance(item, pg.PlotDataItem):
if x is None or all(v is None for v in y_values): name = item.name()
return x, y = x_snap_values[name], y_snap_values[name]
coordinate_to_emit = ( if x is None or y is None:
round(x, self.precision), continue
[round(y_val, self.precision) for y_val in y_values], self.marker_clicked_1d[name].setData([x], [y])
) coordinate_to_emit = (name, round(x, self.precision), round(y, self.precision))
self.coordinatesClicked1D.emit(coordinate_to_emit) self.coordinatesClicked1D.emit(coordinate_to_emit)
for i, y_val in enumerate(y_values):
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): elif isinstance(item, pg.ImageItem):
if x is None or y_values is None: name = item.config.monitor
return x, y = x_snap_values[name], y_snap_values[name]
coordinate_to_emit = (x, y_values) if x is None or y is None:
continue
self.marker_2d.setPos([x, y])
coordinate_to_emit = (name, x, y)
self.coordinatesClicked2D.emit(coordinate_to_emit) self.coordinatesClicked2D.emit(coordinate_to_emit)
self.marker_2d.setPos([x, y_values]) else:
continue
def clear_markers(self):
"""Clears the markers from the plot."""
for marker in self.marker_moved_1d.values():
marker.clear()
# marker.deleteLater()
for marker in self.marker_clicked_1d.values():
marker.clear()
# marker.deleteLater()
def check_log(self): def check_log(self):
"""Checks if the x or y axis is in log scale and updates the internal state accordingly.""" """Checks if the x or y axis is in log scale and updates the internal state accordingly."""
self.is_log_x = self.plot_item.ctrl.logXCheck.isChecked() self.is_log_x = self.plot_item.ctrl.logXCheck.isChecked()
self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked() self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked()
self.clear_markers()
def check_derivatives(self):
"""Checks if the derivatives are enabled and updates the internal state accordingly."""
self.is_derivative = self.plot_item.ctrl.derivativeCheck.isChecked()
self.clear_markers()
def cleanup(self):
self.v_line.deleteLater()
self.h_line.deleteLater()
self.clear_markers()

View File

@ -7,6 +7,7 @@ from pydantic import BaseModel, Field
from qtpy.QtWidgets import QWidget from qtpy.QtWidgets import QWidget
from bec_widgets.utils import BECConnector, ConnectionConfig from bec_widgets.utils import BECConnector, ConnectionConfig
from bec_widgets.utils.crosshair import Crosshair
class AxisConfig(BaseModel): class AxisConfig(BaseModel):
@ -41,6 +42,18 @@ class SubplotConfig(ConnectionConfig):
) )
class BECViewBox(pg.ViewBox):
def itemBoundsChanged(self, item):
self._itemBoundsCache.pop(item, None)
if (self.state["autoRange"][0] is not False) or (self.state["autoRange"][1] is not False):
# check if the call is coming from a mouse-move event
if hasattr(item, "skip_auto_range") and item.skip_auto_range:
return
self._autoRangeNeedsUpdate = True
self.update()
class BECPlotBase(BECConnector, pg.GraphicsLayout): class BECPlotBase(BECConnector, pg.GraphicsLayout):
USER_ACCESS = [ USER_ACCESS = [
"_config_dict", "_config_dict",
@ -73,9 +86,13 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
pg.GraphicsLayout.__init__(self, parent) pg.GraphicsLayout.__init__(self, parent)
self.figure = parent_figure self.figure = parent_figure
self.plot_item = self.addPlot(row=0, col=0)
# self.plot_item = self.addPlot(row=0, col=0)
self.plot_item = pg.PlotItem(viewBox=BECViewBox(parent=self, enableMenu=True), parent=self)
self.addItem(self.plot_item, row=0, col=0)
self.add_legend() self.add_legend()
self.crosshair = None
def set(self, **kwargs) -> None: def set(self, **kwargs) -> None:
""" """
@ -304,6 +321,25 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
""" """
self.plot_item.enableAutoRange(axis, enabled) self.plot_item.enableAutoRange(axis, enabled)
def hook_crosshair(self) -> None:
"""Hook the crosshair to all plots."""
if self.crosshair is None:
self.crosshair = Crosshair(self.plot_item, precision=3)
def unhook_crosshair(self) -> None:
"""Unhook the crosshair from all plots."""
if self.crosshair is not None:
self.crosshair.cleanup()
self.crosshair.deleteLater()
self.crosshair = None
def toggle_crosshair(self) -> None:
"""Toggle the crosshair on all plots."""
if self.crosshair is None:
return self.hook_crosshair()
self.unhook_crosshair()
def export(self): def export(self):
"""Show the Export Dialog of the plot widget.""" """Show the Export Dialog of the plot widget."""
scene = self.plot_item.scene() scene = self.plot_item.scene()
@ -317,6 +353,7 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
def cleanup_pyqtgraph(self): def cleanup_pyqtgraph(self):
"""Cleanup pyqtgraph items.""" """Cleanup pyqtgraph items."""
self.unhook_crosshair()
item = self.plot_item item = self.plot_item
item.vb.menu.close() item.vb.menu.close()
item.vb.menu.deleteLater() item.vb.menu.deleteLater()

View File

@ -14,7 +14,7 @@ from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtWidgets import QWidget from qtpy.QtWidgets import QWidget
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
from bec_widgets.utils import Colors, EntryValidator from bec_widgets.utils import Colors, Crosshair, EntryValidator
from bec_widgets.widgets.figure.plots.plot_base import BECPlotBase, SubplotConfig from bec_widgets.widgets.figure.plots.plot_base import BECPlotBase, SubplotConfig
from bec_widgets.widgets.figure.plots.waveform.waveform_curve import ( from bec_widgets.widgets.figure.plots.waveform.waveform_curve import (
BECCurve, BECCurve,

View File

@ -9,7 +9,12 @@ from qtpy.QtWidgets import QVBoxLayout, QWidget
from bec_widgets.qt_utils.error_popups import SafeSlot, WarningPopupUtility from bec_widgets.qt_utils.error_popups import SafeSlot, WarningPopupUtility
from bec_widgets.qt_utils.settings_dialog import SettingsDialog from bec_widgets.qt_utils.settings_dialog import SettingsDialog
from bec_widgets.qt_utils.toolbar import IconAction, ModularToolBar, SeparatorAction from bec_widgets.qt_utils.toolbar import (
IconAction,
MaterialIconAction,
ModularToolBar,
SeparatorAction,
)
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.widgets.figure import BECFigure from bec_widgets.widgets.figure import BECFigure
from bec_widgets.widgets.figure.plots.axis_settings import AxisSettings from bec_widgets.widgets.figure.plots.axis_settings import AxisSettings
@ -93,8 +98,11 @@ class BECWaveformWidget(BECWidget, QWidget):
"fit_params": IconAction( "fit_params": IconAction(
icon_path="fitting_parameters.svg", tooltip="Open Fitting Parameters" icon_path="fitting_parameters.svg", tooltip="Open Fitting Parameters"
), ),
"axis_settings": IconAction( "axis_settings": MaterialIconAction(
icon_path="settings.svg", tooltip="Open Configuration Dialog" icon_name="settings", tooltip="Open Configuration Dialog"
),
"crosshair": MaterialIconAction(
icon_name="grid_goldenratio", tooltip="Show Crosshair"
), ),
}, },
target_widget=self, target_widget=self,
@ -123,6 +131,7 @@ class BECWaveformWidget(BECWidget, QWidget):
self.toolbar.widgets["curves"].action.triggered.connect(self.show_curve_settings) self.toolbar.widgets["curves"].action.triggered.connect(self.show_curve_settings)
self.toolbar.widgets["fit_params"].action.triggered.connect(self.show_fit_summary_dialog) self.toolbar.widgets["fit_params"].action.triggered.connect(self.show_fit_summary_dialog)
self.toolbar.widgets["axis_settings"].action.triggered.connect(self.show_axis_settings) self.toolbar.widgets["axis_settings"].action.triggered.connect(self.show_axis_settings)
self.toolbar.widgets["crosshair"].action.triggered.connect(self.waveform.toggle_crosshair)
# self.toolbar.widgets["import"].action.triggered.connect( # self.toolbar.widgets["import"].action.triggered.connect(
# lambda: self.load_config(path=None, gui=True) # lambda: self.load_config(path=None, gui=True)
# ) # )

View File

@ -1,19 +1,40 @@
# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring # pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
import numpy as np import numpy as np
import pyqtgraph as pg import pytest
from qtpy.QtCore import QPointF from qtpy.QtCore import QPointF
from bec_widgets.utils import Crosshair from bec_widgets.widgets.image.image_widget import BECImageWidget
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
from .client_mocks import mocked_client
# pylint: disable = redefined-outer-name
def test_mouse_moved_lines(qtbot): @pytest.fixture
# Create a PlotWidget and add a PlotItem def plot_widget_with_crosshair(qtbot, mocked_client):
plot_widget = pg.PlotWidget(title="1D PlotWidget with multiple curves") widget = BECWaveformWidget(client=mocked_client())
plot_item = plot_widget.getPlotItem() widget.plot(x=[1, 2, 3], y=[4, 5, 6])
plot_item.plot([1, 2, 3], [4, 5, 6]) widget.waveform.hook_crosshair()
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
# Create a Crosshair instance yield widget.waveform.crosshair, widget.waveform.plot_item
crosshair = Crosshair(plot_item=plot_item, precision=2)
@pytest.fixture
def image_widget_with_crosshair(qtbot, mocked_client):
widget = BECImageWidget(client=mocked_client())
widget._image.add_custom_image(name="test", data=np.random.random((100, 200)))
widget._image.hook_crosshair()
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
yield widget._image.crosshair, widget._image.plot_item
def test_mouse_moved_lines(plot_widget_with_crosshair):
crosshair, plot_item = plot_widget_with_crosshair
# Connect the signals to slots that will store the emitted values # Connect the signals to slots that will store the emitted values
emitted_values_1D = [] emitted_values_1D = []
@ -32,14 +53,8 @@ def test_mouse_moved_lines(qtbot):
assert crosshair.h_line.pos().y() == 5 assert crosshair.h_line.pos().y() == 5
def test_mouse_moved_signals(qtbot): def test_mouse_moved_signals(plot_widget_with_crosshair):
# Create a PlotWidget and add a PlotItem crosshair, plot_item = plot_widget_with_crosshair
plot_widget = pg.PlotWidget(title="1D PlotWidget with multiple curves")
plot_item = plot_widget.getPlotItem()
plot_item.plot([1, 2, 3], [4, 5, 6])
# Create a Crosshair instance
crosshair = Crosshair(plot_item=plot_item, precision=2)
# Create a slot that will store the emitted values as tuples # Create a slot that will store the emitted values as tuples
emitted_values_1D = [] emitted_values_1D = []
@ -59,17 +74,11 @@ def test_mouse_moved_signals(qtbot):
crosshair.mouse_moved(event_mock) crosshair.mouse_moved(event_mock)
# Assert the expected behavior # Assert the expected behavior
assert emitted_values_1D == [(2, [5])] assert emitted_values_1D == [("Curve 1", 2, 5)]
def test_mouse_moved_signals_outside(qtbot): def test_mouse_moved_signals_outside(plot_widget_with_crosshair):
# Create a PlotWidget and add a PlotItem crosshair, plot_item = plot_widget_with_crosshair
plot_widget = pg.PlotWidget(title="1D PlotWidget with multiple curves")
plot_item = plot_widget.getPlotItem()
plot_item.plot([1, 2, 3], [4, 5, 6])
# Create a Crosshair instance
crosshair = Crosshair(plot_item=plot_item, precision=2)
# Create a slot that will store the emitted values as tuples # Create a slot that will store the emitted values as tuples
emitted_values_1D = [] emitted_values_1D = []
@ -92,17 +101,9 @@ def test_mouse_moved_signals_outside(qtbot):
assert emitted_values_1D == [] assert emitted_values_1D == []
def test_mouse_moved_signals_2D(qtbot): def test_mouse_moved_signals_2D(image_widget_with_crosshair):
# write similar test for 2D plot crosshair, plot_item = image_widget_with_crosshair
# Create a PlotWidget and add a PlotItem
plot_widget = pg.PlotWidget(title="2D plot with crosshair and ROI square")
data_2D = np.random.random((100, 200))
plot_item = plot_widget.getPlotItem()
image_item = pg.ImageItem(data_2D)
plot_item.addItem(image_item)
# Create a Crosshair instance
crosshair = Crosshair(plot_item=plot_item)
# Create a slot that will store the emitted values as tuples # Create a slot that will store the emitted values as tuples
emitted_values_2D = [] emitted_values_2D = []
@ -118,20 +119,12 @@ def test_mouse_moved_signals_2D(qtbot):
# Call the mouse_moved method # Call the mouse_moved method
crosshair.mouse_moved(event_mock) crosshair.mouse_moved(event_mock)
# Assert the expected behavior # Assert the expected behavior
assert emitted_values_2D == [(22.0, 55.0)] assert emitted_values_2D == [("test", 22.0, 55.0)]
def test_mouse_moved_signals_2D_outside(qtbot): def test_mouse_moved_signals_2D_outside(image_widget_with_crosshair):
# write similar test for 2D plot crosshair, plot_item = image_widget_with_crosshair
# Create a PlotWidget and add a PlotItem
plot_widget = pg.PlotWidget(title="2D plot with crosshair and ROI square")
data_2D = np.random.random((100, 200))
plot_item = plot_widget.getPlotItem()
image_item = pg.ImageItem(data_2D)
plot_item.addItem(image_item)
# Create a Crosshair instance
crosshair = Crosshair(plot_item=plot_item, precision=2)
# Create a slot that will store the emitted values as tuples # Create a slot that will store the emitted values as tuples
emitted_values_2D = [] emitted_values_2D = []