From 7209700343406f79bcbafcd1f6dea7f159342508 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 18 Feb 2020 11:40:14 +0000 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 07/10] 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 08/10] 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 09/10] 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 10/10] 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": [ {