From ade713b6d08065af121970ebff8e3f1a82695a59 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Wed, 16 Apr 2025 18:14:24 +0100 Subject: [PATCH] Create a stack folder and copy the middle image to image_dir --- .../things/smart_scan.py | 12 +++++- .../things/z_stack.py | 42 +++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index d11d81c3..bf33ad10 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -45,6 +45,7 @@ ZStackDep = direct_thing_client_dependency(ZStackThing, "/z_stack/") IMAGE_REGEX = re.compile(r"-?[0-9]+_-?[0-9]+\.jpeg$") + def unpack_autofocus(scan_data): """Extract z, sharpness data from a move_and_measure call @@ -589,7 +590,16 @@ class SmartScanThing(Thing): current_pos_xyz, imaged=True, focused=focused ) - self._z_stack.smart_stack(images_dir = self._ongoing_scan_images_dir) + site_folder = os.path.join( + self._ongoing_scan_images_dir, + "stacks", + f"{new_pos_xyz[0]}_{new_pos_xyz[1]}", + ) + os.makedirs(site_folder, exist_ok=True) + self._z_stack.smart_stack( + images_dir=self._ongoing_scan_images_dir, + stack_dir=site_folder, + ) # increment capure counter as thread has completed self._scan_images_taken += 1 diff --git a/src/openflexure_microscope_server/things/z_stack.py b/src/openflexure_microscope_server/things/z_stack.py index 9b1edfe6..1e6922e6 100644 --- a/src/openflexure_microscope_server/things/z_stack.py +++ b/src/openflexure_microscope_server/things/z_stack.py @@ -4,6 +4,8 @@ from PIL import Image import time import piexif import json +import shutil +import glob from labthings_fastapi.dependencies.metadata import GetThingStates from labthings_fastapi.thing import Thing @@ -18,12 +20,13 @@ from labthings_fastapi.dependencies.invocation import ( class CaptureError(RuntimeError): """An error trying to capture from Picamera""" + class ZStackThing(Thing): @thing_property def images_to_capture(self) -> int: """The number of images to capture and save in a stack Defaults to 1 unless you need to see either side of focus""" - return self.thing_settings.get("stack_height", 9) + return self.thing_settings.get("images_to_capture", 1) @images_to_capture.setter def images_to_capture(self, value: int) -> None: @@ -46,9 +49,10 @@ class ZStackThing(Thing): self, cam: CamDep, stage: StageDep, - images_dir: str, logger: InvocationLogger, metadata_getter: GetThingStates, + images_dir: str, + stack_dir: str, ) -> None: stack_dz = self.stack_dz images_to_capture = self.images_to_capture @@ -58,13 +62,19 @@ class ZStackThing(Thing): for capture_count in range(images_to_capture): jpeg_path = os.path.join( - images_dir, + stack_dir, f"{capture_count}.jpeg", ) self._capture_and_save( - jpeg_path, cam, logger, metadata_getter, + jpeg_path=jpeg_path, + cam=cam, + logger=logger, + metadata_getter=metadata_getter, ) stage.move_relative(z=stack_dz) + time.sleep(0.3) + + self.copy_central_image(images_dir, stack_dir, logger) def _capture_and_save( self, @@ -79,7 +89,10 @@ class ZStackThing(Thing): that the stage may be moved while it's saved. """ capture_start = time.time() - image, metadata = self._capture_image(cam, metadata_getter,) + image, metadata = self._capture_image( + cam, + metadata_getter, + ) acquisition_time = time.time() self._save_capture(jpeg_path, image, metadata, logger) save_time = time.time() @@ -91,8 +104,6 @@ class ZStackThing(Thing): def _capture_image(self, cam, metadata_getter) -> tuple[np.ndarray, dict]: """Capture an image in memory and return it with metadata - This will set the event `acquired` once the image has been acquired, so - that the stage may be moved while it's saved. CaptureError raised if the capture fails for any reason returns tuple with numpy array of image data, and dict of metadata """ @@ -130,3 +141,20 @@ class ZStackThing(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 copy_central_image( + self, + images_dir: str, + stack_dir: str, + logger: InvocationLogger, + ): + """Gets a list of images in a folder (stack_dir), sorts them, and copies the central image + to images dir.""" + image_list = glob.glob(os.path.join(stack_dir, "*")) + image_list.sort() + central_index = (len(image_list) - 1) // 2 + central_image = image_list[central_index] + logger.warning(central_image) + xy_location = os.path.basename(stack_dir) + + shutil.copy(central_image, os.path.join(images_dir, f"{xy_location}.jpeg"))