Complete first pass of capture mode implementation
This commit is contained in:
parent
83ceb82ea8
commit
9ed488e37d
4 changed files with 41 additions and 24 deletions
|
|
@ -64,14 +64,19 @@ class OFMThing(lt.Thing):
|
||||||
)
|
)
|
||||||
return self._data_dir
|
return self._data_dir
|
||||||
|
|
||||||
def create_data_path(self, path: str) -> "RelativeDataPath":
|
def create_data_path(self, path: str, absolute: bool = False) -> "RelativeDataPath":
|
||||||
"""Create a ``RelativeDataPath`` object with this Thing set as the saving Thing.
|
"""Create a ``RelativeDataPath`` object with this Thing set as the saving Thing.
|
||||||
|
|
||||||
:param path: The relative path within the data directory of this Thing's data
|
:param path: The relative path within the data directory of this Thing's data
|
||||||
dir that the data shudl be saved.
|
dir that the data shudl be saved.
|
||||||
|
:path absolute: Set to True if the current path is absolute. A relative path
|
||||||
|
will be returned. An validation error will be raised if the absolute path
|
||||||
|
is not within the data directory.
|
||||||
|
|
||||||
:return: A ``RelativeDataPath`` object with the saving Thing already set.
|
:return: A ``RelativeDataPath`` object with the saving Thing already set.
|
||||||
"""
|
"""
|
||||||
|
if absolute:
|
||||||
|
path = os.path.relpath(path, self.data_dir)
|
||||||
rel_data_path = RelativeDataPath(path)
|
rel_data_path = RelativeDataPath(path)
|
||||||
rel_data_path.set_saving_thing(self)
|
rel_data_path.set_saving_thing(self)
|
||||||
return rel_data_path
|
return rel_data_path
|
||||||
|
|
@ -136,12 +141,15 @@ class RelativeDataPath(RootModel[str]):
|
||||||
|
|
||||||
:return: A new ``RelativeDataPath`` object with the path appended.
|
:return: A new ``RelativeDataPath`` object with the path appended.
|
||||||
"""
|
"""
|
||||||
return RelativeDataPath(os.path.join(self.root, sub_path))
|
new_path = RelativeDataPath(os.path.join(self.root, sub_path))
|
||||||
|
if self._saving_thing is not None:
|
||||||
|
new_path.set_saving_thing(self._saving_thing)
|
||||||
|
return new_path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def abs_data_path(self) -> str:
|
def abs_data_path(self) -> str:
|
||||||
"""The absolute data directory to save to."""
|
"""The absolute data directory to save to."""
|
||||||
if self.save_location_set:
|
if self._saving_thing is None:
|
||||||
raise RuntimeError("The saving Thing for the relative path was never set")
|
raise RuntimeError("The saving Thing for the relative path was never set")
|
||||||
if isinstance(self._saving_thing, TemporaryDirectory):
|
if isinstance(self._saving_thing, TemporaryDirectory):
|
||||||
return os.path.join(self._saving_thing.name, self.root)
|
return os.path.join(self._saving_thing.name, self.root)
|
||||||
|
|
|
||||||
|
|
@ -591,7 +591,7 @@ class BaseCamera(OFMThing, ABC):
|
||||||
self.capture_and_save_to_path(path, capture_mode)
|
self.capture_and_save_to_path(path, capture_mode)
|
||||||
|
|
||||||
if tmpdir is None:
|
if tmpdir is None:
|
||||||
blob = lt.blob.Blob.from_file(path)
|
blob = lt.blob.Blob.from_file(path.abs_data_path)
|
||||||
else:
|
else:
|
||||||
blob = lt.blob.Blob.from_temporary_directory(tmpdir, fname)
|
blob = lt.blob.Blob.from_temporary_directory(tmpdir, fname)
|
||||||
blob.media_type = format_info.media_type
|
blob.media_type = format_info.media_type
|
||||||
|
|
|
||||||
|
|
@ -111,19 +111,27 @@ class ScanWorkflow(Generic[SettingModelType], lt.Thing):
|
||||||
)
|
)
|
||||||
|
|
||||||
def all_settings(
|
def all_settings(
|
||||||
self, images_dir: str
|
self, images_dir: RelativeDataPath
|
||||||
) -> tuple[SettingModelType, Optional[StitchingSettings]]:
|
) -> tuple[SettingModelType, Optional[StitchingSettings], tuple[int, int]]:
|
||||||
"""Return the scan settings and the stitching settings.
|
"""Return the scan settings and the stitching settings.
|
||||||
|
|
||||||
- The specific settings for this scan workflow are returned as a Base Model of
|
- The specific settings for this scan workflow are returned as a Base Model of
|
||||||
the type set when defining the class.
|
the type set when defining the class.
|
||||||
- Stitiching settings are returned either as a StitchingSettings object or None
|
- Stitiching settings are returned either as a StitchingSettings object or None
|
||||||
is returned if it is not possible to stitch the scan.
|
is returned if it is not possible to stitch the scan.
|
||||||
|
- The save resolution as determined by a test image.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"Each specific ScanWorkflow must implement a `all_settings` method."
|
"Each specific ScanWorkflow must implement a `all_settings` method."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _get_save_resolution(self) -> tuple[int, int]:
|
||||||
|
"""Return the save resolution as determined by a test image."""
|
||||||
|
# Capture an example image.
|
||||||
|
image = self._cam._capture_image(capture_mode=self.capture_mode)
|
||||||
|
# Check side to create a unit faction for downsampling.
|
||||||
|
return image.size
|
||||||
|
|
||||||
def pre_scan_routine(self, settings: SettingModelType) -> None:
|
def pre_scan_routine(self, settings: SettingModelType) -> None:
|
||||||
"""Overload to set the routine that happens before each scan."""
|
"""Overload to set the routine that happens before each scan."""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
|
|
@ -268,12 +276,11 @@ class RectGridWorkflow(
|
||||||
)
|
)
|
||||||
return y_move_stage["x"], x_move_stage["y"]
|
return y_move_stage["x"], x_move_stage["y"]
|
||||||
|
|
||||||
def _get_stitching_settings_model(self) -> StitchingSettings:
|
def _get_stitching_settings_model(
|
||||||
|
self, save_resolution: tuple[int, int]
|
||||||
|
) -> StitchingSettings:
|
||||||
"""Return a stitching settings model based on current settings."""
|
"""Return a stitching settings model based on current settings."""
|
||||||
# Use the save resolution and target stitch resolution to choose a unit fraction,
|
width, height = save_resolution
|
||||||
# which makes correlating faster
|
|
||||||
# TODO Calculate this using a camera method.
|
|
||||||
width, height = self.save_resolution
|
|
||||||
# Target area in pixels
|
# Target area in pixels
|
||||||
target_area = TARGET_STITCHING_DIMENSION**2
|
target_area = TARGET_STITCHING_DIMENSION**2
|
||||||
# Find N so that (width/N) * (height/N) ~ target_area
|
# Find N so that (width/N) * (height/N) ~ target_area
|
||||||
|
|
@ -293,24 +300,24 @@ class RectGridWorkflow(
|
||||||
return self._settings_model(**base_kwargs)
|
return self._settings_model(**base_kwargs)
|
||||||
|
|
||||||
def all_settings(
|
def all_settings(
|
||||||
self, images_dir: str
|
self, images_dir: RelativeDataPath
|
||||||
) -> tuple[RectGridSettingModelType, Optional[StitchingSettings]]:
|
) -> tuple[RectGridSettingModelType, Optional[StitchingSettings], tuple[int, int]]:
|
||||||
"""Return scan settings and the stitching settings.
|
"""Return scan settings and the stitching settings.
|
||||||
|
|
||||||
:param images_dir: The directory that images are to be written to.
|
:param images_dir: The directory that images are to be written to.
|
||||||
:return: A tuple containing the settings model for this workflow and the
|
:return: A tuple containing the settings model for this workflow, the
|
||||||
settings model for stitching.
|
settings model for stitching, and the save resolution.
|
||||||
"""
|
"""
|
||||||
|
save_resolution = self._get_save_resolution()
|
||||||
# Developer Note: When subclassing RectGridWorkflow rather than override
|
# Developer Note: When subclassing RectGridWorkflow rather than override
|
||||||
# this method first consider overriding _build_scan_settings
|
# this method first consider overriding _build_scan_settings
|
||||||
stitching_settings = self._get_stitching_settings_model()
|
stitching_settings = self._get_stitching_settings_model(save_resolution)
|
||||||
dx, dy = self._calc_displacement_from_overlap(self.overlap)
|
dx, dy = self._calc_displacement_from_overlap(self.overlap)
|
||||||
|
|
||||||
base_kwargs = {
|
base_kwargs = {
|
||||||
"overlap": self.overlap,
|
"overlap": self.overlap,
|
||||||
"dx": dx,
|
"dx": dx,
|
||||||
"dy": dy,
|
"dy": dy,
|
||||||
# TODO set images dir correctly as a RelDataPath
|
|
||||||
"capture_params": CaptureParams(
|
"capture_params": CaptureParams(
|
||||||
images_dir=images_dir, capture_mode=self.capture_mode
|
images_dir=images_dir, capture_mode=self.capture_mode
|
||||||
),
|
),
|
||||||
|
|
@ -319,7 +326,7 @@ class RectGridWorkflow(
|
||||||
|
|
||||||
scan_settings = self._build_scan_settings(base_kwargs)
|
scan_settings = self._build_scan_settings(base_kwargs)
|
||||||
|
|
||||||
return scan_settings, stitching_settings
|
return scan_settings, stitching_settings, save_resolution
|
||||||
|
|
||||||
@lt.property
|
@lt.property
|
||||||
def ready(self) -> bool:
|
def ready(self) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -304,8 +304,8 @@ class SmartScanThing(OFMThing):
|
||||||
# Ensure any PreviewStitcher created cannot be reused.
|
# Ensure any PreviewStitcher created cannot be reused.
|
||||||
self._preview_stitcher = None
|
self._preview_stitcher = None
|
||||||
|
|
||||||
# Remove any scan folders containing zero images.
|
# Remove any scan folders containing zero images.
|
||||||
self.purge_empty_scans()
|
self.purge_empty_scans()
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _move_to_next_point(
|
def _move_to_next_point(
|
||||||
|
|
@ -343,8 +343,8 @@ class SmartScanThing(OFMThing):
|
||||||
if images_dir is None:
|
if images_dir is None:
|
||||||
raise RuntimeError("Couldn't run scan, images directory was not created.")
|
raise RuntimeError("Couldn't run scan, images directory was not created.")
|
||||||
|
|
||||||
workflow_settings, stitching_settings = workflow.all_settings(
|
workflow_settings, stitching_settings, save_resolution = workflow.all_settings(
|
||||||
images_dir=images_dir
|
images_dir=self.create_data_path(images_dir, absolute=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
# If stitching settings is None then this workflow doesn't support stitching.
|
# If stitching settings is None then this workflow doesn't support stitching.
|
||||||
|
|
@ -356,7 +356,7 @@ class SmartScanThing(OFMThing):
|
||||||
starting_position=starting_position,
|
starting_position=starting_position,
|
||||||
start_time=datetime.now(),
|
start_time=datetime.now(),
|
||||||
stitch_automatically=auto_stitch,
|
stitch_automatically=auto_stitch,
|
||||||
save_resolution=workflow.save_resolution,
|
save_resolution=save_resolution,
|
||||||
workflow=type(workflow).__name__,
|
workflow=type(workflow).__name__,
|
||||||
workflow_settings=workflow_settings,
|
workflow_settings=workflow_settings,
|
||||||
stitching_settings=stitching_settings,
|
stitching_settings=stitching_settings,
|
||||||
|
|
@ -392,6 +392,9 @@ class SmartScanThing(OFMThing):
|
||||||
starting x,y,z position.
|
starting x,y,z position.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# Change into streaming mode instantly so mode is correct when collecting
|
||||||
|
# Scan Data
|
||||||
|
self._cam.change_streaming_mode(mode="full_resolution")
|
||||||
self._scan_data = self._collect_scan_data(workflow)
|
self._scan_data = self._collect_scan_data(workflow)
|
||||||
images_dir = self.ongoing_scan.images_dir
|
images_dir = self.ongoing_scan.images_dir
|
||||||
# Type narrowing
|
# Type narrowing
|
||||||
|
|
@ -401,7 +404,6 @@ class SmartScanThing(OFMThing):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.ongoing_scan.save_scan_data(self._scan_data)
|
self.ongoing_scan.save_scan_data(self._scan_data)
|
||||||
self._cam.change_streaming_mode(mode="full_resolution")
|
|
||||||
workflow.pre_scan_routine(self._scan_data.workflow_settings)
|
workflow.pre_scan_routine(self._scan_data.workflow_settings)
|
||||||
|
|
||||||
# If stitching settings are None then this type of scan can't be stitched
|
# If stitching settings are None then this type of scan can't be stitched
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue