From 664bb00c128f6e8c562d5cd98e7a9fe2b745b349 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Thu, 7 May 2026 19:45:24 +0100 Subject: [PATCH] Allow users to specify method for autofocus --- .../things/autofocus.py | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/openflexure_microscope_server/things/autofocus.py b/src/openflexure_microscope_server/things/autofocus.py index 98726c86..b7c92b5e 100644 --- a/src/openflexure_microscope_server/things/autofocus.py +++ b/src/openflexure_microscope_server/things/autofocus.py @@ -380,6 +380,14 @@ class JPEGSharpnessMonitor: sharpnesses = np.array(self.jpeg_sizes) elif self.method == SharpnessMethod.FOCUS_FOM: sharpnesses = np.array(self.focus_foms) + + # If no valid FocusFoM values were recorded, fall back to JPEG size + if np.all(np.isnan(self.focus_foms)): + LOGGER.warning( + "Sharpness method FOCUS_FOM selected, but no FocusFoM values " + "were recorded. Falling back to JPEG sharpness." + ) + sharpnesses = np.array(self.jpeg_sizes) else: raise ValueError(f"Unknown sharpness method: {self.method}") stage_times: np.ndarray = np.array(self.stage_times)[istart:istop] @@ -449,14 +457,19 @@ class AutofocusThing(lt.Thing): self, dz: int = 2000, start: Literal["centre", "base"] = "centre", + sharpness_metric: SharpnessMethod = SharpnessMethod.JPEG, ) -> SharpnessDataArrays: """Sweep the stage up and down, then move to the sharpest point. This method will will move down by dz/2, sweep up by dz, and then evaluate the position where the image was sharpest. We'll then move back down, and finally up to the sharpest point. + + sharpness_metric is either JPEG file size (JPEG) or inbuilt figure of metric (FOCUS_FOM). """ - with JPEGSharpnessMonitor(self._stage, self._cam) as sharpness_monitor: + with JPEGSharpnessMonitor( + self._stage, self._cam, sharpness_metric + ) as sharpness_monitor: # Move to (-dz / 2) if start == "centre": sharpness_monitor.focus_rel(-dz // 2) @@ -483,7 +496,7 @@ class AutofocusThing(lt.Thing): """Make a move (or a series of moves) and monitor sharpness. This method will will make a series of relative moves in z, and - return the sharpness (JPEG size) vs time, along with timestamps + return the sharpness (JPEG size and FOM) vs time, along with timestamps for the moves. This can be used to calibrate autofocus. Each move is relative to the last one, i.e. we will finish at @@ -504,6 +517,7 @@ class AutofocusThing(lt.Thing): self, dz: int = 2000, start: Literal["centre", "base"] = "centre", + sharpness_metric: SharpnessMethod = SharpnessMethod.JPEG, ) -> tuple[list[float], list[float]]: """Repeatedly autofocus the stage until it looks focused. @@ -511,10 +525,14 @@ class AutofocusThing(lt.Thing): in the middle 3/5 of its range. Such logic can be helpful if the microscope is close to focus, but not quite within ``dz/2``. It will attempt to autofocus up to 10 times. + + sharpness_metric is either JPEG file size (JPEG) or inbuilt figure of metric (FOCUS_FOM). """ attempt = 0 - with JPEGSharpnessMonitor(self._stage, self._cam) as sharpness_monitor: + with JPEGSharpnessMonitor( + self._stage, self._cam, sharpness_metric + ) as sharpness_monitor: while attempt < 10: attempt += 1 if start == "centre": @@ -531,9 +549,11 @@ class AutofocusThing(lt.Thing): focus_data_index, _ = sharpness_monitor.focus_rel( dz, block_cancellation=True ) - _times, heights, sizes = sharpness_monitor.move_data(focus_data_index) + _times, heights, sharpnesses = sharpness_monitor.move_data( + focus_data_index + ) - peak_height = heights[np.argmax(sizes)] + peak_height = heights[np.argmax(sharpnesses)] target_min = np.min(heights) + dz / 5 target_max = np.max(heights) - dz / 5 @@ -545,7 +565,7 @@ class AutofocusThing(lt.Thing): if target_min < peak_height < target_max: # If it is within the target range then return - return heights.tolist(), sizes.tolist() + return heights.tolist(), sharpnesses.tolist() raise NoFocusFoundError( "Looping autofocus couldn't converge on a focus location." )