Ensure ROM z-predict returns an integer and add more tests.
This commit is contained in:
parent
3d49cbf921
commit
95f5bbb16c
2 changed files with 85 additions and 25 deletions
|
|
@ -72,12 +72,12 @@ class RomDataTracker:
|
||||||
"""The last stage coordinate recorded."""
|
"""The last stage coordinate recorded."""
|
||||||
return self.stage_coords[-1].copy()
|
return self.stage_coords[-1].copy()
|
||||||
|
|
||||||
def predict_z_displacament(
|
def predict_z_displacement(
|
||||||
self,
|
self,
|
||||||
movement: dict[str, int],
|
movement: dict[str, int],
|
||||||
stage_position: dict[str, int],
|
stage_position: dict[str, int],
|
||||||
csm_matrix: np.ndarray,
|
csm_matrix: np.ndarray,
|
||||||
) -> float:
|
) -> 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 movement: The movement to be performed in image coordinates.
|
||||||
|
|
@ -99,7 +99,7 @@ class RomDataTracker:
|
||||||
stage_position[axis] + (movement[axis] / pixel_step[axis]), *fit_params
|
stage_position[axis] + (movement[axis] / pixel_step[axis]), *fit_params
|
||||||
)
|
)
|
||||||
|
|
||||||
return z_dest - stage_position["z"]
|
return int(z_dest - stage_position["z"])
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -357,8 +357,8 @@ class RangeofMotionThing(lt.Thing):
|
||||||
big_movement = self._movement_in_img_coords(
|
big_movement = self._movement_in_img_coords(
|
||||||
fov_perc=BIG_STEP, axis=axis, direction=direction
|
fov_perc=BIG_STEP, axis=axis, direction=direction
|
||||||
)
|
)
|
||||||
z_disp = self._rom_data.predict_z_displacament(
|
z_disp = self._rom_data.predict_z_displacement(
|
||||||
movement=big_movement[axis],
|
movement=big_movement,
|
||||||
stage_position=rom_deps.stage.position,
|
stage_position=rom_deps.stage.position,
|
||||||
csm_matrix=rom_deps.csm.image_to_stage_displacement_matrix,
|
csm_matrix=rom_deps.csm.image_to_stage_displacement_matrix,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""File contains unit tests for stage_measure."""
|
"""File contains unit tests for stage_measure."""
|
||||||
|
|
||||||
|
from copy import copy
|
||||||
import logging
|
import logging
|
||||||
import pytest
|
import pytest
|
||||||
from openflexure_microscope_server.things import stage_measure
|
from openflexure_microscope_server.things import stage_measure
|
||||||
|
|
@ -7,8 +8,18 @@ from openflexure_microscope_server.things import stage_measure
|
||||||
LOGGER = logging.getLogger("mock-invocation_logger")
|
LOGGER = logging.getLogger("mock-invocation_logger")
|
||||||
|
|
||||||
|
|
||||||
def test_predict_z():
|
@pytest.fixture
|
||||||
"""Check that the prediction for the next z position is correct."""
|
def csm_matrix():
|
||||||
|
"""Return an example CSM matrix."""
|
||||||
|
return [
|
||||||
|
[0.03061156624485296, 1.8031242270940833],
|
||||||
|
[1.773236372778601, 0.006660431608601435],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def example_rom_data():
|
||||||
|
"""Return some example data in a RomDataTracker."""
|
||||||
mock_positions = [
|
mock_positions = [
|
||||||
{"x": 0, "y": 0, "z": 42},
|
{"x": 0, "y": 0, "z": 42},
|
||||||
{"x": 727, "y": 2, "z": 154},
|
{"x": 727, "y": 2, "z": 154},
|
||||||
|
|
@ -19,21 +30,21 @@ def test_predict_z():
|
||||||
]
|
]
|
||||||
rom_data = stage_measure.RomDataTracker()
|
rom_data = stage_measure.RomDataTracker()
|
||||||
|
|
||||||
|
# loop through mock positions recording them with an offset.
|
||||||
for position in mock_positions:
|
for position in mock_positions:
|
||||||
offset = {"x": 54.4, "y": 0}
|
offset = {"x": 54.4, "y": 0}
|
||||||
rom_data.record_movement(position, offset)
|
rom_data.record_movement(position, offset)
|
||||||
|
return rom_data
|
||||||
|
|
||||||
csm_matrix = [
|
|
||||||
[0.03061156624485296, 1.8031242270940833],
|
|
||||||
[1.773236372778601, 0.006660431608601435],
|
|
||||||
]
|
|
||||||
|
|
||||||
mock_z_diff = rom_data.predict_z_displacament(
|
def test_predict_z(csm_matrix, example_rom_data):
|
||||||
|
"""Check that the prediction for the next z position is correct."""
|
||||||
|
mock_z_diff = example_rom_data.predict_z_displacement(
|
||||||
movement={"x": 2908, "y": 0},
|
movement={"x": 2908, "y": 0},
|
||||||
stage_position={"x": 3635, "y": 10, "z": 617},
|
stage_position={"x": 3635, "y": 10, "z": 617},
|
||||||
csm_matrix=csm_matrix,
|
csm_matrix=csm_matrix,
|
||||||
)
|
)
|
||||||
expected_z_diff = 1343.1625053206606
|
expected_z_diff = 1343
|
||||||
assert mock_z_diff == expected_z_diff
|
assert mock_z_diff == expected_z_diff
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -61,28 +72,56 @@ def test_error_on_axis_from_movement_dict(movement):
|
||||||
assert stage_measure._axis_from_movement_dict(movement)
|
assert stage_measure._axis_from_movement_dict(movement)
|
||||||
|
|
||||||
|
|
||||||
def test_parasitic_detect():
|
@pytest.mark.parametrize(
|
||||||
"""Check that the parasitic error is raised correctly."""
|
("par_fraction", "should_error"),
|
||||||
with pytest.raises(stage_measure.ParasiticMotionError):
|
[
|
||||||
stage_measure._detect_parasitic_motion(
|
(-0.20, True),
|
||||||
movement={"x": 2908, "y": 0}, offset={"x": 2908, "y": 300}
|
(-0.11, True),
|
||||||
)
|
(-0.09, False),
|
||||||
|
(-0.05, False),
|
||||||
|
(0.00, False),
|
||||||
|
(0.05, False),
|
||||||
|
(0.09, False),
|
||||||
|
(0.11, True),
|
||||||
|
(0.20, True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_parasitic_detect(par_fraction, should_error):
|
||||||
|
"""Check error is raised if the fraction of parastitic motion is too high."""
|
||||||
|
movement = {"x": 2908, "y": 0}
|
||||||
|
|
||||||
|
offset = copy(movement)
|
||||||
|
offset["y"] = movement["x"] * par_fraction
|
||||||
|
if should_error:
|
||||||
|
with pytest.raises(stage_measure.ParasiticMotionError):
|
||||||
|
stage_measure._detect_parasitic_motion(movement=movement, offset=offset)
|
||||||
|
else:
|
||||||
|
# Nothing to check here as the only job of _detect_parasitic_motion is to
|
||||||
|
# error if there is too much motion
|
||||||
|
stage_measure._detect_parasitic_motion(movement=movement, offset=offset)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def rom_thing() -> stage_measure.RangeofMotionThing:
|
def rom_thing(example_rom_data) -> stage_measure.RangeofMotionThing:
|
||||||
"""Return a RangeofMotionThing."""
|
"""Return a RangeofMotionThing already populated with some example rom_data."""
|
||||||
return stage_measure.RangeofMotionThing()
|
rom_thing = stage_measure.RangeofMotionThing()
|
||||||
|
rom_thing._stream_resolution = [800, 600]
|
||||||
|
rom_thing._rom_data = example_rom_data
|
||||||
|
return rom_thing
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_rom_deps(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."""
|
||||||
|
mock_csm = mocker.Mock()
|
||||||
|
# Set up mock csm to return a CSM matrix
|
||||||
|
mock_csm.image_to_stage_displacement_matrix = csm_matrix
|
||||||
|
|
||||||
return stage_measure.RomDeps(
|
return stage_measure.RomDeps(
|
||||||
autofocus=mocker.Mock(),
|
autofocus=mocker.Mock(),
|
||||||
stage=mocker.Mock(),
|
stage=mocker.Mock(),
|
||||||
cam=mocker.Mock(),
|
cam=mocker.Mock(),
|
||||||
csm=mocker.Mock(),
|
csm=mock_csm,
|
||||||
logger=LOGGER,
|
logger=LOGGER,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -247,7 +286,6 @@ def test_stage_still_moves(
|
||||||
mocker,
|
mocker,
|
||||||
):
|
):
|
||||||
"""Test _stage_still_moves correctly detects stage movement."""
|
"""Test _stage_still_moves correctly detects stage movement."""
|
||||||
rom_thing._stream_resolution = [800, 600]
|
|
||||||
min_offset = 800 * stage_measure.SMALL_STEP / 100 * stage_measure.DETECT_MOTION_TOL
|
min_offset = 800 * stage_measure.SMALL_STEP / 100 * stage_measure.DETECT_MOTION_TOL
|
||||||
|
|
||||||
def gen_offsets(*_args, **_kwargs):
|
def gen_offsets(*_args, **_kwargs):
|
||||||
|
|
@ -269,3 +307,25 @@ def test_stage_still_moves(
|
||||||
)
|
)
|
||||||
assert still_moves is expected_to_detect_motion
|
assert still_moves is expected_to_detect_motion
|
||||||
assert mock_offset_from.call_count == offset_calls
|
assert mock_offset_from.call_count == offset_calls
|
||||||
|
|
||||||
|
|
||||||
|
def test_big_z_corrected_movement(rom_thing, mock_rom_deps):
|
||||||
|
"""Check big z corrected move moves in x/y and z the expected distances."""
|
||||||
|
mock_rom_deps.stage.position = {"x": 5000, "y": 30, "z": 500}
|
||||||
|
|
||||||
|
rom_thing._big_z_corrected_movement("x", direction=1, rom_deps=mock_rom_deps)
|
||||||
|
|
||||||
|
expected_movement = {"x": 800 * stage_measure.BIG_STEP / 100, "y": 0}
|
||||||
|
|
||||||
|
# Check there is one z move in steps
|
||||||
|
assert mock_rom_deps.stage.move_relative.call_count == 1
|
||||||
|
move_kwargs = mock_rom_deps.stage.move_relative.call_args.kwargs
|
||||||
|
assert "x" not in move_kwargs
|
||||||
|
assert "y" not in move_kwargs
|
||||||
|
assert "z" in move_kwargs
|
||||||
|
move_kwargs["z"] = 1162
|
||||||
|
|
||||||
|
# And one move in image coordinates
|
||||||
|
assert mock_rom_deps.csm.move_in_image_coordinates.call_count == 1
|
||||||
|
lat_mov_kwargs = mock_rom_deps.csm.move_in_image_coordinates.call_args.kwargs
|
||||||
|
assert lat_mov_kwargs == expected_movement
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue