mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-07-21 10:42:59 +02:00
feat(crosshair): restore frozen ROI profiles when a pin is re-adopted
This commit is contained in:
@@ -137,6 +137,9 @@ class Crosshair(QObject):
|
||||
self.pinned_v_line = None
|
||||
self.pinned_h_line = None
|
||||
self.pinned_pos = None
|
||||
# Last emitted pin payload, kept so the pin can be re-announced to consumers
|
||||
# after it is re-adopted: ("2d", payload) | ("1d", payload) | None.
|
||||
self._pinned_emit = None
|
||||
self._pin_hit_radius_px = 12 # how close (scene px) a click counts as "on the pin"
|
||||
self.update_markers()
|
||||
self.check_log()
|
||||
@@ -648,8 +651,10 @@ class Crosshair(QObject):
|
||||
self._draw_pin(draw_pt[0], draw_pt[1])
|
||||
self.pinned_pos = draw_pt
|
||||
if pinned_2d is not None:
|
||||
self._pinned_emit = ("2d", pinned_2d)
|
||||
self.coordinatesPinned2D.emit(pinned_2d)
|
||||
if pinned_1d is not None:
|
||||
elif pinned_1d is not None:
|
||||
self._pinned_emit = ("1d", pinned_1d)
|
||||
self.coordinatesPinned1D.emit(pinned_1d)
|
||||
|
||||
def _draw_pin(self, x: float, y: float):
|
||||
@@ -715,6 +720,7 @@ class Crosshair(QObject):
|
||||
self.plot_item.removeItem(self.pinned_label)
|
||||
self.pinned_label = None
|
||||
self.pinned_pos = None
|
||||
self._pinned_emit = None
|
||||
if had_pin:
|
||||
self.pinCleared.emit()
|
||||
|
||||
@@ -733,6 +739,7 @@ class Crosshair(QObject):
|
||||
"h_line": self.pinned_h_line,
|
||||
"label": self.pinned_label,
|
||||
"pos": self.pinned_pos,
|
||||
"emit": self._pinned_emit,
|
||||
}
|
||||
# Detach the removal callback: this crosshair is about to be deleted and a
|
||||
# detached pin has no owner to clear it through.
|
||||
@@ -744,6 +751,7 @@ class Crosshair(QObject):
|
||||
self.pinned_h_line = None
|
||||
self.pinned_label = None
|
||||
self.pinned_pos = None
|
||||
self._pinned_emit = None
|
||||
return state
|
||||
|
||||
def adopt_pin(self, state):
|
||||
@@ -759,10 +767,25 @@ class Crosshair(QObject):
|
||||
self.pinned_h_line = state.get("h_line")
|
||||
self.pinned_label = state.get("label")
|
||||
self.pinned_pos = state.get("pos")
|
||||
self._pinned_emit = state.get("emit")
|
||||
# Rebind the removal callback to this crosshair (the previous owner is gone).
|
||||
if self.pinned_point is not None:
|
||||
self.pinned_point._on_remove = self.clear_pin
|
||||
|
||||
def reemit_pin(self):
|
||||
"""Re-announce the current pin's coordinates to consumers.
|
||||
|
||||
Used after :meth:`adopt_pin` so dependent state (e.g. the image ROI panels'
|
||||
frozen reference profiles) can be rebuilt for a pin that survived a toggle.
|
||||
"""
|
||||
if self._pinned_emit is None:
|
||||
return
|
||||
kind, payload = self._pinned_emit
|
||||
if kind == "2d":
|
||||
self.coordinatesPinned2D.emit(payload)
|
||||
elif kind == "1d":
|
||||
self.coordinatesPinned1D.emit(payload)
|
||||
|
||||
def _pin_hit(self, scene_pos) -> bool:
|
||||
"""Return True if a click at ``scene_pos`` (scene pixels) lands on the pinned marker."""
|
||||
if self.pinned_pos is None:
|
||||
|
||||
@@ -658,6 +658,10 @@ class ImageBase(PlotBase):
|
||||
self.crosshair.coordinatesPinned2D.connect(self.pin_image_slices)
|
||||
self.crosshair.pinCleared.connect(self.clear_pinned_slices)
|
||||
self.image_updated.connect(self.update_image_slices)
|
||||
# If a pin survived a previous toggle, rebuild its frozen profiles now that
|
||||
# the consumer slots are connected again.
|
||||
if self.crosshair.pinned_pos is not None:
|
||||
self.crosshair.reemit_pin()
|
||||
else:
|
||||
self.unhook_crosshair()
|
||||
# Hide the ROI panels (the crosshair is destroyed, so its pin signals
|
||||
|
||||
@@ -1095,6 +1095,39 @@ def test_pin_survives_scan_reset(qtbot, mocked_client):
|
||||
assert bec_image_view.y_roi_pinned is not None
|
||||
|
||||
|
||||
def test_pin_and_profiles_restored_after_roi_toggle(qtbot, mocked_client):
|
||||
"""Toggling crosshair-ROI off then on restores both the marker and its frozen profiles."""
|
||||
import numpy as np
|
||||
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
test_data = np.arange(25).reshape(5, 5)
|
||||
bec_image_view.on_image_update_2d({"data": test_data}, {})
|
||||
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
|
||||
|
||||
# Toggle the ROI crosshair off: marker persists, frozen curves are cleared.
|
||||
switch.actions["crosshair_roi"].action.trigger()
|
||||
qtbot.wait(20)
|
||||
assert bec_image_view.crosshair is None
|
||||
assert bec_image_view._detached_pin is not None
|
||||
assert bec_image_view.x_roi_pinned is None
|
||||
|
||||
# Toggle back on: marker is re-adopted AND the frozen profiles are rebuilt.
|
||||
switch.actions["crosshair_roi"].action.trigger()
|
||||
qtbot.wait(20)
|
||||
assert bec_image_view.crosshair is not None
|
||||
assert bec_image_view.crosshair.pinned_point is pin_point
|
||||
assert bec_image_view.x_roi_pinned is not None
|
||||
assert bec_image_view.y_roi_pinned is not None
|
||||
_, x_pinned = bec_image_view.x_roi_pinned.getData()
|
||||
np.testing.assert_array_equal(x_pinned, test_data[:, 3])
|
||||
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user