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

@ -5,4 +5,4 @@ from .microscope import Microscope
from . import config
from . import utilities
from . import task
from . import lock
from . import lock

View file

@ -1,31 +1,16 @@
#!/usr/bin/env python
"""
TODO: Implement API route to cleanly shut down server
TODO: Implement plugin API routes somehow
"""
import numpy as np
from importlib import import_module
import time
import datetime
import os
from flask import (
Flask, render_template, Response, url_for,
redirect, request, jsonify, send_file, abort,
make_response)
from flask.views import MethodView
from werkzeug.exceptions import default_exceptions
from serial import SerialException
Flask, render_template)
from flask_cors import CORS
from openflexure_microscope.api.utilities import list_routes
from openflexure_microscope.api.exceptions import JSONExceptionHandler
from openflexure_microscope import Microscope
from openflexure_microscope.exceptions import LockError
from openflexure_microscope.camera.pi import StreamingCamera
from openflexure_microscope.stage.openflexure import Stage

View file

@ -2,6 +2,7 @@ from flask import jsonify
from werkzeug.exceptions import default_exceptions
from werkzeug.exceptions import HTTPException
class JSONExceptionHandler(object):
def __init__(self, app=None):
@ -24,7 +25,6 @@ class JSONExceptionHandler(object):
}
return jsonify(response)
def init_app(self, app):
self.app = app
self.register(HTTPException)
@ -32,4 +32,4 @@ class JSONExceptionHandler(object):
self.register(code)
def register(self, exception_or_code, handler=None):
self.app.errorhandler(exception_or_code)(handler or self.std_handler)
self.app.errorhandler(exception_or_code)(handler or self.std_handler)

View file

@ -1,8 +1,8 @@
import pprint
import logging
import copy
from werkzeug.exceptions import BadRequest
class JsonPayload:
def __init__(self, request):
"""
@ -60,10 +60,9 @@ def gen(camera):
def get_bool(get_arg):
"""Convert GET request argument string to a Python bool"""
if (
get_arg == 'true' or
get_arg == 'True' or
get_arg == '1'):
if (get_arg == 'true' or
get_arg == 'True' or
get_arg == '1'):
return True
else:
return False

View file

@ -197,4 +197,4 @@ def construct_blueprint(microscope_obj):
view_func=ConfigAPI.as_view('config', microscope=microscope_obj)
)
return(blueprint)
return blueprint

View file

@ -2,9 +2,7 @@ from openflexure_microscope.api.utilities import gen, get_bool, JsonPayload
from openflexure_microscope.api.v1.views import MicroscopeView
from openflexure_microscope.utilities import filter_dict
from flask import Response, Blueprint, jsonify, request, abort, url_for, redirect, send_file
import logging
from flask import Response, jsonify, request, abort, url_for, redirect, send_file
class ListAPI(MicroscopeView):
@ -368,7 +366,7 @@ class MetadataAPI(MicroscopeView):
# If no filename is specified, redirect to the capture's currently set filename
if not filename:
return redirect(url_for('capture_download', capture_id=capture_id, filename=capture_obj.metadataname, as_attachment=as_attachment), code=307)
return redirect(url_for('capture_download', capture_id=capture_id, filename=capture_obj.metadataname), code=307)
# Download the metadata using the requested filename
data = capture_obj.yaml
@ -377,6 +375,7 @@ class MetadataAPI(MicroscopeView):
data,
mimetype="text/yaml")
class TagsAPI(MicroscopeView):
def get(self, capture_id):
"""
@ -402,7 +401,7 @@ class TagsAPI(MicroscopeView):
if not capture_obj or not capture_obj.state['available']:
return abort(404) # 404 Not Found
metadata_tags = filter_dict(capture_obj.state, ('metadata', 'tags'))
metadata_tags = filter_dict(capture_obj.state, ['metadata', 'tags'])
return jsonify(metadata_tags)
@ -440,7 +439,7 @@ class TagsAPI(MicroscopeView):
for tag in data_dict:
capture_obj.put_tag(str(tag))
metadata_tags = filter_dict(capture_obj.state, ('metadata', 'tags'))
metadata_tags = filter_dict(capture_obj.state, ['metadata', 'tags'])
return jsonify(metadata_tags)
@ -477,6 +476,6 @@ class TagsAPI(MicroscopeView):
for tag in data_dict:
capture_obj.delete_tag(str(tag))
metadata_tags = filter_dict(capture_obj.state, ('metadata', 'tags'))
metadata_tags = filter_dict(capture_obj.state, ['metadata', 'tags'])
return jsonify(metadata_tags)
return jsonify(metadata_tags)

View file

@ -1,9 +1,7 @@
from openflexure_microscope.api.v1.views import MicroscopeView
from openflexure_microscope.api.utilities import JsonPayload
from flask import Response, Blueprint, jsonify, request, abort, url_for, redirect, send_file
import logging
from flask import jsonify, request
class ZoomAPI(MicroscopeView):
@ -102,4 +100,4 @@ class OverlayAPI(MicroscopeView):
self.microscope.camera.camera.annotate_text = text
self.microscope.camera.camera.annotate_text_size = size
return jsonify({'text': text, 'size': size})
return jsonify({'text': text, 'size': size})

View file

@ -1,8 +1,6 @@
from openflexure_microscope.api.v1.views import MicroscopeView
from flask import Response, Blueprint, jsonify, request, abort, url_for, redirect, send_file
import logging
from flask import jsonify
class GPUPreviewAPI(MicroscopeView):

View file

@ -1,11 +1,12 @@
from openflexure_microscope.api.v1.views import MicroscopeViewPlugin
from flask import Response, Blueprint, jsonify
from flask import Blueprint
import logging, warnings
import logging
import warnings
def construct_blueprint(microscope_obj, plugin_paths=[], include_default=True):
def construct_blueprint(microscope_obj):
blueprint = Blueprint('plugin_blueprint', __name__)
@ -56,4 +57,4 @@ def construct_blueprint(microscope_obj, plugin_paths=[], include_default=True):
"No valid 'api_views' dictionary found in {}".format(plugin_obj)
)
return(blueprint)
return blueprint

View file

@ -2,7 +2,7 @@ from openflexure_microscope.api.utilities import gen, JsonPayload
from openflexure_microscope.api.v1.views import MicroscopeView
from openflexure_microscope.utilities import axes_to_array, filter_dict
from flask import Response, Blueprint, jsonify, request
from flask import Blueprint, jsonify, request
import logging
@ -42,7 +42,7 @@ class PositionAPI(MicroscopeView):
}
"""
out = filter_dict(self.microscope.state, ('stage', 'position'))
out = filter_dict(self.microscope.state, ['stage', 'position'])
return jsonify(out)
def post(self):
@ -85,7 +85,7 @@ class PositionAPI(MicroscopeView):
with self.microscope.stage.lock:
self.microscope.stage.move_rel(position)
out = filter_dict(self.microscope.state, ('stage', 'position'))
out = filter_dict(self.microscope.state, ['stage', 'position'])
return jsonify(out)
@ -99,4 +99,4 @@ def construct_blueprint(microscope_obj):
view_func=PositionAPI.as_view('position', microscope=microscope_obj)
)
return(blueprint)
return blueprint

View file

@ -1,6 +1,7 @@
from openflexure_microscope.api.v1.views import MicroscopeView
from flask import jsonify, abort, Blueprint
class TaskListAPI(MicroscopeView):
def get(self):
@ -65,6 +66,7 @@ class TaskListAPI(MicroscopeView):
return jsonify(data)
class TaskAPI(MicroscopeView):
def get(self, task_id):
@ -135,6 +137,7 @@ class TaskAPI(MicroscopeView):
return jsonify(data)
def construct_blueprint(microscope_obj):
blueprint = Blueprint('task_blueprint', __name__)
@ -149,4 +152,4 @@ def construct_blueprint(microscope_obj):
view_func=TaskAPI.as_view('task', microscope=microscope_obj)
)
return(blueprint)
return blueprint

View file

@ -23,4 +23,4 @@ class MicroscopeViewPlugin(MicroscopeView):
self.plugin = plugin
MicroscopeView.__init__(self, microscope=microscope, **kwargs)
MicroscopeView.__init__(self, microscope=microscope, **kwargs)

View file

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
import time
import io
import os
import threading
from PIL import Image
import datetime
import yaml
import logging
@ -35,6 +33,18 @@ def generate_basename():
return datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
def generate_numbered_basename(obj_list: list) -> str:
initial_basename = generate_basename()
basename = initial_basename
# Handle clashing
iterator = 1
while basename in [obj.basename for obj in obj_list]:
basename = initial_basename + "_{}".format(iterator)
iterator += 1
return basename
class CameraEvent(object):
"""
A frame-signaller object used by any instances or subclasses of BaseCamera.
@ -79,18 +89,6 @@ class CameraEvent(object):
self.events[get_ident()][0].clear()
def generate_basename(obj_list: list) -> str:
initial_basename = generate_basename()
basename = initial_basename
# Handle clashing
iterator = 1
while basename in [obj.basename for obj in obj_list]:
basename = initial_basename + "_{}".format(iterator)
iterator += 1
return basename
class BaseCamera(object):
"""
Base implementation of StreamingCamera.
@ -155,7 +153,7 @@ class BaseCamera(object):
# START AND STOP WORKER THREAD
def start_worker(self, timeout: int=5) -> bool:
def start_worker(self, timeout: int = 5) -> bool:
"""Start the background camera thread if it isn't running yet."""
timeout_time = time.time() + timeout
@ -176,7 +174,7 @@ class BaseCamera(object):
time.sleep(0)
return True
def stop_worker(self, timeout: int=5) -> bool:
def stop_worker(self, timeout: int = 5) -> bool:
"""Flag worker thread for stop. Waits for thread close or timeout."""
logging.debug("Stopping worker thread")
timeout_time = time.time() + timeout
@ -217,13 +215,13 @@ class BaseCamera(object):
"""Return the latest recorded video."""
return last_entry(self.videos)
def image_from_id(self, id):
def image_from_id(self, image_id):
"""Return an image StreamObject with a matching ID."""
return entry_by_id(id, self.images)
return entry_by_id(image_id, self.images)
def video_from_id(self, id):
def video_from_id(self, video_id):
"""Return a video StreamObject with a matching ID."""
return entry_by_id(id, self.videos)
return entry_by_id(video_id, self.videos)
# MANAGE CAPTURE DATABASE
@ -277,25 +275,24 @@ class BaseCamera(object):
def new_image(
self,
write_to_file: bool=False,
temporary: bool=True,
filename: str=None,
fmt: str='jpeg',
shunt_others: bool=True):
write_to_file: bool = False,
temporary: bool = True,
filename: str = None,
fmt: str = 'jpeg'):
"""
Create a new image capture object. Adds to the image list, and shunt all others.
Args:
write_to_file (bool): Should the StreamObject write to a file, or an in-memory byte stream.
keep_on_disk (bool): Should the data be deleted after session ends. Creating the capture with a content manager sets this to true.
temporary (bool): Should the data be deleted after session ends. Creating the capture with a content manager sets this to true.
filename (str): Name of the stored file. Defaults to timestamp.
fmt (str): Format of the capture.
"""
# Generate file name
if not filename:
filename = generate_basename(self.images)
filename = generate_numbered_basename(self.images)
logging.debug(filename)
# Create capture object
@ -317,11 +314,10 @@ class BaseCamera(object):
def new_video(
self,
write_to_file: bool=True,
temporary: bool=False,
filename: str=None,
fmt: str='h264',
shunt_others: bool=True):
write_to_file: bool = True,
temporary: bool = False,
filename: str = None,
fmt: str = 'h264'):
"""
Create a new video capture object. Adds to the image list, and shunt all others.
@ -335,7 +331,7 @@ class BaseCamera(object):
# Generate file name
if not filename:
filename = generate_basename(self.videos)
filename = generate_numbered_basename(self.videos)
logging.debug(filename)
# Create capture object

View file

@ -15,6 +15,7 @@ thumbnail_size = (60, 60)
BASE_CAPTURE_PATH = os.path.join(os.path.expanduser('~'), 'micrographs')
TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, 'tmp')
def clear_tmp():
global TEMP_CAPTURE_PATH
@ -24,6 +25,7 @@ def clear_tmp():
os.remove(f)
logging.debug("Removed {}".format(f))
def capture_from_dict(capture_dict):
capture = CaptureObject(create_metadata_file=False) # Create a placeholder capture
@ -48,14 +50,15 @@ class CaptureObject(object):
Note: Captures cannot be stored in a lower-level directory than BASE_CAPTURE_PATH.
"""
def __init__(
self,
write_to_file: bool=False,
temporary: bool=False,
filename: str='',
folder: str='',
fmt: str='',
create_metadata_file: bool=True) -> None:
write_to_file: bool = False,
temporary: bool = False,
filename: str = '',
folder: str = '',
fmt: str = '',
create_metadata_file: bool = True) -> None:
"""Create a new StreamObject, to manage capture data."""
# Store a nice ID
@ -102,7 +105,9 @@ class CaptureObject(object):
def __enter__(self):
"""Create StreamObject in context, to auto-clean disk data."""
logging.debug("Entering context for {}. Stored files will be cleaned up automatically regardless of location.".format(self.id))
logging.debug(
"Entering context for {}. Stored files will be cleaned up automatically regardless of location.".format(
self.id))
self.temporary = True # Flag file to be removed on close.
self.context_manager = True # Used in metadata
@ -129,7 +134,7 @@ class CaptureObject(object):
else:
logging.debug("Target for {} set to 'file'".format(self.id))
self.stream = self.file
# Save initial metadata file
if create_metadata_file:
self.save_metadata()
@ -170,7 +175,8 @@ class CaptureObject(object):
def split_file_path(self, filepath):
"""Takes a full file path, and splits it into separated class properties."""
self.filefolder, self.filename = os.path.split(filepath) # Split the full file path into a folder and a filename
self.filefolder, self.filename = os.path.split(
filepath) # Split the full file path into a folder and a filename
self.basename = os.path.splitext(self.filename)[0] # Split the filename out from it's file extension
self.metadataname = "{}.yaml".format(self.basename)
@ -234,18 +240,10 @@ class CaptureObject(object):
@property
def metadata(self) -> dict:
# Create basic metadata dictionary
d = {
'id': self.id,
'filename': self.filename,
'path': self.file,
'time': self.timestring,
'format': self.format,
'tags': self.tags
}
d = {'id': self.id, 'filename': self.filename, 'path': self.file, 'time': self.timestring,
'format': self.format, 'tags': self.tags, 'custom': self._metadata}
# Add custom metadata to dictionary
d['custom'] = self._metadata
return d
@property
@ -261,14 +259,10 @@ class CaptureObject(object):
"""Return dictionary of StreamObject properties."""
# Create basic state dictionary
d = {
'locked': self.locked,
'temporary': self.temporary,
}
d = {'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata,
'metadata_path': self.metadata_file}
# Add metadata to state
d['metadata'] = self.metadata
d['metadata_path'] = self.metadata_file
# Check bytestream
if self.stream_exists:
@ -404,4 +398,5 @@ class CaptureObject(object):
if self.temporary:
self.delete()
atexit.register(clear_tmp)
atexit.register(clear_tmp)

