From 17f3bbe646f4bd26919a0dbd270bb0b5c94e78e6 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Wed, 7 May 2025 10:39:05 +0100 Subject: [PATCH] Low res stream during scanning for instant high res captures --- .../things/camera/__init__.py | 21 +++++++++++++++++++ .../things/capture.py | 8 +++++++ .../things/smart_scan.py | 19 +++++++++-------- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/openflexure_microscope_server/things/camera/__init__.py b/src/openflexure_microscope_server/things/camera/__init__.py index 8e095e75..cf765e99 100644 --- a/src/openflexure_microscope_server/things/camera/__init__.py +++ b/src/openflexure_microscope_server/things/camera/__init__.py @@ -79,6 +79,16 @@ class CameraProtocol(Protocol): """Acquire one image from the preview stream and return its size""" ... + def start_highres_streaming(self) -> None: + """Start (or stop and restart) the camera in a configuration for low + res streaming and instant high res captures""" + ... + + def start_streaming(self) -> None: + """Start (or stop and restart) the camera in the default streaming + configuration which balances stream and capture quality""" + ... + class BaseCamera(Thing): """A Thing representing a camera @@ -182,6 +192,17 @@ class CameraStub(BaseCamera): """Acquire one image from the camera and return as a JPEG blob""" raise NotImplementedError("Cameras must not inherit from CameraStub") + @thing_action + def start_highres_streaming(self): + """Start (or stop and restart) the camera in a configuration for low + res streaming and instant high res captures""" + raise NotImplementedError("Cameras must not inherit from CameraStub") + + @thing_action + def start_streaming(self) -> None: + """Start (or stop and restart) the camera in the default streaming + configuration which balances stream and capture quality""" + raise NotImplementedError("Cameras must not inherit from CameraStub") CameraDependency = direct_thing_client_dependency(CameraStub, "/camera/") RawCameraDependency = raw_thing_dependency(CameraProtocol) diff --git a/src/openflexure_microscope_server/things/capture.py b/src/openflexure_microscope_server/things/capture.py index 8d3dc413..7b8d260e 100644 --- a/src/openflexure_microscope_server/things/capture.py +++ b/src/openflexure_microscope_server/things/capture.py @@ -65,6 +65,14 @@ class CaptureThing(Thing): logger.warning(e) raise CaptureError("An error occurred while capturing after 5 attempts") + @thing_action + def stop_streaming(self, cam: CamDep): + cam.stop_streaming() + + @thing_action + def restart_stream(self, cam:CamDep): + cam.restart_stream() + @thing_action def _capture_array(self, cam: CamDep, metadata_getter: GetThingStates, logger: InvocationLogger, stream: str = 'main'): """Capture an image in memory and return it with metadata diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 46799104..61cc00c2 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -182,6 +182,8 @@ class SmartScanThing(Thing): self._scan_images_taken = 0 self._stitch_resize = 1 + self._cam.start_highres_streaming() + # Don't set self._scan_data dictionary. This is done at the start of _run_scan try: @@ -202,6 +204,7 @@ class SmartScanThing(Thing): # Error must be raised so UI gives correct output raise e finally: + self._cam.start_streaming() # However the scan finishes, unset all variables and release lock self._cancel = None self._scan_logger = None @@ -404,19 +407,17 @@ class SmartScanThing(Thing): test_jpg = self._cam.grab_jpeg() test_image = np.array(Image.open(test_jpg.open())) - test_image_res = list(test_image.shape[:2]) + test_image_res = list(test_image.shape) csm_image_res = [int(i) for i in self._csm.image_resolution] - if test_image_res != csm_image_res: - raise RuntimeError( - "Cannot start scan as it is set up to capture with a resolution that " - "has not been mapped.\n" - f"Scan resolution: {test_image_res}\n" - f"camera-stage-mapping resolution {csm_image_res}." - ) + + # If current stream width is different to csm calibration width, + # perform the conversion here + res_ratio = csm_image_res[0] / test_image_res[0] # get displacement matrix. note it is for (y, x) not (x, y) coordinates - csm_disp_matrix = self._csm.image_to_stage_displacement_matrix + csm_disp_matrix = np.array(self._csm.image_to_stage_displacement_matrix) + csm_disp_matrix *= res_ratio # Calculate displacements in image coordinates dx_img = test_image.shape[1] * (1 - overlap)