From b8eb1907c387baf4875a19fa6388dd23154bd4fd Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Tue, 21 Oct 2025 14:14:06 +0100 Subject: [PATCH] Modify range of motion _move_back_until_motion_detected to move in SMALL_STEP steps This will carry on until at least 1.5x BIG_STEP in total motion. If no motion is detected then something is up. --- .../things/stage_measure.py | 27 +++++++++---------- tests/test_stage_measure.py | 21 +++++++-------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/openflexure_microscope_server/things/stage_measure.py b/src/openflexure_microscope_server/things/stage_measure.py index 0e7ad37a..c3078702 100644 --- a/src/openflexure_microscope_server/things/stage_measure.py +++ b/src/openflexure_microscope_server/things/stage_measure.py @@ -5,8 +5,8 @@ enough positions to predict future z positions. Next, one 'big' step is taken fo by 3 'small' steps to test that the stage is still moving as expected and has not reached the edge. Once the edge has been found and to account for the possibility that the edge was reached during a "big" step, the stage is moved in a sequence of steps -(incrementing in size) in the opposite direction until motion is detected. This is -position is taken as the true final position. +in the opposite direction until motion is detected. This is position is taken as the +true final position. Throughout the test, parasitic motion (motion in the axis not being measured) is tracked and an error is raised if it exceeds a minimum amount. Currently @@ -421,30 +421,29 @@ class RangeofMotionThing(lt.Thing): """Move the stage against the direction of test unil motion is detected. In the case that the stage has reached the end of the motion, this method moves - the motor in the opposite direction until motion is detected. + the motor in the opposite direction until reliable motion is detected. :param axis: The axis in which the stage is moving. This must be 'x' or 'y'. :param direction: The direction in which the stage was moving during the test, this method will move in the opposite direction. :param rom_deps: All dependencies that were passed to the calling Action """ + movement = self._movement_in_img_coords( + fov_perc=SMALL_STEP, axis=axis, direction=-direction + ) - def _reverse_move_dict(displacement: float) -> dict[str, float]: - move = {"x": 0.0, "y": 0.0} - move[axis] = displacement * direction * -1 - return move + max_moves = int(1.5 * BIG_STEP / SMALL_STEP) + # minimum number of pixels for motion to be detected as reliable + motion_minimum = abs(movement[axis] * DETECT_MOTION_TOL) - # Array of increasing pixel sizes (powers of 2 from 1 to 512) - movements = [_reverse_move_dict(2**i) for i in range(10)] - - motion_minimum = 20 # minimum number of pixels for motion to be detected - - for movement in movements: + for i in range(max_moves): # Increment movement rom_deps.logger.info(f"Testing with step size {movement[axis]}") + # Run an autofocus every 3 steps + run_autofocus = i % 3 == 2 offset = self._move_and_measure( - movement=movement, rom_deps=rom_deps, perform_autofocus=False + movement=movement, rom_deps=rom_deps, perform_autofocus=run_autofocus ) rom_deps.logger.info(f"Offset measured as {abs(offset[axis])}") diff --git a/tests/test_stage_measure.py b/tests/test_stage_measure.py index e3b283d3..5f702a9a 100644 --- a/tests/test_stage_measure.py +++ b/tests/test_stage_measure.py @@ -279,19 +279,18 @@ def test_move_back_until_motion_detected(rom_thing, mock_rom_deps, mocker): The moves for this method should be in opposite direction to the direction specified as this is moving back after the stage reaches end of its movement. """ - - def gen_offset(*_args, **_kwargs): - return {"x": 0, "y": 0} - mock_move_n_meas = mocker.patch.object( - rom_thing, "_move_and_measure", side_effect=gen_offset + rom_thing, "_move_and_measure", return_value={"x": 0, "y": 0} ) with pytest.raises(RuntimeError, match="Cannot detect motion again"): rom_thing._move_back_until_motion_detected("y", -1, rom_deps=mock_rom_deps) - assert mock_move_n_meas.call_count == 10 - for i, call_args in enumerate(mock_move_n_meas.call_args_list): - call_args.kwargs["movement"] = {"x": 0, "y": 2**i} + + max_tries = int(1.5 * stage_measure.BIG_STEP / stage_measure.SMALL_STEP) + expected_step = 800 * stage_measure.SMALL_STEP / 100 + assert mock_move_n_meas.call_count == max_tries + for _i, call_args in enumerate(mock_move_n_meas.call_args_list): + call_args.kwargs["movement"] = {"x": 0, "y": expected_step} call_args.kwargs["perform_autofocus"] = False ## Reset mock and change the side effect @@ -299,7 +298,7 @@ def test_move_back_until_motion_detected(rom_thing, mock_rom_deps, mocker): mock_move_n_meas.side_effect = ( {"x": 0, "y": 0}, {"x": 0, "y": 0}, - {"x": 21, "y": 0}, + {"x": expected_step * 0.8, "y": 0}, ) # Other axis and direction this time @@ -307,8 +306,8 @@ def test_move_back_until_motion_detected(rom_thing, mock_rom_deps, mocker): # Should only be called 3 times assert mock_move_n_meas.call_count == 3 - for i, call_args in enumerate(mock_move_n_meas.call_args_list): - call_args.kwargs["movement"] = {"x": -(2**i), "y": 0} + for _i, call_args in enumerate(mock_move_n_meas.call_args_list): + call_args.kwargs["movement"] = {"x": -expected_step, "y": 0} call_args.kwargs["perform_autofocus"] = False