View file

@ -36,7 +36,6 @@ def to_map(data, func):
Args:
data: Input iterable data
func: Function to apply to all non-iterable values
excluded_keys: Any dictionary keys to exclude from the returned data
"""
# If the object is a dictionary
if isinstance(data, abc.Mapping):
@ -58,7 +57,6 @@ def json_map(data, clean_keys=True):
Args:
data: Input dictionary
clean_keys: Modify any keys unsuitable for JSON return
"""
# Do not overwrite original data dictionary

View file

@ -1,8 +1,10 @@
from threading import ThreadError
class TaskDeniedException(Exception):
pass
class LockError(ThreadError):
ERROR_CODES = {
'ACQUIRE_ERROR': "Unable to acquire. Lock in use by another thread.",
@ -22,4 +24,4 @@ class LockError(ThreadError):
ThreadError.__init__(self)
def __str__(self):
return self.string
return self.string

View file

@ -53,4 +53,4 @@ class CompositeLock(object):
def release(self):
for lock in self.locks:
lock.release()
lock.release()

View file

@ -3,7 +3,6 @@
Defines a microscope object, binding a camera and stage with basic functionality.
"""
import logging
import os
import numpy as np
import uuid

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

View file

@ -5,6 +5,7 @@ from openflexure_microscope.lock import StrictLock
import logging
# TODO: Implement lock on movement
class Stage(OpenFlexureStage):
def __init__(self, *args, **kwargs):
@ -16,7 +17,7 @@ class Stage(OpenFlexureStage):
try:
OpenFlexureStage.__init__(self, *args, **kwargs)
except SerialException as e:
except SerialException:
logging.error("No stage found. Aborting stage.")
logging.warning("Stage lock can be acquired, but any stage methods will fail and raise exceptions.")
@ -25,4 +26,4 @@ class Stage(OpenFlexureStage):
Overrides :py:function:`openflexure_stage.stage.OpenFlexureStage._move_rel_nobacklash` to acquire lock first.
"""
with self.lock:
OpenFlexureStage._move_rel_nobacklash(self, *args, **kwargs)
OpenFlexureStage._move_rel_nobacklash(self, *args, **kwargs)

View file

@ -1,14 +1,13 @@
from threading import Thread
from functools import wraps
import datetime
import logging
import traceback
import time
import uuid
from openflexure_microscope.exceptions import TaskDeniedException
from openflexure_microscope.utilities import entry_by_id
class TaskOrchestrator:
"""
Class responsible for spawning threaded tasks, and storing their returns.
@ -76,7 +75,7 @@ class TaskOrchestrator:
Method returning bool describing if a particular task is allowed to run.
Currently always allows tasks to start, as locks determine access to hardware.
"""
#return not any([task._running for task in self.tasks])
# return not any([task._running for task in self.tasks])
return True
@ -139,4 +138,4 @@ class Task:
self._running = True # Set start command
thread = Thread(target=self.run) # Define thread
thread.daemon = True # Stop this thread when main thread closes
thread.start() # Start thread
thread.start() # Start thread

View file

@ -1,9 +1,9 @@
import copy
import operator
from fractions import Fraction
from functools import reduce
from contextlib import contextmanager
@contextmanager
def set_properties(obj, **kwargs):
"""A context manager to set, then reset, certain properties of an object.
@ -25,7 +25,8 @@ def set_properties(obj, **kwargs):
for k, v in saved_properties.items():
setattr(obj, k, v)
def axes_to_array(coordinate_dictionary, axis_keys=['x', 'y', 'z'], base_array=None):
def axes_to_array(coordinate_dictionary, axis_keys=('x', 'y', 'z'), base_array=None):
"""Takes key-value pairs of a JSON value, and maps onto an array"""
# If no base array is given
if not base_array:
@ -42,19 +43,21 @@ def axes_to_array(coordinate_dictionary, axis_keys=['x', 'y', 'z'], base_array=N
return base_array
def filter_dict(dictionary: dict, keys: list):
# Get value by recursively applying getitem
val = reduce(operator.getitem, keys, dictionary)
# Create new dictionary by running reduce on key, val pairs
out = reduce(lambda x, y: {y: x}, reversed(keys), val)
return out
def entry_by_id(id: str, object_list: list):
def filter_dict(dictionary: dict, keys: list):
# Get value by recursively applying getitem
val = reduce(operator.getitem, keys, dictionary)
# Create new dictionary by running reduce on key, val pairs
out = reduce(lambda x, y: {y: x}, reversed(keys), val)
return out
def entry_by_id(entry_id: str, object_list: list):
"""Return an object from a list, if <object>.id matches id argument."""
found = None
for o in object_list:
if o.id == id:
if o.id == entry_id:
found = o
return found

View file

@ -3,6 +3,7 @@ from PIL import Image
from io import BytesIO
import numpy as np
class APIconnection:
def __init__(self, host="localhost", port=5000, api_ver="v1"):
self.base = self.build_base(host=host, port=port, api_ver=api_ver)
@ -100,4 +101,4 @@ class APIconnection:
return self.post('/camera/zoom', json=json)
def get_zoom(self):
return self.get('/camera/zoom')
return self.get('/camera/zoom')

View file

@ -1,19 +1,11 @@
#!/usr/bin/env python
from api_client import APIconnection
import os
import io
import sys
import time
import numpy as np
import uuid
from PIL import Image
import unittest
from pprint import pprint
import logging, sys
import logging
import sys
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
@ -66,6 +58,7 @@ class TestCapture(unittest.TestCase):
self.assertTrue(capture_array.shape == (resolution[1], resolution[0], 3))
class TestStage(unittest.TestCase):
def test_stage_config(self):
@ -114,6 +107,7 @@ class TestStage(unittest.TestCase):
self.assertTrue(np.array_equal(diff, move))
if __name__ == '__main__':
suites = [

View file

@ -3,17 +3,15 @@ from openflexure_microscope.camera.pi import StreamingCamera, CaptureObject
import os
import io
import sys
import time
import numpy as np
import uuid
from PIL import Image
import unittest
from pprint import pprint
import logging, sys
import logging
import sys
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

View file

@ -46,4 +46,3 @@ if __name__ == '__main__':
alltests = unittest.TestSuite(suites)
result = unittest.TextTestRunner(verbosity=2).run(alltests)

View file

@ -1,21 +1,13 @@
#!/usr/bin/env python
from openflexure_microscope.camera.pi import StreamingCamera
from openflexure_stage import OpenFlexureStage
from openflexure_microscope import Microscope
import os
import io
import sys
import time
import numpy as np
import uuid
from PIL import Image
import unittest
from pprint import pprint
import logging, sys
import logging
import sys
logging.basicConfig(stream=sys.stderr, level=logging.INFO)

View file

@ -4,9 +4,7 @@ from openflexure_microscope.camera.pi import StreamingCamera
from openflexure_microscope import config
import numpy as np
import sys
import time
import matplotlib.pyplot as plt
import os
import logging, sys
@ -164,4 +162,4 @@ def generate_lens_shading_table_closed_loop(output_fname="microscopelst.npy",
if __name__ == '__main__':
generate_lens_shading_table_closed_loop()
generate_lens_shading_table_closed_loop()