From 5f1bca91bf6da5b517b85751ddf5ccf00d06387d Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 13:49:54 +0100 Subject: [PATCH 01/30] Use simpler-spec branch of labthings (temporary pre-release) --- poetry.lock | 33 ++++++++++++++++++--------------- pyproject.toml | 2 +- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9dd9b4b5..4534f083 100644 --- a/poetry.lock +++ b/poetry.lock @@ -110,6 +110,7 @@ ofmclient = [] reference = "87a66aadbbea211b260dc279c0f009a8a2b8de51" type = "git" url = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git" + [[package]] category = "main" description = "Pure Python CBOR (de)serializer with extensive tag support" @@ -317,23 +318,27 @@ i18n = ["Babel (>=0.8)"] [[package]] category = "main" -description = "Python implementation of LabThings, based on the Flask microframework" +description = "" name = "labthings" optional = false -python-versions = ">=3.6,<4.0" -version = "0.6.7" +python-versions = "^3.6" +version = "0.7.0-beta.0" [package.dependencies] -Flask = ">=1.1.1,<2.0.0" -apispec = ">=3.2.0,<4.0.0" -cbor2 = ">=5.1.0,<6.0.0" -flask-cors = ">=3.0.8,<4.0.0" +Flask = "^1.1.1" +apispec = "^3.2.0" +cbor2 = "^5.1.0" +flask-cors = "^3.0.8" gevent = ">=1.4,<21.0" -gevent-websocket = ">=0.10.1,<0.11.0" -marshmallow = ">=3.4.0,<4.0.0" -webargs = ">=6.0.0,<7.0.0" +gevent-websocket = "^0.10.1" +marshmallow = "^3.4.0" +webargs = "^6.0.0" zeroconf = ">=0.24.5,<0.28.0" +[package.source] +reference = "6d4c8737c4eb1d73632944f1db5b6c31af8b413f" +type = "git" +url = "https://github.com/labthings/python-labthings.git" [[package]] category = "dev" description = "A fast and thorough lazy object proxy." @@ -433,6 +438,7 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "" type = "url" url = "https://github.com/rwb27/picamera/releases/download/v1.13.2b0/picamera-1.13.2b0-py3-none-any.whl" + [[package]] category = "main" description = "Python Imaging Library (Fork)" @@ -835,7 +841,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] rpi = ["RPi.GPIO"] [metadata] -content-hash = "ae04fc9fc87b8a1dd96e9fe8b442fb6261d2ded29aef1385dcb35beac5d8f467" +content-hash = "d6978ac9c76482b473689cd4a1e48348d7c93cbfcb823856f1bb3a6b46ed3283" python-versions = "^3.6" [metadata.files] @@ -1003,10 +1009,7 @@ jinja2 = [ {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, ] -labthings = [ - {file = "labthings-0.6.7-py3-none-any.whl", hash = "sha256:c39d3de301cc2c0b21e44bee49c84bdc912be84ace0168de7c16dc4966b4397b"}, - {file = "labthings-0.6.7.tar.gz", hash = "sha256:5c70fde942436a21764f0ba7e5a77c0f080e51270dc8b4c69917b5b23e253c7b"}, -] +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"}, diff --git a/pyproject.toml b/pyproject.toml index ea51fa64..bf849fc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ opencv-python-headless = [ {version = "4.1.0.25", python = "~3.7"}, # PiWheels build for RPi running Py37 {version = "4.2.0.34", python = "^3.8"} # Latest for Py38 systems ] -labthings = "0.6.7" +labthings = {git = "https://github.com/labthings/python-labthings.git", rev = "simpler-spec"} pynpm = "^0.1.2" camera-stage-mapping = {git = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git"} sangaboard = "^0.2" From 3bb870a3a352eb60f11807d87f65ae6374be2296 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:24:49 +0100 Subject: [PATCH 02/30] Updated labthings dev version --- poetry.lock | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4534f083..f451c548 100644 --- a/poetry.lock +++ b/poetry.lock @@ -110,7 +110,6 @@ ofmclient = [] reference = "87a66aadbbea211b260dc279c0f009a8a2b8de51" type = "git" url = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git" - [[package]] category = "main" description = "Pure Python CBOR (de)serializer with extensive tag support" @@ -336,7 +335,7 @@ webargs = "^6.0.0" zeroconf = ">=0.24.5,<0.28.0" [package.source] -reference = "6d4c8737c4eb1d73632944f1db5b6c31af8b413f" +reference = "c80e45f1a391c353b8cbe78a667f51c2e36ced76" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -438,7 +437,6 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "" type = "url" url = "https://github.com/rwb27/picamera/releases/download/v1.13.2b0/picamera-1.13.2b0-py3-none-any.whl" - [[package]] category = "main" description = "Python Imaging Library (Fork)" From e6cb9f6b35f2961f5c2aea778eef5b945747e76d Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:24:59 +0100 Subject: [PATCH 03/30] Initial 0.7.0 conversion --- .../api/default_extensions/autofocus.py | 22 +++--- .../api/default_extensions/autostorage.py | 7 +- .../api/default_extensions/scan.py | 38 +++++----- .../api/default_extensions/zip_builder.py | 12 +-- .../api/v2/views/actions/__init__.py | 1 - .../api/v2/views/actions/camera.py | 76 +++++++++---------- .../api/v2/views/actions/stage.py | 20 +++-- .../api/v2/views/actions/system.py | 8 +- .../api/v2/views/captures.py | 33 ++++---- .../api/v2/views/instrument.py | 24 +++--- .../api/v2/views/streams.py | 18 +++-- 11 files changed, 129 insertions(+), 130 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/autofocus.py b/openflexure_microscope/api/default_extensions/autofocus.py index d61f72df..8ce2575a 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, ActionView, PropertyView -from labthings.server.decorators import ThingAction, ThingProperty +from labthings.server import fields from openflexure_microscope.devel import JsonResponse, request, abort from openflexure_microscope.utilities import set_properties @@ -336,7 +336,9 @@ class AutofocusAPI(ActionView): """ Run a standard autofocus """ - def post(self): + args = {"dz": fields.List(fields.Int())} + + def post(self, args): payload = JsonResponse(request) microscope = find_component("org.openflexure.microscope") @@ -344,7 +346,7 @@ class AutofocusAPI(ActionView): abort(503, "No microscope connected. Unable to autofocus.") # Figure out the range of z values to use - dz = payload.param("dz", default=np.linspace(-300, 300, 7), convert=np.array) + dz = np.array(args.get("dz", np.linspace(-300, 300, 7))) if microscope.has_real_stage(): logging.debug("Running autofocus...") @@ -360,18 +362,20 @@ class FastAutofocusAPI(ActionView): """ Run a fast autofocus """ - def post(self): - payload = JsonResponse(request) + args = { + "dz": fields.Int(default=2000), + "backlash": fields.Int(default=25, minimum=0) + } + + def post(self, args): microscope = find_component("org.openflexure.microscope") if not microscope: abort(503, "No microscope connected. Unable to autofocus.") # Figure out the parameters to use - dz = payload.param("dz", default=2000, convert=int) - backlash = payload.param("backlash", default=25, convert=int) - if backlash < 0: - backlash = 0 + dz = args.get("dz") + backlash = args.get("backlash") if microscope.has_real_stage(): logging.debug("Running autofocus...") diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index da282827..5f0640c0 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -1,6 +1,5 @@ from labthings.server.extensions import BaseExtension from labthings.server.view import View, PropertyView -from labthings.server.decorators import ThingProperty, PropertySchema, use_args from labthings.server import fields from labthings.server.find import find_component @@ -198,8 +197,9 @@ class GetLocationsView(PropertyView): return autostorage_extension_v2.get_locations() -@PropertySchema(fields.String(required=True, example="Default")) class PreferredLocationView(PropertyView): + schema = fields.String(required=True, example="Default") + def get(self): global autostorage_extension_v2 @@ -219,7 +219,8 @@ class PreferredLocationView(PropertyView): class PreferredLocationGUIView(View): - @use_args({"new_path_title": fields.String(required=True)}) + args = {"new_path_title": fields.String(required=True)} + def post(self, args): global autostorage_extension_v2 diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index 65db21b9..c58d282f 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -8,7 +8,6 @@ from functools import reduce from openflexure_microscope.captures.capture_manager import generate_basename from labthings.server.find import find_component, find_extension from labthings.server.extensions import BaseExtension -from labthings.server.decorators import use_args from labthings.server import fields from openflexure_microscope.devel import abort, update_task_progress @@ -91,7 +90,7 @@ class ScanExtension(BaseExtension): def __init__(self): self._images_to_be_captured: int = 1 self._images_captured_so_far: int = 0 - return BaseExtension.__init__(self, "org.openflexure.scan", version="2.0.0") + BaseExtension.__init__(self, "org.openflexure.scan", version="2.0.0") def progress(self): progress = (self._images_captured_so_far / self._images_to_be_captured) * 100 @@ -289,24 +288,23 @@ class ScanExtension(BaseExtension): scan_extension_v2 = ScanExtension() class TileScanAPI(ActionView): - @use_args( - { - "filename": fields.String(missing=None, example=None), - "temporary": fields.Boolean(missing=False), - "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), - "annotations": fields.Dict(missing={}, example={"Foo": "Bar"}), - "tags": fields.List(fields.String, missing=[]), - "resize": fields.Dict(missing=None), # TODO: Validate keys - } - ) + args = { + "filename": fields.String(missing=None, example=None), + "temporary": fields.Boolean(missing=False), + "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), + "annotations": fields.Dict(missing={}, example={"Foo": "Bar"}), + "tags": fields.List(fields.String, missing=[]), + "resize": fields.Dict(missing=None), # TODO: Validate keys + } + def post(self, args): microscope = find_component("org.openflexure.microscope") diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index e168e42f..d012741c 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -14,16 +14,10 @@ import logging from labthings.server.find import find_component from labthings.server.view import View, ActionView, PropertyView -from labthings.server.schema import Schema +from labthings.server.schema import Schema, pre_dump from labthings.server import fields from labthings.server.extensions import BaseExtension from labthings.server.utilities import description_from_view -from labthings.server.decorators import ( - ThingAction, - ThingProperty, - marshal_with, - pre_dump, -) class ZipObjectSchema(Schema): @@ -140,7 +134,6 @@ default_zip_manager = ZipManager() class ZipBuilderAPIView(ActionView): def post(self): - ids = list(JsonResponse(request).json) microscope = find_component("org.openflexure.microscope") @@ -151,7 +144,8 @@ class ZipBuilderAPIView(ActionView): class ZipListAPIView(PropertyView): - @marshal_with(ZipObjectSchema(many=True)) + schema = ZipObjectSchema(many=True) + def get(self): return default_zip_manager.session_zips.values() diff --git a/openflexure_microscope/api/v2/views/actions/__init__.py b/openflexure_microscope/api/v2/views/actions/__init__.py index e06454c5..9c7ad8b3 100644 --- a/openflexure_microscope/api/v2/views/actions/__init__.py +++ b/openflexure_microscope/api/v2/views/actions/__init__.py @@ -58,7 +58,6 @@ def enabled_root_actions(): return {k: v for k, v in _actions.items() if v["conditions"]} -# @Tag("actions") class ActionsView(View): def get(self): """ diff --git a/openflexure_microscope/api/v2/views/actions/camera.py b/openflexure_microscope/api/v2/views/actions/camera.py index 59872992..176112cc 100644 --- a/openflexure_microscope/api/v2/views/actions/camera.py +++ b/openflexure_microscope/api/v2/views/actions/camera.py @@ -1,19 +1,11 @@ from openflexure_microscope.api.utilities import get_bool, JsonResponse from labthings.server.view import View, ActionView from labthings.server.find import find_component -from labthings.server.decorators import ( - use_args, - marshal_with, - doc, - tag, - ThingAction, - doc_response, -) from labthings.server import fields from openflexure_microscope.utilities import filter_dict -from openflexure_microscope.api.v2.views.captures import capture_schema +from openflexure_microscope.api.v2.views.captures import CaptureSchema import logging import io @@ -25,24 +17,23 @@ class CaptureAPI(ActionView): Create a new image capture. """ - @use_args( - { - "filename": fields.String(example="MyFileName"), - "temporary": fields.Boolean( - missing=False, description="Delete capture on shutdown" - ), - "use_video_port": fields.Boolean(missing=False), - "bayer": fields.Boolean( - missing=False, description="Store raw bayer data in file" - ), - "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} - ), # TODO: Validate keys - } - ) - @marshal_with(capture_schema) + args = { + "filename": fields.String(example="MyFileName"), + "temporary": fields.Boolean( + missing=False, description="Delete capture on shutdown" + ), + "use_video_port": fields.Boolean(missing=False), + "bayer": fields.Boolean( + missing=False, description="Store raw bayer data in file" + ), + "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} + ), # TODO: Validate keys + } + schema = CaptureSchema() + def post(self, args): """ Create a new capture @@ -78,18 +69,22 @@ class RAMCaptureAPI(ActionView): Take a non-persistant image capture. """ - @use_args( - { - "use_video_port": fields.Boolean(missing=True), - "bayer": fields.Boolean( - missing=False, description="Return with raw bayer data" - ), - "resize": fields.Dict( - missing=None, example={"width": 640, "height": 480} - ), # TODO: Validate keys + args = { + "use_video_port": fields.Boolean(missing=True), + "bayer": fields.Boolean( + missing=False, description="Return with raw bayer data" + ), + "resize": fields.Dict( + missing=None, example={"width": 640, "height": 480} + ), # TODO: Validate keys + } + + responses = { + 200: { + "content_type": "image/jpeg" } - ) - @doc_response(200, mimetype="image/jpeg") + } + def post(self, args): """ Take a non-persistant image capture. @@ -128,9 +123,8 @@ class GPUPreviewStartAPI(ActionView): in the format ``[x, y, width, height]``. """ - @use_args( - {"window": fields.List(fields.Integer, missing=[], example=[0, 0, 640, 480])} - ) + args = {"window": fields.List(fields.Integer, missing=[], example=[0, 0, 640, 480])} + def post(self, args): """ Start the onboard GPU preview. diff --git a/openflexure_microscope/api/v2/views/actions/stage.py b/openflexure_microscope/api/v2/views/actions/stage.py index 0b9d1372..334de1b8 100644 --- a/openflexure_microscope/api/v2/views/actions/stage.py +++ b/openflexure_microscope/api/v2/views/actions/stage.py @@ -1,7 +1,6 @@ from openflexure_microscope.api.utilities import JsonResponse from labthings.server.view import View, ActionView from labthings.server.find import find_component -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 @@ -12,16 +11,15 @@ import logging class MoveStageAPI(ActionView): - @use_args( - { - "absolute": fields.Boolean( - default=False, example=False, description="Move to an absolute position" - ), - "x": fields.Int(default=0, example=100), - "y": fields.Int(default=0, example=100), - "z": fields.Int(default=0, example=20), - } - ) + args = { + "absolute": fields.Boolean( + default=False, example=False, description="Move to an absolute position" + ), + "x": fields.Int(default=0, example=100), + "y": fields.Int(default=0, example=100), + "z": fields.Int(default=0, example=20), + } + def post(self, args): """ Move the microscope stage in x, y, z diff --git a/openflexure_microscope/api/v2/views/actions/system.py b/openflexure_microscope/api/v2/views/actions/system.py index d8756891..8677478d 100644 --- a/openflexure_microscope/api/v2/views/actions/system.py +++ b/openflexure_microscope/api/v2/views/actions/system.py @@ -3,8 +3,6 @@ import subprocess import os from sys import platform -from labthings.server.decorators import ThingAction, doc_response - def is_raspberrypi(raise_on_errors=False): """ @@ -19,7 +17,6 @@ class ShutdownAPI(ActionView): Attempt to shutdown the device """ - @doc_response(201) def post(self): """ Attempt to shutdown the device @@ -31,7 +28,7 @@ class ShutdownAPI(ActionView): ) out, err = p.communicate() - return {"out": out, "err": err}, 201 + return {"out": out, "err": err} class RebootAPI(ActionView): @@ -39,7 +36,6 @@ class RebootAPI(ActionView): Attempt to reboot the device """ - @doc_response(201) def post(self): """ Attempt to reboot the device @@ -51,4 +47,4 @@ class RebootAPI(ActionView): ) out, err = p.communicate() - return {"out": out, "err": err}, 201 + return {"out": out, "err": err} diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index b4b00e04..09f3ae4f 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -7,7 +7,6 @@ from labthings.server.schema import Schema from labthings.server import fields from labthings.server.view import View, PropertyView 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 @@ -84,16 +83,10 @@ class CaptureSchema(Schema): return data -capture_schema = CaptureSchema() -capture_list_schema = CaptureSchema(many=True) - - -from pprint import pprint - - -@Tag("captures") class CaptureList(PropertyView): - @marshal_with(CaptureSchema(many=True)) + tags = ["captures"] + schema = CaptureSchema(many=True) + def get(self): """ List all image captures @@ -103,9 +96,10 @@ class CaptureList(PropertyView): return image_list -@Tag("captures") class CaptureView(View): - @marshal_with(CaptureSchema()) + tags = ["captures"] + schema = CaptureSchema() + def get(self, id): """ Description of a single image capture @@ -136,9 +130,14 @@ class CaptureView(View): return "", 204 -@Tag("captures") class CaptureDownload(View): - @doc_response(200, mimetype="image/jpeg") + tags = ["captures"] + responses = { + 200: { + "content_type": "image/jpeg" + } + } + def get(self, id, filename): """ Image data for a single image capture @@ -172,8 +171,9 @@ class CaptureDownload(View): return send_file(img, mimetype="image/jpeg") -@Tag("captures") class CaptureTags(View): + tags = ["captures"] + def get(self, id): """ Get tags associated with a single image capture @@ -227,8 +227,9 @@ class CaptureTags(View): return capture_obj.tags -@Tag("captures") class CaptureAnnotations(View): + tags = ["captures"] + def get(self, id): """ Get annotations associated with a single image capture diff --git a/openflexure_microscope/api/v2/views/instrument.py b/openflexure_microscope/api/v2/views/instrument.py index 3a51633e..e7198373 100644 --- a/openflexure_microscope/api/v2/views/instrument.py +++ b/openflexure_microscope/api/v2/views/instrument.py @@ -5,8 +5,6 @@ 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, PropertyView -from labthings.server.decorators import ThingProperty, Tag, doc_response - from flask import request, abort import logging @@ -35,9 +33,12 @@ class SettingsProperty(PropertyView): return self.get() -@Tag("properties") class NestedSettingsProperty(View): - @doc_response(404, description="Settings key cannot be found") + tags = ["properties"] + responses = { + 404: {"description": "Settings key cannot be found"} + } + def get(self, route): """ Show a nested section of the current microscope settings @@ -52,7 +53,6 @@ class NestedSettingsProperty(View): return value - @doc_response(404, description="Settings key cannot be found") def put(self, route): """ Update a nested section of the current microscope settings @@ -79,9 +79,12 @@ class StateProperty(PropertyView): return microscope.state -@Tag("properties") class NestedStateProperty(View): - @doc_response(404, description="Status key cannot be found") + tags = ["properties"] + responses = { + 404: {"description": "Status key cannot be found"} + } + def get(self, route): """ Show a nested section of the current microscope state @@ -106,9 +109,12 @@ class ConfigurationProperty(PropertyView): return microscope.configuration -@Tag("properties") class NestedConfigurationProperty(View): - @doc_response(404, description="Configuration key cannot be found") + tags = ["properties"] + responses = { + 404: {"description": "Status key cannot be found"} + } + def get(self, route): """ Show a nested section of the current microscope state diff --git a/openflexure_microscope/api/v2/views/streams.py b/openflexure_microscope/api/v2/views/streams.py index f8b8cbe7..02eddc2e 100644 --- a/openflexure_microscope/api/v2/views/streams.py +++ b/openflexure_microscope/api/v2/views/streams.py @@ -4,17 +4,19 @@ 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, PropertyView -from labthings.server.decorators import doc_response, ThingProperty - -from flask import Response +from labthings.server.responses import Response class MjpegStream(PropertyView): """ Real-time MJPEG stream from the microscope camera """ + responses = { + 200: { + "content_type": "multipart/x-mixed-replace" + } + } - @doc_response(200, mimetype="multipart/x-mixed-replace") def get(self): """ MJPEG stream from the microscope camera. @@ -42,7 +44,13 @@ class SnapshotStream(PropertyView): Single JPEG snapshot from the camera stream """ - @doc_response(200, description="Snapshot taken", mimetype="image/jpeg") + responses = { + 200: { + "content_type": "image/jpeg", + "description": "Snapshot taken" + } + } + def get(self): """ Single snapshot from the camera stream From a72211a6dc623e8f01e53288561cbb0c8939083b Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:34:32 +0100 Subject: [PATCH 04/30] Removed old decorator import --- openflexure_microscope/api/v2/views/actions/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openflexure_microscope/api/v2/views/actions/__init__.py b/openflexure_microscope/api/v2/views/actions/__init__.py index 9c7ad8b3..56a3a4c4 100644 --- a/openflexure_microscope/api/v2/views/actions/__init__.py +++ b/openflexure_microscope/api/v2/views/actions/__init__.py @@ -7,7 +7,6 @@ from . import camera, stage, system from labthings.server.view import View from labthings.server.find import current_labthing from labthings.server.utilities import description_from_view -from labthings.server.decorators import Tag _actions = { "capture": { From 89e8d041a86e21b6cac73bc2e5b8aa293c6027c2 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:34:39 +0100 Subject: [PATCH 05/30] Removed unused explicit LST handler --- openflexure_microscope/camera/pi.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index d24ac186..8eeb1def 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -212,18 +212,6 @@ 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): From 8152d1dccd0e77ac8fc04cb398c29d15f60c4e70 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:35:29 +0100 Subject: [PATCH 06/30] Revert "Removed unused explicit LST handler" This reverts commit 89e8d041a86e21b6cac73bc2e5b8aa293c6027c2. --- openflexure_microscope/camera/pi.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 8eeb1def..d24ac186 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -212,6 +212,18 @@ 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): From 0081ac6649060ee0ec17f6a44c27622e25245bb8 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:35:50 +0100 Subject: [PATCH 07/30] Removed unused explicit LST handler --- openflexure_microscope/microscope.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 6672c4cb..eaa019bb 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -212,21 +212,6 @@ class Microscope: settings_current_camera = self.camera.read_settings() 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() - - if lst_arr is not None: - 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, - } - # If attached to a stage if self.stage: settings_current_stage = self.stage.read_settings() From aa8e916e4bb16284a9b568439951e05dd8b256e0 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:37:23 +0100 Subject: [PATCH 08/30] Converted to LT070 --- openflexure_microscope/api/dev_extensions/tools.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openflexure_microscope/api/dev_extensions/tools.py b/openflexure_microscope/api/dev_extensions/tools.py index 564dcd6d..3d0077b3 100644 --- a/openflexure_microscope/api/dev_extensions/tools.py +++ b/openflexure_microscope/api/dev_extensions/tools.py @@ -2,7 +2,6 @@ from labthings.server.extensions import BaseExtension from labthings.server.view import ActionView from labthings.server import fields -from labthings.server.decorators import use_args, marshal_with import logging import time @@ -12,12 +11,9 @@ class RaiseException(ActionView): raise Exception("The developer raised an exception") class SleepFor(ActionView): - @marshal_with({ - "TimeAsleep": fields.Float() - }) - @use_args({ - "time": fields.Float(description="Time to sleep, in seconds", example=0.5) - }) + schema = {"TimeAsleep": fields.Float()} + args = {"time": fields.Float(description="Time to sleep, in seconds", example=0.5)} + def post(self, args): sleep_time = args.get("time") logging.info(f"Going to sleep for {sleep_time}...") From 54a2e785512c5dc809f2cf685aacce753c5c290a Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:38:09 +0100 Subject: [PATCH 09/30] Fixed broken error handler --- openflexure_microscope/camera/piexif/_remove.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/camera/piexif/_remove.py b/openflexure_microscope/camera/piexif/_remove.py index c55fa101..10940b96 100644 --- a/openflexure_microscope/camera/piexif/_remove.py +++ b/openflexure_microscope/camera/piexif/_remove.py @@ -40,8 +40,8 @@ def remove(src, new_file=None): new_data = _webp.remove(src_data) except ValueError: new_data = src_data - except e: - print(e.args) + except Exception as e: + print(e) raise ValueError("Error occurred.") if isinstance(new_file, io.BytesIO): From 2e9fb72f987503e46583f2dda8bfec7e5744967f Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:38:19 +0100 Subject: [PATCH 10/30] Removed old decorators import --- .../api/default_extensions/picamera_autocalibrate/extension.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py index 5a6f4b21..45a3e213 100644 --- a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py +++ b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py @@ -1,7 +1,6 @@ from labthings.server.view import View, ActionView from labthings.server.find import find_component from labthings.server.extensions import BaseExtension -from labthings.server.decorators import ThingAction from flask import abort From d897ad38a5fca0eb7fa2dd91cbedccadc27c03bb Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 15:39:33 +0100 Subject: [PATCH 11/30] Added basecamera preview_active --- openflexure_microscope/camera/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index 9d2c197d..637fc34c 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -35,6 +35,7 @@ class BaseCamera(metaclass=ABCMeta): self.stream_active = False self.record_active = False + self.preview_active = False @property @abstractmethod From 513f3cc0570c2348b4fde76b7a3763ce2aaf5e47 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 16:22:28 +0100 Subject: [PATCH 12/30] Updated dev labthings --- .../api/static/package-lock.json | 44 ++++++++++++++----- poetry.lock | 4 +- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/openflexure_microscope/api/static/package-lock.json b/openflexure_microscope/api/static/package-lock.json index fd6ff999..8a0083a6 100644 --- a/openflexure_microscope/api/static/package-lock.json +++ b/openflexure_microscope/api/static/package-lock.json @@ -6918,7 +6918,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -6939,12 +6940,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6959,17 +6962,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -7086,7 +7092,8 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -7098,6 +7105,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7112,6 +7120,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7119,12 +7128,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7143,6 +7154,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -7232,7 +7244,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -7244,6 +7257,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -7329,7 +7343,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -7365,6 +7380,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7384,6 +7400,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -7427,12 +7444,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -9904,7 +9923,8 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true + "dev": true, + "optional": true }, "pify": { "version": "4.0.1", diff --git a/poetry.lock b/poetry.lock index f451c548..d3d4b35d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -107,7 +107,7 @@ ipython = [] ofmclient = [] [package.source] -reference = "87a66aadbbea211b260dc279c0f009a8a2b8de51" +reference = "e6f1a8ade441064a0267a434692cd755aae33a8e" type = "git" url = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git" [[package]] @@ -335,7 +335,7 @@ webargs = "^6.0.0" zeroconf = ">=0.24.5,<0.28.0" [package.source] -reference = "c80e45f1a391c353b8cbe78a667f51c2e36ced76" +reference = "b893cce91504a2e24c95f8ddb72d47b7a949411e" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] From 29ee73cedc9aef2d3661d82221ec1b41f0991ea7 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 16:39:37 +0100 Subject: [PATCH 13/30] Fixed argument 'missing' values --- .../api/default_extensions/autofocus.py | 4 ++-- openflexure_microscope/api/v2/views/actions/stage.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/autofocus.py b/openflexure_microscope/api/default_extensions/autofocus.py index 8ce2575a..aa3b47f8 100644 --- a/openflexure_microscope/api/default_extensions/autofocus.py +++ b/openflexure_microscope/api/default_extensions/autofocus.py @@ -363,8 +363,8 @@ class FastAutofocusAPI(ActionView): Run a fast autofocus """ args = { - "dz": fields.Int(default=2000), - "backlash": fields.Int(default=25, minimum=0) + "dz": fields.Int(missing=2000), + "backlash": fields.Int(missing=25, minimum=0) } def post(self, args): diff --git a/openflexure_microscope/api/v2/views/actions/stage.py b/openflexure_microscope/api/v2/views/actions/stage.py index 334de1b8..b21202e1 100644 --- a/openflexure_microscope/api/v2/views/actions/stage.py +++ b/openflexure_microscope/api/v2/views/actions/stage.py @@ -13,11 +13,11 @@ import logging class MoveStageAPI(ActionView): args = { "absolute": fields.Boolean( - default=False, example=False, description="Move to an absolute position" + missing=False, example=False, description="Move to an absolute position" ), - "x": fields.Int(default=0, example=100), - "y": fields.Int(default=0, example=100), - "z": fields.Int(default=0, example=20), + "x": fields.Int(missing=0, example=100), + "y": fields.Int(missing=0, example=100), + "z": fields.Int(missing=0, example=20), } def post(self, args): From 13e7df1c54ad06a9773ff117ab203aee3573249a Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 16:46:02 +0100 Subject: [PATCH 14/30] Specify LabThings 0.7 branch of camera-stage-mapping --- poetry.lock | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index d3d4b35d..072950bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -107,7 +107,7 @@ ipython = [] ofmclient = [] [package.source] -reference = "e6f1a8ade441064a0267a434692cd755aae33a8e" +reference = "f5d0d8977a2511b20b47061ac203551f97b8b4ab" type = "git" url = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git" [[package]] @@ -335,9 +335,10 @@ webargs = "^6.0.0" zeroconf = ">=0.24.5,<0.28.0" [package.source] -reference = "b893cce91504a2e24c95f8ddb72d47b7a949411e" +reference = "d7889d07dee0344b112a606caabc836e69bac63f" type = "git" url = "https://github.com/labthings/python-labthings.git" + [[package]] category = "dev" description = "A fast and thorough lazy object proxy." @@ -437,6 +438,7 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "" type = "url" url = "https://github.com/rwb27/picamera/releases/download/v1.13.2b0/picamera-1.13.2b0-py3-none-any.whl" + [[package]] category = "main" description = "Python Imaging Library (Fork)" @@ -839,7 +841,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] rpi = ["RPi.GPIO"] [metadata] -content-hash = "d6978ac9c76482b473689cd4a1e48348d7c93cbfcb823856f1bb3a6b46ed3283" +content-hash = "0607e2b39e77ac530c84a4c2e1dfc14b373ca96322b634e52001cb0591b4b3e7" python-versions = "^3.6" [metadata.files] From 83b3ea1d1d15f44777f46ab0b7d4ec67248d27c2 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 16:46:14 +0100 Subject: [PATCH 15/30] Specify LabThings 0.7 branch of camera-stage-mapping --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bf849fc7..a2754a00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ opencv-python-headless = [ ] labthings = {git = "https://github.com/labthings/python-labthings.git", rev = "simpler-spec"} pynpm = "^0.1.2" -camera-stage-mapping = {git = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git"} +camera-stage-mapping = {git = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git", rev = "labthings-0.7"} sangaboard = "^0.2" gevent = "20.6.0" # Pinned version to guarantee piwheels wheel From 9ae2ae06d0d78a1fa6f3f2440433a0f6648ccbe9 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 17:47:51 +0100 Subject: [PATCH 16/30] Updated documentation to LT070 style --- docs/source/capture.rst | 4 +- docs/source/extensions/actions.rst | 11 ++-- .../example_extension/02_adding_views.py | 11 ++-- .../example_extension/03_marshaling_data.py | 10 ++-- .../example_extension/04_properties.py | 20 +++---- .../example_extension/05_actions.py | 32 +++++----- .../example_extension/06_tasks_locks.py | 31 ++++------ .../extensions/example_extension/07_ev_gui.py | 31 ++++------ docs/source/extensions/marshaling.rst | 60 +++++++++++-------- docs/source/extensions/properties.rst | 21 ++++--- docs/source/extensions/views.rst | 6 +- docs/source/microscope.rst | 4 +- 12 files changed, 114 insertions(+), 127 deletions(-) diff --git a/docs/source/capture.rst b/docs/source/capture.rst index 21c3dd27..a2d0846f 100644 --- a/docs/source/capture.rst +++ b/docs/source/capture.rst @@ -1,11 +1,11 @@ Capture Object ======================================================= -By default, all image and video capture data are stored to instances of :py:class:`openflexure_microscope.camera.capture.CaptureObject`. This class mostly wraps up complexity associated with moving data between disk and memory. +By default, all image and video capture data are stored to instances of :py:class:`openflexure_microscope.captures.CaptureObject`. This class mostly wraps up complexity associated with moving data between disk and memory. The class also includes some convenience features such as handling metadata tags and file names, and generating image thumbnails. Additionally, the class handles storing capture metadata to Exif tags in supported formats. Below are details of available methods and attributes. -.. automodule:: openflexure_microscope.camera.capture +.. automodule:: openflexure_microscope.captures.capture :members: \ No newline at end of file diff --git a/docs/source/extensions/actions.rst b/docs/source/extensions/actions.rst index e68a614a..5463ccfd 100644 --- a/docs/source/extensions/actions.rst +++ b/docs/source/extensions/actions.rst @@ -17,11 +17,14 @@ Like properties, we use a special view class to identify a view as an action: `` """ Take an image capture and return it without saving """ - # Expect a "use_video_port" boolean, which defaults to True if none is given - @use_args({"use_video_port": fields.Boolean(missing=True)}) + args = {"use_video_port": fields.Boolean(missing=True)} + # Our success response (200) returns an image (image/jpeg mimetype) - @doc_response(200, mimetype="image/jpeg") + responses = { + 200: {"content_type": "image/jpeg"} + } + def post(self, args): """ Take a non-persistant image capture. @@ -41,7 +44,7 @@ Like properties, we use a special view class to identify a view as an action: `` # Return our image data using Flasks send_file function return send_file(io.BytesIO(stream.read()), mimetype="image/jpeg") -In this example, we are also making use of the ``@doc_response`` decorator, to document that our successful response (HTTP code 200) will return data with a mimetype ``image/jpeg``, as well as ``@use_args`` to accept optional parameters with POST requests. +In this example, we are also making use of the ``responses`` attribute, to document that our successful response (HTTP code 200) will return data with a mimetype ``image/jpeg``, as well as ``args`` to accept optional parameters with POST requests. Complete example ---------------- diff --git a/docs/source/extensions/example_extension/02_adding_views.py b/docs/source/extensions/example_extension/02_adding_views.py index ce815d55..8e313e30 100644 --- a/docs/source/extensions/example_extension/02_adding_views.py +++ b/docs/source/extensions/example_extension/02_adding_views.py @@ -2,7 +2,6 @@ from labthings.server.extensions import BaseExtension from labthings.server.find import find_component from labthings.server.view import View -from labthings.server.decorators import use_body from labthings.server import fields ## Extension methods @@ -44,11 +43,13 @@ class ExampleIdentifyView(View): class ExampleRenameView(View): - # Expect a request parameter called "name", which is a string. Pass to argument "args". - @use_body(fields.String(required=True, example="My Example Microscope")) - def post(self, body): + # Expect a request parameter called "name", which is a string. + # Passed to the argument "args". + args = fields.String(required=True, example="My Example Microscope") + + def post(self, args): # Look for our new name in the request body - new_name = body + new_name = args # Find our microscope component microscope = find_component("org.openflexure.microscope") diff --git a/docs/source/extensions/example_extension/03_marshaling_data.py b/docs/source/extensions/example_extension/03_marshaling_data.py index d3179c10..a686a775 100644 --- a/docs/source/extensions/example_extension/03_marshaling_data.py +++ b/docs/source/extensions/example_extension/03_marshaling_data.py @@ -2,7 +2,6 @@ from labthings.server.extensions import BaseExtension from labthings.server.find import find_component from labthings.server.view import View -from labthings.server.decorators import use_args, marshal_with from labthings.server.schema import Schema from labthings.server import fields @@ -30,10 +29,10 @@ def rename(microscope, new_name): ## Extension views - class ExampleIdentifyView(View): # Format our returned object using MicroscopeIdentifySchema - @marshal_with(MicroscopeIdentifySchema()) + schema = MicroscopeIdentifySchema() + def get(self): # Find our microscope component microscope = find_component("org.openflexure.microscope") @@ -45,9 +44,10 @@ class ExampleIdentifyView(View): class ExampleRenameView(View): # Format our returned object using MicroscopeIdentifySchema - @marshal_with(MicroscopeIdentifySchema()) + schema = MicroscopeIdentifySchema() # Expect a request parameter called "name", which is a string. Pass to argument "args". - @use_args({"name": fields.String(required=True, example="My Example Microscope")}) + args = {"name": fields.String(required=True, example="My Example Microscope")} + def post(self, args): # Look for our "name" parameter in the request arguments new_name = args.get("name") diff --git a/docs/source/extensions/example_extension/04_properties.py b/docs/source/extensions/example_extension/04_properties.py index 68d06933..5e1614d3 100644 --- a/docs/source/extensions/example_extension/04_properties.py +++ b/docs/source/extensions/example_extension/04_properties.py @@ -2,12 +2,6 @@ from labthings.server.extensions import BaseExtension from labthings.server.find import find_component from labthings.server.view import View, PropertyView -from labthings.server.decorators import ( - use_args, - marshal_with, - ThingProperty, - PropertySchema, -) from labthings.server.schema import Schema from labthings.server import fields @@ -38,7 +32,8 @@ def rename(microscope, new_name): # Since we only have a GET method here, it'll register as a read-only property class ExampleIdentifyView(PropertyView): # Format our returned object using MicroscopeIdentifySchema - @marshal_with(MicroscopeIdentifySchema()) + schema = MicroscopeIdentifySchema() + def get(self): """ Show identifying information about the current microscope object @@ -47,14 +42,15 @@ class ExampleIdentifyView(PropertyView): microscope = find_component("org.openflexure.microscope") # Return our microscope object, - # let @marshal_with handle formatting the output + # let schema handle formatting the output return microscope -# We can use a single schema for all methods if the input and output will be formatted identically -# Eg. Here, we will always expect a "name" string argument, and always return a "name" string attribute -@PropertySchema({"name": fields.String(required=True, example="My Example Microscope")}) +# We can use a single schema as the input and output will be formatted identically +# Eg. We always expect a "name" string argument, and always return a "name" string attribute class ExampleRenameView(PropertyView): + schema = {"name": fields.String(required=True, example="My Example Microscope")} + def get(self): """ Show the current microscope name @@ -78,7 +74,7 @@ class ExampleRenameView(PropertyView): rename(microscope, new_name) # Return our microscope object, - # let @marshal_with handle formatting the output + # let schema handle formatting the output return microscope diff --git a/docs/source/extensions/example_extension/05_actions.py b/docs/source/extensions/example_extension/05_actions.py index e67711d4..bba3fd03 100644 --- a/docs/source/extensions/example_extension/05_actions.py +++ b/docs/source/extensions/example_extension/05_actions.py @@ -2,14 +2,6 @@ from labthings.server.extensions import BaseExtension from labthings.server.find import find_component from labthings.server.view import View, ActionView, PropertyView -from labthings.server.decorators import ( - use_args, - marshal_with, - ThingProperty, - PropertySchema, - ThingAction, - doc_response, -) from labthings.server.schema import Schema from labthings.server import fields @@ -43,7 +35,8 @@ def rename(microscope, new_name): # Since we only have a GET method here, it'll register as a read-only property class ExampleIdentifyView(PropertyView): # Format our returned object using MicroscopeIdentifySchema - @marshal_with(MicroscopeIdentifySchema()) + schema = MicroscopeIdentifySchema() + def get(self): """ Show identifying information about the current microscope object @@ -52,14 +45,16 @@ class ExampleIdentifyView(PropertyView): microscope = find_component("org.openflexure.microscope") # Return our microscope object, - # let @marshal_with handle formatting the output + # let schema handle formatting the output return microscope -# We can use a single schema for all methods if the input and output will be formatted identically -# Eg. Here, we will always expect a "name" string argument, and always return a "name" string attribute -@PropertySchema({"name": fields.String(required=True, example="My Example Microscope")}) + +# We can use a single schema as the input and output will be formatted identically +# Eg. We always expect a "name" string argument, and always return a "name" string attribute class ExampleRenameView(PropertyView): + schema = {"name": fields.String(required=True, example="My Example Microscope")} + def get(self): """ Show the current microscope name @@ -83,7 +78,7 @@ class ExampleRenameView(PropertyView): rename(microscope, new_name) # Return our microscope object, - # let @marshal_with handle formatting the output + # let schema handle formatting the output return microscope @@ -91,11 +86,14 @@ class QuickCaptureAPI(ActionView): """ Take an image capture and return it without saving """ - # Expect a "use_video_port" boolean, which defaults to True if none is given - @use_args({"use_video_port": fields.Boolean(missing=True)}) + args = {"use_video_port": fields.Boolean(missing=True)} + # Our success response (200) returns an image (image/jpeg mimetype) - @doc_response(200, mimetype="image/jpeg") + responses = { + 200: {"content_type": "image/jpeg"} + } + def post(self, args): """ Take a non-persistant image capture. diff --git a/docs/source/extensions/example_extension/06_tasks_locks.py b/docs/source/extensions/example_extension/06_tasks_locks.py index 0d2e435d..e38d9b60 100644 --- a/docs/source/extensions/example_extension/06_tasks_locks.py +++ b/docs/source/extensions/example_extension/06_tasks_locks.py @@ -2,14 +2,6 @@ from labthings.server.extensions import BaseExtension from labthings.server.find import find_component from labthings.server.view import View, ActionView -from labthings.server.decorators import ( - use_args, - marshal_with, - ThingProperty, - PropertySchema, - ThingAction, - doc_response, -) from labthings.server.schema import Schema from labthings.server import fields @@ -18,10 +10,10 @@ import io # Used in our capture action import time # Used in our timelapse function # Used in our timelapse function -from openflexure_microscope.camera.base import generate_basename +from openflexure_microscope.captures.capture_manager import generate_basename # Used to run our timelapse in a background thread -from labthings.core.tasks import taskify, update_task_progress +from labthings.core.tasks import update_task_progress ## Extension methods @@ -70,16 +62,15 @@ class TimelapseAPI(ActionView): """ Take a series of images in a timelapse """ - @use_args( - { - "n_images": fields.Integer( - required=True, example=5, description="Number of images" - ), - "t_between": fields.Number( - missing=1, example=1, description="Time (seconds) between images" - ), - } - ) + args = { + "n_images": fields.Integer( + required=True, example=5, description="Number of images" + ), + "t_between": fields.Number( + missing=1, example=1, description="Time (seconds) between images" + ), + } + def post(self, args): # Find our microscope component microscope = find_component("org.openflexure.microscope") diff --git a/docs/source/extensions/example_extension/07_ev_gui.py b/docs/source/extensions/example_extension/07_ev_gui.py index 0726020f..8203f802 100644 --- a/docs/source/extensions/example_extension/07_ev_gui.py +++ b/docs/source/extensions/example_extension/07_ev_gui.py @@ -2,14 +2,6 @@ from labthings.server.extensions import BaseExtension from labthings.server.find import find_component from labthings.server.view import View, ActionView -from labthings.server.decorators import ( - use_args, - marshal_with, - ThingProperty, - PropertySchema, - ThingAction, - doc_response, -) from labthings.server.schema import Schema from labthings.server import fields @@ -18,10 +10,10 @@ import io # Used in our capture action import time # Used in our timelapse function # Used in our timelapse function -from openflexure_microscope.camera.base import generate_basename +from openflexure_microscope.captures.capture_manager import generate_basename # Used to run our timelapse in a background thread -from labthings.core.tasks import taskify, update_task_progress +from labthings.core.tasks import update_task_progress # Used to convert our GUI dictionary into a complete eV extension GUI from openflexure_microscope.api.utilities.gui import build_gui @@ -72,16 +64,15 @@ class TimelapseAPI(ActionView): """ Take a series of images in a timelapse, running as a background task """ - @use_args( - { - "n_images": fields.Integer( - required=True, example=5, description="Number of images" - ), - "t_between": fields.Number( - missing=1, example=1, description="Time (seconds) between images" - ), - } - ) + args = { + "n_images": fields.Integer( + required=True, example=5, description="Number of images" + ), + "t_between": fields.Number( + missing=1, example=1, description="Time (seconds) between images" + ), + } + def post(self, args): # Find our microscope component microscope = find_component("org.openflexure.microscope") diff --git a/docs/source/extensions/marshaling.rst b/docs/source/extensions/marshaling.rst index 1f09162a..94f9ce1b 100644 --- a/docs/source/extensions/marshaling.rst +++ b/docs/source/extensions/marshaling.rst @@ -14,7 +14,7 @@ The OpenFlexure Microscope Server makes use of the `Marshmallow library `_, we can allow for more complex requests containing many parameters of different types. The parsed request parameters are then passed to the view function as a positional argument (as before), in the form of a dictionary. +In the previous section we saw how to use fields and ``args`` to get simple arguments from requests, in which a single parameter is required. By making use of Marshmallow schemas, and the `Webargs library `_, we can allow for more complex requests containing many parameters of different types. The parsed request parameters are then passed to the view function as a positional argument (as before), in the form of a dictionary. For example, if you are creating an API route, in which you expect parameters ``name``, ``age``, and optionally, ``job``, your schema class may look like: @@ -49,21 +49,25 @@ To inform your POST method to expect these arguments, use the ``@use_args`` deco .. code-block:: python - @use_args(UserSchema()) - def post(self, args): + class MyView(View): + args = UserSchema() + + def post(self, args): .. Alternatively, if your schema is only used in a single location, it may be simpler to create a dictionary schema only where it is used, for example: .. code-block:: python - @use_args({ - "name": fields.String(required=True), - "age": fields.Integer(required=True), - "job": fields.String(required=False, missing="Unknown") - }) - def post(self, args): - ... + class MyView(View): + args = { + "name": fields.String(required=True), + "age": fields.Integer(required=True), + "job": fields.String(required=False, missing="Unknown") + } + + def post(self, args): + ... A compatible request body, in JSON format, may look like: @@ -80,15 +84,17 @@ This JSON data is the parsed, converted into a Python dictionary, and passed as .. code-block:: python - @use_args({ - "name": fields.String(required=True), - "age": fields.Integer(required=True), - "job": fields.String(required=False, missing="Unknown") - }) - def post(self, args): - name = args.get("name") # Returns "John Doe", type str - age = args.get("age") # Returns 45, type int - job = args.get("job") # Returns "Python developer", type str + class MyView(View): + args = { + "name": fields.String(required=True), + "age": fields.Integer(required=True), + "job": fields.String(required=False, missing="Unknown") + } + + def post(self, args): + name = args.get("name") # Returns "John Doe", type str + age = args.get("age") # Returns 45, type int + job = args.get("job") # Returns "Python developer", type str Object serialization @@ -118,16 +124,17 @@ We use this new schema in our ``identify`` view like so: class ExampleIdentifyView(View): # Format our returned object using MicroscopeIdentifySchema - @marshal_with(MicroscopeIdentifySchema()) + schema = MicroscopeIdentifySchema() + def get(self): # Find our microscope component microscope = find_component("org.openflexure.microscope") # Return our microscope object, - # let @marshal_with handle formatting the output + # let schema handle formatting the output return microscope -Note that our ``get`` method now returns the :py:class:`openflexure_microscope.Microscope` object itself. No formatting is done by the function, it is entirely handled by ``@marshal_with(MicroscopeIdentifySchema())``. Additionally, since we defined our schema as a class, it can be re-used elsewhere. +Note that our ``get`` method now returns the :py:class:`openflexure_microscope.Microscope` object itself. No formatting is done by the function, it is entirely handled by the view class, and its `schema` attribute. Additionally, since we defined our schema as a class, it can be re-used elsewhere. For our ``rename`` view, we will use a simpler schema for our input arguments, defined by a dictionary (since we are only expecting a single parameter in, and it will likely not be re-used elsewhere). Our response, however, will use our ``MicroscopeIdentifySchema`` class. This means that the *response* of our ``identify`` and ``rename`` views will be identically formatted. @@ -137,9 +144,10 @@ Our ``rename`` view class may now look like: class ExampleRenameView(View): # Format our returned object using MicroscopeIdentifySchema - @marshal_with(MicroscopeIdentifySchema()) + schema = MicroscopeIdentifySchema() # Expect a request parameter called "name", which is a string. Pass to argument "args". - @use_args({"name": fields.String(required=True, example="My Example Microscope")}) + args = {"name": fields.String(required=True, example="My Example Microscope")} + def post(self, args): # Look for our "name" parameter in the request arguments new_name = args.get("name") @@ -151,7 +159,7 @@ Our ``rename`` view class may now look like: rename(microscope, new_name) # Return our microscope object, - # let @marshal_with handle formatting the output + # let schema handle formatting the output return microscope diff --git a/docs/source/extensions/properties.rst b/docs/source/extensions/properties.rst index cb23761e..3a86e2d5 100644 --- a/docs/source/extensions/properties.rst +++ b/docs/source/extensions/properties.rst @@ -21,7 +21,8 @@ In order to register a view as a Thing property, we use the ``PropertyView`` cla # Since we only have a GET method here, it'll register as a read-only property class ExampleIdentifyView(PropertyView): # Format our returned object using MicroscopeIdentifySchema - @marshal_with(MicroscopeIdentifySchema()) + schema = MicroscopeIdentifySchema() + def get(self): """ Show identifying information about the current microscope object @@ -30,12 +31,9 @@ In order to register a view as a Thing property, we use the ``PropertyView`` cla microscope = find_component("org.openflexure.microscope") # Return our microscope object, - # let @marshal_with handle formatting the output + # let schemah handle formatting the output return microscope -This decorator serves only to add documentation: The view will be tagged with ``property`` in Swagger documentation, and will appear as a property in the microscopes Thing Description. - -However, the additional ``@PropertySchema`` decorator allows for simplified argument parsing and object marshaling. Property schema --------------- @@ -71,16 +69,17 @@ This request would update the property, such that a GET request would *now* retu "job": "Landscape gardener" } -The ``@PropertySchema`` decorator can be applied to your view *class*, and essentially acts as shorthand for applying ``@marshal_with`` to all methods, and ``@use_args`` to all POST and PUT methods (or ``@use_body`` if only a single field is given). +In Property Views the ``schema`` class attribute acts as the schema for both marshalling responses *and* parsing arguments. This is because property requests and responses should be identically formatted. -We will implement the ``@PropertySchema`` decorator in our ``ExampleRenameView`` view from our previous example: +We will implement the ``schema`` attribute in our ``ExampleRenameView`` view from our previous example: .. code-block:: python - # We can use a single schema for all methods if the input and output will be formatted identically - # Eg. Here, we will always expect a "name" string argument, and always return a "name" string attribute - @PropertySchema({"name": fields.String(required=True, example="My Example Microscope")}) + # We can use a single schema as the input and output will be formatted identically + # Eg. We always expect a "name" string argument, and always return a "name" string attribute class ExampleRenameView(PropertyView): + schema = {"name": fields.String(required=True, example="My Example Microscope")} + def get(self): """ Show the current microscope name @@ -104,7 +103,7 @@ We will implement the ``@PropertySchema`` decorator in our ``ExampleRenameView`` rename(microscope, new_name) # Return our microscope object, - # let @marshal_with handle formatting the output + # let schema handle formatting the output return microscope Complete example diff --git a/docs/source/extensions/views.rst b/docs/source/extensions/views.rst index ea35ddd2..2b8912b0 100644 --- a/docs/source/extensions/views.rst +++ b/docs/source/extensions/views.rst @@ -18,13 +18,13 @@ In this case, our extension will have two new API views at `/identify` and `/ren Request arguments +++++++++++++++++ -For POST and PUT requests, data usually needs to be provided to the view in order to perform its function. In this example, our ``rename`` view requires a new microscope name to be passed. We make use of the ``@use_body`` decorator to provide this functionality. +For POST and PUT requests, data usually needs to be provided to the view in order to perform its function. In this example, our ``rename`` view requires a new microscope name to be passed. We make use of the ``args`` class attribute to provide this functionality. -``@use_body`` defines the type of data expected in the request body. In this example, we use ``String`` type data. The arguments of ``fields.String`` allow us to provide additional information, such as the parameter being required, and example values to appear in API documentation. +``args`` defines the type of data expected in the request body. In this example, we use ``String`` type data. The arguments of ``fields.String`` allow us to provide additional information, such as the parameter being required, and example values to appear in API documentation. Adding additional fields, and the meaning of the field types, will be discussed further in the next section. -When a POST request is made to our API view, the ``@use_body`` converts the body of the request into a ``String``, and passes it as a positional argument to our ``post`` function. +When a POST request is made to our API view, the server converts the body of the request into a ``String``, and passes it as a positional argument to our ``post`` function. Swagger documentation +++++++++++++++++++++ diff --git a/docs/source/microscope.rst b/docs/source/microscope.rst index 60933839..55a96cdb 100644 --- a/docs/source/microscope.rst +++ b/docs/source/microscope.rst @@ -7,5 +7,5 @@ The main microscope class handles microscope settings, passing these between the :maxdepth: 2 :caption: Contents: -.. automodule:: openflexure_microscope - :members: \ No newline at end of file +.. automodule:: openflexure_microscope.microscope + :members: \ No newline at end of file From 3100530edbb1a44d6d9608f949b60e047ca92a92 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 29 Jun 2020 17:49:01 +0100 Subject: [PATCH 17/30] Fixed leftover decorator references --- .../source/extensions/example_extension/03_marshaling_data.py | 4 ++-- docs/source/extensions/marshaling.rst | 2 +- docs/source/extensions/views.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/extensions/example_extension/03_marshaling_data.py b/docs/source/extensions/example_extension/03_marshaling_data.py index a686a775..4015b9fc 100644 --- a/docs/source/extensions/example_extension/03_marshaling_data.py +++ b/docs/source/extensions/example_extension/03_marshaling_data.py @@ -38,7 +38,7 @@ class ExampleIdentifyView(View): microscope = find_component("org.openflexure.microscope") # Return our microscope object, - # let @marshal_with handle formatting the output + # let schema handle formatting the output return microscope @@ -59,7 +59,7 @@ class ExampleRenameView(View): rename(microscope, new_name) # Return our microscope object, - # let @marshal_with handle formatting the output + # let schema handle formatting the output return microscope diff --git a/docs/source/extensions/marshaling.rst b/docs/source/extensions/marshaling.rst index 94f9ce1b..9ee8f384 100644 --- a/docs/source/extensions/marshaling.rst +++ b/docs/source/extensions/marshaling.rst @@ -45,7 +45,7 @@ For example, if you are creating an API route, in which you expect parameters `` age = fields.Integer(required=True) job = fields.String(required=False, missing="Unknown") -To inform your POST method to expect these arguments, use the ``@use_args`` decorator: +To inform your POST method to expect these arguments, use the ``args`` class attribute: .. code-block:: python diff --git a/docs/source/extensions/views.rst b/docs/source/extensions/views.rst index 2b8912b0..a27ec4b2 100644 --- a/docs/source/extensions/views.rst +++ b/docs/source/extensions/views.rst @@ -31,4 +31,4 @@ Swagger documentation At this point, it is useful to introduce the automatically generated Swagger documentation. From any web browser, go to ``http://microscope.local/api/v2/swagger-ui`` (or replace ``microscope.local`` with your microscope's IP address if ``microscope.local`` doesn't work for your system). -This page uses `SwaggerUI `_ to provide visual, interactive API documentation. Find your extensions URL in the documentation under the ``extensions`` group. Basic documentation about the parameters required for your POST method should be visible, as well as an interactive example filled out with the example request given in ``@use_body``. +This page uses `SwaggerUI `_ to provide visual, interactive API documentation. Find your extensions URL in the documentation under the ``extensions`` group. Basic documentation about the parameters required for your POST method should be visible, as well as an interactive example filled out with the example request given in the view ``schema``. From 4cb3ae03ed1325f945193ebcf9c79de93b05e735 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Fri, 3 Jul 2020 18:22:35 +0100 Subject: [PATCH 18/30] Switched to testing LabThings restructure --- poetry.lock | 22 +++------------------- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5487a171..b5cf53cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -104,17 +104,6 @@ numpy = ">=1.17,<2.0" all = ["opencv-python-headless (>=4.1,<5.0)", "scipy (>=1.4,<2.0)"] correlation = ["opencv-python-headless (>=4.1,<5.0)", "scipy (>=1.4,<2.0)"] -[[package]] -category = "main" -description = "Pure Python CBOR (de)serializer with extensive tag support" -name = "cbor2" -optional = false -python-versions = "*" -version = "5.1.0" - -[package.extras] -test = ["pytest", "pytest-cov"] - [[package]] category = "dev" description = "Python package for providing Mozilla's CA Bundle." @@ -326,12 +315,11 @@ description = "" name = "labthings" optional = false python-versions = "^3.6" -version = "0.7.0-beta.0" +version = "0.7.0" [package.dependencies] Flask = "^1.1.1" apispec = "^3.2.0" -cbor2 = "^5.1.0" flask-cors = "^3.0.8" gevent = ">=1.4,<21.0" gevent-websocket = "^0.10.1" @@ -340,10 +328,9 @@ webargs = "^6.0.0" zeroconf = ">=0.24.5,<0.28.0" [package.source] -reference = "d7889d07dee0344b112a606caabc836e69bac63f" +reference = "b718258fe219fbdca7f82d3987c0680f7157e738" type = "git" url = "https://github.com/labthings/python-labthings.git" - [[package]] category = "dev" description = "A fast and thorough lazy object proxy." @@ -846,7 +833,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] rpi = ["RPi.GPIO"] [metadata] -content-hash = "94e35da71817800c617a54e54dc937df62953fa95caa2e2e05f69b1163582c4c" +content-hash = "fff0dba8c9a8ffe0a496148d68bedf1e12802e6700ab3e5f82788e10891eb774" python-versions = "^3.6" [metadata.files] @@ -882,9 +869,6 @@ camera-stage-mapping = [ {file = "camera-stage-mapping-0.1.2.tar.gz", hash = "sha256:0d1a6f93b3a60054bc8057359464adbb40488b4daeb93c672b90e4fe525e7b69"}, {file = "camera_stage_mapping-0.1.2-py3-none-any.whl", hash = "sha256:3f3da3e95792e811e6808173c368be67ebb00e6f7d7e37d68cb36fa765d13da9"}, ] -cbor2 = [ - {file = "cbor2-5.1.0.tar.gz", hash = "sha256:43ce11e8c2fe4971d386d1a60cf83bfa0a4a667b97668ba76acbf5e6398821aa"}, -] certifi = [ {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, diff --git a/pyproject.toml b/pyproject.toml index b4474f84..425902de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ opencv-python-headless = [ {version = "4.1.0.25", python = "~3.7"}, # PiWheels build for RPi running Py37 {version = "4.2.0.34", python = "^3.8"} # Latest for Py38 systems ] -labthings = {git = "https://github.com/labthings/python-labthings.git", rev = "simpler-spec"} +labthings = {git = "https://github.com/labthings/python-labthings.git", rev = "100-restructure"} pynpm = "^0.1.2" sangaboard = "^0.2" expiringdict = "^1.2.1" From 646645cc1d481fb45fdd984a2d9557b1214c6a8f Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 5 Jul 2020 16:55:10 +0100 Subject: [PATCH 19/30] Improved tab contrast --- .../api/static/src/assets/less/theme.less | 18 ++++++++++++------ .../components/genericComponents/tabIcon.vue | 4 +++- .../viewComponents/settingsDisplay.vue | 10 +++++++++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/openflexure_microscope/api/static/src/assets/less/theme.less b/openflexure_microscope/api/static/src/assets/less/theme.less index 17d014a2..a85a599c 100644 --- a/openflexure_microscope/api/static/src/assets/less/theme.less +++ b/openflexure_microscope/api/static/src/assets/less/theme.less @@ -59,6 +59,12 @@ text-rendering: optimizeLegibility; } +// +// Decorations +// +@small-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); +@big-shadow: 0 14px 25px rgba(0, 0, 0, 0.16); + // // Container // @@ -148,6 +154,7 @@ // Paper // @paper-border-radius: 4px; +@button-border-radius: 3px; /* ======================================================================== @@ -187,9 +194,8 @@ .uk-card { border-radius: @paper-border-radius; - border: 1px solid rgba(180, 180, 180, 0.25); - box-shadow: none; - -webkit-box-shabox-shadow: none; + //border: 1px solid rgba(180, 180, 180, 0.25); + box-shadow: @small-shadow; } .uk-card-media-top img { @@ -228,7 +234,7 @@ */ .uk-modal-dialog { border-radius: @paper-border-radius; - box-shadow: 0 14px 25px rgba(0, 0, 0, 0.16); + box-shadow: @big-shadow; transition: 0.3s ease !important; } @@ -245,7 +251,7 @@ */ .uk-notification-message { border-radius: @paper-border-radius; - box-shadow: 0 14px 25px rgba(0, 0, 0, 0.16); + box-shadow: @big-shadow; } .uk-notification-message-danger { @@ -287,7 +293,7 @@ a:hover { * Buttons */ .uk-button { - border-radius: 2px; + border-radius: @button-border-radius; padding: 0 8px; } diff --git a/openflexure_microscope/api/static/src/components/genericComponents/tabIcon.vue b/openflexure_microscope/api/static/src/components/genericComponents/tabIcon.vue index 15dfd38f..da770a4d 100644 --- a/openflexure_microscope/api/static/src/components/genericComponents/tabIcon.vue +++ b/openflexure_microscope/api/static/src/components/genericComponents/tabIcon.vue @@ -93,7 +93,9 @@ export default { @import "../../assets/less/theme.less"; .tabicon-active { - color: @global-primary-background !important; + color: #fff !important; + background-color: @global-primary-background !important; + box-shadow: @small-shadow; } .hook-inverse() { diff --git a/openflexure_microscope/api/static/src/components/viewComponents/settingsDisplay.vue b/openflexure_microscope/api/static/src/components/viewComponents/settingsDisplay.vue index 303c5cf2..c0242cc5 100644 --- a/openflexure_microscope/api/static/src/components/viewComponents/settingsDisplay.vue +++ b/openflexure_microscope/api/static/src/components/viewComponents/settingsDisplay.vue @@ -153,7 +153,10 @@ export default { }; - From 3814a71a0add4e83ceaafcb272d46be1544fef86 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 5 Jul 2020 18:19:44 +0100 Subject: [PATCH 20/30] Simplified keyboard shortcuts --- .../api/static/package-lock.json | 5 + .../api/static/package.json | 3 +- openflexure_microscope/api/static/src/App.vue | 185 ++++++++---------- 3 files changed, 94 insertions(+), 99 deletions(-) diff --git a/openflexure_microscope/api/static/package-lock.json b/openflexure_microscope/api/static/package-lock.json index 8a0083a6..88511dc4 100644 --- a/openflexure_microscope/api/static/package-lock.json +++ b/openflexure_microscope/api/static/package-lock.json @@ -9210,6 +9210,11 @@ "minimist": "^1.2.5" } }, + "mousetrap": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", + "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/openflexure_microscope/api/static/package.json b/openflexure_microscope/api/static/package.json index bcdca6a0..055d4b50 100644 --- a/openflexure_microscope/api/static/package.json +++ b/openflexure_microscope/api/static/package.json @@ -13,7 +13,8 @@ "serve": "vue-cli-service serve --mode development" }, "dependencies": { - "material-design-icons": "^3.0.1" + "material-design-icons": "^3.0.1", + "mousetrap": "^1.6.5" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.12.1", diff --git a/openflexure_microscope/api/static/src/App.vue b/openflexure_microscope/api/static/src/App.vue index 0fc0f38c..a391b778 100644 --- a/openflexure_microscope/api/static/src/App.vue +++ b/openflexure_microscope/api/static/src/App.vue @@ -1,9 +1,5 @@