Went on a PEP-8 rampage

This commit is contained in:
Joel Collins 2019-01-31 17:20:46 +00:00
parent f99ad30fb6
commit 0948c9308a
36 changed files with 186 additions and 218 deletions

View file

@ -3,21 +3,21 @@ import numpy as np
from openflexure_microscope.api.v1.views import MicroscopeViewPlugin
from openflexure_microscope.api.utilities import JsonPayload
from flask import request, Response, escape, jsonify
from flask import request, jsonify
import logging
class MeasureSharpnessAPI(MicroscopeViewPlugin):
def post(self):
payload = JsonPayload(request)
return jsonify({'sharpness': self.plugin.measure_sharpness()})
class AutofocusAPI(MicroscopeViewPlugin):
def post(self):
payload = JsonPayload(request)
# Figure out the range of z values to use
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)
print("Running autofocus...")
task = self.microscope.task.start(self.plugin.autofocus, dz)

View file

@ -1,21 +1,24 @@
import numpy as np
from scipy import ndimage
def decimate_to(shape, image):
"""Decimate an image to reduce its size if it's too big."""
decimation = np.max(np.ceil(np.array(image.shape, dtype=np.float)[:len(shape)]/np.array(shape)))
return image[::int(decimation), ::int(decimation), ...]
def sharpness_sum_lap2(rgb_image):
"""Return an image sharpness metric: sum(laplacian(image)**")"""
#image_bw=np.mean(decimate_to((1000,1000), rgb_image),2)
image_bw=np.mean(rgb_image, 2)
image_lap=ndimage.filters.laplace(image_bw)
# image_bw=np.mean(decimate_to((1000,1000), rgb_image),2)
image_bw = np.mean(rgb_image, 2)
image_lap = ndimage.filters.laplace(image_bw)
return np.mean(image_lap.astype(np.float)**4)
def sharpness_edge(image):
"""Return a sharpness metric optimised for vertical lines"""
gray = np.mean(image.astype(float), 2)
n = 20
edge = np.array([[-1]*n + [1]*n])
return np.sum([np.sum(ndimage.filters.convolve(gray,W)**2) for W in [edge, edge.T]])
return np.sum([np.sum(ndimage.filters.convolve(gray, W)**2) for W in [edge, edge.T]])

View file

@ -34,7 +34,7 @@ class AutofocusPlugin(MicroscopePlugin):
positions = []
camera.annotate_text = ""
for i in stage.scan_z(dz, return_to_start=False):
for _ in stage.scan_z(dz, return_to_start=False):
positions.append(stage.position[2])
time.sleep(settle)
sharpnesses.append(self.measure_sharpness(metric_fn))
@ -46,4 +46,4 @@ class AutofocusPlugin(MicroscopePlugin):
def measure_sharpness(self, metric_fn=sharpness_sum_lap2):
"""Measure the sharpness of the camera's current view."""
return metric_fn(self.microscope.camera.array(use_video_port=True, resize=(640,480)))
return metric_fn(self.microscope.camera.array(use_video_port=True, resize=(640, 480)))

View file

@ -1,28 +1,26 @@
import time
import numpy as np
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, Response, escape, jsonify
from flask import request, jsonify
import logging
from .recalibrate_utils import recalibrate_camera
class RecalibrateAPIView(MicroscopeViewPlugin):
def post(self):
payload = JsonPayload(request)
# Figure out the range of z values to use
# TODO: Figure out the range of z values to use
print("Starting microscope recalibration...")
task = self.microscope.task.start(self.plugin.recalibrate)
# return a handle on the autofocus task
# Return a handle on the autofocus task
return jsonify(task.state, 202)
class Plugin(MicroscopePlugin):
"""
A set of default plugins
@ -45,16 +43,14 @@ class Plugin(MicroscopePlugin):
streaming = scamera.state['stream_active']
if streaming:
logging.info("Stopping stream before recalibration")
scamera.stop_stream_recording(resolution=(640,480))
scamera.stop_stream_recording(resolution=(640, 480))
old_resolution = scamera.camera.resolution
try:
scamera.camera.resolution=(640,480)
scamera.camera.resolution = (640, 480)
recalibrate_camera(scamera.camera)
finally:
scamera.camera.resolution=old_resolution
scamera.camera.resolution = old_resolution
self.microscope.save_config()
if streaming:
logging.info("Restarting stream after recalibration")
scamera.start_stream_recording()

View file

@ -3,12 +3,14 @@ from picamera import PiCamera
from picamera.array import PiRGBArray, PiBayerArray
import time
def rgb_image(camera, resize=None, **kwargs):
"""Capture an image and return an RGB numpy array"""
with PiRGBArray(camera, size=resize) as output:
camera.capture(output, format='rgb', resize=resize, **kwargs)
return output.array
def flat_lens_shading_table(camera):
"""Return a flat (i.e. unity gain) lens shading table.
@ -20,6 +22,7 @@ def flat_lens_shading_table(camera):
raise ImportError("This program requires the forked picamera library with lens shading support")
return np.zeros(camera._lens_shading_table_shape(), dtype=np.uint8) + 32
def adjust_exposure_to_setpoint(camera, setpoint):
"""Adjust the camera's exposure time until the maximum pixel value is <setpoint>."""
print("Adjusting shutter speed to hit setpoint {}".format(setpoint), end="")
@ -29,11 +32,12 @@ def adjust_exposure_to_setpoint(camera, setpoint):
time.sleep(1)
print("done")
def auto_expose_and_freeze_settings(camera):
"""Freeze the settings after auto-exposing to white illumination"""
print("Allowing the camera to auto-expose")
camera.awb_mode="auto"
camera.exposure_mode="auto"
camera.awb_mode = "auto"
camera.exposure_mode = "auto"
for i in range(6):
print(".", end="")
time.sleep(0.5)
@ -54,18 +58,19 @@ def auto_expose_and_freeze_settings(camera):
def channels_from_bayer_array(bayer_array):
"""Given the 'array' from a PiBayerArray, return the 4 channels."""
bayer_pattern = [(i//2, i%2) for i in range(4)]
bayer_pattern = [(i//2, i % 2) for i in range(4)]
channels = np.zeros((4, bayer_array.shape[0]//2, bayer_array.shape[1]//2), dtype=bayer_array.dtype)
for i, offset in enumerate(bayer_pattern):
# We simplify life by dealing with only one channel at a time.
channels[i, :, :] = np.sum(bayer_array[offset[0]::2, offset[1]::2, :], axis=2)
channels[i, :, :] = np.sum(bayer_array[offset[0]::2, offset[1]::2, :], axis=2)
return channels
def lst_from_channels(channels):
"""Given the 4 Bayer colour channels from a white image, generate a LST."""
full_resolution = np.array(channels.shape[1:]) * 2 # channels have been binned
#lst_resolution = list(np.ceil(full_resolution / 64.0).astype(int))
full_resolution = np.array(channels.shape[1:]) * 2 # channels have been binned
# lst_resolution = list(np.ceil(full_resolution / 64.0).astype(int))
lst_resolution = [(r // 64) + 1 for r in full_resolution]
# NB the size of the LST is 1/64th of the image, but rounded UP.
print("Generating a lens shading table at {}x{}".format(*lst_resolution))
@ -73,7 +78,7 @@ def lst_from_channels(channels):
for i in range(lens_shading.shape[0]):
image_channel = channels[i, :, :]
iw, ih = image_channel.shape
ls_channel = lens_shading[i,:,:]
ls_channel = lens_shading[i, :, :]
lw, lh = ls_channel.shape
# The lens shading table is rounded **up** in size to 1/64th of the size of
# the image. Rather than handle edge images separately, I'm just going to
@ -84,19 +89,23 @@ def lst_from_channels(channels):
# less computationally efficient!
padded_image_channel = np.pad(image_channel,
[(0, lw*32 - iw), (0, lh*32 - ih)],
mode="edge") # Pad image to the right and bottom
print("Channel shape: {}x{}, shading table shape: {}x{}, after padding {}".format(iw,ih,lw*32,lh*32,padded_image_channel.shape))
mode="edge") # Pad image to the right and bottom
print("Channel shape: {}x{}, shading table shape: {}x{}, after padding {}".format(iw,
ih,
lw*32,
lh*32,
padded_image_channel.shape))
# Next, fill the shading table (except edge pixels). Please excuse the
# for loop - I know it's not fast but this code needn't be!
box = 3 # We average together a square of this side length for each pixel.
box = 3 # We average together a square of this side length for each pixel.
# NB this isn't quite what 6by9's program does - it averages 3 pixels
# horizontally, but not vertically.
for dx in np.arange(box) - box//2:
for dy in np.arange(box) - box//2:
ls_channel[:,:] += padded_image_channel[16+dx::32,16+dy::32] - 64
ls_channel[:, :] += padded_image_channel[16+dx::32, 16+dy::32] - 64
ls_channel /= box**2
# The original C code written by 6by9 normalises to the central 64 pixels in each channel.
#ls_channel /= np.mean(image_channel[iw//2-4:iw//2+4, ih//2-4:ih//2+4])
# ls_channel /= np.mean(image_channel[iw//2-4:iw//2+4, ih//2-4:ih//2+4])
# I have had better results just normalising to the maximum:
ls_channel /= np.max(ls_channel)
# NB the central pixel should now be *approximately* 1.0 (may not be exactly
@ -107,11 +116,12 @@ def lst_from_channels(channels):
# What we actually want to calculate is the gains needed to compensate for the
# lens shading - that's 1/lens_shading_table_float as we currently have it.
gains = 32.0/lens_shading # 32 is unity gain
gains[gains > 255] = 255 # clip at 255, maximum gain is 255/32
gains[gains < 32] = 32 # clip at 32, minimum gain is 1 (is this necessary?)
gains = 32.0/lens_shading # 32 is unity gain
gains[gains > 255] = 255 # clip at 255, maximum gain is 255/32
gains[gains < 32] = 32 # clip at 32, minimum gain is 1 (is this necessary?)
lens_shading_table = gains.astype(np.uint8)
return lens_shading_table[::-1,:,:].copy()
return lens_shading_table[::-1, :, :].copy()
def recalibrate_camera(camera):
"""Reset the lens shading table and exposure settings.
@ -124,7 +134,7 @@ def recalibrate_camera(camera):
``StreamingCamera``.
"""
camera.lens_shading_table = flat_lens_shading_table(camera)
discarded = rgb_image(camera) # for some reason the camera won't work unless I do this!
_ = rgb_image(camera) # for some reason the camera won't work unless I do this!
with PiBayerArray(camera) as a:
camera.capture(a, format="jpeg", bayer=True)
@ -132,18 +142,19 @@ def recalibrate_camera(camera):
# Now we need to calculate a lens shading table that would make this flat.
# raw_image is a 3D array, with full resolution and 3 colour channels. No
# demosaicing has been done, so 2/3 of the values are zero (3/4 for R and B
# channels, 1/2 for green because there's twize as many green pixels).
# de-mosaicing has been done, so 2/3 of the values are zero (3/4 for R and B
# channels, 1/2 for green because there's twice as many green pixels).
channels = channels_from_bayer_array(raw_image)
lens_shading_table = lst_from_channels(channels)
camera.lens_shading_table=lens_shading_table
test = rgb_image(camera)
camera.lens_shading_table = lens_shading_table
_ = rgb_image(camera)
# Fix the AWB gains so the image is neutral
channel_means = np.mean(np.mean(rgb_image(camera), axis=0, dtype=np.float), axis=0)
old_gains = camera.awb_gains
camera.awb_gains = (channel_means[1]/channel_means[0] * old_gains[0], channel_means[1]/channel_means[2]*old_gains[1])
camera.awb_gains = (channel_means[1]/channel_means[0] * old_gains[0],
channel_means[1]/channel_means[2]*old_gains[1])
time.sleep(1)
# Ensure the background is bright but not saturated
adjust_exposure_to_setpoint(camera, 230)
@ -157,4 +168,3 @@ if __name__ == "__main__":
recalibrate_camera(camera)
print("Done.")
time.sleep(2)

View file

@ -4,8 +4,6 @@ from openflexure_microscope.exceptions import TaskDeniedException
from flask import request, Response, escape, jsonify, abort
import logging
class IdentifyAPI(MicroscopeViewPlugin):
"""
@ -53,6 +51,7 @@ class HelloWorldAPI(MicroscopeViewPlugin):
return Response(self.microscope.plugin_string)
class LongRunningAPI(MicroscopeViewPlugin):
"""
An example API plugin that uses a long-running plugin method.
@ -75,6 +74,7 @@ class LongRunningAPI(MicroscopeViewPlugin):
except TaskDeniedException:
return abort(409)
class SomeExceptionAPI(MicroscopeViewPlugin):
"""
An example API plugin that uses a long-running but broken plugin method.
@ -92,4 +92,4 @@ class SomeExceptionAPI(MicroscopeViewPlugin):
return jsonify(task.state), 202
except TaskDeniedException:
return abort(409)
return abort(409)

View file

@ -58,4 +58,4 @@ class Plugin(MicroscopePlugin):
print("Long-running task finished! Releasing locks.")
return n_array
return n_array

View file

@ -4,7 +4,7 @@ import inspect
import logging
class bcolors:
class ConColors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
@ -23,7 +23,7 @@ def module_from_file(plugin_path):
# Check if the path is to a file
if not os.path.isfile(plugin_path):
logging.warning(bcolors.FAIL + "No valid plugin found at {}.".format(plugin_path) + bcolors.ENDC)
logging.warning(ConColors.FAIL + "No valid plugin found at {}.".format(plugin_path) + ConColors.ENDC)
return None, None, None
else:
@ -63,6 +63,7 @@ def check_module(module_path):
# If all checks pass, return True
return True
def name_from_module(plugin_path):
path_array = plugin_path.split('.')
@ -100,7 +101,7 @@ def load_plugin_class(plugin_path, plugin_class_name):
try:
plugin_class = getattr(plugin_module, plugin_class_name)
except AttributeError:
logging.warning(bcolors.FAIL + "Class {} does not exist in plugin {}. Skipping.".format(plugin_class_name, plugin_path) + bcolors.ENDC)
logging.warning(ConColors.FAIL + "Class {} does not exist in plugin {}. Skipping.".format(plugin_class_name, plugin_path) + ConColors.ENDC)
return None, None
else:
return plugin_class, plugin_name
@ -112,7 +113,7 @@ def class_from_map(plugin_map):
plugin_arr = plugin_map.split(':')
if not len(plugin_arr) == 2:
logging.warning(bcolors.WARNING + "Malformed plugin map {}. Skipping.".format(plugin_map) + bcolors.ENDC)
logging.warning(ConColors.WARNING + "Malformed plugin map {}. Skipping.".format(plugin_map) + ConColors.ENDC)
return None, None
else:
return load_plugin_class(*plugin_arr)
@ -155,7 +156,7 @@ class PluginMount(object):
plugin_object = plugin_class()
if hasattr(self, plugin_name): # If a plugin with the same name is already attached.
logging.warning(bcolors.WARNING + "A plugin named {} has already been loaded. Skipping {}.".format(plugin_name, plugin_map) + bcolors.ENDC)
logging.warning(ConColors.WARNING + "A plugin named {} has already been loaded. Skipping {}.".format(plugin_name, plugin_map) + ConColors.ENDC)
elif isinstance(plugin_object, MicroscopePlugin): # If plugin_object is an instance of MicroscopePlugin
# Attach plugin_object to the plugin mount
@ -165,10 +166,10 @@ class PluginMount(object):
# Grant plugin access to the hardware
plugin_object.microscope = self.parent
logging.info(bcolors.OKGREEN + "Plugin {} loaded as {}.".format(plugin_map, plugin_name) + bcolors.ENDC)
logging.info(ConColors.OKGREEN + "Plugin {} loaded as {}.".format(plugin_map, plugin_name) + ConColors.ENDC)
class MicroscopePlugin():
class MicroscopePlugin:
"""
Parent class for all microscope plugins.

View file

@ -10,4 +10,4 @@ class Plugin(MicroscopePlugin):
"""
Tests for access to Microscope.camera, and Microscope.stage
"""
return (self.microscope.camera, self.microscope.stage)
return self.microscope.camera, self.microscope.stage