From 2936327de807fc5c385d96eef0e9002171b5899c Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 24 Jan 2020 10:06:32 +0000 Subject: [PATCH 01/70] Iterated version to beta 3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 65e4def9..fa4bb194 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "openflexure_microscope" -version = "2.0.0-beta.2" +version = "2.0.0-beta.3" description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope." authors = [ From 61c357d0f1b572a232b16454c2d18e126266b692 Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 24 Jan 2020 10:07:12 +0000 Subject: [PATCH 02/70] Updated lockfile --- poetry.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 894a2805..c1af1449 100644 --- a/poetry.lock +++ b/poetry.lock @@ -229,7 +229,7 @@ marshmallow = "^3.3.0" webargs = "^5.5.2" [package.source] -reference = "ea5142a9642e7b852a6e1175f3c4aa1a4bfac044" +reference = "23732b17074486367541439a1caa2eb61b9cc22f" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -561,8 +561,8 @@ category = "dev" description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" -version = "1.25.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.25.8" [package.extras] brotli = ["brotlipy (>=0.6.0)"] @@ -612,7 +612,7 @@ version = "1.11.2" rpi = ["picamera", "RPi.GPIO"] [metadata] -content-hash = "53c82daa835ef29f7aac8ac7d36048487a9d989662a58a2d05d9a70465fbc8c6" +content-hash = "cd4e5d59e9a63ae053a05c994a61a46be87a29ec6867bcd47f6ef9b39bb785d3" python-versions = "^3.6" [metadata.files] @@ -967,8 +967,8 @@ typed-ast = [ {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] urllib3 = [ - {file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"}, - {file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"}, + {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, + {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, ] webargs = [ {file = "webargs-5.5.2-py2-none-any.whl", hash = "sha256:3f9dc15de183d356c9a0acc159c100ea0506c0c240c1e6f1d8b308c5fed4dbbd"}, From dfbef9c354e7c8276ec9292972b9356b7bbd9f88 Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 24 Jan 2020 15:39:44 +0000 Subject: [PATCH 03/70] Fixed broken capture target --- openflexure_microscope/stage/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openflexure_microscope/stage/mock.py b/openflexure_microscope/stage/mock.py index fe2d2ac6..e85fa43b 100644 --- a/openflexure_microscope/stage/mock.py +++ b/openflexure_microscope/stage/mock.py @@ -23,7 +23,7 @@ class MockStage(BaseStage): "position": self.position_map, "board": None, "firmware": None, - "version": None + "version": None, } return status From e1d2e21bf6a344305d928204448578f14ab33618 Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 24 Jan 2020 15:41:14 +0000 Subject: [PATCH 04/70] Fixed paths (mutable files now all in /var/openflexure) --- openflexure_microscope/api/app.py | 2 -- openflexure_microscope/config.py | 2 +- openflexure_microscope/paths.py | 43 +++++++------------------------ 3 files changed, 11 insertions(+), 36 deletions(-) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index 6ca80e75..4b6fa948 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -16,7 +16,6 @@ from openflexure_microscope.api.utilities import list_routes, init_default_exten from openflexure_microscope.config import JSONEncoder from openflexure_microscope.paths import ( - OPENFLEXURE_ETC_PATH, OPENFLEXURE_VAR_PATH, OPENFLEXURE_EXTENSIONS_PATH, settings_file_path, @@ -60,7 +59,6 @@ else: # Log server paths being used logging.info(f"Running with data path {OPENFLEXURE_VAR_PATH}") -logging.info(f"Running with server path {OPENFLEXURE_ETC_PATH}") # Create flask app app, labthing = create_app( diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index d4b3aa3f..8e613574 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -7,7 +7,7 @@ from uuid import UUID import numpy as np from fractions import Fraction -from .paths import OPENFLEXURE_ETC_PATH, CONFIG_FILE_PATH, DEFAULT_CONFIG_FILE_PATH +from .paths import CONFIG_FILE_PATH, DEFAULT_CONFIG_FILE_PATH class OpenflexureSettingsFile: diff --git a/openflexure_microscope/paths.py b/openflexure_microscope/paths.py index 6650f5a5..4fbcfd53 100644 --- a/openflexure_microscope/paths.py +++ b/openflexure_microscope/paths.py @@ -10,12 +10,17 @@ def check_rw(path): def settings_file_path(filename: str): """Generate a full file path for a filename to be stored in server settings folder""" - return os.path.join(OPENFLEXURE_ETC_PATH, filename) + return os.path.join(OPENFLEXURE_VAR_PATH, "settings", filename) def data_file_path(filename: str): """Generate a full file path for a filename to be stored in server data folder""" - return os.path.join(OPENFLEXURE_VAR_PATH, filename) + return os.path.join(OPENFLEXURE_VAR_PATH, "data", filename) + + +def extensions_file_path(filename: str): + """Generate a full file path for a folder to be stored in server extensions""" + return os.path.join(OPENFLEXURE_VAR_PATH, "extensions", filename) # HANDLE DEFAULTS FILES STORED IN THIS APPLICATION @@ -26,7 +31,7 @@ HERE = os.path.abspath(os.path.dirname(__file__)) DEFAULT_CONFIG_FILE_PATH = os.path.join(HERE, "microscope_settings.default.json") -# DATA BASE PATHS +# BASE PATHS if os.name == "nt": PREFERRED_VAR_PATH = os.getenv("PROGRAMDATA") or "C:\\ProgramData" @@ -49,37 +54,9 @@ else: OPENFLEXURE_VAR_PATH = FALLBACK_OPENFLEXURE_VAR_PATH -# SERVER BASE PATHS - -if os.name == "nt": - PREFERRED_ETC_PATH = os.getenv("PROGRAMDATA") or "C:\\ProgramData" - FALLBACK_ETC_PATH = os.path.expanduser("~") -else: - PREFERRED_ETC_PATH = "/etc" - FALLBACK_ETC_PATH = os.path.join(os.path.expanduser("~"), ".config") - -PREFERRED_OPENFLEXURE_ETC_PATH = os.path.join(PREFERRED_ETC_PATH, "openflexure") -FALLBACK_OPENFLEXURE_ETC_PATH = os.path.join(FALLBACK_ETC_PATH, "openflexure") - -if not os.path.exists(PREFERRED_OPENFLEXURE_ETC_PATH) and check_rw(PREFERRED_ETC_PATH): - os.makedirs(PREFERRED_OPENFLEXURE_ETC_PATH) - -if check_rw(PREFERRED_OPENFLEXURE_ETC_PATH): - OPENFLEXURE_ETC_PATH = PREFERRED_OPENFLEXURE_ETC_PATH -else: - if not os.path.exists(FALLBACK_OPENFLEXURE_ETC_PATH): - os.makedirs(FALLBACK_OPENFLEXURE_ETC_PATH) - OPENFLEXURE_ETC_PATH = FALLBACK_OPENFLEXURE_ETC_PATH - - # SERVER PATHS #: Path of microscope settings directory -CONFIG_FILE_PATH = os.path.join(OPENFLEXURE_ETC_PATH, "microscope_settings.json") +CONFIG_FILE_PATH = settings_file_path("microscope_settings.json") #: Path of microscope extensions directory -OPENFLEXURE_EXTENSIONS_PATH = os.path.join( - OPENFLEXURE_ETC_PATH, "microscope_extensions" -) - - -# DATA PATHS +OPENFLEXURE_EXTENSIONS_PATH = extensions_file_path("microscope_extensions") From 386f85d0a1f4fc8518028bf33e76f5524c885b06 Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 24 Jan 2020 15:41:21 +0000 Subject: [PATCH 05/70] Fixed default target --- openflexure_microscope/camera/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openflexure_microscope/camera/mock.py b/openflexure_microscope/camera/mock.py index 4f614b1a..abffb221 100644 --- a/openflexure_microscope/camera/mock.py +++ b/openflexure_microscope/camera/mock.py @@ -192,7 +192,7 @@ class MockStreamer(BaseCamera): if isinstance(output, CaptureObject): target = output.file else: - target = target + target = output with self.lock: if isinstance(target, str): From ffc944fe993cdc93103cb60062334ed00a31ab0c Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 24 Jan 2020 15:41:27 +0000 Subject: [PATCH 06/70] Updated lockfile --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index c1af1449..603d03f8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -284,7 +284,7 @@ description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.0" +version = "20.1" [package.dependencies] pyparsing = ">=2.0.2" @@ -778,8 +778,8 @@ numpy = [ {file = "numpy-1.18.1.zip", hash = "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77"}, ] packaging = [ - {file = "packaging-20.0-py2.py3-none-any.whl", hash = "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb"}, - {file = "packaging-20.0.tar.gz", hash = "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8"}, + {file = "packaging-20.1-py2.py3-none-any.whl", hash = "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73"}, + {file = "packaging-20.1.tar.gz", hash = "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"}, ] picamera = [] pillow = [ From 6df7fe940524ce12165c2f876708fbba94db4b3e Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 24 Jan 2020 15:41:33 +0000 Subject: [PATCH 07/70] Blackened --- docs/source/conf.py | 2 +- .../api/default_extensions/__init__.py | 2 +- .../api/default_extensions/autofocus.py | 9 ++++----- .../picamera_autocalibrate/extension.py | 5 +---- openflexure_microscope/api/default_extensions/scan.py | 11 ++--------- .../api/default_extensions/zip_builder.py | 5 +---- .../api/example_extensions/ev_gui.py | 9 +++------ .../api/v2/views/actions/__init__.py | 2 +- openflexure_microscope/api/v2/views/actions/camera.py | 5 +---- openflexure_microscope/api/v2/views/actions/stage.py | 7 +------ openflexure_microscope/api/v2/views/actions/system.py | 5 +---- openflexure_microscope/api/v2/views/captures.py | 4 +--- openflexure_microscope/api/v2/views/state.py | 6 +----- openflexure_microscope/api/v2/views/streams.py | 6 +----- openflexure_microscope/microscope.py | 2 +- openflexure_microscope/stage/base.py | 6 +----- 16 files changed, 22 insertions(+), 64 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index ca5cecdf..cf5293a7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -226,7 +226,7 @@ intersphinx_mapping = { "openflexure_stage": ("https://openflexure-stage.readthedocs.io/en/latest/", None), "picamera": ("https://picamera.readthedocs.io/en/release-1.13/", None), "marshmallow": ("https://marshmallow.readthedocs.io/en/stable/", None), - "webargs": ("https://webargs.readthedocs.io/en/latest/", None) + "webargs": ("https://webargs.readthedocs.io/en/latest/", None), } # -- Options for todo extension ---------------------------------------------- diff --git a/openflexure_microscope/api/default_extensions/__init__.py b/openflexure_microscope/api/default_extensions/__init__.py index 6d9a592d..2b321f34 100644 --- a/openflexure_microscope/api/default_extensions/__init__.py +++ b/openflexure_microscope/api/default_extensions/__init__.py @@ -11,4 +11,4 @@ try: except Exception as e: logging.error( f"Exception loading builtin extension picamera_autocalibrate: \n{traceback.format_exc()}" - ) \ No newline at end of file + ) diff --git a/openflexure_microscope/api/default_extensions/autofocus.py b/openflexure_microscope/api/default_extensions/autofocus.py index 510272b7..ce873a7f 100644 --- a/openflexure_microscope/api/default_extensions/autofocus.py +++ b/openflexure_microscope/api/default_extensions/autofocus.py @@ -1,10 +1,7 @@ from labthings.server.find import find_component from labthings.server.extensions import BaseExtension from labthings.server.view import View -from labthings.server.decorators import ( - ThingAction, - ThingProperty, -) +from labthings.server.decorators import ThingAction, ThingProperty from openflexure_microscope.devel import JsonResponse, request, jsonify, taskify, abort from openflexure_microscope.utilities import set_properties @@ -359,7 +356,9 @@ class FastAutofocusAPI(View): abort(503, "No stage connected. Unable to autofocus.") -autofocus_extension_v2 = BaseExtension("org.openflexure.autofocus", version="2.0.0-beta.1") +autofocus_extension_v2 = BaseExtension( + "org.openflexure.autofocus", version="2.0.0-beta.1" +) autofocus_extension_v2.add_method(fast_autofocus, "fast_autofocus") autofocus_extension_v2.add_method(autofocus, "autofocus") diff --git a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py index 2ac16466..37cbe8cb 100644 --- a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py +++ b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py @@ -1,10 +1,7 @@ from labthings.server.view import View from labthings.server.find import find_component from labthings.server.extensions import BaseExtension -from labthings.server.decorators import ( - marshal_task, - ThingAction, -) +from labthings.server.decorators import marshal_task, ThingAction from labthings.core.tasks import taskify diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index de57511e..26a7dd6f 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -5,16 +5,9 @@ from typing import Tuple from functools import reduce from openflexure_microscope.camera.base import generate_basename -from labthings.server.find import ( - find_component, - find_extension, -) +from labthings.server.find import find_component, find_extension from labthings.server.extensions import BaseExtension -from labthings.server.decorators import ( - marshal_task, - use_args, - ThingAction, -) +from labthings.server.decorators import marshal_task, use_args, ThingAction from labthings.server import fields from openflexure_microscope.devel import taskify, abort, update_task_progress diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index 2b2514f6..02032c65 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -17,10 +17,7 @@ import logging from labthings.server.find import find_component from labthings.server.view import View from labthings.server.extensions import BaseExtension -from labthings.server.decorators import ( - ThingAction, - ThingProperty, -) +from labthings.server.decorators import ThingAction, ThingProperty class ZipManager: diff --git a/openflexure_microscope/api/example_extensions/ev_gui.py b/openflexure_microscope/api/example_extensions/ev_gui.py index 1acb3f9f..70089ccf 100644 --- a/openflexure_microscope/api/example_extensions/ev_gui.py +++ b/openflexure_microscope/api/example_extensions/ev_gui.py @@ -1,9 +1,6 @@ from labthings.server.view import View from labthings.server.extensions import BaseExtension -from labthings.server.decorators import ( - ThingAction, - ThingProperty, -) +from labthings.server.decorators import ThingAction, ThingProperty from openflexure_microscope.api.utilities.gui import build_gui @@ -35,7 +32,7 @@ def dynamic_form(): "name": "val_int", "label": "Number value", "minValue": 0, - "value": val_int + "value": val_int, }, { "fieldType": "htmlBlock", @@ -66,7 +63,7 @@ static_form = { "name": "val_int", "label": "Number value", "minValue": 0, - "default": 1 + "default": 1, }, { "fieldType": "htmlBlock", diff --git a/openflexure_microscope/api/v2/views/actions/__init__.py b/openflexure_microscope/api/v2/views/actions/__init__.py index b256c60b..aa4a2ae9 100644 --- a/openflexure_microscope/api/v2/views/actions/__init__.py +++ b/openflexure_microscope/api/v2/views/actions/__init__.py @@ -76,7 +76,7 @@ class ActionsView(View): "self": { "href": current_labthing().url_for(action["view_class"]), "mimetype": "application/json", - **description_from_view(action["view_class"]) + **description_from_view(action["view_class"]), } }, "rule": action["rule"], diff --git a/openflexure_microscope/api/v2/views/actions/camera.py b/openflexure_microscope/api/v2/views/actions/camera.py index ec4445e2..e9912ad9 100644 --- a/openflexure_microscope/api/v2/views/actions/camera.py +++ b/openflexure_microscope/api/v2/views/actions/camera.py @@ -132,10 +132,7 @@ class RAMCaptureAPI(View): stream.seek(0) - return send_file( - io.BytesIO(stream.getbuffer()), - mimetype="image/jpeg" - ) + return send_file(io.BytesIO(stream.getbuffer()), mimetype="image/jpeg") @ThingAction diff --git a/openflexure_microscope/api/v2/views/actions/stage.py b/openflexure_microscope/api/v2/views/actions/stage.py index 446e719e..8b1020eb 100644 --- a/openflexure_microscope/api/v2/views/actions/stage.py +++ b/openflexure_microscope/api/v2/views/actions/stage.py @@ -1,12 +1,7 @@ from openflexure_microscope.api.utilities import JsonResponse from labthings.server.view import View from labthings.server.find import find_component -from labthings.server.decorators import ( - use_args, - marshal_with, - doc, - ThingAction, -) +from labthings.server.decorators import use_args, marshal_with, doc, ThingAction from labthings.server import fields from openflexure_microscope.utilities import axes_to_array, filter_dict diff --git a/openflexure_microscope/api/v2/views/actions/system.py b/openflexure_microscope/api/v2/views/actions/system.py index 43cf0ead..631c5c79 100644 --- a/openflexure_microscope/api/v2/views/actions/system.py +++ b/openflexure_microscope/api/v2/views/actions/system.py @@ -3,10 +3,7 @@ import subprocess import os from sys import platform -from labthings.server.decorators import ( - ThingAction, - doc_response, -) +from labthings.server.decorators import ThingAction, doc_response def is_raspberrypi(raise_on_errors=False): diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index e97e8765..f4831aa3 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -6,9 +6,7 @@ from openflexure_microscope.api.utilities import get_bool, JsonResponse from labthings.server.schema import Schema from labthings.server import fields from labthings.server.view import View -from labthings.server.utilities import ( - description_from_view, -) +from labthings.server.utilities import description_from_view from labthings.server.decorators import marshal_with, doc_response, Tag, ThingProperty from labthings.server.find import find_component diff --git a/openflexure_microscope/api/v2/views/state.py b/openflexure_microscope/api/v2/views/state.py index 6aa7f958..aa59ffdf 100644 --- a/openflexure_microscope/api/v2/views/state.py +++ b/openflexure_microscope/api/v2/views/state.py @@ -1,10 +1,6 @@ from openflexure_microscope.api.utilities import JsonResponse -from labthings.core.utilities import ( - get_by_path, - set_by_path, - create_from_path, -) +from labthings.core.utilities import get_by_path, set_by_path, create_from_path from labthings.server.find import find_component from labthings.server.view import View diff --git a/openflexure_microscope/api/v2/views/streams.py b/openflexure_microscope/api/v2/views/streams.py index 3061af62..4382e2ab 100644 --- a/openflexure_microscope/api/v2/views/streams.py +++ b/openflexure_microscope/api/v2/views/streams.py @@ -1,10 +1,6 @@ from openflexure_microscope.api.utilities import gen, JsonResponse -from labthings.core.utilities import ( - get_by_path, - set_by_path, - create_from_path, -) +from labthings.core.utilities import get_by_path, set_by_path, create_from_path from labthings.server.find import find_component from labthings.server.view import View diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index dabf7033..31ea505c 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -28,7 +28,7 @@ class Microscope: # Initial attributes self.id = uuid.uuid4() #: Microscope UUID self.name = self.id #: Microscope name (modifiable) - self.fov = [0, 0] #: Microscope field-of-view in stage motor steps + self.fov = [0, 0] #: Microscope field-of-view in stage motor steps self.camera = None #: Currently connected camera object self.stage = None #: Currently connected stage object diff --git a/openflexure_microscope/stage/base.py b/openflexure_microscope/stage/base.py index 5821cff2..d5ef31f3 100644 --- a/openflexure_microscope/stage/base.py +++ b/openflexure_microscope/stage/base.py @@ -51,11 +51,7 @@ class BaseStage(metaclass=ABCMeta): @property def position_map(self): - return { - "x": self.position[0], - "y": self.position[1], - "z": self.position[2], - } + return {"x": self.position[0], "y": self.position[1], "z": self.position[2]} @property @abstractmethod From b39a0653f27ea06a3415d6dc6c1f94cdb3fcc9be Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 24 Jan 2020 18:08:07 +0000 Subject: [PATCH 08/70] Moved logs into openflexure logs folder --- openflexure_microscope/api/app.py | 3 ++- openflexure_microscope/paths.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index 4b6fa948..7f688fe0 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -19,6 +19,7 @@ from openflexure_microscope.paths import ( OPENFLEXURE_VAR_PATH, OPENFLEXURE_EXTENSIONS_PATH, settings_file_path, + logs_file_path ) from labthings.server.quick import create_app @@ -31,7 +32,7 @@ from openflexure_microscope.api.v2 import views # Handle logging is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "") -DEFAULT_LOGFILE = settings_file_path("openflexure_microscope.log") +DEFAULT_LOGFILE = logs_file_path("openflexure_microscope.log") logger = logging.getLogger() if (__name__ == "__main__") or (not is_gunicorn): diff --git a/openflexure_microscope/paths.py b/openflexure_microscope/paths.py index 4fbcfd53..a261d618 100644 --- a/openflexure_microscope/paths.py +++ b/openflexure_microscope/paths.py @@ -23,6 +23,10 @@ def extensions_file_path(filename: str): return os.path.join(OPENFLEXURE_VAR_PATH, "extensions", filename) +def logs_file_path(filename: str): + """Generate a full file path for a filename to be stored in server logs""" + return os.path.join(OPENFLEXURE_VAR_PATH, "logs", filename) + # HANDLE DEFAULTS FILES STORED IN THIS APPLICATION HERE = os.path.abspath(os.path.dirname(__file__)) From 19af98736d80731dd7e010fbf5ca3abf4e2e4771 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 11:55:39 +0000 Subject: [PATCH 09/70] Draft of new semantics --- openflexure_microscope/api/microscope.py | 29 --- openflexure_microscope/api/v2/views/state.py | 4 +- openflexure_microscope/camera/base.py | 35 +++- openflexure_microscope/camera/mock.py | 22 ++- openflexure_microscope/camera/pi.py | 56 ++++-- openflexure_microscope/config.py | 35 ++-- openflexure_microscope/microscope.py | 176 ++++++++++-------- .../microscope_configuration.default.json | 17 ++ .../microscope_settings.default.json | 6 +- openflexure_microscope/paths.py | 11 +- openflexure_microscope/stage/base.py | 16 +- openflexure_microscope/stage/mock.py | 18 +- openflexure_microscope/stage/sanga.py | 14 +- 13 files changed, 252 insertions(+), 187 deletions(-) create mode 100644 openflexure_microscope/microscope_configuration.default.json diff --git a/openflexure_microscope/api/microscope.py b/openflexure_microscope/api/microscope.py index 04053892..2f534b49 100644 --- a/openflexure_microscope/api/microscope.py +++ b/openflexure_microscope/api/microscope.py @@ -3,38 +3,9 @@ from openflexure_microscope.camera.capture import build_captures_from_exif import logging -# Import device modules -# NB this will eventually be handled by the RC file, so you can choose what device -# class should be attached. -try: - from openflexure_microscope.camera.pi import PiCameraStreamer -except ImportError: - logging.warning("Unable to import PiCameraStreamer") -from openflexure_microscope.camera.mock import MockStreamer - -from openflexure_microscope.stage.sanga import SangaStage -from openflexure_microscope.stage.mock import MockStage default_microscope = Microscope() -# Initialise camera -logging.debug("Creating camera object...") -try: - api_camera = PiCameraStreamer() -except Exception as e: - logging.error(e) - logging.warning("No valid camera hardware found. Falling back to mock camera!") - api_camera = MockStreamer() - -# Initialise stage -logging.debug("Creating stage object...") - -api_stage = MockStage() - -# Attach devices to microscope -logging.debug("Attaching devices to microscope...") -default_microscope.attach(api_camera, api_stage) - # Restore loaded capture array to camera object logging.debug("Restoring captures...") default_microscope.camera.images = build_captures_from_exif( diff --git a/openflexure_microscope/api/v2/views/state.py b/openflexure_microscope/api/v2/views/state.py index aa59ffdf..34637754 100644 --- a/openflexure_microscope/api/v2/views/state.py +++ b/openflexure_microscope/api/v2/views/state.py @@ -30,7 +30,7 @@ class SettingsProperty(View): logging.debug("Updating settings from PUT request:") logging.debug(payload.json) - microscope.apply_settings(payload.json) + microscope.update_settings(payload.json) microscope.save_settings() return self.get() @@ -65,7 +65,7 @@ class NestedSettingsProperty(View): dictionary = create_from_path(keys) set_by_path(dictionary, keys, payload.json) - microscope.apply_settings(dictionary) + microscope.update_settings(dictionary) microscope.save_settings() return self.get(route) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index e6395e1f..e823cfd0 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -121,7 +121,8 @@ class BaseCamera(metaclass=ABCMeta): self.stream_timeout = 20 self.stream_timeout_enabled = False - self.status = {"board": None} + self.stream_active = False + self.record_active = False self.paths = {"default": BASE_CAPTURE_PATH, "temp": TEMP_CAPTURE_PATH} @@ -129,8 +130,24 @@ class BaseCamera(metaclass=ABCMeta): self.images = [] self.videos = [] + @property @abstractmethod - def apply_settings(self, config: dict): + def configuration(self): + """The current camera configuration.""" + pass + + @property + @abstractmethod + def state(self): + """The current read-only camera state.""" + pass + + @property + def settings(self): + return self.read_settings() + + @abstractmethod + def update_settings(self, config: dict): """Update settings from a config dictionary""" with self.lock: # Apply valid config params to camera object @@ -187,7 +204,7 @@ class BaseCamera(metaclass=ABCMeta): self.last_access = time.time() self.stop = False - if not self.status["stream_active"]: + if not self.stream_active: # start background frame thread self.thread = threading.Thread(target=self._thread) self.thread.daemon = True @@ -207,12 +224,12 @@ class BaseCamera(metaclass=ABCMeta): logging.debug("Stopping worker thread") timeout_time = time.time() + timeout - if self.status["stream_active"]: + if self.stream_active: self.stop = True self.thread.join() # Wait for stream thread to exit logging.debug("Waiting for stream thread to exit.") - while self.status["stream_active"]: + while self.stream_active: if time.time() > timeout_time: logging.debug("Timeout waiting for worker thread close.") raise TimeoutError("Timeout waiting for worker thread close.") @@ -352,7 +369,7 @@ class BaseCamera(metaclass=ABCMeta): self.frames_iterator = self.frames() logging.debug("Entering worker thread.") - self.status["stream_active"] = True + self.stream_active = True for frame in self.frames_iterator: self.frame = frame @@ -365,9 +382,7 @@ class BaseCamera(metaclass=ABCMeta): and ( # If using timeout time.time() - self.last_access > self.stream_timeout ) - and not self.status[ # And timeout time - "preview_active" - ] # And GPU preview is not active + and not self.preview_active # And GPU preview is not active ): self.frames_iterator.close() break @@ -383,4 +398,4 @@ class BaseCamera(metaclass=ABCMeta): logging.debug("BaseCamera worker thread exiting...") # Set stream_activate state - self.status["stream_active"] = False + self.stream_active = False diff --git a/openflexure_microscope/camera/mock.py b/openflexure_microscope/camera/mock.py index abffb221..322bae58 100644 --- a/openflexure_microscope/camera/mock.py +++ b/openflexure_microscope/camera/mock.py @@ -26,17 +26,13 @@ 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) + # MAIN CLASS -class MockStreamer(BaseCamera): +class MissingCamera(BaseCamera): def __init__(self): # Run BaseCamera init BaseCamera.__init__(self) - # Store state of PiCameraStreamer - self.status.update( - {"stream_active": False, "record_active": False, "board": None} - ) - # Update config properties self.image_resolution = (1312, 976) self.stream_resolution = (640, 480) @@ -71,6 +67,16 @@ class MockStreamer(BaseCamera): image.save(self.stream, format="JPEG") + @property + def configuration(self): + """The current camera configuration.""" + return {} + + @property + def state(self): + """The current read-only camera state.""" + return {} + def initialisation(self): """Run any initialisation code when the frame iterator starts.""" pass @@ -101,7 +107,7 @@ class MockStreamer(BaseCamera): return conf_dict - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """ Write a config dictionary to the PiCameraStreamer config. @@ -117,7 +123,7 @@ class MockStreamer(BaseCamera): with self.lock: # Apply valid config params to camera object - if not self.status["record_active"]: # If not recording a video + if not self.record_active: # If not recording a video for key, value in config.items(): # For each provided setting if hasattr(self, key): diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 5cafb045..bbd04e44 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -47,6 +47,7 @@ from .base import BaseCamera, CaptureObject from .set_picamera_gain import set_analog_gain, set_digital_gain from openflexure_microscope.paths import settings_file_path +from openflexure_microscope.utilities import serialise_array_b64 # MAIN CLASS @@ -85,14 +86,7 @@ class PiCameraStreamer(BaseCamera): ) #: :py:class:`picamera.PiCamera`: Picamera object # Store status of PiCameraStreamer - self.status.update( - { - "stream_active": False, - "record_active": False, - "preview_active": False, - "board": f"picamera_{self.camera.revision}", - } - ) + self.preview_active = False # Reset variable states self.set_zoom(1.0) @@ -116,15 +110,37 @@ class PiCameraStreamer(BaseCamera): "picamera_lst.npy" ) #: str: Path of .npy lens shading table file - # Update board identifier - self.status.update({}) - # Create an empty stream self.stream = io.BytesIO() # Start streaming self.start_worker() + @property + def configuration(self): + """The current camera configuration.""" + config = { + "board": self.camera.revision, + } + + if self.read_lens_shading_table(): + b64_string, dtype, shape = serialise_array_b64(self.read_lens_shading_table()) + + config.update({ + "lens_shading_table": { + "b64_string": b64_string, + "dtype": dtype, + "shape": shape, + } + }) + + return config + + @property + def state(self): + """The current read-only camera state.""" + return {} + def initialisation(self): """Run any initialisation code when the frame iterator starts.""" pass @@ -176,7 +192,7 @@ class PiCameraStreamer(BaseCamera): logging.info("Saving picamera_lst to {}".format(self.picamera_lst_path)) self.save_lens_shading_table() - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """ Write a config dictionary to the PiCameraStreamer config. @@ -194,10 +210,10 @@ class PiCameraStreamer(BaseCamera): with self.lock: # Apply valid config params to Picamera object - if not self.status["record_active"]: # If not recording a video + if not self.record_active: # If not recording a video # Pause stream while changing settings - if self.status["stream_active"]: # If stream is active + if self.stream_active: # If stream is active logging.info("Pausing stream to update config.") self.stop_stream_recording() # Pause stream paused_stream = True # Remember to unpause stream when done @@ -365,7 +381,7 @@ class PiCameraStreamer(BaseCamera): self.camera.preview.window = window if fullscreen: self.camera.preview.fullscreen = fullscreen - self.status["preview_active"] = True + self.preview_active = True except picamera.exc.PiCameraMMALError as e: logging.error( "Suppressed a MMALError in start_preview. Exception: {}".format(e) @@ -380,7 +396,7 @@ class PiCameraStreamer(BaseCamera): def stop_preview(self): """Stop the on board GPU camera preview.""" self.camera.stop_preview() - self.status["preview_active"] = False + self.preview_active = False def start_recording(self, output, fmt: str = "h264", quality: int = 15): """Start recording. @@ -398,7 +414,7 @@ class PiCameraStreamer(BaseCamera): """ with self.lock: # Start recording method only if a current recording is not running - if not self.status["record_active"]: + if not self.record_active: # Start the camera video recording on port 2 logging.info("Recording to {}".format(output)) @@ -412,7 +428,7 @@ class PiCameraStreamer(BaseCamera): ) # Update status dictionary - self.status["record_active"] = True + self.record_active = True return output @@ -432,7 +448,7 @@ class PiCameraStreamer(BaseCamera): logging.info("Recording stopped") # Update status dictionary - self.status["record_active"] = False + self.record_active = False def stop_stream_recording( self, splitter_port: int = 1, resolution: Tuple[int, int] = None @@ -500,7 +516,7 @@ class PiCameraStreamer(BaseCamera): self.camera.resolution = resolution # If the stream should be active - if self.status["stream_active"]: + if self.stream_active: try: # Start recording on stream port self.camera.start_recording( diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index 8e613574..cfc458a1 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -7,7 +7,7 @@ from uuid import UUID import numpy as np from fractions import Fraction -from .paths import CONFIG_FILE_PATH, DEFAULT_CONFIG_FILE_PATH +from .paths import SETTINGS_FILE_PATH, DEFAULT_SETTINGS_FILE_PATH, CONFIGURATION_FILE_PATH, DEFAULT_CONFIGURATION_FILE_PATH class OpenflexureSettingsFile: @@ -19,21 +19,21 @@ class OpenflexureSettingsFile: expand (bool): Expand paths to valid auxillary config files. """ - def __init__(self, config_path: str = None): - global DEFAULT_CONFIG, CONFIG_FILE_PATH + def __init__(self, path: str, defaults: dict = {}): + global DEFAULT_SETTINGS # Set arguments - self.config_path = config_path or CONFIG_FILE_PATH + self.path = path # Initialise basic config file with defaults if it doesn't exist - initialise_file(self.config_path, populate=DEFAULT_CONFIG) + initialise_file(self.path, populate=defaults) def load(self) -> dict: """ Loads settings from a file on-disk. """ # Unexpanded config dictionary (used at load/save time) - loaded_config = load_json_file(self.config_path) + loaded_config = load_json_file(self.path) logging.debug("Reading settings from disk") return loaded_config @@ -50,11 +50,11 @@ class OpenflexureSettingsFile: save_settings = config if backup: - if os.path.isfile(self.config_path): - shutil.copyfile(self.config_path, self.config_path + ".bk") + if os.path.isfile(self.path): + shutil.copyfile(self.path, self.path + ".bk") logging.debug("Saving settings dictionary to disk") - save_json_file(self.config_path, save_settings) + save_json_file(self.path, save_settings) def merge(self, config: dict) -> dict: """ @@ -178,10 +178,17 @@ def initialise_file(config_path, populate: str = "{}\n"): outfile.write(populate) -# Load the default config -with open(DEFAULT_CONFIG_FILE_PATH, "r") as default_rc: - DEFAULT_CONFIG = default_rc.read() - +# Load the default settings +with open(DEFAULT_SETTINGS_FILE_PATH, "r") as default_settings: + DEFAULT_SETTINGS = default_settings.read() #: Default user settings object -user_settings = OpenflexureSettingsFile(config_path=CONFIG_FILE_PATH) +user_settings = OpenflexureSettingsFile(path=SETTINGS_FILE_PATH, defaults=DEFAULT_SETTINGS) + + +# Load the default configuration +with open(DEFAULT_CONFIGURATION_FILE_PATH, "r") as default_configuration: + DEFAULT_CONFIGURATION = default_configuration.read() + +#: Default user settings object +user_configuration = OpenflexureSettingsFile(path=CONFIGURATION_FILE_PATH, defaults=DEFAULT_CONFIGURATION) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 31ea505c..2f52c6d9 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -6,15 +6,20 @@ import logging import pkg_resources import uuid -from openflexure_microscope.stage.base import BaseStage -from openflexure_microscope.stage.mock import MockStage -from openflexure_microscope.camera.base import BaseCamera -from openflexure_microscope.camera.mock import MockStreamer +from openflexure_microscope.stage.mock import MissingStage +from openflexure_microscope.camera.mock import MissingCamera +from openflexure_microscope.stage.sanga import SangaStage +try: + from openflexure_microscope.camera.pi import PiCameraStreamer +except ImportError: + logging.warning("Unable to import PiCameraStreamer") +from openflexure_microscope.camera.mock import MissingCamera from openflexure_microscope.utilities import serialise_array_b64 -from openflexure_microscope.config import user_settings +from openflexure_microscope.config import user_settings, user_configuration from labthings.core.lock import CompositeLock +from labthings.core.utilities import rupdate class Microscope: @@ -24,20 +29,32 @@ class Microscope: The camera and stage objects may already be initialised, and can be passed as arguments. """ - def __init__(self): - # Initial attributes - self.id = uuid.uuid4() #: Microscope UUID - self.name = self.id #: Microscope name (modifiable) - self.fov = [0, 0] #: Microscope field-of-view in stage motor steps - self.camera = None #: Currently connected camera object - self.stage = None #: Currently connected stage object + def __init__(self, settings = user_settings, configuration = user_configuration): + # Store settings and configuration files + self.settings_file = settings + self.configuration_file = configuration # Initialise with an empty composite lock #: :py:class:`labthings.lock.CompositeLock`: Composite lock for locking both camera and stage self.lock = CompositeLock([]) + self.camera = None #: Currently connected camera object + self.stage = None #: Currently connected stage object + self.setup(self.configuration_file.load()) # Attach components + # Apply settings loaded from file - self.apply_settings(user_settings.load()) + self.update_settings(self.settings_file.load()) + + # Initial attributes + if self.configuration_file.load().get("id"): + self.id = configuration.get("id") + else: + self.id = uuid.uuid4() + self.configuration_file.save({ + "id": self.id + }) + + self.name = self.id def __enter__(self): """Create microscope on context enter.""" @@ -56,58 +73,49 @@ class Microscope: self.stage.close() logging.info("Closed {}".format(self)) - def attach(self, camera: BaseCamera, stage: BaseStage): + def setup(self, configuration): """ - Retroactively attaches a camera and stage to the microscope object. - - Allows the microscope to be created as a "dummy", with hardware communications - opened at a later time. - - Args: - camera (:py:class:`openflexure_microscope.camera.base.BaseCamera`): camera object - stage (:py:class:`openflexure_microscope.stage.base.BaseStage`): stage object + Attach microscope components based on initially passed configuration file """ - settings_full = self.read_settings() + ### Detector + if configuration.get("detector"): + detector_type = configuration["detector"].get("type") + if detector_type == "PiCamera" or detector_type == "PiCameraStreamer": + try: + self.camera = PiCameraStreamer() + except Exception as e: + logging.error(e) + logging.warning("No compatible camera hardware found.") - logging.debug("Attaching camera...") - self.camera = ( - camera - ) #: :py:class:`openflexure_microscope.camera.base.BaseCamera`: Picamera object + ### Stage + if configuration.get("stage"): + stage_type = configuration["stage"].get("type") + stage_port = configuration["stage"].get("port") + if stage_type == "SangaBoard" or detector_type == "SangaStage": + try: + self.stage = SangaStage(port=stage_port) + except Exception as e: + logging.error(e) + logging.warning("No compatible stage hardware found.") + + ### Fallbacks if not self.camera: - logging.info("No camera attached.") - else: - logging.info("Attached camera {}".format(camera)) - - if hasattr(self.camera, "lock"): # If camera has a lock - logging.info("Attaching {} to composite lock.".format(self.camera.lock)) - # Add the lock to the microscope composite lock - self.lock.locks.append(self.camera.lock) - - logging.debug("Attaching stage...") - self.stage = ( - stage - ) #: :py:class:`openflexure_microscope.stage.base.BaseStage`: OpenFlexure stage object + self.camera = MissingCamera() if not self.stage: - logging.info("No stage attached.") - else: - logging.info("Attached stage {}".format(stage)) + self.stage = MissingStage() - if hasattr(self.stage, "lock"): # If stage object has a lock - logging.info( - "Attaching lock {} to composite lock.".format(self.stage.lock) - ) - # Add the lock to the microscope composite lock - self.lock.locks.append(self.stage.lock) - - logging.info("Reapplying settings to newly attached devices") - self.apply_settings(settings_full) + ### Locks + if hasattr(self.camera, "lock"): + self.lock.locks.append(self.camera.lock) + if hasattr(self.stage, "lock"): + self.lock.locks.append(self.stage.lock) def has_real_stage(self) -> bool: """ Check if a real (non-mock) stage is currently attached. """ - if hasattr(self, "stage") and not isinstance(self.stage, MockStage): + if hasattr(self, "stage") and not isinstance(self.stage, MissingStage): return True else: return False @@ -116,27 +124,26 @@ class Microscope: """ Check if a real (non-mock) camera is currently attached. """ - if hasattr(self, "camera") and not isinstance(self.camera, MockStreamer): + if hasattr(self, "camera") and not isinstance(self.camera, MissingCamera): return True else: return False # Create unified status @property - def status(self): + def state(self): """Dictionary of the basic microscope status. Return: dict: Dictionary containing complete microscope status """ state = { - "camera": self.camera.status, - "stage": self.stage.status, - "version": pkg_resources.get_distribution("openflexure_microscope").version, + "camera": self.camera.state, + "stage": self.stage.state, } return state - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """ Applies a settings dictionary to the microscope. Missing parameters will be left untouched. """ @@ -144,21 +151,19 @@ class Microscope: # If attached to a camera if ("camera_settings" in config) and self.camera: - self.camera.apply_settings(config["camera_settings"]) + self.camera.update_settings(config["camera_settings"]) # If attached to a stage if ("stage_settings" in config) and self.stage: - self.stage.apply_settings(config["stage_settings"]) + self.stage.update_settings(config["stage_settings"]) # Todo: tidy up with some loopy goodness - if "id" in config: - self.id = config["id"] if "name" in config: self.name = config["name"] if "fov" in config: self.fov = config["fov"] - def read_settings(self, json_safe=False): + def read_settings(self, full: bool=True): """ Get an updated settings dictionary. @@ -169,7 +174,7 @@ class Microscope: don't get removed from the settings file. """ - settings_current = {"id": self.id, "name": self.name, "fov": self.fov} + settings_current = {"name": self.name, "fov": self.fov} # If attached to a camera if self.camera: @@ -181,9 +186,12 @@ class Microscope: settings_current_stage = self.stage.read_settings() settings_current["stage_settings"] = settings_current_stage - settings_full = user_settings.merge(settings_current) + settings_full = self.settings_file.merge(settings_current) - return settings_full + if full: + return settings_full + else: + return settings_current def save_settings(self): """ @@ -196,7 +204,7 @@ class Microscope: self.camera.save_settings() if self.stage: self.stage.save_settings() - user_settings.save(current_config, backup=True) + self.settings_file.save(current_config, backup=True) @property def metadata(self): @@ -204,10 +212,10 @@ class Microscope: Microscope system metadata, to be applied to basically all captures """ system_metadata = { - "microscope_settings": self.read_settings(), - "microscope_state": self.status, - "microscope_id": self.id, - "microscope_name": self.name, + "@ID": self.id, + "settings": self.read_settings(full=False), + "state": self.state, + "configuration": self.configuration } # Store an encoded copy of the PiCamera lens shading table, if it exists @@ -217,10 +225,32 @@ class Microscope: b64_string, dtype, shape = serialise_array_b64(lst_arr) - system_metadata["lens_shading_table"] = { + system_metadata["configuration"]["detector"]["lens_shading_table"] = { "b64_string": b64_string, "dtype": dtype, "shape": shape, } return system_metadata + + @property + def configuration(self): + initial_configuration = self.configuration_file.load() + + current_configuration = { + "@application": { + "name": "openflexure_microscope", + "version": pkg_resources.get_distribution("openflexure_microscope").version + }, + "stage": { + "type": self.stage.__class__.__name__, + **self.stage.configuration + }, + "detector": { + "type": self.camera.__class__.__name__, + **self.camera.configuration + } + } + + initial_configuration.update(current_configuration) + return initial_configuration diff --git a/openflexure_microscope/microscope_configuration.default.json b/openflexure_microscope/microscope_configuration.default.json new file mode 100644 index 00000000..d7129bc8 --- /dev/null +++ b/openflexure_microscope/microscope_configuration.default.json @@ -0,0 +1,17 @@ +{ + "microscope": { + "stepsPerView": [4100, 3146] + }, + "detector": { + "type": "PiCamera" + }, + "stage": { + "type": "SangaStage", + "port": null + }, + "lightSource": { + "type": "LED" + }, + "objective": { + } +} \ No newline at end of file diff --git a/openflexure_microscope/microscope_settings.default.json b/openflexure_microscope/microscope_settings.default.json index bde62f47..fa0fcb28 100644 --- a/openflexure_microscope/microscope_settings.default.json +++ b/openflexure_microscope/microscope_settings.default.json @@ -1,7 +1,3 @@ { - "fov": [ - 4100, - 3146 - ], - "jpeg_quality": 75 + "jpeg_quality": 100 } \ No newline at end of file diff --git a/openflexure_microscope/paths.py b/openflexure_microscope/paths.py index a261d618..8fbf9b55 100644 --- a/openflexure_microscope/paths.py +++ b/openflexure_microscope/paths.py @@ -32,8 +32,9 @@ def logs_file_path(filename: str): HERE = os.path.abspath(os.path.dirname(__file__)) #: Path of default (first-run) microscope settings -DEFAULT_CONFIG_FILE_PATH = os.path.join(HERE, "microscope_settings.default.json") - +DEFAULT_SETTINGS_FILE_PATH = os.path.join(HERE, "microscope_settings.default.json") +#: Path of default (first-run) microscope configuration +DEFAULT_CONFIGURATION_FILE_PATH = os.path.join(HERE, "microscope_configuration.default.json") # BASE PATHS @@ -60,7 +61,9 @@ else: # SERVER PATHS -#: Path of microscope settings directory -CONFIG_FILE_PATH = settings_file_path("microscope_settings.json") +#: Path of microscope settings file +SETTINGS_FILE_PATH = settings_file_path("microscope_settings.json") +#: Path of microscope configuration file +CONFIGURATION_FILE_PATH = settings_file_path("microscope_configuration.json") #: Path of microscope extensions directory OPENFLEXURE_EXTENSIONS_PATH = extensions_file_path("microscope_extensions") diff --git a/openflexure_microscope/stage/base.py b/openflexure_microscope/stage/base.py index d5ef31f3..564ba1a6 100644 --- a/openflexure_microscope/stage/base.py +++ b/openflexure_microscope/stage/base.py @@ -14,7 +14,7 @@ class BaseStage(metaclass=ABCMeta): self.lock = StrictLock(timeout=5) @abstractmethod - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """Update settings from a config dictionary""" pass @@ -29,12 +29,14 @@ class BaseStage(metaclass=ABCMeta): @property @abstractmethod - def status(self): - """The general state dictionary of the board. - Should at least contain 'position', and 'board' keys. - Note: A None/Null value for 'board' will disable stage - movement in the OpenFlexure eV client software, - """ + def state(self): + """The general state dictionary of the board.""" + pass + + @property + @abstractmethod + def configuration(self): + """The general stage configuration.""" pass @property diff --git a/openflexure_microscope/stage/mock.py b/openflexure_microscope/stage/mock.py index e85fa43b..e4810f3c 100644 --- a/openflexure_microscope/stage/mock.py +++ b/openflexure_microscope/stage/mock.py @@ -7,7 +7,7 @@ import time import logging -class MockStage(BaseStage): +class MissingStage(BaseStage): def __init__(self, port=None, **kwargs): BaseStage.__init__(self) self._position = [0, 0, 0] @@ -17,19 +17,17 @@ class MockStage(BaseStage): self.axis_names = ["x", "y", "z"] # Assume all sangaboards are 3 axis @property - def status(self): + def state(self): """The general status dictionary of the board.""" - status = { - "position": self.position_map, - "board": None, - "firmware": None, - "version": None, - } + status = {"position": self.position_map} return status - def apply_settings(self, config: dict): - """Update settings from a config dictionary""" + @property + def configuration(self): + return {} + def update_settings(self, config: dict): + """Update settings from a config dictionary""" # Set backlash. Expects a dictionary with axis labels if "backlash" in config: # Construct backlash array diff --git a/openflexure_microscope/stage/sanga.py b/openflexure_microscope/stage/sanga.py index 2c4fa6d9..11ffbbc6 100644 --- a/openflexure_microscope/stage/sanga.py +++ b/openflexure_microscope/stage/sanga.py @@ -23,6 +23,7 @@ class SangaStage(BaseStage): """Class managing serial communications with the motors for an Openflexure stage""" BaseStage.__init__(self) + self.port = port self.board = Sangaboard(port, **kwargs) self._backlash = ( @@ -31,14 +32,17 @@ class SangaStage(BaseStage): self.axis_names = ["x", "y", "z"] # Assume all sangaboards are 3 axis @property - def status(self): + def state(self): """The general status dictionary of the board.""" - status = { - "position": self.position_map, + return {"position": self.position_map} + + @property + def configuration(self): + return { + "port": self.port, "board": self.board.board, "firmware": self.board.firmware, } - return status @property def n_axes(self): @@ -81,7 +85,7 @@ class SangaStage(BaseStage): else: self._backlash = np.array([int(blsh)] * self.n_axes, dtype=np.int) - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """Update settings from a config dictionary""" # Set backlash. Expects a dictionary with axis labels From 5d65c62001ce7cd4717f5982f9aa9838d108d86a Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 15:36:46 +0000 Subject: [PATCH 10/70] Updated dependencies --- poetry.lock | 537 ++++++++----------------------------------------- pyproject.toml | 1 + 2 files changed, 82 insertions(+), 456 deletions(-) diff --git a/poetry.lock b/poetry.lock index 603d03f8..923babf7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,14 +14,6 @@ optional = false python-versions = ">=3.5" version = "3.2.0" -[package.extras] -dev = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"] -docs = ["marshmallow (>=2.19.2)", "pyyaml (5.2)", "sphinx (2.3.0)", "sphinx-issues (1.2.0)", "sphinx-rtd-theme (0.4.3)"] -lint = ["flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"] -tests = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock"] -validation = ["prance (>=0.11)"] -yaml = ["PyYAML (>=3.10)"] - [[package]] category = "dev" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." @@ -55,12 +47,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "19.3.0" -[package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] - [[package]] category = "dev" description = "Internationalization utilities" @@ -86,9 +72,6 @@ attrs = ">=17.4.0" click = ">=6.5" toml = ">=0.9.4" -[package.extras] -d = ["aiohttp (>=3.3.2)"] - [[package]] category = "dev" description = "Python package for providing Mozilla's CA Bundle." @@ -144,11 +127,6 @@ Werkzeug = ">=0.15" click = ">=5.1" itsdangerous = ">=0.24" -[package.extras] -dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] -docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] -dotenv = ["python-dotenv"] - [[package]] category = "main" description = "A Flask extension adding a decorator for CORS support" @@ -185,12 +163,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "4.3.21" -[package.extras] -pipfile = ["pipreqs", "requirementslib"] -pyproject = ["toml"] -requirements = ["pipreqs", "pip-api"] -xdg_home = ["appdirs (>=1.4.0)"] - [[package]] category = "main" description = "Various helpers to pass data to untrusted environments and back." @@ -204,15 +176,12 @@ category = "main" description = "A very fast and expressive template engine." name = "jinja2" optional = false -python-versions = "*" -version = "2.10.3" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.0" [package.dependencies] MarkupSafe = ">=0.23" -[package.extras] -i18n = ["Babel (>=0.8)"] - [[package]] category = "main" description = "" @@ -229,7 +198,7 @@ marshmallow = "^3.3.0" webargs = "^5.5.2" [package.source] -reference = "23732b17074486367541439a1caa2eb61b9cc22f" +reference = "cb1ef6a1f8849f4d58601a7a9617ffea12703f39" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -256,12 +225,6 @@ optional = false python-versions = ">=3.5" version = "3.3.0" -[package.extras] -dev = ["pytest", "pytz", "simplejson", "mypy (0.750)", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"] -docs = ["sphinx (2.2.2)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"] -lint = ["mypy (0.750)", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"] -tests = ["pytest", "pytz", "simplejson"] - [[package]] category = "dev" description = "McCabe checker, plugin for flake8" @@ -298,11 +261,6 @@ optional = true python-versions = "*" version = "1.13.1b0" -[package.extras] -array = ["numpy"] -doc = ["sphinx"] -test = ["coverage", "pytest", "mock", "pillow", "numpy"] - [package.source] reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb" type = "git" @@ -353,6 +311,17 @@ optional = false python-versions = "*" version = "3.4" +[[package]] +category = "main" +description = "Extensions to the standard Python datetime module" +name = "python-dateutil" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +version = "2.8.1" + +[package.dependencies] +six = ">=1.5" + [[package]] category = "dev" description = "World timezone definitions, modern and historical" @@ -383,10 +352,6 @@ chardet = ">=3.0.2,<3.1.0" idna = ">=2.5,<2.9" urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" -[package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] - [[package]] category = "dev" description = "a python refactoring library..." @@ -414,6 +379,14 @@ version = "1.3.0" [package.dependencies] numpy = ">=1.13.3" +[[package]] +category = "main" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +name = "simplejson" +optional = false +python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" +version = "3.17.0" + [[package]] category = "main" description = "Python 2 and 3 compatibility utilities" @@ -457,10 +430,6 @@ sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = "*" -[package.extras] -docs = ["sphinxcontrib-websupport"] -test = ["pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"] - [[package]] category = "dev" description = "" @@ -469,9 +438,6 @@ optional = false python-versions = "*" version = "1.0.1" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "" @@ -480,9 +446,6 @@ optional = false python-versions = "*" version = "1.0.1" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "" @@ -491,9 +454,6 @@ optional = false python-versions = "*" version = "1.0.2" -[package.extras] -test = ["pytest", "flake8", "mypy", "html5lib"] - [[package]] category = "dev" description = "Sphinx domain for documenting HTTP APIs" @@ -514,9 +474,6 @@ optional = false python-versions = ">=3.5" version = "1.0.1" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "" @@ -525,9 +482,6 @@ optional = false python-versions = "*" version = "1.0.2" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "" @@ -536,9 +490,6 @@ optional = false python-versions = "*" version = "1.1.3" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "Python Library for Tom's Obvious, Minimal Language" @@ -564,28 +515,17 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" version = "1.25.8" -[package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] - [[package]] category = "main" description = "Declarative parsing and validation of HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, webapp2, Falcon, and aiohttp." name = "webargs" optional = false python-versions = "*" -version = "5.5.2" +version = "5.5.3" [package.dependencies] marshmallow = ">=2.15.2" - -[package.extras] -dev = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "tox", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"] -docs = ["Sphinx (2.2.0)", "sphinx-issues (1.2.0)", "sphinx-typlog-theme (0.7.3)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"] -frameworks = ["Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"] -lint = ["flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"] -tests = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)"] +simplejson = ">=2.1.0" [[package]] category = "main" @@ -593,12 +533,7 @@ description = "The comprehensive WSGI web application library." name = "werkzeug" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.16.0" - -[package.extras] -dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] -termcolor = ["termcolor"] -watchdog = ["watchdog"] +version = "0.16.1" [[package]] category = "dev" @@ -612,373 +547,63 @@ version = "1.11.2" rpi = ["picamera", "RPi.GPIO"] [metadata] -content-hash = "cd4e5d59e9a63ae053a05c994a61a46be87a29ec6867bcd47f6ef9b39bb785d3" +content-hash = "34da76b3341563a0bebfa3e4a3fca90f34b857027e90811fed92b106ad06c2ba" python-versions = "^3.6" -[metadata.files] -alabaster = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, -] -apispec = [ - {file = "apispec-3.2.0-py2.py3-none-any.whl", hash = "sha256:f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"}, - {file = "apispec-3.2.0.tar.gz", hash = "sha256:c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a"}, -] -appdirs = [ - {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, - {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, -] -astroid = [ - {file = "astroid-2.3.3-py3-none-any.whl", hash = "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"}, - {file = "astroid-2.3.3.tar.gz", hash = "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a"}, -] -attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, -] -babel = [ - {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, - {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, -] -black = [ - {file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"}, - {file = "black-18.9b0.tar.gz", hash = "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"}, -] -certifi = [ - {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, - {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, -] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, -] -click = [ - {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, - {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, -] -colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, -] -docutils = [ - {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, - {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, -] -flask = [ - {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, - {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, -] -flask-cors = [ - {file = "Flask-Cors-3.0.8.tar.gz", hash = "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16"}, - {file = "Flask_Cors-3.0.8-py2.py3-none-any.whl", hash = "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"}, -] -idna = [ - {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, - {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, -] -imagesize = [ - {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, - {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, -] -isort = [ - {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, - {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, -] -itsdangerous = [ - {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, - {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, -] -jinja2 = [ - {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"}, - {file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"}, -] +[metadata.hashes] +alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"] +apispec = ["c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a", "f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"] +appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] +astroid = ["71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", "840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"] +attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"] +babel = ["1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", "d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"] +black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"] +certifi = ["017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", "25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"] +chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] +click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] +colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"] +docutils = ["0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", "c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"] +flask = ["13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", "45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"] +flask-cors = ["72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16", "f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"] +idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] +imagesize = ["6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", "b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"] +isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"] +itsdangerous = ["321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"] +jinja2 = ["6e7a3c2934694d59ad334c93dd1b6c96699cf24c53fdb8ec848ac6b23e685734", "d6609ae5ec3d56212ca7d802eda654eaf2310000816ce815361041465b108be4"] labthings = [] -lazy-object-proxy = [ - {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, -] -markupsafe = [ - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, -] -marshmallow = [ - {file = "marshmallow-3.3.0-py2.py3-none-any.whl", hash = "sha256:3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"}, - {file = "marshmallow-3.3.0.tar.gz", hash = "sha256:0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -numpy = [ - {file = "numpy-1.18.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc"}, - {file = "numpy-1.18.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121"}, - {file = "numpy-1.18.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e"}, - {file = "numpy-1.18.1-cp35-cp35m-win32.whl", hash = "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"}, - {file = "numpy-1.18.1-cp35-cp35m-win_amd64.whl", hash = "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6"}, - {file = "numpy-1.18.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480"}, - {file = "numpy-1.18.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572"}, - {file = "numpy-1.18.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57"}, - {file = "numpy-1.18.1-cp36-cp36m-win32.whl", hash = "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc"}, - {file = "numpy-1.18.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd"}, - {file = "numpy-1.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa"}, - {file = "numpy-1.18.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca"}, - {file = "numpy-1.18.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec"}, - {file = "numpy-1.18.1-cp37-cp37m-win32.whl", hash = "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73"}, - {file = "numpy-1.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971"}, - {file = "numpy-1.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07"}, - {file = "numpy-1.18.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26"}, - {file = "numpy-1.18.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474"}, - {file = "numpy-1.18.1-cp38-cp38-win32.whl", hash = "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3"}, - {file = "numpy-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a"}, - {file = "numpy-1.18.1.zip", hash = "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77"}, -] -packaging = [ - {file = "packaging-20.1-py2.py3-none-any.whl", hash = "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73"}, - {file = "packaging-20.1.tar.gz", hash = "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"}, -] +lazy-object-proxy = ["0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", "194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", "1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", "4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", "48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", "5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", "59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", "8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", "9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", "9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", "97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", "9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", "a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", "a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", "ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", "cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", "d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", "d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", "eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", "efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"] +markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] +marshmallow = ["0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e", "3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"] +mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] +numpy = ["1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", "17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", "20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", "2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", "39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", "56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", "590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", "70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", "77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", "9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", "9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", "ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", "b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", "b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", "b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", "c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", "cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", "d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", "e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", "e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", "f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"] +packaging = ["170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", "e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"] picamera = [] -pillow = [ - {file = "Pillow-5.4.1-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1"}, - {file = "Pillow-5.4.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7"}, - {file = "Pillow-5.4.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e"}, - {file = "Pillow-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c"}, - {file = "Pillow-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f"}, - {file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c"}, - {file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc"}, - {file = "Pillow-5.4.1-cp34-cp34m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1"}, - {file = "Pillow-5.4.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8"}, - {file = "Pillow-5.4.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055"}, - {file = "Pillow-5.4.1-cp34-cp34m-win32.whl", hash = "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6"}, - {file = "Pillow-5.4.1-cp34-cp34m-win_amd64.whl", hash = "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf"}, - {file = "Pillow-5.4.1-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239"}, - {file = "Pillow-5.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3"}, - {file = "Pillow-5.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697"}, - {file = "Pillow-5.4.1-cp35-cp35m-win32.whl", hash = "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7"}, - {file = "Pillow-5.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe"}, - {file = "Pillow-5.4.1-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca"}, - {file = "Pillow-5.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"}, - {file = "Pillow-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad"}, - {file = "Pillow-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a"}, - {file = "Pillow-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356"}, - {file = "Pillow-5.4.1-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1"}, - {file = "Pillow-5.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9"}, - {file = "Pillow-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3"}, - {file = "Pillow-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f"}, - {file = "Pillow-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3"}, - {file = "Pillow-5.4.1-pp260-pypy_41-win32.whl", hash = "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494"}, - {file = "Pillow-5.4.1-pp360-pp360-win32.whl", hash = "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000"}, - {file = "Pillow-5.4.1-py2.7-macosx-10.13-x86_64.egg", hash = "sha256:634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca"}, - {file = "Pillow-5.4.1-py2.7-win-amd64.egg", hash = "sha256:0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d"}, - {file = "Pillow-5.4.1-py2.7-win32.egg", hash = "sha256:f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28"}, - {file = "Pillow-5.4.1-py3.4-win-amd64.egg", hash = "sha256:01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1"}, - {file = "Pillow-5.4.1-py3.4-win32.egg", hash = "sha256:4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964"}, - {file = "Pillow-5.4.1-py3.5-win-amd64.egg", hash = "sha256:f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15"}, - {file = "Pillow-5.4.1-py3.5-win32.egg", hash = "sha256:e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838"}, - {file = "Pillow-5.4.1-py3.6-win-amd64.egg", hash = "sha256:07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa"}, - {file = "Pillow-5.4.1-py3.6-win32.egg", hash = "sha256:87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9"}, - {file = "Pillow-5.4.1-py3.7-win-amd64.egg", hash = "sha256:c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e"}, - {file = "Pillow-5.4.1-py3.7-win32.egg", hash = "sha256:5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f"}, - {file = "Pillow-5.4.1.tar.gz", hash = "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f"}, - {file = "Pillow-5.4.1.win-amd64-py2.7.exe", hash = "sha256:e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e"}, - {file = "Pillow-5.4.1.win-amd64-py3.4.exe", hash = "sha256:52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2"}, - {file = "Pillow-5.4.1.win-amd64-py3.5.exe", hash = "sha256:ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2"}, - {file = "Pillow-5.4.1.win-amd64-py3.6.exe", hash = "sha256:39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c"}, - {file = "Pillow-5.4.1.win-amd64-py3.7.exe", hash = "sha256:5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4"}, - {file = "Pillow-5.4.1.win32-py2.7.exe", hash = "sha256:db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0"}, - {file = "Pillow-5.4.1.win32-py3.4.exe", hash = "sha256:ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28"}, - {file = "Pillow-5.4.1.win32-py3.5.exe", hash = "sha256:f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b"}, - {file = "Pillow-5.4.1.win32-py3.6.exe", hash = "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d"}, - {file = "Pillow-5.4.1.win32-py3.7.exe", hash = "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522"}, -] -pygments = [ - {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"}, - {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"}, -] -pylint = [ - {file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"}, - {file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"}, -] -pyparsing = [ - {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, - {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, -] -pyserial = [ - {file = "pyserial-3.4-py2.py3-none-any.whl", hash = "sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"}, - {file = "pyserial-3.4.tar.gz", hash = "sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627"}, -] -pytz = [ - {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, - {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, -] -pyyaml = [ - {file = "PyYAML-3.13-cp27-cp27m-win32.whl", hash = "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f"}, - {file = "PyYAML-3.13-cp27-cp27m-win_amd64.whl", hash = "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537"}, - {file = "PyYAML-3.13-cp34-cp34m-win32.whl", hash = "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3"}, - {file = "PyYAML-3.13-cp34-cp34m-win_amd64.whl", hash = "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04"}, - {file = "PyYAML-3.13-cp35-cp35m-win32.whl", hash = "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1"}, - {file = "PyYAML-3.13-cp35-cp35m-win_amd64.whl", hash = "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613"}, - {file = "PyYAML-3.13-cp36-cp36m-win32.whl", hash = "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a"}, - {file = "PyYAML-3.13-cp36-cp36m-win_amd64.whl", hash = "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b"}, - {file = "PyYAML-3.13-cp37-cp37m-win32.whl", hash = "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"}, - {file = "PyYAML-3.13-cp37-cp37m-win_amd64.whl", hash = "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1"}, - {file = "PyYAML-3.13.tar.gz", hash = "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf"}, -] -requests = [ - {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, - {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, -] -rope = [ - {file = "rope-0.14.0-py2-none-any.whl", hash = "sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969"}, - {file = "rope-0.14.0-py3-none-any.whl", hash = "sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"}, - {file = "rope-0.14.0.tar.gz", hash = "sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf"}, -] -"rpi.gpio" = [ - {file = "RPi.GPIO-0.6.5.tar.gz", hash = "sha256:a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"}, -] -scipy = [ - {file = "scipy-1.3.0-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340"}, - {file = "scipy-1.3.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba"}, - {file = "scipy-1.3.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef"}, - {file = "scipy-1.3.0-cp35-cp35m-win32.whl", hash = "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351"}, - {file = "scipy-1.3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae"}, - {file = "scipy-1.3.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7"}, - {file = "scipy-1.3.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785"}, - {file = "scipy-1.3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"}, - {file = "scipy-1.3.0-cp36-cp36m-win32.whl", hash = "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb"}, - {file = "scipy-1.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d"}, - {file = "scipy-1.3.0-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e"}, - {file = "scipy-1.3.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06"}, - {file = "scipy-1.3.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921"}, - {file = "scipy-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1"}, - {file = "scipy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e"}, - {file = "scipy-1.3.0.tar.gz", hash = "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a"}, -] -six = [ - {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, - {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, -] -snowballstemmer = [ - {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, - {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, -] -sphinx = [ - {file = "Sphinx-2.3.1-py3-none-any.whl", hash = "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159"}, - {file = "Sphinx-2.3.1.tar.gz", hash = "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"}, -] -sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"}, - {file = "sphinxcontrib_applehelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"}, -] -sphinxcontrib-devhelp = [ - {file = "sphinxcontrib-devhelp-1.0.1.tar.gz", hash = "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34"}, - {file = "sphinxcontrib_devhelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"}, -] -sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-1.0.2.tar.gz", hash = "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422"}, - {file = "sphinxcontrib_htmlhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"}, -] -sphinxcontrib-httpdomain = [ - {file = "sphinxcontrib-httpdomain-1.7.0.tar.gz", hash = "sha256:ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"}, - {file = "sphinxcontrib_httpdomain-1.7.0-py2.py3-none-any.whl", hash = "sha256:1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e"}, -] -sphinxcontrib-jsmath = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] -sphinxcontrib-qthelp = [ - {file = "sphinxcontrib-qthelp-1.0.2.tar.gz", hash = "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"}, - {file = "sphinxcontrib_qthelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20"}, -] -sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.3.tar.gz", hash = "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227"}, - {file = "sphinxcontrib_serializinghtml-1.1.3-py2.py3-none-any.whl", hash = "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"}, -] -toml = [ - {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, - {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, - {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, -] -typed-ast = [ - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, - {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, - {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, - {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, - {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, -] -urllib3 = [ - {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, - {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, -] -webargs = [ - {file = "webargs-5.5.2-py2-none-any.whl", hash = "sha256:3f9dc15de183d356c9a0acc159c100ea0506c0c240c1e6f1d8b308c5fed4dbbd"}, - {file = "webargs-5.5.2-py3-none-any.whl", hash = "sha256:fa4ad3ad9b38bedd26c619264fdc50d7ae014b49186736bca851e5b5228f2a1b"}, - {file = "webargs-5.5.2.tar.gz", hash = "sha256:3beca296598067cec24a0b6f91c0afcc19b6e3c4d84ab026b931669628bb47b4"}, -] -werkzeug = [ - {file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"}, - {file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"}, -] -wrapt = [ - {file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"}, -] +pillow = ["01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", "051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", "07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", "0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", "0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", "0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", "163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", "18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", "24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", "267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", "3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", "39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", "4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", "4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", "4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", "4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", "505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", "5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", "52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", "5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", "5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", "5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", "634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", "75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", "7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", "801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", "825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", "87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", "9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", "a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", "a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", "a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", "a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", "ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", "b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", "ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", "ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", "c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", "cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", "cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", "d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", "d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", "db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", "e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", "e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", "e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", "f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", "f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", "f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", "f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", "fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"] +pygments = ["2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", "98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"] +pylint = ["3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", "886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"] +pyparsing = ["4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", "c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"] +pyserial = ["6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627", "e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"] +python-dateutil = ["73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"] +pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"] +pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"] +requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] +rope = ["6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969", "c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf", "f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"] +"rpi.gpio" = ["a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"] +scipy = ["03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", "09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", "10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", "1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", "409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", "4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", "6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", "826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", "a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", "a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", "adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", "b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", "c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", "c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", "c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", "db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"] +simplejson = ["0fe3994207485efb63d8f10a833ff31236ed27e3b23dadd0bf51c9900313f8f2", "17163e643dbf125bb552de17c826b0161c68c970335d270e174363d19e7ea882", "1d1e929cdd15151f3c0b2efe953b3281b2fd5ad5f234f77aca725f28486466f6", "1d346c2c1d7dd79c118f0cc7ec5a1c4127e0c8ffc83e7b13fc5709ff78c9bb84", "1ea59f570b9d4916ae5540a9181f9c978e16863383738b69a70363bc5e63c4cb", "1fbba86098bbfc1f85c5b69dc9a6d009055104354e0d9880bb00b692e30e0078", "229edb079d5dd81bf12da952d4d825bd68d1241381b37d3acf961b384c9934de", "22a7acb81968a7c64eba7526af2cf566e7e2ded1cb5c83f0906b17ff1540f866", "2b4b2b738b3b99819a17feaf118265d0753d5536049ea570b3c43b51c4701e81", "4cf91aab51b02b3327c9d51897960c554f00891f9b31abd8a2f50fd4a0071ce8", "4fd5f79590694ebff8dc980708e1c182d41ce1fda599a12189f0ca96bf41ad70", "5cfd495527f8b85ce21db806567de52d98f5078a8e9427b18e251c68bd573a26", "60aad424e47c5803276e332b2a861ed7a0d46560e8af53790c4c4fb3420c26c2", "7739940d68b200877a15a5ff5149e1599737d6dd55e302625650629350466418", "7cce4bac7e0d66f3a080b80212c2238e063211fe327f98d764c6acbc214497fc", "8027bd5f1e633eb61b8239994e6fc3aba0346e76294beac22a892eb8faa92ba1", "86afc5b5cbd42d706efd33f280fec7bd7e2772ef54e3f34cf6b30777cd19a614", "87d349517b572964350cc1adc5a31b493bbcee284505e81637d0174b2758ba17", "8de378d589eccbc75941e480b4d5b4db66f22e4232f87543b136b1f093fff342", "926bcbef9eb60e798eabda9cd0bbcb0fca70d2779aa0aa56845749d973eb7ad5", "9a126c3a91df5b1403e965ba63b304a50b53d8efc908a8c71545ed72535374a3", "ad8dd3454d0c65c0f92945ac86f7b9efb67fa2040ba1b0189540e984df904378", "d140e9376e7f73c1f9e0a8e3836caf5eec57bbafd99259d56979da05a6356388", "da00675e5e483ead345429d4f1374ab8b949fba4429d60e71ee9d030ced64037", "daaf4d11db982791be74b23ff4729af2c7da79316de0bebf880fa2d60bcc8c5a", "f4b64a1031acf33e281fd9052336d6dad4d35eee3404c95431c8c6bc7a9c0588", "fc046afda0ed8f5295212068266c92991ab1f4a50c6a7144b69364bdee4a0159", "fc9051d249dd5512e541f20330a74592f7a65b2d62e18122ca89bf71f94db748"] +six = ["236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", "8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"] +snowballstemmer = ["209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", "df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"] +sphinx = ["298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159", "e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"] +sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"] +sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"] +sphinxcontrib-htmlhelp = ["4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"] +sphinxcontrib-httpdomain = ["1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e", "ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"] +sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"] +sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"] +sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"] +toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] +typed-ast = ["0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", "0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", "249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", "24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", "269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", "4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", "498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", "4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", "6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", "715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", "73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", "8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", "8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", "aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", "bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", "c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", "d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", "d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", "d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", "fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", "fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"] +urllib3 = ["2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", "87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"] +webargs = ["4f04918864c7602886335d8099f9b8960ee698b6b914f022736ed50be6b71235", "871642a2e0c62f21d5b78f357750ac7a87e6bc734c972f633aa5fb6204fbf29a", "fc81c9f9d391acfbce406a319217319fd8b2fd862f7fdb5319ad06944f36ed25"] +werkzeug = ["1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2", "b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04"] +wrapt = ["565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"] diff --git a/pyproject.toml b/pyproject.toml index fa4bb194..f9ccc42e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ picamera = { git = "https://github.com/rwb27/picamera.git", branch = "lens-shadi pyserial = "^3.4" # Used for sangaboard (basic_serial_instrument) until we move to sangaboard pip library labthings = { git = "https://github.com/labthings/python-labthings.git", branch = "master"} # TODO: Pin to a fixed version before 2.0.0 stable +python-dateutil = "^2.8" [tool.poetry.extras] rpi = ["picamera", "RPi.GPIO"] From 7458d278d8ed1c155ef81d651185b9c92965db59 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 15:36:57 +0000 Subject: [PATCH 11/70] Simplified new configuration and metadata --- openflexure_microscope/api/app.py | 8 +- .../api/default_extensions/scan.py | 101 ++++++-------- .../api/v2/views/actions/camera.py | 7 +- .../api/v2/views/captures.py | 22 +-- openflexure_microscope/api/v2/views/state.py | 32 ++++- openflexure_microscope/camera/capture.py | 84 +++++------ openflexure_microscope/camera/pi.py | 17 +-- openflexure_microscope/config.py | 19 ++- openflexure_microscope/microscope.py | 130 +++++++++--------- .../microscope_configuration.default.json | 10 +- 10 files changed, 214 insertions(+), 216 deletions(-) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index 7f688fe0..e3aa1404 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -19,7 +19,7 @@ from openflexure_microscope.paths import ( OPENFLEXURE_VAR_PATH, OPENFLEXURE_EXTENSIONS_PATH, settings_file_path, - logs_file_path + logs_file_path, ) from labthings.server.quick import create_app @@ -92,7 +92,7 @@ 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.CaptureMetadata, f"/captures//metadata") +labthing.add_view(views.CaptureAnnotations, f"/captures//annotations") # Attach settings and status resources labthing.add_view(views.SettingsProperty, f"/settings") @@ -101,6 +101,10 @@ labthing.add_view(views.NestedSettingsProperty, "/settings/") labthing.add_view(views.StatusProperty, "/status") labthing.add_view(views.NestedStatusProperty, "/status/") labthing.add_root_link(views.StatusProperty, "status") +labthing.add_view(views.ConfigurationProperty, "/configuration") +labthing.add_view(views.NestedConfigurationProperty, "/configuration/") +labthing.add_root_link(views.ConfigurationProperty, "configuration") + # Attach streams resources labthing.add_view(views.MjpegStream, f"/streams/mjpeg") diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index 26a7dd6f..261b15b3 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -1,6 +1,7 @@ import itertools import logging import uuid +import datetime from typing import Tuple from functools import reduce @@ -71,12 +72,12 @@ def progress(): def capture( microscope, basename, - scan_id, temporary: bool = False, use_video_port: bool = False, resize: Tuple[int, int] = None, bayer: bool = False, metadata: dict = {}, + annotations: dict = {}, tags: list = [], ): @@ -94,16 +95,14 @@ def capture( output.file, use_video_port=use_video_port, resize=resize, bayer=bayer ) - # Affix metadata - if "scan" not in tags: - tags.append("scan") - # Inject system metadata - output.put_metadata(microscope.metadata, system=True) + output.put_metadata({"instrument": microscope.metadata}) # Insert custom metadata output.put_metadata(metadata) + # Insert custom metadata + output.put_annotations(annotations) # Insert custom tags output.put_tags(tags) @@ -115,7 +114,7 @@ def tile( microscope, basename: str = None, temporary: bool = False, - step_size: int = [2000, 1500, 100], + stride_size: int = [2000, 1500, 100], grid: list = [3, 3, 5], style="raster", autofocus_dz: int = 50, @@ -124,13 +123,13 @@ def tile( bayer: bool = False, fast_autofocus=False, metadata: dict = {}, + annotations: dict = {}, tags: list = [], ): global _images_to_be_captured global _images_captured_so_far # Keep task progress - # TODO: Make this line not nasty _images_to_be_captured = reduce((lambda x, y: x * y), grid) _images_captured_so_far = 0 @@ -138,28 +137,22 @@ def tile( if not basename: basename = generate_basename() - # Generate a stack ID - scan_id = uuid.uuid4() - # Store initial position initial_position = microscope.stage.position - # Add scan metadata - if "time" not in metadata: - metadata["time"] = generate_basename() - - metadata.update( - { - "scan_id": scan_id, - "basename": basename, - "scan_parameters": { - "step_size": step_size, - "grid": grid, - "style": style, - "autofocus_dz": autofocus_dz, - }, + # Add dataset metadata + dataset_d = { + "dataset": { + "id": uuid.uuid4(), + "type": "xyzScan", + "name": basename, + "acquisitionDate": datetime.datetime.now().isoformat(), + "strideSize": stride_size, + "grid": grid, + "style": style, + "autofocusDz": autofocus_dz, } - ) + } # Check if autofocus is enabled autofocus_extension = find_extension("org.openflexure.autofocus") @@ -174,11 +167,11 @@ def tile( autofocus_enabled = False z_stack_dz = ( - grid[2] * step_size[2] if grid[2] > 1 else 0 + grid[2] * stride_size[2] if grid[2] > 1 else 0 ) # shorthand for Z stack range # Construct an x-y grid (worry about z later) - x_y_grid = construct_grid(initial_position, step_size[:2], grid[:2], style=style) + x_y_grid = construct_grid(initial_position, stride_size[:2], grid[:2], style=style) # Keep the initial Z position the same as our current position next_z = initial_position[2] @@ -209,26 +202,25 @@ def tile( target_z=-z_stack_dz / 2.0, # Finish below the focus initial_move_up=False, # We're already at the top of the scan ) - # TODO: save the focus data for future reference? Use it for diagnostics? else: logging.debug("Running autofocus") autofocus_extension.autofocus( range(-3 * autofocus_dz, 4 * autofocus_dz, autofocus_dz) ) logging.debug("Finished autofocus") - time.sleep(1) # TODO: Remove + time.sleep(1) # If we're not doing a z-stack, just capture if grid[2] <= 1: capture( microscope, basename, - scan_id, temporary=temporary, use_video_port=use_video_port, resize=resize, bayer=bayer, - metadata=metadata, + metadata=dataset_d, + annotations=annotations, tags=tags, ) # Update task progress @@ -240,15 +232,14 @@ def tile( microscope=microscope, basename=basename, temporary=temporary, - scan_id=scan_id, - step_size=step_size[2], + step_size=stride_size[2], steps=grid[2], - center=not fast_autofocus, # fast_autofocus does this for us! return_to_start=not fast_autofocus, use_video_port=use_video_port, resize=resize, bayer=bayer, - metadata=metadata, + metadata=dataset_d, + annotations=annotations, tags=tags, ) # Make sure we use our current best estimate of focus (i.e. the current position) next point @@ -259,7 +250,7 @@ def tile( ) # Fast autofocus requires us to start at the top of the range if grid[2] > 1: next_z -= int( - grid[2] / 2.0 * step_size[2] + grid[2] / 2.0 * stride_size[2] ) # Z stacking means we're higher up to start with logging.debug("Returning to {}".format(initial_position)) @@ -270,39 +261,25 @@ def stack( microscope, basename: str = None, temporary: bool = False, - scan_id: str = None, step_size: int = 100, steps: int = 5, - center: bool = True, return_to_start: bool = True, use_video_port: bool = False, resize: Tuple[int, int] = None, bayer: bool = False, metadata: dict = {}, + annotations: dict = {}, tags: list = [], ): global _images_captured_so_far - # Generate a basename if none given - if not basename: - basename = generate_basename() - - # Generate a stack ID - if not scan_id: - scan_id = uuid.uuid4() - - # Add scan metadata - if not "time" in metadata: - metadata["time"] = generate_basename() - # Store initial position initial_position = microscope.stage.position with microscope.lock: # Move to center scan - if center: - logging.debug("Moving to starting position") - microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)]) + logging.debug("Moving to starting position") + microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)]) for i in range(steps): time.sleep(0.1) @@ -310,12 +287,12 @@ def stack( capture( microscope, basename, - scan_id, temporary=temporary, use_video_port=use_video_port, resize=resize, bayer=bayer, metadata=metadata, + annotations=annotations, tags=tags, ) # Update task progress @@ -337,16 +314,18 @@ def stack( class TileScanAPI(View): @use_args( { - "filename": fields.String(), + "filename": fields.String(missing=None, example=None), "temporary": fields.Boolean(missing=False), - "step_size": fields.List(fields.Integer, missing=[2000, 1500, 100]), - "grid": fields.List(fields.Integer, missing=[3, 3, 5]), + "stride_size": fields.List( + fields.Integer, missing=[2000, 1500, 100], example=[2000, 1500, 100] + ), + "grid": fields.List(fields.Integer, missing=[3, 3, 3], example=[3, 3, 3]), "style": fields.String(missing="raster"), "autofocus_dz": fields.Integer(missing=50), "fast_autofocus": fields.Boolean(missing=False), "use_video_port": fields.Boolean(missing=False), "bayer": fields.Boolean(missing=False), - "metadata": fields.Dict(missing={}), + "annotations": fields.Dict(missing={}, example={"Foo": "Bar"}), "tags": fields.List(fields.String, missing=[]), "resize": fields.Dict(missing=None), # TODO: Validate keys } @@ -373,7 +352,7 @@ class TileScanAPI(View): microscope, basename=args.get("filename"), temporary=args.get("temporary"), - step_size=args.get("step_size"), + stride_size=args.get("stride_size"), grid=args.get("grid"), style=args.get("style"), autofocus_dz=args.get("autofocus_dz"), @@ -381,7 +360,7 @@ class TileScanAPI(View): resize=resize, bayer=args.get("bayer"), fast_autofocus=args.get("fast_autofocus"), - metadata=args.get("metadata"), + annotations=args.get("annotations"), tags=args.get("tags"), ) diff --git a/openflexure_microscope/api/v2/views/actions/camera.py b/openflexure_microscope/api/v2/views/actions/camera.py index e9912ad9..8b618193 100644 --- a/openflexure_microscope/api/v2/views/actions/camera.py +++ b/openflexure_microscope/api/v2/views/actions/camera.py @@ -36,7 +36,7 @@ class CaptureAPI(View): "bayer": fields.Boolean( missing=False, description="Store raw bayer data in file" ), - "metadata": fields.Dict(missing={}, example={"Client": "SwaggerUI"}), + "annotations": fields.Dict(missing={}, example={"Client": "SwaggerUI"}), "tags": fields.List(fields.String, missing=[], example=["docs"]), "resize": fields.Dict( missing=None, example={"width": 640, "height": 480} @@ -75,11 +75,10 @@ class CaptureAPI(View): ) # Inject system metadata - output.put_metadata(microscope.metadata, system=True) + output.put_metadata({"instrument": microscope.metadata}) # Insert custom metadata - output.put_metadata(args.get("metadata")) - + output.put_annotations(args.get("annotations")) # Insert custom tags output.put_tags(args.get("tags")) diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index f4831aa3..c5e00623 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -39,10 +39,12 @@ class CaptureSchema(Schema): "mimetype": "application/json", **description_from_view(CaptureTags), }, - "metadata": { - "href": url_for(CaptureMetadata.endpoint, id=data.id, _external=True), + "annotations": { + "href": url_for( + CaptureAnnotations.endpoint, id=data.id, _external=True + ), "mimetype": "application/json", - **description_from_view(CaptureMetadata), + **description_from_view(CaptureAnnotations), }, "download": { "href": url_for( @@ -62,6 +64,9 @@ capture_schema = CaptureSchema() capture_list_schema = CaptureSchema(many=True) +from pprint import pprint + + @ThingProperty @Tag("captures") class CaptureList(View): @@ -197,10 +202,10 @@ class CaptureTags(View): @Tag("captures") -class CaptureMetadata(View): +class CaptureAnnotations(View): def get(self, id): """ - Get metadata associated with a single image capture + Get annotations associated with a single image capture """ microscope = find_component("org.openflexure.microscope") capture_obj = microscope.camera.image_from_id(id) @@ -208,7 +213,7 @@ class CaptureMetadata(View): if not capture_obj: return abort(404) # 404 Not Found - return jsonify(capture_obj.metadata) + return jsonify(capture_obj.annotations) def put(self, id): """ @@ -226,7 +231,6 @@ class CaptureMetadata(View): if type(data_dict) != dict: return abort(400) - # TODO: Allow putting system metadata maybe? - capture_obj.put_metadata(data_dict) + capture_obj.put_annotations(data_dict) - return jsonify(capture_obj.metadata) + return jsonify(capture_obj.annotations) diff --git a/openflexure_microscope/api/v2/views/state.py b/openflexure_microscope/api/v2/views/state.py index 34637754..7f22323c 100644 --- a/openflexure_microscope/api/v2/views/state.py +++ b/openflexure_microscope/api/v2/views/state.py @@ -78,7 +78,7 @@ class StatusProperty(View): Show current read-only state of the microscope """ microscope = find_component("org.openflexure.microscope") - return jsonify(microscope.status) + return jsonify(microscope.state) @Tag("properties") @@ -92,7 +92,35 @@ class NestedStatusProperty(View): keys = route.split("/") try: - value = get_by_path(microscope.status, keys) + value = get_by_path(microscope.state, keys) + except KeyError: + return abort(404) + + return jsonify(value) + + +@ThingProperty +class ConfigurationProperty(View): + def get(self): + """ + Show current read-only state of the microscope + """ + microscope = find_component("org.openflexure.microscope") + return jsonify(microscope.configuration) + + +@Tag("properties") +class NestedConfigurationProperty(View): + @doc_response(404, description="Configuration key cannot be found") + def get(self, route): + """ + Show a nested section of the current microscope state + """ + microscope = find_component("org.openflexure.microscope") + keys = route.split("/") + + try: + value = get_by_path(microscope.configuration, keys) except KeyError: return abort(404) diff --git a/openflexure_microscope/camera/capture.py b/openflexure_microscope/camera/capture.py index 20ec71da..e7264b6c 100644 --- a/openflexure_microscope/camera/capture.py +++ b/openflexure_microscope/camera/capture.py @@ -8,6 +8,7 @@ import yaml import json import logging from PIL import Image +import dateutil.parser import atexit from openflexure_microscope.camera import piexif @@ -93,18 +94,18 @@ def capture_from_exif(path, exif_dict): # Build file path information capture.split_file_path(capture.file) - # Populate capture parameters - capture.id = exif_dict["id"] - capture.timestring = exif_dict["time"] - capture.format = exif_dict["format"] + # Image metadata + image_metadata = exif_dict.pop("image") - capture.custom_metadata = ( - exif_dict["custom"] if "custom" in exif_dict.keys() else {} - ) - capture.system_metadata = ( - exif_dict["system"] if "system" in exif_dict.keys() else {} - ) - capture.tags = exif_dict["tags"] + # Populate capture parameters + capture.id = image_metadata.get("id") + capture.datetime = dateutil.parser.isoparse(image_metadata.get("acquisitionDate")) + capture.format = image_metadata.get("format") + capture.tags = image_metadata.get("tags") + capture.annotations = image_metadata.get("annotations") + + # Since we popped the "image" key, we dump whatever is left in _metadata + capture._metadata = exif_dict return capture @@ -113,16 +114,6 @@ class CaptureObject(object): """ StreamObject used to store and process on-disk capture data, and metadata. Serves to simplify modifying properties of on-disk capture data. - - Attributes: - timestring (str): Timestring of capture creation time - custom_metadata (dict): Dictionary of custom metadata to be included in metadata file - tags (list): List of tags. Essentially just as extra custom metadata field, but useful for quick organisation - filefolder (str): Folder in which the capture file will be stored - filename (str): Full name of the capture file - basename (str): Filename of the capture, without a file extension - format (str): Format of the capture data - """ def __init__(self, filepath) -> None: @@ -131,17 +122,20 @@ class CaptureObject(object): # Store a nice ID self.id = uuid.uuid4() #: str: Unique capture ID logging.debug("Created StreamObject {}".format(self.id)) - self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + self.datetime = datetime.datetime.now() # Create file name. Default to UUID self.file = filepath self.split_file_path(self.file) - # Dictionary for storing custom metadata - self.custom_metadata = {} - # Dictionary for adding top-level metadata (cannmot be accessed through web API) - self.system_metadata = {} + if not os.path.exists(self.filefolder): + os.makedirs(self.filefolder) + # Dictionary for adding top-level metadata (cannmot be accessed through web API) + self._metadata = {} + + # Dictionary for storing custom annotations + self.annotations = {} # List for storing tags self.tags = [] @@ -164,10 +158,6 @@ class CaptureObject(object): self.basename = os.path.splitext(self.filename)[0] self.format = self.filename.split(".")[-1] - # Create folder and file - if not os.path.exists(self.filefolder): - os.makedirs(self.filefolder) - @property def exists(self) -> bool: """Check if capture data file exists on disk.""" @@ -204,17 +194,24 @@ class CaptureObject(object): # HANDLE METADATA - def put_metadata(self, data: dict, system: bool = False) -> None: + def put_annotations(self, data: dict) -> None: """ - Merge metadata from a passed dictionary into the capture metadata, and saves. + Merge annotations from a passed dictionary into the capture metadata, and saves. Args: data (dict): Dictionary of metadata to be added """ - if system: - self.system_metadata.update(data) - else: - self.custom_metadata.update(data) + self.annotations.update(data) + self.save_metadata() + + def put_metadata(self, data: dict) -> None: + """ + Merge root metadata from a passed dictionary into the capture metadata, and saves. + + Args: + data (dict): Dictionary of metadata to be added + """ + self._metadata.update(data) self.save_metadata() def save_metadata(self) -> None: @@ -244,12 +241,15 @@ class CaptureObject(object): and any added custom metadata and tags. """ d = { - "id": self.id, - "time": self.timestring, - "format": self.format, - "tags": self.tags, - "custom": self.custom_metadata, - "system": self.system_metadata, + "image": { + "id": self.id, + "name": self.filename, + "acquisitionDate": self.datetime.isoformat(), + "format": self.format, + "tags": self.tags, + "annotations": self.annotations, + }, + **self._metadata, } # Add custom metadata to dictionary diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index bbd04e44..8f2a4d94 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -119,22 +119,7 @@ class PiCameraStreamer(BaseCamera): @property def configuration(self): """The current camera configuration.""" - config = { - "board": self.camera.revision, - } - - if self.read_lens_shading_table(): - b64_string, dtype, shape = serialise_array_b64(self.read_lens_shading_table()) - - config.update({ - "lens_shading_table": { - "b64_string": b64_string, - "dtype": dtype, - "shape": shape, - } - }) - - return config + return {"board": self.camera.revision} @property def state(self): diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index cfc458a1..5172a07c 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -1,4 +1,5 @@ import json +import flask import os import errno import logging @@ -7,7 +8,12 @@ from uuid import UUID import numpy as np from fractions import Fraction -from .paths import SETTINGS_FILE_PATH, DEFAULT_SETTINGS_FILE_PATH, CONFIGURATION_FILE_PATH, DEFAULT_CONFIGURATION_FILE_PATH +from .paths import ( + SETTINGS_FILE_PATH, + DEFAULT_SETTINGS_FILE_PATH, + CONFIGURATION_FILE_PATH, + DEFAULT_CONFIGURATION_FILE_PATH, +) class OpenflexureSettingsFile: @@ -71,7 +77,7 @@ class OpenflexureSettingsFile: return settings -class JSONEncoder(json.JSONEncoder): +class JSONEncoder(flask.json.JSONEncoder): """ A custom JSON encoder, with type conversions for PiCamera fractions, Numpy integers, and Numpy arrays """ @@ -183,7 +189,9 @@ with open(DEFAULT_SETTINGS_FILE_PATH, "r") as default_settings: DEFAULT_SETTINGS = default_settings.read() #: Default user settings object -user_settings = OpenflexureSettingsFile(path=SETTINGS_FILE_PATH, defaults=DEFAULT_SETTINGS) +user_settings = OpenflexureSettingsFile( + path=SETTINGS_FILE_PATH, defaults=DEFAULT_SETTINGS +) # Load the default configuration @@ -191,4 +199,7 @@ with open(DEFAULT_CONFIGURATION_FILE_PATH, "r") as default_configuration: DEFAULT_CONFIGURATION = default_configuration.read() #: Default user settings object -user_configuration = OpenflexureSettingsFile(path=CONFIGURATION_FILE_PATH, defaults=DEFAULT_CONFIGURATION) +user_configuration = OpenflexureSettingsFile( + path=CONFIGURATION_FILE_PATH, defaults=DEFAULT_CONFIGURATION +) + diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 2f52c6d9..b061bf32 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -9,6 +9,7 @@ import uuid from openflexure_microscope.stage.mock import MissingStage from openflexure_microscope.camera.mock import MissingCamera from openflexure_microscope.stage.sanga import SangaStage + try: from openflexure_microscope.camera.pi import PiCameraStreamer except ImportError: @@ -29,7 +30,12 @@ class Microscope: The camera and stage objects may already be initialised, and can be passed as arguments. """ - def __init__(self, settings = user_settings, configuration = user_configuration): + def __init__(self, settings=user_settings, configuration=user_configuration): + self.id = uuid.uuid4() + self.name = self.id + + self.fov = [0, 0] #: Microscope field-of-view in stage motor steps + # Store settings and configuration files self.settings_file = settings self.configuration_file = configuration @@ -45,17 +51,6 @@ class Microscope: # Apply settings loaded from file self.update_settings(self.settings_file.load()) - # Initial attributes - if self.configuration_file.load().get("id"): - self.id = configuration.get("id") - else: - self.id = uuid.uuid4() - self.configuration_file.save({ - "id": self.id - }) - - self.name = self.id - def __enter__(self): """Create microscope on context enter.""" return self @@ -79,9 +74,9 @@ class Microscope: """ ### Detector - if configuration.get("detector"): - detector_type = configuration["detector"].get("type") - if detector_type == "PiCamera" or detector_type == "PiCameraStreamer": + if configuration.get("camera"): + camera_type = configuration["camera"].get("type") + if camera_type == "PiCamera" or camera_type == "PiCameraStreamer": try: self.camera = PiCameraStreamer() except Exception as e: @@ -92,7 +87,7 @@ class Microscope: if configuration.get("stage"): stage_type = configuration["stage"].get("type") stage_port = configuration["stage"].get("port") - if stage_type == "SangaBoard" or detector_type == "SangaStage": + if stage_type == "SangaBoard" or camera_type == "SangaStage": try: self.stage = SangaStage(port=stage_port) except Exception as e: @@ -137,33 +132,32 @@ class Microscope: Return: dict: Dictionary containing complete microscope status """ - state = { - "camera": self.camera.state, - "stage": self.stage.state, - } + state = {"camera": self.camera.state, "stage": self.stage.state} return state - def update_settings(self, config: dict): + def update_settings(self, settings: dict): """ Applies a settings dictionary to the microscope. Missing parameters will be left untouched. """ - logging.debug("Microscope: Applying config: {}".format(config)) + logging.debug("Microscope: Applying settings: {}".format(settings)) # If attached to a camera - if ("camera_settings" in config) and self.camera: - self.camera.update_settings(config["camera_settings"]) + if ("camera" in settings) and self.camera: + self.camera.update_settings(settings["camera"]) # If attached to a stage - if ("stage_settings" in config) and self.stage: - self.stage.update_settings(config["stage_settings"]) + if ("stage" in settings) and self.stage: + self.stage.update_settings(settings["stage"]) - # Todo: tidy up with some loopy goodness - if "name" in config: - self.name = config["name"] - if "fov" in config: - self.fov = config["fov"] + # Microscope settings + if "id" in settings: + self.id = settings["id"] + if "name" in settings: + self.name = settings["name"] + if "fov" in settings: + self.fov = settings["fov"] - def read_settings(self, full: bool=True): + def read_settings(self, full: bool = True): """ Get an updated settings dictionary. @@ -174,17 +168,30 @@ class Microscope: don't get removed from the settings file. """ - settings_current = {"name": self.name, "fov": self.fov} + settings_current = {"id": self.id, "name": self.name, "fov": self.fov} # If attached to a camera if self.camera: settings_current_camera = self.camera.read_settings() - settings_current["camera_settings"] = settings_current_camera + settings_current["camera"] = settings_current_camera + + # Store an encoded copy of the PiCamera lens shading table, if it exists + if hasattr(self.camera, "read_lens_shading_table"): + # Read LST. Returns None if no LST is active + lst_arr = self.camera.read_lens_shading_table() + + b64_string, dtype, shape = serialise_array_b64(lst_arr) + + settings_current["camera"]["lens_shading_table"] = { + "b64_string": b64_string, + "dtype": dtype, + "shape": shape, + } # If attached to a stage if self.stage: settings_current_stage = self.stage.read_settings() - settings_current["stage_settings"] = settings_current_stage + settings_current["stage"] = settings_current_stage settings_full = self.settings_file.merge(settings_current) @@ -206,33 +213,6 @@ class Microscope: self.stage.save_settings() self.settings_file.save(current_config, backup=True) - @property - def metadata(self): - """ - Microscope system metadata, to be applied to basically all captures - """ - system_metadata = { - "@ID": self.id, - "settings": self.read_settings(full=False), - "state": self.state, - "configuration": self.configuration - } - - # Store an encoded copy of the PiCamera lens shading table, if it exists - if self.camera and hasattr(self.camera, "read_lens_shading_table"): - # Read LST. Returns None if no LST is active - lst_arr = self.camera.read_lens_shading_table() - - b64_string, dtype, shape = serialise_array_b64(lst_arr) - - system_metadata["configuration"]["detector"]["lens_shading_table"] = { - "b64_string": b64_string, - "dtype": dtype, - "shape": shape, - } - - return system_metadata - @property def configuration(self): initial_configuration = self.configuration_file.load() @@ -240,17 +220,33 @@ class Microscope: current_configuration = { "@application": { "name": "openflexure_microscope", - "version": pkg_resources.get_distribution("openflexure_microscope").version + "version": pkg_resources.get_distribution( + "openflexure_microscope" + ).version, }, "stage": { "type": self.stage.__class__.__name__, - **self.stage.configuration + **self.stage.configuration, }, - "detector": { + "camera": { "type": self.camera.__class__.__name__, - **self.camera.configuration - } + **self.camera.configuration, + }, } initial_configuration.update(current_configuration) return initial_configuration + + @property + def metadata(self): + """ + Microscope system metadata, to be applied to basically all captures + """ + system_metadata = { + "id": self.id, + "settings": self.read_settings(full=False), + "state": self.state, + "configuration": self.configuration, + } + + return system_metadata diff --git a/openflexure_microscope/microscope_configuration.default.json b/openflexure_microscope/microscope_configuration.default.json index d7129bc8..b9cbfdce 100644 --- a/openflexure_microscope/microscope_configuration.default.json +++ b/openflexure_microscope/microscope_configuration.default.json @@ -1,17 +1,9 @@ { - "microscope": { - "stepsPerView": [4100, 3146] - }, - "detector": { + "camera": { "type": "PiCamera" }, "stage": { "type": "SangaStage", "port": null - }, - "lightSource": { - "type": "LED" - }, - "objective": { } } \ No newline at end of file From ebdd63f37b2543122504b1027e1ab14d598b4be2 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 15:43:29 +0000 Subject: [PATCH 12/70] Added ndarray type to serialised LST (can be used in custom decoder) --- openflexure_microscope/microscope.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index b061bf32..d65a8fcb 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -183,6 +183,7 @@ class Microscope: b64_string, dtype, shape = serialise_array_b64(lst_arr) settings_current["camera"]["lens_shading_table"] = { + "@type": "ndarray", "b64_string": b64_string, "dtype": dtype, "shape": shape, From 265eed7bfac79ea03140cca1c540e9f3a9738d84 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 16:16:09 +0000 Subject: [PATCH 13/70] Fixed picamera setup --- .../picamera_autocalibrate/extension.py | 4 ++-- .../api/v2/views/actions/stage.py | 2 +- openflexure_microscope/camera/pi.py | 10 ++++---- openflexure_microscope/microscope.py | 23 ++++++++----------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py index 37cbe8cb..343a2c02 100644 --- a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py +++ b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py @@ -21,8 +21,8 @@ def recalibrate(microscope): """ scamera = microscope.camera with scamera.lock: - assert not scamera.status["record_active"], "Can't recalibrate while recording!" - streaming = scamera.status["stream_active"] + assert not scamera.record_active, "Can't recalibrate while recording!" + streaming = scamera.stream_active if streaming: logging.info("Stopping stream before recalibration") scamera.stop_stream_recording(resolution=(640, 480)) diff --git a/openflexure_microscope/api/v2/views/actions/stage.py b/openflexure_microscope/api/v2/views/actions/stage.py index 8b1020eb..6aa969bb 100644 --- a/openflexure_microscope/api/v2/views/actions/stage.py +++ b/openflexure_microscope/api/v2/views/actions/stage.py @@ -53,7 +53,7 @@ class MoveStageAPI(View): logging.warning("Unable to move. No stage found.") # TODO: Make schema for microscope status - return jsonify(microscope.status["stage"]["position"]) + return jsonify(microscope.state["stage"]["position"]) @ThingAction diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 8f2a4d94..d1b71141 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -333,13 +333,13 @@ class PiCameraStreamer(BaseCamera): Change the camera zoom, handling re-centering and scaling. """ with self.lock: - self.status["zoom_value"] = float(zoom_value) - if self.status["zoom_value"] < 1: - self.status["zoom_value"] = 1 + self.zoom_value = float(zoom_value) + if self.zoom_value < 1: + self.zoom_value = 1 # Richard's code for zooming ! fov = self.camera.zoom centre = np.array([fov[0] + fov[2] / 2.0, fov[1] + fov[3] / 2.0]) - size = 1.0 / self.status["zoom_value"] + size = 1.0 / self.zoom_value # If the new zoom value would be invalid, move the centre to # keep it within the camera's sensor (this is only relevant # when zooming out, if the FoV is not centred on (0.5, 0.5) @@ -552,7 +552,7 @@ class PiCameraStreamer(BaseCamera): if isinstance(output, CaptureObject): target = output.file else: - target = target + target = output with self.lock: logging.info("Capturing to {}".format(output)) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index b061bf32..f1023e13 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -76,7 +76,7 @@ class Microscope: ### Detector if configuration.get("camera"): camera_type = configuration["camera"].get("type") - if camera_type == "PiCamera" or camera_type == "PiCameraStreamer": + if camera_type in ("PiCamera", "PiCameraStreamer"): try: self.camera = PiCameraStreamer() except Exception as e: @@ -87,12 +87,8 @@ class Microscope: if configuration.get("stage"): stage_type = configuration["stage"].get("type") stage_port = configuration["stage"].get("port") - if stage_type == "SangaBoard" or camera_type == "SangaStage": - try: - self.stage = SangaStage(port=stage_port) - except Exception as e: - logging.error(e) - logging.warning("No compatible stage hardware found.") + if stage_type in ("SangaBoard", "SangaStage"): + self.stage = SangaStage(port=stage_port) ### Fallbacks if not self.camera: @@ -180,13 +176,14 @@ class Microscope: # Read LST. Returns None if no LST is active lst_arr = self.camera.read_lens_shading_table() - b64_string, dtype, shape = serialise_array_b64(lst_arr) + if lst_arr: + b64_string, dtype, shape = serialise_array_b64(lst_arr) - settings_current["camera"]["lens_shading_table"] = { - "b64_string": b64_string, - "dtype": dtype, - "shape": shape, - } + settings_current["camera"]["lens_shading_table"] = { + "b64_string": b64_string, + "dtype": dtype, + "shape": shape, + } # If attached to a stage if self.stage: From e9e2419fe4648f3ef8508a63a873b1da18d7f79c Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 16:21:38 +0000 Subject: [PATCH 14/70] Renamed capture.filename to capture.name --- openflexure_microscope/api/v2/views/captures.py | 4 ++-- openflexure_microscope/camera/capture.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index c5e00623..097ff665 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -20,7 +20,7 @@ class CaptureSchema(Schema): data_key="path", description="Path of file on microscope device" ) exists = fields.Bool(data_key="available") - filename = fields.String() + name = fields.String() metadata = fields.Dict() links = fields.Dict() @@ -50,7 +50,7 @@ class CaptureSchema(Schema): "href": url_for( CaptureDownload.endpoint, id=data.id, - filename=data.filename, + filename=data.name, _external=True, ), "mimetype": "image/jpeg", diff --git a/openflexure_microscope/camera/capture.py b/openflexure_microscope/camera/capture.py index e7264b6c..81edff1a 100644 --- a/openflexure_microscope/camera/capture.py +++ b/openflexure_microscope/camera/capture.py @@ -152,11 +152,11 @@ class CaptureObject(object): Args: filepath (str): String of the full file path, including file format extension """ - # Split the full file path into a folder and a filename - self.filefolder, self.filename = os.path.split(filepath) - # Split the filename out from it's file extension - self.basename = os.path.splitext(self.filename)[0] - self.format = self.filename.split(".")[-1] + # Split the full file path into a folder and a name + self.filefolder, self.name = os.path.split(filepath) + # Split the name out from it's file extension + self.basename = os.path.splitext(self.name)[0] + self.format = self.name.split(".")[-1] @property def exists(self) -> bool: @@ -243,7 +243,7 @@ class CaptureObject(object): d = { "image": { "id": self.id, - "name": self.filename, + "name": self.name, "acquisitionDate": self.datetime.isoformat(), "format": self.format, "tags": self.tags, @@ -262,7 +262,7 @@ class CaptureObject(object): """ # Create basic state dictionary - d = {"path": self.file, "filename": self.filename, "metadata": self.metadata} + d = {"path": self.file, "name": self.name, "metadata": self.metadata} # Combined availability of data if self.exists: From 9b4bf4c8fceb24584e97b767dcbebd666e5f0088 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 17:43:52 +0000 Subject: [PATCH 15/70] Moved status routes to instrument/state --- .../example_extension/03_marshaling_data.py | 2 +- .../example_extension/04_properties.py | 2 +- .../example_extension/05_actions.py | 2 +- docs/source/extensions/marshaling.rst | 2 +- openflexure_microscope/api/app.py | 22 ++++++++++--------- .../api/default_extensions/zip_builder.py | 14 +++++------- .../api/v2/views/__init__.py | 2 +- .../api/v2/views/actions/camera.py | 8 +++---- .../api/v2/views/actions/stage.py | 6 ++--- .../api/v2/views/{state.py => instrument.py} | 4 ++-- openflexure_microscope/camera/base.py | 14 ------------ openflexure_microscope/camera/pi.py | 17 +++++++------- openflexure_microscope/config.py | 5 ++++- openflexure_microscope/microscope.py | 8 +++---- openflexure_microscope/stage/mock.py | 6 ++--- openflexure_microscope/stage/sanga.py | 2 +- 16 files changed, 52 insertions(+), 64 deletions(-) rename openflexure_microscope/api/v2/views/{state.py => instrument.py} (98%) diff --git a/docs/source/extensions/example_extension/03_marshaling_data.py b/docs/source/extensions/example_extension/03_marshaling_data.py index f9ad668d..d3179c10 100644 --- a/docs/source/extensions/example_extension/03_marshaling_data.py +++ b/docs/source/extensions/example_extension/03_marshaling_data.py @@ -14,7 +14,7 @@ from labthings.server import fields class MicroscopeIdentifySchema(Schema): name = fields.String() # Microscopes name id = fields.UUID() # Microscopes unique ID - status = fields.Dict() # Status dictionary + state = fields.Dict() # Status dictionary camera = fields.String() # Camera object (represented as a string) stage = fields.String() # Stage object (represented as a string) diff --git a/docs/source/extensions/example_extension/04_properties.py b/docs/source/extensions/example_extension/04_properties.py index 45ee6c18..922270a5 100644 --- a/docs/source/extensions/example_extension/04_properties.py +++ b/docs/source/extensions/example_extension/04_properties.py @@ -19,7 +19,7 @@ from labthings.server import fields class MicroscopeIdentifySchema(Schema): name = fields.String() # Microscopes name id = fields.UUID() # Microscopes unique ID - status = fields.Dict() # Status dictionary + state = fields.Dict() # Status dictionary camera = fields.String() # Camera object (represented as a string) stage = fields.String() # Stage object (represented as a string) diff --git a/docs/source/extensions/example_extension/05_actions.py b/docs/source/extensions/example_extension/05_actions.py index 027580e8..dc3826a0 100644 --- a/docs/source/extensions/example_extension/05_actions.py +++ b/docs/source/extensions/example_extension/05_actions.py @@ -24,7 +24,7 @@ import io # Used in our capture action class MicroscopeIdentifySchema(Schema): name = fields.String() # Microscopes name id = fields.UUID() # Microscopes unique ID - status = fields.Dict() # Status dictionary + state = fields.Dict() # Status dictionary camera = fields.String() # Camera object (represented as a string) stage = fields.String() # Stage object (represented as a string) diff --git a/docs/source/extensions/marshaling.rst b/docs/source/extensions/marshaling.rst index 10ab188e..1f09162a 100644 --- a/docs/source/extensions/marshaling.rst +++ b/docs/source/extensions/marshaling.rst @@ -107,7 +107,7 @@ We start by creating a schema to describe how to serialise a :py:class:`openflex class MicroscopeIdentifySchema(Schema): name = fields.String() # Microscopes name id = fields.UUID() # Microscopes unique ID - status = fields.Dict() # Status dictionary + state = fields.Dict() # Status dictionary camera = fields.String() # Camera object (represented as a string) stage = fields.String() # Stage object (represented as a string) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index e3aa1404..ce0d1068 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -94,16 +94,18 @@ labthing.add_view(views.CaptureDownload, f"/captures//download/") labthing.add_view(views.CaptureTags, f"/captures//tags") labthing.add_view(views.CaptureAnnotations, f"/captures//annotations") -# Attach settings and status resources -labthing.add_view(views.SettingsProperty, f"/settings") -labthing.add_root_link(views.SettingsProperty, "settings") -labthing.add_view(views.NestedSettingsProperty, "/settings/") -labthing.add_view(views.StatusProperty, "/status") -labthing.add_view(views.NestedStatusProperty, "/status/") -labthing.add_root_link(views.StatusProperty, "status") -labthing.add_view(views.ConfigurationProperty, "/configuration") -labthing.add_view(views.NestedConfigurationProperty, "/configuration/") -labthing.add_root_link(views.ConfigurationProperty, "configuration") +# Attach settings and state resources +labthing.add_view(views.SettingsProperty, f"/instrument/settings") +labthing.add_root_link(views.SettingsProperty, "instrumentSettings") +labthing.add_view(views.NestedSettingsProperty, "/instrument/settings/") +labthing.add_view(views.StateProperty, "/instrument/state") +labthing.add_view(views.NestedStateProperty, "/instrument/state/") +labthing.add_root_link(views.StateProperty, "instrumentState") +labthing.add_view(views.ConfigurationProperty, "/instrument/configuration") +labthing.add_view( + views.NestedConfigurationProperty, "/instrument/configuration/" +) +labthing.add_root_link(views.ConfigurationProperty, "instrumentConfiguration") # Attach streams resources diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index 02032c65..a57a3ad1 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -73,17 +73,15 @@ class ZipManager: # Update task progress update_task_progress(int((index / n_files) * 100)) - session_id = uuid.uuid4() - session_key = str(session_id) - # self.session_zips[session_id] = fp - self.session_zips[session_key] = { + session_id = str(uuid.uuid4()) + self.session_zips[session_id] = { "id": session_id, "fp": fp, "data_size": data_size_megabytes, "zip_size": os.path.getsize(fp.name) * 1e-6, } - return self.session_zips[session_key] + return self.session_zips[session_id] def zip_from_id(self, session_id): return self.session_zips[session_id]["fp"] @@ -103,13 +101,13 @@ class ZipBuilderAPIView(View): task = taskify(default_zip_manager.build_zip_from_capture_ids)(microscope, ids) # Return a handle on the autofocus task - return jsonify(task.state), 201 + return task.state, 201 @ThingProperty class ZipListAPIView(View): def get(self): - return jsonify(default_zip_manager.session_zips) + return default_zip_manager.session_zips class ZipGetterAPIView(View): @@ -141,7 +139,7 @@ class ZipGetterAPIView(View): del default_zip_manager.session_zips[session_id] - return jsonify({"return": session_id}) + return {"return": session_id} zip_extension_v2 = BaseExtension("org.openflexure.zipbuilder", version="2.0.0-beta.1") diff --git a/openflexure_microscope/api/v2/views/__init__.py b/openflexure_microscope/api/v2/views/__init__.py index a998c703..3c1fae87 100644 --- a/openflexure_microscope/api/v2/views/__init__.py +++ b/openflexure_microscope/api/v2/views/__init__.py @@ -1,4 +1,4 @@ from .actions import enabled_root_actions from .captures import * -from .state import * +from .instrument import * from .streams import * diff --git a/openflexure_microscope/api/v2/views/actions/camera.py b/openflexure_microscope/api/v2/views/actions/camera.py index 8b618193..62f777d7 100644 --- a/openflexure_microscope/api/v2/views/actions/camera.py +++ b/openflexure_microscope/api/v2/views/actions/camera.py @@ -163,8 +163,8 @@ class GPUPreviewStartAPI(View): microscope.camera.start_preview(fullscreen=fullscreen, window=window) - # TODO: Make schema for microscope status - return jsonify(microscope.status) + # TODO: Make schema for microscope state + return jsonify(microscope.state) @ThingAction @@ -175,5 +175,5 @@ class GPUPreviewStopAPI(View): """ microscope = find_component("org.openflexure.microscope") microscope.camera.stop_preview() - # TODO: Make schema for microscope status - return jsonify(microscope.status) + # TODO: Make schema for microscope state + return jsonify(microscope.state) diff --git a/openflexure_microscope/api/v2/views/actions/stage.py b/openflexure_microscope/api/v2/views/actions/stage.py index 6aa969bb..5cf4059e 100644 --- a/openflexure_microscope/api/v2/views/actions/stage.py +++ b/openflexure_microscope/api/v2/views/actions/stage.py @@ -52,7 +52,7 @@ class MoveStageAPI(View): else: logging.warning("Unable to move. No stage found.") - # TODO: Make schema for microscope status + # TODO: Make schema for microscope state return jsonify(microscope.state["stage"]["position"]) @@ -66,5 +66,5 @@ class ZeroStageAPI(View): microscope = find_component("org.openflexure.microscope") microscope.stage.zero_position() - # TODO: Make schema for microscope status - return jsonify(microscope.status["stage"]) + # TODO: Make schema for microscope state + return jsonify(microscope.state["stage"]) diff --git a/openflexure_microscope/api/v2/views/state.py b/openflexure_microscope/api/v2/views/instrument.py similarity index 98% rename from openflexure_microscope/api/v2/views/state.py rename to openflexure_microscope/api/v2/views/instrument.py index 7f22323c..633d73ff 100644 --- a/openflexure_microscope/api/v2/views/state.py +++ b/openflexure_microscope/api/v2/views/instrument.py @@ -72,7 +72,7 @@ class NestedSettingsProperty(View): @ThingProperty -class StatusProperty(View): +class StateProperty(View): def get(self): """ Show current read-only state of the microscope @@ -82,7 +82,7 @@ class StatusProperty(View): @Tag("properties") -class NestedStatusProperty(View): +class NestedStateProperty(View): @doc_response(404, description="Status key cannot be found") def get(self, route): """ diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index e823cfd0..2dd16dc4 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -91,20 +91,6 @@ class CameraEvent(object): class BaseCamera(metaclass=ABCMeta): """ Base implementation of StreamingCamera. - - Attributes: - thread: Background thread reading frames from camera - camera: Camera object - lock (:py:class:`labthings.lock.StrictLock`): Strict lock controlling thread - access to stage hardware - frame (bytes): Current frame is stored here by background thread - last_access (time): Time of last client access to the camera - stream_timeout (int): Number of inactive seconds before timing out the stream - stream_timeout_enabled (bool): Enable or disable timing out the stream - status (dict): Dictionary for capture state - paths (dict): Dictionary of capture paths - images (list): List of image capture objects - videos (list): List of video capture objects """ def __init__(self): diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index d1b71141..85628042 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -85,7 +85,7 @@ class PiCameraStreamer(BaseCamera): picamera.PiCamera() ) #: :py:class:`picamera.PiCamera`: Picamera object - # Store status of PiCameraStreamer + # Store state of PiCameraStreamer self.preview_active = False # Reset variable states @@ -157,7 +157,7 @@ class PiCameraStreamer(BaseCamera): "picamera_lst_path": self.picamera_lst_path if os.path.isfile(self.picamera_lst_path) else None, - "picamera_settings": {}, + "picamera": {}, } ) @@ -166,7 +166,7 @@ class PiCameraStreamer(BaseCamera): try: value = getattr(self.camera, key) logging.debug("Reading PiCamera().{}: {}".format(key, value)) - conf_dict["picamera_settings"][key] = value + conf_dict["picamera"][key] = value except AttributeError: logging.debug("Unable to read PiCamera attribute {}".format(key)) @@ -204,14 +204,14 @@ class PiCameraStreamer(BaseCamera): paused_stream = True # Remember to unpause stream when done # PiCamera parameters - if "picamera_settings" in config: # If new settings are given + if "picamera" in config: # If new settings are given self.apply_picamera_settings( - config["picamera_settings"], pause_for_effect=True + config["picamera"], pause_for_effect=True ) # PiCameraStreamer parameters for key, value in config.items(): # For each provided setting - if (key != "picamera_settings") and hasattr(self, key): + if (key != "picamera") and hasattr(self, key): setattr(self, key, value) # Handle lens shading if camera supports it @@ -412,7 +412,7 @@ class PiCameraStreamer(BaseCamera): quality=quality, ) - # Update status dictionary + # Update state self.record_active = True return output @@ -432,7 +432,7 @@ class PiCameraStreamer(BaseCamera): self.camera.stop_recording(splitter_port=2) logging.info("Recording stopped") - # Update status dictionary + # Update state self.record_active = False def stop_stream_recording( @@ -681,7 +681,6 @@ class PiCameraStreamer(BaseCamera): # Start stream recording (and set resolution) self.start_stream_recording() - # Update status logging.debug("STREAM ACTIVE") # While the iterator is not closed diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index 5172a07c..8bbe796a 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -94,11 +94,14 @@ class JSONEncoder(flask.json.JSONEncoder): # Numpy arrays elif isinstance(o, np.ndarray): return o.tolist() + # UUIDs + elif isinstance(o, UUID): + return str(o) else: # call base class implementation which takes care of # raising exceptions for unsupported types try: - return json.JSONEncoder.default(self, o) + return flask.json.JSONEncoder.default(self, o) # if it's some mystery object, just return a string representation except TypeError: return str(o) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 19c01be7..1e397339 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -120,13 +120,13 @@ class Microscope: else: return False - # Create unified status + # Create unified state @property def state(self): - """Dictionary of the basic microscope status. + """Dictionary of the basic microscope state. Return: - dict: Dictionary containing complete microscope status + dict: Dictionary containing complete microscope state """ state = {"camera": self.camera.state, "stage": self.stage.state} return state @@ -216,7 +216,7 @@ class Microscope: initial_configuration = self.configuration_file.load() current_configuration = { - "@application": { + "application": { "name": "openflexure_microscope", "version": pkg_resources.get_distribution( "openflexure_microscope" diff --git a/openflexure_microscope/stage/mock.py b/openflexure_microscope/stage/mock.py index e4810f3c..fe523c64 100644 --- a/openflexure_microscope/stage/mock.py +++ b/openflexure_microscope/stage/mock.py @@ -18,9 +18,9 @@ class MissingStage(BaseStage): @property def state(self): - """The general status dictionary of the board.""" - status = {"position": self.position_map} - return status + """The general state dictionary of the board.""" + state = {"position": self.position_map} + return state @property def configuration(self): diff --git a/openflexure_microscope/stage/sanga.py b/openflexure_microscope/stage/sanga.py index 11ffbbc6..d5ffcf15 100644 --- a/openflexure_microscope/stage/sanga.py +++ b/openflexure_microscope/stage/sanga.py @@ -33,7 +33,7 @@ class SangaStage(BaseStage): @property def state(self): - """The general status dictionary of the board.""" + """The general state dictionary of the board.""" return {"position": self.position_map} @property From c650912ffe4bdeb322f9b5ac8d65b8662da9a267 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 18:06:34 +0000 Subject: [PATCH 16/70] Updated LabThings --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 923babf7..6ddcd5b8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -198,7 +198,7 @@ marshmallow = "^3.3.0" webargs = "^5.5.2" [package.source] -reference = "cb1ef6a1f8849f4d58601a7a9617ffea12703f39" +reference = "916cc21d120cf4fc7064999082b40bedfec9b3ed" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] From e7d1f211bc97a9e143790837ee6ebc9477324307 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 30 Jan 2020 14:20:40 +0000 Subject: [PATCH 17/70] Removed forced string serialisation of objects --- openflexure_microscope/config.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index 8bbe796a..2b2abff4 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -100,11 +100,8 @@ class JSONEncoder(flask.json.JSONEncoder): else: # call base class implementation which takes care of # raising exceptions for unsupported types - try: - return flask.json.JSONEncoder.default(self, o) - # if it's some mystery object, just return a string representation - except TypeError: - return str(o) + return flask.json.JSONEncoder.default(self, o) + # HANDLE BASIC LOADING AND SAVING OF SETTINGS FILES From 953a5d7004e738a9c059511b5c2d9f4095001733 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 30 Jan 2020 14:21:18 +0000 Subject: [PATCH 18/70] Fixed checking if LST exists --- openflexure_microscope/microscope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 1e397339..afdb4d22 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -176,7 +176,7 @@ class Microscope: # Read LST. Returns None if no LST is active lst_arr = self.camera.read_lens_shading_table() - if lst_arr: + if lst_arr is not None: b64_string, dtype, shape = serialise_array_b64(lst_arr) settings_current["camera"]["lens_shading_table"] = { From 945c7e4e27066f94b088c85dc79d1190a6dc53d1 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 30 Jan 2020 14:21:40 +0000 Subject: [PATCH 19/70] Fixed checking if LST path exists --- openflexure_microscope/camera/pi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 85628042..0f23d5f3 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -155,7 +155,7 @@ class PiCameraStreamer(BaseCamera): "numpy_resolution": self.numpy_resolution, "jpeg_quality": self.jpeg_quality, "picamera_lst_path": self.picamera_lst_path - if os.path.isfile(self.picamera_lst_path) + if (self.picamera_lst_path and os.path.isfile(self.picamera_lst_path)) else None, "picamera": {}, } From 65fc58c65149f000f63819bee40649679120a9b3 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 30 Jan 2020 14:22:03 +0000 Subject: [PATCH 20/70] Fixed scan to use newer fast autofocus --- .../api/default_extensions/autofocus.py | 19 +++++++++++-------- .../api/default_extensions/scan.py | 6 ++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/autofocus.py b/openflexure_microscope/api/default_extensions/autofocus.py index ce873a7f..5fbc9f7c 100644 --- a/openflexure_microscope/api/default_extensions/autofocus.py +++ b/openflexure_microscope/api/default_extensions/autofocus.py @@ -1,7 +1,7 @@ from labthings.server.find import find_component from labthings.server.extensions import BaseExtension from labthings.server.view import View -from labthings.server.decorators import ThingAction, ThingProperty +from labthings.server.decorators import ThingAction, ThingProperty, marshal_task from openflexure_microscope.devel import JsonResponse, request, jsonify, taskify, abort from openflexure_microscope.utilities import set_properties @@ -195,7 +195,7 @@ def move_and_find_focus(microscope, dz): def fast_autofocus(microscope, dz=2000, backlash=None): """Perform a down-up-down-up autofocus""" - with monitor_sharpness(microscope) as m: + with monitor_sharpness(microscope) as m, microscope.camera.lock: i, z = m.focus_rel(-dz / 2) i, z = m.focus_rel(dz) fz = m.sharpest_z_on_move(i) @@ -208,7 +208,7 @@ def fast_autofocus(microscope, dz=2000, backlash=None): def fast_up_down_up_autofocus( - microscope, dz=2000, target_z=0, initial_move_up=True, mini_backlash=150 + microscope, dz=2000, target_z=0, initial_move_up=True, mini_backlash=25 ): """Autofocus by measuring on the way down, and moving back up with feedback. @@ -239,7 +239,7 @@ def fast_up_down_up_autofocus( from the starting position. Mostly useful if you're able to combine the initial move with something else, e.g. moving to the next scan point. - mini_backlash: (optional, default 50) is a small extra move made in step + mini_backlash: (optional, default 25) is a small extra move made in step 3 to help counteract backlash. It should be small enough that you would always expect there to be greater backlash than this. Too small might slightly hurt accuracy, but is unlikely to be a big issue. Too big @@ -305,6 +305,7 @@ class AutofocusAPI(View): Run a standard autofocus """ + @marshal_task def post(self): payload = JsonResponse(request) microscope = find_component("org.openflexure.microscope") @@ -320,7 +321,7 @@ class AutofocusAPI(View): task = taskify(autofocus)(microscope, dz) # return a handle on the autofocus task - return jsonify(task.state), 201 + return task else: abort(503, "No stage connected. Unable to autofocus.") @@ -332,6 +333,7 @@ class FastAutofocusAPI(View): Run a fast autofocus """ + @marshal_task def post(self): payload = JsonResponse(request) microscope = find_component("org.openflexure.microscope") @@ -341,16 +343,16 @@ class FastAutofocusAPI(View): # Figure out the parameters to use dz = payload.param("dz", default=2000, convert=int) - backlash = payload.param("backlash", default=0, convert=int) + backlash = payload.param("backlash", default=25, convert=int) if backlash < 0: backlash = 0 if microscope.has_real_stage(): logging.info("Running autofocus...") - task = taskify(fast_autofocus)(microscope, dz, backlash=backlash) + task = taskify(fast_up_down_up_autofocus)(microscope, dz=dz, mini_backlash=backlash) # return a handle on the autofocus task - return jsonify(task.state), 201 + return task else: abort(503, "No stage connected. Unable to autofocus.") @@ -361,6 +363,7 @@ autofocus_extension_v2 = BaseExtension( ) autofocus_extension_v2.add_method(fast_autofocus, "fast_autofocus") +autofocus_extension_v2.add_method(fast_up_down_up_autofocus, "fast_up_down_up_autofocus") autofocus_extension_v2.add_method(autofocus, "autofocus") autofocus_extension_v2.add_view(MeasureSharpnessAPI, "/measure_sharpness") diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index 261b15b3..94c6f327 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -197,13 +197,15 @@ def tile( # Refocus if autofocus_enabled: if fast_autofocus: - autofocus_extension.fast_autofocus( + # Run fast autofocus. Client should provide dz ~ 2000 + autofocus_extension.fast_up_down_up_autofocus( + microscope, dz=autofocus_dz, target_z=-z_stack_dz / 2.0, # Finish below the focus initial_move_up=False, # We're already at the top of the scan ) else: - logging.debug("Running autofocus") + # Run slow autofocus. Client should provide dz ~ 50 autofocus_extension.autofocus( range(-3 * autofocus_dz, 4 * autofocus_dz, autofocus_dz) ) From 1ba840933be302e782f7b552c97d319b6a7a8487 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 30 Jan 2020 14:22:52 +0000 Subject: [PATCH 21/70] Fixed marshaling in zip_builder extension --- .../api/default_extensions/zip_builder.py | 77 +++++++++++++------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index a57a3ad1..6f76ab50 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -16,9 +16,47 @@ import logging from labthings.server.find import find_component from labthings.server.view import View +from labthings.server.schema import Schema +from labthings.server import fields from labthings.server.extensions import BaseExtension -from labthings.server.decorators import ThingAction, ThingProperty +from labthings.server.decorators import ThingAction, ThingProperty, marshal_task, marshal_with, pre_dump +class ZipObjectSchema(Schema): + id = fields.String() + data_size = fields.Number() + zip_size = fields.Number() + + @pre_dump + def generate_links(self, data, **kwargs): + data.links = { + "download": { + "href": url_for( + ZipGetterAPIView.endpoint, + session_id=data.id + ), + **description_from_view(ZipGetterAPIView), + }, + } + return data + + +class ZipObjectDescription: + 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 + + def close(self): + logging.debug(self.fp.name) + self.fp.close() + os.unlink(self.fp.name) + + assert not os.path.exists(self.fp.name) + + def __del__(self): + self.close() + self.super().__del__() class ZipManager: """ @@ -74,17 +112,16 @@ class ZipManager: update_task_progress(int((index / n_files) * 100)) session_id = str(uuid.uuid4()) - self.session_zips[session_id] = { - "id": session_id, - "fp": fp, - "data_size": data_size_megabytes, - "zip_size": os.path.getsize(fp.name) * 1e-6, - } + session_description = ZipObjectDescription(session_id, fp, data_size=data_size_megabytes) + self.session_zips[session_id] = session_description return self.session_zips[session_id] - def zip_from_id(self, session_id): - return self.session_zips[session_id]["fp"] + def marshaled_build_zip_from_capture_ids(self, *args, **kwargs): + return ZipObjectSchema().dump(self.build_zip_from_capture_ids(*args, **kwargs)) + + def zip_fp_from_id(self, session_id): + return self.session_zips[session_id].fp # Create a global ZIP manager @@ -93,21 +130,23 @@ default_zip_manager = ZipManager() @ThingAction class ZipBuilderAPIView(View): + @marshal_task def post(self): ids = list(JsonResponse(request).json) microscope = find_component("org.openflexure.microscope") - task = taskify(default_zip_manager.build_zip_from_capture_ids)(microscope, ids) + task = taskify(default_zip_manager.marshaled_build_zip_from_capture_ids)(microscope, ids) # Return a handle on the autofocus task - return task.state, 201 + return task @ThingProperty class ZipListAPIView(View): + @marshal_with(ZipObjectSchema(many=True)) def get(self): - return default_zip_manager.session_zips + return default_zip_manager.session_zips.values() class ZipGetterAPIView(View): @@ -118,7 +157,7 @@ class ZipGetterAPIView(View): logging.info(f"Session ID: {session_id}") return send_file( - default_zip_manager.zip_from_id(session_id).name, + default_zip_manager.zip_fp_from_id(session_id).name, mimetype="application/zip", as_attachment=True, attachment_filename=f"{session_id}.zip", @@ -128,15 +167,9 @@ 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}") - - fp = default_zip_manager.zip_from_id(session_id) - logging.debug(fp.name) - fp.close() - os.unlink(fp.name) - - assert not os.path.exists(fp.name) - + # Close the file + default_zip_manager.session_zips[session_id].close() + # Delete the file reference del default_zip_manager.session_zips[session_id] return {"return": session_id} From ed9b6231990078d4d10014eed0fb7899b1d2fba8 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 30 Jan 2020 14:35:33 +0000 Subject: [PATCH 22/70] Fixed ZIP builder extension --- .../api/default_extensions/zip_builder.py | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index 6f76ab50..3b4840f0 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -6,7 +6,7 @@ from openflexure_microscope.devel import ( update_task_progress, ) -from flask import send_file, abort +from flask import send_file, abort, url_for import uuid import os @@ -19,23 +19,31 @@ from labthings.server.view import View from labthings.server.schema import Schema from labthings.server import fields from labthings.server.extensions import BaseExtension -from labthings.server.decorators import ThingAction, ThingProperty, marshal_task, marshal_with, pre_dump +from labthings.server.utilities import description_from_view +from labthings.server.decorators import ( + ThingAction, + ThingProperty, + marshal_task, + marshal_with, + pre_dump, +) + class ZipObjectSchema(Schema): id = fields.String() data_size = fields.Number() zip_size = fields.Number() + links = fields.Dict() @pre_dump def generate_links(self, data, **kwargs): data.links = { "download": { "href": url_for( - ZipGetterAPIView.endpoint, - session_id=data.id + ZipGetterAPIView.endpoint, session_id=data.id, _external=True ), **description_from_view(ZipGetterAPIView), - }, + } } return data @@ -56,7 +64,7 @@ class ZipObjectDescription: def __del__(self): self.close() - self.super().__del__() + class ZipManager: """ @@ -112,7 +120,9 @@ class ZipManager: update_task_progress(int((index / n_files) * 100)) session_id = str(uuid.uuid4()) - session_description = ZipObjectDescription(session_id, fp, data_size=data_size_megabytes) + session_description = ZipObjectDescription( + session_id, fp, data_size=data_size_megabytes + ) self.session_zips[session_id] = session_description return self.session_zips[session_id] @@ -123,6 +133,10 @@ class ZipManager: def zip_fp_from_id(self, session_id): return self.session_zips[session_id].fp + def __del__(self): + for zd in self.session_zips.values(): + zd.close() + # Create a global ZIP manager default_zip_manager = ZipManager() @@ -136,7 +150,9 @@ class ZipBuilderAPIView(View): ids = list(JsonResponse(request).json) microscope = find_component("org.openflexure.microscope") - task = taskify(default_zip_manager.marshaled_build_zip_from_capture_ids)(microscope, ids) + task = taskify(default_zip_manager.marshaled_build_zip_from_capture_ids)( + microscope, ids + ) # Return a handle on the autofocus task return task @@ -150,7 +166,14 @@ class ZipListAPIView(View): class ZipGetterAPIView(View): + """ + Download or delete a particular capture collection ZIP file + """ + def get(self, session_id): + """ + Download a particular capture collection ZIP file + """ if not session_id in default_zip_manager.session_zips: return abort(404) # 404 Not Found @@ -164,6 +187,9 @@ class ZipGetterAPIView(View): ) def delete(self, session_id): + """ + Close and delete a particular capture collection ZIP file + """ if not session_id in default_zip_manager.session_zips: return abort(404) # 404 Not Found @@ -175,7 +201,11 @@ class ZipGetterAPIView(View): return {"return": session_id} -zip_extension_v2 = BaseExtension("org.openflexure.zipbuilder", version="2.0.0-beta.1") +zip_extension_v2 = BaseExtension( + "org.openflexure.zipbuilder", + version="2.0.0-beta.1", + description="Build and download capture collections as ZIP files", +) zip_extension_v2.add_view(ZipGetterAPIView, "/get/") zip_extension_v2.add_view(ZipListAPIView, "/get") From e1e611236d67f56dc0bc346cb1ad2d773b4126b2 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 30 Jan 2020 14:37:05 +0000 Subject: [PATCH 23/70] Updated lockfile --- poetry.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6ddcd5b8..559583d8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -195,10 +195,10 @@ Flask = "^1.1.1" apispec = "^3.2.0" flask-cors = "^3.0.8" marshmallow = "^3.3.0" -webargs = "^5.5.2" +webargs = "^5.5.3" [package.source] -reference = "916cc21d120cf4fc7064999082b40bedfec9b3ed" +reference = "696a00cc371603580d4c2346100d65e432ab36de" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -265,6 +265,7 @@ version = "1.13.1b0" reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb" type = "git" url = "https://github.com/rwb27/picamera.git" + [[package]] category = "main" description = "Python Imaging Library (Fork)" From fd78dc3e6128467845ebd4291fc998b264dc354f Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 30 Jan 2020 14:49:03 +0000 Subject: [PATCH 24/70] Added FoV back while we wait on CwL calibration extension --- openflexure_microscope/microscope_settings.default.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openflexure_microscope/microscope_settings.default.json b/openflexure_microscope/microscope_settings.default.json index fa0fcb28..5c408d2e 100644 --- a/openflexure_microscope/microscope_settings.default.json +++ b/openflexure_microscope/microscope_settings.default.json @@ -1,3 +1,7 @@ { + "fov": [ + 4100, + 3146 + ], "jpeg_quality": 100 } \ No newline at end of file From 011ed976f1412a62597cd0d3a00c6e1da567f332 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 30 Jan 2020 15:52:28 +0000 Subject: [PATCH 25/70] Iterated to beta 4 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f9ccc42e..29c6e50b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "openflexure_microscope" -version = "2.0.0-beta.3" +version = "2.0.0-beta.4" description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope." authors = [ From ccd5e9b89103426f72cd4b2f1bf9ca996ef13139 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Fri, 31 Jan 2020 11:18:47 +0000 Subject: [PATCH 26/70] Create OpenFlexure var directories if they don't exist --- openflexure_microscope/paths.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/openflexure_microscope/paths.py b/openflexure_microscope/paths.py index 8fbf9b55..b9d26bda 100644 --- a/openflexure_microscope/paths.py +++ b/openflexure_microscope/paths.py @@ -10,22 +10,35 @@ def check_rw(path): def settings_file_path(filename: str): """Generate a full file path for a filename to be stored in server settings folder""" - return os.path.join(OPENFLEXURE_VAR_PATH, "settings", filename) + settings_dir = os.path.join(OPENFLEXURE_VAR_PATH, "settings") + if not os.path.exists(settings_dir): + os.makedirs(settings_dir) + return os.path.join(settings_dir, filename) def data_file_path(filename: str): """Generate a full file path for a filename to be stored in server data folder""" - return os.path.join(OPENFLEXURE_VAR_PATH, "data", filename) + data_dir = os.path.join(OPENFLEXURE_VAR_PATH, "data") + if not os.path.exists(data_dir): + os.makedirs(data_dir) + return os.path.join(data_dir, filename) def extensions_file_path(filename: str): """Generate a full file path for a folder to be stored in server extensions""" - return os.path.join(OPENFLEXURE_VAR_PATH, "extensions", filename) + ext_dir = os.path.join(OPENFLEXURE_VAR_PATH, "extensions") + if not os.path.exists(ext_dir): + os.makedirs(ext_dir) + return os.path.join(ext_dir, filename) def logs_file_path(filename: str): """Generate a full file path for a filename to be stored in server logs""" - return os.path.join(OPENFLEXURE_VAR_PATH, "logs", filename) + logs_dir = os.path.join(OPENFLEXURE_VAR_PATH, "logs") + if not os.path.exists(logs_dir): + os.makedirs(logs_dir) + return os.path.join(logs_dir, filename) + # HANDLE DEFAULTS FILES STORED IN THIS APPLICATION @@ -34,7 +47,9 @@ HERE = os.path.abspath(os.path.dirname(__file__)) #: Path of default (first-run) microscope settings DEFAULT_SETTINGS_FILE_PATH = os.path.join(HERE, "microscope_settings.default.json") #: Path of default (first-run) microscope configuration -DEFAULT_CONFIGURATION_FILE_PATH = os.path.join(HERE, "microscope_configuration.default.json") +DEFAULT_CONFIGURATION_FILE_PATH = os.path.join( + HERE, "microscope_configuration.default.json" +) # BASE PATHS From cf66359559034e1e72e2628cda9e894a4d574030 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 6 Feb 2020 13:35:05 +0000 Subject: [PATCH 27/70] Reimplemented serialising LST --- openflexure_microscope/camera/base.py | 4 -- openflexure_microscope/camera/pi.py | 81 +++++++++------------------ openflexure_microscope/microscope.py | 4 -- openflexure_microscope/stage/base.py | 4 -- openflexure_microscope/utilities.py | 22 ++++++++ 5 files changed, 48 insertions(+), 67 deletions(-) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index 2dd16dc4..f74bbc0a 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -146,10 +146,6 @@ class BaseCamera(metaclass=ABCMeta): """Return the current settings as a dictionary""" return {"paths": self.paths} - def save_settings(self): - """(Optional) Save any settings to disk that need to be stored""" - return - def __enter__(self): """Create camera on context enter.""" return self diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 0f23d5f3..990767d0 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -47,7 +47,11 @@ from .base import BaseCamera, CaptureObject from .set_picamera_gain import set_analog_gain, set_digital_gain from openflexure_microscope.paths import settings_file_path -from openflexure_microscope.utilities import serialise_array_b64 +from openflexure_microscope.utilities import ( + serialise_array_b64, + ndarray_to_json, + json_to_ndarray, +) # MAIN CLASS @@ -154,9 +158,6 @@ class PiCameraStreamer(BaseCamera): "image_resolution": self.image_resolution, "numpy_resolution": self.numpy_resolution, "jpeg_quality": self.jpeg_quality, - "picamera_lst_path": self.picamera_lst_path - if (self.picamera_lst_path and os.path.isfile(self.picamera_lst_path)) - else None, "picamera": {}, } ) @@ -170,12 +171,16 @@ class PiCameraStreamer(BaseCamera): except AttributeError: logging.debug("Unable to read PiCamera attribute {}".format(key)) - return conf_dict + # Include a serialised lens shading table + if ( + hasattr(self.camera, "lens_shading_table") + and getattr(self.camera, "lens_shading_table") is not None + ): + conf_dict["picamera"]["lens_shading_table"] = ndarray_to_json( + getattr(self.camera, "lens_shading_table") + ) - def save_settings(self): - """Save lens-shading table to disk""" - logging.info("Saving picamera_lst to {}".format(self.picamera_lst_path)) - self.save_lens_shading_table() + return conf_dict def update_settings(self, config: dict): """ @@ -209,22 +214,23 @@ class PiCameraStreamer(BaseCamera): config["picamera"], pause_for_effect=True ) + # Handle lens shading if camera supports it + if ( + hasattr(self.camera, "lens_shading_table") + and "lens_shading_table" in config["picamera"] + ): + try: + self.camera.lens_shading_table = json_to_ndarray( + config["picamera"].get("lens_shading_table") + ) + except KeyError as e: + logging.error(e) + # PiCameraStreamer parameters for key, value in config.items(): # For each provided setting if (key != "picamera") and hasattr(self, key): setattr(self, key, value) - # Handle lens shading if camera supports it - if ("picamera_lst_path" in config) and hasattr( - self.camera, "lens_shading_table" - ): - logging.debug( - "Applying lens_shading_table from file: {}".format( - config["picamera_lst_path"] - ) - ) - self.apply_lens_shading_table(config["picamera_lst_path"]) - # If stream was paused to update config, unpause if paused_stream: logging.info("Resuming stream.") @@ -293,41 +299,6 @@ class PiCameraStreamer(BaseCamera): if pause_for_effect: time.sleep(0.2) - def read_lens_shading_table(self): - """ - Read the current lens shading table as a numpy array, if it exists. Return None otherwise. - """ - if hasattr(self.camera, "lens_shading_table"): - return self.camera.lens_shading_table - else: - return None - - def save_lens_shading_table(self): - """ - Save the current lens shading table to an .npy file, if it exists. - """ - logging.debug(self.read_lens_shading_table()) - if self.read_lens_shading_table() is not None: - np.save(self.picamera_lst_path, self.read_lens_shading_table()) - else: - logging.warning("Unable to save a nonexistant lens shading table") - - def apply_lens_shading_table(self, lst_array_or_path): - """ - Apply a lens shading table from an .npy file, or numpy array. - - Args: - lst_array_or_path: Numpy array, or path to .npy file, describing the lens-shading table - """ - if isinstance(lst_array_or_path, np.ndarray): - self.camera.lens_shading_table = lst_array_or_path - elif (type(lst_array_or_path) == str) and os.path.isfile(lst_array_or_path): - self.camera.lens_shading_table = np.load(lst_array_or_path) - else: - logging.error( - "Unsupported or missing data for camera lens_shading_table. Must be numpy ndarray, or .npy file path string. Skipping." - ) - def set_zoom(self, zoom_value: float = 1.0) -> None: """ Change the camera zoom, handling re-centering and scaling. diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index afdb4d22..b1d8d00d 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -205,10 +205,6 @@ class Microscope: # Read curent config current_config = self.read_settings() # Save config to file - if self.camera: - self.camera.save_settings() - if self.stage: - self.stage.save_settings() self.settings_file.save(current_config, backup=True) @property diff --git a/openflexure_microscope/stage/base.py b/openflexure_microscope/stage/base.py index 564ba1a6..b04909b2 100644 --- a/openflexure_microscope/stage/base.py +++ b/openflexure_microscope/stage/base.py @@ -23,10 +23,6 @@ class BaseStage(metaclass=ABCMeta): """Return the current settings as a dictionary""" pass - def save_settings(self): - """(Optional) Save any settings to disk that need to be stored""" - return - @property @abstractmethod def state(self): diff --git a/openflexure_microscope/utilities.py b/openflexure_microscope/utilities.py index bd88664e..27e46212 100644 --- a/openflexure_microscope/utilities.py +++ b/openflexure_microscope/utilities.py @@ -4,6 +4,7 @@ import operator import base64 from uuid import UUID import numpy as np +import logging from collections import abc from functools import reduce from contextlib import contextmanager @@ -21,6 +22,27 @@ def serialise_array_b64(npy_arr): return b64_string, dtype, shape +def ndarray_to_json(arr: np.ndarray): + b64_string, dtype, shape = serialise_array_b64(arr) + return { + "@type": "ndarray", + "dtype": dtype, + "shape": shape, + "base64": b64_string + } + + +def json_to_ndarray(json_dict: dict): + if not json_dict.get("@type") != "ndarray": + logging.warning("No valid @type attribute found. Conversion may fail.") + for required_param in ("dtype", "shape", "base64"): + if not json_dict.get(required_param): + raise KeyError(f"Missing required key {required_param}") + + return deserialise_array_b64(json_dict.get("base64"), json_dict.get("dtype"), json_dict.get("shape")) + + + @contextmanager def set_properties(obj, **kwargs): """A context manager to set, then reset, certain properties of an object. From 2d6ead5f80b5c9fbf8547fde28b6110541a24cd8 Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 7 Feb 2020 17:05:18 +0000 Subject: [PATCH 28/70] Remove PyYAML --- poetry.lock | 545 ++++++++++++++++++++++++++++++++++++++++++------- pyproject.toml | 1 - 2 files changed, 476 insertions(+), 70 deletions(-) diff --git a/poetry.lock b/poetry.lock index 559583d8..6665eb7d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,6 +14,14 @@ optional = false python-versions = ">=3.5" version = "3.2.0" +[package.extras] +dev = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"] +docs = ["marshmallow (>=2.19.2)", "pyyaml (5.2)", "sphinx (2.3.0)", "sphinx-issues (1.2.0)", "sphinx-rtd-theme (0.4.3)"] +lint = ["flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"] +tests = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock"] +validation = ["prance (>=0.11)"] +yaml = ["PyYAML (>=3.10)"] + [[package]] category = "dev" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." @@ -47,6 +55,12 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "19.3.0" +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] + [[package]] category = "dev" description = "Internationalization utilities" @@ -72,6 +86,9 @@ attrs = ">=17.4.0" click = ">=6.5" toml = ">=0.9.4" +[package.extras] +d = ["aiohttp (>=3.3.2)"] + [[package]] category = "dev" description = "Python package for providing Mozilla's CA Bundle." @@ -127,6 +144,11 @@ Werkzeug = ">=0.15" click = ">=5.1" itsdangerous = ">=0.24" +[package.extras] +dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +dotenv = ["python-dotenv"] + [[package]] category = "main" description = "A Flask extension adding a decorator for CORS support" @@ -163,6 +185,12 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "4.3.21" +[package.extras] +pipfile = ["pipreqs", "requirementslib"] +pyproject = ["toml"] +requirements = ["pipreqs", "pip-api"] +xdg_home = ["appdirs (>=1.4.0)"] + [[package]] category = "main" description = "Various helpers to pass data to untrusted environments and back." @@ -177,11 +205,14 @@ description = "A very fast and expressive template engine." name = "jinja2" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.11.0" +version = "2.11.1" [package.dependencies] MarkupSafe = ">=0.23" +[package.extras] +i18n = ["Babel (>=0.8)"] + [[package]] category = "main" description = "" @@ -198,7 +229,7 @@ marshmallow = "^3.3.0" webargs = "^5.5.3" [package.source] -reference = "696a00cc371603580d4c2346100d65e432ab36de" +reference = "8af573592efc59bb0b3f0a478391b4bc32f4b572" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -223,7 +254,13 @@ description = "A lightweight library for converting complex datatypes to and fro name = "marshmallow" optional = false python-versions = ">=3.5" -version = "3.3.0" +version = "3.4.0" + +[package.extras] +dev = ["pytest", "pytz", "simplejson", "mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.3)", "pre-commit (>=1.20,<3.0)", "tox"] +docs = ["sphinx (2.3.1)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"] +lint = ["mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.3)", "pre-commit (>=1.20,<3.0)"] +tests = ["pytest", "pytz", "simplejson"] [[package]] category = "dev" @@ -261,6 +298,11 @@ optional = true python-versions = "*" version = "1.13.1b0" +[package.extras] +array = ["numpy"] +doc = ["sphinx"] +test = ["coverage", "pytest", "mock", "pillow", "numpy"] + [package.source] reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb" type = "git" @@ -331,14 +373,6 @@ optional = false python-versions = "*" version = "2019.3" -[[package]] -category = "main" -description = "YAML parser and emitter for Python" -name = "pyyaml" -optional = false -python-versions = "*" -version = "3.13" - [[package]] category = "dev" description = "Python HTTP for Humans." @@ -353,6 +387,10 @@ chardet = ">=3.0.2,<3.1.0" idna = ">=2.5,<2.9" urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + [[package]] category = "dev" description = "a python refactoring library..." @@ -431,6 +469,10 @@ sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = "*" +[package.extras] +docs = ["sphinxcontrib-websupport"] +test = ["pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"] + [[package]] category = "dev" description = "" @@ -439,6 +481,9 @@ optional = false python-versions = "*" version = "1.0.1" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" description = "" @@ -447,6 +492,9 @@ optional = false python-versions = "*" version = "1.0.1" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" description = "" @@ -455,6 +503,9 @@ optional = false python-versions = "*" version = "1.0.2" +[package.extras] +test = ["pytest", "flake8", "mypy", "html5lib"] + [[package]] category = "dev" description = "Sphinx domain for documenting HTTP APIs" @@ -475,6 +526,9 @@ optional = false python-versions = ">=3.5" version = "1.0.1" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" description = "" @@ -483,6 +537,9 @@ optional = false python-versions = "*" version = "1.0.2" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" description = "" @@ -491,6 +548,9 @@ optional = false python-versions = "*" version = "1.1.3" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" description = "Python Library for Tom's Obvious, Minimal Language" @@ -516,6 +576,11 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" version = "1.25.8" +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + [[package]] category = "main" description = "Declarative parsing and validation of HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, webapp2, Falcon, and aiohttp." @@ -528,13 +593,24 @@ version = "5.5.3" marshmallow = ">=2.15.2" simplejson = ">=2.1.0" +[package.extras] +dev = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "tox", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"] +docs = ["Sphinx (2.2.0)", "sphinx-issues (1.2.0)", "sphinx-typlog-theme (0.7.3)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"] +frameworks = ["Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"] +lint = ["flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"] +tests = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)"] + [[package]] category = "main" description = "The comprehensive WSGI web application library." name = "werkzeug" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.16.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "1.0.0" + +[package.extras] +dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] +watchdog = ["watchdog"] [[package]] category = "dev" @@ -548,63 +624,394 @@ version = "1.11.2" rpi = ["picamera", "RPi.GPIO"] [metadata] -content-hash = "34da76b3341563a0bebfa3e4a3fca90f34b857027e90811fed92b106ad06c2ba" +content-hash = "03ea9560d58dababb9f7f86d533aa9effa087872bcbed58073923325e190ee32" python-versions = "^3.6" -[metadata.hashes] -alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"] -apispec = ["c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a", "f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"] -appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] -astroid = ["71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", "840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"] -attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"] -babel = ["1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", "d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"] -black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"] -certifi = ["017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", "25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"] -chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] -click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] -colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"] -docutils = ["0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", "c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"] -flask = ["13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", "45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"] -flask-cors = ["72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16", "f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"] -idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] -imagesize = ["6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", "b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"] -isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"] -itsdangerous = ["321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"] -jinja2 = ["6e7a3c2934694d59ad334c93dd1b6c96699cf24c53fdb8ec848ac6b23e685734", "d6609ae5ec3d56212ca7d802eda654eaf2310000816ce815361041465b108be4"] +[metadata.files] +alabaster = [ + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, +] +apispec = [ + {file = "apispec-3.2.0-py2.py3-none-any.whl", hash = "sha256:f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"}, + {file = "apispec-3.2.0.tar.gz", hash = "sha256:c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a"}, +] +appdirs = [ + {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, + {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, +] +astroid = [ + {file = "astroid-2.3.3-py3-none-any.whl", hash = "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"}, + {file = "astroid-2.3.3.tar.gz", hash = "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +babel = [ + {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, + {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, +] +black = [ + {file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"}, + {file = "black-18.9b0.tar.gz", hash = "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"}, +] +certifi = [ + {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, + {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +click = [ + {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, + {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +docutils = [ + {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, + {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, +] +flask = [ + {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, + {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, +] +flask-cors = [ + {file = "Flask-Cors-3.0.8.tar.gz", hash = "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16"}, + {file = "Flask_Cors-3.0.8-py2.py3-none-any.whl", hash = "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"}, +] +idna = [ + {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, + {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, +] +imagesize = [ + {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, + {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, +] +isort = [ + {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, + {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, +] +itsdangerous = [ + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, +] +jinja2 = [ + {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"}, + {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"}, +] labthings = [] -lazy-object-proxy = ["0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", "194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", "1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", "4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", "48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", "5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", "59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", "8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", "9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", "9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", "97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", "9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", "a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", "a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", "ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", "cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", "d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", "d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", "eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", "efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"] -markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] -marshmallow = ["0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e", "3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"] -mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] -numpy = ["1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", "17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", "20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", "2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", "39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", "56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", "590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", "70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", "77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", "9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", "9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", "ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", "b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", "b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", "b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", "c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", "cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", "d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", "e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", "e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", "f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"] -packaging = ["170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", "e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, + {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, + {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, + {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, + {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, + {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, + {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, + {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, + {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, + {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, + {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, + {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, + {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, + {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, + {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, + {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, + {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, + {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, + {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, + {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, + {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +marshmallow = [ + {file = "marshmallow-3.4.0-py2.py3-none-any.whl", hash = "sha256:7669b944d6233b81f68739d5826f1176c3841cc31cf6b856841083b5a72f5ca9"}, + {file = "marshmallow-3.4.0.tar.gz", hash = "sha256:c9d277f6092f32300395fb83d343be9f61b5e99d66d22bae1e5e7cd82608fee6"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +numpy = [ + {file = "numpy-1.18.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc"}, + {file = "numpy-1.18.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121"}, + {file = "numpy-1.18.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e"}, + {file = "numpy-1.18.1-cp35-cp35m-win32.whl", hash = "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"}, + {file = "numpy-1.18.1-cp35-cp35m-win_amd64.whl", hash = "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6"}, + {file = "numpy-1.18.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480"}, + {file = "numpy-1.18.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572"}, + {file = "numpy-1.18.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57"}, + {file = "numpy-1.18.1-cp36-cp36m-win32.whl", hash = "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc"}, + {file = "numpy-1.18.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd"}, + {file = "numpy-1.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa"}, + {file = "numpy-1.18.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca"}, + {file = "numpy-1.18.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec"}, + {file = "numpy-1.18.1-cp37-cp37m-win32.whl", hash = "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73"}, + {file = "numpy-1.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971"}, + {file = "numpy-1.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07"}, + {file = "numpy-1.18.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26"}, + {file = "numpy-1.18.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474"}, + {file = "numpy-1.18.1-cp38-cp38-win32.whl", hash = "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3"}, + {file = "numpy-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a"}, + {file = "numpy-1.18.1.zip", hash = "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77"}, +] +packaging = [ + {file = "packaging-20.1-py2.py3-none-any.whl", hash = "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73"}, + {file = "packaging-20.1.tar.gz", hash = "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"}, +] picamera = [] -pillow = ["01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", "051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", "07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", "0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", "0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", "0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", "163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", "18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", "24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", "267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", "3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", "39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", "4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", "4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", "4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", "4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", "505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", "5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", "52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", "5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", "5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", "5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", "634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", "75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", "7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", "801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", "825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", "87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", "9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", "a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", "a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", "a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", "a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", "ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", "b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", "ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", "ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", "c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", "cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", "cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", "d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", "d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", "db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", "e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", "e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", "e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", "f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", "f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", "f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", "f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", "fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"] -pygments = ["2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", "98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"] -pylint = ["3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", "886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"] -pyparsing = ["4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", "c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"] -pyserial = ["6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627", "e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"] -python-dateutil = ["73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"] -pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"] -pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"] -requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] -rope = ["6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969", "c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf", "f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"] -"rpi.gpio" = ["a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"] -scipy = ["03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", "09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", "10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", "1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", "409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", "4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", "6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", "826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", "a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", "a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", "adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", "b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", "c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", "c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", "c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", "db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"] -simplejson = ["0fe3994207485efb63d8f10a833ff31236ed27e3b23dadd0bf51c9900313f8f2", "17163e643dbf125bb552de17c826b0161c68c970335d270e174363d19e7ea882", "1d1e929cdd15151f3c0b2efe953b3281b2fd5ad5f234f77aca725f28486466f6", "1d346c2c1d7dd79c118f0cc7ec5a1c4127e0c8ffc83e7b13fc5709ff78c9bb84", "1ea59f570b9d4916ae5540a9181f9c978e16863383738b69a70363bc5e63c4cb", "1fbba86098bbfc1f85c5b69dc9a6d009055104354e0d9880bb00b692e30e0078", "229edb079d5dd81bf12da952d4d825bd68d1241381b37d3acf961b384c9934de", "22a7acb81968a7c64eba7526af2cf566e7e2ded1cb5c83f0906b17ff1540f866", "2b4b2b738b3b99819a17feaf118265d0753d5536049ea570b3c43b51c4701e81", "4cf91aab51b02b3327c9d51897960c554f00891f9b31abd8a2f50fd4a0071ce8", "4fd5f79590694ebff8dc980708e1c182d41ce1fda599a12189f0ca96bf41ad70", "5cfd495527f8b85ce21db806567de52d98f5078a8e9427b18e251c68bd573a26", "60aad424e47c5803276e332b2a861ed7a0d46560e8af53790c4c4fb3420c26c2", "7739940d68b200877a15a5ff5149e1599737d6dd55e302625650629350466418", "7cce4bac7e0d66f3a080b80212c2238e063211fe327f98d764c6acbc214497fc", "8027bd5f1e633eb61b8239994e6fc3aba0346e76294beac22a892eb8faa92ba1", "86afc5b5cbd42d706efd33f280fec7bd7e2772ef54e3f34cf6b30777cd19a614", "87d349517b572964350cc1adc5a31b493bbcee284505e81637d0174b2758ba17", "8de378d589eccbc75941e480b4d5b4db66f22e4232f87543b136b1f093fff342", "926bcbef9eb60e798eabda9cd0bbcb0fca70d2779aa0aa56845749d973eb7ad5", "9a126c3a91df5b1403e965ba63b304a50b53d8efc908a8c71545ed72535374a3", "ad8dd3454d0c65c0f92945ac86f7b9efb67fa2040ba1b0189540e984df904378", "d140e9376e7f73c1f9e0a8e3836caf5eec57bbafd99259d56979da05a6356388", "da00675e5e483ead345429d4f1374ab8b949fba4429d60e71ee9d030ced64037", "daaf4d11db982791be74b23ff4729af2c7da79316de0bebf880fa2d60bcc8c5a", "f4b64a1031acf33e281fd9052336d6dad4d35eee3404c95431c8c6bc7a9c0588", "fc046afda0ed8f5295212068266c92991ab1f4a50c6a7144b69364bdee4a0159", "fc9051d249dd5512e541f20330a74592f7a65b2d62e18122ca89bf71f94db748"] -six = ["236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", "8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"] -snowballstemmer = ["209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", "df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"] -sphinx = ["298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159", "e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"] -sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"] -sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"] -sphinxcontrib-htmlhelp = ["4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"] -sphinxcontrib-httpdomain = ["1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e", "ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"] -sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"] -sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"] -sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"] -toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] -typed-ast = ["0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", "0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", "249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", "24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", "269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", "4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", "498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", "4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", "6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", "715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", "73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", "8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", "8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", "aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", "bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", "c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", "d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", "d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", "d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", "fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", "fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"] -urllib3 = ["2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", "87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"] -webargs = ["4f04918864c7602886335d8099f9b8960ee698b6b914f022736ed50be6b71235", "871642a2e0c62f21d5b78f357750ac7a87e6bc734c972f633aa5fb6204fbf29a", "fc81c9f9d391acfbce406a319217319fd8b2fd862f7fdb5319ad06944f36ed25"] -werkzeug = ["1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2", "b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04"] -wrapt = ["565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"] +pillow = [ + {file = "Pillow-5.4.1-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1"}, + {file = "Pillow-5.4.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7"}, + {file = "Pillow-5.4.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e"}, + {file = "Pillow-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c"}, + {file = "Pillow-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f"}, + {file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c"}, + {file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc"}, + {file = "Pillow-5.4.1-cp34-cp34m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1"}, + {file = "Pillow-5.4.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8"}, + {file = "Pillow-5.4.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055"}, + {file = "Pillow-5.4.1-cp34-cp34m-win32.whl", hash = "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6"}, + {file = "Pillow-5.4.1-cp34-cp34m-win_amd64.whl", hash = "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf"}, + {file = "Pillow-5.4.1-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239"}, + {file = "Pillow-5.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3"}, + {file = "Pillow-5.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697"}, + {file = "Pillow-5.4.1-cp35-cp35m-win32.whl", hash = "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7"}, + {file = "Pillow-5.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe"}, + {file = "Pillow-5.4.1-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca"}, + {file = "Pillow-5.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"}, + {file = "Pillow-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad"}, + {file = "Pillow-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a"}, + {file = "Pillow-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356"}, + {file = "Pillow-5.4.1-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1"}, + {file = "Pillow-5.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9"}, + {file = "Pillow-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3"}, + {file = "Pillow-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f"}, + {file = "Pillow-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3"}, + {file = "Pillow-5.4.1-pp260-pypy_41-win32.whl", hash = "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494"}, + {file = "Pillow-5.4.1-pp360-pp360-win32.whl", hash = "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000"}, + {file = "Pillow-5.4.1-py2.7-macosx-10.13-x86_64.egg", hash = "sha256:634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca"}, + {file = "Pillow-5.4.1-py2.7-win-amd64.egg", hash = "sha256:0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d"}, + {file = "Pillow-5.4.1-py2.7-win32.egg", hash = "sha256:f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28"}, + {file = "Pillow-5.4.1-py3.4-win-amd64.egg", hash = "sha256:01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1"}, + {file = "Pillow-5.4.1-py3.4-win32.egg", hash = "sha256:4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964"}, + {file = "Pillow-5.4.1-py3.5-win-amd64.egg", hash = "sha256:f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15"}, + {file = "Pillow-5.4.1-py3.5-win32.egg", hash = "sha256:e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838"}, + {file = "Pillow-5.4.1-py3.6-win-amd64.egg", hash = "sha256:07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa"}, + {file = "Pillow-5.4.1-py3.6-win32.egg", hash = "sha256:87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9"}, + {file = "Pillow-5.4.1-py3.7-win-amd64.egg", hash = "sha256:c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e"}, + {file = "Pillow-5.4.1-py3.7-win32.egg", hash = "sha256:5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f"}, + {file = "Pillow-5.4.1.tar.gz", hash = "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f"}, + {file = "Pillow-5.4.1.win-amd64-py2.7.exe", hash = "sha256:e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e"}, + {file = "Pillow-5.4.1.win-amd64-py3.4.exe", hash = "sha256:52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2"}, + {file = "Pillow-5.4.1.win-amd64-py3.5.exe", hash = "sha256:ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2"}, + {file = "Pillow-5.4.1.win-amd64-py3.6.exe", hash = "sha256:39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c"}, + {file = "Pillow-5.4.1.win-amd64-py3.7.exe", hash = "sha256:5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4"}, + {file = "Pillow-5.4.1.win32-py2.7.exe", hash = "sha256:db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0"}, + {file = "Pillow-5.4.1.win32-py3.4.exe", hash = "sha256:ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28"}, + {file = "Pillow-5.4.1.win32-py3.5.exe", hash = "sha256:f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b"}, + {file = "Pillow-5.4.1.win32-py3.6.exe", hash = "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d"}, + {file = "Pillow-5.4.1.win32-py3.7.exe", hash = "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522"}, +] +pygments = [ + {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"}, + {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"}, +] +pylint = [ + {file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"}, + {file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"}, +] +pyparsing = [ + {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, + {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, +] +pyserial = [ + {file = "pyserial-3.4-py2.py3-none-any.whl", hash = "sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"}, + {file = "pyserial-3.4.tar.gz", hash = "sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] +pytz = [ + {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, + {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, +] +requests = [ + {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, + {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, +] +rope = [ + {file = "rope-0.14.0-py2-none-any.whl", hash = "sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969"}, + {file = "rope-0.14.0-py3-none-any.whl", hash = "sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"}, + {file = "rope-0.14.0.tar.gz", hash = "sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf"}, +] +"rpi.gpio" = [ + {file = "RPi.GPIO-0.6.5.tar.gz", hash = "sha256:a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"}, +] +scipy = [ + {file = "scipy-1.3.0-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340"}, + {file = "scipy-1.3.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba"}, + {file = "scipy-1.3.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef"}, + {file = "scipy-1.3.0-cp35-cp35m-win32.whl", hash = "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351"}, + {file = "scipy-1.3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae"}, + {file = "scipy-1.3.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7"}, + {file = "scipy-1.3.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785"}, + {file = "scipy-1.3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"}, + {file = "scipy-1.3.0-cp36-cp36m-win32.whl", hash = "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb"}, + {file = "scipy-1.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d"}, + {file = "scipy-1.3.0-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e"}, + {file = "scipy-1.3.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06"}, + {file = "scipy-1.3.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921"}, + {file = "scipy-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1"}, + {file = "scipy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e"}, + {file = "scipy-1.3.0.tar.gz", hash = "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a"}, +] +simplejson = [ + {file = "simplejson-3.17.0-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:87d349517b572964350cc1adc5a31b493bbcee284505e81637d0174b2758ba17"}, + {file = "simplejson-3.17.0-cp27-cp27m-win32.whl", hash = "sha256:1d1e929cdd15151f3c0b2efe953b3281b2fd5ad5f234f77aca725f28486466f6"}, + {file = "simplejson-3.17.0-cp27-cp27m-win_amd64.whl", hash = "sha256:1ea59f570b9d4916ae5540a9181f9c978e16863383738b69a70363bc5e63c4cb"}, + {file = "simplejson-3.17.0-cp33-cp33m-win32.whl", hash = "sha256:8027bd5f1e633eb61b8239994e6fc3aba0346e76294beac22a892eb8faa92ba1"}, + {file = "simplejson-3.17.0-cp33-cp33m-win_amd64.whl", hash = "sha256:22a7acb81968a7c64eba7526af2cf566e7e2ded1cb5c83f0906b17ff1540f866"}, + {file = "simplejson-3.17.0-cp34-cp34m-win32.whl", hash = "sha256:17163e643dbf125bb552de17c826b0161c68c970335d270e174363d19e7ea882"}, + {file = "simplejson-3.17.0-cp34-cp34m-win_amd64.whl", hash = "sha256:0fe3994207485efb63d8f10a833ff31236ed27e3b23dadd0bf51c9900313f8f2"}, + {file = "simplejson-3.17.0-cp35-cp35m-win32.whl", hash = "sha256:4cf91aab51b02b3327c9d51897960c554f00891f9b31abd8a2f50fd4a0071ce8"}, + {file = "simplejson-3.17.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fc9051d249dd5512e541f20330a74592f7a65b2d62e18122ca89bf71f94db748"}, + {file = "simplejson-3.17.0-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:86afc5b5cbd42d706efd33f280fec7bd7e2772ef54e3f34cf6b30777cd19a614"}, + {file = "simplejson-3.17.0-cp36-cp36m-win32.whl", hash = "sha256:926bcbef9eb60e798eabda9cd0bbcb0fca70d2779aa0aa56845749d973eb7ad5"}, + {file = "simplejson-3.17.0-cp36-cp36m-win_amd64.whl", hash = "sha256:daaf4d11db982791be74b23ff4729af2c7da79316de0bebf880fa2d60bcc8c5a"}, + {file = "simplejson-3.17.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:9a126c3a91df5b1403e965ba63b304a50b53d8efc908a8c71545ed72535374a3"}, + {file = "simplejson-3.17.0-cp37-cp37m-win32.whl", hash = "sha256:fc046afda0ed8f5295212068266c92991ab1f4a50c6a7144b69364bdee4a0159"}, + {file = "simplejson-3.17.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7cce4bac7e0d66f3a080b80212c2238e063211fe327f98d764c6acbc214497fc"}, + {file = "simplejson-3.17.0.tar.gz", hash = "sha256:2b4b2b738b3b99819a17feaf118265d0753d5536049ea570b3c43b51c4701e81"}, + {file = "simplejson-3.17.0.win-amd64-py2.7.exe", hash = "sha256:1d346c2c1d7dd79c118f0cc7ec5a1c4127e0c8ffc83e7b13fc5709ff78c9bb84"}, + {file = "simplejson-3.17.0.win-amd64-py3.3.exe", hash = "sha256:5cfd495527f8b85ce21db806567de52d98f5078a8e9427b18e251c68bd573a26"}, + {file = "simplejson-3.17.0.win-amd64-py3.4.exe", hash = "sha256:8de378d589eccbc75941e480b4d5b4db66f22e4232f87543b136b1f093fff342"}, + {file = "simplejson-3.17.0.win-amd64-py3.5.exe", hash = "sha256:f4b64a1031acf33e281fd9052336d6dad4d35eee3404c95431c8c6bc7a9c0588"}, + {file = "simplejson-3.17.0.win-amd64-py3.6.exe", hash = "sha256:ad8dd3454d0c65c0f92945ac86f7b9efb67fa2040ba1b0189540e984df904378"}, + {file = "simplejson-3.17.0.win-amd64-py3.7.exe", hash = "sha256:229edb079d5dd81bf12da952d4d825bd68d1241381b37d3acf961b384c9934de"}, + {file = "simplejson-3.17.0.win32-py2.7.exe", hash = "sha256:4fd5f79590694ebff8dc980708e1c182d41ce1fda599a12189f0ca96bf41ad70"}, + {file = "simplejson-3.17.0.win32-py3.3.exe", hash = "sha256:d140e9376e7f73c1f9e0a8e3836caf5eec57bbafd99259d56979da05a6356388"}, + {file = "simplejson-3.17.0.win32-py3.4.exe", hash = "sha256:da00675e5e483ead345429d4f1374ab8b949fba4429d60e71ee9d030ced64037"}, + {file = "simplejson-3.17.0.win32-py3.5.exe", hash = "sha256:7739940d68b200877a15a5ff5149e1599737d6dd55e302625650629350466418"}, + {file = "simplejson-3.17.0.win32-py3.6.exe", hash = "sha256:60aad424e47c5803276e332b2a861ed7a0d46560e8af53790c4c4fb3420c26c2"}, + {file = "simplejson-3.17.0.win32-py3.7.exe", hash = "sha256:1fbba86098bbfc1f85c5b69dc9a6d009055104354e0d9880bb00b692e30e0078"}, +] +six = [ + {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, + {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, +] +snowballstemmer = [ + {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, + {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, +] +sphinx = [ + {file = "Sphinx-2.3.1-py3-none-any.whl", hash = "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159"}, + {file = "Sphinx-2.3.1.tar.gz", hash = "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"}, +] +sphinxcontrib-applehelp = [ + {file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"}, + {file = "sphinxcontrib_applehelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"}, +] +sphinxcontrib-devhelp = [ + {file = "sphinxcontrib-devhelp-1.0.1.tar.gz", hash = "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34"}, + {file = "sphinxcontrib_devhelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"}, +] +sphinxcontrib-htmlhelp = [ + {file = "sphinxcontrib-htmlhelp-1.0.2.tar.gz", hash = "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422"}, + {file = "sphinxcontrib_htmlhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"}, +] +sphinxcontrib-httpdomain = [ + {file = "sphinxcontrib-httpdomain-1.7.0.tar.gz", hash = "sha256:ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"}, + {file = "sphinxcontrib_httpdomain-1.7.0-py2.py3-none-any.whl", hash = "sha256:1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e"}, +] +sphinxcontrib-jsmath = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] +sphinxcontrib-qthelp = [ + {file = "sphinxcontrib-qthelp-1.0.2.tar.gz", hash = "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"}, + {file = "sphinxcontrib_qthelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20"}, +] +sphinxcontrib-serializinghtml = [ + {file = "sphinxcontrib-serializinghtml-1.1.3.tar.gz", hash = "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227"}, + {file = "sphinxcontrib_serializinghtml-1.1.3-py2.py3-none-any.whl", hash = "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"}, +] +toml = [ + {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, + {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, + {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, +] +typed-ast = [ + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, + {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, + {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, + {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, + {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, +] +urllib3 = [ + {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, + {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, +] +webargs = [ + {file = "webargs-5.5.3-py2-none-any.whl", hash = "sha256:fc81c9f9d391acfbce406a319217319fd8b2fd862f7fdb5319ad06944f36ed25"}, + {file = "webargs-5.5.3-py3-none-any.whl", hash = "sha256:4f04918864c7602886335d8099f9b8960ee698b6b914f022736ed50be6b71235"}, + {file = "webargs-5.5.3.tar.gz", hash = "sha256:871642a2e0c62f21d5b78f357750ac7a87e6bc734c972f633aa5fb6204fbf29a"}, +] +werkzeug = [ + {file = "Werkzeug-1.0.0-py2.py3-none-any.whl", hash = "sha256:6dc65cf9091cf750012f56f2cad759fa9e879f511b5ff8685e456b4e3bf90d16"}, + {file = "Werkzeug-1.0.0.tar.gz", hash = "sha256:169ba8a33788476292d04186ab33b01d6add475033dfc07215e6d219cc077096"}, +] +wrapt = [ + {file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"}, +] diff --git a/pyproject.toml b/pyproject.toml index 29c6e50b..632d2ee0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ license = "GPL-3.0" [tool.poetry.dependencies] python = "^3.6" Flask = "^1.0" -pyyaml = "^3.13" numpy = "^1.17.0" Pillow = "^5.4" scipy = "1.3.0" # Exact version until we can guarantee a wheel From f2588a506e63a963e30f85a5897e1b4a64382ecf Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 7 Feb 2020 17:05:30 +0000 Subject: [PATCH 29/70] Add more complete Capture object schemas --- .../api/v2/views/captures.py | 24 ++++++++++++++++++- openflexure_microscope/camera/capture.py | 16 +++++++------ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index 097ff665..f52a7ed0 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -14,6 +14,28 @@ from labthings.server.find import find_component from marshmallow import pre_dump +class InstrumentSchema(Schema): + id = fields.UUID() + configuration = fields.Dict() + settings = fields.Dict() + state = fields.Dict() + +class CaptureMetadataImageSchema(Schema): + id = fields.UUID() + acquisitionDate = fields.String(format="date") + format = fields.String() + name = fields.String() + tags = fields.List(fields.String()) + annotations = fields.Dict() + + +class CaptureMetadataSchema(Schema): + experimenter = fields.Dict() # TODO: Make schema + experimenterGroup = fields.Dict() # TODO: Make schema + dataset = fields.Dict() # TODO: Make schema + image = fields.Nested(CaptureMetadataImageSchema()) + instrument = fields.Nested(InstrumentSchema()) + class CaptureSchema(Schema): id = fields.String() file = fields.String( @@ -21,7 +43,7 @@ class CaptureSchema(Schema): ) exists = fields.Bool(data_key="available") name = fields.String() - metadata = fields.Dict() + metadata = fields.Nested(CaptureMetadataSchema()) links = fields.Dict() diff --git a/openflexure_microscope/camera/capture.py b/openflexure_microscope/camera/capture.py index 81edff1a..0f190c58 100644 --- a/openflexure_microscope/camera/capture.py +++ b/openflexure_microscope/camera/capture.py @@ -4,7 +4,6 @@ import os import shutil import glob import datetime -import yaml import json import logging from PIL import Image @@ -35,11 +34,9 @@ def pull_usercomment_dict(filepath): try: return json.loads(exif_dict["Exif"][37510].decode()) except json.decoder.JSONDecodeError: - # TODO: Remove YAML support in a later version - logging.warning( - f"Capture {filepath} has metadata stored in YAML format. This is now deprecated in favour of JSON." + logging.error( + f"Capture {filepath} has old, corrupt, or missing OpenFlexure metadata. Unable to reload to server." ) - return yaml.load(exif_dict["Exif"][37510].decode()) else: return None @@ -68,7 +65,8 @@ def build_captures_from_exif(capture_path): exif = pull_usercomment_dict(f) if exif: capture = capture_from_exif(f, exif) - captures.append(capture) + if capture: + captures.append(capture) else: logging.error("Invalid data at {}. Skipping.".format(f)) @@ -95,7 +93,11 @@ def capture_from_exif(path, exif_dict): capture.split_file_path(capture.file) # Image metadata - image_metadata = exif_dict.pop("image") + try: + image_metadata = exif_dict.pop("image") + except KeyError as e: + logging.error(f"Unable to obtain OpenFlexure metadata from file {path}") + return None # Populate capture parameters capture.id = image_metadata.get("id") From ede9fcbf2be55ff059fcbea2e15d8b8f0777eac1 Mon Sep 17 00:00:00 2001 From: jtc42 Date: Fri, 7 Feb 2020 17:11:38 +0000 Subject: [PATCH 30/70] Updated LabThings --- poetry.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6665eb7d..8dcfa73c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -229,7 +229,7 @@ marshmallow = "^3.3.0" webargs = "^5.5.3" [package.source] -reference = "8af573592efc59bb0b3f0a478391b4bc32f4b572" +reference = "d9156c37a01c451f37d9949c7da72921d3b16ad2" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -307,7 +307,6 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb" type = "git" url = "https://github.com/rwb27/picamera.git" - [[package]] category = "main" description = "Python Imaging Library (Fork)" From 72af35e4aeb851b7705bc5e74bf706268b7492b5 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 11 Feb 2020 17:04:11 +0000 Subject: [PATCH 31/70] Fixed rebooting for openflexure-ws user --- openflexure_microscope/api/v2/views/actions/system.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/api/v2/views/actions/system.py b/openflexure_microscope/api/v2/views/actions/system.py index 631c5c79..38cc3c5c 100644 --- a/openflexure_microscope/api/v2/views/actions/system.py +++ b/openflexure_microscope/api/v2/views/actions/system.py @@ -25,7 +25,7 @@ class ShutdownAPI(View): """ Attempt to shutdown the device """ - subprocess.Popen(["sudo", "shutdown", "-h", "now"]) + subprocess.Popen(["shutdown", "-h", "now"]) return "{}", 201 @@ -41,6 +41,6 @@ class RebootAPI(View): """ Attempt to reboot the device """ - subprocess.Popen(["sudo", "shutdown", "-r", "now"]) + subprocess.Popen(["systemctl", "reboot", "-i"]) return "{}", 201 From 51692795c67148f17e8c9c4ca8a151c8ce9c137c Mon Sep 17 00:00:00 2001 From: Joel Collins <2579603-jtc42@users.noreply.gitlab.com> Date: Tue, 11 Feb 2020 17:31:11 +0000 Subject: [PATCH 32/70] Revert "Fixed rebooting for openflexure-ws user" This reverts commit 72af35e4aeb851b7705bc5e74bf706268b7492b5 --- openflexure_microscope/api/v2/views/actions/system.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/api/v2/views/actions/system.py b/openflexure_microscope/api/v2/views/actions/system.py index 38cc3c5c..631c5c79 100644 --- a/openflexure_microscope/api/v2/views/actions/system.py +++ b/openflexure_microscope/api/v2/views/actions/system.py @@ -25,7 +25,7 @@ class ShutdownAPI(View): """ Attempt to shutdown the device """ - subprocess.Popen(["shutdown", "-h", "now"]) + subprocess.Popen(["sudo", "shutdown", "-h", "now"]) return "{}", 201 @@ -41,6 +41,6 @@ class RebootAPI(View): """ Attempt to reboot the device """ - subprocess.Popen(["systemctl", "reboot", "-i"]) + subprocess.Popen(["sudo", "shutdown", "-r", "now"]) return "{}", 201 From ae7846887d1cf0b38235ff8f0be47436f73a0429 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 11 Feb 2020 17:31:56 +0000 Subject: [PATCH 33/70] Show output when shutting down --- .../api/v2/views/actions/system.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/openflexure_microscope/api/v2/views/actions/system.py b/openflexure_microscope/api/v2/views/actions/system.py index 631c5c79..df002df1 100644 --- a/openflexure_microscope/api/v2/views/actions/system.py +++ b/openflexure_microscope/api/v2/views/actions/system.py @@ -25,9 +25,14 @@ class ShutdownAPI(View): """ Attempt to shutdown the device """ - subprocess.Popen(["sudo", "shutdown", "-h", "now"]) + p = subprocess.Popen( + ["sudo", "shutdown", "-h", "now"], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) - return "{}", 201 + out, err = p.communicate() + return {"out": out, "err": err}, 201 @ThingAction @@ -41,6 +46,11 @@ class RebootAPI(View): """ Attempt to reboot the device """ - subprocess.Popen(["sudo", "shutdown", "-r", "now"]) + p = subprocess.Popen( + ["sudo", "shutdown", "-r", "now"], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) - return "{}", 201 + out, err = p.communicate() + return {"out": out, "err": err}, 201 From 19b0101718f5da701ab5a8999715d4b090789232 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 12 Feb 2020 13:52:57 +0000 Subject: [PATCH 34/70] Make action links external --- openflexure_microscope/api/v2/views/actions/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/api/v2/views/actions/__init__.py b/openflexure_microscope/api/v2/views/actions/__init__.py index aa4a2ae9..abab2118 100644 --- a/openflexure_microscope/api/v2/views/actions/__init__.py +++ b/openflexure_microscope/api/v2/views/actions/__init__.py @@ -1,7 +1,7 @@ """ Top-level representation of enabled actions """ - +from flask import url_for from . import camera, stage, system from labthings.server.view import View @@ -74,7 +74,7 @@ class ActionsView(View): d = { "links": { "self": { - "href": current_labthing().url_for(action["view_class"]), + "href": url_for(action["view_class"].endpoint, _external=True), "mimetype": "application/json", **description_from_view(action["view_class"]), } From c9539ed816831b84d6f73c339909f04e3f5953ca Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 12 Feb 2020 16:40:14 +0000 Subject: [PATCH 35/70] Try serving eV as a static directory --- .gitattributes | 1 + openflexure_microscope/api/app.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..5f08879f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +openflexure_microscope/api/static/** filter=lfs diff=lfs merge=lfs -text diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index ce0d1068..2d15f673 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -68,6 +68,7 @@ app, labthing = create_app( title=f"OpenFlexure Microscope {api_microscope.name}", description="Test LabThing-based API for OpenFlexure Microscope", version=pkg_resources.get_distribution("openflexure_microscope").version, + flask_kwargs={"static_url_path": ""}, ) # Enable CORS for some routes outside of LabThings @@ -120,6 +121,11 @@ for name, action in views.enabled_root_actions().items(): labthing.add_view(view_class, f"/actions{rule}") +@app.route("/") +def openflexure_ev(): + return app.send_static_file("index.html") + + @app.route("/routes") @cross_origin() def routes(): From 4c4642b6950929d0fea646999a3ab27f988fe714 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 12 Feb 2020 16:41:53 +0000 Subject: [PATCH 36/70] Added beta of eV Lite --- .../api/static/css/chunk-vendors.1800c649.css | 3 +++ openflexure_microscope/api/static/css/main.cdaf7df3.css | 3 +++ openflexure_microscope/api/static/favicon-16x16.png | 3 +++ openflexure_microscope/api/static/favicon-32x32.png | 3 +++ openflexure_microscope/api/static/favicon.ico | 3 +++ .../api/static/fonts/MaterialIcons-Regular.012cf6a1.woff | 3 +++ .../api/static/fonts/MaterialIcons-Regular.570eb838.woff2 | 3 +++ .../api/static/fonts/MaterialIcons-Regular.a37b0c01.ttf | 3 +++ .../api/static/fonts/MaterialIcons-Regular.e79bfd88.eot | 3 +++ openflexure_microscope/api/static/index.html | 3 +++ openflexure_microscope/api/static/js/chunk-vendors.0975939e.js | 3 +++ .../api/static/js/chunk-vendors.0975939e.js.map | 3 +++ openflexure_microscope/api/static/js/main.c89d8483.js | 3 +++ openflexure_microscope/api/static/js/main.c89d8483.js.map | 3 +++ openflexure_microscope/api/static/titleicon.svg | 3 +++ 15 files changed, 45 insertions(+) create mode 100644 openflexure_microscope/api/static/css/chunk-vendors.1800c649.css create mode 100644 openflexure_microscope/api/static/css/main.cdaf7df3.css create mode 100644 openflexure_microscope/api/static/favicon-16x16.png create mode 100644 openflexure_microscope/api/static/favicon-32x32.png create mode 100644 openflexure_microscope/api/static/favicon.ico create mode 100644 openflexure_microscope/api/static/fonts/MaterialIcons-Regular.012cf6a1.woff create mode 100644 openflexure_microscope/api/static/fonts/MaterialIcons-Regular.570eb838.woff2 create mode 100644 openflexure_microscope/api/static/fonts/MaterialIcons-Regular.a37b0c01.ttf create mode 100644 openflexure_microscope/api/static/fonts/MaterialIcons-Regular.e79bfd88.eot create mode 100644 openflexure_microscope/api/static/index.html create mode 100644 openflexure_microscope/api/static/js/chunk-vendors.0975939e.js create mode 100644 openflexure_microscope/api/static/js/chunk-vendors.0975939e.js.map create mode 100644 openflexure_microscope/api/static/js/main.c89d8483.js create mode 100644 openflexure_microscope/api/static/js/main.c89d8483.js.map create mode 100644 openflexure_microscope/api/static/titleicon.svg diff --git a/openflexure_microscope/api/static/css/chunk-vendors.1800c649.css b/openflexure_microscope/api/static/css/chunk-vendors.1800c649.css new file mode 100644 index 00000000..c967969b --- /dev/null +++ b/openflexure_microscope/api/static/css/chunk-vendors.1800c649.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8605f70737729f74f48d3183b30b4a2f50706f4f750cd208aeb8c136253f7d01 +size 772 diff --git a/openflexure_microscope/api/static/css/main.cdaf7df3.css b/openflexure_microscope/api/static/css/main.cdaf7df3.css new file mode 100644 index 00000000..876b6d73 --- /dev/null +++ b/openflexure_microscope/api/static/css/main.cdaf7df3.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ce2e59fe15570b1f7b685035be666930233db8146bfa6da2b002a1e1747bb79 +size 1374686 diff --git a/openflexure_microscope/api/static/favicon-16x16.png b/openflexure_microscope/api/static/favicon-16x16.png new file mode 100644 index 00000000..6f9b9751 --- /dev/null +++ b/openflexure_microscope/api/static/favicon-16x16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee944cc13be0a52ce48a81a09c179dac70729bd7580a516ccc8ac10b4cfa3f98 +size 1310 diff --git a/openflexure_microscope/api/static/favicon-32x32.png b/openflexure_microscope/api/static/favicon-32x32.png new file mode 100644 index 00000000..21fb950d --- /dev/null +++ b/openflexure_microscope/api/static/favicon-32x32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:710327f8b3d83c83019ed2c4b27de8caf8e4e923c9148f93c1c0a2662f683f26 +size 1659 diff --git a/openflexure_microscope/api/static/favicon.ico b/openflexure_microscope/api/static/favicon.ico new file mode 100644 index 00000000..d55c92be --- /dev/null +++ b/openflexure_microscope/api/static/favicon.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0405ef60ab42df1c2e8721bf1f500c8c7d9e1afae130307101b8f77925ffdac1 +size 1150 diff --git a/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.012cf6a1.woff b/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.012cf6a1.woff new file mode 100644 index 00000000..ebaebe27 --- /dev/null +++ b/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.012cf6a1.woff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4a1baec300d09e03a8380b85918267ee80faae8e00c6c56b48e2e74b1d9b38d +size 57620 diff --git a/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.570eb838.woff2 b/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.570eb838.woff2 new file mode 100644 index 00000000..ed48a958 --- /dev/null +++ b/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.570eb838.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a87d66c91b2e7dc5530aef76c03bd6a3d25ea5826110bf4803b561b811cc8726 +size 44300 diff --git a/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.a37b0c01.ttf b/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.a37b0c01.ttf new file mode 100644 index 00000000..acdfd2e6 --- /dev/null +++ b/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.a37b0c01.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7f4a3ab562048f28dd1fa691601bc43363a61d0f876d16d8316c52e4f32d696 +size 128180 diff --git a/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.e79bfd88.eot b/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.e79bfd88.eot new file mode 100644 index 00000000..e3f70a00 --- /dev/null +++ b/openflexure_microscope/api/static/fonts/MaterialIcons-Regular.e79bfd88.eot @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c998b4a9c0acbb9fe5dd572c206a5a33fdd5ca2b58db87fc3b893beac85068d +size 143258 diff --git a/openflexure_microscope/api/static/index.html b/openflexure_microscope/api/static/index.html new file mode 100644 index 00000000..5ad377a3 --- /dev/null +++ b/openflexure_microscope/api/static/index.html @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:499cb597f5b2e482cdd75a80387f5e70c5daab3223e9842f200b1f7f2e9ba75f +size 843 diff --git a/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js b/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js new file mode 100644 index 00000000..5057a351 --- /dev/null +++ b/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d2f532e5437b2efe083793f70c743ecddd649490bae0e558f3ee462f54beb71 +size 377877 diff --git a/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js.map b/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js.map new file mode 100644 index 00000000..37ae6e73 --- /dev/null +++ b/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js.map @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa72fceabde1763773ccf44fd39a215be2c78f158b647bf5125a81578e7012b6 +size 1595324 diff --git a/openflexure_microscope/api/static/js/main.c89d8483.js b/openflexure_microscope/api/static/js/main.c89d8483.js new file mode 100644 index 00000000..24f56172 --- /dev/null +++ b/openflexure_microscope/api/static/js/main.c89d8483.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ea3ea783bea1f4aa9438bd3f560ea179773686f330c6a21618eaf5afa7c0616 +size 114840 diff --git a/openflexure_microscope/api/static/js/main.c89d8483.js.map b/openflexure_microscope/api/static/js/main.c89d8483.js.map new file mode 100644 index 00000000..85eaff09 --- /dev/null +++ b/openflexure_microscope/api/static/js/main.c89d8483.js.map @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51105b87c7a0cf0b39db3a5f518ee62088c5d8e930c0d5ad735faf103511f102 +size 385345 diff --git a/openflexure_microscope/api/static/titleicon.svg b/openflexure_microscope/api/static/titleicon.svg new file mode 100644 index 00000000..5307728d --- /dev/null +++ b/openflexure_microscope/api/static/titleicon.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:187e61d8f6171eebf611502412556c707fab1d837f8763b2b60ce3915303f0ad +size 1734 From c1705cf0e8e522a4e1d08ba87e1f1ddff1a920d8 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 13 Feb 2020 16:37:58 +0000 Subject: [PATCH 37/70] Updated labthings and marshmallow --- poetry.lock | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8dcfa73c..ef92d3b7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -225,11 +225,11 @@ version = "0.1.0" Flask = "^1.1.1" apispec = "^3.2.0" flask-cors = "^3.0.8" -marshmallow = "^3.3.0" +marshmallow = "^3.4.0" webargs = "^5.5.3" [package.source] -reference = "d9156c37a01c451f37d9949c7da72921d3b16ad2" +reference = "2a8213651083ad9387bf3f7f27e9b8ec64c6e2bc" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -447,7 +447,7 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "2.3.1" +version = "2.4.1" [package.dependencies] Jinja2 = ">=2.3" @@ -470,7 +470,7 @@ sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -test = ["pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"] +test = ["pytest (<5.3.3)", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"] [[package]] category = "dev" @@ -755,6 +755,11 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] marshmallow = [ @@ -939,8 +944,8 @@ snowballstemmer = [ {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, ] sphinx = [ - {file = "Sphinx-2.3.1-py3-none-any.whl", hash = "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159"}, - {file = "Sphinx-2.3.1.tar.gz", hash = "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"}, + {file = "Sphinx-2.4.1-py3-none-any.whl", hash = "sha256:5024a67f065fe60d9db2005580074d81f22a02dd8f00a5b1ec3d5f4d42bc88d8"}, + {file = "Sphinx-2.4.1.tar.gz", hash = "sha256:f929b72e0cfe45fa581b8964d54457117863a6a6c9369ecc1a65b8827abd3bf2"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"}, From ab7f3c3d958b1aaeb9898c4cf800bb7428852354 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 17 Feb 2020 11:05:35 +0000 Subject: [PATCH 38/70] Updated client --- openflexure_microscope/api/static/css/main.01cbd384.css | 3 +++ openflexure_microscope/api/static/css/main.cdaf7df3.css | 3 --- openflexure_microscope/api/static/index.html | 2 +- openflexure_microscope/api/static/js/main.bf0d35e5.js | 3 +++ openflexure_microscope/api/static/js/main.bf0d35e5.js.map | 3 +++ openflexure_microscope/api/static/js/main.c89d8483.js | 3 --- openflexure_microscope/api/static/js/main.c89d8483.js.map | 3 --- 7 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 openflexure_microscope/api/static/css/main.01cbd384.css delete mode 100644 openflexure_microscope/api/static/css/main.cdaf7df3.css create mode 100644 openflexure_microscope/api/static/js/main.bf0d35e5.js create mode 100644 openflexure_microscope/api/static/js/main.bf0d35e5.js.map delete mode 100644 openflexure_microscope/api/static/js/main.c89d8483.js delete mode 100644 openflexure_microscope/api/static/js/main.c89d8483.js.map diff --git a/openflexure_microscope/api/static/css/main.01cbd384.css b/openflexure_microscope/api/static/css/main.01cbd384.css new file mode 100644 index 00000000..57d803ca --- /dev/null +++ b/openflexure_microscope/api/static/css/main.01cbd384.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ec225f9adc44241191d098eb95adcbbf5f29c0f78e2148dc2adde992110f20d +size 1374686 diff --git a/openflexure_microscope/api/static/css/main.cdaf7df3.css b/openflexure_microscope/api/static/css/main.cdaf7df3.css deleted file mode 100644 index 876b6d73..00000000 --- a/openflexure_microscope/api/static/css/main.cdaf7df3.css +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ce2e59fe15570b1f7b685035be666930233db8146bfa6da2b002a1e1747bb79 -size 1374686 diff --git a/openflexure_microscope/api/static/index.html b/openflexure_microscope/api/static/index.html index 5ad377a3..c4fc387d 100644 --- a/openflexure_microscope/api/static/index.html +++ b/openflexure_microscope/api/static/index.html @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:499cb597f5b2e482cdd75a80387f5e70c5daab3223e9842f200b1f7f2e9ba75f +oid sha256:1969142cc4f0de816ffd8cea840822c0c6b8d038fc078459a732ecd28e1de610 size 843 diff --git a/openflexure_microscope/api/static/js/main.bf0d35e5.js b/openflexure_microscope/api/static/js/main.bf0d35e5.js new file mode 100644 index 00000000..bb1855ae --- /dev/null +++ b/openflexure_microscope/api/static/js/main.bf0d35e5.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:807c453fc0831b5668ebb74fb73c6acba46ec95cb7685dc1b4dbb5f91fe0e9dc +size 114858 diff --git a/openflexure_microscope/api/static/js/main.bf0d35e5.js.map b/openflexure_microscope/api/static/js/main.bf0d35e5.js.map new file mode 100644 index 00000000..16c04539 --- /dev/null +++ b/openflexure_microscope/api/static/js/main.bf0d35e5.js.map @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79825fda16fdd7bf8840c4728658620dfb9fd202e3581a5b1abdc54d4ef31e64 +size 385394 diff --git a/openflexure_microscope/api/static/js/main.c89d8483.js b/openflexure_microscope/api/static/js/main.c89d8483.js deleted file mode 100644 index 24f56172..00000000 --- a/openflexure_microscope/api/static/js/main.c89d8483.js +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ea3ea783bea1f4aa9438bd3f560ea179773686f330c6a21618eaf5afa7c0616 -size 114840 diff --git a/openflexure_microscope/api/static/js/main.c89d8483.js.map b/openflexure_microscope/api/static/js/main.c89d8483.js.map deleted file mode 100644 index 85eaff09..00000000 --- a/openflexure_microscope/api/static/js/main.c89d8483.js.map +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:51105b87c7a0cf0b39db3a5f518ee62088c5d8e930c0d5ad735faf103511f102 -size 385345 From a4f6bc7dbed4514150db8e9c24e47356a63ebe40 Mon Sep 17 00:00:00 2001 From: Joel Collins <2579603-jtc42@users.noreply.gitlab.com> Date: Mon, 17 Feb 2020 11:44:26 +0000 Subject: [PATCH 39/70] Fixed scanning fast autofocus --- .../api/default_extensions/scan.py | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index 94c6f327..83ba4e35 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -166,25 +166,19 @@ def tile( else: autofocus_enabled = False - z_stack_dz = ( - grid[2] * stride_size[2] if grid[2] > 1 else 0 - ) # shorthand for Z stack range - # Construct an x-y grid (worry about z later) x_y_grid = construct_grid(initial_position, stride_size[:2], grid[:2], style=style) # Keep the initial Z position the same as our current position - next_z = initial_position[2] - if fast_autofocus: # If fast autofocus is enabled, make - next_z += autofocus_dz / 2 # sure we start from the top of the range - initial_z = next_z # Save this value for use in raster scans + initial_z = initial_position[2] + next_z = initial_z # Save this value for use in raster scans # Now step through each point in the x-y coordinate array for line in x_y_grid: # If rastering, rather than snake (or eventually spiral) # Return focus to initial position if style == "raster": - next_z = initial_z + next_z = initial_z # Reset z position at start of each new row logging.debug("Returning to initial z position") microscope.stage.move_abs( [line[0][0], line[0][1], next_z] @@ -199,10 +193,7 @@ def tile( if fast_autofocus: # Run fast autofocus. Client should provide dz ~ 2000 autofocus_extension.fast_up_down_up_autofocus( - microscope, - dz=autofocus_dz, - target_z=-z_stack_dz / 2.0, # Finish below the focus - initial_move_up=False, # We're already at the top of the scan + microscope, dz=autofocus_dz ) else: # Run slow autofocus. Client should provide dz ~ 50 @@ -236,7 +227,6 @@ def tile( temporary=temporary, step_size=stride_size[2], steps=grid[2], - return_to_start=not fast_autofocus, use_video_port=use_video_port, resize=resize, bayer=bayer, @@ -246,14 +236,6 @@ def tile( ) # Make sure we use our current best estimate of focus (i.e. the current position) next point next_z = microscope.stage.position[2] - if fast_autofocus: - next_z += ( - autofocus_dz / 2 - ) # Fast autofocus requires us to start at the top of the range - if grid[2] > 1: - next_z -= int( - grid[2] / 2.0 * stride_size[2] - ) # Z stacking means we're higher up to start with logging.debug("Returning to {}".format(initial_position)) microscope.stage.move_abs(initial_position) @@ -277,15 +259,17 @@ def stack( # Store initial position initial_position = microscope.stage.position + logging.debug(f"Starting z-stack from position {microscope.stage.position}") with microscope.lock: # Move to center scan - logging.debug("Moving to starting position") + 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}") for i in range(steps): time.sleep(0.1) - logging.debug("Capturing...") + logging.debug(f"Capturing from position {microscope.stage.position}") capture( microscope, basename, From d6f942577ad7b33ae7bc22a9f262a9ecbcbd0f20 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Mon, 17 Feb 2020 14:43:59 +0000 Subject: [PATCH 40/70] Handle memoryview in ndarray_to_json --- openflexure_microscope/utilities.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openflexure_microscope/utilities.py b/openflexure_microscope/utilities.py index 27e46212..63db1651 100644 --- a/openflexure_microscope/utilities.py +++ b/openflexure_microscope/utilities.py @@ -23,6 +23,10 @@ def serialise_array_b64(npy_arr): def ndarray_to_json(arr: np.ndarray): + if isinstance(arr, memoryview): + # We can transparently convert memoryview objects to arrays + # This comes in very handy for the lens shading table. + arr = np.array(arr) b64_string, dtype, shape = serialise_array_b64(arr) return { "@type": "ndarray", From 649ac074ad931c867b413afd669df28c8ce8cf20 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 17 Feb 2020 15:01:58 +0000 Subject: [PATCH 41/70] Close #125 --- openflexure_microscope/api/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index 2d15f673..86195503 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -115,6 +115,7 @@ labthing.add_view(views.SnapshotStream, f"/streams/snapshot") # Attach microscope action resources labthing.add_view(views.actions.ActionsView, "/actions") +labthing.add_root_link(views.actions.ActionsView, "actions") for name, action in views.enabled_root_actions().items(): view_class = action["view_class"] rule = action["rule"] From 7209700343406f79bcbafcd1f6dea7f159342508 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 11:40:14 +0000 Subject: [PATCH 42/70] Skeleton for autostorage extension --- .../api/default_extensions/__init__.py | 1 + .../api/default_extensions/autostorage.py | 37 +++++++++++++++++++ openflexure_microscope/config.py | 3 +- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 openflexure_microscope/api/default_extensions/autostorage.py diff --git a/openflexure_microscope/api/default_extensions/__init__.py b/openflexure_microscope/api/default_extensions/__init__.py index 2b321f34..577f10ba 100644 --- a/openflexure_microscope/api/default_extensions/__init__.py +++ b/openflexure_microscope/api/default_extensions/__init__.py @@ -4,6 +4,7 @@ import traceback from .autofocus import autofocus_extension_v2 from .scan import scan_extension_v2 from .zip_builder import zip_extension_v2 +from .autostorage import autostorage_extension_v2 # "Gracefully" handle cases where picamera cannot be imported (eg test server) try: diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py new file mode 100644 index 00000000..1046e895 --- /dev/null +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -0,0 +1,37 @@ +from labthings.server.extensions import BaseExtension +from openflexure_microscope.paths import settings_file_path +from openflexure_microscope.config import OpenflexureSettingsFile + +import logging + +AS_SETTINGS_PATH = settings_file_path("autostorage_settings.json") + + +class AutostorageExtension(BaseExtension): + def __init__(self): + BaseExtension.__init__( + self, + "org.openflexure.autostorage", + version="2.0.0-beta.1", + description="Handle switching capture storage devices", + ) + + self.settings = OpenflexureSettingsFile( + AS_SETTINGS_PATH, defaults={"preferred": None} + ) + + # We'll store a reference to a camera object, who's capture paths will be modified + self.camera = None + + # Register the on_microscope function to run when the microscope is attached + self.on_component("org.openflexure.microscope", self.on_microscope) + + 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}") + if hasattr(microscope_obj, "camera"): + self.camera = microscope_obj.camera + logging.debug(f"Autostorage extension bound to camera {self.camera}") + + +autostorage_extension_v2 = AutostorageExtension() diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index 2b2abff4..c0a971ba 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -32,7 +32,8 @@ class OpenflexureSettingsFile: self.path = path # Initialise basic config file with defaults if it doesn't exist - initialise_file(self.path, populate=defaults) + defaults_str = json.dumps(defaults, cls=JSONEncoder, indent=2, sort_keys=True) + initialise_file(self.path, populate=defaults_str) def load(self) -> dict: """ From 891581846b7d67289ed5ce4eefc2e756fb40039e Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 11:40:50 +0000 Subject: [PATCH 43/70] Updated LabThings --- poetry.lock | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index ef92d3b7..8014b557 100644 --- a/poetry.lock +++ b/poetry.lock @@ -229,7 +229,7 @@ marshmallow = "^3.4.0" webargs = "^5.5.3" [package.source] -reference = "2a8213651083ad9387bf3f7f27e9b8ec64c6e2bc" +reference = "12b6dda521f00e449c67c5ea8bf34005918fd649" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -307,6 +307,7 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb" type = "git" url = "https://github.com/rwb27/picamera.git" + [[package]] category = "main" description = "Python Imaging Library (Fork)" @@ -755,11 +756,6 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] marshmallow = [ From da0891ef89cbd74892b2eb25982456ca1d6dd1bf Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 13:41:24 +0000 Subject: [PATCH 44/70] Added psutil --- poetry.lock | 27 ++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 8014b557..ff602628 100644 --- a/poetry.lock +++ b/poetry.lock @@ -232,6 +232,7 @@ webargs = "^5.5.3" reference = "12b6dda521f00e449c67c5ea8bf34005918fd649" type = "git" url = "https://github.com/labthings/python-labthings.git" + [[package]] category = "dev" description = "A fast and thorough lazy object proxy." @@ -316,6 +317,17 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "5.4.1" +[[package]] +category = "main" +description = "Cross-platform lib for process and system monitoring in Python." +name = "psutil" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "5.6.7" + +[package.extras] +enum = ["enum34"] + [[package]] category = "dev" description = "Pygments is a syntax highlighting package written in Python." @@ -624,7 +636,7 @@ version = "1.11.2" rpi = ["picamera", "RPi.GPIO"] [metadata] -content-hash = "03ea9560d58dababb9f7f86d533aa9effa087872bcbed58073923325e190ee32" +content-hash = "3727711968c7246075a9b1e250158bcf546aab4654a0cf4fde8a77869180c29f" python-versions = "^3.6" [metadata.files] @@ -847,6 +859,19 @@ pillow = [ {file = "Pillow-5.4.1.win32-py3.6.exe", hash = "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d"}, {file = "Pillow-5.4.1.win32-py3.7.exe", hash = "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522"}, ] +psutil = [ + {file = "psutil-5.6.7-cp27-none-win32.whl", hash = "sha256:1b1575240ca9a90b437e5a40db662acd87bbf181f6aa02f0204978737b913c6b"}, + {file = "psutil-5.6.7-cp27-none-win_amd64.whl", hash = "sha256:28f771129bfee9fc6b63d83a15d857663bbdcae3828e1cb926e91320a9b5b5cd"}, + {file = "psutil-5.6.7-cp35-cp35m-win32.whl", hash = "sha256:21231ef1c1a89728e29b98a885b8e0a8e00d09018f6da5cdc1f43f988471a995"}, + {file = "psutil-5.6.7-cp35-cp35m-win_amd64.whl", hash = "sha256:b74b43fecce384a57094a83d2778cdfc2e2d9a6afaadd1ebecb2e75e0d34e10d"}, + {file = "psutil-5.6.7-cp36-cp36m-win32.whl", hash = "sha256:e85f727ffb21539849e6012f47b12f6dd4c44965e56591d8dec6e8bc9ab96f4a"}, + {file = "psutil-5.6.7-cp36-cp36m-win_amd64.whl", hash = "sha256:b560f5cd86cf8df7bcd258a851ca1ad98f0d5b8b98748e877a0aec4e9032b465"}, + {file = "psutil-5.6.7-cp37-cp37m-win32.whl", hash = "sha256:094f899ac3ef72422b7e00411b4ed174e3c5a2e04c267db6643937ddba67a05b"}, + {file = "psutil-5.6.7-cp37-cp37m-win_amd64.whl", hash = "sha256:fd2e09bb593ad9bdd7429e779699d2d47c1268cbde4dda95fcd1bd17544a0217"}, + {file = "psutil-5.6.7-cp38-cp38-win32.whl", hash = "sha256:70387772f84fa5c3bb6a106915a2445e20ac8f9821c5914d7cbde148f4d7ff73"}, + {file = "psutil-5.6.7-cp38-cp38-win_amd64.whl", hash = "sha256:10b7f75cc8bd676cfc6fa40cd7d5c25b3f45a0e06d43becd7c2d2871cbb5e806"}, + {file = "psutil-5.6.7.tar.gz", hash = "sha256:ffad8eb2ac614518bbe3c0b8eb9dffdb3a8d2e3a7d5da51c5b974fb723a5c5aa"}, +] pygments = [ {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"}, {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"}, diff --git a/pyproject.toml b/pyproject.toml index 632d2ee0..3f15a302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ pyserial = "^3.4" # Used for sangaboard (basic_serial_instrument) until we move labthings = { git = "https://github.com/labthings/python-labthings.git", branch = "master"} # TODO: Pin to a fixed version before 2.0.0 stable python-dateutil = "^2.8" +psutil = "^5.6.7" # Autostorage extension [tool.poetry.extras] rpi = ["picamera", "RPi.GPIO"] From 201da43fb7d2d52b931330a9b0b40ea4837bf703 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 13:41:42 +0000 Subject: [PATCH 45/70] Generate dictionary of available storage locations --- .../api/default_extensions/autostorage.py | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index 1046e895..e2f43fcc 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -1,12 +1,69 @@ from labthings.server.extensions import BaseExtension -from openflexure_microscope.paths import settings_file_path +from openflexure_microscope.paths import settings_file_path, check_rw from openflexure_microscope.config import OpenflexureSettingsFile +from openflexure_microscope.camera.base import BASE_CAPTURE_PATH import logging +import os +import psutil + +from sys import platform + AS_SETTINGS_PATH = settings_file_path("autostorage_settings.json") +def get_partitions(): + return [disk.mountpoint for disk in psutil.disk_partitions() if "rw" in disk.opts] + + +def get_permissive_partitions(): + return [partition for partition in get_partitions() if check_rw(partition)] + + +def get_permissive_locations(): + return [ + (partition, os.path.join(partition, "openflexure", "data", "micrographs")) + for partition in get_permissive_partitions() + ] + + +def get_current_location(camera): + return camera.paths.get("default") + + +def get_default_location(): + return BASE_CAPTURE_PATH + + +def get_all_locations(camera): + locations = {"System": get_current_location(camera)} + # If default is not already listed (e.g. if it's currently set) + if get_default_location() not in locations.values(): + locations["Default"] = get_default_location() + + for ppartition, plocation in get_permissive_locations(): + pdrive = os.path.splitdrive(plocation)[0] + if not ( + pdrive # If path actually has a drive (basically just Windows?) + and any( # And shares a common drive with an existing location + [ + pdrive == os.path.splitdrive(location)[0] + for location in locations.values() + ] + ) + ): + locations[ppartition] = plocation + + # Strip out Nones + return {k: v for k, v in locations.items() if v} + + +class CaptureStorageLocation: + def __init__(self, mountpoint): + pass + + class AutostorageExtension(BaseExtension): def __init__(self): BaseExtension.__init__( @@ -33,5 +90,13 @@ class AutostorageExtension(BaseExtension): self.camera = microscope_obj.camera logging.debug(f"Autostorage extension bound to camera {self.camera}") + logging.debug(self.get_locations()) + + def get_locations(self): + if self.camera: + return get_all_locations(self.camera) + else: + return [] + autostorage_extension_v2 = AutostorageExtension() From 4521837c45eb677d9fd5ff946743e479cdf5f7c6 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 15:14:30 +0000 Subject: [PATCH 46/70] Property to read and write capture storage path (by location key) --- .../api/default_extensions/autostorage.py | 109 ++++++++++++++++-- 1 file changed, 100 insertions(+), 9 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index e2f43fcc..43ed829d 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -1,8 +1,14 @@ from labthings.server.extensions import BaseExtension +from labthings.server.view import View +from labthings.server.decorators import ThingProperty, PropertySchema +from labthings.server import fields +from labthings.server.find import find_component + from openflexure_microscope.paths import settings_file_path, check_rw from openflexure_microscope.config import OpenflexureSettingsFile from openflexure_microscope.camera.base import BASE_CAPTURE_PATH +from flask import abort import logging import os import psutil @@ -32,12 +38,18 @@ def get_current_location(camera): return camera.paths.get("default") +def set_current_location(camera, location: str): + if not os.path.isdir(location): + os.makedirs(location) + return camera.paths.update({"default": location}) + + def get_default_location(): return BASE_CAPTURE_PATH -def get_all_locations(camera): - locations = {"System": get_current_location(camera)} +def get_all_locations(): + locations = {} # If default is not already listed (e.g. if it's currently set) if get_default_location() not in locations.values(): locations["Default"] = get_default_location() @@ -73,30 +85,109 @@ class AutostorageExtension(BaseExtension): description="Handle switching capture storage devices", ) - self.settings = OpenflexureSettingsFile( - AS_SETTINGS_PATH, defaults={"preferred": None} - ) - # We'll store a reference to a camera object, who's capture paths will be modified self.camera = None + self.initial_location = get_default_location() + # Register the on_microscope function to run when the microscope is attached self.on_component("org.openflexure.microscope", self.on_microscope) + @property + def preferred(self): + return self._preferred + + @preferred.setter + def preferred(self, new_path_key: str): + if not new_path_key in self.get_locations().keys(): + raise KeyError(f"No location named {new_path_key}") + self._preferred = new_path_key + 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}") if hasattr(microscope_obj, "camera"): - self.camera = microscope_obj.camera logging.debug(f"Autostorage extension bound to camera {self.camera}") + # Store a reference to the camera + self.camera = microscope_obj.camera + # Store the initial storage location + self.initial_location = get_current_location(self.camera) + + # If preferred path does not exist, or cannot be written to + if not ( + os.path.isdir(self.initial_location) and check_rw(self.initial_location) + ): + logging.error( + f"Preferred capture path {self.initial_location} is missing or cannot be written to. Restoring defaults." + ) + # Reset the storage location to default + set_current_location(self.camera, get_default_location()) + logging.debug(self.get_locations()) def get_locations(self): if self.camera: - return get_all_locations(self.camera) + locations = get_all_locations() + + current_location = get_current_location(self.camera) + if current_location not in locations.values(): + locations.update({"Custom": current_location}) + # Add location from the cameras settings file + return locations else: - return [] + return {} + + def get_preferred_key(self): + current = get_current_location(self.camera) + locations = self.get_locations() + + matches = [k for k, v in locations.items() if v == current] + + if len(matches) > 1: + logging.warning( + "Multiple path matches found. Weird, but carrying on using zeroth." + ) + + return matches[0] + + def set_preferred_key(self, new_path_key: str): + if not new_path_key in self.get_locations().keys(): + raise KeyError(f"No location named {new_path_key}") + + location = self.get_locations().get(new_path_key) + set_current_location(self.camera, location) autostorage_extension_v2 = AutostorageExtension() + + +@ThingProperty +class GetLocationsView(View): + def get(self): + global autostorage_extension_v2 + + return autostorage_extension_v2.get_locations() + + +@ThingProperty +@PropertySchema(fields.String(required=True, example="Default")) +class PreferredLocationView(View): + def get(self): + global autostorage_extension_v2 + + 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: + abort(503, "No microscope connected. Unable to autofocus.") + + autostorage_extension_v2.set_preferred_key(new_path_key) + microscope.save_settings() + + +autostorage_extension_v2.add_view(GetLocationsView, "list-locations") +autostorage_extension_v2.add_view(PreferredLocationView, "location") From d39f897531b02b17968088af2888868a99994d21 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 15:57:28 +0000 Subject: [PATCH 47/70] Fixed GUI rule matching --- openflexure_microscope/api/utilities/gui.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openflexure_microscope/api/utilities/gui.py b/openflexure_microscope/api/utilities/gui.py index 19d83bd8..1a6a11ef 100644 --- a/openflexure_microscope/api/utilities/gui.py +++ b/openflexure_microscope/api/utilities/gui.py @@ -7,10 +7,18 @@ 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._rules) + # Expand shorthand routes into full relative URLs if "forms" in gui_description and isinstance(api_gui["forms"], list): for form in api_gui["forms"]: + # Clean leading slashes from rule + if "route" in form: + while form["route"][0] == "/": + form["route"] = form["route"][1:] + # Match rule in extension object if "route" in form and form["route"] in extension_object._rules.keys(): + logging.debug(f"Expanding {form}") form["route"] = extension_object._rules[form["route"]]["rule"] else: logging.warn( From 0595d3c620a766f890f2462cbccc8b949057c160 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 16:16:06 +0000 Subject: [PATCH 48/70] Let camera object handle its own library rebuild --- openflexure_microscope/api/microscope.py | 5 +---- openflexure_microscope/camera/base.py | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openflexure_microscope/api/microscope.py b/openflexure_microscope/api/microscope.py index 2f534b49..09711e50 100644 --- a/openflexure_microscope/api/microscope.py +++ b/openflexure_microscope/api/microscope.py @@ -1,5 +1,4 @@ from openflexure_microscope import Microscope -from openflexure_microscope.camera.capture import build_captures_from_exif import logging @@ -8,8 +7,6 @@ default_microscope = Microscope() # Restore loaded capture array to camera object logging.debug("Restoring captures...") -default_microscope.camera.images = build_captures_from_exif( - default_microscope.camera.paths["default"] -) +default_microscope.camera.rebuild_captures() logging.debug("Microscope successfully attached!") diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index f74bbc0a..edd55dca 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -8,7 +8,7 @@ import logging from abc import ABCMeta, abstractmethod -from .capture import CaptureObject +from .capture import CaptureObject, build_captures_from_exif from openflexure_microscope.utilities import entry_by_uuid from labthings.core.lock import StrictLock @@ -177,6 +177,9 @@ class BaseCamera(metaclass=ABCMeta): shutil.rmtree(self.paths["temp"]) logging.debug("Cleared {}.".format(self.paths["temp"])) + def rebuild_captures(self): + self.images = build_captures_from_exif(self.paths["default"]) + # START AND STOP WORKER THREAD def start_worker(self, timeout: int = 5) -> bool: From bf00696e14512605a3b2a02fbe4c806945ae22b1 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 16:16:23 +0000 Subject: [PATCH 49/70] Added basic eV GUI --- .../api/default_extensions/autostorage.py | 78 ++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index 43ed829d..8d2101b9 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -1,12 +1,15 @@ from labthings.server.extensions import BaseExtension from labthings.server.view import View -from labthings.server.decorators import ThingProperty, PropertySchema +from labthings.server.decorators import ThingProperty, PropertySchema, use_args from labthings.server import fields from labthings.server.find import find_component from openflexure_microscope.paths import settings_file_path, check_rw from openflexure_microscope.config import OpenflexureSettingsFile from openflexure_microscope.camera.base import BASE_CAPTURE_PATH +from openflexure_microscope.camera.capture import build_captures_from_exif + +from openflexure_microscope.api.utilities.gui import build_gui from flask import abort import logging @@ -158,6 +161,31 @@ class AutostorageExtension(BaseExtension): location = self.get_locations().get(new_path_key) set_current_location(self.camera, location) + def key_to_title(self, path_key: str): + if not path_key in self.get_locations().keys(): + raise KeyError(f"No location named {path_key}") + + return f"{path_key} ({self.get_locations().get(path_key)})" + + def title_to_key(self, path_title: str): + matches = [] + for loc_key in self.get_locations().keys(): + if path_title.startswith(loc_key): + matches.append(loc_key) + + if len(matches) > 1: + logging.warning( + "Multiple path matches found. Weird, but carrying on using zeroth." + ) + + return matches[0] + + def get_titles(self): + return [self.key_to_title(key) for key in self.get_locations().keys()] + + def get_preferred_title(self): + return self.key_to_title(self.get_preferred_key()) + autostorage_extension_v2 = AutostorageExtension() @@ -189,5 +217,53 @@ class PreferredLocationView(View): microscope.save_settings() +class PreferredLocationGUIView(View): + @use_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}") + + autostorage_extension_v2.set_preferred_key(new_path_key) + + logging.debug("Restoring captures...") + autostorage_extension_v2.camera.rebuild_captures() + + return new_path_title + + +def dynamic_form(): + global autostorage_extension_v2 + return { + "icon": "sd_storage", + "forms": [ + { + "name": "Autostorage", + "isCollapsible": False, + "isTask": False, + "route": "/location-from-title", + "submitLabel": "Set path", + "schema": [ + { + "fieldType": "selectList", + "name": "new_path_title", + "label": "Capture storage path", + "options": autostorage_extension_v2.get_titles(), + "value": autostorage_extension_v2.get_preferred_title(), + } + ], + } + ], + } + + autostorage_extension_v2.add_view(GetLocationsView, "list-locations") autostorage_extension_v2.add_view(PreferredLocationView, "location") +autostorage_extension_v2.add_view(PreferredLocationGUIView, "location-from-title") +autostorage_extension_v2.add_meta( + "gui", build_gui(dynamic_form, autostorage_extension_v2) +) From f0d6afec88f6d25ad1cb4bcb04606d39941b6354 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 16:27:02 +0000 Subject: [PATCH 50/70] Removed some debug statements --- openflexure_microscope/api/utilities/gui.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openflexure_microscope/api/utilities/gui.py b/openflexure_microscope/api/utilities/gui.py index 1a6a11ef..c0121594 100644 --- a/openflexure_microscope/api/utilities/gui.py +++ b/openflexure_microscope/api/utilities/gui.py @@ -7,8 +7,6 @@ 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._rules) - # Expand shorthand routes into full relative URLs if "forms" in gui_description and isinstance(api_gui["forms"], list): for form in api_gui["forms"]: @@ -18,7 +16,6 @@ def build_gui_from_dict(gui_description, extension_object): form["route"] = form["route"][1:] # Match rule in extension object if "route" in form and form["route"] in extension_object._rules.keys(): - logging.debug(f"Expanding {form}") form["route"] = extension_object._rules[form["route"]]["rule"] else: logging.warn( From ff90e998c77d5a3c6efaef05df8e3e2231212f51 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 16:55:58 +0000 Subject: [PATCH 51/70] Better handle reloading captures on path change --- .../api/default_extensions/autostorage.py | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index 8d2101b9..8f74df3f 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -44,7 +44,11 @@ def get_current_location(camera): def set_current_location(camera, location: str): if not os.path.isdir(location): os.makedirs(location) - return camera.paths.update({"default": location}) + logging.debug("Updating location...") + camera.paths.update({"default": location}) + logging.debug("Rebuilding captures...") + camera.rebuild_captures() + logging.debug("Capture location changed successfully.") def get_default_location(): @@ -96,16 +100,6 @@ class AutostorageExtension(BaseExtension): # Register the on_microscope function to run when the microscope is attached self.on_component("org.openflexure.microscope", self.on_microscope) - @property - def preferred(self): - return self._preferred - - @preferred.setter - def preferred(self, new_path_key: str): - if not new_path_key in self.get_locations().keys(): - raise KeyError(f"No location named {new_path_key}") - self._preferred = new_path_key - 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}") @@ -118,17 +112,21 @@ class AutostorageExtension(BaseExtension): self.initial_location = get_current_location(self.camera) # If preferred path does not exist, or cannot be written to - if not ( - os.path.isdir(self.initial_location) and check_rw(self.initial_location) - ): - logging.error( - f"Preferred capture path {self.initial_location} is missing or cannot be written to. Restoring defaults." - ) - # Reset the storage location to default - set_current_location(self.camera, get_default_location()) + self.check_location(self.initial_location) logging.debug(self.get_locations()) + def check_location(self, location=None): + if not location: + location = get_current_location(self.camera) + # 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." + ) + # Reset the storage location to default + set_current_location(self.camera, get_default_location()) + def get_locations(self): if self.camera: locations = get_all_locations() @@ -195,6 +193,7 @@ class GetLocationsView(View): def get(self): global autostorage_extension_v2 + autostorage_extension_v2.check_location() return autostorage_extension_v2.get_locations() @@ -204,6 +203,7 @@ class PreferredLocationView(View): 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): @@ -213,6 +213,7 @@ class PreferredLocationView(View): if not microscope: abort(503, "No microscope connected. Unable to autofocus.") + autostorage_extension_v2.check_location() autostorage_extension_v2.set_preferred_key(new_path_key) microscope.save_settings() @@ -228,16 +229,15 @@ class PreferredLocationGUIView(View): new_path_key = autostorage_extension_v2.title_to_key(new_path_title) logging.debug(f"{new_path_key}") + autostorage_extension_v2.check_location() autostorage_extension_v2.set_preferred_key(new_path_key) - logging.debug("Restoring captures...") - autostorage_extension_v2.camera.rebuild_captures() - return new_path_title def dynamic_form(): global autostorage_extension_v2 + autostorage_extension_v2.check_location() return { "icon": "sd_storage", "forms": [ @@ -246,6 +246,7 @@ def dynamic_form(): "isCollapsible": False, "isTask": False, "route": "/location-from-title", + "emitOnResponse": "globalUpdateCaptures", "submitLabel": "Set path", "schema": [ { From 5264b04be4880272dc2ccaa671bc34cc72299363 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 17:25:39 +0000 Subject: [PATCH 52/70] Updated and iterated to beta 5 --- poetry.lock | 20 +++++++++----------- pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index ff602628..99194619 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,12 +12,12 @@ description = "A pluggable API specification generator. Currently supports the O name = "apispec" optional = false python-versions = ">=3.5" -version = "3.2.0" +version = "3.3.0" [package.extras] -dev = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"] -docs = ["marshmallow (>=2.19.2)", "pyyaml (5.2)", "sphinx (2.3.0)", "sphinx-issues (1.2.0)", "sphinx-rtd-theme (0.4.3)"] -lint = ["flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"] +dev = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)", "tox"] +docs = ["marshmallow (>=2.19.2)", "pyyaml (5.3)", "sphinx (2.4.1)", "sphinx-issues (1.2.0)", "sphinx-rtd-theme (0.4.3)"] +lint = ["flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)"] tests = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock"] validation = ["prance (>=0.11)"] yaml = ["PyYAML (>=3.10)"] @@ -232,7 +232,6 @@ webargs = "^5.5.3" reference = "12b6dda521f00e449c67c5ea8bf34005918fd649" type = "git" url = "https://github.com/labthings/python-labthings.git" - [[package]] category = "dev" description = "A fast and thorough lazy object proxy." @@ -308,7 +307,6 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb" type = "git" url = "https://github.com/rwb27/picamera.git" - [[package]] category = "main" description = "Python Imaging Library (Fork)" @@ -460,7 +458,7 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "2.4.1" +version = "2.4.2" [package.dependencies] Jinja2 = ">=2.3" @@ -645,8 +643,8 @@ alabaster = [ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] apispec = [ - {file = "apispec-3.2.0-py2.py3-none-any.whl", hash = "sha256:f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"}, - {file = "apispec-3.2.0.tar.gz", hash = "sha256:c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a"}, + {file = "apispec-3.3.0-py2.py3-none-any.whl", hash = "sha256:9bf4e51d56c9067c60668b78210ae213894f060f85593dc2ad8805eb7d875a2a"}, + {file = "apispec-3.3.0.tar.gz", hash = "sha256:419d0564b899e182c2af50483ea074db8cb05fee60838be58bb4542095d5c08d"}, ] appdirs = [ {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, @@ -965,8 +963,8 @@ snowballstemmer = [ {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, ] sphinx = [ - {file = "Sphinx-2.4.1-py3-none-any.whl", hash = "sha256:5024a67f065fe60d9db2005580074d81f22a02dd8f00a5b1ec3d5f4d42bc88d8"}, - {file = "Sphinx-2.4.1.tar.gz", hash = "sha256:f929b72e0cfe45fa581b8964d54457117863a6a6c9369ecc1a65b8827abd3bf2"}, + {file = "Sphinx-2.4.2-py3-none-any.whl", hash = "sha256:543d39db5f82d83a5c1aa0c10c88f2b6cff2da3e711aa849b2c627b4b403bbd9"}, + {file = "Sphinx-2.4.2.tar.gz", hash = "sha256:525527074f2e0c2585f68f73c99b4dc257c34bbe308b27f5f8c7a6e20642742f"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"}, diff --git a/pyproject.toml b/pyproject.toml index 3f15a302..69e03271 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "openflexure_microscope" -version = "2.0.0-beta.4" +version = "2.0.0-beta.5" description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope." authors = [ From 0182f18e33ef12c951c30e635d9db011778adef4 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 19 Feb 2020 10:41:31 +0000 Subject: [PATCH 53/70] Reverted config initialisation behaviour --- openflexure_microscope/config.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index c0a971ba..3cd36f72 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -32,8 +32,7 @@ class OpenflexureSettingsFile: self.path = path # Initialise basic config file with defaults if it doesn't exist - defaults_str = json.dumps(defaults, cls=JSONEncoder, indent=2, sort_keys=True) - initialise_file(self.path, populate=defaults_str) + initialise_file(self.path, populate=defaults) def load(self) -> dict: """ @@ -104,7 +103,6 @@ class JSONEncoder(flask.json.JSONEncoder): return flask.json.JSONEncoder.default(self, o) - # HANDLE BASIC LOADING AND SAVING OF SETTINGS FILES From 7235445cf27bf12f1fb23324f91a3e5404330f61 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 1 Mar 2020 17:38:31 +0000 Subject: [PATCH 54/70] Upgraded dependencies and picamera fork --- poetry.lock | 157 +++++++++++++++++++++++++------------------------ pyproject.toml | 2 +- 2 files changed, 82 insertions(+), 77 deletions(-) diff --git a/poetry.lock b/poetry.lock index 99194619..390ef18e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -167,7 +167,7 @@ description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8" +version = "2.9" [[package]] category = "dev" @@ -254,12 +254,12 @@ description = "A lightweight library for converting complex datatypes to and fro name = "marshmallow" optional = false python-versions = ">=3.5" -version = "3.4.0" +version = "3.5.0" [package.extras] -dev = ["pytest", "pytz", "simplejson", "mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.3)", "pre-commit (>=1.20,<3.0)", "tox"] -docs = ["sphinx (2.3.1)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"] -lint = ["mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.3)", "pre-commit (>=1.20,<3.0)"] +dev = ["pytest", "pytz", "simplejson", "mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)", "tox"] +docs = ["sphinx (2.4.2)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"] +lint = ["mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -296,7 +296,7 @@ description = "A pure Python interface for the Raspberry Pi camera module." name = "picamera" optional = true python-versions = "*" -version = "1.13.1b0" +version = "1.13.2b0" [package.extras] array = ["numpy"] @@ -304,7 +304,7 @@ doc = ["sphinx"] test = ["coverage", "pytest", "mock", "pillow", "numpy"] [package.source] -reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb" +reference = "79177fa7f28d6d5c6eab5ba814b420b1785b6b24" type = "git" url = "https://github.com/rwb27/picamera.git" [[package]] @@ -321,7 +321,7 @@ description = "Cross-platform lib for process and system monitoring in Python." name = "psutil" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "5.6.7" +version = "5.7.0" [package.extras] enum = ["enum34"] @@ -389,16 +389,16 @@ description = "Python HTTP for Humans." name = "requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.22.0" +version = "2.23.0" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<3.1.0" -idna = ">=2.5,<2.9" +chardet = ">=3.0.2,<4" +idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] [[package]] @@ -458,7 +458,7 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "2.4.2" +version = "2.4.3" [package.dependencies] Jinja2 = ">=2.3" @@ -485,36 +485,39 @@ test = ["pytest (<5.3.3)", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8 [[package]] category = "dev" -description = "" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" name = "sphinxcontrib-applehelp" optional = false -python-versions = "*" -version = "1.0.1" - -[package.extras] -test = ["pytest", "flake8", "mypy"] - -[[package]] -category = "dev" -description = "" -name = "sphinxcontrib-devhelp" -optional = false -python-versions = "*" -version = "1.0.1" - -[package.extras] -test = ["pytest", "flake8", "mypy"] - -[[package]] -category = "dev" -description = "" -name = "sphinxcontrib-htmlhelp" -optional = false -python-versions = "*" +python-versions = ">=3.5" version = "1.0.2" [package.extras] -test = ["pytest", "flake8", "mypy", "html5lib"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] + +[[package]] +category = "dev" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +name = "sphinxcontrib-devhelp" +optional = false +python-versions = ">=3.5" +version = "1.0.2" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] + +[[package]] +category = "dev" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +name = "sphinxcontrib-htmlhelp" +optional = false +python-versions = ">=3.5" +version = "1.0.3" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest", "html5lib"] [[package]] category = "dev" @@ -541,25 +544,27 @@ test = ["pytest", "flake8", "mypy"] [[package]] category = "dev" -description = "" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." name = "sphinxcontrib-qthelp" optional = false -python-versions = "*" -version = "1.0.2" +python-versions = ">=3.5" +version = "1.0.3" [package.extras] -test = ["pytest", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" -description = "" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." name = "sphinxcontrib-serializinghtml" optional = false -python-versions = "*" -version = "1.1.3" +python-versions = ">=3.5" +version = "1.1.4" [package.extras] -test = ["pytest", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" @@ -634,7 +639,7 @@ version = "1.11.2" rpi = ["picamera", "RPi.GPIO"] [metadata] -content-hash = "3727711968c7246075a9b1e250158bcf546aab4654a0cf4fde8a77869180c29f" +content-hash = "ad29f5c7d3a9ac55697f3d74f7b1c992cb2617039cbcab23caa0221f8ed4718a" python-versions = "^3.6" [metadata.files] @@ -695,8 +700,8 @@ flask-cors = [ {file = "Flask_Cors-3.0.8-py2.py3-none-any.whl", hash = "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"}, ] idna = [ - {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, - {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, + {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, + {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, ] imagesize = [ {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, @@ -769,8 +774,8 @@ markupsafe = [ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] marshmallow = [ - {file = "marshmallow-3.4.0-py2.py3-none-any.whl", hash = "sha256:7669b944d6233b81f68739d5826f1176c3841cc31cf6b856841083b5a72f5ca9"}, - {file = "marshmallow-3.4.0.tar.gz", hash = "sha256:c9d277f6092f32300395fb83d343be9f61b5e99d66d22bae1e5e7cd82608fee6"}, + {file = "marshmallow-3.5.0-py2.py3-none-any.whl", hash = "sha256:4b95c7735f93eb781dfdc4dded028108998cad759dda8dd9d4b5b4ac574cbf13"}, + {file = "marshmallow-3.5.0.tar.gz", hash = "sha256:3a94945a7461f2ab4df9576e51c97d66bee2c86155d3d3933fab752b31effab8"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -858,17 +863,17 @@ pillow = [ {file = "Pillow-5.4.1.win32-py3.7.exe", hash = "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522"}, ] psutil = [ - {file = "psutil-5.6.7-cp27-none-win32.whl", hash = "sha256:1b1575240ca9a90b437e5a40db662acd87bbf181f6aa02f0204978737b913c6b"}, - {file = "psutil-5.6.7-cp27-none-win_amd64.whl", hash = "sha256:28f771129bfee9fc6b63d83a15d857663bbdcae3828e1cb926e91320a9b5b5cd"}, - {file = "psutil-5.6.7-cp35-cp35m-win32.whl", hash = "sha256:21231ef1c1a89728e29b98a885b8e0a8e00d09018f6da5cdc1f43f988471a995"}, - {file = "psutil-5.6.7-cp35-cp35m-win_amd64.whl", hash = "sha256:b74b43fecce384a57094a83d2778cdfc2e2d9a6afaadd1ebecb2e75e0d34e10d"}, - {file = "psutil-5.6.7-cp36-cp36m-win32.whl", hash = "sha256:e85f727ffb21539849e6012f47b12f6dd4c44965e56591d8dec6e8bc9ab96f4a"}, - {file = "psutil-5.6.7-cp36-cp36m-win_amd64.whl", hash = "sha256:b560f5cd86cf8df7bcd258a851ca1ad98f0d5b8b98748e877a0aec4e9032b465"}, - {file = "psutil-5.6.7-cp37-cp37m-win32.whl", hash = "sha256:094f899ac3ef72422b7e00411b4ed174e3c5a2e04c267db6643937ddba67a05b"}, - {file = "psutil-5.6.7-cp37-cp37m-win_amd64.whl", hash = "sha256:fd2e09bb593ad9bdd7429e779699d2d47c1268cbde4dda95fcd1bd17544a0217"}, - {file = "psutil-5.6.7-cp38-cp38-win32.whl", hash = "sha256:70387772f84fa5c3bb6a106915a2445e20ac8f9821c5914d7cbde148f4d7ff73"}, - {file = "psutil-5.6.7-cp38-cp38-win_amd64.whl", hash = "sha256:10b7f75cc8bd676cfc6fa40cd7d5c25b3f45a0e06d43becd7c2d2871cbb5e806"}, - {file = "psutil-5.6.7.tar.gz", hash = "sha256:ffad8eb2ac614518bbe3c0b8eb9dffdb3a8d2e3a7d5da51c5b974fb723a5c5aa"}, + {file = "psutil-5.7.0-cp27-none-win32.whl", hash = "sha256:298af2f14b635c3c7118fd9183843f4e73e681bb6f01e12284d4d70d48a60953"}, + {file = "psutil-5.7.0-cp27-none-win_amd64.whl", hash = "sha256:75e22717d4dbc7ca529ec5063000b2b294fc9a367f9c9ede1f65846c7955fd38"}, + {file = "psutil-5.7.0-cp35-cp35m-win32.whl", hash = "sha256:f344ca230dd8e8d5eee16827596f1c22ec0876127c28e800d7ae20ed44c4b310"}, + {file = "psutil-5.7.0-cp35-cp35m-win_amd64.whl", hash = "sha256:e2d0c5b07c6fe5a87fa27b7855017edb0d52ee73b71e6ee368fae268605cc3f5"}, + {file = "psutil-5.7.0-cp36-cp36m-win32.whl", hash = "sha256:a02f4ac50d4a23253b68233b07e7cdb567bd025b982d5cf0ee78296990c22d9e"}, + {file = "psutil-5.7.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1413f4158eb50e110777c4f15d7c759521703bd6beb58926f1d562da40180058"}, + {file = "psutil-5.7.0-cp37-cp37m-win32.whl", hash = "sha256:d008ddc00c6906ec80040d26dc2d3e3962109e40ad07fd8a12d0284ce5e0e4f8"}, + {file = "psutil-5.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:73f35ab66c6c7a9ce82ba44b1e9b1050be2a80cd4dcc3352cc108656b115c74f"}, + {file = "psutil-5.7.0-cp38-cp38-win32.whl", hash = "sha256:60b86f327c198561f101a92be1995f9ae0399736b6eced8f24af41ec64fb88d4"}, + {file = "psutil-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:d84029b190c8a66a946e28b4d3934d2ca1528ec94764b180f7d6ea57b0e75e26"}, + {file = "psutil-5.7.0.tar.gz", hash = "sha256:685ec16ca14d079455892f25bd124df26ff9137664af445563c1bd36629b5e0e"}, ] pygments = [ {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"}, @@ -895,8 +900,8 @@ pytz = [ {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, ] requests = [ - {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, - {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, + {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, + {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, ] rope = [ {file = "rope-0.14.0-py2-none-any.whl", hash = "sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969"}, @@ -963,20 +968,20 @@ snowballstemmer = [ {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, ] sphinx = [ - {file = "Sphinx-2.4.2-py3-none-any.whl", hash = "sha256:543d39db5f82d83a5c1aa0c10c88f2b6cff2da3e711aa849b2c627b4b403bbd9"}, - {file = "Sphinx-2.4.2.tar.gz", hash = "sha256:525527074f2e0c2585f68f73c99b4dc257c34bbe308b27f5f8c7a6e20642742f"}, + {file = "Sphinx-2.4.3-py3-none-any.whl", hash = "sha256:776ff8333181138fae52df65be733127539623bb46cc692e7fa0fcfc80d7aa88"}, + {file = "Sphinx-2.4.3.tar.gz", hash = "sha256:ca762da97c3b5107cbf0ab9e11d3ec7ab8d3c31377266fd613b962ed971df709"}, ] sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"}, - {file = "sphinxcontrib_applehelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"}, + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, ] sphinxcontrib-devhelp = [ - {file = "sphinxcontrib-devhelp-1.0.1.tar.gz", hash = "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34"}, - {file = "sphinxcontrib_devhelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"}, + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-1.0.2.tar.gz", hash = "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422"}, - {file = "sphinxcontrib_htmlhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"}, + {file = "sphinxcontrib-htmlhelp-1.0.3.tar.gz", hash = "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"}, + {file = "sphinxcontrib_htmlhelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f"}, ] sphinxcontrib-httpdomain = [ {file = "sphinxcontrib-httpdomain-1.7.0.tar.gz", hash = "sha256:ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"}, @@ -987,12 +992,12 @@ sphinxcontrib-jsmath = [ {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] sphinxcontrib-qthelp = [ - {file = "sphinxcontrib-qthelp-1.0.2.tar.gz", hash = "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"}, - {file = "sphinxcontrib_qthelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20"}, + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.3.tar.gz", hash = "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227"}, - {file = "sphinxcontrib_serializinghtml-1.1.3-py2.py3-none-any.whl", hash = "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"}, + {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, + {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, ] toml = [ {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, diff --git a/pyproject.toml b/pyproject.toml index 69e03271..185344a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ numpy = "^1.17.0" Pillow = "^5.4" scipy = "1.3.0" # Exact version until we can guarantee a wheel -picamera = { git = "https://github.com/rwb27/picamera.git", branch = "lens-shading", optional = true } # Lens shading requires >1.14 or RWB fork, lens-shading branch. +picamera = { git = "https://github.com/rwb27/picamera.git", branch = "master", optional = true } # Lens shading requires >1.14 or RWB fork, lens-shading branch. "RPi.GPIO" = { version = "^0.6.5", optional = true } pyserial = "^3.4" # Used for sangaboard (basic_serial_instrument) until we move to sangaboard pip library From 60c6ae72f8a7fb52b87b0c22593e2db2c225292e Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 1 Mar 2020 17:57:30 +0000 Subject: [PATCH 55/70] Handle captures as an OrderedDict. Fixes file deletion --- .../api/default_extensions/zip_builder.py | 3 +-- .../api/v2/views/captures.py | 23 +++++++++------- openflexure_microscope/camera/base.py | 26 +++++++++++-------- openflexure_microscope/camera/capture.py | 6 +++-- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index 3b4840f0..22375a1e 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -81,8 +81,7 @@ class ZipManager: # Get array of captures from IDs capture_list = [ - microscope.camera.image_from_id(capture_id) - for capture_id in capture_id_list + microscope.camera.images.get(capture_id) for capture_id in capture_id_list ] # Remove Nones from list (missing/invalid captures) capture_list = [capture for capture in capture_list if capture] diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index f52a7ed0..27a4a636 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -20,6 +20,7 @@ class InstrumentSchema(Schema): settings = fields.Dict() state = fields.Dict() + class CaptureMetadataImageSchema(Schema): id = fields.UUID() acquisitionDate = fields.String(format="date") @@ -36,6 +37,7 @@ class CaptureMetadataSchema(Schema): image = fields.Nested(CaptureMetadataImageSchema()) instrument = fields.Nested(InstrumentSchema()) + class CaptureSchema(Schema): id = fields.String() file = fields.String( @@ -98,7 +100,7 @@ class CaptureList(View): List all image captures """ microscope = find_component("org.openflexure.microscope") - image_list = microscope.camera.images + image_list = microscope.camera.images.values() return image_list @@ -110,7 +112,7 @@ class CaptureView(View): Description of a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.camera.image_from_id(id) + capture_obj = microscope.camera.images.get(id) if not capture_obj: return abort(404) # 404 Not Found @@ -122,12 +124,15 @@ class CaptureView(View): Delete a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.camera.image_from_id(id) + capture_obj = microscope.camera.images.get(id) if not capture_obj: return abort(404) # 404 Not Found + # Delete the capture file capture_obj.delete() + # Delete from capture list + del microscope.camera.images[id] return "", 204 @@ -140,7 +145,7 @@ class CaptureDownload(View): Image data for a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.camera.image_from_id(id) + capture_obj = microscope.camera.images.get(id) if not capture_obj: return abort(404) # 404 Not Found @@ -175,7 +180,7 @@ class CaptureTags(View): Get tags associated with a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.camera.image_from_id(id) + capture_obj = microscope.camera.images.get(id) if not capture_obj: return abort(404) # 404 Not Found @@ -187,7 +192,7 @@ class CaptureTags(View): Add tags to a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.camera.image_from_id(id) + capture_obj = microscope.camera.images.get(id) if not capture_obj: return abort(404) # 404 Not Found @@ -207,7 +212,7 @@ class CaptureTags(View): Delete tags from a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.camera.image_from_id(id) + capture_obj = microscope.camera.images.get(id) if not capture_obj: return abort(404) # 404 Not Found @@ -230,7 +235,7 @@ class CaptureAnnotations(View): Get annotations associated with a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.camera.image_from_id(id) + capture_obj = microscope.camera.images.get(id) if not capture_obj: return abort(404) # 404 Not Found @@ -242,7 +247,7 @@ class CaptureAnnotations(View): Update metadata for a single image capture """ microscope = find_component("org.openflexure.microscope") - capture_obj = microscope.camera.image_from_id(id) + capture_obj = microscope.camera.images.get(id) if not capture_obj: return abort(404) # 404 Not Found diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index edd55dca..41b43254 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -7,6 +7,7 @@ import datetime import logging from abc import ABCMeta, abstractmethod +from collections import OrderedDict from .capture import CaptureObject, build_captures_from_exif from openflexure_microscope.utilities import entry_by_uuid @@ -113,8 +114,8 @@ class BaseCamera(metaclass=ABCMeta): self.paths = {"default": BASE_CAPTURE_PATH, "temp": TEMP_CAPTURE_PATH} # Capture data - self.images = [] - self.videos = [] + self.images = OrderedDict() + self.videos = OrderedDict() @property @abstractmethod @@ -158,7 +159,7 @@ class BaseCamera(metaclass=ABCMeta): """Close the BaseCamera and all attached StreamObjects.""" logging.info("Closing {}".format(self)) # Close all StreamObjects - for capture_list in [self.images, self.videos]: + for capture_list in [self.images.values(), self.videos.values()]: for stream_object in capture_list: stream_object.close() # Empty temp directory @@ -244,20 +245,22 @@ class BaseCamera(metaclass=ABCMeta): @property def image(self): """Return the latest captured image.""" - return last_entry(self.images) + return last_entry(self.images.values()) @property def video(self): """Return the latest recorded video.""" - return last_entry(self.videos) + return last_entry(self.videos.values()) def image_from_id(self, image_id): """Return an image StreamObject with a matching ID.""" - return entry_by_uuid(image_id, self.images) + logging.warning("image_from_id is deprecated. Access captures as a dictionary.") + return entry_by_uuid(image_id, self.images.values()) def video_from_id(self, video_id): """Return a video StreamObject with a matching ID.""" - return entry_by_uuid(video_id, self.videos) + logging.warning("video_from_id is deprecated. Access captures as a dictionary.") + return entry_by_uuid(video_id, self.videos.values()) # CREATING NEW CAPTURES @@ -282,7 +285,7 @@ class BaseCamera(metaclass=ABCMeta): # Generate file name if not filename: - filename = generate_numbered_basename(self.images) + filename = generate_numbered_basename(self.images.values()) logging.debug(filename) filename = "{}.{}".format(filename, fmt) @@ -300,7 +303,7 @@ class BaseCamera(metaclass=ABCMeta): output.put_tags(["temporary"]) # Update capture list - self.images.append(output) + self.images[output.id] = output return output @@ -322,10 +325,11 @@ class BaseCamera(metaclass=ABCMeta): folder (str): Name of the folder in which to store the capture. fmt (str): Format of the capture. """ + # TODO: Remove the redundancy here # Generate file name if not filename: - filename = generate_numbered_basename(self.videos) + filename = generate_numbered_basename(self.videos.values()) logging.debug(filename) filename = "{}.{}".format(filename, fmt) @@ -343,7 +347,7 @@ class BaseCamera(metaclass=ABCMeta): output.put_tags(["temporary"]) # Update capture list - self.videos.append(output) + self.videos[output.id] = output return output diff --git a/openflexure_microscope/camera/capture.py b/openflexure_microscope/camera/capture.py index 0f190c58..071a52c8 100644 --- a/openflexure_microscope/camera/capture.py +++ b/openflexure_microscope/camera/capture.py @@ -10,6 +10,8 @@ from PIL import Image import dateutil.parser import atexit +from collections import OrderedDict + from openflexure_microscope.camera import piexif from openflexure_microscope.camera.piexif._exceptions import InvalidImageDataError from openflexure_microscope.config import JSONEncoder @@ -58,7 +60,7 @@ def build_captures_from_exif(capture_path): logging.debug("Reloading captures from {}...".format(capture_path)) files = make_file_list(capture_path, EXIF_FORMATS) - captures = [] + captures = OrderedDict() for f in files: logging.debug("Reloading capture {}...".format(f)) @@ -66,7 +68,7 @@ def build_captures_from_exif(capture_path): if exif: capture = capture_from_exif(f, exif) if capture: - captures.append(capture) + captures[capture.id] = capture else: logging.error("Invalid data at {}. Skipping.".format(f)) From a1531e9b1480a49a6c1f5c28ba4b3b0bf91cc9cf Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 1 Mar 2020 18:04:33 +0000 Subject: [PATCH 56/70] Fixed capture key types --- openflexure_microscope/camera/base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index 41b43254..e3da4131 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -303,7 +303,9 @@ class BaseCamera(metaclass=ABCMeta): output.put_tags(["temporary"]) # Update capture list - self.images[output.id] = output + capture_key = str(output.id) + logging.debug(f"Adding image {output} with key {capture_key}") + self.images[capture_key] = output return output @@ -347,7 +349,9 @@ class BaseCamera(metaclass=ABCMeta): output.put_tags(["temporary"]) # Update capture list - self.videos[output.id] = output + capture_key = str(output.id) + logging.debug(f"Adding video {output} with key {capture_key}") + self.videos[capture_key] = output return output From 843d282dfc75030ced52dbe66c2a40627b3ebb4f Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 3 Mar 2020 09:56:04 +0000 Subject: [PATCH 57/70] Added GUI title to autostorage --- openflexure_microscope/api/default_extensions/autostorage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index 8f74df3f..6b92bea0 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -240,6 +240,7 @@ def dynamic_form(): autostorage_extension_v2.check_location() return { "icon": "sd_storage", + "title": "Storage", "forms": [ { "name": "Autostorage", From 9842de88593e5915f614a5d8b8211a843c49c3a1 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 3 Mar 2020 16:13:21 +0000 Subject: [PATCH 58/70] Started basic rescue script --- openflexure_microscope/__init__.py | 6 -- openflexure_microscope/api/microscope.py | 2 +- openflexure_microscope/camera/capture.py | 4 +- openflexure_microscope/rescue/__init__.py | 0 openflexure_microscope/rescue/auto.py | 77 +++++++++++++++++++ .../rescue/check_capture_reload.py | 34 ++++++++ .../rescue/check_settings.py | 47 +++++++++++ .../rescue/monitor_service.py | 59 ++++++++++++++ .../rescue/monitor_timeout.py | 39 ++++++++++ 9 files changed, 260 insertions(+), 8 deletions(-) create mode 100644 openflexure_microscope/rescue/__init__.py create mode 100644 openflexure_microscope/rescue/auto.py create mode 100644 openflexure_microscope/rescue/check_capture_reload.py create mode 100644 openflexure_microscope/rescue/check_settings.py create mode 100644 openflexure_microscope/rescue/monitor_service.py create mode 100644 openflexure_microscope/rescue/monitor_timeout.py diff --git a/openflexure_microscope/__init__.py b/openflexure_microscope/__init__.py index d8fd15ec..e69de29b 100644 --- a/openflexure_microscope/__init__.py +++ b/openflexure_microscope/__init__.py @@ -1,6 +0,0 @@ -__all__ = ["Microscope", "config", "utilities"] -__version__ = "0.1.0" - -from .microscope import Microscope -from . import config -from . import utilities diff --git a/openflexure_microscope/api/microscope.py b/openflexure_microscope/api/microscope.py index 09711e50..d9496684 100644 --- a/openflexure_microscope/api/microscope.py +++ b/openflexure_microscope/api/microscope.py @@ -1,4 +1,4 @@ -from openflexure_microscope import Microscope +from openflexure_microscope.microscope import Microscope import logging diff --git a/openflexure_microscope/camera/capture.py b/openflexure_microscope/camera/capture.py index 071a52c8..70bee3ec 100644 --- a/openflexure_microscope/camera/capture.py +++ b/openflexure_microscope/camera/capture.py @@ -98,7 +98,9 @@ def capture_from_exif(path, exif_dict): try: image_metadata = exif_dict.pop("image") except KeyError as e: - logging.error(f"Unable to obtain OpenFlexure metadata from file {path}") + logging.error( + f"Unable to obtain valid 2.0 OpenFlexure metadata from file {path}" + ) return None # Populate capture parameters diff --git a/openflexure_microscope/rescue/__init__.py b/openflexure_microscope/rescue/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/openflexure_microscope/rescue/auto.py b/openflexure_microscope/rescue/auto.py new file mode 100644 index 00000000..9983c1a9 --- /dev/null +++ b/openflexure_microscope/rescue/auto.py @@ -0,0 +1,77 @@ +from openflexure_microscope.paths import ( + FALLBACK_OPENFLEXURE_VAR_PATH, + PREFERRED_OPENFLEXURE_VAR_PATH, +) +import logging +import os + +from . import check_settings, check_capture_reload + +# Paths for suggestions +LOGS_PATHS = [ + os.path.join(PREFERRED_OPENFLEXURE_VAR_PATH, "logs"), + os.path.join(FALLBACK_OPENFLEXURE_VAR_PATH, "logs"), +] +SETTINGS_PATHS = [ + os.path.join(var_path, "settings", "microscope_settings.json") + for var_path in (PREFERRED_OPENFLEXURE_VAR_PATH, FALLBACK_OPENFLEXURE_VAR_PATH) +] +CONFIG_PATHS = [ + os.path.join(var_path, "settings", "microscope_configuration.json") + for var_path in (PREFERRED_OPENFLEXURE_VAR_PATH, FALLBACK_OPENFLEXURE_VAR_PATH) +] +DATA_PATHS = [ + os.path.join(PREFERRED_OPENFLEXURE_VAR_PATH, "data"), + os.path.join(FALLBACK_OPENFLEXURE_VAR_PATH, "data"), +] + +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) +logging.debug("Testing debug logger. One two one two.") + + +class bcolors: + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + +error_keys = { + "config_settings_import_error": "The configuration submodule could not be imported properly. This is usually because the default config or settings files are badly broken somehow. See further errors for details", + "default_config_empty": "The default configuration file is empty, and could not be automatically populated.", + "default_settings_empty": "The default settings file is empty, and could not be automatically populated.", + "default_config_error": f"The default configuration file could not be parsed. To fix, consider backing up and deleting your configuration files from {CONFIG_PATHS} to reset the configuration.", + "default_settings_error": f"The default settings file could not be parsed. To fix, consider backing up and deleting your configuration files from {SETTINGS_PATHS} to reset the configuration.", + "capture_rebuild_timeout": f"Capture database rebuilding took a long time. This may not cause catastrophic errors, but rather will cause the server to hang for a while. To fix, consider moving your captures from {DATA_PATHS} to another location.", +} + +if __name__ == "__main__": + spoof = False + + error_sources = [] + + if spoof: + error_sources = list(error_keys.keys()) + + error_sources.extend(check_settings.main()) + error_sources.extend(check_capture_reload.main()) + + if not error_sources: + + print() + print(bcolors.OKGREEN + "No errors found!" + bcolors.ENDC) + print( + "That's not to say everything is fine, only that our automatic diagnostics couldn't find much." + ) + print(f"You can check through the server logs at {LOGS_PATHS}") + + else: + for err_code in error_sources: + logging.error(err_code) + if error_keys.get(err_code): + print(bcolors.FAIL + error_keys.get(err_code) + bcolors.ENDC) diff --git a/openflexure_microscope/rescue/check_capture_reload.py b/openflexure_microscope/rescue/check_capture_reload.py new file mode 100644 index 00000000..3a61c692 --- /dev/null +++ b/openflexure_microscope/rescue/check_capture_reload.py @@ -0,0 +1,34 @@ +from openflexure_microscope.rescue.monitor_timeout import launch_timeout_test_process +from openflexure_microscope.camera.capture import build_captures_from_exif +from openflexure_microscope.camera.base import BASE_CAPTURE_PATH +from openflexure_microscope.config import user_settings + +import logging + + +def check_capture_rebuild(timeout=10): + logging.info("Loading user settings...") + settings = user_settings.load() + + cap_path = str(settings.get("camera", {}).get("paths", {}).get("default")) + logging.info(f"Capture path found: {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." + ) + cap_path = BASE_CAPTURE_PATH + + logging.info("Starting capture reload with a 10 second timeout...") + passed_timeout_test = launch_timeout_test_process( + build_captures_from_exif, args=(cap_path,) + ) + + return passed_timeout_test + + +def main(): + error_sources = [] + passed_timeout = check_capture_rebuild() + if not passed_timeout: + error_sources.append("capture_rebuild_timeout") + return error_sources diff --git a/openflexure_microscope/rescue/check_settings.py b/openflexure_microscope/rescue/check_settings.py new file mode 100644 index 00000000..9ba33ba7 --- /dev/null +++ b/openflexure_microscope/rescue/check_settings.py @@ -0,0 +1,47 @@ +import logging +import json + +ERROR_SOURCES = [] + + +def trace_config_exceptions(): + error_sources = [] + + from openflexure_microscope.paths import ( + DEFAULT_CONFIGURATION_FILE_PATH, + SETTINGS_FILE_PATH, + ) + + try: + default_config = json.load(DEFAULT_CONFIGURATION_FILE_PATH) + if not default_config: + error_sources.append("default_config_empty") + except Exception as e: + logging.error("Error parsing config:") + logging.error(e) + error_sources.append("default_config_error") + + try: + default_settings = json.load(SETTINGS_FILE_PATH) + if not default_settings: + error_sources.append("default_settings_empty") + except Exception as e: + logging.error("Error parsing settings:") + logging.error(e) + error_sources.append("default_settings_error") + + return error_sources + + +def main(): + error_sources = [] + logging.info("Attempting default settings and config import...") + try: + from openflexure_microscope import config + except Exception as e: + error_sources.append("config_settings_import_error") + logging.error("Error importing config:") + logging.error(e) + error_sources.extend(trace_config_exceptions()) + + return error_sources diff --git a/openflexure_microscope/rescue/monitor_service.py b/openflexure_microscope/rescue/monitor_service.py new file mode 100644 index 00000000..f12ab1b9 --- /dev/null +++ b/openflexure_microscope/rescue/monitor_service.py @@ -0,0 +1,59 @@ +#!/bin/python +# +# Copyright 2016 Flavio Garcia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Usage: monitor_process.py +# +# Example(crontab, every 5 minutes): +# */5 * * * * /root/bin/monitor_service.py prosody > /dev/null 2>&1 +# + +import sys +import subprocess + + +class ServiceMonitor(object): + def __init__(self, service): + self.service = service + + def is_active(self): + """Return True if service is running""" + for line in self.status(): + if "Active:" in line: + if "(running)" in line: + return True + return False + + def status(self): + cmd = f"/bin/systemctl status {self.service}.service" + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + stdout_list = proc.communicate()[0].decode().split("\n") + return stdout_list + + def start(self): + cmd = f"/bin/systemctl start {self.service}.service" + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + proc.communicate() + + def stop(self): + cmd = f"/bin/systemctl stop {self.service}.service" + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + proc.communicate() + + def log(self, n_lines: int = 100): + cmd = f"/bin/journalctl -u {self.service}.service -n {n_lines} --no-pager" + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + stdout_list = proc.communicate()[0].decode().split("\n") + return stdout_list diff --git a/openflexure_microscope/rescue/monitor_timeout.py b/openflexure_microscope/rescue/monitor_timeout.py new file mode 100644 index 00000000..b02b29b6 --- /dev/null +++ b/openflexure_microscope/rescue/monitor_timeout.py @@ -0,0 +1,39 @@ +import multiprocessing +import logging +import time + +# bar +def test_long_fn(): + for _ in range(100): + print("Tick") + time.sleep(1) + + +def launch_timeout_test_process(target, args=(), kwargs=None, timeout=10): + if not kwargs: + kwargs = {} + + # Start target as a process + p = multiprocessing.Process(target=target, args=args, kwargs=kwargs) + p.start() + + # Wait for 10 seconds or until process finishes + p.join(timeout) + + # If thread is still active + if p.is_alive(): + logging.error( + f"Function {target} reached timeout after {timeout} seconds. Terminating." + ) + + # Terminate + p.terminate() + p.join() + + return False + else: + return True + + +if __name__ == "__main__": + launch_timeout_test_process(test_long_fn, timeout=5) From 2b33c7fd86b825bcc82ac0bf9067e8d00d979c61 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 3 Mar 2020 16:51:25 +0000 Subject: [PATCH 59/70] Iterated to beta 6 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 185344a1..ee526317 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "openflexure_microscope" -version = "2.0.0-beta.5" +version = "2.0.0-beta.6" description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope." authors = [ From 5c27ae5a45b5e29db74af8581c42ad62b8b7fdf2 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 4 Mar 2020 10:42:29 +0000 Subject: [PATCH 60/70] Fixed extension rule matching --- openflexure_microscope/api/utilities/gui.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/api/utilities/gui.py b/openflexure_microscope/api/utilities/gui.py index c0121594..e2187ba5 100644 --- a/openflexure_microscope/api/utilities/gui.py +++ b/openflexure_microscope/api/utilities/gui.py @@ -3,6 +3,12 @@ import logging from functools import wraps +def clean_rule(rule: str): + while rule[0] == "/": + rule = rule[1:] + return f"/{rule}" + + def build_gui_from_dict(gui_description, extension_object): # Make a working copy of GUI description api_gui = copy.deepcopy(gui_description) @@ -12,8 +18,7 @@ def build_gui_from_dict(gui_description, extension_object): for form in api_gui["forms"]: # Clean leading slashes from rule if "route" in form: - while form["route"][0] == "/": - form["route"] = form["route"][1:] + 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"]]["rule"] From ead4e108cc6d7429d32e2e0f64f4daca109587e8 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 4 Mar 2020 22:06:03 +0000 Subject: [PATCH 61/70] Updated eV Lite --- openflexure_microscope/api/static/css/main.01cbd384.css | 3 --- openflexure_microscope/api/static/css/main.24f9722e.css | 3 +++ openflexure_microscope/api/static/index.html | 2 +- openflexure_microscope/api/static/js/chunk-vendors.0975939e.js | 3 --- .../api/static/js/chunk-vendors.0975939e.js.map | 3 --- openflexure_microscope/api/static/js/chunk-vendors.3b7762c4.js | 3 +++ .../api/static/js/chunk-vendors.3b7762c4.js.map | 3 +++ openflexure_microscope/api/static/js/main.bf0d35e5.js | 3 --- openflexure_microscope/api/static/js/main.bf0d35e5.js.map | 3 --- openflexure_microscope/api/static/js/main.eaa8787b.js | 3 +++ openflexure_microscope/api/static/js/main.eaa8787b.js.map | 3 +++ 11 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 openflexure_microscope/api/static/css/main.01cbd384.css create mode 100644 openflexure_microscope/api/static/css/main.24f9722e.css delete mode 100644 openflexure_microscope/api/static/js/chunk-vendors.0975939e.js delete mode 100644 openflexure_microscope/api/static/js/chunk-vendors.0975939e.js.map create mode 100644 openflexure_microscope/api/static/js/chunk-vendors.3b7762c4.js create mode 100644 openflexure_microscope/api/static/js/chunk-vendors.3b7762c4.js.map delete mode 100644 openflexure_microscope/api/static/js/main.bf0d35e5.js delete mode 100644 openflexure_microscope/api/static/js/main.bf0d35e5.js.map create mode 100644 openflexure_microscope/api/static/js/main.eaa8787b.js create mode 100644 openflexure_microscope/api/static/js/main.eaa8787b.js.map diff --git a/openflexure_microscope/api/static/css/main.01cbd384.css b/openflexure_microscope/api/static/css/main.01cbd384.css deleted file mode 100644 index 57d803ca..00000000 --- a/openflexure_microscope/api/static/css/main.01cbd384.css +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ec225f9adc44241191d098eb95adcbbf5f29c0f78e2148dc2adde992110f20d -size 1374686 diff --git a/openflexure_microscope/api/static/css/main.24f9722e.css b/openflexure_microscope/api/static/css/main.24f9722e.css new file mode 100644 index 00000000..b8cd1050 --- /dev/null +++ b/openflexure_microscope/api/static/css/main.24f9722e.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e68506e72389393d35cc47e5184459fff9a23bedea8c9ee96491f66c88e71954 +size 1375875 diff --git a/openflexure_microscope/api/static/index.html b/openflexure_microscope/api/static/index.html index c4fc387d..f9009ab2 100644 --- a/openflexure_microscope/api/static/index.html +++ b/openflexure_microscope/api/static/index.html @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1969142cc4f0de816ffd8cea840822c0c6b8d038fc078459a732ecd28e1de610 +oid sha256:463bfdbd635ddff1d00df38b1149ffd3947b492d71cfb546549dffebd311b090 size 843 diff --git a/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js b/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js deleted file mode 100644 index 5057a351..00000000 --- a/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d2f532e5437b2efe083793f70c743ecddd649490bae0e558f3ee462f54beb71 -size 377877 diff --git a/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js.map b/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js.map deleted file mode 100644 index 37ae6e73..00000000 --- a/openflexure_microscope/api/static/js/chunk-vendors.0975939e.js.map +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa72fceabde1763773ccf44fd39a215be2c78f158b647bf5125a81578e7012b6 -size 1595324 diff --git a/openflexure_microscope/api/static/js/chunk-vendors.3b7762c4.js b/openflexure_microscope/api/static/js/chunk-vendors.3b7762c4.js new file mode 100644 index 00000000..47dda2fd --- /dev/null +++ b/openflexure_microscope/api/static/js/chunk-vendors.3b7762c4.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a536bfa88fd1bf69284804285c5cf1da33cf05f164f175eb4085f7d057ff4f17 +size 378133 diff --git a/openflexure_microscope/api/static/js/chunk-vendors.3b7762c4.js.map b/openflexure_microscope/api/static/js/chunk-vendors.3b7762c4.js.map new file mode 100644 index 00000000..834b0baf --- /dev/null +++ b/openflexure_microscope/api/static/js/chunk-vendors.3b7762c4.js.map @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:996b19391845b7cf1810f1181d935aad524897763da4c36847c7715f762b63fa +size 1597950 diff --git a/openflexure_microscope/api/static/js/main.bf0d35e5.js b/openflexure_microscope/api/static/js/main.bf0d35e5.js deleted file mode 100644 index bb1855ae..00000000 --- a/openflexure_microscope/api/static/js/main.bf0d35e5.js +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:807c453fc0831b5668ebb74fb73c6acba46ec95cb7685dc1b4dbb5f91fe0e9dc -size 114858 diff --git a/openflexure_microscope/api/static/js/main.bf0d35e5.js.map b/openflexure_microscope/api/static/js/main.bf0d35e5.js.map deleted file mode 100644 index 16c04539..00000000 --- a/openflexure_microscope/api/static/js/main.bf0d35e5.js.map +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79825fda16fdd7bf8840c4728658620dfb9fd202e3581a5b1abdc54d4ef31e64 -size 385394 diff --git a/openflexure_microscope/api/static/js/main.eaa8787b.js b/openflexure_microscope/api/static/js/main.eaa8787b.js new file mode 100644 index 00000000..4826f329 --- /dev/null +++ b/openflexure_microscope/api/static/js/main.eaa8787b.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a77d7d2e5e4220883bc1491a627435cc80d0b2574d51972bcd6b12add7a4a94 +size 116850 diff --git a/openflexure_microscope/api/static/js/main.eaa8787b.js.map b/openflexure_microscope/api/static/js/main.eaa8787b.js.map new file mode 100644 index 00000000..72e98b54 --- /dev/null +++ b/openflexure_microscope/api/static/js/main.eaa8787b.js.map @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1404c938e8f60b1694dd48357d486d919f5b1e32c9f226ca26fc0ee399019542 +size 395811 From 4dd01bfcd2c8917d81fcd3ceee0ea350142ed56d Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 10 Mar 2020 14:21:56 +0000 Subject: [PATCH 62/70] Always log to log file --- openflexure_microscope/api/app.py | 33 ++++++++++++------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index 86195503..e1991132 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -2,7 +2,7 @@ import time import atexit -import logging +import logging, logging.handlers import os import pkg_resources @@ -30,32 +30,25 @@ from openflexure_microscope.api.microscope import default_microscope as api_micr from openflexure_microscope.api.v2 import views # Handle logging -is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "") - DEFAULT_LOGFILE = logs_file_path("openflexure_microscope.log") logger = logging.getLogger() -if (__name__ == "__main__") or (not is_gunicorn): - # If imported, but not by gunicorn - print("Letting sys handle logs") - logger.setLevel(logging.DEBUG) -else: - # Direct standard Python logging to file and console - error_formatter = logging.Formatter( - "[%(asctime)s] [%(threadName)s] [%(levelname)s] %(message)s" - ) - rotating_logfile = logging.handlers.RotatingFileHandler( - DEFAULT_LOGFILE, maxBytes=1_000_000, backupCount=7 - ) +error_formatter = logging.Formatter( + "[%(asctime)s] [%(threadName)s] [%(levelname)s] %(message)s" +) - error_handlers = [rotating_logfile, logging.StreamHandler()] +rotating_logfile = logging.handlers.RotatingFileHandler( + DEFAULT_LOGFILE, maxBytes=1_000_000, backupCount=7 +) - for handler in error_handlers: - handler.setFormatter(error_formatter) - logger.addHandler(handler) +error_handlers = [rotating_logfile, logging.StreamHandler()] - logger.setLevel(logging.getLogger("gunicorn.error").level) +for handler in error_handlers: + handler.setFormatter(error_formatter) + logger.addHandler(handler) + +logger.setLevel(logging.INFO) # Log server paths being used From 9f4d87dfc41867521729342d0d0274e002c43cbc Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 10 Mar 2020 15:52:24 +0000 Subject: [PATCH 63/70] Updated lockfile --- poetry.lock | 188 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 168 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index 390ef18e..2813d79e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -97,6 +97,18 @@ optional = false python-versions = "*" version = "2019.11.28" +[[package]] +category = "main" +description = "Foreign Function Interface for Python calling C code." +marker = "sys_platform == \"win32\" and platform_python_implementation == \"CPython\"" +name = "cffi" +optional = false +python-versions = "*" +version = "1.14.0" + +[package.dependencies] +pycparser = "*" + [[package]] category = "dev" description = "Universal encoding detector for Python 2 and 3" @@ -110,8 +122,8 @@ category = "main" description = "Composable command line interface toolkit" name = "click" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "7.0" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.1" [[package]] category = "dev" @@ -161,6 +173,44 @@ version = "3.0.8" Flask = ">=0.9" Six = "*" +[[package]] +category = "main" +description = "Coroutine-based network library" +name = "gevent" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.4.0" + +[package.dependencies] +cffi = ">=1.11.5" +greenlet = ">=0.4.14" + +[package.extras] +dnspython = ["dnspython", "idna"] +doc = ["repoze.sphinx.autointerface"] +events = ["zope.event", "zope.interface"] +test = ["zope.interface", "zope.event", "requests", "objgraph", "psutil", "futures", "mock", "coverage (>=5.0a3)", "coveralls (>=1.0)"] + +[[package]] +category = "main" +description = "Websocket handler for the gevent pywsgi server, a Python network library" +name = "gevent-websocket" +optional = false +python-versions = "*" +version = "0.10.1" + +[package.dependencies] +gevent = "*" + +[[package]] +category = "main" +description = "Lightweight in-process concurrent programming" +marker = "platform_python_implementation == \"CPython\"" +name = "greenlet" +optional = false +python-versions = "*" +version = "0.4.15" + [[package]] category = "dev" description = "Internationalized Domain Names in Applications (IDNA)" @@ -219,17 +269,19 @@ description = "" name = "labthings" optional = false python-versions = "^3.6" -version = "0.1.0" +version = "0.2.0" [package.dependencies] Flask = "^1.1.1" apispec = "^3.2.0" flask-cors = "^3.0.8" +gevent = "^1.4.0" +gevent-websocket = "^0.10.1" marshmallow = "^3.4.0" webargs = "^5.5.3" [package.source] -reference = "12b6dda521f00e449c67c5ea8bf34005918fd649" +reference = "7413d4d0fd30ef534a710c0c1cfbbfbdba7eab8d" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -254,11 +306,11 @@ description = "A lightweight library for converting complex datatypes to and fro name = "marshmallow" optional = false python-versions = ">=3.5" -version = "3.5.0" +version = "3.5.1" [package.extras] dev = ["pytest", "pytz", "simplejson", "mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)", "tox"] -docs = ["sphinx (2.4.2)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"] +docs = ["sphinx (2.4.3)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"] lint = ["mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)"] tests = ["pytest", "pytz", "simplejson"] @@ -284,7 +336,7 @@ description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.1" +version = "20.3" [package.dependencies] pyparsing = ">=2.0.2" @@ -326,13 +378,22 @@ version = "5.7.0" [package.extras] enum = ["enum34"] +[[package]] +category = "main" +description = "C parser in Python" +marker = "sys_platform == \"win32\" and platform_python_implementation == \"CPython\"" +name = "pycparser" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.20" + [[package]] category = "dev" description = "Pygments is a syntax highlighting package written in Python." name = "pygments" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.5.2" +python-versions = ">=3.5" +version = "2.6.1" [[package]] category = "dev" @@ -458,7 +519,7 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "2.4.3" +version = "2.4.4" [package.dependencies] Jinja2 = ">=2.3" @@ -675,13 +736,43 @@ certifi = [ {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, ] +cffi = [ + {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, + {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, + {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, + {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, + {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, + {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, + {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, + {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, + {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, + {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, + {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, + {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, + {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, + {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, + {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, + {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, +] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] click = [ - {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, - {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, + {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, + {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, ] colorama = [ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, @@ -699,6 +790,59 @@ flask-cors = [ {file = "Flask-Cors-3.0.8.tar.gz", hash = "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16"}, {file = "Flask_Cors-3.0.8-py2.py3-none-any.whl", hash = "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"}, ] +gevent = [ + {file = "gevent-1.4.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b7d3a285978b27b469c0ff5fb5a72bcd69f4306dbbf22d7997d83209a8ba917"}, + {file = "gevent-1.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44089ed06a962a3a70e96353c981d628b2d4a2f2a75ea5d90f916a62d22af2e8"}, + {file = "gevent-1.4.0-cp27-cp27m-win32.whl", hash = "sha256:0e1e5b73a445fe82d40907322e1e0eec6a6745ca3cea19291c6f9f50117bb7ea"}, + {file = "gevent-1.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:74b7528f901f39c39cdbb50cdf08f1a2351725d9aebaef212a29abfbb06895ee"}, + {file = "gevent-1.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0ff2b70e8e338cf13bedf146b8c29d475e2a544b5d1fe14045aee827c073842c"}, + {file = "gevent-1.4.0-cp34-cp34m-macosx_10_14_x86_64.whl", hash = "sha256:0774babec518a24d9a7231d4e689931f31b332c4517a771e532002614e270a64"}, + {file = "gevent-1.4.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d752bcf1b98174780e2317ada12013d612f05116456133a6acf3e17d43b71f05"}, + {file = "gevent-1.4.0-cp34-cp34m-win32.whl", hash = "sha256:3249011d13d0c63bea72d91cec23a9cf18c25f91d1f115121e5c9113d753fa12"}, + {file = "gevent-1.4.0-cp34-cp34m-win_amd64.whl", hash = "sha256:d1e6d1f156e999edab069d79d890859806b555ce4e4da5b6418616322f0a3df1"}, + {file = "gevent-1.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d0809e2991c9784eceeadef01c27ee6a33ca09ebba6154317a257353e3af922"}, + {file = "gevent-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:14b4d06d19d39a440e72253f77067d27209c67e7611e352f79fe69e0f618f76e"}, + {file = "gevent-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:53b72385857e04e7faca13c613c07cab411480822ac658d97fd8a4ddbaf715c8"}, + {file = "gevent-1.4.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8d9ec51cc06580f8c21b41fd3f2b3465197ba5b23c00eb7d422b7ae0380510b0"}, + {file = "gevent-1.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2711e69788ddb34c059a30186e05c55a6b611cb9e34ac343e69cf3264d42fe1c"}, + {file = "gevent-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:e5bcc4270671936349249d26140c267397b7b4b1381f5ec8b13c53c5b53ab6e1"}, + {file = "gevent-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:9f7a1e96fec45f70ad364e46de32ccacab4d80de238bd3c2edd036867ccd48ad"}, + {file = "gevent-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:50024a1ee2cf04645535c5ebaeaa0a60c5ef32e262da981f4be0546b26791950"}, + {file = "gevent-1.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4bfa291e3c931ff3c99a349d8857605dca029de61d74c6bb82bd46373959c942"}, + {file = "gevent-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:ab4dc33ef0e26dc627559786a4fba0c2227f125db85d970abbf85b77506b3f51"}, + {file = "gevent-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:896b2b80931d6b13b5d9feba3d4eebc67d5e6ec54f0cf3339d08487d55d93b0e"}, + {file = "gevent-1.4.0-pp260-pypy_41-macosx_10_14_x86_64.whl", hash = "sha256:107f4232db2172f7e8429ed7779c10f2ed16616d75ffbe77e0e0c3fcdeb51a51"}, + {file = "gevent-1.4.0-pp260-pypy_41-win32.whl", hash = "sha256:28a0c5417b464562ab9842dd1fb0cc1524e60494641d973206ec24d6ec5f6909"}, + {file = "gevent-1.4.0.tar.gz", hash = "sha256:1eb7fa3b9bd9174dfe9c3b59b7a09b768ecd496debfc4976a9530a3e15c990d1"}, +] +gevent-websocket = [ + {file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"}, + {file = "gevent_websocket-0.10.1-py3-none-any.whl", hash = "sha256:17b67d91282f8f4c973eba0551183fc84f56f1c90c8f6b6b30256f31f66f5242"}, +] +greenlet = [ + {file = "greenlet-0.4.15-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163"}, + {file = "greenlet-0.4.15-cp27-cp27m-win32.whl", hash = "sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87"}, + {file = "greenlet-0.4.15-cp27-cp27m-win_amd64.whl", hash = "sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638"}, + {file = "greenlet-0.4.15-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248"}, + {file = "greenlet-0.4.15-cp33-cp33m-win32.whl", hash = "sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720"}, + {file = "greenlet-0.4.15-cp33-cp33m-win_amd64.whl", hash = "sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28"}, + {file = "greenlet-0.4.15-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939"}, + {file = "greenlet-0.4.15-cp34-cp34m-win32.whl", hash = "sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625"}, + {file = "greenlet-0.4.15-cp34-cp34m-win_amd64.whl", hash = "sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490"}, + {file = "greenlet-0.4.15-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0"}, + {file = "greenlet-0.4.15-cp35-cp35m-win32.whl", hash = "sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4"}, + {file = "greenlet-0.4.15-cp35-cp35m-win_amd64.whl", hash = "sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6"}, + {file = "greenlet-0.4.15-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8"}, + {file = "greenlet-0.4.15-cp36-cp36m-win32.whl", hash = "sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0"}, + {file = "greenlet-0.4.15-cp36-cp36m-win_amd64.whl", hash = "sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656"}, + {file = "greenlet-0.4.15-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214"}, + {file = "greenlet-0.4.15-cp37-cp37m-win32.whl", hash = "sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043"}, + {file = "greenlet-0.4.15-cp37-cp37m-win_amd64.whl", hash = "sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304"}, + {file = "greenlet-0.4.15-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e538b8dae561080b542b0f5af64d47ef859f22517f7eca617bb314e0e03fd7ef"}, + {file = "greenlet-0.4.15-cp38-cp38-win32.whl", hash = "sha256:51155342eb4d6058a0ffcd98a798fe6ba21195517da97e15fca3db12ab201e6e"}, + {file = "greenlet-0.4.15-cp38-cp38-win_amd64.whl", hash = "sha256:7457d685158522df483196b16ec648b28f8e847861adb01a55d41134e7734122"}, + {file = "greenlet-0.4.15.tar.gz", hash = "sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc"}, +] idna = [ {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, @@ -774,8 +918,8 @@ markupsafe = [ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] marshmallow = [ - {file = "marshmallow-3.5.0-py2.py3-none-any.whl", hash = "sha256:4b95c7735f93eb781dfdc4dded028108998cad759dda8dd9d4b5b4ac574cbf13"}, - {file = "marshmallow-3.5.0.tar.gz", hash = "sha256:3a94945a7461f2ab4df9576e51c97d66bee2c86155d3d3933fab752b31effab8"}, + {file = "marshmallow-3.5.1-py2.py3-none-any.whl", hash = "sha256:ac2e13b30165501b7d41fc0371b8df35944f5849769d136f20e2c5f6cdc6e665"}, + {file = "marshmallow-3.5.1.tar.gz", hash = "sha256:90854221bbb1498d003a0c3cc9d8390259137551917961c8b5258c64026b2f85"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -805,8 +949,8 @@ numpy = [ {file = "numpy-1.18.1.zip", hash = "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77"}, ] packaging = [ - {file = "packaging-20.1-py2.py3-none-any.whl", hash = "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73"}, - {file = "packaging-20.1.tar.gz", hash = "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"}, + {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, + {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, ] picamera = [] pillow = [ @@ -875,9 +1019,13 @@ psutil = [ {file = "psutil-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:d84029b190c8a66a946e28b4d3934d2ca1528ec94764b180f7d6ea57b0e75e26"}, {file = "psutil-5.7.0.tar.gz", hash = "sha256:685ec16ca14d079455892f25bd124df26ff9137664af445563c1bd36629b5e0e"}, ] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] pygments = [ - {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"}, - {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"}, + {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, + {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, ] pylint = [ {file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"}, @@ -968,8 +1116,8 @@ snowballstemmer = [ {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, ] sphinx = [ - {file = "Sphinx-2.4.3-py3-none-any.whl", hash = "sha256:776ff8333181138fae52df65be733127539623bb46cc692e7fa0fcfc80d7aa88"}, - {file = "Sphinx-2.4.3.tar.gz", hash = "sha256:ca762da97c3b5107cbf0ab9e11d3ec7ab8d3c31377266fd613b962ed971df709"}, + {file = "Sphinx-2.4.4-py3-none-any.whl", hash = "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb"}, + {file = "Sphinx-2.4.4.tar.gz", hash = "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, From 7182e9d5df7ed24dd30efc838f72467d68a8ba96 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 10 Mar 2020 16:02:04 +0000 Subject: [PATCH 64/70] Updated labthings --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 2813d79e..07cee8f4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -281,7 +281,7 @@ marshmallow = "^3.4.0" webargs = "^5.5.3" [package.source] -reference = "7413d4d0fd30ef534a710c0c1cfbbfbdba7eab8d" +reference = "028e21cb5b53cc8feeea3af7a0579fde67a54daf" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] From 43d13cb7cd3e3fa2cab66519cb4d260be76df755 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 11 Mar 2020 10:24:37 +0000 Subject: [PATCH 65/70] Fixed rule cleaning --- openflexure_microscope/api/utilities/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openflexure_microscope/api/utilities/gui.py b/openflexure_microscope/api/utilities/gui.py index e2187ba5..dcdfe79e 100644 --- a/openflexure_microscope/api/utilities/gui.py +++ b/openflexure_microscope/api/utilities/gui.py @@ -6,7 +6,7 @@ from functools import wraps def clean_rule(rule: str): while rule[0] == "/": rule = rule[1:] - return f"/{rule}" + return f"{rule}" def build_gui_from_dict(gui_description, extension_object): From bf4cb24cb3f8dccaa3b4506395833d8537ad1499 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 11 Mar 2020 10:48:55 +0000 Subject: [PATCH 66/70] Fall back to mock stage if Sangastage is missing --- openflexure_microscope/microscope.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index b1d8d00d..3ed73568 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -88,7 +88,11 @@ class Microscope: stage_type = configuration["stage"].get("type") stage_port = configuration["stage"].get("port") if stage_type in ("SangaBoard", "SangaStage"): - self.stage = SangaStage(port=stage_port) + try: + self.stage = SangaStage(port=stage_port) + except Exception as e: + logging.error(e) + logging.warning("No compatible Sangaboard hardware found.") ### Fallbacks if not self.camera: From 1a27a8e4f354442f78a0de6334cd06276422612a Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Fri, 13 Mar 2020 15:56:38 +0000 Subject: [PATCH 67/70] Pin LabThings to 0.2.0 PyPI release --- poetry.lock | 35 ++++++++++++++++++++--------------- pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index 07cee8f4..c1f800e8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -265,25 +265,21 @@ i18n = ["Babel (>=0.8)"] [[package]] category = "main" -description = "" +description = "Python implementation of LabThings, based on the Flask microframework" name = "labthings" optional = false -python-versions = "^3.6" +python-versions = ">=3.6,<4.0" version = "0.2.0" [package.dependencies] -Flask = "^1.1.1" -apispec = "^3.2.0" -flask-cors = "^3.0.8" -gevent = "^1.4.0" -gevent-websocket = "^0.10.1" -marshmallow = "^3.4.0" -webargs = "^5.5.3" +Flask = ">=1.1.1,<2.0.0" +apispec = ">=3.2.0,<4.0.0" +flask-cors = ">=3.0.8,<4.0.0" +gevent = ">=1.4.0,<2.0.0" +gevent-websocket = ">=0.10.1,<0.11.0" +marshmallow = ">=3.4.0,<4.0.0" +webargs = ">=5.5.3,<6.0.0" -[package.source] -reference = "028e21cb5b53cc8feeea3af7a0579fde67a54daf" -type = "git" -url = "https://github.com/labthings/python-labthings.git" [[package]] category = "dev" description = "A fast and thorough lazy object proxy." @@ -359,6 +355,7 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "79177fa7f28d6d5c6eab5ba814b420b1785b6b24" type = "git" url = "https://github.com/rwb27/picamera.git" + [[package]] category = "main" description = "Python Imaging Library (Fork)" @@ -700,7 +697,7 @@ version = "1.11.2" rpi = ["picamera", "RPi.GPIO"] [metadata] -content-hash = "ad29f5c7d3a9ac55697f3d74f7b1c992cb2617039cbcab23caa0221f8ed4718a" +content-hash = "3a3a3bd4ffd7e0fe4c4c418a1b191c33afd375e6f06bb298189fd84ff75ee4cc" python-versions = "^3.6" [metadata.files] @@ -863,7 +860,10 @@ jinja2 = [ {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"}, {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"}, ] -labthings = [] +labthings = [ + {file = "labthings-0.2.0-py3-none-any.whl", hash = "sha256:e2235c0db45c7134403bd78b2aa55ae5aa1bfee677735c9674f52bfcb605e998"}, + {file = "labthings-0.2.0.tar.gz", hash = "sha256:f77391bbb02676ac7c28de59c425c4dc8d6de63031f2a2db8bffd26bc691cb5e"}, +] lazy-object-proxy = [ {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, @@ -915,6 +915,11 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] marshmallow = [ diff --git a/pyproject.toml b/pyproject.toml index ee526317..975b03bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,9 @@ picamera = { git = "https://github.com/rwb27/picamera.git", branch = "master", o "RPi.GPIO" = { version = "^0.6.5", optional = true } pyserial = "^3.4" # Used for sangaboard (basic_serial_instrument) until we move to sangaboard pip library -labthings = { git = "https://github.com/labthings/python-labthings.git", branch = "master"} # TODO: Pin to a fixed version before 2.0.0 stable python-dateutil = "^2.8" psutil = "^5.6.7" # Autostorage extension +labthings = "0.2.0" [tool.poetry.extras] rpi = ["picamera", "RPi.GPIO"] From 093e5a0a7aa9f35c62b9b3b7bfe8e79041a88f03 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 17 Mar 2020 17:14:29 +0000 Subject: [PATCH 68/70] Fixed broken delete argument (capture_id instead of id) --- openflexure_microscope/api/v2/views/captures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index 27a4a636..2deb72b6 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -207,7 +207,7 @@ class CaptureTags(View): return jsonify(capture_obj.tags) - def delete(self, capture_id): + def delete(self, id): """ Delete tags from a single image capture """ From 4626fa0565df7aa6f131fc2c239f3b651e718452 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 24 Mar 2020 10:22:39 +0000 Subject: [PATCH 69/70] Added docs for subclassing and lifecycle hooks --- .../01b_basic_structure_subclass.py | 41 ++++++++++++++ docs/source/extensions/lifecycle_hooks.rst | 53 +++++++++++++++++++ docs/source/extensions/structure.rst | 12 ++++- docs/source/plugins.rst | 3 +- 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 docs/source/extensions/example_extension/01b_basic_structure_subclass.py create mode 100644 docs/source/extensions/lifecycle_hooks.rst diff --git a/docs/source/extensions/example_extension/01b_basic_structure_subclass.py b/docs/source/extensions/example_extension/01b_basic_structure_subclass.py new file mode 100644 index 00000000..6177a2a9 --- /dev/null +++ b/docs/source/extensions/example_extension/01b_basic_structure_subclass.py @@ -0,0 +1,41 @@ +from labthings.server.extensions import BaseExtension +from labthings.server.find import find_component + + +# Create the extension class +class MyExtension(BaseExtension): + def __init__(self): + + # Create some instance variable + self.state_variable = "An example of a persistant instance variable" + + # Superclass init function + super().__init__("com.myname.myextension", version="0.0.0") + + def identify(self): + """ + Demonstrate access to Microscope.camera, and Microscope.stage + """ + microscope = find_component("org.openflexure.microscope") + + response = ( + f"My name is {microscope.name}. " + f"My parent camera is {microscope.camera}, " + f"and my parent stage is {microscope.stage}." + ) + + return response + + def rename(self, new_name): + """ + Rename the microscope + """ + + microscope = find_component("org.openflexure.microscope") + + microscope.name = new_name + microscope.save_settings() + + +# Create your extension object +my_extension = MyExtension() diff --git a/docs/source/extensions/lifecycle_hooks.rst b/docs/source/extensions/lifecycle_hooks.rst new file mode 100644 index 00000000..451618a4 --- /dev/null +++ b/docs/source/extensions/lifecycle_hooks.rst @@ -0,0 +1,53 @@ +Lifecycle Hooks +=============== + +Introduction +------------ +In some cases it is useful to have functions triggered by events in an extensions lifecycle. Currently two such lifecycle events can be used, ``on_register``, and ``on_component``. + +``on_register`` +--------------- + +The ``on_register`` method can be used to have a function call as soon as the extension has been successfully registered to the microscope. For example: + +.. code-block:: python + + class MyExtension(BaseExtension): + def __init__(self): + + # Track if the extension has been registered + self.registered = False + + # Add lifecycle hooks + self.on_register(self.on_register_handler, args=(), kwargs={}) + + # Superclass init function + super().__init__("com.myname.myextension", version="0.0.0") + + def on_register_handler(self, *args, **kwargs): + self.registered = True + print("Extension has been registered!") + + +``on_component`` +---------------- + +The ``on_component`` method can be used to have a function call as soon as a particular LabThings component has been added. This can be used, for example, to get information about the microscope instance as soon as it is available. For example: + +.. code-block:: python + + class MyExtension(BaseExtension): + def __init__(self): + + # Hold a reference to the microscope object as soon as it is available + self.microscope = None + + # Add lifecycle hooks + self.on_component("com.myname.myextension", self.on_microscope_handler) + + # Superclass init function + super().__init__("org.openflexure.microscope", version="0.0.0") + + def on_microscope_handler(self, microscope_object): + print("Microscope object has been found!") + self.microscope = microscope_object \ No newline at end of file diff --git a/docs/source/extensions/structure.rst b/docs/source/extensions/structure.rst index ee2bc478..9d35e934 100644 --- a/docs/source/extensions/structure.rst +++ b/docs/source/extensions/structure.rst @@ -23,4 +23,14 @@ Once this extension is loaded, any other extensions will have access to your met # Call a function from your extension if my_found_extension: - my_found_extension.identify() \ No newline at end of file + my_found_extension.identify() + + +Subclassing ``BaseExtension`` +------------------------------- + +The syntax used above allows novice programmers to easily start building extensions, without having to deal with subclassing. However, for more complex extensions which require persistent state, subclassing :py:class:`labthings.server.extensions.BaseExtension` is recommended. + +The same simple extension as seen above can be written using subclassing: + +.. literalinclude:: ./example_extension/01b_basic_structure_subclass.py diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index ad28657e..bb865f26 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -11,4 +11,5 @@ Developing API Extensions ./extensions/properties.rst ./extensions/actions.rst ./extensions/tasks_locks.rst - ./extensions/ev_gui.rst \ No newline at end of file + ./extensions/ev_gui.rst + ./extensions/lifecycle_hooks.rst \ No newline at end of file From 10fa93d11cc73cafb33d5650e3cc4de9a31609dd Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 24 Mar 2020 10:22:49 +0000 Subject: [PATCH 70/70] Iterated to 2.0.0 --- .../api/default_extensions/autofocus.py | 2 +- .../api/default_extensions/autostorage.py | 2 +- .../api/default_extensions/scan.py | 2 +- .../api/default_extensions/zip_builder.py | 2 +- poetry.lock | 50 ++++++++----------- pyproject.toml | 2 +- 6 files changed, 27 insertions(+), 33 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/autofocus.py b/openflexure_microscope/api/default_extensions/autofocus.py index 5fbc9f7c..1da9960b 100644 --- a/openflexure_microscope/api/default_extensions/autofocus.py +++ b/openflexure_microscope/api/default_extensions/autofocus.py @@ -359,7 +359,7 @@ class FastAutofocusAPI(View): autofocus_extension_v2 = BaseExtension( - "org.openflexure.autofocus", version="2.0.0-beta.1" + "org.openflexure.autofocus", version="2.0.0" ) autofocus_extension_v2.add_method(fast_autofocus, "fast_autofocus") diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index 6b92bea0..f92d4d99 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -88,7 +88,7 @@ class AutostorageExtension(BaseExtension): BaseExtension.__init__( self, "org.openflexure.autostorage", - version="2.0.0-beta.1", + version="2.0.0", description="Handle switching capture storage devices", ) diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index 83ba4e35..a5f82f52 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -354,6 +354,6 @@ class TileScanAPI(View): return task -scan_extension_v2 = BaseExtension("org.openflexure.scan", version="2.0.0-beta.1") +scan_extension_v2 = BaseExtension("org.openflexure.scan", version="2.0.0") scan_extension_v2.add_view(TileScanAPI, "/tile") diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index 22375a1e..b32eb995 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -202,7 +202,7 @@ class ZipGetterAPIView(View): zip_extension_v2 = BaseExtension( "org.openflexure.zipbuilder", - version="2.0.0-beta.1", + version="2.0.0", description="Build and download capture collections as ZIP files", ) diff --git a/poetry.lock b/poetry.lock index c1f800e8..d177e8f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -324,7 +324,7 @@ description = "NumPy is the fundamental package for array computing with Python. name = "numpy" optional = false python-versions = ">=3.5" -version = "1.18.1" +version = "1.18.2" [[package]] category = "dev" @@ -355,7 +355,6 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "79177fa7f28d6d5c6eab5ba814b420b1785b6b24" type = "git" url = "https://github.com/rwb27/picamera.git" - [[package]] category = "main" description = "Python Imaging Library (Fork)" @@ -915,11 +914,6 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] marshmallow = [ @@ -931,27 +925,27 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] numpy = [ - {file = "numpy-1.18.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc"}, - {file = "numpy-1.18.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121"}, - {file = "numpy-1.18.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e"}, - {file = "numpy-1.18.1-cp35-cp35m-win32.whl", hash = "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"}, - {file = "numpy-1.18.1-cp35-cp35m-win_amd64.whl", hash = "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6"}, - {file = "numpy-1.18.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480"}, - {file = "numpy-1.18.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572"}, - {file = "numpy-1.18.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57"}, - {file = "numpy-1.18.1-cp36-cp36m-win32.whl", hash = "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc"}, - {file = "numpy-1.18.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd"}, - {file = "numpy-1.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa"}, - {file = "numpy-1.18.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca"}, - {file = "numpy-1.18.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec"}, - {file = "numpy-1.18.1-cp37-cp37m-win32.whl", hash = "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73"}, - {file = "numpy-1.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971"}, - {file = "numpy-1.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07"}, - {file = "numpy-1.18.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26"}, - {file = "numpy-1.18.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474"}, - {file = "numpy-1.18.1-cp38-cp38-win32.whl", hash = "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3"}, - {file = "numpy-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a"}, - {file = "numpy-1.18.1.zip", hash = "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77"}, + {file = "numpy-1.18.2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a1baa1dc8ecd88fb2d2a651671a84b9938461e8a8eed13e2f0a812a94084d1fa"}, + {file = "numpy-1.18.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a244f7af80dacf21054386539699ce29bcc64796ed9850c99a34b41305630286"}, + {file = "numpy-1.18.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6fcc5a3990e269f86d388f165a089259893851437b904f422d301cdce4ff25c8"}, + {file = "numpy-1.18.2-cp35-cp35m-win32.whl", hash = "sha256:b5ad0adb51b2dee7d0ee75a69e9871e2ddfb061c73ea8bc439376298141f77f5"}, + {file = "numpy-1.18.2-cp35-cp35m-win_amd64.whl", hash = "sha256:87902e5c03355335fc5992a74ba0247a70d937f326d852fc613b7f53516c0963"}, + {file = "numpy-1.18.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9ab21d1cb156a620d3999dd92f7d1c86824c622873841d6b080ca5495fa10fef"}, + {file = "numpy-1.18.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cdb3a70285e8220875e4d2bc394e49b4988bdb1298ffa4e0bd81b2f613be397c"}, + {file = "numpy-1.18.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6d205249a0293e62bbb3898c4c2e1ff8a22f98375a34775a259a0523111a8f6c"}, + {file = "numpy-1.18.2-cp36-cp36m-win32.whl", hash = "sha256:a35af656a7ba1d3decdd4fae5322b87277de8ac98b7d9da657d9e212ece76a61"}, + {file = "numpy-1.18.2-cp36-cp36m-win_amd64.whl", hash = "sha256:1598a6de323508cfeed6b7cd6c4efb43324f4692e20d1f76e1feec7f59013448"}, + {file = "numpy-1.18.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:deb529c40c3f1e38d53d5ae6cd077c21f1d49e13afc7936f7f868455e16b64a0"}, + {file = "numpy-1.18.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cd77d58fb2acf57c1d1ee2835567cd70e6f1835e32090538f17f8a3a99e5e34b"}, + {file = "numpy-1.18.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b1fe1a6f3a6f355f6c29789b5927f8bd4f134a4bd9a781099a7c4f66af8850f5"}, + {file = "numpy-1.18.2-cp37-cp37m-win32.whl", hash = "sha256:2e40be731ad618cb4974d5ba60d373cdf4f1b8dcbf1dcf4d9dff5e212baf69c5"}, + {file = "numpy-1.18.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4ba59db1fcc27ea31368af524dcf874d9277f21fd2e1f7f1e2e0c75ee61419ed"}, + {file = "numpy-1.18.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59ca9c6592da581a03d42cc4e270732552243dc45e87248aa8d636d53812f6a5"}, + {file = "numpy-1.18.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1b0ece94018ae21163d1f651b527156e1f03943b986188dd81bc7e066eae9d1c"}, + {file = "numpy-1.18.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:82847f2765835c8e5308f136bc34018d09b49037ec23ecc42b246424c767056b"}, + {file = "numpy-1.18.2-cp38-cp38-win32.whl", hash = "sha256:5e0feb76849ca3e83dd396254e47c7dba65b3fa9ed3df67c2556293ae3e16de3"}, + {file = "numpy-1.18.2-cp38-cp38-win_amd64.whl", hash = "sha256:ba3c7a2814ec8a176bb71f91478293d633c08582119e713a0c5351c0f77698da"}, + {file = "numpy-1.18.2.zip", hash = "sha256:e7894793e6e8540dbeac77c87b489e331947813511108ae097f1715c018b8f3d"}, ] packaging = [ {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, diff --git a/pyproject.toml b/pyproject.toml index 975b03bc..c5080ad0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "openflexure_microscope" -version = "2.0.0-beta.6" +version = "2.0.0" description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope." authors = [