Adjust CSM Thing to allow calculation of stage coords from img coords and use this in stage measure
Using this stops the z-axis being flipped when the off diagonal elements of the CSM are different signs.
This commit is contained in:
parent
635375a012
commit
72c35c302c
3 changed files with 36 additions and 27 deletions
|
|
@ -268,10 +268,18 @@ 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))
|
||||||
|
|
||||||
|
@lt.thing_action
|
||||||
|
def convert_image_to_stage_coordinates(
|
||||||
|
self, x: float, y: float
|
||||||
|
) -> Mapping[str, int]:
|
||||||
|
"""Convert image coordinates to stage coordinates."""
|
||||||
|
self.assert_calibrated()
|
||||||
relative_move: np.ndarray = np.dot(
|
relative_move: np.ndarray = np.dot(
|
||||||
np.array([y, x]), np.array(self.image_to_stage_displacement_matrix)
|
np.array([y, x]), np.array(self.image_to_stage_displacement_matrix)
|
||||||
)
|
)
|
||||||
stage.move_relative(x=relative_move[0], y=relative_move[1])
|
return {"x": int(relative_move[0]), "y": int(relative_move[1])}
|
||||||
|
|
||||||
@lt.thing_property
|
@lt.thing_property
|
||||||
def thing_state(self) -> Mapping[str, Any]:
|
def thing_state(self) -> Mapping[str, Any]:
|
||||||
|
|
|
||||||
|
|
@ -74,30 +74,23 @@ class RomDataTracker:
|
||||||
|
|
||||||
def predict_z_displacement(
|
def predict_z_displacement(
|
||||||
self,
|
self,
|
||||||
movement: dict[str, int],
|
axis: Literal["x", "y"],
|
||||||
|
stage_movement: dict[str, int],
|
||||||
stage_position: dict[str, int],
|
stage_position: dict[str, int],
|
||||||
csm_matrix: list[list[int]],
|
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Predict the z-displacement needed for a given movement.
|
"""Predict the z-displacement needed for a given movement.
|
||||||
|
|
||||||
:param movement: The movement to be performed in image coordinates.
|
:param axis: The axis which is being measured. This must be 'x' or 'y'.
|
||||||
|
:param stage_movement: The movement to be performed in stage coordinates.
|
||||||
:param stage_position: The current stage position in stage coordinates.
|
:param stage_position: The current stage position in stage coordinates.
|
||||||
:param csm_matrix: The camera stage mapping matrix.
|
|
||||||
:returns: The predicted relative z displacement needed to stay in focus.
|
:returns: The predicted relative z displacement needed to stay in focus.
|
||||||
"""
|
"""
|
||||||
pixel_step = {
|
# x or y positions
|
||||||
"x": 1 / csm_matrix[0][1],
|
lateral_positions = [i[axis] for i in self.stage_coords]
|
||||||
"y": 1 / csm_matrix[1][0],
|
|
||||||
}
|
|
||||||
|
|
||||||
axis = _axis_from_movement_dict(movement)
|
|
||||||
|
|
||||||
lateral_positions = [i[axis] for i in self.stage_coords] # x or y positions
|
|
||||||
z_positions = [i["z"] for i in self.stage_coords]
|
z_positions = [i["z"] for i in self.stage_coords]
|
||||||
fit_params, *_others = curve_fit(quadratic, lateral_positions, z_positions)
|
fit_params, *_others = curve_fit(quadratic, lateral_positions, z_positions)
|
||||||
z_dest = quadratic(
|
z_dest = quadratic(stage_position[axis] + stage_movement[axis], *fit_params)
|
||||||
stage_position[axis] + (movement[axis] / pixel_step[axis]), *fit_params
|
|
||||||
)
|
|
||||||
return int(z_dest - stage_position["z"])
|
return int(z_dest - stage_position["z"])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -248,7 +241,6 @@ class RangeofMotionThing(lt.Thing):
|
||||||
self._big_z_corrected_movement(
|
self._big_z_corrected_movement(
|
||||||
axis=axis, direction=direction, rom_deps=rom_deps
|
axis=axis, direction=direction, rom_deps=rom_deps
|
||||||
)
|
)
|
||||||
|
|
||||||
# Autofocus and record position
|
# Autofocus and record position
|
||||||
rom_deps.autofocus.looping_autofocus(dz=800)
|
rom_deps.autofocus.looping_autofocus(dz=800)
|
||||||
self._rom_data.stage_coords.append(rom_deps.stage.position)
|
self._rom_data.stage_coords.append(rom_deps.stage.position)
|
||||||
|
|
@ -334,7 +326,6 @@ class RangeofMotionThing(lt.Thing):
|
||||||
)
|
)
|
||||||
for _loop in range(5):
|
for _loop in range(5):
|
||||||
offset = self._move_and_measure(movement=movement, rom_deps=rom_deps)
|
offset = self._move_and_measure(movement=movement, rom_deps=rom_deps)
|
||||||
|
|
||||||
rom_deps.logger.info(f"Offset measured as {offset[axis]}")
|
rom_deps.logger.info(f"Offset measured as {offset[axis]}")
|
||||||
|
|
||||||
self._rom_data.record_movement(rom_deps.stage.position, offset)
|
self._rom_data.record_movement(rom_deps.stage.position, offset)
|
||||||
|
|
@ -351,16 +342,19 @@ class RangeofMotionThing(lt.Thing):
|
||||||
:param direction: The direction to move in.
|
:param direction: The direction to move in.
|
||||||
:param rom_deps: All dependencies that were passed to the calling Action
|
:param rom_deps: All dependencies that were passed to the calling Action
|
||||||
"""
|
"""
|
||||||
big_movement = self._movement_in_img_coords(
|
movement = self._movement_in_img_coords(
|
||||||
fov_perc=BIG_STEP, axis=axis, direction=direction
|
fov_perc=BIG_STEP, axis=axis, direction=direction
|
||||||
)
|
)
|
||||||
|
# Convert to stage coordinates
|
||||||
|
stage_movemenet = rom_deps.csm.convert_image_to_stage_coordinates(**movement)
|
||||||
|
|
||||||
z_disp = self._rom_data.predict_z_displacement(
|
z_disp = self._rom_data.predict_z_displacement(
|
||||||
movement=big_movement,
|
axis=axis,
|
||||||
|
stage_movement=stage_movemenet,
|
||||||
stage_position=rom_deps.stage.position,
|
stage_position=rom_deps.stage.position,
|
||||||
csm_matrix=rom_deps.csm.image_to_stage_displacement_matrix,
|
|
||||||
)
|
)
|
||||||
rom_deps.stage.move_relative(z=z_disp)
|
rom_deps.stage.move_relative(z=z_disp)
|
||||||
rom_deps.csm.move_in_image_coordinates(**big_movement)
|
rom_deps.csm.move_in_image_coordinates(**movement)
|
||||||
|
|
||||||
def _stage_still_moves(
|
def _stage_still_moves(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ def increasing_xyz_dict_generator(*_args, **_kwargs):
|
||||||
def csm_matrix():
|
def csm_matrix():
|
||||||
"""Return an example CSM matrix."""
|
"""Return an example CSM matrix."""
|
||||||
return [
|
return [
|
||||||
[0.03061156624485296, 1.8031242270940833],
|
[0.03061156624485296, -1.8031242270940833],
|
||||||
[1.773236372778601, 0.006660431608601435],
|
[1.773236372778601, 0.006660431608601435],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -70,12 +70,12 @@ def example_rom_data():
|
||||||
return rom_data
|
return rom_data
|
||||||
|
|
||||||
|
|
||||||
def test_predict_z(csm_matrix, example_rom_data):
|
def test_predict_z(example_rom_data):
|
||||||
"""Check that the prediction for the next z position is correct."""
|
"""Check that the prediction for the next z position is correct."""
|
||||||
mock_z_diff = example_rom_data.predict_z_displacement(
|
mock_z_diff = example_rom_data.predict_z_displacement(
|
||||||
movement={"x": 2908, "y": 0},
|
axis="x",
|
||||||
|
stage_movement={"x": 5243, "y": 0},
|
||||||
stage_position={"x": 3635, "y": 10, "z": 617},
|
stage_position={"x": 3635, "y": 10, "z": 617},
|
||||||
csm_matrix=csm_matrix,
|
|
||||||
)
|
)
|
||||||
expected_z_diff = 1343
|
expected_z_diff = 1343
|
||||||
assert mock_z_diff == expected_z_diff
|
assert mock_z_diff == expected_z_diff
|
||||||
|
|
@ -157,9 +157,16 @@ def rom_thing(example_rom_data) -> stage_measure.RangeofMotionThing:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_rom_deps(csm_matrix, mocker) -> stage_measure.RomDeps:
|
def mock_rom_deps(csm_matrix, mocker) -> stage_measure.RomDeps:
|
||||||
"""Return a RomDeps object full of mocks, except the logger which is LOGGER."""
|
"""Return a RomDeps object full of mocks, except the logger which is LOGGER."""
|
||||||
|
|
||||||
|
def apply_csm(x: float, y: float) -> dict[str, int]:
|
||||||
|
"""Convert image coordinates to stage coordinates."""
|
||||||
|
vec = np.dot(np.array([y, x]), np.array(csm_matrix))
|
||||||
|
return {"x": int(vec[0]), "y": int(vec[1])}
|
||||||
|
|
||||||
mock_csm = mocker.Mock()
|
mock_csm = mocker.Mock()
|
||||||
# Set up mock csm to return a CSM matrix
|
# Set up mock csm to return a CSM matrix
|
||||||
mock_csm.image_to_stage_displacement_matrix = csm_matrix
|
mock_csm.image_to_stage_displacement_matrix = csm_matrix
|
||||||
|
mock_csm.convert_image_to_stage_coordinates.side_effect = apply_csm
|
||||||
|
|
||||||
return stage_measure.RomDeps(
|
return stage_measure.RomDeps(
|
||||||
autofocus=mocker.Mock(),
|
autofocus=mocker.Mock(),
|
||||||
|
|
@ -367,7 +374,7 @@ def test_big_z_corrected_movement(rom_thing, mock_rom_deps):
|
||||||
assert "x" not in move_kwargs
|
assert "x" not in move_kwargs
|
||||||
assert "y" not in move_kwargs
|
assert "y" not in move_kwargs
|
||||||
assert "z" in move_kwargs
|
assert "z" in move_kwargs
|
||||||
move_kwargs["z"] = 1162
|
assert move_kwargs["z"] == 1148
|
||||||
|
|
||||||
# And one move in image coordinates
|
# And one move in image coordinates
|
||||||
assert mock_rom_deps.csm.move_in_image_coordinates.call_count == 1
|
assert mock_rom_deps.csm.move_in_image_coordinates.call_count == 1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue