From e90eb7c2d47e5e72a6658966c079b5b405efb8f2 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Mon, 8 Apr 2019 22:40:50 +0100 Subject: [PATCH 1/3] deleted old autofocus plugin --- .../default/fast_autofocus/__init__.py | 168 ------------------ 1 file changed, 168 deletions(-) delete mode 100644 openflexure_microscope/plugins/default/fast_autofocus/__init__.py diff --git a/openflexure_microscope/plugins/default/fast_autofocus/__init__.py b/openflexure_microscope/plugins/default/fast_autofocus/__init__.py deleted file mode 100644 index 885a1f1b..00000000 --- a/openflexure_microscope/plugins/default/fast_autofocus/__init__.py +++ /dev/null @@ -1,168 +0,0 @@ -import time -import numpy as np -import threading - -from openflexure_microscope.plugins import MicroscopePlugin -from openflexure_microscope.utilities import set_properties -from openflexure_microscope.api.v1.views import MicroscopeViewPlugin -from openflexure_microscope.api.utilities import JsonPayload -from flask import request, jsonify, abort, current_app -import logging -from contextlib import contextmanager - -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 - -class FastAutofocusAPI(MicroscopeViewPlugin): - def post(self): - payload = JsonPayload(request) - - # Figure out the parameters to use - dz = payload.param("dz", default=2000, convert=int) - backlash = payload.param("backlash", default=None, convert=int) - if backlash < 0: - backlash = None - - print("Running autofocus...") - task = self.microscope.task.start(self.plugin.fast_autofocus, dz, backlash=backlash) - - # return a handle on the autofocus task - return jsonify(task.state), 202 - - - -class Plugin(MicroscopePlugin): - """ - Plugin to provide fast autofocus - """ - JPEGSharpnessMonitor = JPEGSharpnessMonitor # make the class available - def sharpness_monitor(self): - return JPEGSharpnessMonitor(self.microscope) - - @contextmanager - def monitor_sharpness(self): - m = self.sharpness_monitor() - m.start() - try: - yield m - finally: - m.stop() - - def move_and_find_focus(self, dz): - """Make a relative Z move and return the peak sharpness position""" - with self.monitor_sharpness() as m: - m.focus_rel(dz) - return m.sharpest_z_on_move(0) - - - def fast_autofocus(self, dz=2000, backlash=None): - """Perform a down-up-down-up autofocus""" - with self.monitor_sharpness() as m: - i, z = m.focus_rel(-dz/2) - i, z = m.focus_rel(dz) - fz = m.sharpest_z_on_move(i) - if backlash is None: - i, z = m.focus_rel(-dz) # move all the way to the start so it's consistent - else: - i, z = m.focus_rel(fz - z - backlash) - m.focus_rel(fz - z) - return m.data_dict() - - - api_views = { - '/fast_autofocus': FastAutofocusAPI, - } - From e6ec800c21dbd1c10387561dc57881073eae70a9 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Mon, 8 Apr 2019 22:41:12 +0100 Subject: [PATCH 2/3] Fixed camera settings Since splitting the camera config into a separate YAML file, we've broken the ability to change the camera's configuration via the API (or at any time except when the camera is set up, I think). This (mostly) fixes that, though I get type errors that I've had to work around (e.g. when setting shutter_speed), see `pi.py` --- openflexure_microscope/api/v1/blueprints/base.py | 10 +++++----- openflexure_microscope/camera/base.py | 8 ++++++++ openflexure_microscope/camera/pi.py | 7 ++++++- openflexure_microscope/microscope.py | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/openflexure_microscope/api/v1/blueprints/base.py b/openflexure_microscope/api/v1/blueprints/base.py index 022015c7..2dc7c600 100644 --- a/openflexure_microscope/api/v1/blueprints/base.py +++ b/openflexure_microscope/api/v1/blueprints/base.py @@ -2,6 +2,7 @@ from openflexure_microscope.api.utilities import gen, JsonPayload from openflexure_microscope.api.v1.views import MicroscopeView from flask import Response, Blueprint, jsonify, request +import logging class StreamAPI(MicroscopeView): @@ -99,8 +100,6 @@ class ConfigAPI(MicroscopeView): Content-Type: application/json { - "analog_gain": 1.0, - "digital_gain": 1.0, "image_resolution": [ 2592, 1944 @@ -111,7 +110,9 @@ class ConfigAPI(MicroscopeView): 1312, 976 ], - "picamera_params": { + "picamera_settings": { + "analog_gain": 1.0, + "digital_gain": 1.0, "awb_gains": [ 0.92578125, 2.94921875 @@ -125,7 +126,6 @@ class ConfigAPI(MicroscopeView): "plugins": [ "openflexure_microscope.plugins.default:Plugin" ], - "shading_table_path": "/home/pi/.openflexure/microscopelst.npy", "video_resolution": [ 832, 624 @@ -170,7 +170,7 @@ class ConfigAPI(MicroscopeView): """ payload = JsonPayload(request) - print(payload.json) + logging.debug("Updating settings from POST request.") self.microscope.write_config(payload.json) self.microscope.save_config() diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index c3f237d8..1477f8a7 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -125,6 +125,14 @@ class BaseCamera(object): self.images = self.load_capture_db(self.images_db) self.videos = self.load_capture_db(self.videos_db) + def apply_config(self, config): + """Update settings from a config dictionary""" + self.config.update(config) + + def read_config(self, config): + """Return the current settings as a dictionary""" + return self.config + def __enter__(self): """Create camera on context enter.""" return self diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 7dd40b03..3f693ad6 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -184,12 +184,17 @@ class StreamingCamera(BaseCamera): set_digital_gain(self.camera, value) elif key == 'analog_gain': set_analog_gain(self.camera, value) + elif key == "shutter_speed": + self.camera.shutter_speed = int(value) else: setattr(self.camera, key, value) # Write setting to camera # StreamingCamera parameters (applied via StreamingCamera config) for key, value in config.items(): # For each provided setting - if key != 'picamera_settings': # We already handled this + if key in self.config.keys(): + if key != 'picamera_settings': # We already handled this + logging.warn("{} is not in the streaming camera settings dictionary - adding it.") + #continue #TODO: filter settings somehow? logging.debug("Setting parameter {}: {}".format(key, value)) self.config[key] = value diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 45fcc398..fcc3030b 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -192,7 +192,7 @@ class Microscope(object): # If attached to a camera if self.camera: # Update camera config - self.camera.config.update(config) + self.camera.apply_config(config) # If attached to a stage # TODO: Convert stage settings into a config expansion From 33f719894d26f5bc575aa581eb5aea5af6a860df Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 9 Apr 2019 06:27:07 +0000 Subject: [PATCH 3/3] Fixed if statement for updating camera config. --- openflexure_microscope/camera/pi.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 3f693ad6..401f0012 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -185,14 +185,15 @@ class StreamingCamera(BaseCamera): elif key == 'analog_gain': set_analog_gain(self.camera, value) elif key == "shutter_speed": + # TODO: use types from CONFIG_KEYS? @jtc42 self.camera.shutter_speed = int(value) else: setattr(self.camera, key, value) # Write setting to camera # StreamingCamera parameters (applied via StreamingCamera config) for key, value in config.items(): # For each provided setting - if key in self.config.keys(): - if key != 'picamera_settings': # We already handled this + if key != 'picamera_settings': # We already handled this + if key not in CONFIG_KEYS.keys(): logging.warn("{} is not in the streaming camera settings dictionary - adding it.") #continue #TODO: filter settings somehow? logging.debug("Setting parameter {}: {}".format(key, value))