Update move_and_measure, add unit tests

This commit is contained in:
jaknapper 2026-05-13 12:18:36 +01:00
parent e93d9777a2
commit 563bfcf7be
2 changed files with 170 additions and 4 deletions

View file

@ -10,7 +10,9 @@ from labthings_fastapi.testing import create_thing_without_server
from openflexure_microscope_server.things.autofocus import (
AutofocusThing,
JPEGSharpnessMonitor,
NoFocusFoundError,
SharpnessMethod,
)
@ -98,3 +100,157 @@ def test_looping_autofocus(start_z, max_loc, centre, attempts_expected, passes,
)
assert sharpness_monitor.focus_rel.call_count == attempts_expected
assert abs(max_loc - autofocus_thing._stage.position["z"]) < dz / 40
@pytest.fixture
def mock_camera(mocker):
"""Mock a camera to return fom properties."""
camera = mocker.MagicMock()
camera.supports_focus_fom = True
camera.focus_fom = 123
return camera
@pytest.fixture
def mock_stage(mocker):
"""Mock a stage to return position."""
stage = mocker.MagicMock()
stage.position = {"x": 0, "y": 0, "z": 0}
return stage
def test_record_defaults_to_method(mock_stage, mock_camera):
"""If record=None, only the selected method is recorded."""
monitor = JPEGSharpnessMonitor(
mock_stage,
mock_camera,
method=SharpnessMethod.FOCUS_FOM,
)
assert monitor.record == SharpnessMethod.FOCUS_FOM
def test_record_must_include_selected_method(mock_stage, mock_camera):
"""The selected autofocus metric must also be recorded."""
with pytest.raises(ValueError, match="not being recorded"):
JPEGSharpnessMonitor(
mock_stage,
mock_camera,
method=SharpnessMethod.FOCUS_FOM,
record=SharpnessMethod.JPEG,
)
def test_focus_fom_requires_camera_support(mock_stage, mock_camera):
"""FocusFoM recording requires camera support."""
mock_camera.supports_focus_fom = False
with pytest.raises(ValueError, match="doesn't support"):
JPEGSharpnessMonitor(
mock_stage,
mock_camera,
method=SharpnessMethod.FOCUS_FOM,
)
def test_jpeg_sizes_property_requires_recording(mock_stage, mock_camera):
"""JPEG sizes should error if JPEG recording disabled."""
monitor = JPEGSharpnessMonitor(
mock_stage,
mock_camera,
method=SharpnessMethod.FOCUS_FOM,
)
with pytest.raises(ValueError, match="JPEG sizes are not being recorded"):
_ = monitor.jpeg_sizes
def test_focus_fom_property_requires_recording(mock_stage, mock_camera):
"""FocusFoM values should error if FOM recording disabled."""
monitor = JPEGSharpnessMonitor(
mock_stage,
mock_camera,
method=SharpnessMethod.JPEG,
)
with pytest.raises(ValueError, match="FocusFoM values are not being recorded"):
_ = monitor.focus_foms
@pytest.mark.parametrize(
("method", "expected"),
[
(SharpnessMethod.JPEG, [100, 200, 300]),
(SharpnessMethod.FOCUS_FOM, [1, 5, 2]),
],
)
def test_move_data_uses_selected_method(
method,
expected,
mock_stage,
mock_camera,
):
"""move_data should select the correct sharpness metric."""
monitor = JPEGSharpnessMonitor(
mock_stage,
mock_camera,
method=method,
record=SharpnessMethod.JPEG + SharpnessMethod.FOCUS_FOM,
)
monitor._jpeg_times = [1.0, 2.0, 3.0]
monitor._jpeg_sizes = [100, 200, 300]
monitor._focus_foms = [1, 5, 2]
monitor._stage_times = [0.5, 3.5]
monitor._stage_positions = [
{"z": 0},
{"z": 100},
]
_, _, sharpnesses = monitor.move_data(0)
assert sharpnesses.tolist() == expected
def test_monitor_records_both_metrics(mock_stage, mock_camera):
"""Both JPEG and FocusFoM metrics can be recorded together."""
monitor = JPEGSharpnessMonitor(
mock_stage,
mock_camera,
method=SharpnessMethod.JPEG,
record=SharpnessMethod.JPEG + SharpnessMethod.FOCUS_FOM,
)
monitor._jpeg_sizes.append(999)
monitor._focus_foms.append(321)
assert monitor.jpeg_sizes == [999]
assert monitor.focus_foms == [321]
def test_move_data_uses_focus_fom(mock_stage, mock_camera):
"""Test that move_data returns the chosen metric."""
monitor = JPEGSharpnessMonitor(
mock_stage,
mock_camera,
method=SharpnessMethod.FOCUS_FOM,
record=SharpnessMethod.JPEG + SharpnessMethod.FOCUS_FOM,
)
# Record both sharpness metrics
monitor._jpeg_times = [1.0, 2.0, 3.0]
monitor._jpeg_sizes = [100, 200, 300]
monitor._focus_foms = [10, 99, 20]
monitor._stage_times = [0.5, 3.5]
monitor._stage_positions = [
{"z": 0},
{"z": 100},
]
# sharpnesses chosen by the chosen method
_, _, sharpnesses = monitor.move_data(0)
# ensure sharpnesses matches focus_foms
assert sharpnesses.tolist() == [10, 99, 20]