From 994e83dbeb31b0e344771f6f6a08c1da2e8555ac Mon Sep 17 00:00:00 2001 From: Joel Collins <2579603-jtc42@users.noreply.gitlab.com> Date: Wed, 14 Oct 2020 14:56:29 +0000 Subject: [PATCH] Code cleanup --- .gitlab-ci.yml | 40 +++++++++++++++++ openflexure_microscope/api/app.py | 23 +++++----- .../api/default_extensions/__init__.py | 6 ++- .../api/default_extensions/autofocus.py | 26 ++++------- .../api/default_extensions/autostorage.py | 20 +++------ .../picamera_autocalibrate/extension.py | 30 ++++--------- .../recalibrate_utils.py | 18 ++++---- .../api/default_extensions/scan.py | 44 ++++++++++++------- .../api/default_extensions/zip_builder.py | 8 ++-- .../api/dev_extensions/tools.py | 2 +- .../api/utilities/__init__.py | 1 - openflexure_microscope/api/utilities/gui.py | 15 +++---- .../api/v2/views/actions/__init__.py | 3 -- .../api/v2/views/actions/camera.py | 6 +-- .../api/v2/views/actions/stage.py | 6 +-- .../api/v2/views/actions/system.py | 5 +-- .../api/v2/views/captures.py | 39 ++++++++-------- openflexure_microscope/api/v2/views/stage.py | 4 +- .../api/v2/views/streams.py | 5 +-- openflexure_microscope/camera/base.py | 7 +-- openflexure_microscope/camera/mock.py | 36 ++++----------- openflexure_microscope/camera/pi.py | 12 ++--- .../camera/piexif/_common.py | 2 +- openflexure_microscope/camera/piexif/_dump.py | 28 ++++++------ .../camera/piexif/_insert.py | 2 +- openflexure_microscope/camera/piexif/_load.py | 6 ++- .../camera/piexif/_remove.py | 4 +- .../camera/piexif/_transplant.py | 2 +- openflexure_microscope/camera/piexif/_webp.py | 2 - .../camera/piexif/helper.py | 4 +- .../camera/set_picamera_gain.py | 4 +- openflexure_microscope/captures/capture.py | 36 ++++++++------- .../captures/capture_manager.py | 5 ++- openflexure_microscope/config.py | 7 ++- openflexure_microscope/exceptions.py | 23 ---------- openflexure_microscope/microscope.py | 23 +++++----- openflexure_microscope/paths.py | 1 - .../rescue/check_capture_reload.py | 4 +- .../rescue/check_settings.py | 8 ++-- .../rescue/monitor_service.py | 1 - .../rescue/monitor_timeout.py | 4 +- openflexure_microscope/stage/base.py | 14 +----- openflexure_microscope/stage/mock.py | 18 +++----- openflexure_microscope/stage/sanga.py | 16 ++++--- openflexure_microscope/utilities.py | 5 +-- pyproject.toml | 4 ++ 46 files changed, 261 insertions(+), 318 deletions(-) delete mode 100644 openflexure_microscope/exceptions.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 044c1094..abf85207 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,5 @@ stages: + - analysis - build - deploy @@ -9,6 +10,45 @@ cache: paths: - node_modules/ +pylint: + stage: analysis + image: python:3.7 + allow_failure: true + + before_script: + - curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python + + script: + # Install server + - $HOME/.poetry/bin/poetry install + # Run static build script + - $HOME/.poetry/bin/poetry run pylint ./openflexure_microscope/ + + only: + - branches + - merge_requests + - tags + - web + +black: + stage: analysis + image: python:3.7 + allow_failure: true + + before_script: + - curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python + + script: + # Install server + - $HOME/.poetry/bin/poetry install + # Run static build script + - $HOME/.poetry/bin/poetry run black --check . + + only: + - branches + - merge_requests + - tags + - web # Electron app build build: diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index a4a049a8..71c6bc94 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -21,7 +21,7 @@ import os from datetime import datetime import pkg_resources -from flask import Flask, abort, send_file +from flask import abort, send_file from flask_cors import CORS, cross_origin from labthings import create_app from labthings.extensions import find_extensions @@ -34,7 +34,6 @@ from openflexure_microscope.paths import ( OPENFLEXURE_EXTENSIONS_PATH, OPENFLEXURE_VAR_PATH, logs_file_path, - settings_file_path, ) # Handle logging @@ -72,7 +71,7 @@ root_log.addHandler(fh) access_log.addHandler(afh) # Log server paths being used -logging.info(f"Running with data path {OPENFLEXURE_VAR_PATH}") +logging.info("Running with data path %s", OPENFLEXURE_VAR_PATH) logging.info("Creating app") # Create flask app @@ -103,16 +102,16 @@ for extension in find_extensions(OPENFLEXURE_EXTENSIONS_PATH): labthing.register_extension(extension) # Attach captures resources -labthing.add_view(views.CaptureList, f"/captures") +labthing.add_view(views.CaptureList, "/captures") labthing.add_root_link(views.CaptureList, "captures") -labthing.add_view(views.CaptureView, f"/captures/") -labthing.add_view(views.CaptureDownload, f"/captures//download/") -labthing.add_view(views.CaptureTags, f"/captures//tags") -labthing.add_view(views.CaptureAnnotations, f"/captures//annotations") +labthing.add_view(views.CaptureView, "/captures/") +labthing.add_view(views.CaptureDownload, "/captures//download/") +labthing.add_view(views.CaptureTags, "/captures//tags") +labthing.add_view(views.CaptureAnnotations, "/captures//annotations") # Attach settings and state resources -labthing.add_view(views.SettingsProperty, f"/instrument/settings") +labthing.add_view(views.SettingsProperty, "/instrument/settings") labthing.add_root_link(views.SettingsProperty, "instrumentSettings") labthing.add_view(views.NestedSettingsProperty, "/instrument/settings/") labthing.add_view(views.StateProperty, "/instrument/state") @@ -129,8 +128,8 @@ labthing.add_view(views.StageTypeProperty, "/instrument/stage/type") # Attach streams resources -labthing.add_view(views.MjpegStream, f"/streams/mjpeg") -labthing.add_view(views.SnapshotStream, f"/streams/snapshot") +labthing.add_view(views.MjpegStream, "/streams/mjpeg") +labthing.add_view(views.SnapshotStream, "/streams/snapshot") # Attach microscope action resources labthing.add_view(views.actions.ActionsView, "/actions") @@ -170,7 +169,7 @@ def err_log(): @app.route("/api/v1/", defaults={"path": ""}) @app.route("/api/v1/") -def api_v1_catch_all(path): +def api_v1_catch_all(path): # pylint: disable=W0613 abort(410, "API v1 is no longer in use. Please upgrade your client.") diff --git a/openflexure_microscope/api/default_extensions/__init__.py b/openflexure_microscope/api/default_extensions/__init__.py index 8a28495f..561239af 100644 --- a/openflexure_microscope/api/default_extensions/__init__.py +++ b/openflexure_microscope/api/default_extensions/__init__.py @@ -8,9 +8,11 @@ def handle_extension_error(extension_name): """'gracefully' log an error if an extension fails to load.""" try: yield - except Exception as e: + except Exception: # pylint: disable=W0703 logging.error( - f"Exception loading builtin extension {extension_name}: \n{traceback.format_exc()}" + "Exception loading builtin extension %s: \n%s", + extension_name, + traceback.format_exc(), ) diff --git a/openflexure_microscope/api/default_extensions/autofocus.py b/openflexure_microscope/api/default_extensions/autofocus.py index 3f5121a2..9d16ce65 100644 --- a/openflexure_microscope/api/default_extensions/autofocus.py +++ b/openflexure_microscope/api/default_extensions/autofocus.py @@ -6,10 +6,10 @@ from threading import Event, Thread import numpy as np from labthings import current_action, fields, find_component from labthings.extensions import BaseExtension -from labthings.views import ActionView, PropertyView, View +from labthings.views import ActionView, View from scipy import ndimage -from openflexure_microscope.devel import JsonResponse, abort, request +from openflexure_microscope.devel import abort from openflexure_microscope.utilities import set_properties ### Autofocus utilities @@ -36,19 +36,15 @@ class JPEGSharpnessMonitor: 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" if not self.camera.stream_active: - logging.warn( + logging.warning( "Autofocus sharpness monitor was started but the camera isn't streaming. Attempting to start the stream..." ) self.camera.start_stream_recording() @@ -66,7 +62,7 @@ class JPEGSharpnessMonitor: "Function that runs in a background thread to record sharpness" logging.debug("Starting sharpness measurement in background thread") self.keep_alive() - logging.debug(f"_measure_jpegs stop_event: {self.stop_event.is_set()}") + logging.debug("_measure_jpegs stop_event: %s", self.stop_event.is_set()) while not self.stop_event.is_set() and not self.should_stop(): size_now = self.jpeg_size() time_now = time.time() @@ -107,7 +103,7 @@ class JPEGSharpnessMonitor: if np.sum(jpeg_times > stage_times[0]) == 0: raise ValueError( "No images were captured during the move of the stage. Perhaps the camera is not streaming images?" - ) + ) from e else: raise e if stop < 1: @@ -119,7 +115,7 @@ class JPEGSharpnessMonitor: 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) + _, jz, js = self.move_data(index) if len(js) == 0: raise ValueError( "No images were captured during the move of the stage. Perhaps the camera is not streaming images?" @@ -226,7 +222,7 @@ def fast_autofocus(microscope, dz=2000, backlash=None): """Perform a down-up-down-up autofocus""" with monitor_sharpness( microscope - ) as m, microscope.camera.lock, microscope.stage.lock: + ) as m, microscope.camera.lock as _, microscope.stage.lock as _: i, z = m.focus_rel(-dz / 2) i, z = m.focus_rel(dz) fz = m.sharpest_z_on_move(i) @@ -278,7 +274,7 @@ def fast_up_down_up_autofocus( """ with monitor_sharpness( microscope - ) as m, microscope.camera.lock, microscope.stage.lock: + ) as m, microscope.camera.lock as _, microscope.stage.lock as _: # Ensure the MJPEG stream has started microscope.camera.start_stream_recording() @@ -291,12 +287,8 @@ def fast_up_down_up_autofocus( i, z = m.focus_rel(-df) # now inspect where the sharpest point is, and estimate the sharpness # (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) + _, jz, js = m.move_data(i) best_z = jz[np.argmax(js)] - target_s = np.interp( - [best_z + target_z], jz[::-1], js[::-1] - ) # NB jz is decreasing # now move to the start of the z stack logging.debug("Move to the start of the z stack") diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index 4f3d3188..c3f2df11 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -1,6 +1,5 @@ import logging import os -from sys import platform import psutil from flask import abort @@ -9,9 +8,7 @@ from labthings.extensions import BaseExtension from labthings.views import PropertyView, View from openflexure_microscope.api.utilities.gui import build_gui -from openflexure_microscope.captures.capture import build_captures_from_exif from openflexure_microscope.captures.capture_manager import BASE_CAPTURE_PATH -from openflexure_microscope.config import OpenflexureSettingsFile from openflexure_microscope.paths import check_rw, settings_file_path AS_SETTINGS_PATH = settings_file_path("autostorage_settings.json") @@ -97,10 +94,10 @@ class AutostorageExtension(BaseExtension): def on_microscope(self, microscope_obj): """Function to automatically call when the parent LabThing has a microscope attached.""" - logging.debug(f"Autostorage extension found microscope {microscope_obj}") + logging.debug("Autostorage extension found microscope %s", microscope_obj) if hasattr(microscope_obj, "captures"): logging.debug( - f"Autostorage extension bound to CaptureManager {self.capture_manager}" + "Autostorage extension bound to CaptureManager %s", self.capture_manager ) # Store a reference to the CaptureManager @@ -119,7 +116,8 @@ class AutostorageExtension(BaseExtension): # If preferred path does not exist, or cannot be written to if not (os.path.isdir(location) and check_rw(location)): logging.error( - f"Preferred capture path {location} is missing or cannot be written to. Restoring defaults." + "Preferred capture path %s is missing or cannot be written to. Restoring defaults.", + location, ) # Reset the storage location to default set_current_location(self.capture_manager, get_default_location()) @@ -187,8 +185,6 @@ autostorage_extension_v2 = AutostorageExtension() class GetLocationsView(PropertyView): def get(self): - global autostorage_extension_v2 - autostorage_extension_v2.check_location() return autostorage_extension_v2.get_locations() @@ -197,13 +193,10 @@ class PreferredLocationView(PropertyView): schema = fields.String(required=True, example="Default") def get(self): - global autostorage_extension_v2 - autostorage_extension_v2.check_location() return autostorage_extension_v2.get_preferred_key() def post(self, new_path_key): - global autostorage_extension_v2 microscope = find_component("org.openflexure.microscope") if not microscope: @@ -218,13 +211,11 @@ class PreferredLocationGUIView(View): args = {"new_path_title": fields.String(required=True)} def post(self, args): - global autostorage_extension_v2 - new_path_title = args.get("new_path_title") logging.debug(new_path_title) new_path_key = autostorage_extension_v2.title_to_key(new_path_title) - logging.debug(f"{new_path_key}") + logging.debug(new_path_key) autostorage_extension_v2.check_location() autostorage_extension_v2.set_preferred_key(new_path_key) @@ -233,7 +224,6 @@ class PreferredLocationGUIView(View): def dynamic_form(): - global autostorage_extension_v2 autostorage_extension_v2.check_location() return { "icon": "sd_storage", diff --git a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py index 1ff46768..b763119b 100644 --- a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py +++ b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py @@ -7,7 +7,7 @@ from typing import Tuple from flask import abort from labthings import find_component from labthings.extensions import BaseExtension -from labthings.views import ActionView, View +from labthings.views import ActionView from .recalibrate_utils import ( auto_expose_and_freeze_settings, @@ -78,17 +78,10 @@ class FlattenLSTView(ActionView): "No microscope connected. Unable to flatten the lens shading table.", ) - try: - with pause_stream(microscope.camera) as scamera: - flat_lst = flat_lens_shading_table(scamera.camera) - scamera.camera.lens_shading_table = flat_lst - microscope.save_settings() - except: - logging.exception("Error flattening the lens shading table.") - abort( - 503, - "Couldn't flatten the lens shading table - do you have the forked PiCamera library installed?", - ) + with pause_stream(microscope.camera) as scamera: + flat_lst = flat_lens_shading_table(scamera.camera) + scamera.camera.lens_shading_table = flat_lst + microscope.save_settings() class DeleteLSTView(ActionView): @@ -101,16 +94,9 @@ class DeleteLSTView(ActionView): "No microscope connected. Unable to flatten the lens shading table.", ) - try: - with pause_stream(microscope.camera) as scamera: - scamera.camera.lens_shading_table = None - microscope.save_settings() - except: - logging.exception("Error deleting the lens shading table.") - abort( - 503, - "Couldn't flatten the lens shading table - do you have the forked PiCamera library installed?", - ) + with pause_stream(microscope.camera) as scamera: + scamera.camera.lens_shading_table = None + microscope.save_settings() lst_extension_v2 = BaseExtension( diff --git a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/recalibrate_utils.py b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/recalibrate_utils.py index 6886224c..ddbdb0ba 100644 --- a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/recalibrate_utils.py +++ b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/recalibrate_utils.py @@ -24,13 +24,15 @@ 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 + return ( + np.zeros(camera._lens_shading_table_shape(), dtype=np.uint8) + 32 + ) # pylint: disable=W0212 def adjust_exposure_to_setpoint(camera, setpoint): """Adjust the camera's exposure time until the maximum pixel value is .""" print("Adjusting shutter speed to hit setpoint {}".format(setpoint), end="") - for i in range(3): + for _ in range(3): print(".", end="") camera.shutter_speed = int( camera.shutter_speed * setpoint / np.max(rgb_image(camera)) @@ -50,9 +52,9 @@ def auto_expose_and_freeze_settings(camera): camera.awb_mode = "auto" camera.exposure_mode = "auto" camera.iso = ( - 0 - ) # This is important, if it's on a fixed ISO, gain might not set properly. - for i in range(6): + 0 # This is important, if it's on a fixed ISO, gain might not set properly. + ) + for _ in range(6): print(".", end="") time.sleep(0.5) logging.info("done") @@ -188,10 +190,10 @@ def recalibrate_camera(camera): if __name__ == "__main__": - with PiCamera() as camera: - camera.start_preview() + with PiCamera() as main_camera: + main_camera.start_preview() time.sleep(3) logging.info("Recalibrating...") - recalibrate_camera(camera) + recalibrate_camera(main_camera) logging.info("Done.") time.sleep(2) diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index 8d0ddfd3..45579d3e 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -14,7 +14,7 @@ from labthings import ( update_action_progress, ) from labthings.extensions import BaseExtension -from labthings.views import ActionView, View +from labthings.views import ActionView from openflexure_microscope.captures.capture_manager import generate_basename from openflexure_microscope.devel import abort @@ -74,10 +74,13 @@ class ScanExtension(BaseExtension): use_video_port: bool = False, resize: Tuple[int, int] = None, bayer: bool = False, - metadata: dict = {}, - annotations: dict = {}, - tags: list = [], + metadata: dict = None, + annotations: dict = None, + tags: list = None, ): + metadata = metadata or {} + annotations = annotations or {} + tags = tags or [] # Construct a tile filename if namemode == "coordinates": @@ -117,18 +120,22 @@ class ScanExtension(BaseExtension): basename: str = None, namemode: str = "coordinates", temporary: bool = False, - stride_size: int = [2000, 1500, 100], - grid: list = [3, 3, 5], + stride_size: list = (2000, 1500, 100), + grid: list = (3, 3, 5), style="raster", autofocus_dz: int = 50, use_video_port: bool = False, resize: Tuple[int, int] = None, bayer: bool = False, fast_autofocus=False, - metadata: dict = {}, - annotations: dict = {}, - tags: list = [], + metadata: dict = None, + annotations: dict = None, + tags: list = None, ): + metadata = metadata or {} + annotations = annotations or {} + tags = tags or [] + start = time.time() # Keep task progress @@ -210,7 +217,7 @@ class ScanExtension(BaseExtension): # If we're not doing a z-stack, just capture if grid[2] <= 1: - capture( + self.capture( microscope, basename, namemode=namemode, @@ -250,7 +257,7 @@ class ScanExtension(BaseExtension): microscope.stage.move_abs(initial_position) end = time.time() - logging.info(f"Scan took {end - start} seconds") + logging.info("Scan took %s seconds", end - start) def stack( self, @@ -264,24 +271,27 @@ class ScanExtension(BaseExtension): use_video_port: bool = False, resize: Tuple[int, int] = None, bayer: bool = False, - metadata: dict = {}, - annotations: dict = {}, - tags: list = [], + metadata: dict = None, + annotations: dict = None, + tags: list = None, ): + metadata = metadata or {} + annotations = annotations or {} + tags = tags or [] # Store initial position initial_position = microscope.stage.position - logging.debug(f"Starting z-stack from position {microscope.stage.position}") + logging.debug("Starting z-stack from position %s", microscope.stage.position) with microscope.lock: # Move to center scan logging.debug("Moving to z-stack starting position") microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)]) - logging.debug(f"Starting scan from position {microscope.stage.position}") + logging.debug("Starting scan from position %s", microscope.stage.position) for i in range(steps): time.sleep(0.1) - logging.debug(f"Capturing from position {microscope.stage.position}") + logging.debug("Capturing from position %s", microscope.stage.position) self.capture( microscope, basename, diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index ab69a18b..42470901 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -21,7 +21,7 @@ class ZipObjectSchema(Schema): links = fields.Dict() @pre_dump - def generate_links(self, data, **kwargs): + def generate_links(self, data, **_): data.links = { "download": { "href": url_for( @@ -34,8 +34,8 @@ class ZipObjectSchema(Schema): class ZipObjectDescription: - def __init__(self, id, file_pointer, data_size=None): - self.id = id + def __init__(self, id_, file_pointer, data_size=None): + self.id = id_ self.fp = file_pointer self.data_size = data_size self.zip_size = os.path.getsize(self.fp.name) * 1e-6 @@ -155,7 +155,7 @@ class ZipGetterAPIView(View): if not session_id in default_zip_manager.session_zips: return abort(404) # 404 Not Found - logging.info(f"Session ID: {session_id}") + logging.info("Session ID: %s", session_id) return send_file( default_zip_manager.zip_fp_from_id(session_id).name, diff --git a/openflexure_microscope/api/dev_extensions/tools.py b/openflexure_microscope/api/dev_extensions/tools.py index c3039a44..ddbadfef 100644 --- a/openflexure_microscope/api/dev_extensions/tools.py +++ b/openflexure_microscope/api/dev_extensions/tools.py @@ -17,7 +17,7 @@ class SleepFor(ActionView): def post(self, args): sleep_time = args.get("time") - logging.info(f"Going to sleep for {sleep_time}...") + logging.info("Going to sleep for %s...", sleep_time) start = time.time() time.sleep(sleep_time) end = time.time() diff --git a/openflexure_microscope/api/utilities/__init__.py b/openflexure_microscope/api/utilities/__init__.py index ce1d3033..6e01e9dc 100644 --- a/openflexure_microscope/api/utilities/__init__.py +++ b/openflexure_microscope/api/utilities/__init__.py @@ -116,7 +116,6 @@ def create_file(config_path): def init_default_extensions(extension_dir): - global _DEFAULT_EXTENSION_INIT os.makedirs(extension_dir, exist_ok=True) default_ext_path = os.path.join(extension_dir, "defaults.py") diff --git a/openflexure_microscope/api/utilities/gui.py b/openflexure_microscope/api/utilities/gui.py index 6e00b04c..1fc03c1f 100644 --- a/openflexure_microscope/api/utilities/gui.py +++ b/openflexure_microscope/api/utilities/gui.py @@ -12,9 +12,10 @@ def clean_rule(rule: str): def build_gui_from_dict(gui_description, extension_object): # Make a working copy of GUI description api_gui = copy.deepcopy(gui_description) - - logging.debug(extension_object) - logging.debug(extension_object._rules) + # Grab the extensions rules dictionary + # NOTE: The LabThings extension object should probably have a non-private + # way to get the rules dictionary. For now we need to ignore pylint W0212 + ext_rules = extension_object._rules # pylint: disable=W0212 # Expand shorthand routes into full relative URLs if "forms" in gui_description and isinstance(api_gui["forms"], list): @@ -23,12 +24,10 @@ def build_gui_from_dict(gui_description, extension_object): if "route" in form: form["route"] = clean_rule(form["route"]) # Match rule in extension object - if "route" in form and form["route"] in extension_object._rules.keys(): - form["route"] = extension_object._rules[form["route"]]["urls"][0] + if "route" in form and form["route"] in ext_rules.keys(): + form["route"] = ext_rules[form["route"]]["urls"][0] else: - logging.warn( - "No valid expandable route found for {}".format(form["route"]) - ) + logging.warning("No valid expandable route found for %s", form["route"]) # Inject extension information api_gui["id"] = extension_object.name diff --git a/openflexure_microscope/api/v2/views/actions/__init__.py b/openflexure_microscope/api/v2/views/actions/__init__.py index 4b444a23..7ac028af 100644 --- a/openflexure_microscope/api/v2/views/actions/__init__.py +++ b/openflexure_microscope/api/v2/views/actions/__init__.py @@ -53,7 +53,6 @@ _actions = { def enabled_root_actions(): - global _actions return {k: v for k, v in _actions.items() if v["conditions"]} @@ -65,8 +64,6 @@ class ActionsView(View): This list does not include any actions added by LabThings extensions, only those part of the default OpenFlexure Microscope API. """ - global _actions - actions = {} for name, action in enabled_root_actions().items(): d = { diff --git a/openflexure_microscope/api/v2/views/actions/camera.py b/openflexure_microscope/api/v2/views/actions/camera.py index a045098a..02d9b229 100644 --- a/openflexure_microscope/api/v2/views/actions/camera.py +++ b/openflexure_microscope/api/v2/views/actions/camera.py @@ -1,13 +1,11 @@ import io import logging -from flask import abort, redirect, request, send_file, url_for +from flask import abort, send_file from labthings import fields, find_component -from labthings.views import ActionView, View +from labthings.views import ActionView -from openflexure_microscope.api.utilities import JsonResponse, get_bool from openflexure_microscope.api.v2.views.captures import CaptureSchema -from openflexure_microscope.utilities import filter_dict class CaptureAPI(ActionView): diff --git a/openflexure_microscope/api/v2/views/actions/stage.py b/openflexure_microscope/api/v2/views/actions/stage.py index df323c5d..cd989f5c 100644 --- a/openflexure_microscope/api/v2/views/actions/stage.py +++ b/openflexure_microscope/api/v2/views/actions/stage.py @@ -1,11 +1,9 @@ import logging -from flask import Blueprint, request from labthings import fields, find_component -from labthings.views import ActionView, View +from labthings.views import ActionView -from openflexure_microscope.api.utilities import JsonResponse -from openflexure_microscope.utilities import axes_to_array, filter_dict +from openflexure_microscope.utilities import axes_to_array class MoveStageAPI(ActionView): diff --git a/openflexure_microscope/api/v2/views/actions/system.py b/openflexure_microscope/api/v2/views/actions/system.py index 885b4482..5461e053 100644 --- a/openflexure_microscope/api/v2/views/actions/system.py +++ b/openflexure_microscope/api/v2/views/actions/system.py @@ -1,11 +1,10 @@ import os import subprocess -from sys import platform -from labthings.views import ActionView, View +from labthings.views import ActionView -def is_raspberrypi(raise_on_errors=False): +def is_raspberrypi(): """ Checks if Raspberry Pi. """ diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index 04062774..5728b49a 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -45,9 +45,8 @@ class CaptureSchema(Schema): links = fields.Dict() - # TODO: Automate this somewhat @pre_dump - def generate_links(self, data, **kwargs): + def generate_links(self, data, **_): data.links = { "self": { "href": url_for(CaptureView.endpoint, id=data.id, _external=True), @@ -97,24 +96,24 @@ class CaptureView(View): tags = ["captures"] @marshal_with(CaptureSchema()) - def get(self, id): + def get(self, id_): """ Description of a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.captures.images.get(id) + capture_obj = microscope.captures.images.get(id_) if not capture_obj: return abort(404) # 404 Not Found return capture_obj - def delete(self, id): + def delete(self, id_): """ Delete a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.captures.images.get(id) + capture_obj = microscope.captures.images.get(id_) if not capture_obj: return abort(404) # 404 Not Found @@ -122,7 +121,7 @@ class CaptureView(View): # Delete the capture file capture_obj.delete() # Delete from capture list - del microscope.captures.images[id] + del microscope.captures.images[id_] return "", 204 @@ -131,12 +130,12 @@ class CaptureDownload(View): tags = ["captures"] responses = {200: {"content_type": "image/jpeg"}} - def get(self, id, filename): + def get(self, id_, filename): """ Image data for a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.captures.images.get(id) + capture_obj = microscope.captures.images.get(id_) if not capture_obj: return abort(404) # 404 Not Found @@ -148,7 +147,7 @@ class CaptureDownload(View): return redirect( url_for( "DownloadAPI", - id=id, + id=id_, filename=capture_obj.filename, thumbnail=thumbnail, ), @@ -167,24 +166,24 @@ class CaptureDownload(View): class CaptureTags(View): tags = ["captures"] - def get(self, id): + def get(self, id_): """ Get tags associated with a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.captures.images.get(id) + capture_obj = microscope.captures.images.get(id_) if not capture_obj: return abort(404) # 404 Not Found return capture_obj.tags - def put(self, id): + def put(self, id_): """ Add tags to a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.captures.images.get(id) + capture_obj = microscope.captures.images.get(id_) if not capture_obj: return abort(404) # 404 Not Found @@ -199,12 +198,12 @@ class CaptureTags(View): return capture_obj.tags - def delete(self, id): + def delete(self, id_): """ Delete tags from a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.captures.images.get(id) + capture_obj = microscope.captures.images.get(id_) if not capture_obj: return abort(404) # 404 Not Found @@ -223,24 +222,24 @@ class CaptureTags(View): class CaptureAnnotations(View): tags = ["captures"] - def get(self, id): + def get(self, id_): """ Get annotations associated with a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.captures.images.get(id) + capture_obj = microscope.captures.images.get(id_) if not capture_obj: return abort(404) # 404 Not Found return capture_obj.annotations - def put(self, id): + def put(self, id_): """ Update metadata for a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.captures.images.get(id) + capture_obj = microscope.captures.images.get(id_) if not capture_obj: return abort(404) # 404 Not Found diff --git a/openflexure_microscope/api/v2/views/stage.py b/openflexure_microscope/api/v2/views/stage.py index ef66decd..e65138eb 100644 --- a/openflexure_microscope/api/v2/views/stage.py +++ b/openflexure_microscope/api/v2/views/stage.py @@ -1,6 +1,4 @@ -import logging - -from labthings import fields, find_component, schema +from labthings import fields, find_component from labthings.views import PropertyView diff --git a/openflexure_microscope/api/v2/views/streams.py b/openflexure_microscope/api/v2/views/streams.py index 71ae4f61..4e415936 100644 --- a/openflexure_microscope/api/v2/views/streams.py +++ b/openflexure_microscope/api/v2/views/streams.py @@ -1,9 +1,8 @@ from flask import Response from labthings import find_component -from labthings.utilities import create_from_path, get_by_path, set_by_path -from labthings.views import PropertyView, View +from labthings.views import PropertyView -from openflexure_microscope.api.utilities import JsonResponse, gen +from openflexure_microscope.api.utilities import gen class MjpegStream(PropertyView): diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index d8781f76..c2451dbe 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -import datetime import logging -import os -import shutil import threading import time from abc import ABCMeta, abstractmethod @@ -21,6 +18,7 @@ class BaseCamera(metaclass=ABCMeta): self.lock = StrictLock(name="Camera", timeout=None) + self.frames_iterator = None self.frame = None self.last_access = 0 self.event = ClientEvent() @@ -34,13 +32,11 @@ class BaseCamera(metaclass=ABCMeta): @abstractmethod def configuration(self): """The current camera configuration.""" - pass @property @abstractmethod def state(self): """The current read-only camera state.""" - pass @property def settings(self): @@ -132,7 +128,6 @@ class BaseCamera(metaclass=ABCMeta): @abstractmethod def frames(self): """Create generator that returns frames from the camera.""" - pass # WORKER THREAD diff --git a/openflexure_microscope/camera/mock.py b/openflexure_microscope/camera/mock.py index 6f5965dd..2d20ef39 100644 --- a/openflexure_microscope/camera/mock.py +++ b/openflexure_microscope/camera/mock.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -""" -""" - from __future__ import division import io @@ -11,18 +8,13 @@ import time from datetime import datetime # Type hinting -from typing import Tuple - -import numpy as np -from PIL import Image, ImageDraw, ImageFont +from PIL import Image, ImageDraw from openflexure_microscope.camera.base import BaseCamera -from openflexure_microscope.captures import CaptureObject -""" -PIL spams the logger with debug-level information. This is a pain when debugging api.app. -We override the logging settings in api.app by setting a level for PIL here. -""" +# PIL spams the logger with debug-level information. This is a pain when debugging api.app. +# We override the logging settings in api.app by setting a level for PIL here. + pil_logger = logging.getLogger("PIL") pil_logger.setLevel(logging.INFO) @@ -79,7 +71,6 @@ class MissingCamera(BaseCamera): def initialisation(self): """Run any initialisation code when the frame iterator starts.""" - pass def close(self): """Close the Raspberry Pi PiCameraStreamer.""" @@ -134,7 +125,7 @@ class MissingCamera(BaseCamera): "Cannot update camera config while recording is active." ) - def set_zoom(self, zoom_value: float = 1.0) -> None: + def set_zoom(self, *_) -> None: """ Change the camera zoom, handling re-centering and scaling. """ @@ -142,7 +133,7 @@ class MissingCamera(BaseCamera): # LAUNCH ACTIONS - def start_preview(self, fullscreen=True, window=None): + def start_preview(self, *_): """Start the on board GPU camera preview.""" logging.warning("GPU preview not implemented in mock camera") @@ -150,7 +141,7 @@ class MissingCamera(BaseCamera): """Stop the on board GPU camera preview.""" logging.warning("GPU preview not implemented in mock camera") - def start_recording(self, output, fmt: str = "h264", quality: int = 15): + def start_recording(self, *_): """Start recording. Start a new video recording, writing to a output object. @@ -173,14 +164,7 @@ class MissingCamera(BaseCamera): with self.lock: logging.warning("Recording not implemented in mock camera") - def capture( - self, - output, - fmt: str = "jpeg", - use_video_port: bool = False, - resize: Tuple[int, int] = None, - bayer: bool = True, - ): + def capture(self, output, *_): """ Capture a still image to a StreamObject. @@ -189,10 +173,6 @@ class MissingCamera(BaseCamera): Args: output: String or file-like object to write capture data to - use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster. - fmt (str): Format of the capture. - resize ((int, int)): Resize the captured image. - bayer (bool): Store raw bayer data in capture """ with self.lock: diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 575445bf..076241c3 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -30,7 +30,6 @@ from __future__ import division import io import logging -import os import time # Type hinting @@ -43,13 +42,8 @@ import picamerax import picamerax.array from openflexure_microscope.camera.base import BaseCamera -from openflexure_microscope.captures import CaptureObject from openflexure_microscope.paths import settings_file_path -from openflexure_microscope.utilities import ( - json_to_ndarray, - ndarray_to_json, - serialise_array_b64, -) +from openflexure_microscope.utilities import json_to_ndarray, ndarray_to_json # Richard's fix gain from .set_picamera_gain import set_analog_gain, set_digital_gain @@ -117,6 +111,7 @@ class PiCameraStreamer(BaseCamera): ) #: str: Path of .npy lens shading table file # Start the stream worker on init + self.stream = None self.start_worker() @property @@ -131,7 +126,6 @@ class PiCameraStreamer(BaseCamera): def initialisation(self): """Run any initialisation code when the frame iterator starts.""" - pass def close(self): """Close the Raspberry Pi PiCameraStreamer.""" @@ -466,7 +460,7 @@ class PiCameraStreamer(BaseCamera): # Reduce the resolution for video streaming try: - self.camera._check_recording_stopped() + self.camera._check_recording_stopped() # pylint: disable=W0212 except picamerax.exc.PiCameraRuntimeError: logging.info( "Error while changing resolution: Recording already running." diff --git a/openflexure_microscope/camera/piexif/_common.py b/openflexure_microscope/camera/piexif/_common.py index 2661a1ee..9cad2620 100644 --- a/openflexure_microscope/camera/piexif/_common.py +++ b/openflexure_microscope/camera/piexif/_common.py @@ -39,7 +39,7 @@ def read_exif_from_file(filename): head = data[2:6] HEAD_LENGTH = 4 exif = None - while 1: + while len(head) == HEAD_LENGTH: length = struct.unpack(">H", head[2:4])[0] if head[:2] == b"\xff\xe1": diff --git a/openflexure_microscope/camera/piexif/_dump.py b/openflexure_microscope/camera/piexif/_dump.py index 3e0a3244..190e239f 100644 --- a/openflexure_microscope/camera/piexif/_dump.py +++ b/openflexure_microscope/camera/piexif/_dump.py @@ -2,8 +2,8 @@ import copy import numbers import struct -from ._common import * -from ._exif import * +from ._common import split_into_segments +from ._exif import ExifIFD, ImageIFD, TAGS, TYPES TIFF_HEADER_LENGTH = 8 @@ -243,11 +243,11 @@ def _value_to_bytes(raw_value, value_type, offset): elif value_type == TYPES.Ascii: try: new_value = raw_value.encode("latin1") + b"\x00" - except: + except: # pylint: disable=W0702 try: new_value = raw_value + b"\x00" - except TypeError: - raise ValueError("Got invalid type to convert.") + except TypeError as e: + raise ValueError("Got invalid type to convert.") from e length = len(new_value) if length > 4: value_str = struct.pack(">I", offset) @@ -262,7 +262,7 @@ def _value_to_bytes(raw_value, value_type, offset): elif isinstance(raw_value[0], tuple): length = len(raw_value) new_value = b"" - for n, val in enumerate(raw_value): + for _, val in enumerate(raw_value): num, den = val new_value += struct.pack(">L", num) + struct.pack(">L", den) value_str = struct.pack(">I", offset) @@ -275,7 +275,7 @@ def _value_to_bytes(raw_value, value_type, offset): elif isinstance(raw_value[0], tuple): length = len(raw_value) new_value = b"" - for n, val in enumerate(raw_value): + for _, val in enumerate(raw_value): num, den = val new_value += struct.pack(">l", num) + struct.pack(">l", den) value_str = struct.pack(">I", offset) @@ -286,13 +286,13 @@ def _value_to_bytes(raw_value, value_type, offset): value_str = struct.pack(">I", offset) try: four_bytes_over = b"" + raw_value - except TypeError: - raise ValueError("Got invalid type to convert.") + except TypeError as e: + raise ValueError("Got invalid type to convert.") from e else: try: value_str = raw_value + b"\x00" * (4 - length) - except TypeError: - raise ValueError("Got invalid type to convert.") + except TypeError as e: + raise ValueError("Got invalid type to convert.") from e elif value_type == TYPES.SByte: # Signed Byte length = len(raw_value) if length <= 4: @@ -333,7 +333,7 @@ def _dict_to_bytes(ifd_dict, ifd, ifd_offset): entries = b"" values = b"" - for n, key in enumerate(sorted(ifd_dict)): + for _, key in enumerate(sorted(ifd_dict)): if (ifd == "0th") and (key in (ImageIFD.ExifTag, ImageIFD.GPSTag)): continue elif (ifd == "Exif") and (key == ExifIFD.InteroperabilityTag): @@ -358,11 +358,11 @@ def _dict_to_bytes(ifd_dict, ifd, ifd_offset): length_str, value_str, four_bytes_over = _value_to_bytes( raw_value, value_type, offset ) - except ValueError: + except ValueError as e: raise ValueError( '"dump" got wrong type of exif value.\n' + "{0} in {1} IFD. Got as {2}.".format(key, ifd, type(ifd_dict[key])) - ) + ) from e entries += key_str + type_str + length_str + value_str values += four_bytes_over diff --git a/openflexure_microscope/camera/piexif/_insert.py b/openflexure_microscope/camera/piexif/_insert.py index 31958a25..098bd7cc 100644 --- a/openflexure_microscope/camera/piexif/_insert.py +++ b/openflexure_microscope/camera/piexif/_insert.py @@ -3,7 +3,7 @@ import struct import sys from . import _webp -from ._common import * +from ._common import merge_segments, split_into_segments from ._exceptions import InvalidImageDataError diff --git a/openflexure_microscope/camera/piexif/_load.py b/openflexure_microscope/camera/piexif/_load.py index 50b46189..9ea899f8 100644 --- a/openflexure_microscope/camera/piexif/_load.py +++ b/openflexure_microscope/camera/piexif/_load.py @@ -2,9 +2,9 @@ import struct import sys from . import _webp -from ._common import * +from ._common import get_exif_seg, read_exif_from_file, split_into_segments from ._exceptions import InvalidImageDataError -from ._exif import * +from ._exif import ExifIFD, ImageIFD, TAGS, TYPES LITTLE_ENDIAN = b"\x49\x49" @@ -109,6 +109,8 @@ class _ExifReader(object): else: raise InvalidImageDataError("Given file is neither JPEG nor TIFF.") + self.endian_mark = None + def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): ifd_dict = {} tag_count = struct.unpack( diff --git a/openflexure_microscope/camera/piexif/_remove.py b/openflexure_microscope/camera/piexif/_remove.py index 8efabfc0..aba71dad 100644 --- a/openflexure_microscope/camera/piexif/_remove.py +++ b/openflexure_microscope/camera/piexif/_remove.py @@ -1,7 +1,7 @@ import io from . import _webp -from ._common import * +from ._common import get_exif_seg, split_into_segments def remove(src, new_file=None): @@ -42,7 +42,7 @@ def remove(src, new_file=None): new_data = src_data except Exception as e: print(e) - raise ValueError("Error occurred.") + raise ValueError("Error occurred.") from e if isinstance(new_file, io.BytesIO): new_file.write(new_data) diff --git a/openflexure_microscope/camera/piexif/_transplant.py b/openflexure_microscope/camera/piexif/_transplant.py index 8f2a0a4f..330a1722 100644 --- a/openflexure_microscope/camera/piexif/_transplant.py +++ b/openflexure_microscope/camera/piexif/_transplant.py @@ -1,6 +1,6 @@ import io -from ._common import * +from ._common import get_exif_seg, merge_segments, split_into_segments def transplant(exif_src, image, new_file=None): diff --git a/openflexure_microscope/camera/piexif/_webp.py b/openflexure_microscope/camera/piexif/_webp.py index 10649edf..ab833abe 100644 --- a/openflexure_microscope/camera/piexif/_webp.py +++ b/openflexure_microscope/camera/piexif/_webp.py @@ -204,8 +204,6 @@ def get_exif(data): CHUNK_FOURCC_LENGTH = 4 LENGTH_BYTES_LENGTH = 4 - chunks = [] - exif = b"" while pointer < file_size: fourcc = data[pointer : pointer + CHUNK_FOURCC_LENGTH] pointer += CHUNK_FOURCC_LENGTH diff --git a/openflexure_microscope/camera/piexif/helper.py b/openflexure_microscope/camera/piexif/helper.py index 95b7f8c7..5ba045ef 100644 --- a/openflexure_microscope/camera/piexif/helper.py +++ b/openflexure_microscope/camera/piexif/helper.py @@ -46,8 +46,8 @@ class UserComment: cls._JIS_PREFIX: cls._JIS, cls._UNICODE_PREFIX: cls._UNICODE, }[prefix] - except KeyError: - raise ValueError("unable to determine appropriate encoding") + except KeyError as e: + raise ValueError("unable to determine appropriate encoding") from e return body.decode(encoding, errors="replace") @classmethod diff --git a/openflexure_microscope/camera/set_picamera_gain.py b/openflexure_microscope/camera/set_picamera_gain.py index 8de9068d..d1d7175a 100644 --- a/openflexure_microscope/camera/set_picamera_gain.py +++ b/openflexure_microscope/camera/set_picamera_gain.py @@ -4,7 +4,7 @@ import logging import time import picamerax -from picamerax import exc, mmal, mmalobj +from picamerax import exc, mmal from picamerax.mmalobj import to_rational MMAL_PARAMETER_ANALOG_GAIN = mmal.MMAL_PARAMETER_GROUP_CAMERA + 0x59 @@ -21,7 +21,7 @@ def set_gain(camera, gain, value): if gain not in [MMAL_PARAMETER_ANALOG_GAIN, MMAL_PARAMETER_DIGITAL_GAIN]: raise ValueError("The gain parameter was not valid") ret = mmal.mmal_port_parameter_set_rational( - camera._camera.control._port, gain, to_rational(value) + camera._camera.control._port, gain, to_rational(value) # pylint: disable=W0212 ) if ret == 4: raise exc.PiCameraMMALError( diff --git a/openflexure_microscope/captures/capture.py b/openflexure_microscope/captures/capture.py index bdc99c6f..2c2812f5 100644 --- a/openflexure_microscope/captures/capture.py +++ b/openflexure_microscope/captures/capture.py @@ -1,11 +1,9 @@ -import atexit import datetime import glob import io import json import logging import os -import shutil import uuid from collections import OrderedDict @@ -37,7 +35,8 @@ def pull_usercomment_dict(filepath): return json.loads(exif_dict["Exif"][piexif.ExifIFD.UserComment].decode()) except json.decoder.JSONDecodeError: logging.error( - f"Capture {filepath} has old, corrupt, or missing OpenFlexure metadata. Unable to reload to server." + "Capture %s has old, corrupt, or missing OpenFlexure metadata. Unable to reload to server.", + filepath, ) else: return None @@ -56,8 +55,6 @@ def make_file_list(directory, formats): def build_captures_from_exif(capture_path): - global EXIF_FORMATS - logging.debug("Reloading captures from {}...".format(capture_path)) files = make_file_list(capture_path, EXIF_FORMATS) captures = OrderedDict() @@ -97,9 +94,9 @@ def capture_from_exif(path, exif_dict): # Image metadata try: image_metadata = exif_dict.pop("image") - except KeyError as e: + except KeyError: logging.error( - f"Unable to obtain valid 2.0 OpenFlexure metadata from file {path}" + "Unable to obtain valid 2.0 OpenFlexure metadata from file %s", path ) return None @@ -111,7 +108,7 @@ def capture_from_exif(path, exif_dict): capture.annotations = image_metadata.get("annotations") # Since we popped the "image" key, we dump whatever is left in _metadata - capture._metadata = exif_dict + capture.set_metadata(exif_dict) return capture @@ -133,6 +130,7 @@ class CaptureObject(object): self.datetime = datetime.datetime.now() # Create file name. Default to UUID + self.format = None self.file = filepath self.split_file_path(self.file) @@ -151,15 +149,15 @@ class CaptureObject(object): self.thumb_bytes = None def write(self, s): - logging.debug(f"Writing to {self}") + logging.debug("Writing to %s", self) self.stream.write(s) def flush(self): - logging.info(f"Writing to disk {self.file}") + logging.info("Writing to disk %s", self.file) with open(self.file, "wb") as outfile: outfile.write(self.stream.getbuffer()) self.stream.close() - logging.info(f"Finished writing to disk {self.file}") + logging.info("Finished writing to disk %s", self.file) def open(self, mode): return open(self.file, mode) @@ -233,6 +231,15 @@ class CaptureObject(object): self._metadata.update(data) self.save_metadata() + def set_metadata(self, data: dict) -> None: + """ + Write root metadata from a passed dictionary into the capture metadata. + + Args: + data (dict): Dictionary of metadata to be added + """ + self._metadata = data + def put_and_save( self, tags: list = None, annotations: dict = None, metadata: dict = None ): @@ -261,22 +268,20 @@ class CaptureObject(object): """ Save metadata to exif, if supported """ - global EXIF_FORMATS - if self.format.upper() in EXIF_FORMATS and self.exists: logging.debug("Writing exif data to capture file") # Extract current Exif data exif_dict = piexif.load(self.file) # Serialize metadata metadata_string = json.dumps(self.metadata, cls=JSONEncoder) - logging.debug(f"Saving metadata string to file: {metadata_string}") + logging.debug("Saving metadata string to file: %s", metadata_string) # Insert metadata into exif_dict exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_string.encode() # Convert new exif dict to exif bytes exif_bytes = piexif.dump(exif_dict) # Insert exif into file piexif.insert(exif_bytes, self.file) - logging.info(f"Finished saving metadata to {self.file}") + logging.info("Finished saving metadata to %s", self.file) @property def metadata(self) -> dict: @@ -344,7 +349,6 @@ class CaptureObject(object): """ Returns a thumbnail of the capture data, for supported image formats. """ - global THUMBNAIL_SIZE # If no thumbnail exists, try and make one if not self.thumb_bytes: logging.info("Building thumbnail") diff --git a/openflexure_microscope/captures/capture_manager.py b/openflexure_microscope/captures/capture_manager.py index 37cde155..e5e290eb 100644 --- a/openflexure_microscope/captures/capture_manager.py +++ b/openflexure_microscope/captures/capture_manager.py @@ -158,7 +158,8 @@ class CaptureManager: # Update capture list capture_key = str(output.id) - logging.debug(f"Adding image {output} with key {capture_key}") + logging.debug("Adding image %s with key %s", output, capture_key) + self.images[capture_key] = output return output @@ -204,7 +205,7 @@ class CaptureManager: # Update capture list capture_key = str(output.id) - logging.debug(f"Adding video {output} with key {capture_key}") + logging.debug("Adding video %s with key %s", output, capture_key) self.videos[capture_key] = output return output diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index a88bab77..cfca29f3 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -6,7 +6,6 @@ import shutil from fractions import Fraction from uuid import UUID -import flask import numpy as np from labthings.json import LabThingsJSONEncoder @@ -27,8 +26,8 @@ class OpenflexureSettingsFile: expand (bool): Expand paths to valid auxillary config files. """ - def __init__(self, path: str, defaults: dict = {}): - global DEFAULT_SETTINGS + def __init__(self, path: str, defaults: dict = None): + defaults = defaults or {} # Set arguments self.path = path @@ -84,7 +83,7 @@ class JSONEncoder(LabThingsJSONEncoder): A custom JSON encoder, with type conversions for PiCamera fractions, Numpy integers, and Numpy arrays """ - def default(self, o, markers=None): + def default(self, o): if isinstance(o, UUID): return str(o) # PiCamera fractions diff --git a/openflexure_microscope/exceptions.py b/openflexure_microscope/exceptions.py deleted file mode 100644 index b85196d8..00000000 --- a/openflexure_microscope/exceptions.py +++ /dev/null @@ -1,23 +0,0 @@ -from threading import ThreadError - - -class LockError(ThreadError): - ERROR_CODES = { - "ACQUIRE_ERROR": "Unable to acquire. Lock in use by another thread.", - "IN_USE_ERROR": "Lock in use by another thread.", - } - - def __init__(self, code, lock): - self.code = code - if code in LockError.ERROR_CODES: - self.message = LockError.ERROR_CODES[code] - else: - self.message = "Unknown error." - - self.string = "{}: {}".format(self.code, self.message) - print(self.string) - - ThreadError.__init__(self) - - def __str__(self): - return self.string diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 1611222b..824ce971 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -16,14 +16,12 @@ from openflexure_microscope.stage.sanga import SangaDeltaStage, SangaStage try: from openflexure_microscope.camera.pi import PiCameraStreamer -except Exception as e: +except Exception as e: # pylint: disable=W0703 logging.error(e) logging.warning("Unable to import PiCameraStreamer") from labthings import CompositeLock -from openflexure_microscope.camera.mock import MissingCamera from openflexure_microscope.config import user_configuration, user_settings -from openflexure_microscope.utilities import Timer, serialise_array_b64 class Microscope: @@ -99,7 +97,7 @@ class Microscope: if camera_type in ("PiCamera", "PiCameraStreamer"): try: self.camera = PiCameraStreamer() - except Exception as e: + except Exception as e: # pylint: disable=W0703 logging.error(e) logging.warning("No compatible camera hardware found.") @@ -146,7 +144,7 @@ class Microscope: try: logging.info("Trying SangaStage") self.stage = SangaStage(port=stage_port) - except Exception as e: + except Exception as e: # pylint: disable=W0703 logging.error(e) logging.warning("No compatible Sangaboard hardware found.") elif stage_type in ("SangaDeltaStage"): @@ -154,7 +152,7 @@ class Microscope: logging.info("Trying SangaDeltaStage") self.stage = SangaDeltaStage(port=stage_port) - except Exception as e: + except Exception as e: # pylint: disable=W0703 logging.error(e) logging.warning("No compatible Sangaboard hardware found.") else: @@ -334,14 +332,14 @@ class Microscope: # Load cached bits of metadata if cache_key: - logging.debug(f"Reading cached microscope metadata: {cache_key}") + logging.debug("Reading cached microscope metadata: %s", cache_key) metadata = self.metadata_cache.get(cache_key, None) if not metadata: - logging.debug(f"Building and caching microscope metadata: {cache_key}") + logging.debug("Building and caching microscope metadata: %s", cache_key) metadata = self.force_get_metadata() self.metadata_cache[cache_key] = metadata else: - logging.debug(f"Building microscope metadata: {cache_key}") + logging.debug("Building microscope metadata: %s", cache_key) metadata = self.force_get_metadata() # Keys that should never be cached @@ -367,7 +365,7 @@ class Microscope: metadata: dict = None, cache_key: str = None, ): - logging.debug(f"Microscope capturing to {filename}") + logging.debug("Microscope capturing to %s", filename) if not annotations: annotations = {} if not metadata: @@ -386,8 +384,7 @@ class Microscope: ) # Capture to output object - logging.info(f"Starting microscope capture {output.file}") - print(output) + logging.info("Starting microscope capture %s", output.file) self.camera.capture( output, use_video_port=use_video_port, @@ -397,6 +394,6 @@ class Microscope: ) output.put_and_save(tags, annotations, full_metadata) - logging.debug(f"Finished capture to {output.file}") + logging.debug("Finished capture to %s", output.file) return output diff --git a/openflexure_microscope/paths.py b/openflexure_microscope/paths.py index 98746f00..d497873b 100644 --- a/openflexure_microscope/paths.py +++ b/openflexure_microscope/paths.py @@ -1,4 +1,3 @@ -import logging import os # UTILITIES diff --git a/openflexure_microscope/rescue/check_capture_reload.py b/openflexure_microscope/rescue/check_capture_reload.py index 272d4c8e..c87b2247 100644 --- a/openflexure_microscope/rescue/check_capture_reload.py +++ b/openflexure_microscope/rescue/check_capture_reload.py @@ -6,12 +6,12 @@ from openflexure_microscope.config import user_settings from openflexure_microscope.rescue.monitor_timeout import launch_timeout_test_process -def check_capture_rebuild(timeout=10): +def check_capture_rebuild(): logging.info("Loading user settings...") settings = user_settings.load() cap_path = str(settings.get("captures", {}).get("paths", {}).get("default")) - logging.info(f"Capture path found: {cap_path}") + logging.info("Capture path found: %s", cap_path) if not cap_path: logging.error( "No capture path defined in settings. This is unusual for anything other than a first-run. \nFalling back to default path." diff --git a/openflexure_microscope/rescue/check_settings.py b/openflexure_microscope/rescue/check_settings.py index 86c64b7d..1c28c9fd 100644 --- a/openflexure_microscope/rescue/check_settings.py +++ b/openflexure_microscope/rescue/check_settings.py @@ -16,7 +16,7 @@ def trace_config_exceptions(): default_config = json.load(DEFAULT_CONFIGURATION_FILE_PATH) if not default_config: error_sources.append("default_config_empty") - except Exception as e: + except Exception as e: # pylint: disable=W0703 logging.error("Error parsing config:") logging.error(e) error_sources.append("default_config_error") @@ -25,7 +25,7 @@ def trace_config_exceptions(): default_settings = json.load(SETTINGS_FILE_PATH) if not default_settings: error_sources.append("default_settings_empty") - except Exception as e: + except Exception as e: # pylint: disable=W0703 logging.error("Error parsing settings:") logging.error(e) error_sources.append("default_settings_error") @@ -37,8 +37,8 @@ def main(): error_sources = [] logging.info("Attempting default settings and config import...") try: - from openflexure_microscope import config - except Exception as e: + from openflexure_microscope import config as _ + except Exception as e: # pylint: disable=W0703 error_sources.append("config_settings_import_error") logging.error("Error importing config:") logging.error(e) diff --git a/openflexure_microscope/rescue/monitor_service.py b/openflexure_microscope/rescue/monitor_service.py index 43c18a6c..6090ed7e 100644 --- a/openflexure_microscope/rescue/monitor_service.py +++ b/openflexure_microscope/rescue/monitor_service.py @@ -21,7 +21,6 @@ # import subprocess -import sys class ServiceMonitor(object): diff --git a/openflexure_microscope/rescue/monitor_timeout.py b/openflexure_microscope/rescue/monitor_timeout.py index c59b7813..49af1169 100644 --- a/openflexure_microscope/rescue/monitor_timeout.py +++ b/openflexure_microscope/rescue/monitor_timeout.py @@ -24,7 +24,9 @@ def launch_timeout_test_process(target, args=(), kwargs=None, timeout=10): # If thread is still active if p.is_alive(): logging.error( - f"Function {target} reached timeout after {timeout} seconds. Terminating." + "Function %s reached timeout after %s seconds. Terminating.", + target, + timeout, ) # Terminate diff --git a/openflexure_microscope/stage/base.py b/openflexure_microscope/stage/base.py index 539ea93c..98ed516d 100644 --- a/openflexure_microscope/stage/base.py +++ b/openflexure_microscope/stage/base.py @@ -17,36 +17,30 @@ class BaseStage(metaclass=ABCMeta): @abstractmethod def update_settings(self, config: dict): """Update settings from a config dictionary""" - pass @abstractmethod def read_settings(self): """Return the current settings as a dictionary""" - pass @property @abstractmethod def state(self): """The general state dictionary of the board.""" - pass @property @abstractmethod def configuration(self): """The general stage configuration.""" - pass @property @abstractmethod def n_axes(self): """The number of axes this stage has.""" - pass @property @abstractmethod def position(self): """The current position, as a list""" - pass @property def position_map(self): @@ -56,36 +50,30 @@ class BaseStage(metaclass=ABCMeta): @abstractmethod def backlash(self): """Get the distance used for backlash compensation.""" - pass @backlash.setter @abstractmethod def backlash(self): """Set the distance used for backlash compensation.""" - pass @abstractmethod - def move_rel(self, displacement, backlash=True): + def move_rel(self, displacement: list, axis=None, backlash=True): """Make a relative move, optionally correcting for backlash. displacement: integer or array/list of 3 integers backlash: (default: True) whether to correct for backlash. """ - pass @abstractmethod def move_abs(self, final, **kwargs): """Make an absolute move to a position""" - pass @abstractmethod def zero_position(self): """Set the current position to zero""" - pass @abstractmethod def close(self): """Cleanly close communication with the stage""" - pass def scan_linear(self, rel_positions, backlash=True, return_to_start=True): """ diff --git a/openflexure_microscope/stage/mock.py b/openflexure_microscope/stage/mock.py index 58324081..9e9eb20f 100644 --- a/openflexure_microscope/stage/mock.py +++ b/openflexure_microscope/stage/mock.py @@ -9,7 +9,7 @@ from openflexure_microscope.utilities import axes_to_array class MissingStage(BaseStage): - def __init__(self, port=None, **kwargs): + def __init__(self, *args, **kwargs): # pylint: disable=unused-argument BaseStage.__init__(self) self._position = [0, 0, 0] self._n_axis = 3 @@ -66,11 +66,8 @@ class MissingStage(BaseStage): else: self._backlash = np.array([int(blsh)] * self.n_axes, dtype=np.int) - def move_rel( - self, displacement: list, axis=None, backlash=True, simulate_time: bool = True - ): - if simulate_time: - time.sleep(0.5) + def move_rel(self, displacement: list, axis=None, backlash=True): + time.sleep(0.5) if axis is not None: assert axis in self.axis_names, "axis must be one of {}".format( self.axis_names @@ -83,14 +80,13 @@ class MissingStage(BaseStage): self._position = list(np.array(self._position) + np.array(initial_move)) logging.debug(np.array(self._position) + np.array(initial_move)) - logging.debug(f"New position: {self._position}") + logging.debug("New position: %s", self._position) - def move_abs(self, final, simulate_time: bool = True, **kwargs): - if simulate_time: - time.sleep(0.5) + def move_abs(self, final, **kwargs): + time.sleep(0.5) self._position = list(final) - logging.debug(f"New position: {self._position}") + logging.debug("New position: %s", self._position) def zero_position(self): """Set the current position to zero""" diff --git a/openflexure_microscope/stage/sanga.py b/openflexure_microscope/stage/sanga.py index f4eb32ca..a122abba 100644 --- a/openflexure_microscope/stage/sanga.py +++ b/openflexure_microscope/stage/sanga.py @@ -29,10 +29,12 @@ class SangaStage(BaseStage): self.board = Sangaboard(port, **kwargs) self._backlash = ( - None - ) # Initialise backlash storage, used by property setter/getter + None # Initialise backlash storage, used by property setter/getter + ) self.axis_names = ["x", "y", "z"] # Assume all sangaboards are 3 axis + self._position_on_enter = None + @property def state(self): """The general state dictionary of the board.""" @@ -111,7 +113,7 @@ class SangaStage(BaseStage): backlash: (default: True) whether to correct for backlash. """ with self.lock: - logging.debug(f"Moving sangaboard by {displacement}") + logging.debug("Moving sangaboard by %s", displacement) if not backlash or self.backlash is None: return self.board.move_rel(displacement, axis=axis) if axis is not None: @@ -148,7 +150,7 @@ class SangaStage(BaseStage): """Make an absolute move to a position """ with self.lock: - logging.debug(f"Moving sangaboard to {final}") + logging.debug("Moving sangaboard to %s", final) self.board.move_abs(final, **kwargs) def zero_position(self): @@ -171,12 +173,12 @@ class SangaStage(BaseStage): self._position_on_enter = self.position return self - def __exit__(self, type, value, traceback): + def __exit__(self, type_, value, traceback): """The end of the with statement. Reset position if it went wrong. NB the instrument is closed when the object is deleted, so we don't need to worry about that here. """ - if type is not None: + if type_ is not None: print( "An exception occurred inside a with block, resetting position \ to its value at the start of the with block" @@ -184,7 +186,7 @@ class SangaStage(BaseStage): try: time.sleep(0.5) self.move_abs(self._position_on_enter) - except Exception as e: + except Exception as e: # pylint: disable=W0703 print( "A further exception occurred when resetting position: {}".format(e) ) diff --git a/openflexure_microscope/utilities.py b/openflexure_microscope/utilities.py index 7bd5f418..e0756303 100644 --- a/openflexure_microscope/utilities.py +++ b/openflexure_microscope/utilities.py @@ -2,7 +2,6 @@ import base64 import copy import logging import operator -import re import time from collections import abc from contextlib import contextmanager @@ -21,9 +20,9 @@ class Timer(object): def __enter__(self): self.start = time.time() - def __exit__(self, type, value, traceback): + def __exit__(self, type_, value, traceback): self.end = time.time() - logging.debug(f"{self.name} time: {self.end - self.start}") + logging.debug("%s time: %s", self.name, self.end - self.start) def deserialise_array_b64(b64_string, dtype, shape): diff --git a/pyproject.toml b/pyproject.toml index c0df4629..7af34710 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,3 +72,7 @@ force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true line_length = 88 + +[tool.pylint.'MESSAGES CONTROL'] +disable = "C0330, C0326, W1202, W0511, C, R" +max-line-length = 88 \ No newline at end of file