Use backlash compensation in smart scan and autofocus

This commit is contained in:
Julian Stirling 2026-02-24 17:45:21 +00:00
parent c12814f0eb
commit 0527990282
4 changed files with 21 additions and 13 deletions

View file

@ -22,7 +22,7 @@ import labthings_fastapi as lt
from labthings_fastapi.types.numpy import NDArray from labthings_fastapi.types.numpy import NDArray
from .camera import BaseCamera, CaptureParams from .camera import BaseCamera, CaptureParams
from .stage import BaseStage from .stage import BacklashCompensation, BaseStage
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
MIN_TEST_IMAGE_COUNT = 3 MIN_TEST_IMAGE_COUNT = 3
@ -458,14 +458,17 @@ class AutofocusThing(lt.Thing):
up to 10 times. up to 10 times.
""" """
attempt = 0 attempt = 0
backlash = 200
with JPEGSharpnessMonitor(self._stage, self._cam) as sharpness_monitor: with JPEGSharpnessMonitor(self._stage, self._cam) as sharpness_monitor:
while attempt < 10: while attempt < 10:
attempt += 1 attempt += 1
if start == "centre": if start == "centre":
self._stage.move_relative(x=0, y=0, z=-int(backlash + dz / 2)) self._stage.move_relative(
self._stage.move_relative(x=0, y=0, z=backlash) x=0,
y=0,
z=-int(dz / 2),
backlash_compensation=BacklashCompensation.Z_ONLY,
)
# Always start centrally for future runs # Always start centrally for future runs
start = "centre" start = "centre"
@ -480,8 +483,10 @@ class AutofocusThing(lt.Thing):
target_max = np.max(heights) - dz / 5 target_max = np.max(heights) - dz / 5
# move to the peak # move to the peak
self._stage.move_absolute(z=peak_height - backlash) self._stage.move_absolute(
self._stage.move_absolute(z=peak_height) z=peak_height,
backlash_compensation=BacklashCompensation.Z_ONLY,
)
if target_min < peak_height < target_max: if target_min < peak_height < target_max:
# If it is within the target range then return # If it is within the target range then return
@ -622,12 +627,10 @@ class AutofocusThing(lt.Thing):
# Better to start too low and take too many images than too high and need to refocus # Better to start too low and take too many images than too high and need to refocus
self._stage.move_relative( self._stage.move_relative(
z=-int( z=-int(
stack_parameters.steps_undershoot stack_parameters.steps_undershoot + stack_parameters.stack_z_range / 2
+ stack_parameters.backlash_correction ),
+ stack_parameters.stack_z_range / 2 backlash_compensation=BacklashCompensation.Z_ONLY,
)
) )
self._stage.move_relative(z=stack_parameters.backlash_correction)
captures: list[CaptureInfo] = [] captures: list[CaptureInfo] = []
# Always check for focus using the the last `min_images_to_test` in the # Always check for focus using the the last `min_images_to_test` in the

View file

@ -37,7 +37,7 @@ from openflexure_microscope_server.utilities import coerce_thing_selector
# Things # Things
from .camera import BaseCamera from .camera import BaseCamera
from .scan_workflows import ScanWorkflow from .scan_workflows import ScanWorkflow
from .stage import BaseStage from .stage import BacklashCompensation, BaseStage
T = TypeVar("T") T = TypeVar("T")
P = ParamSpec("P") P = ParamSpec("P")
@ -311,6 +311,7 @@ class SmartScanThing(lt.Thing):
x=next_point[0], x=next_point[0],
y=next_point[1], y=next_point[1],
z=z_estimate, z=z_estimate,
backlash_compensation=BacklashCompensation.XY_ONLY,
) )
return (next_point[0], next_point[1], z_estimate) return (next_point[0], next_point[1], z_estimate)

View file

@ -142,7 +142,9 @@ class BaseStage(lt.Thing):
"""Current position of the stage.""" """Current position of the stage."""
return self._apply_axis_direction(self._hardware_position) return self._apply_axis_direction(self._hardware_position)
backlash_steps: dict[str, int] = lt.setting(default={"x": 200, "y": 200, "z": 200}) backlash_steps: dict[str, int] = lt.setting(
default={"x": 200, "y": 200, "z": 200}, readonly=True
)
"""The number of steps to elimate backlash. The sign sets the direction. """The number of steps to elimate backlash. The sign sets the direction.
A positive number sets the direction of the second move in a backlash correction. A positive number sets the direction of the second move in a backlash correction.

View file

@ -60,6 +60,8 @@ def test_looping_autofocus(start_z, max_loc, centre, attempts_expected, passes,
def adjust_pos(**kwargs: int) -> None: def adjust_pos(**kwargs: int) -> None:
"""Move relative should update position. So make a side effect for the mock.""" """Move relative should update position. So make a side effect for the mock."""
# Remove backlash compensation from the dict if it exists.
kwargs.pop("backlash_compensation", None)
for axis, value in kwargs.items(): for axis, value in kwargs.items():
autofocus_thing._stage.position[axis] += value autofocus_thing._stage.position[axis] += value