diff --git a/src/openflexure_microscope_server/things/autofocus.py b/src/openflexure_microscope_server/things/autofocus.py index 45f43dcd..21223f6f 100644 --- a/src/openflexure_microscope_server/things/autofocus.py +++ b/src/openflexure_microscope_server/things/autofocus.py @@ -33,7 +33,7 @@ from labthings_fastapi.dependencies.invocation import InvocationLogger from .camera import RawCameraDependency as Camera from .camera import CameraDependency as WrappedCamera from .stage import StageDependency as Stage -from .capture import CaptureThing +from .capture import CaptureThing, _save_capture import numpy as np from pydantic import BaseModel @@ -310,7 +310,27 @@ class AutofocusThing(Thing): stack_dir, f"{capture_count}.jpeg", ) - capture.capture_jpeg(filename=jpeg_path, cam=cam) + # The pre-existing capture_jpeg + # capture.capture_jpeg(filename=jpeg_path, cam=cam) + + # A new way to get the full array + img = capture.capture_jpeg_array() + + metadata = metadata_getter() + _save_capture( + jpeg_path, + img, + metadata, + logger + ) + + # The old capture and save a "main" res image + # capture._capture_and_save( + # jpeg_path=jpeg_path, + # cam=cam, + # logger=logger, + # metadata_getter=metadata_getter, + # ) # If the stack isn't complete yet, move if capture_count + 1 < images_to_capture: diff --git a/src/openflexure_microscope_server/things/camera/__init__.py b/src/openflexure_microscope_server/things/camera/__init__.py index 8d3fab23..5b4f8c4a 100644 --- a/src/openflexure_microscope_server/things/camera/__init__.py +++ b/src/openflexure_microscope_server/things/camera/__init__.py @@ -77,6 +77,11 @@ class CameraProtocol(Protocol): """Acquire one image from the preview stream and return its size""" ... + def capture_jpeg_array( + self, + ): + ... + class BaseCamera(Thing): """A Thing representing a camera @@ -170,6 +175,12 @@ class CameraStub(BaseCamera): ) -> NDArray: raise NotImplementedError("Cameras must not inherit from CameraStub") + @thing_action + def capture_jpeg_array( + self, + ): + raise NotImplementedError("Cameras must not inherit from CameraStub") + @thing_action def capture_jpeg( self, diff --git a/src/openflexure_microscope_server/things/capture.py b/src/openflexure_microscope_server/things/capture.py index 5c82da95..f99e2791 100644 --- a/src/openflexure_microscope_server/things/capture.py +++ b/src/openflexure_microscope_server/things/capture.py @@ -54,6 +54,11 @@ class CaptureThing(Thing): jpeg = cam.capture_jpeg(resolution="full") jpeg.save(filename) + @thing_action + def capture_jpeg_array(self, cam: CamDep): + """Return a full resolution stream array""" + return cam.capture_jpeg_array() + @thing_action def _capture_array(self, cam: CamDep, metadata_getter: GetThingStates): """Capture an image in memory and return it with metadata @@ -94,3 +99,30 @@ class CaptureThing(Thing): logger.warning(f"Failed to add metadata to {jpeg_path}") except Exception as e: raise IOError(f"An error occurred while saving {jpeg_path}") from e + +def _save_capture( + jpeg_path: str, + image: np.ndarray, + metadata: dict, + logger: InvocationLogger, +) -> None: + """Saving the captured image and metadata to disk + logger warning (via InvocationLogger) is raised if metadata is failed to be added + IOError is raised if the file cannot be saved + nothing is returned on success""" + try: + Image.fromarray(image.astype("uint8"), "RGB").save( + jpeg_path, quality=95, subsampling=0 + ) + try: + exif_dict = piexif.load(jpeg_path) + exif_dict["Exif"][piexif.ExifIFD.UserComment] = json.dumps( + metadata + ).encode("utf-8") + piexif.insert(piexif.dump(exif_dict), jpeg_path) + except: # noqa: E722 + # We need to capture any exception as there are many reasons metadata + # might not be added. We warn rather than log the error. + logger.warning(f"Failed to add metadata to {jpeg_path}") + except Exception as e: + raise IOError(f"An error occurred while saving {jpeg_path}") from e