Fix typing in stage_measure
This commit is contained in:
parent
85671e897e
commit
dcf329f1c5
1 changed files with 27 additions and 24 deletions
|
|
@ -14,8 +14,9 @@ this is 10% of the expected motion is the measured axis.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from copy import copy
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Any, Literal, Optional, overload
|
from typing import Any, Literal, Mapping, Optional, overload
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
@ -48,20 +49,20 @@ class RomDataTracker:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Define useful data tracked throughout test."""
|
"""Define useful data tracked throughout test."""
|
||||||
self.stage_coords: list[dict[str, int]] = []
|
self.stage_coords: list[Mapping[str, int]] = []
|
||||||
self.offsets: list[dict[str, float]] = []
|
self.offsets: list[Mapping[str, float]] = []
|
||||||
|
|
||||||
def record_movement(
|
def record_movement(
|
||||||
self, current_pos: dict[str, int], offset: dict[str, float]
|
self, current_pos: Mapping[str, int], offset: Mapping[str, float]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Record the current position and the measured offset of the last move."""
|
"""Record the current position and the measured offset of the last move."""
|
||||||
self.stage_coords.append(current_pos)
|
self.stage_coords.append(current_pos)
|
||||||
self.offsets.append(offset)
|
self.offsets.append(offset)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def final_position(self) -> dict[str, int]:
|
def final_position(self) -> Mapping[str, int]:
|
||||||
"""The last stage coordinate recorded."""
|
"""The last stage coordinate recorded."""
|
||||||
return self.stage_coords[-1].copy()
|
return copy(self.stage_coords[-1])
|
||||||
|
|
||||||
def fit_axis(self, axis: Literal["x", "y"]) -> np.poly1d:
|
def fit_axis(self, axis: Literal["x", "y"]) -> np.poly1d:
|
||||||
"""Quadratic fit stage z against x or y and return poly1d of fit.
|
"""Quadratic fit stage z against x or y and return poly1d of fit.
|
||||||
|
|
@ -78,8 +79,8 @@ class RomDataTracker:
|
||||||
def predict_z_displacement(
|
def predict_z_displacement(
|
||||||
self,
|
self,
|
||||||
axis: Literal["x", "y"],
|
axis: Literal["x", "y"],
|
||||||
stage_movement: dict[str, int],
|
stage_movement: Mapping[str, int],
|
||||||
stage_position: dict[str, int],
|
stage_position: Mapping[str, int],
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Predict the z-displacement needed for a given movement.
|
"""Predict the z-displacement needed for a given movement.
|
||||||
|
|
||||||
|
|
@ -93,7 +94,7 @@ class RomDataTracker:
|
||||||
|
|
||||||
return int(z_dest - stage_position["z"])
|
return int(z_dest - stage_position["z"])
|
||||||
|
|
||||||
def find_turning_point(self, axis: Literal["x", "y"]) -> dict[str, int]:
|
def find_turning_point(self, axis: Literal["x", "y"]) -> Mapping[str, int]:
|
||||||
"""Find the turing point from the recorded coordinates."""
|
"""Find the turing point from the recorded coordinates."""
|
||||||
fit_func = self.fit_axis(axis)
|
fit_func = self.fit_axis(axis)
|
||||||
turning_loc = fit_func.deriv().roots[0]
|
turning_loc = fit_func.deriv().roots[0]
|
||||||
|
|
@ -121,7 +122,9 @@ class RangeofMotionThing(lt.Thing):
|
||||||
_csm: CameraStageMapper = lt.thing_slot()
|
_csm: CameraStageMapper = lt.thing_slot()
|
||||||
_stage: BaseStage = lt.thing_slot()
|
_stage: BaseStage = lt.thing_slot()
|
||||||
|
|
||||||
calibrated_range: Optional[list[int, int]] = lt.setting(default=None, readonly=True)
|
calibrated_range: Optional[tuple[int, int]] = lt.setting(
|
||||||
|
default=None, readonly=True
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, thing_server_interface: lt.ThingServerInterface) -> None:
|
def __init__(self, thing_server_interface: lt.ThingServerInterface) -> None:
|
||||||
"""Initialise and create the lock."""
|
"""Initialise and create the lock."""
|
||||||
|
|
@ -131,7 +134,7 @@ class RangeofMotionThing(lt.Thing):
|
||||||
self._rom_data = RomDataTracker()
|
self._rom_data = RomDataTracker()
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def perform_rom_test(self) -> dict[str, Any]:
|
def perform_rom_test(self) -> Mapping[str, Any]:
|
||||||
"""Measures the range of motion of the stage across the x and y axes.
|
"""Measures the range of motion of the stage across the x and y axes.
|
||||||
|
|
||||||
:return: Results dictionary separated into keys of each axis and direction.
|
:return: Results dictionary separated into keys of each axis and direction.
|
||||||
|
|
@ -173,7 +176,7 @@ class RangeofMotionThing(lt.Thing):
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"Range of motion is {step_range[0]} x {step_range[1]} steps"
|
f"Range of motion is {step_range[0]} x {step_range[1]} steps"
|
||||||
)
|
)
|
||||||
self.calibrated_range = step_range
|
self.calibrated_range = (step_range[0], step_range[1])
|
||||||
finally:
|
finally:
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
return {
|
return {
|
||||||
|
|
@ -324,7 +327,7 @@ class RangeofMotionThing(lt.Thing):
|
||||||
# if the distance is less than 1 big step away then move to it and exit
|
# if the distance is less than 1 big step away then move to it and exit
|
||||||
if abs(img_perc) < BIG_STEP:
|
if abs(img_perc) < BIG_STEP:
|
||||||
self.logger.info(f"Estimated centre of {axis}-axis is {estimate[axis]}")
|
self.logger.info(f"Estimated centre of {axis}-axis is {estimate[axis]}")
|
||||||
self._stage.move_absolute(**estimate)
|
self._stage.move_absolute(**estimate, block_cancellation=False)
|
||||||
# Note the second return, the direction, is meaningless here.
|
# Note the second return, the direction, is meaningless here.
|
||||||
return True, 1
|
return True, 1
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
|
|
@ -353,7 +356,7 @@ class RangeofMotionThing(lt.Thing):
|
||||||
return (fov_perc / 100) * self._stream_resolution[img_index]
|
return (fov_perc / 100) * self._stream_resolution[img_index]
|
||||||
|
|
||||||
def _img_dir_from_stage_coords(
|
def _img_dir_from_stage_coords(
|
||||||
self, target: dict[str, int], axis: Literal["x", "y"]
|
self, target: Mapping[str, int], axis: Literal["x", "y"]
|
||||||
) -> Literal[1, -1]:
|
) -> Literal[1, -1]:
|
||||||
"""For a target location in stage coords, return the direction in image coordinates.
|
"""For a target location in stage coords, return the direction in image coordinates.
|
||||||
|
|
||||||
|
|
@ -369,7 +372,7 @@ class RangeofMotionThing(lt.Thing):
|
||||||
return -1 if target_im_coords[axis] < current_loc_im_coords[axis] else 1
|
return -1 if target_im_coords[axis] < current_loc_im_coords[axis] else 1
|
||||||
|
|
||||||
def _distance_in_img_percentage(
|
def _distance_in_img_percentage(
|
||||||
self, target: dict[str, int], axis: Literal["x", "y"]
|
self, target: Mapping[str, int], axis: Literal["x", "y"]
|
||||||
) -> float:
|
) -> float:
|
||||||
"""For a target location in stage coords return the distance in percentage of FOV.
|
"""For a target location in stage coords return the distance in percentage of FOV.
|
||||||
|
|
||||||
|
|
@ -394,7 +397,7 @@ class RangeofMotionThing(lt.Thing):
|
||||||
fov_perc: int,
|
fov_perc: int,
|
||||||
axis: Literal["x", "y"],
|
axis: Literal["x", "y"],
|
||||||
direction: Literal[1, -1],
|
direction: Literal[1, -1],
|
||||||
) -> dict[str, float]:
|
) -> Mapping[str, float]:
|
||||||
"""Return the dictionary for a move in image coordinates.
|
"""Return the dictionary for a move in image coordinates.
|
||||||
|
|
||||||
This dictionary can be passed directly to csm.move_in_image_coordinates
|
This dictionary can be passed directly to csm.move_in_image_coordinates
|
||||||
|
|
@ -546,11 +549,11 @@ class RangeofMotionThing(lt.Thing):
|
||||||
|
|
||||||
def _move_and_measure(
|
def _move_and_measure(
|
||||||
self,
|
self,
|
||||||
movement: dict[str, float],
|
movement: Mapping[str, float],
|
||||||
perform_autofocus: bool = True,
|
perform_autofocus: bool = True,
|
||||||
max_autofocus_repeats: int = 0,
|
max_autofocus_repeats: int = 0,
|
||||||
abs_min_offset: float = 0.0,
|
abs_min_offset: float = 0.0,
|
||||||
) -> dict[str, float]:
|
) -> Mapping[str, float]:
|
||||||
"""Move the stage and measure the offset between the two positions.
|
"""Move the stage and measure the offset between the two positions.
|
||||||
|
|
||||||
:param movement: A dictionary containing the distance to move in image coords.
|
:param movement: A dictionary containing the distance to move in image coords.
|
||||||
|
|
@ -596,7 +599,7 @@ class RangeofMotionThing(lt.Thing):
|
||||||
|
|
||||||
return offset
|
return offset
|
||||||
|
|
||||||
def _offset_from(self, before_img: np.ndarray) -> dict[str, float]:
|
def _offset_from(self, before_img: np.ndarray) -> Mapping[str, float]:
|
||||||
"""Take an image and calculate the offset from an input image.
|
"""Take an image and calculate the offset from an input image.
|
||||||
|
|
||||||
:param before_img: The image the offset should be calculated with respect to.
|
:param before_img: The image the offset should be calculated with respect to.
|
||||||
|
|
@ -623,11 +626,11 @@ class RangeofMotionThing(lt.Thing):
|
||||||
# let MyPy know with overloads typed to Literal[False] and Literal[True].
|
# let MyPy know with overloads typed to Literal[False] and Literal[True].
|
||||||
@overload
|
@overload
|
||||||
def _axis_from_movement_dict(
|
def _axis_from_movement_dict(
|
||||||
movement: dict[str, float], return_other: Literal[False]
|
movement: Mapping[str, float], return_other: Literal[False]
|
||||||
) -> str: ...
|
) -> str: ...
|
||||||
@overload
|
@overload
|
||||||
def _axis_from_movement_dict(
|
def _axis_from_movement_dict(
|
||||||
movement: dict[str, float], return_other: Literal[True]
|
movement: Mapping[str, float], return_other: Literal[True]
|
||||||
) -> tuple[str, str]: ...
|
) -> tuple[str, str]: ...
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -635,12 +638,12 @@ def _axis_from_movement_dict(
|
||||||
# because MyPy doesn't see that return other's default is `False` and use
|
# because MyPy doesn't see that return other's default is `False` and use
|
||||||
# the `Literal[False]` overload.
|
# the `Literal[False]` overload.
|
||||||
@overload
|
@overload
|
||||||
def _axis_from_movement_dict(movement: dict[str, float]) -> str: ...
|
def _axis_from_movement_dict(movement: Mapping[str, float]) -> str: ...
|
||||||
|
|
||||||
|
|
||||||
# Finally The function.
|
# Finally The function.
|
||||||
def _axis_from_movement_dict(
|
def _axis_from_movement_dict(
|
||||||
movement: dict[str, float], return_other: bool = False
|
movement: Mapping[str, float], return_other: bool = False
|
||||||
) -> str | tuple[str, str]:
|
) -> str | tuple[str, str]:
|
||||||
"""Return the axis that a given movement dictionary moves in.
|
"""Return the axis that a given movement dictionary moves in.
|
||||||
|
|
||||||
|
|
@ -661,7 +664,7 @@ def _axis_from_movement_dict(
|
||||||
|
|
||||||
|
|
||||||
def _parasitic_motion_detected(
|
def _parasitic_motion_detected(
|
||||||
movement: dict[str, float], offset: dict[str, float]
|
movement: Mapping[str, float], offset: Mapping[str, float]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Compare desired movement to measured offset, report if parasitic motion was detected.
|
"""Compare desired movement to measured offset, report if parasitic motion was detected.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue