Merge branch 'dont-test-focus-change' into 'v3'

Remove focus_change_acceptable test

Closes #342

See merge request openflexure/openflexure-microscope-server!249
This commit is contained in:
Julian Stirling 2025-04-23 11:28:05 +00:00
commit 3adc34c30b

View file

@ -66,30 +66,6 @@ def unpack_autofocus(scan_data):
return jpeg_heights[turning[0] : turning[1]], jpeg_sizes_mb[turning[0] : turning[1]] return jpeg_heights[turning[0] : turning[1]], jpeg_sizes_mb[turning[0] : turning[1]]
def focus_change_acceptable(prev_pos, prev_z, new_pos, new_z, fractional_limit):
"""Return true if the change in z-position is small enough.
A large change in z-position in an indication of focus failure.
fractional_limit is the largest ratio of change in z to change in xy that's allowed
"""
prev_xy = np.asarray(prev_pos, dtype="float64")
new_xy = np.asarray(new_pos, dtype="float64")
dist = np.sqrt(np.sum(((new_xy - prev_xy) / 10**4) ** 2, dtype="float64"))
focus_change = abs(new_z - prev_z) / 10**4
if dist == 0:
print(f"Not moved between {prev_pos} and {new_pos}")
movement_ratio = 0
else:
movement_ratio = np.divide(focus_change, dist, dtype="float64")
if movement_ratio > fractional_limit:
return False
return True
class NotEnoughFreeSpaceError(IOError): class NotEnoughFreeSpaceError(IOError):
pass pass
@ -388,7 +364,7 @@ class SmartScanThing(Thing):
self._stage.move_absolute( self._stage.move_absolute(
x=next_point[0], x=next_point[0],
y=next_point[1], y=next_point[1],
z=z_estimate - self._scan_data["autofocus_dz"] / 2, z=z_estimate,
) )
return (next_point[0], next_point[1], z_estimate) return (next_point[0], next_point[1], z_estimate)
@ -578,6 +554,11 @@ class SmartScanThing(Thing):
next_pos_xy, z_est = route_planner.get_next_location_and_z_estimate() next_pos_xy, z_est = route_planner.get_next_location_and_z_estimate()
new_pos_xyz = self._move_to_next_point(next_pos_xy, z_est) new_pos_xyz = self._move_to_next_point(next_pos_xy, z_est)
current_pos_xyz = (
new_pos_xyz[0],
new_pos_xyz[1],
self._stage.position["z"],
)
capture_image = True capture_image = True
# If skipping background, take an image to check if it is background # If skipping background, take an image to check if it is background
@ -596,11 +577,11 @@ class SmartScanThing(Thing):
focused = False focused = False
if self._scan_data["autofocus_on"]: if self._scan_data["autofocus_on"]:
closest_xyz = route_planner.closest_focus_site(new_pos_xyz[:2]) focused, focused_z = self._try_autofocus(new_pos_xyz)
focused = self._try_autofocus(new_pos_xyz, closest_xyz) current_pos_xyz = (new_pos_xyz[0], new_pos_xyz[1], focused_z)
route_planner.mark_location_visited( route_planner.mark_location_visited(
new_pos_xyz, imaged=True, focused=focused current_pos_xyz, imaged=True, focused=focused
) )
# wait for the previous capture to be saved, i.e. don't leave more than one image saving in the background # wait for the previous capture to be saved, i.e. don't leave more than one image saving in the background
@ -625,18 +606,15 @@ class SmartScanThing(Thing):
def _try_autofocus( def _try_autofocus(
self, self,
this_xyz: tuple[int, int, int], this_xyz: tuple[int, int, int],
closest_xyz: Optional[tuple[int, int, int]],
) -> bool: ) -> bool:
""" """
Try to perform autofocus and return boolean for if successful Try to perform autofocus and return boolean for if successful
Args: Args:
this_xyz is the current x,y,z position. this_xyz is the current x,y,z position.
closest_xyz is the (x, y, z) coordinates of the closest position, this is None
if no previous images have been taken or in focus
Return True on successful autofocus. Return True, focused_height on successful autofocus.
Return False if failed after 3 tries - the position will be the initial estimate Return False, initial_height if failed after 3 tries - the position will be the initial estimate
""" """
attempts = 0 attempts = 0
max_attempts = 3 max_attempts = 3
@ -645,46 +623,20 @@ class SmartScanThing(Thing):
while attempts < max_attempts: while attempts < max_attempts:
attempts += 1 attempts += 1
_, jpeg_sizes = self._autofocus.looping_autofocus(dz=dz, start="base") _, jpeg_sizes = self._autofocus.looping_autofocus(dz=dz, start="centre")
current_height = self._stage.position["z"]
time.sleep(0.2) time.sleep(0.2)
autofocus_sharp_enough = self._autofocus.verify_focus_sharpness( autofocus_sharp_enough = self._autofocus.verify_focus_sharpness(
sweep_sizes=jpeg_sizes, camera=CamDep, threshold=0.92 sweep_sizes=jpeg_sizes, camera=CamDep, threshold=0.92
) )
# Not sharp enough, go to start and try again # If sharp enough, break and return success, otherwise go to start and try again
if not autofocus_sharp_enough: if autofocus_sharp_enough:
z = this_xyz[2] - dz / 2 if attempts < max_attempts else this_xyz[2] return True, self._stage.position["z"]
self._stage.move_absolute(z=z) else:
continue self._stage.move_absolute(z=this_xyz[2])
# No previous positions to compare against return success
if closest_xyz is None:
return True
# Check the change in z-position is acceptable
# If the z change compared to the closest focused image exceeds
# a given fraction of the xy displacement this indicates failure
success = focus_change_acceptable(
prev_pos=closest_xyz[:2],
prev_z=closest_xyz[2],
new_pos=this_xyz[:2],
new_z=current_height,
fractional_limit=0.5,
)
# No focus change acceptable return success
if success:
return True
# Shifted to far move to start and try again
z = this_xyz[2] - dz / 2 if attempts < max_attempts else this_xyz[2]
self._stage.move_absolute(z=z)
self._scan_logger.info(
"The focus has shifted further than we expect: retrying."
)
self._scan_logger.warning("Could not autofocus after 3 attempts.") self._scan_logger.warning("Could not autofocus after 3 attempts.")
return False return False, self._stage.position["z"]
@_scan_running @_scan_running
def _wait_for_capture_thread(self) -> None: def _wait_for_capture_thread(self) -> None: