Remove deps from camera_stage_mapping
This commit is contained in:
parent
b5fdc0778d
commit
4a327cc901
1 changed files with 31 additions and 43 deletions
|
|
@ -33,8 +33,8 @@ from camera_stage_mapping.camera_stage_tracker import Tracker
|
||||||
from camera_stage_mapping.exceptions import MappingError
|
from camera_stage_mapping.exceptions import MappingError
|
||||||
from labthings_fastapi.types.numpy import DenumpifyingDict
|
from labthings_fastapi.types.numpy import DenumpifyingDict
|
||||||
|
|
||||||
from .camera import CameraDependency as CameraClient
|
from .camera import BaseCamera
|
||||||
from .stage import StageDependency as Stage
|
from .stage import BaseStage
|
||||||
|
|
||||||
CoordinateType = Tuple[float, float, float]
|
CoordinateType = Tuple[float, float, float]
|
||||||
XYCoordinateType = Tuple[float, float]
|
XYCoordinateType = Tuple[float, float]
|
||||||
|
|
@ -62,13 +62,13 @@ class RecordedMove:
|
||||||
for calibrating the stage as it allows measuring how long moves take.
|
for calibrating the stage as it allows measuring how long moves take.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, stage: Stage) -> None:
|
def __init__(self, stage: BaseStage) -> None:
|
||||||
"""Set the stage client used for for movement.
|
"""Set the stage client used for for movement.
|
||||||
|
|
||||||
:param stage: the stage client to be used. ``stage.move_to_xyz_position`` will
|
:param stage: the stage client to be used. ``stage.move_to_xyz_position`` will
|
||||||
be called whenever the instance is called.
|
be called whenever the instance is called.
|
||||||
"""
|
"""
|
||||||
self._stage: Stage = stage
|
self._stage = stage
|
||||||
self._current_position: Optional[CoordinateType] = None
|
self._current_position: Optional[CoordinateType] = None
|
||||||
self._history: List[Tuple[float, Optional[CoordinateType]]] = []
|
self._history: List[Tuple[float, Optional[CoordinateType]]] = []
|
||||||
|
|
||||||
|
|
@ -118,58 +118,53 @@ class CameraStageMapper(lt.Thing):
|
||||||
override the ``get_xyz_position()`` and ``move_to_xyz_position()`` methods.
|
override the ``get_xyz_position()`` and ``move_to_xyz_position()`` methods.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_cam: BaseCamera = lt.thing_slot()
|
||||||
|
_stage: BaseStage = lt.thing_slot()
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def calibrate_1d(
|
def calibrate_1d(self, direction: Tuple[float, float, float]) -> DenumpifyingDict:
|
||||||
self,
|
|
||||||
camera: CameraClient,
|
|
||||||
stage: Stage,
|
|
||||||
logger: lt.deps.InvocationLogger,
|
|
||||||
direction: Tuple[float, float, float],
|
|
||||||
) -> DenumpifyingDict:
|
|
||||||
"""Move a microscope's stage in 1D, and figure out the relationship with the camera."""
|
"""Move a microscope's stage in 1D, and figure out the relationship with the camera."""
|
||||||
# Record positions and times for stage calibration
|
# Record positions and times for stage calibration
|
||||||
recorded_move = RecordedMove(stage)
|
recorded_move = RecordedMove(self._stage)
|
||||||
tracker = Tracker(
|
tracker = Tracker(
|
||||||
camera.capture_downsampled_array,
|
self._cam.capture_downsampled_array,
|
||||||
stage.get_xyz_position,
|
self._stage.get_xyz_position,
|
||||||
settle=camera.settle,
|
settle=self._cam.settle,
|
||||||
)
|
)
|
||||||
direction_array: np.ndarray = np.array(direction)
|
direction_array: np.ndarray = np.array(direction)
|
||||||
|
|
||||||
starting_position = stage.position
|
starting_position = self._stage.position
|
||||||
try:
|
try:
|
||||||
result: dict = calibrate_backlash_1d(
|
result: dict = calibrate_backlash_1d(
|
||||||
tracker, recorded_move, direction_array, logger=logger
|
tracker, recorded_move, direction_array
|
||||||
)
|
)
|
||||||
except lt.exceptions.InvocationCancelledError as e:
|
except lt.exceptions.InvocationCancelledError as e:
|
||||||
logger.info("User cancelled the camera stage mapping calibration")
|
self.logger.info("User cancelled the camera stage mapping calibration")
|
||||||
logger.info("Returning to starting position")
|
self.logger.info("Returning to starting position")
|
||||||
stage.move_absolute(**starting_position, block_cancellation=True)
|
self._stage.move_absolute(**starting_position, block_cancellation=True)
|
||||||
raise e
|
raise e
|
||||||
except MappingError as e:
|
except MappingError as e:
|
||||||
logger.info("Returning to starting position due to failed calibration")
|
self.logger.info("Returning to starting position due to failed calibration")
|
||||||
stage.move_absolute(**starting_position, block_cancellation=True)
|
self._stage.move_absolute(**starting_position, block_cancellation=True)
|
||||||
raise e
|
raise e
|
||||||
result["move_history"] = recorded_move.history
|
result["move_history"] = recorded_move.history
|
||||||
result["image_resolution"] = camera.capture_downsampled_array().shape[:2]
|
result["image_resolution"] = self._cam.capture_downsampled_array().shape[:2]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def calibrate_xy(
|
def calibrate_xy(self) -> DenumpifyingDict:
|
||||||
self, camera: CameraClient, stage: Stage, logger: lt.deps.InvocationLogger
|
|
||||||
) -> DenumpifyingDict:
|
|
||||||
"""Move the microscope's stage in X and Y, to calibrate its relationship to the camera.
|
"""Move the microscope's stage in X and Y, to calibrate its relationship to the camera.
|
||||||
|
|
||||||
This performs two 1d calibrations in x and y, then combines their results.
|
This performs two 1d calibrations in x and y, then combines their results.
|
||||||
"""
|
"""
|
||||||
downsampling_factor = camera.downsampled_array_factor
|
downsampling_factor = self._cam.downsampled_array_factor
|
||||||
# Calibrate y-axis first as it is more likely to fail.
|
# Calibrate y-axis first as it is more likely to fail.
|
||||||
# The x-y difference is due to the camera aspect ratio, not the stage hardware.
|
# The x-y difference is due to the camera aspect ratio, not the stage hardware.
|
||||||
logger.info("Calibrating Y axis:")
|
self.logger.info("Calibrating Y axis:")
|
||||||
cal_y: dict = self.calibrate_1d(camera, stage, logger, (0, 1, 0))
|
cal_y: dict = self.calibrate_1d((0, 1, 0))
|
||||||
logger.info("Calibrating X axis:")
|
self.logger.info("Calibrating X axis:")
|
||||||
cal_x: dict = self.calibrate_1d(camera, stage, logger, (1, 0, 0))
|
cal_x: dict = self.calibrate_1d((1, 0, 0))
|
||||||
logger.info("Calibration complete, updating metadata.")
|
self.logger.info("Calibration complete, updating metadata.")
|
||||||
|
|
||||||
# Combine X and Y calibrations to make a 2D calibration
|
# Combine X and Y calibrations to make a 2D calibration
|
||||||
cal_xy: dict = image_to_stage_displacement_from_1d([cal_x, cal_y])
|
cal_xy: dict = image_to_stage_displacement_from_1d([cal_x, cal_y])
|
||||||
|
|
@ -185,7 +180,7 @@ class CameraStageMapper(lt.Thing):
|
||||||
f"[{round(csm_matrix[0][0], 2)}, {round(csm_matrix[0][1], 2)},],"
|
f"[{round(csm_matrix[0][0], 2)}, {round(csm_matrix[0][1], 2)},],"
|
||||||
f"[{round(csm_matrix[1][0], 2)}, {round(csm_matrix[1][1], 2)}]"
|
f"[{round(csm_matrix[1][0], 2)}, {round(csm_matrix[1][1], 2)}]"
|
||||||
)
|
)
|
||||||
logger.info(f"CSM matrix is {csm_as_string}.")
|
self.logger.info(f"CSM matrix is {csm_as_string}.")
|
||||||
|
|
||||||
data: Dict[str, dict] = {
|
data: Dict[str, dict] = {
|
||||||
"camera_stage_mapping_calibration": cal_xy,
|
"camera_stage_mapping_calibration": cal_xy,
|
||||||
|
|
@ -201,9 +196,7 @@ class CameraStageMapper(lt.Thing):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@lt.property
|
@lt.property
|
||||||
def image_to_stage_displacement_matrix(
|
def image_to_stage_displacement_matrix(self) -> Optional[List[List[float]]]:
|
||||||
self,
|
|
||||||
) -> Optional[List[List[float]]]: # 2x2 integer array
|
|
||||||
"""A 2x2 matrix that converts displacement in image coordinates to stage coordinates.
|
"""A 2x2 matrix that converts displacement in image coordinates to stage coordinates.
|
||||||
|
|
||||||
Note that this matrix is defined using "matrix coordinates", i.e. image coordinates
|
Note that this matrix is defined using "matrix coordinates", i.e. image coordinates
|
||||||
|
|
@ -253,12 +246,7 @@ class CameraStageMapper(lt.Thing):
|
||||||
raise CSMUncalibratedError() # noqa: RSE102
|
raise CSMUncalibratedError() # noqa: RSE102
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def move_in_image_coordinates(
|
def move_in_image_coordinates(self, x: float, y: float) -> None:
|
||||||
self,
|
|
||||||
stage: Stage,
|
|
||||||
x: float,
|
|
||||||
y: float,
|
|
||||||
) -> None:
|
|
||||||
"""Move by a given number of pixels on the camera.
|
"""Move by a given number of pixels on the camera.
|
||||||
|
|
||||||
NB x and y here refer to what is usually understood to be the horizontal and
|
NB x and y here refer to what is usually understood to be the horizontal and
|
||||||
|
|
@ -271,7 +259,7 @@ class CameraStageMapper(lt.Thing):
|
||||||
an image usually helps resolve any ambiguity.
|
an image usually helps resolve any ambiguity.
|
||||||
"""
|
"""
|
||||||
self.assert_calibrated()
|
self.assert_calibrated()
|
||||||
stage.move_relative(**self.convert_image_to_stage_coordinates(x=x, y=y))
|
self._stage.move_relative(**self.convert_image_to_stage_coordinates(x=x, y=y))
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def convert_image_to_stage_coordinates(
|
def convert_image_to_stage_coordinates(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue