Test stack params at start of scan main loop

This commit is contained in:
jaknapper 2025-09-24 16:03:26 +01:00 committed by Joe Knapper
parent 4345f8c336
commit 18e49ba50c
2 changed files with 54 additions and 24 deletions

View file

@ -10,7 +10,7 @@ See repository root for licensing information.
from contextlib import contextmanager from contextlib import contextmanager
import logging import logging
import time import time
from typing import Annotated, Mapping, Optional, Sequence, Literal from typing import Annotated, Mapping, Optional, Sequence, Literal, Type
import os import os
from dataclasses import dataclass from dataclasses import dataclass
@ -44,6 +44,7 @@ class StackParams:
autofocus_dz: int, autofocus_dz: int,
images_dir: str, images_dir: str,
save_resolution: tuple[int, int], save_resolution: tuple[int, int],
logger: lt.deps.InvocationLogger,
) -> None: ) -> None:
"""Initialise the parameters. All arguments are keyword only. """Initialise the parameters. All arguments are keyword only.
@ -58,7 +59,18 @@ class StackParams:
:param autofocus_dz: The number of steps in a full autofocus (when required) :param autofocus_dz: The number of steps in a full autofocus (when required)
:param images_dir: The directory to save images to disk :param images_dir: The directory to save images to disk
:param save_resolution: The resolution to save the captures to disk with :param save_resolution: The resolution to save the captures to disk with
:param logger: A logger passed from the calling Thing
""" """
if min_images_to_test < 3:
logger.warning(
"Can't test for focus with fewer than 3 images. Running scan with test images = 3"
)
min_images_to_test = 3
elif min_images_to_test > 9:
logger.warning(
"Testing with more than 9 images is likely to focus on the cover slip, or strike the sample. Running scan with test images = 9"
)
min_images_to_test = 9
if min_images_to_test < images_to_save: if min_images_to_test < images_to_save:
raise ValueError("Can't save more images than the minimum number tested") raise ValueError("Can't save more images than the minimum number tested")
if min_images_to_test % 2 == 0 or min_images_to_test <= 0: if min_images_to_test % 2 == 0 or min_images_to_test <= 0:
@ -467,7 +479,7 @@ class AutofocusThing(lt.Thing):
enough more images may be captured. After new images are captured the number sets enough more images may be captured. After new images are captured the number sets
the number of images used for checking if focus is central. the number of images used for checking if focus is central.
Defaults to 9 which balances reliability and speed/ Defaults to 9 which balances reliability and speed.
""" """
stack_dz = lt.ThingSetting(initial_value=50, model=int) stack_dz = lt.ThingSetting(initial_value=50, model=int)
@ -480,15 +492,40 @@ class AutofocusThing(lt.Thing):
* 200 for 20x * 200 for 20x
""" """
@lt.thing_action
def set_stack_params(
self,
images_dir: str,
autofocus_dz: int,
save_resolution: tuple[int, int],
logger: lt.deps.InvocationLogger,
) -> Type[StackParams]:
"""Set up the parameters used for all stacks in a scan.
:param images_dir: the folder to save all images
:param autofocus_dz: the range to autofocus over if a stack fails
:param save_resolution: The resolution to save the captures to disk with
:param logger: A logger passed from the calling Thing
:returns: A StackParams object with the required parameters.
"""
return StackParams(
stack_dz=self.stack_dz,
images_to_save=self.stack_images_to_save,
min_images_to_test=self.stack_min_images_to_test,
autofocus_dz=autofocus_dz,
images_dir=images_dir,
save_resolution=save_resolution,
logger=logger,
)
@lt.thing_action @lt.thing_action
def run_smart_stack( def run_smart_stack(
self, self,
cam: CameraClient, cam: CameraClient,
stage: Stage, stage: Stage,
sharpness_monitor: SharpnessMonitorDep, sharpness_monitor: SharpnessMonitorDep,
images_dir: str, stack_parameters: Type[StackParams],
autofocus_dz: int,
save_resolution: tuple[int, int],
) -> tuple[bool, int]: ) -> tuple[bool, int]:
"""Run a smart stack. """Run a smart stack.
@ -503,27 +540,14 @@ class AutofocusThing(lt.Thing):
:param stage: Stage Dependency supplied by LabThings dependency injection :param stage: Stage Dependency supplied by LabThings dependency injection
:param sharpness_monitor: Sharpness Monitor Dependency (for focus detection) :param sharpness_monitor: Sharpness Monitor Dependency (for focus detection)
supplied by LabThings dependency injection supplied by LabThings dependency injection
:param images_dir: the folder to save all images :param stack_parameters: A StackParams object containing the required
:param autofocus_dz: the range to autofocus over if a stack fails parameters to run a stack.
:param save_resolution: The resolution the images should be saved at, the
images will be resampled if this doesn't match the camera's capture
resolution
:returns: A tuple containing: :returns: A tuple containing:
* A boolean, True if stack was successfully * A boolean, True if stack was successfully
* The z position of the sharpest image * The z position of the sharpest image
""" """
# Set the variables to prevent changes from the GUI or other windows
stack_parameters = StackParams(
stack_dz=self.stack_dz,
images_to_save=self.stack_images_to_save,
min_images_to_test=self.stack_min_images_to_test,
autofocus_dz=autofocus_dz,
images_dir=images_dir,
save_resolution=save_resolution,
)
tries = 0 tries = 0
# Loop until a stack is successful # Loop until a stack is successful
while tries < stack_parameters.max_attempts: while tries < stack_parameters.max_attempts:

View file

@ -26,7 +26,7 @@ from openflexure_microscope_server import scan_planners
from openflexure_microscope_server import stitching from openflexure_microscope_server import stitching
# Things # Things
from .autofocus import AutofocusThing from .autofocus import AutofocusThing, StackParams
from .camera_stage_mapping import CameraStageMapper from .camera_stage_mapping import CameraStageMapper
from .camera import CameraDependency as CameraClient from .camera import CameraDependency as CameraClient
from .stage import StageDependency as StageDep from .stage import StageDependency as StageDep
@ -121,6 +121,7 @@ class SmartScanThing(lt.Thing):
self._stage: Optional[StageDep] = None self._stage: Optional[StageDep] = None
self._cam: Optional[CameraClient] = None self._cam: Optional[CameraClient] = None
self._csm: Optional[CSMDep] = None self._csm: Optional[CSMDep] = None
self._stack_params: Optional[StackParams] = None
self._ongoing_scan: Optional[scan_directories.ScanDirectory] = None self._ongoing_scan: Optional[scan_directories.ScanDirectory] = None
self._scan_data: Optional[scan_directories.ScanData] = None self._scan_data: Optional[scan_directories.ScanData] = None
@ -189,6 +190,7 @@ class SmartScanThing(lt.Thing):
self._scan_lock.release() self._scan_lock.release()
# Ensure any PreviewStitcher created cannot be reused. # Ensure any PreviewStitcher created cannot be reused.
self._preview_stitcher = None self._preview_stitcher = None
self._stack_params = None
# Remove any scan folders containing zero images. # Remove any scan folders containing zero images.
self.purge_empty_scans(logger=logger) self.purge_empty_scans(logger=logger)
@ -363,6 +365,12 @@ class SmartScanThing(lt.Thing):
self._cam.start_streaming(main_resolution=(3280, 2464)) self._cam.start_streaming(main_resolution=(3280, 2464))
self._scan_data = self._collect_scan_data() self._scan_data = self._collect_scan_data()
self._ongoing_scan.save_scan_data(self._scan_data) self._ongoing_scan.save_scan_data(self._scan_data)
self._stack_params = self._autofocus.set_stack_params(
images_dir=self._ongoing_scan.images_dir,
autofocus_dz=self.autofocus_dz,
save_resolution=self._scan_data.save_resolution,
logger=self._scan_logger,
)
self._preview_stitcher = stitching.PreviewStitcher( self._preview_stitcher = stitching.PreviewStitcher(
self._ongoing_scan.images_dir, self._ongoing_scan.images_dir,
overlap=self._scan_data.overlap, overlap=self._scan_data.overlap,
@ -451,9 +459,7 @@ class SmartScanThing(lt.Thing):
continue continue
focused, focused_height = self._autofocus.run_smart_stack( focused, focused_height = self._autofocus.run_smart_stack(
images_dir=self._ongoing_scan.images_dir, stack_parameters=self._stack_params
autofocus_dz=self._scan_data.autofocus_dz,
save_resolution=self._scan_data.save_resolution,
) )
current_pos_xyz = (new_pos_xyz[0], new_pos_xyz[1], focused_height) current_pos_xyz = (new_pos_xyz[0], new_pos_xyz[1], focused_height)