A start to implementing ScanWorkflow
This commit is contained in:
parent
5762fc5947
commit
5ce74cad8a
2 changed files with 60 additions and 8 deletions
50
src/openflexure_microscope_server/things/scan_workflows.py
Normal file
50
src/openflexure_microscope_server/things/scan_workflows.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from typing import Mapping, Optional
|
||||
|
||||
import labthings_fastapi as lt
|
||||
|
||||
from openflexure_microscope_server.scan_planners import ScanPlanner, SmartSpiral
|
||||
from openflexure_microscope_server.things.autofocus import AutofocusThing
|
||||
from openflexure_microscope_server.things.background_detect import (
|
||||
BackgroundDetectAlgorithm,
|
||||
ChannelDeviationLUV,
|
||||
)
|
||||
|
||||
|
||||
class ScanWorkflow(lt.Thing):
|
||||
"""A base class for all Scanworkflows.
|
||||
|
||||
Scan workflows set the behaviour of a scan, inclduing the background detection,
|
||||
scan planning, aquisition routine.
|
||||
"""
|
||||
|
||||
_detector: Optional[BackgroundDetectAlgorithm | Mapping[str, BackgroundDetectAlgorithm]] = lt.thing_slot()
|
||||
_planner_cls: type[ScanPlanner]
|
||||
|
||||
|
||||
@lt.property
|
||||
def ready(self) -> bool:
|
||||
"""Whether this scanworkflow is ready to start."""
|
||||
raise NotImplementedError(
|
||||
"Each specific ScanWorkflow must implement a ready property."
|
||||
)
|
||||
|
||||
def pre_scan_routine(self) -> None:
|
||||
raise NotImplementedError(
|
||||
"Each specific ScanWorkflow must implement a pre-scan routine."
|
||||
)
|
||||
|
||||
class HistoScanWorkflow(ScanWorkflow):
|
||||
_detector: ChannelDeviationLUV = lt.thing_slot()
|
||||
_planner_cls: type[ScanPlanner] = SmartSpiral
|
||||
_autofocus: AutofocusThing = lt.thing_slot()
|
||||
|
||||
autofocus_dz: int = lt.setting(default=1000)
|
||||
"""The z distance to perform an autofocus in steps."""
|
||||
|
||||
@lt.property
|
||||
def ready(self) -> bool:
|
||||
"""Whether this scanworkflow is ready to start."""
|
||||
return self._detector.ready
|
||||
|
||||
def pre_scan_routine(self) -> None:
|
||||
self._autofocus.looping_autofocus(dz=self.autofocus_dz, start="centre")
|
||||
|
|
@ -35,6 +35,7 @@ from openflexure_microscope_server import scan_directories, scan_planners, stitc
|
|||
from .autofocus import AutofocusThing, StackParams
|
||||
from .camera import BaseCamera
|
||||
from .camera_stage_mapping import CameraStageMapper, CSMUncalibratedError
|
||||
from .scan_workflows import HistoScanWorkflow, ScanWorkflow
|
||||
from .stage import BaseStage
|
||||
|
||||
T = TypeVar("T")
|
||||
|
|
@ -103,6 +104,7 @@ class SmartScanThing(lt.Thing):
|
|||
_cam: BaseCamera = lt.thing_slot()
|
||||
_csm: CameraStageMapper = lt.thing_slot()
|
||||
_stage: BaseStage = lt.thing_slot()
|
||||
_workflow: HistoScanWorkflow = lt.thing_slot()
|
||||
|
||||
def __init__(
|
||||
self, thing_server_interface: lt.ThingServerInterface, scans_folder: str
|
||||
|
|
@ -195,7 +197,6 @@ class SmartScanThing(lt.Thing):
|
|||
self._check_background_and_csm_set()
|
||||
self._ongoing_scan = self._scan_dir_manager.new_scan_dir(scan_name)
|
||||
self._latest_scan_name = self.ongoing_scan.name
|
||||
self._autofocus.looping_autofocus(dz=self.autofocus_dz, start="centre")
|
||||
self._run_scan()
|
||||
except Exception as e:
|
||||
# If _scan_data is set then scan started
|
||||
|
|
@ -388,8 +389,11 @@ class SmartScanThing(lt.Thing):
|
|||
starting x,y,z position.
|
||||
"""
|
||||
try:
|
||||
# probably make workflow a context manager with a lock?
|
||||
workflow = self._workflow
|
||||
self._cam.start_streaming(main_resolution=(3280, 2464))
|
||||
self._scan_data = self._collect_scan_data()
|
||||
workflow.pre_scan_routine(self._scan_data)
|
||||
self.ongoing_scan.save_scan_data(self._scan_data)
|
||||
images_dir = self.ongoing_scan.images_dir
|
||||
if images_dir is None:
|
||||
|
|
@ -398,7 +402,7 @@ class SmartScanThing(lt.Thing):
|
|||
)
|
||||
self._stack_params = self._autofocus.create_stack_params(
|
||||
images_dir=images_dir,
|
||||
autofocus_dz=self.autofocus_dz,
|
||||
autofocus_dz=self.scan_data.autofocus_dz,
|
||||
save_resolution=self.scan_data.save_resolution,
|
||||
)
|
||||
self._preview_stitcher = stitching.PreviewStitcher(
|
||||
|
|
@ -408,7 +412,7 @@ class SmartScanThing(lt.Thing):
|
|||
)
|
||||
|
||||
# This is the main loop of the scan!
|
||||
self._main_scan_loop()
|
||||
self._main_scan_loop(workflow)
|
||||
self._save_final_scan_data(scan_result="success")
|
||||
|
||||
except lt.exceptions.InvocationCancelledError:
|
||||
|
|
@ -437,7 +441,7 @@ class SmartScanThing(lt.Thing):
|
|||
self._perform_final_stitch()
|
||||
|
||||
@_scan_running
|
||||
def _main_scan_loop(self) -> None:
|
||||
def _main_scan_loop(self, workflow: ScanWorkflow) -> None:
|
||||
"""Run the main loop of the scan.
|
||||
|
||||
This loop runs during a scan, until no more scan x,y positions
|
||||
|
|
@ -580,10 +584,8 @@ class SmartScanThing(lt.Thing):
|
|||
skip_background: bool = lt.setting(default=True)
|
||||
"""Whether to detect and skip empty fields of view.
|
||||
|
||||
This uses the settings from the ``BackgroundDetectThing``."""
|
||||
|
||||
autofocus_dz: int = lt.setting(default=1000)
|
||||
"""The z distance to perform an autofocus in steps."""
|
||||
This uses the settings from the ``BackgroundDetectThing``.
|
||||
"""
|
||||
|
||||
overlap: float = lt.setting(default=0.45)
|
||||
"""The fraction (0-1) that adjacent images should overlap in x or y."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue