fix(LamNI): decouple smear composite display from live acquisition channel
The "camera running" toggle (IDSCamera.live_mode_enabled) was flapping
on/off during the smear sweep, and on real hardware that's genuinely
slow (starting/stopping the live acquisition thread has real driver
overhead, unlike the simulator). Root cause: composite pushes shared
the same device_preview channel the live thread continuously publishes
to, so live_mode_enabled had to be toggled off around every push to
keep the composite from being instantly overwritten.
Give the composite its own channel instead:
- IDSCamera gains a smear_preview PreviewSignal (no rotation_90/
transpose configured -- composite data is already display-oriented)
and push_smear_preview(), mirroring push_preview_image() but fully
decoupled from the live channel.
- XRayEye gains set_live_view_signal() to switch which camera signal
the live Image view displays, defaulting to the normal channel for
every fresh GUI instance; on_live_view_enabled() now uses whichever
signal is currently selected instead of a hardcoded constant.
- _smear_sweep() switches the GUI to "smear_preview" for the sweep and
pushes composite updates there -- live_mode_enabled is never touched
at all (just ensured on at the start, exactly like _live_sweep()).
The GUI is deliberately left on the composite after a successful
sweep until the caller has collected the operator's click, then
switched back to "image"; on KeyboardInterrupt it's switched back
immediately.
This removes the resume_live/hold_display_s toggle-avoidance mechanism
added in def9bf4, which is no longer needed with a dedicated channel.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -380,22 +380,18 @@ def _report_with_status_sequence(*statuses):
|
||||
return report
|
||||
|
||||
|
||||
def test_push_smear_composite_duty_cycles_live_mode(bec_client_mock):
|
||||
def test_push_smear_composite_uses_dedicated_channel(bec_client_mock):
|
||||
client = bec_client_mock
|
||||
align, dev_mock = _make_calibration_align(client)
|
||||
composite = np.zeros((2, 2), dtype=np.uint8)
|
||||
|
||||
with mock.patch(f"{XRAY_EYE_ALIGN}.dev", dev_mock):
|
||||
with mock.patch(f"{XRAY_EYE_ALIGN}.time.sleep"):
|
||||
align._push_smear_composite(composite, hold_display_s=0.4)
|
||||
align._push_smear_composite(composite)
|
||||
|
||||
# live view must be turned off before the composite is pushed (so the
|
||||
# background live-mode thread can't overwrite it) and back on after.
|
||||
assert [c.args[0] for c in dev_mock.cam_xeye.live_mode_enabled.put.call_args_list] == [
|
||||
False,
|
||||
True,
|
||||
]
|
||||
dev_mock.cam_xeye.push_preview_image.assert_called_once_with(composite)
|
||||
# pushed via the dedicated smear_preview channel -- live_mode_enabled
|
||||
# ("camera running") is never touched to publish it.
|
||||
dev_mock.cam_xeye.push_smear_preview.assert_called_once_with(composite)
|
||||
dev_mock.cam_xeye.live_mode_enabled.put.assert_not_called()
|
||||
|
||||
|
||||
def test_smear_sweep_max_projection(bec_client_mock):
|
||||
@@ -418,6 +414,11 @@ def test_smear_sweep_max_projection(bec_client_mock):
|
||||
# exactly 3 frames were grabbed (get_last_image's side_effect list is
|
||||
# exhausted) -- no extra grabs happened after status went COMPLETED.
|
||||
assert dev_mock.cam_xeye.get_last_image.call_count == 3
|
||||
# never toggled off -- that's the whole point of the dedicated channel
|
||||
dev_mock.cam_xeye.live_mode_enabled.put.assert_not_called()
|
||||
# GUI switched to the composite channel, and NOT switched back yet --
|
||||
# that's the caller's job, after collecting the operator's click.
|
||||
align.gui.set_live_view_signal.assert_called_once_with("smear_preview")
|
||||
|
||||
|
||||
def test_smear_sweep_issues_single_continuous_move(bec_client_mock):
|
||||
@@ -468,4 +469,31 @@ def test_smear_sweep_cancels_report_on_keyboard_interrupt(bec_client_mock):
|
||||
align._smear_sweep()
|
||||
|
||||
report.cancel.assert_called_once()
|
||||
dev_mock.cam_xeye.live_mode_enabled.put.assert_any_call(True)
|
||||
# GUI switched back to the normal live channel since there's nothing to
|
||||
# click on when interrupted mid-sweep.
|
||||
align.gui.set_live_view_signal.assert_any_call("image")
|
||||
|
||||
|
||||
def test_find_rotation_center_smear_experimental_switches_live_view_signal(bec_client_mock):
|
||||
"""Regression guard: the GUI must stay on the composite channel until
|
||||
AFTER the operator's click is collected, then switch back -- switching
|
||||
back inside _smear_sweep itself (before the click) would show the
|
||||
operator a live raw frame instead of the composite to click on."""
|
||||
client = bec_client_mock
|
||||
align, dev_mock = _make_calibration_align(client)
|
||||
|
||||
dev_mock.omny_xray_gui.xval_x_1.get.return_value = 100.0
|
||||
dev_mock.omny_xray_gui.yval_y_1.get.return_value = 0.0
|
||||
dev_mock.cam_xeye.get_last_image.return_value = np.zeros((2, 2), dtype=np.uint8)
|
||||
report = _report_with_status_sequence("RUNNING", "COMPLETED")
|
||||
|
||||
with mock.patch(f"{XRAY_EYE_ALIGN}.dev", dev_mock):
|
||||
with mock.patch(f"{XRAY_EYE_ALIGN}.umv"):
|
||||
with mock.patch(f"{XRAY_EYE_ALIGN}.mv", return_value=report):
|
||||
with mock.patch(f"{XRAY_EYE_ALIGN}.time.sleep"):
|
||||
with mock.patch(f"{XRAY_EYE_ALIGN}.input", side_effect=["y", "y"]):
|
||||
align.find_rotation_center_smear_experimental()
|
||||
|
||||
calls = [c.args[0] for c in align.gui.set_live_view_signal.call_args_list]
|
||||
assert calls == ["smear_preview", "image"]
|
||||
dev_mock.cam_xeye.live_mode_enabled.put.assert_not_called()
|
||||
Reference in New Issue
Block a user