Stack parameter coercion in create_stack_params

Co-authored with Julian Stirling
This commit is contained in:
Joe Knapper 2025-10-09 12:22:42 +01:00
parent 33ff356f76
commit fa6fdb1a67
2 changed files with 53 additions and 8 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, Type from typing import Annotated, Mapping, Optional, Sequence, Literal
import os import os
from dataclasses import dataclass from dataclasses import dataclass
@ -105,7 +105,7 @@ class StackParams(BaseModel):
return self return self
# Note MyPy doesn't support decorating properties. See MyPy Pull #16571 and issue #14461. # Note MyPy doesn't support decorating properties. See MyPy Pull #16571 and issue #14461.
@computed_field # type: ignore[prop-decorator] @computed_field # type: ignore[prop-decorator]
@property @property
def stack_z_range(self) -> int: def stack_z_range(self) -> int:
"""The range of the z stack, in steps. """The range of the z stack, in steps.
@ -116,7 +116,7 @@ class StackParams(BaseModel):
""" """
return self.stack_dz * (self.min_images_to_test - 1) return self.stack_dz * (self.min_images_to_test - 1)
@computed_field # type: ignore[prop-decorator] @computed_field # type: ignore[prop-decorator]
@property @property
def steps_undershoot(self) -> int: def steps_undershoot(self) -> int:
"""The distance to deliberately undershoot the estimated optimal starting point.""" """The distance to deliberately undershoot the estimated optimal starting point."""
@ -126,7 +126,7 @@ class StackParams(BaseModel):
# requires extra +z movements and captures. # requires extra +z movements and captures.
return self.stack_dz * self.img_undershoot return self.stack_dz * self.img_undershoot
@computed_field # type: ignore[prop-decorator] @computed_field # type: ignore[prop-decorator]
@property @property
def max_images_to_test(self) -> int: def max_images_to_test(self) -> int:
"""The maximum number of images that will be captured and tested in a stack. """The maximum number of images that will be captured and tested in a stack.
@ -487,7 +487,7 @@ class AutofocusThing(lt.Thing):
""" """
@lt.thing_action @lt.thing_action
def set_stack_params( def create_stack_params(
self, self,
images_dir: str, images_dir: str,
autofocus_dz: int, autofocus_dz: int,
@ -503,6 +503,53 @@ class AutofocusThing(lt.Thing):
:returns: A StackParams object with the required parameters. :returns: A StackParams object with the required parameters.
""" """
# Coerce min_images_to_test parameter
min_images_to_test = self.stack_min_images_to_test
if min_images_to_test < MIN_TEST_IMAGE_COUNT:
logger.warning(
f"Cannot test only {min_images_to_test} image(s) as this will fail. "
"Setting min images to test to lowest possible value of"
f"{MIN_TEST_IMAGE_COUNT}."
)
min_images_to_test = MIN_TEST_IMAGE_COUNT
elif min_images_to_test > MAX_TEST_IMAGE_COUNT:
logger.warning(
f"Testing {min_images_to_test} images will cause defocus. "
"Setting min images to test to highest possible value of "
f"{MAX_TEST_IMAGE_COUNT}."
)
min_images_to_test = MAX_TEST_IMAGE_COUNT
elif min_images_to_test % 2 == 0:
min_images_to_test += 1
logger.warning(
"Minimum number of images to test should be odd, setting to "
f"{min_images_to_test}."
)
# Set the Thing property to the coerced value
self.stack_min_images_to_test = min_images_to_test
# Coerce the images to save parameter to be positive, odd, and less than
# min_images_to_save
images_to_save = self.stack_images_to_save
if images_to_save <= 0:
logger.warning(
"At least 1 images must be saved. Setting images to save to 1."
)
images_to_save = 1
elif images_to_save > min_images_to_test:
logger.warning(
f"Cannot save {min_images_to_test} images as this above the minimum "
f"number to test. Setting images to save to {MAX_TEST_IMAGE_COUNT}."
)
images_to_save = min_images_to_test
elif images_to_save % 2 == 0:
images_to_save += 1
logger.warning(
f"Images to save should be odd, setting to {images_to_save}."
)
# Set the Thing property to the coerced value
self.stack_images_to_save = images_to_save
return StackParams( return StackParams(
stack_dz=self.stack_dz, stack_dz=self.stack_dz,
images_to_save=self.stack_images_to_save, images_to_save=self.stack_images_to_save,
@ -510,7 +557,6 @@ class AutofocusThing(lt.Thing):
autofocus_dz=autofocus_dz, autofocus_dz=autofocus_dz,
images_dir=images_dir, images_dir=images_dir,
save_resolution=save_resolution, save_resolution=save_resolution,
logger=logger,
) )
@lt.thing_action @lt.thing_action

View file

@ -122,7 +122,6 @@ class SmartScanThing(lt.Thing):
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._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
self._preview_stitcher: Optional[stitching.PreviewStitcher] = None self._preview_stitcher: Optional[stitching.PreviewStitcher] = None
@ -365,7 +364,7 @@ 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( self._stack_params = self._autofocus.create_stack_params(
images_dir=self._ongoing_scan.images_dir, images_dir=self._ongoing_scan.images_dir,
autofocus_dz=self.autofocus_dz, autofocus_dz=self.autofocus_dz,
save_resolution=self._scan_data.save_resolution, save_resolution=self._scan_data.save_resolution,