diff --git a/csaxs_bec/bec_ipython_client/plugins/LamNI/x_ray_eye_align.py b/csaxs_bec/bec_ipython_client/plugins/LamNI/x_ray_eye_align.py index 87e946a..1f26e5a 100644 --- a/csaxs_bec/bec_ipython_client/plugins/LamNI/x_ray_eye_align.py +++ b/csaxs_bec/bec_ipython_client/plugins/LamNI/x_ray_eye_align.py @@ -674,9 +674,15 @@ class XrayEyeAlign: between this push and the next raw frame the live thread would otherwise publish to the same PreviewSignal/device_preview channel the GUI is subscribed to. + + dev.cam_xeye.image is a PreviewSignal, which is not remotely + writable from client code (BEC excludes BECMessageSignal + subclasses from the client-side device RPC proxy) -- push via the + push_preview_image() device method instead, which runs + device-server side where self.image is a normal ophyd attribute. """ dev.cam_xeye.live_mode_enabled.put(False) - dev.cam_xeye.image.put(composite) + dev.cam_xeye.push_preview_image(composite) time.sleep(hold_display_s) dev.cam_xeye.live_mode_enabled.put(True) diff --git a/csaxs_bec/devices/ids_cameras/ids_camera.py b/csaxs_bec/devices/ids_cameras/ids_camera.py index a5bf68e..c853818 100644 --- a/csaxs_bec/devices/ids_cameras/ids_camera.py +++ b/csaxs_bec/devices/ids_cameras/ids_camera.py @@ -51,7 +51,14 @@ class IDSCamera(PSIDeviceBase): kind=Kind.config, ) - USER_ACCESS = ["start_live_mode", "stop_live_mode", "mask", "set_rect_roi", "get_last_image"] + USER_ACCESS = [ + "start_live_mode", + "stop_live_mode", + "mask", + "set_rect_roi", + "get_last_image", + "push_preview_image", + ] def __init__( self, @@ -195,6 +202,20 @@ class IDSCamera(PSIDeviceBase): if image: return image.data + def push_preview_image(self, data: np.ndarray) -> None: + """Push an arbitrary array to this camera's own preview signal. + + self.image is a PreviewSignal, which is deliberately excluded from + the client-side device RPC/signal proxy (rpc_access defaults to + False for BECMessageSignal subclasses, with no override) -- so + client code cannot do dev.cam_xeye.image.put(...) directly. This + method runs device-server side, where self.image is a normal ophyd + attribute, and is the supported way for a client to publish a + custom (e.g. composite/processed) image through the same + device_preview channel the GUI already subscribes to. + """ + self.image.put(data) + ############## User Interface Methods ############## def on_connected(self): diff --git a/tests/tests_bec_ipython_client/test_x_ray_eye_align.py b/tests/tests_bec_ipython_client/test_x_ray_eye_align.py index 5930066..005124d 100644 --- a/tests/tests_bec_ipython_client/test_x_ray_eye_align.py +++ b/tests/tests_bec_ipython_client/test_x_ray_eye_align.py @@ -395,7 +395,7 @@ def test_push_smear_composite_duty_cycles_live_mode(bec_client_mock): False, True, ] - dev_mock.cam_xeye.image.put.assert_called_once_with(composite) + dev_mock.cam_xeye.push_preview_image.assert_called_once_with(composite) def test_smear_sweep_max_projection(bec_client_mock):