A stack that tests whether the sharpest image is towards the middle
This commit is contained in:
parent
67e8b19603
commit
3e164102f6
3 changed files with 100 additions and 39 deletions
|
|
@ -266,6 +266,16 @@ class AutofocusThing(Thing):
|
||||||
def stack_images_to_capture(self, value: int) -> None:
|
def stack_images_to_capture(self, value: int) -> None:
|
||||||
self.thing_settings["stack_images_to_capture"] = value
|
self.thing_settings["stack_images_to_capture"] = value
|
||||||
|
|
||||||
|
@thing_property
|
||||||
|
def stack_images_to_test(self) -> int:
|
||||||
|
"""The number of images to test for successful focusing in a stack
|
||||||
|
Defaults to 9, which balances reliability and speed"""
|
||||||
|
return self.thing_settings.get("stack_images_to_test", 9)
|
||||||
|
|
||||||
|
@stack_images_to_test.setter
|
||||||
|
def stack_images_to_test(self, value: int) -> None:
|
||||||
|
self.thing_settings["stack_images_to_test"] = value
|
||||||
|
|
||||||
@thing_property
|
@thing_property
|
||||||
def stack_dz(self) -> int:
|
def stack_dz(self) -> int:
|
||||||
"""Space in steps between images in a z-stack
|
"""Space in steps between images in a z-stack
|
||||||
|
|
@ -287,35 +297,78 @@ class AutofocusThing(Thing):
|
||||||
metadata_getter: GetThingStates,
|
metadata_getter: GetThingStates,
|
||||||
capture: CaptureDep,
|
capture: CaptureDep,
|
||||||
images_dir: str,
|
images_dir: str,
|
||||||
stack_dir: str,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run a z stack, saving all images to stack_dir and copying the
|
"""Run a z stack, saving all images to stack_dir and copying the
|
||||||
central image to stack_dir"""
|
central image to stack_dir"""
|
||||||
stack_dz = self.stack_dz
|
stack_dz = self.stack_dz
|
||||||
images_to_capture = self.stack_images_to_capture
|
images_to_capture = self.stack_images_to_capture
|
||||||
|
images_to_test = self.stack_images_to_test
|
||||||
|
|
||||||
stack_z_range = stack_dz * (images_to_capture - 1)
|
if images_to_test < images_to_capture:
|
||||||
stage.move_relative(z=-stack_z_range / 2)
|
raise RuntimeError(
|
||||||
|
"Can't capture more images than are tested. Please increase number to test, or decrease number to capture"
|
||||||
|
)
|
||||||
|
if images_to_test % 2 == 0:
|
||||||
|
raise RuntimeError("Images to test should be odd")
|
||||||
|
|
||||||
for capture_count in range(images_to_capture):
|
stack_z_range = stack_dz * (images_to_test - 1)
|
||||||
|
overshoot = stack_dz * 5
|
||||||
|
backlash_correction = 250
|
||||||
|
|
||||||
|
stage.move_relative(z=-(overshoot + backlash_correction + stack_z_range / 2))
|
||||||
|
stage.move_relative(z=backlash_correction)
|
||||||
|
|
||||||
|
continue_stack = True
|
||||||
|
captures = []
|
||||||
|
sharpnesses = []
|
||||||
|
heights = []
|
||||||
|
capture_count = 0
|
||||||
|
starting_height = stage.position["z"]
|
||||||
|
|
||||||
|
while continue_stack:
|
||||||
time.sleep(SETTLING_TIME)
|
time.sleep(SETTLING_TIME)
|
||||||
|
|
||||||
|
stage_location = stage.position
|
||||||
|
|
||||||
jpeg_path = os.path.join(
|
jpeg_path = os.path.join(
|
||||||
stack_dir,
|
images_dir,
|
||||||
f"{capture_count}.jpeg",
|
f"{stage_location['x']}_{stage_location['y']}_{stage_location['z']}.jpeg",
|
||||||
)
|
)
|
||||||
capture._capture_and_save(
|
image, metadata = capture._capture_image(
|
||||||
jpeg_path=jpeg_path,
|
|
||||||
cam=cam,
|
cam=cam,
|
||||||
logger=logger,
|
|
||||||
metadata_getter=metadata_getter,
|
metadata_getter=metadata_getter,
|
||||||
)
|
)
|
||||||
|
captures.append([jpeg_path, image, metadata])
|
||||||
|
sharpnesses.append(cam.grab_jpeg_size(stream_name="lores"))
|
||||||
|
heights.append(stage.position["z"])
|
||||||
|
capture_count += 1
|
||||||
|
|
||||||
# If the stack isn't complete yet, move
|
# If the stack isn't complete yet, move
|
||||||
if capture_count + 1 < images_to_capture:
|
if capture_count >= images_to_test:
|
||||||
stage.move_relative(z=stack_dz)
|
logger.info(sharpnesses[-images_to_test:])
|
||||||
|
stack_result = self.test_stack(sharpnesses[-images_to_test:])
|
||||||
|
|
||||||
self.copy_central_image_from_stack(images_dir, stack_dir)
|
if stack_result == "success":
|
||||||
|
continue_stack = False
|
||||||
|
elif stack_result == "restart" or capture_count > 20:
|
||||||
|
captures = []
|
||||||
|
sharpnesses = []
|
||||||
|
heights = []
|
||||||
|
capture_count = 0
|
||||||
|
stage.move_absolute(
|
||||||
|
z=starting_height - stack_dz * (images_to_test - 1)
|
||||||
|
)
|
||||||
|
stage.move_relative(z=stack_dz)
|
||||||
|
|
||||||
|
sharpest_index = np.argmax(sharpnesses[-images_to_test:])
|
||||||
|
capture._save_capture(
|
||||||
|
jpeg_path=captures[-images_to_test:][sharpest_index][0],
|
||||||
|
image=captures[-images_to_test:][sharpest_index][1],
|
||||||
|
metadata=captures[-images_to_test:][sharpest_index][2],
|
||||||
|
logger=logger,
|
||||||
|
)
|
||||||
|
|
||||||
|
return heights[-images_to_test:][sharpest_index]
|
||||||
|
|
||||||
def copy_central_image_from_stack(
|
def copy_central_image_from_stack(
|
||||||
self,
|
self,
|
||||||
|
|
@ -331,3 +384,22 @@ class AutofocusThing(Thing):
|
||||||
xy_location = os.path.basename(stack_dir)
|
xy_location = os.path.basename(stack_dir)
|
||||||
|
|
||||||
shutil.copy(central_image, os.path.join(images_dir, f"{xy_location}.jpeg"))
|
shutil.copy(central_image, os.path.join(images_dir, f"{xy_location}.jpeg"))
|
||||||
|
|
||||||
|
def test_stack(self, sharpnesses: list):
|
||||||
|
"""Test a list of sharpnesses, to decide whether the focal plane is within them"""
|
||||||
|
sharpest_index = np.argmax(sharpnesses)
|
||||||
|
sharpness_length = len(sharpnesses)
|
||||||
|
|
||||||
|
if sharpness_length == 1:
|
||||||
|
return "success"
|
||||||
|
if sharpness_length == 3:
|
||||||
|
if sharpest_index == 1:
|
||||||
|
return "success"
|
||||||
|
|
||||||
|
exclusion_range = 3
|
||||||
|
|
||||||
|
if sharpest_index < exclusion_range:
|
||||||
|
return "restart"
|
||||||
|
if sharpest_index >= len(sharpnesses) - exclusion_range:
|
||||||
|
return "continue"
|
||||||
|
return "success"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import numpy as np
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import time
|
import time
|
||||||
import piexif
|
import piexif
|
||||||
|
|
@ -48,7 +47,8 @@ class CaptureThing(Thing):
|
||||||
f"Acquired {jpeg_path} in {acquisition_duration}s then {saving_duration}s saving to disk"
|
f"Acquired {jpeg_path} in {acquisition_duration}s then {saving_duration}s saving to disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _capture_image(self, cam, metadata_getter) -> tuple[np.ndarray, dict]:
|
@thing_action
|
||||||
|
def _capture_image(self, cam, metadata_getter):
|
||||||
"""Capture an image in memory and return it with metadata
|
"""Capture an image in memory and return it with metadata
|
||||||
CaptureError raised if the capture fails for any reason
|
CaptureError raised if the capture fails for any reason
|
||||||
returns tuple with numpy array of image data, and dict of metadata
|
returns tuple with numpy array of image data, and dict of metadata
|
||||||
|
|
@ -60,10 +60,11 @@ class CaptureThing(Thing):
|
||||||
raise CaptureError("An error occurred while capturing") from e
|
raise CaptureError("An error occurred while capturing") from e
|
||||||
return image, metadata
|
return image, metadata
|
||||||
|
|
||||||
|
@thing_action
|
||||||
def _save_capture(
|
def _save_capture(
|
||||||
self,
|
self,
|
||||||
jpeg_path: str,
|
jpeg_path: str,
|
||||||
image: np.ndarray,
|
image,
|
||||||
metadata: dict,
|
metadata: dict,
|
||||||
logger: InvocationLogger,
|
logger: InvocationLogger,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
||||||
|
|
@ -620,6 +620,8 @@ class SmartScanThing(Thing):
|
||||||
self._manage_stitching_threads()
|
self._manage_stitching_threads()
|
||||||
|
|
||||||
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()
|
||||||
|
self._scan_logger.info(z_est)
|
||||||
|
self._scan_logger.info(self._stage.position["z"])
|
||||||
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 = (
|
current_pos_xyz = (
|
||||||
new_pos_xyz[0],
|
new_pos_xyz[0],
|
||||||
|
|
@ -642,32 +644,18 @@ class SmartScanThing(Thing):
|
||||||
self._scan_logger.info(msg)
|
self._scan_logger.info(msg)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
focused = False
|
focused_height = self._autofocus.run_z_stack(
|
||||||
if self._scan_data["autofocus_on"]:
|
images_dir=self._ongoing_scan_images_dir,
|
||||||
self._autofocus.looping_autofocus(
|
)
|
||||||
dz=self._scan_data["autofocus_dz"], start="centre"
|
|
||||||
)
|
current_pos_xyz = (
|
||||||
current_pos_xyz = (
|
new_pos_xyz[0],
|
||||||
new_pos_xyz[0],
|
new_pos_xyz[1],
|
||||||
new_pos_xyz[1],
|
focused_height,
|
||||||
self._stage.position["z"],
|
)
|
||||||
)
|
|
||||||
# Without a test for autofocus success, assume it worked
|
|
||||||
focused = True
|
|
||||||
|
|
||||||
route_planner.mark_location_visited(
|
route_planner.mark_location_visited(
|
||||||
current_pos_xyz, imaged=True, focused=focused
|
current_pos_xyz, imaged=True, focused=True
|
||||||
)
|
|
||||||
|
|
||||||
site_folder = os.path.join(
|
|
||||||
self._ongoing_scan_images_dir,
|
|
||||||
"stacks",
|
|
||||||
f"{new_pos_xyz[0]}_{new_pos_xyz[1]}",
|
|
||||||
)
|
|
||||||
os.makedirs(site_folder, exist_ok=True)
|
|
||||||
self._autofocus.run_z_stack(
|
|
||||||
images_dir=self._ongoing_scan_images_dir,
|
|
||||||
stack_dir=site_folder,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# increment capure counter as thread has completed
|
# increment capure counter as thread has completed
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue