Merge branch 'calibration_thing' into 'v3'
Calibrate settling times after moves See merge request openflexure/openflexure-microscope-server!207
This commit is contained in:
commit
92619e2131
3 changed files with 72 additions and 5 deletions
|
|
@ -385,13 +385,17 @@ class JPEGSharpnessMonitor:
|
|||
)
|
||||
return jpeg_heights[np.argmax(jpeg_sizes)]
|
||||
|
||||
def data_dict(self) -> SharpnessDataArrays:
|
||||
"""Return the gathered data as a single convenient dictionary."""
|
||||
def data_to_array(self) -> SharpnessDataArrays:
|
||||
"""Return the gathered data as SharpnessDataArrays."""
|
||||
data = {}
|
||||
for k in ["jpeg_times", "jpeg_sizes", "stage_times", "stage_positions"]:
|
||||
data[k] = getattr(self, k)
|
||||
return SharpnessDataArrays(**data)
|
||||
|
||||
def data_dict(self) -> dict:
|
||||
"""Return the gathered data as dict."""
|
||||
return self.data_to_array().model_dump()
|
||||
|
||||
|
||||
class AutofocusThing(lt.Thing):
|
||||
"""The Thing concerned with combinations of z axis movements and the camera.
|
||||
|
|
@ -432,7 +436,7 @@ class AutofocusThing(lt.Thing):
|
|||
# Move to the target position fz (relative move of (peak - current z))
|
||||
sharpness_monitor.focus_rel(peak_z - base_z)
|
||||
# Return all focus data
|
||||
return sharpness_monitor.data_dict()
|
||||
return sharpness_monitor.data_to_array()
|
||||
|
||||
@lt.action
|
||||
def z_move_and_measure_sharpness(
|
||||
|
|
@ -457,7 +461,7 @@ class AutofocusThing(lt.Thing):
|
|||
if move_index > 0 and wait > 0:
|
||||
time.sleep(wait)
|
||||
sharpness_monitor.focus_rel(current_dz)
|
||||
return sharpness_monitor.data_dict()
|
||||
return sharpness_monitor.data_to_array()
|
||||
|
||||
@lt.action
|
||||
def looping_autofocus(
|
||||
|
|
@ -836,6 +840,21 @@ class AutofocusThing(lt.Thing):
|
|||
final_z = self._stage.position["z"]
|
||||
return final_z, z_positions
|
||||
|
||||
@lt.action
|
||||
def measure_settling_time(
|
||||
self, delay: float = 2, dz: int = 800
|
||||
) -> SharpnessDataArrays:
|
||||
"""Make a Z move down then up by dz, then pause for delay while monitoring sharpness.
|
||||
|
||||
This is useful to see how long to wait for the sharpness value to converge
|
||||
"""
|
||||
with JPEGSharpnessMonitor(self._stage, self._cam) as sharpness_monitor:
|
||||
sharpness_monitor.focus_rel(-dz)
|
||||
sharpness_monitor.focus_rel(dz)
|
||||
# Sleep for the given delay, continuing to measure frames without moving
|
||||
time.sleep(delay)
|
||||
return sharpness_monitor.data_to_array()
|
||||
|
||||
|
||||
class NotAPeakError(lt.exceptions.InvocationError):
|
||||
"""The data to fit isn't a peak."""
|
||||
|
|
|
|||
|
|
@ -114,3 +114,51 @@ def test_camera_stage_mapping_calibration(simulation_test_env):
|
|||
assert isinstance(csm_matrix, list)
|
||||
# Check CSM is as expected to an absolute tolerance of 1e-3
|
||||
assert np.allclose(csm_matrix, expected_matrix, atol=1e-3)
|
||||
|
||||
|
||||
def test_measure_settling_time(simulation_test_env):
|
||||
"""Test measure_settling_time captures data while moving and holding."""
|
||||
autofocus = simulation_test_env.get_thing_client("autofocus")
|
||||
stage = simulation_test_env.get_thing_client("stage")
|
||||
|
||||
start_z = stage.position["z"]
|
||||
|
||||
delay = 1
|
||||
dz = 1000
|
||||
|
||||
data = autofocus.measure_settling_time(delay=delay, dz=dz)
|
||||
|
||||
# Check returned arrays contain data
|
||||
assert len(data["jpeg_times"]) > 0
|
||||
assert len(data["jpeg_sizes"]) > 0
|
||||
assert len(data["stage_times"]) == 4
|
||||
assert len(data["stage_positions"]) == 4
|
||||
|
||||
# Check the stage moved down then back up
|
||||
z_positions = [p["z"] for p in data["stage_positions"]]
|
||||
|
||||
assert z_positions[0] == start_z
|
||||
assert z_positions[1] == start_z - dz
|
||||
assert z_positions[2] == start_z - dz
|
||||
assert z_positions[3] == start_z
|
||||
|
||||
# Check final stage position returned to start
|
||||
assert stage.position["z"] == start_z
|
||||
|
||||
# Check frames continued to be collected after the final move completed
|
||||
final_move_time = data["stage_times"][-1]
|
||||
jpeg_times_after_final_move = [t for t in data["jpeg_times"] if t > final_move_time]
|
||||
|
||||
assert len(jpeg_times_after_final_move) > 0
|
||||
|
||||
|
||||
def test_measure_settling_time_hold_duration(simulation_test_env):
|
||||
"""Test measure_settling_time continues capturing during hold."""
|
||||
autofocus = simulation_test_env.get_thing_client("autofocus")
|
||||
|
||||
short_data = autofocus.measure_settling_time(delay=0, dz=400)
|
||||
long_data = autofocus.measure_settling_time(delay=1, dz=400)
|
||||
|
||||
# Longer delay should collect more JPEG frames
|
||||
assert len(long_data["jpeg_times"]) > len(short_data["jpeg_times"])
|
||||
assert len(long_data["jpeg_sizes"]) > len(short_data["jpeg_sizes"])
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ def test_offset_from(rom_thing, mocker):
|
|||
def test_move_and_measure(perform_autofocus, rom_thing, mocker):
|
||||
"""Test _move_and_measure with and without initial autofocus checking call counts.
|
||||
|
||||
This doesn't test with the repeate autofocus if motion isn't detected
|
||||
This doesn't test with the repeated autofocus if motion isn't detected
|
||||
"""
|
||||
mock_offset_value = {"x": 100, "y": 3}
|
||||
mocker.patch.object(rom_thing, "_offset_from", return_value=mock_offset_value)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue