Added debug output and stage lock on fast autofocus

This commit is contained in:
Joel Collins 2020-03-27 14:17:11 +00:00
parent 03969fdfb2
commit b9fb94d19d

View file

@ -8,11 +8,13 @@ from openflexure_microscope.utilities import set_properties
import time import time
import numpy as np import numpy as np
import threading
import logging import logging
from scipy import ndimage from scipy import ndimage
from contextlib import contextmanager from contextlib import contextmanager
from threading import Thread, Event
### Autofocus utilities ### Autofocus utilities
@ -25,7 +27,7 @@ class JPEGSharpnessMonitor:
self.jpeg_times = [] self.jpeg_times = []
self.stage_positions = [] self.stage_positions = []
self.stage_times = [] self.stage_times = []
self.stop_event = threading.Event() self.stop_event = Event()
self.timeout = timeout self.timeout = timeout
self.keep_alive() self.keep_alive()
self.background_thread = None self.background_thread = None
@ -48,26 +50,31 @@ class JPEGSharpnessMonitor:
def start(self): def start(self):
"Start monitoring sharpness by looking at JPEG size" "Start monitoring sharpness by looking at JPEG size"
self.background_thread = threading.Thread(target=self._measure_jpegs) self.background_thread = Thread(target=self._measure_jpegs)
self.background_thread.start() self.background_thread.start()
return self return self
def stop(self): def stop(self):
"Stop the background thread" "Stop the background thread"
self.stop_event.set() self.stop_event.set()
print("Joining JPEG thread")
self.background_thread.join() self.background_thread.join()
def _measure_jpegs(self): def _measure_jpegs(self):
"Function that runs in a background thread to record sharpness" "Function that runs in a background thread to record sharpness"
logging.info("Starting sharpness measurement in background thread") logging.debug("Starting sharpness measurement in background thread")
self.keep_alive() self.keep_alive()
logging.debug(f"_measure_jpegs stop_event: {self.stop_event.is_set()}")
while not self.stop_event.is_set() and not self.should_stop(): while not self.stop_event.is_set() and not self.should_stop():
self.jpeg_sizes.append(self.jpeg_size()) size_now = self.jpeg_size()
self.jpeg_times.append(time.time()) time_now = time.time()
self.jpeg_sizes.append(size_now)
self.jpeg_times.append(time_now)
print("Exited JPEG measure loop")
if self.stop_event.is_set(): if self.stop_event.is_set():
logging.info("Cleanly stopped sharpness measurement in background thread") logging.debug("Cleanly stopped sharpness measurement in background thread")
if self.should_stop(): if self.should_stop():
logging.info("Sharpness measurement timed out and has stopped") logging.debug("Sharpness measurement timed out and has stopped")
def jpeg_size(self): def jpeg_size(self):
"""Return the size of a frame from the MJPEG stream""" """Return the size of a frame from the MJPEG stream"""
@ -195,7 +202,7 @@ def move_and_find_focus(microscope, dz):
def fast_autofocus(microscope, dz=2000, backlash=None): def fast_autofocus(microscope, dz=2000, backlash=None):
"""Perform a down-up-down-up autofocus""" """Perform a down-up-down-up autofocus"""
with monitor_sharpness(microscope) as m, microscope.camera.lock: with monitor_sharpness(microscope) as m, microscope.camera.lock, microscope.stage.lock:
i, z = m.focus_rel(-dz / 2) i, z = m.focus_rel(-dz / 2)
i, z = m.focus_rel(dz) i, z = m.focus_rel(dz)
fz = m.sharpest_z_on_move(i) fz = m.sharpest_z_on_move(i)
@ -245,17 +252,20 @@ def fast_up_down_up_autofocus(
might slightly hurt accuracy, but is unlikely to be a big issue. Too big might slightly hurt accuracy, but is unlikely to be a big issue. Too big
may cause you to overshoot, which is a problem. may cause you to overshoot, which is a problem.
""" """
with monitor_sharpness(microscope) as m, microscope.camera.lock: with monitor_sharpness(microscope) as m, microscope.camera.lock, microscope.stage.lock:
# Ensure the MJPEG stream has started # Ensure the MJPEG stream has started
microscope.camera.start_stream_recording() microscope.camera.start_stream_recording()
df = dz # TODO: refactor so I actually use dz in the code below! df = dz # TODO: refactor so I actually use dz in the code below!
logging.debug("Initial move")
if initial_move_up: if initial_move_up:
m.focus_rel(df / 2) m.focus_rel(df / 2)
# move down # move down
logging.debug("Move down")
i, z = m.focus_rel(-df) i, z = m.focus_rel(-df)
# now inspect where the sharpest point is, and estimate the sharpness # now inspect where the sharpest point is, and estimate the sharpness
# (JPEG size) that we should find at the start of the Z stack # (JPEG size) that we should find at the start of the Z stack
logging.debug("Get target_s")
jt, jz, js = m.move_data(i) jt, jz, js = m.move_data(i)
best_z = jz[np.argmax(js)] best_z = jz[np.argmax(js)]
target_s = np.interp( target_s = np.interp(
@ -263,11 +273,13 @@ def fast_up_down_up_autofocus(
) # NB jz is decreasing ) # NB jz is decreasing
# now move to the start of the z stack # now move to the start of the z stack
logging.debug("Move to the start of the z stack")
i, z = m.focus_rel( i, z = m.focus_rel(
best_z + target_z - z + mini_backlash best_z + target_z - z + mini_backlash
) # takes us to the start of the stack ) # takes us to the start of the stack
# We've deliberately undershot - figure out how much further we should move based on the curve # We've deliberately undershot - figure out how much further we should move based on the curve
logging.debug("Calculate remining movement")
current_js = m.jpeg_size() current_js = m.jpeg_size()
imax = np.argmax(js) # we want to crop out just the bit below the peak imax = np.argmax(js) # we want to crop out just the bit below the peak
js = js[imax:] # NB z is in DECREASING order js = js[imax:] # NB z is in DECREASING order
@ -279,6 +291,7 @@ def fast_up_down_up_autofocus(
# So, the Z position corresponding to our current sharpness value is zs[inow] # So, the Z position corresponding to our current sharpness value is zs[inow]
# That means we should move forwards, by best_z - zs[inow] # That means we should move forwards, by best_z - zs[inow]
logging.debug("Correction move")
correction_move = best_z + target_z - jz[inow] correction_move = best_z + target_z - jz[inow]
logging.debug( logging.debug(
"Fast autofocus scan: correcting backlash by moving {} steps".format( "Fast autofocus scan: correcting backlash by moving {} steps".format(
@ -317,7 +330,7 @@ class AutofocusAPI(View):
dz = payload.param("dz", default=np.linspace(-300, 300, 7), convert=np.array) dz = payload.param("dz", default=np.linspace(-300, 300, 7), convert=np.array)
if microscope.has_real_stage(): if microscope.has_real_stage():
logging.info("Running autofocus...") logging.debug("Running autofocus...")
task = taskify(autofocus)(microscope, dz) task = taskify(autofocus)(microscope, dz)
# return a handle on the autofocus task # return a handle on the autofocus task
@ -348,7 +361,7 @@ class FastAutofocusAPI(View):
backlash = 0 backlash = 0
if microscope.has_real_stage(): if microscope.has_real_stage():
logging.info("Running autofocus...") logging.debug("Running autofocus...")
task = taskify(fast_up_down_up_autofocus)(microscope, dz=dz, mini_backlash=backlash) task = taskify(fast_up_down_up_autofocus)(microscope, dz=dz, mini_backlash=backlash)
# return a handle on the autofocus task # return a handle on the autofocus task