Assorted type fixes
This commit is contained in:
parent
472aa03822
commit
4060c06a2b
8 changed files with 41 additions and 44 deletions
|
|
@ -346,7 +346,7 @@ class ScanDirectoryManager:
|
||||||
shutil.rmtree(self.path_for(scan_name))
|
shutil.rmtree(self.path_for(scan_name))
|
||||||
|
|
||||||
@requires_lock
|
@requires_lock
|
||||||
def zip_scan(self, scan_name: str, final_version: bool = False) -> "ScanDirectory":
|
def zip_scan(self, scan_name: str, final_version: bool = False) -> str:
|
||||||
"""Zips any images from the scan not yet zipped, return full path to zip.
|
"""Zips any images from the scan not yet zipped, return full path to zip.
|
||||||
|
|
||||||
``final_version`` Set true to stitch all files not just the scan images
|
``final_version`` Set true to stitch all files not just the scan images
|
||||||
|
|
|
||||||
|
|
@ -555,7 +555,7 @@ class AutofocusThing(lt.Thing):
|
||||||
stack_parameters: StackParams,
|
stack_parameters: StackParams,
|
||||||
save_on_failure: bool = False,
|
save_on_failure: bool = False,
|
||||||
check_turning_points: bool = True,
|
check_turning_points: bool = True,
|
||||||
) -> tuple[bool, Optional[int]]:
|
) -> tuple[bool, int]:
|
||||||
"""Run a smart stack.
|
"""Run a smart stack.
|
||||||
|
|
||||||
A smart stack captures images offset in z, testing whether the sharpest image
|
A smart stack captures images offset in z, testing whether the sharpest image
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ class BaseCamera(lt.Thing):
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def start_streaming(
|
def start_streaming(
|
||||||
self, main_resolution: tuple[int, int], buffer_count: int
|
self, main_resolution: tuple[int, int] = (800, 800), buffer_count: int = 1
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Start (or stop and restart) the camera.
|
"""Start (or stop and restart) the camera.
|
||||||
|
|
||||||
|
|
@ -364,7 +364,7 @@ class BaseCamera(lt.Thing):
|
||||||
self,
|
self,
|
||||||
stream_name: Literal["main", "lores", "raw"],
|
stream_name: Literal["main", "lores", "raw"],
|
||||||
wait: Optional[float] = None,
|
wait: Optional[float] = None,
|
||||||
) -> Image:
|
) -> Image.Image:
|
||||||
"""Capture a PIL image from stream stream_name with timeout wait."""
|
"""Capture a PIL image from stream stream_name with timeout wait."""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"CameraThings must define their own capture_image method"
|
"CameraThings must define their own capture_image method"
|
||||||
|
|
@ -431,7 +431,7 @@ class BaseCamera(lt.Thing):
|
||||||
"""Clear all images in memory."""
|
"""Clear all images in memory."""
|
||||||
self._memory_buffer.clear()
|
self._memory_buffer.clear()
|
||||||
|
|
||||||
def _robust_image_capture(self) -> Tuple[Image, Mapping[str, Any]]:
|
def _robust_image_capture(self) -> Tuple[Image.Image, Mapping[str, Any]]:
|
||||||
"""Capture an image in memory and return it with metadata.
|
"""Capture an image in memory and return it with metadata.
|
||||||
|
|
||||||
This robust capturing method attempts to capture the image five times
|
This robust capturing method attempts to capture the image five times
|
||||||
|
|
@ -513,7 +513,7 @@ class BaseCamera(lt.Thing):
|
||||||
def _save_capture(
|
def _save_capture(
|
||||||
self,
|
self,
|
||||||
jpeg_path: str,
|
jpeg_path: str,
|
||||||
image: Image,
|
image: Image.Image,
|
||||||
metadata: dict,
|
metadata: dict,
|
||||||
save_resolution: Optional[Tuple[int, int]] = None,
|
save_resolution: Optional[Tuple[int, int]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ class OpenCVCamera(BaseCamera):
|
||||||
self,
|
self,
|
||||||
stream_name: Literal["main", "full"] = "main",
|
stream_name: Literal["main", "full"] = "main",
|
||||||
wait: Optional[float] = None,
|
wait: Optional[float] = None,
|
||||||
) -> Image:
|
) -> Image.Image:
|
||||||
"""Acquire one image from the camera and return as a PIL image.
|
"""Acquire one image from the camera and return as a PIL image.
|
||||||
|
|
||||||
This function will produce a JPEG image.
|
This function will produce a JPEG image.
|
||||||
|
|
|
||||||
|
|
@ -534,7 +534,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
self,
|
self,
|
||||||
stream_name: Literal["main", "lores", "full"] = "main",
|
stream_name: Literal["main", "lores", "full"] = "main",
|
||||||
wait: Optional[float] = 0.9,
|
wait: Optional[float] = 0.9,
|
||||||
) -> Image:
|
) -> Image.Image:
|
||||||
"""Acquire one image from the camera and return it as a PIL Image.
|
"""Acquire one image from the camera and return it as a PIL Image.
|
||||||
|
|
||||||
If the ``stream_name`` parameter is ``main`` or ``lores``, it will be captured
|
If the ``stream_name`` parameter is ``main`` or ``lores``, it will be captured
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
|
|
||||||
self.canvas[top:bottom, left:right] -= sprite
|
self.canvas[top:bottom, left:right] -= sprite
|
||||||
|
|
||||||
def generate_image(self, pos: tuple[int, int, int]) -> Image:
|
def generate_image(self, pos: tuple[int, int, int]) -> Image.Image:
|
||||||
"""Generate an image with blobs based on supplied coordinates.
|
"""Generate an image with blobs based on supplied coordinates.
|
||||||
|
|
||||||
:param pos: a 3-item tuple containing the x,y,z coordinates of the 'stage'
|
:param pos: a 3-item tuple containing the x,y,z coordinates of the 'stage'
|
||||||
|
|
@ -230,7 +230,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
image[image > 255] = 255
|
image[image > 255] = 255
|
||||||
return Image.fromarray(image.astype("uint8"))
|
return Image.fromarray(image.astype("uint8"))
|
||||||
|
|
||||||
def generate_frame(self) -> Image:
|
def generate_frame(self) -> Image.Image:
|
||||||
"""Generate a frame with blobs based on the stage coordinates."""
|
"""Generate a frame with blobs based on the stage coordinates."""
|
||||||
try:
|
try:
|
||||||
pos = self._stage.instantaneous_position
|
pos = self._stage.instantaneous_position
|
||||||
|
|
@ -337,7 +337,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
self,
|
self,
|
||||||
stream_name: Literal["main", "lores", "raw"],
|
stream_name: Literal["main", "lores", "raw"],
|
||||||
wait: Optional[float] = None,
|
wait: Optional[float] = None,
|
||||||
) -> Image:
|
) -> Image.Image:
|
||||||
"""Capture to a PIL image. This is not exposed as a ThingAction.
|
"""Capture to a PIL image. This is not exposed as a ThingAction.
|
||||||
|
|
||||||
It is used for capture to memory.
|
It is used for capture to memory.
|
||||||
|
|
@ -403,7 +403,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
return [property_control_for(self, "noise_level", label="Noise Level")]
|
return [property_control_for(self, "noise_level", label="Noise Level")]
|
||||||
|
|
||||||
|
|
||||||
def _frame2bytes(frame: Image) -> bytes:
|
def _frame2bytes(frame: Image.Image) -> bytes:
|
||||||
"""Convert frame to bytes."""
|
"""Convert frame to bytes."""
|
||||||
with io.BytesIO() as buf:
|
with io.BytesIO() as buf:
|
||||||
# Save in low quality for speed.
|
# Save in low quality for speed.
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ from typing import (
|
||||||
)
|
)
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from fastapi import HTTPException
|
|
||||||
|
|
||||||
import labthings_fastapi as lt
|
import labthings_fastapi as lt
|
||||||
from camera_stage_mapping.camera_stage_calibration_1d import (
|
from camera_stage_mapping.camera_stage_calibration_1d import (
|
||||||
|
|
@ -91,25 +90,14 @@ class RecordedMove:
|
||||||
self._history = []
|
self._history = []
|
||||||
|
|
||||||
|
|
||||||
class CSMUncalibratedError(HTTPException):
|
class CSMUncalibratedError(lt.exceptions.InvocationError):
|
||||||
"""An HTTP Exception raised if camera stage mapping data is needed but unavailable.
|
"""An Exception raised if camera stage mapping data is needed but unavailable.
|
||||||
|
|
||||||
Camera Stage Mapping data is needed to convert from distances specified in fractions
|
Camera Stage Mapping data is needed to convert from distances specified in fractions
|
||||||
of the field of view to distances in motor steps. This is used when clicking on the
|
of the field of view to distances in motor steps. This is used when clicking on the
|
||||||
live preview to move, or when performing a scan.
|
live preview to move, or when performing a scan.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
"""Customise the default error code and message of HTTPException."""
|
|
||||||
HTTPException.__init__(
|
|
||||||
self,
|
|
||||||
503,
|
|
||||||
(
|
|
||||||
"The camera_stage_mapping calibration is not yet available. "
|
|
||||||
"This probably means you need to run the calibration routine."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CameraStageMapper(lt.Thing):
|
class CameraStageMapper(lt.Thing):
|
||||||
"""A Thing to manage mapping between image and stage coordinates.
|
"""A Thing to manage mapping between image and stage coordinates.
|
||||||
|
|
@ -241,9 +229,10 @@ class CameraStageMapper(lt.Thing):
|
||||||
def assert_calibrated(self) -> None:
|
def assert_calibrated(self) -> None:
|
||||||
"""Raise an exception if the image_to_stage_displacement matrix is not set."""
|
"""Raise an exception if the image_to_stage_displacement matrix is not set."""
|
||||||
if self.image_to_stage_displacement_matrix is None:
|
if self.image_to_stage_displacement_matrix is None:
|
||||||
# Disable check of no message in raised exception as the message is explicitly
|
raise CSMUncalibratedError(
|
||||||
# added by CSMUncalibratedError
|
"The camera_stage_mapping calibration is not yet available. "
|
||||||
raise CSMUncalibratedError() # noqa: RSE102
|
"This probably means you need to run the calibration routine."
|
||||||
|
)
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def move_in_image_coordinates(self, x: float, y: float) -> None:
|
def move_in_image_coordinates(self, x: float, y: float) -> None:
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ from typing import (
|
||||||
Mapping,
|
Mapping,
|
||||||
Optional,
|
Optional,
|
||||||
ParamSpec,
|
ParamSpec,
|
||||||
Self,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -35,7 +34,7 @@ from openflexure_microscope_server import scan_directories, scan_planners, stitc
|
||||||
# Things
|
# Things
|
||||||
from .autofocus import AutofocusThing, StackParams
|
from .autofocus import AutofocusThing, StackParams
|
||||||
from .camera import BaseCamera
|
from .camera import BaseCamera
|
||||||
from .camera_stage_mapping import CameraStageMapper
|
from .camera_stage_mapping import CameraStageMapper, CSMUncalibratedError
|
||||||
from .stage import BaseStage
|
from .stage import BaseStage
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
@ -69,8 +68,8 @@ class ScanNotRunningError(RuntimeError):
|
||||||
|
|
||||||
|
|
||||||
def _scan_running(
|
def _scan_running(
|
||||||
method: Callable[Concatenate[Self, P], T],
|
method: Callable[Concatenate["SmartScanThing", P], T],
|
||||||
) -> Callable[Concatenate[Self, P], T]:
|
) -> Callable[Concatenate["SmartScanThing", P], T]:
|
||||||
"""Decorate a method so that it will error if a scan is not running.
|
"""Decorate a method so that it will error if a scan is not running.
|
||||||
|
|
||||||
This decorator is used by all methods in SmartScanThing that are using
|
This decorator is used by all methods in SmartScanThing that are using
|
||||||
|
|
@ -79,7 +78,9 @@ def _scan_running(
|
||||||
the same time and released with the lock
|
the same time and released with the lock
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def scan_running_wrapper(self: Self, *args: P.args, **kwargs: P.kwargs) -> T:
|
def scan_running_wrapper(
|
||||||
|
self: "SmartScanThing", *args: P.args, **kwargs: P.kwargs
|
||||||
|
) -> T:
|
||||||
"""Only start the requested method if the scan is running."""
|
"""Only start the requested method if the scan is running."""
|
||||||
if self._scan_lock.locked():
|
if self._scan_lock.locked():
|
||||||
return method(self, *args, **kwargs)
|
return method(self, *args, **kwargs)
|
||||||
|
|
@ -230,11 +231,7 @@ class SmartScanThing(lt.Thing):
|
||||||
|
|
||||||
Raise warning if not using background detect that scan will go on until max steps reached
|
Raise warning if not using background detect that scan will go on until max steps reached
|
||||||
"""
|
"""
|
||||||
if self._csm.image_resolution is None:
|
self._csm.assert_calibrated()
|
||||||
raise RuntimeError(
|
|
||||||
"Camera-stage mapping is not calibrated. This is required before "
|
|
||||||
"scans can be carried out."
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.skip_background:
|
if self.skip_background:
|
||||||
if not self._cam.background_detector_status.ready:
|
if not self._cam.background_detector_status.ready:
|
||||||
|
|
@ -275,7 +272,7 @@ class SmartScanThing(lt.Thing):
|
||||||
return (next_point[0], next_point[1], z_estimate)
|
return (next_point[0], next_point[1], z_estimate)
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _calc_displacement_from_test_image(self, overlap: int) -> tuple[int, int]:
|
def _calc_displacement_from_test_image(self, overlap: float) -> tuple[int, int]:
|
||||||
"""Take a test image and use camera stage mapping to calculate x and y displacement.
|
"""Take a test image and use camera stage mapping to calculate x and y displacement.
|
||||||
|
|
||||||
:param overlap: The desired overlap as a fraction of the image. i.e. 0.5 means
|
:param overlap: The desired overlap as a fraction of the image. i.e. 0.5 means
|
||||||
|
|
@ -283,9 +280,15 @@ class SmartScanThing(lt.Thing):
|
||||||
|
|
||||||
:returns: (dx, dy) - the x and y displacements in steps
|
:returns: (dx, dy) - the x and y displacements in steps
|
||||||
"""
|
"""
|
||||||
|
if (
|
||||||
|
self._csm.image_resolution is None
|
||||||
|
or self._csm.image_to_stage_displacement_matrix is None
|
||||||
|
):
|
||||||
|
raise CSMUncalibratedError("Camera stage mapping is not calibrated")
|
||||||
test_image = self._cam.grab_as_array()
|
test_image = self._cam.grab_as_array()
|
||||||
|
|
||||||
test_image_res = list(test_image.shape)
|
test_image_res = list(test_image.shape)
|
||||||
|
|
||||||
csm_image_res = [int(i) for i in self._csm.image_resolution]
|
csm_image_res = [int(i) for i in self._csm.image_resolution]
|
||||||
|
|
||||||
# If current stream width is different to csm calibration width,
|
# If current stream width is different to csm calibration width,
|
||||||
|
|
@ -363,7 +366,7 @@ class SmartScanThing(lt.Thing):
|
||||||
or the error that ended the scan.
|
or the error that ended the scan.
|
||||||
"""
|
"""
|
||||||
self.scan_data.set_final_data(result=scan_result)
|
self.scan_data.set_final_data(result=scan_result)
|
||||||
self.ongoing_scan.save_scan_data(self._scan_data)
|
self.ongoing_scan.save_scan_data(self.scan_data)
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _manage_stitching_threads(self) -> None:
|
def _manage_stitching_threads(self) -> None:
|
||||||
|
|
@ -385,13 +388,18 @@ 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)
|
||||||
|
images_dir = self.ongoing_scan.images_dir
|
||||||
|
if images_dir is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Couldn't run scan, images directory was not created."
|
||||||
|
)
|
||||||
self._stack_params = self._autofocus.create_stack_params(
|
self._stack_params = self._autofocus.create_stack_params(
|
||||||
images_dir=self.ongoing_scan.images_dir,
|
images_dir=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,
|
||||||
)
|
)
|
||||||
self._preview_stitcher = stitching.PreviewStitcher(
|
self._preview_stitcher = stitching.PreviewStitcher(
|
||||||
self.ongoing_scan.images_dir,
|
images_dir,
|
||||||
overlap=self.scan_data.overlap,
|
overlap=self.scan_data.overlap,
|
||||||
correlation_resize=self.scan_data.correlation_resize,
|
correlation_resize=self.scan_data.correlation_resize,
|
||||||
)
|
)
|
||||||
|
|
@ -476,7 +484,7 @@ class SmartScanThing(lt.Thing):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
focused, focused_height = self._autofocus.run_smart_stack(
|
focused, focused_height = self._autofocus.run_smart_stack(
|
||||||
stack_parameters=self._stack_params,
|
stack_parameters=self.stack_params,
|
||||||
save_on_failure=not self.scan_data.skip_background,
|
save_on_failure=not self.scan_data.skip_background,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue