Simplify planners a little to consolidate snake and raster
This commit is contained in:
parent
1221b6ca02
commit
9e477a26d8
2 changed files with 25 additions and 54 deletions
|
|
@ -304,7 +304,7 @@ class ScanPlanner:
|
||||||
return [FutureScanLocation(location) for line in grid for location in line]
|
return [FutureScanLocation(location) for line in grid for location in line]
|
||||||
|
|
||||||
|
|
||||||
class RectangleScan(ScanPlanner):
|
class RectGridPlanner(ScanPlanner):
|
||||||
"""Base class for planners that operate on a rectangular grid."""
|
"""Base class for planners that operate on a rectangular grid."""
|
||||||
|
|
||||||
_dx: int = 0
|
_dx: int = 0
|
||||||
|
|
@ -312,7 +312,7 @@ class RectangleScan(ScanPlanner):
|
||||||
|
|
||||||
def _parse(self, planner_settings: Optional[dict] = None) -> None:
|
def _parse(self, planner_settings: Optional[dict] = None) -> None:
|
||||||
expected_keys = ["dx", "dy"]
|
expected_keys = ["dx", "dy"]
|
||||||
invalid_msg = "RectangleScan requires planner_settings with keys: "
|
invalid_msg = "RectGridPlanner requires planner_settings with keys: "
|
||||||
if not planner_settings:
|
if not planner_settings:
|
||||||
raise ValueError(invalid_msg + ",".join(expected_keys))
|
raise ValueError(invalid_msg + ",".join(expected_keys))
|
||||||
if not all(k in planner_settings for k in expected_keys):
|
if not all(k in planner_settings for k in expected_keys):
|
||||||
|
|
@ -394,7 +394,7 @@ class RectangleScan(ScanPlanner):
|
||||||
return focused_locations[nearby_indices[-1]]
|
return focused_locations[nearby_indices[-1]]
|
||||||
|
|
||||||
|
|
||||||
class SmartSpiral(RectangleScan):
|
class SmartSpiral(RectGridPlanner):
|
||||||
"""A scan planner that spirals outward from the centre, prioritising short moves.
|
"""A scan planner that spirals outward from the centre, prioritising short moves.
|
||||||
|
|
||||||
This planner spirals out from the centre, but prioritises short moves over rigidly
|
This planner spirals out from the centre, but prioritises short moves over rigidly
|
||||||
|
|
@ -603,8 +603,10 @@ class SmartSpiral(RectangleScan):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SnakeScan(RectangleScan):
|
class RegularGridPlanner(RectGridPlanner):
|
||||||
"""A scan planner that performs a snake scan, right and down from a corner.
|
"""A scan planner that performs a snake or a raster scan.
|
||||||
|
|
||||||
|
Direction cannot yet be set it always scans, right and down from a corner.
|
||||||
|
|
||||||
This planner starts at the corner of the region to scan, snaking back and forth,
|
This planner starts at the corner of the region to scan, snaking back and forth,
|
||||||
starting moving right and down (assuming positive dx and dy.)
|
starting moving right and down (assuming positive dx and dy.)
|
||||||
|
|
@ -612,11 +614,12 @@ class SnakeScan(RectangleScan):
|
||||||
|
|
||||||
_x_count: int = 0
|
_x_count: int = 0
|
||||||
_y_count: int = 0
|
_y_count: int = 0
|
||||||
|
_style: Literal["snake", "raster"]
|
||||||
|
|
||||||
def _parse(self, planner_settings: Optional[dict] = None) -> None:
|
def _parse(self, planner_settings: Optional[dict] = None) -> None:
|
||||||
super()._parse(planner_settings)
|
super()._parse(planner_settings)
|
||||||
|
|
||||||
expected_keys = ["x_count", "y_count"]
|
expected_keys = ["x_count", "y_count", "style"]
|
||||||
invalid_msg = "SnakeScan requires planner_settings with keys: "
|
invalid_msg = "SnakeScan requires planner_settings with keys: "
|
||||||
if not planner_settings or not all(
|
if not planner_settings or not all(
|
||||||
k in planner_settings for k in expected_keys
|
k in planner_settings for k in expected_keys
|
||||||
|
|
@ -625,6 +628,12 @@ class SnakeScan(RectangleScan):
|
||||||
|
|
||||||
self._x_count = int(planner_settings["x_count"])
|
self._x_count = int(planner_settings["x_count"])
|
||||||
self._y_count = int(planner_settings["y_count"])
|
self._y_count = int(planner_settings["y_count"])
|
||||||
|
style = planner_settings["style"]
|
||||||
|
if style not in ("snake", "raster"):
|
||||||
|
raise ValueError(
|
||||||
|
f"Unknown regular grid style {style}. Use snake or raster."
|
||||||
|
)
|
||||||
|
self._style = style
|
||||||
|
|
||||||
def _initial_location_list(self) -> list[FutureScanLocation]:
|
def _initial_location_list(self) -> list[FutureScanLocation]:
|
||||||
"""Set the initial list of locations for this scan planner.
|
"""Set the initial list of locations for this scan planner.
|
||||||
|
|
@ -639,49 +648,7 @@ class SnakeScan(RectangleScan):
|
||||||
y_count=self._y_count,
|
y_count=self._y_count,
|
||||||
dx=self._dx,
|
dx=self._dx,
|
||||||
dy=self._dy,
|
dy=self._dy,
|
||||||
style="snake",
|
style=self._style,
|
||||||
)
|
|
||||||
|
|
||||||
return self._grid_to_future_locations(grid)
|
|
||||||
|
|
||||||
|
|
||||||
class RasterScan(RectangleScan):
|
|
||||||
"""A scan planner that performs a snake scan, always moving right and down.
|
|
||||||
|
|
||||||
This planner starts at the corner of the region to scan, and always moves right
|
|
||||||
to the end of the row, then down to the next row (assuming positive dx, dy).
|
|
||||||
"""
|
|
||||||
|
|
||||||
_x_count: int = 0
|
|
||||||
_y_count: int = 0
|
|
||||||
|
|
||||||
def _parse(self, planner_settings: Optional[dict] = None) -> None:
|
|
||||||
super()._parse(planner_settings)
|
|
||||||
|
|
||||||
expected_keys = ["x_count", "y_count"]
|
|
||||||
invalid_msg = "SnakeScan requires planner_settings with keys: "
|
|
||||||
if not planner_settings or not all(
|
|
||||||
k in planner_settings for k in expected_keys
|
|
||||||
):
|
|
||||||
raise KeyError(invalid_msg + ",".join(expected_keys))
|
|
||||||
|
|
||||||
self._x_count = int(planner_settings["x_count"])
|
|
||||||
self._y_count = int(planner_settings["y_count"])
|
|
||||||
|
|
||||||
def _initial_location_list(self) -> list[FutureScanLocation]:
|
|
||||||
"""Set the initial list of locations for this scan planner.
|
|
||||||
|
|
||||||
This is called on initialisation.
|
|
||||||
|
|
||||||
For raster scan, this is the full grid, and none will be added during scanning.
|
|
||||||
"""
|
|
||||||
grid = create_rectangular_scan_path(
|
|
||||||
starting_pos=self._initial_position,
|
|
||||||
x_count=self._x_count,
|
|
||||||
y_count=self._y_count,
|
|
||||||
dx=self._dx,
|
|
||||||
dy=self._dy,
|
|
||||||
style="raster",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return self._grid_to_future_locations(grid)
|
return self._grid_to_future_locations(grid)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
from typing import (
|
from typing import (
|
||||||
Generic,
|
Generic,
|
||||||
|
Literal,
|
||||||
Mapping,
|
Mapping,
|
||||||
Optional,
|
Optional,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
|
|
@ -19,10 +20,9 @@ from pydantic import BaseModel
|
||||||
import labthings_fastapi as lt
|
import labthings_fastapi as lt
|
||||||
|
|
||||||
from openflexure_microscope_server.scan_planners import (
|
from openflexure_microscope_server.scan_planners import (
|
||||||
RasterScan,
|
RegularGridPlanner,
|
||||||
ScanPlanner,
|
ScanPlanner,
|
||||||
SmartSpiral,
|
SmartSpiral,
|
||||||
SnakeScan,
|
|
||||||
)
|
)
|
||||||
from openflexure_microscope_server.stitching import (
|
from openflexure_microscope_server.stitching import (
|
||||||
STITCHING_RESOLUTION,
|
STITCHING_RESOLUTION,
|
||||||
|
|
@ -563,6 +563,7 @@ class RegularGridSettingsModel(BaseModel):
|
||||||
dy: int
|
dy: int
|
||||||
x_count: int
|
x_count: int
|
||||||
y_count: int
|
y_count: int
|
||||||
|
style: Literal["snake", "raster"]
|
||||||
images_dir: str
|
images_dir: str
|
||||||
autofocus_dz: int
|
autofocus_dz: int
|
||||||
save_resolution: tuple[int, int]
|
save_resolution: tuple[int, int]
|
||||||
|
|
@ -577,7 +578,8 @@ class RegularGridWorkflow(RectGridWorkflow[RegularGridSettingsModel]):
|
||||||
"""The number of rows in the scan."""
|
"""The number of rows in the scan."""
|
||||||
|
|
||||||
_settings_model = RegularGridSettingsModel
|
_settings_model = RegularGridSettingsModel
|
||||||
_planner_cls: type[SnakeScan] | type[RasterScan]
|
_planner_cls = RegularGridPlanner
|
||||||
|
_grid_style: Literal["snake", "raster"]
|
||||||
|
|
||||||
def all_settings(
|
def all_settings(
|
||||||
self, images_dir: str
|
self, images_dir: str
|
||||||
|
|
@ -597,6 +599,7 @@ class RegularGridWorkflow(RectGridWorkflow[RegularGridSettingsModel]):
|
||||||
dy=dy,
|
dy=dy,
|
||||||
x_count=self.x_count,
|
x_count=self.x_count,
|
||||||
y_count=self.y_count,
|
y_count=self.y_count,
|
||||||
|
style=self._grid_style,
|
||||||
images_dir=images_dir,
|
images_dir=images_dir,
|
||||||
autofocus_dz=self.autofocus_dz,
|
autofocus_dz=self.autofocus_dz,
|
||||||
save_resolution=self.save_resolution,
|
save_resolution=self.save_resolution,
|
||||||
|
|
@ -626,6 +629,7 @@ class RegularGridWorkflow(RectGridWorkflow[RegularGridSettingsModel]):
|
||||||
"dy": settings.dy,
|
"dy": settings.dy,
|
||||||
"x_count": settings.x_count,
|
"x_count": settings.x_count,
|
||||||
"y_count": settings.y_count,
|
"y_count": settings.y_count,
|
||||||
|
"style": settings.style,
|
||||||
}
|
}
|
||||||
return self._planner_cls(
|
return self._planner_cls(
|
||||||
initial_position=(position["x"], position["y"]),
|
initial_position=(position["x"], position["y"]),
|
||||||
|
|
@ -675,7 +679,7 @@ class SnakeWorkflow(RegularGridWorkflow):
|
||||||
),
|
),
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
_planner_cls = SnakeScan
|
_grid_style = "snake"
|
||||||
|
|
||||||
|
|
||||||
class RasterWorkflow(RegularGridWorkflow):
|
class RasterWorkflow(RegularGridWorkflow):
|
||||||
|
|
@ -695,4 +699,4 @@ class RasterWorkflow(RegularGridWorkflow):
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
_planner_cls = RasterScan
|
_grid_style = "raster"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue