Restructured fast autofocus into main autofocus plugin

This commit is contained in:
Joel Collins 2019-04-08 15:09:36 +03:00
parent edc5e7c241
commit 24ded8de32
4 changed files with 157 additions and 4 deletions

View file

@ -1,6 +1,104 @@
import time
import numpy as np
import threading
import logging
from scipy import ndimage
class JPEGSharpnessMonitor():
def __init__(self, microscope, timeout=60):
self.microscope = microscope
self.camera = microscope.camera
self.stage = microscope.stage
self.jpeg_sizes = []
self.jpeg_times = []
self.stage_positions = []
self.stage_times = []
self.stop_event = threading.Event()
self.timeout = timeout
self.keep_alive()
self.background_thread = None
def is_alive(self):
if self.background_thread is None:
return False
else:
return self.background_thread.is_alive()
def should_stop(self):
import time
return time.time() - self.kept_alive > self.timeout
def keep_alive(self):
import time
self.kept_alive = time.time()
def start(self):
"Start monitoring sharpness by looking at JPEG size"
self.background_thread = threading.Thread(target=self._measure_jpegs)
self.background_thread.start()
return self
def stop(self):
"Stop the background thread"
self.stop_event.set()
self.background_thread.join()
def _measure_jpegs(self):
"Function that runs in a background thread to record sharpness"
logging.info("Starting sharpness measurement in background thread")
self.keep_alive()
while not self.stop_event.is_set() and not self.should_stop():
self.jpeg_sizes.append(self.jpeg_size())
self.jpeg_times.append(time.time())
if self.stop_event.is_set():
logging.info("Cleanly stopped sharpness measurement in background thread")
if self.should_stop():
logging.info("Sharpness measurement timed out and has stopped")
def jpeg_size(self):
"""Return the size of a frame from the MJPEG stream"""
return len(self.camera.get_frame())
def focus_rel(self, dz, backlash=False, **kwargs):
self.keep_alive()
self.stage_times.append(time.time())
self.stage_positions.append(self.stage.position)
self.stage.move_rel([0, 0, dz], backlash=backlash, **kwargs)
self.stage_times.append(time.time())
self.stage_positions.append(self.stage.position)
i = len(self.stage_positions) - 2
return i, self.stage_positions[-1][2]
def move_data(self, istart, istop=None):
"Extract sharpness as a function of (interpolated) z"
global np, logging
if istop is None:
istop = istart + 2
jpeg_times = np.array(self.jpeg_times)
jpeg_sizes = np.array(self.jpeg_sizes)
stage_times = np.array(self.stage_times)[istart:istop]
stage_zs = np.array(self.stage_positions)[istart:istop, 2]
start = np.argmax(jpeg_times > stage_times[0])
stop = np.argmax(jpeg_times > stage_times[1])
if stop < 1:
stop = len(jpeg_times)
logging.debug("changing stop to {}".format(stop))
jpeg_times = jpeg_times[start:stop]
jpeg_zs = np.interp(jpeg_times, stage_times, stage_zs)
return jpeg_times, jpeg_zs, jpeg_sizes[start:stop]
def sharpest_z_on_move(self, index):
"""Return the z position of the sharpest image on a given move"""
jt, jz, js = self.move_data(index)
return jz[np.argmax(js)]
def data_dict(self):
"""Return the gathered data as a single convenient dictionary"""
data = {}
for k in ['jpeg_times', 'jpeg_sizes', 'stage_times', 'stage_positions']:
data[k] = getattr(self, k)
return data
def decimate_to(shape, image):
"""Decimate an image to reduce its size if it's too big."""