diff --git a/src/openflexure_microscope_server/things/autofocus.py b/src/openflexure_microscope_server/things/autofocus.py index 4e966d27..f833ba53 100644 --- a/src/openflexure_microscope_server/things/autofocus.py +++ b/src/openflexure_microscope_server/things/autofocus.py @@ -26,6 +26,7 @@ from pydantic import BaseModel Stage = direct_thing_client_dependency(SangaboardThing, "/stage/") Camera = raw_thing_dependency(StreamingPiCamera2) +WrappedCamera = direct_thing_client_dependency(StreamingPiCamera2, "/camera/") ### Autofocus utilities @@ -206,12 +207,14 @@ class AutofocusThing(Thing): """ repeat = True attempts = 0 + backlash = 200 with m.run(): while repeat and attempts < 10: if start == 'centre': - stage.move_relative(x = 0, y = 0, z = -dz / 2) + stage.move_relative(x = 0, y = 0, z = -(backlash + dz / 2)) + stage.move_relative(x = 0, y = 0, z = backlash) i, z = m.focus_rel(dz, block_cancellation=True) _, heights, sizes = m.move_data(i) @@ -226,8 +229,24 @@ class AutofocusThing(Thing): ): attempts += 1 start = 'centre' + stage.move_absolute(z = peak_height-backlash) stage.move_absolute(z = peak_height) else: repeat = False - stage.move_relative(x = 0, y = 0, z = -dz) - stage.move_absolute(z = peak_height) \ No newline at end of file + stage.move_relative(x = 0, y = 0, z = -(dz+backlash)) + stage.move_absolute(z = peak_height) + return heights.tolist(), sizes.tolist() + + @thing_action + def verify_focus_sharpness(self, sweep_sizes: list, camera: WrappedCamera, leniency: int = 1): + '''Take the sharpness curve of the autofocus, and the size of the current frame + to see if the autofocus completed successfully. Returns True if current sharpness + is within "leniency" number of frames from the peak of the autofocus''' + + current_sharpness = camera.grab_jpeg_size(stream_name='lores') + + peak = np.argmax(sweep_sizes) + accepted_range = [peak - leniency, peak + leniency] + + sharpness_range = [sweep_sizes[i] for i in accepted_range] + return current_sharpness >= min(sharpness_range) \ No newline at end of file diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index b03c2d93..2c38c7a6 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -640,24 +640,29 @@ class SmartScanThing(Thing): attempts = 0 if self.autofocus_dz > 200: while True: - autofocus.looping_autofocus(dz=self.autofocus_dz, start = 'base') + jpeg_zs, jpeg_sizes = autofocus.looping_autofocus(dz=self.autofocus_dz, start = 'base') current_height = stage.position["z"] + autofocus_success = autofocus.verify_focus_sharpness(sweep_sizes = jpeg_sizes, camera = CamDep, leniency = 1) + logger.info(f"We just tested the focus! Result was {autofocus_success}") - # if there have been successful autofocuses in this scan, find the closest one in x-y - # test if the change in z between them exceeds a ratio (indicating a failed autofocus) - if len(focused_path) > 0: - nearest_focused_site = focused_path[closest(loc, focused_path)] - result = limit_focus_change( - nearest_focused_site[0:2], - nearest_focused_site[-1], - loc[0:2], - current_height, - 0.3, - ) + if autofocus_success: + # if there have been successful autofocuses in this scan, find the closest one in x-y + # test if the change in z between them exceeds a ratio (indicating a failed autofocus) + if len(focused_path) > 0: + nearest_focused_site = focused_path[closest(loc, focused_path)] + result = limit_focus_change( + nearest_focused_site[0:2], + nearest_focused_site[-1], + loc[0:2], + current_height, + 0.3, + ) - # if there haven't been any previous autofocuses, we have to assume this one worked + # if there haven't been any previous autofocuses, we have to assume this one worked + else: + result = "accept" else: - result = "accept" + result = "reject" # if the autofocus worked, add the current position to the list of successful locations if result == "accept": @@ -762,6 +767,15 @@ class SmartScanThing(Thing): def max_range(self, value: int) -> None: self.thing_settings["max_range"] = value + @thing_property + def stitch_tiff(self) -> bool: + """The maximum distance from the centre of the scan before we break""" + return self.thing_settings.get("stitch_tiff", False) + + @stitch_tiff.setter + def stitch_tiff(self, value: bool) -> None: + self.thing_settings["stitch_tiff"] = value + @thing_property def skip_background(self) -> bool: """Whether to detect and skip empty fields of view