From 41c01e661215be9092e20f17966e635e03b80eb6 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Fri, 19 Jun 2026 10:46:08 +0200 Subject: [PATCH] feat(crosshair): keep the pin across data/scan resets --- bec_widgets/utils/crosshair.py | 9 +++++-- tests/unit_tests/test_crosshair.py | 9 ++++++- tests/unit_tests/test_image_view_next_gen.py | 27 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/bec_widgets/utils/crosshair.py b/bec_widgets/utils/crosshair.py index a704418c..9390342a 100644 --- a/bec_widgets/utils/crosshair.py +++ b/bec_widgets/utils/crosshair.py @@ -865,18 +865,23 @@ class Crosshair(QObject): @SafeSlot() def reset(self): - """Resets the crosshair to its initial state.""" + """Reset the transient crosshair markers (e.g. on new data / a new scan). + + The pinned marker is intentionally *not* cleared here: it is a persistent + user annotation that survives data and scan changes. Use :meth:`clear_pin` + for an explicit removal and :meth:`cleanup` for full teardown. + """ if self.marker_2d_row is not None: self.plot_item.removeItem(self.marker_2d_row) self.marker_2d_row = None if self.marker_2d_col is not None: self.plot_item.removeItem(self.marker_2d_col) self.marker_2d_col = None - self.clear_pin() self.clear_markers() def cleanup(self): self.reset() + self.clear_pin() self.plot_item.removeItem(self.v_line) self.plot_item.removeItem(self.h_line) self.plot_item.removeItem(self.coord_label) diff --git a/tests/unit_tests/test_crosshair.py b/tests/unit_tests/test_crosshair.py index 1dfa75af..1c73d7b1 100644 --- a/tests/unit_tests/test_crosshair.py +++ b/tests/unit_tests/test_crosshair.py @@ -573,10 +573,17 @@ def test_set_pin_2d_emits_pixel_coordinates(image_widget_with_crosshair): assert (px, py) == (40, 60) -def test_reset_clears_pin(image_widget_with_crosshair): +def test_reset_preserves_pin_cleanup_removes_it(image_widget_with_crosshair): crosshair, _ = image_widget_with_crosshair crosshair.set_pin(40, 60) assert crosshair.pinned_point is not None + + # A data/scan reset keeps the pin (a persistent annotation). crosshair.reset() + assert crosshair.pinned_point is not None + assert crosshair.pinned_pos is not None + + # Full teardown removes it. + crosshair.cleanup() assert crosshair.pinned_point is None assert crosshair.pinned_pos is None diff --git a/tests/unit_tests/test_image_view_next_gen.py b/tests/unit_tests/test_image_view_next_gen.py index fba0ba65..ad12729e 100644 --- a/tests/unit_tests/test_image_view_next_gen.py +++ b/tests/unit_tests/test_image_view_next_gen.py @@ -1070,6 +1070,31 @@ def test_pinned_roi_profiles_keep_style_on_theme_change(qtbot, mocked_client): assert bec_image_view.x_roi_curve.opts["pen"].color().name() == "#000000" +def test_pin_survives_scan_reset(qtbot, mocked_client): + """A scan transition (crosshair.reset()) must not wipe the pin or its frozen profiles.""" + import numpy as np + + bec_image_view = create_widget(qtbot, Image, client=mocked_client) + bec_image_view.on_image_update_2d({"data": np.arange(25).reshape(5, 5)}, {}) + switch = bec_image_view.toolbar.components.get_action("image_switch_crosshair") + switch.actions["crosshair_roi"].action.trigger() + qtbot.wait(50) + + bec_image_view.crosshair.set_pin(2.0, 3.0) + assert bec_image_view.x_roi_pinned is not None + pin_point = bec_image_view.crosshair.pinned_point + assert pin_point is not None + + # Image._handle_scan_change calls crosshair.reset() on each new scan id. + bec_image_view.crosshair.reset() + + # The pin marker and the frozen reference profiles must still be there. + assert bec_image_view.crosshair.pinned_point is pin_point + assert bec_image_view.crosshair.pinned_pos is not None + assert bec_image_view.x_roi_pinned is not None + assert bec_image_view.y_roi_pinned is not None + + def test_detached_pin_can_be_removed_with_right_click(qtbot, mocked_client, monkeypatch): """A pin left on the plot after disabling crosshair remains removable.""" from qtpy.QtWidgets import QMenu @@ -1100,6 +1125,8 @@ def test_detached_pin_can_be_removed_with_right_click(qtbot, mocked_client, monk assert cleared == [True] + + ############################################## # Device selection toolbar sync ##############################################