From 19af98736d80731dd7e010fbf5ca3abf4e2e4771 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 11:55:39 +0000 Subject: [PATCH 1/7] Draft of new semantics --- openflexure_microscope/api/microscope.py | 29 --- openflexure_microscope/api/v2/views/state.py | 4 +- openflexure_microscope/camera/base.py | 35 +++- openflexure_microscope/camera/mock.py | 22 ++- openflexure_microscope/camera/pi.py | 56 ++++-- openflexure_microscope/config.py | 35 ++-- openflexure_microscope/microscope.py | 176 ++++++++++-------- .../microscope_configuration.default.json | 17 ++ .../microscope_settings.default.json | 6 +- openflexure_microscope/paths.py | 11 +- openflexure_microscope/stage/base.py | 16 +- openflexure_microscope/stage/mock.py | 18 +- openflexure_microscope/stage/sanga.py | 14 +- 13 files changed, 252 insertions(+), 187 deletions(-) create mode 100644 openflexure_microscope/microscope_configuration.default.json diff --git a/openflexure_microscope/api/microscope.py b/openflexure_microscope/api/microscope.py index 04053892..2f534b49 100644 --- a/openflexure_microscope/api/microscope.py +++ b/openflexure_microscope/api/microscope.py @@ -3,38 +3,9 @@ from openflexure_microscope.camera.capture import build_captures_from_exif import logging -# Import device modules -# NB this will eventually be handled by the RC file, so you can choose what device -# class should be attached. -try: - from openflexure_microscope.camera.pi import PiCameraStreamer -except ImportError: - logging.warning("Unable to import PiCameraStreamer") -from openflexure_microscope.camera.mock import MockStreamer - -from openflexure_microscope.stage.sanga import SangaStage -from openflexure_microscope.stage.mock import MockStage default_microscope = Microscope() -# Initialise camera -logging.debug("Creating camera object...") -try: - api_camera = PiCameraStreamer() -except Exception as e: - logging.error(e) - logging.warning("No valid camera hardware found. Falling back to mock camera!") - api_camera = MockStreamer() - -# Initialise stage -logging.debug("Creating stage object...") - -api_stage = MockStage() - -# Attach devices to microscope -logging.debug("Attaching devices to microscope...") -default_microscope.attach(api_camera, api_stage) - # Restore loaded capture array to camera object logging.debug("Restoring captures...") default_microscope.camera.images = build_captures_from_exif( diff --git a/openflexure_microscope/api/v2/views/state.py b/openflexure_microscope/api/v2/views/state.py index aa59ffdf..34637754 100644 --- a/openflexure_microscope/api/v2/views/state.py +++ b/openflexure_microscope/api/v2/views/state.py @@ -30,7 +30,7 @@ class SettingsProperty(View): logging.debug("Updating settings from PUT request:") logging.debug(payload.json) - microscope.apply_settings(payload.json) + microscope.update_settings(payload.json) microscope.save_settings() return self.get() @@ -65,7 +65,7 @@ class NestedSettingsProperty(View): dictionary = create_from_path(keys) set_by_path(dictionary, keys, payload.json) - microscope.apply_settings(dictionary) + microscope.update_settings(dictionary) microscope.save_settings() return self.get(route) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index e6395e1f..e823cfd0 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -121,7 +121,8 @@ class BaseCamera(metaclass=ABCMeta): self.stream_timeout = 20 self.stream_timeout_enabled = False - self.status = {"board": None} + self.stream_active = False + self.record_active = False self.paths = {"default": BASE_CAPTURE_PATH, "temp": TEMP_CAPTURE_PATH} @@ -129,8 +130,24 @@ class BaseCamera(metaclass=ABCMeta): self.images = [] self.videos = [] + @property @abstractmethod - def apply_settings(self, config: dict): + def configuration(self): + """The current camera configuration.""" + pass + + @property + @abstractmethod + def state(self): + """The current read-only camera state.""" + pass + + @property + def settings(self): + return self.read_settings() + + @abstractmethod + def update_settings(self, config: dict): """Update settings from a config dictionary""" with self.lock: # Apply valid config params to camera object @@ -187,7 +204,7 @@ class BaseCamera(metaclass=ABCMeta): self.last_access = time.time() self.stop = False - if not self.status["stream_active"]: + if not self.stream_active: # start background frame thread self.thread = threading.Thread(target=self._thread) self.thread.daemon = True @@ -207,12 +224,12 @@ class BaseCamera(metaclass=ABCMeta): logging.debug("Stopping worker thread") timeout_time = time.time() + timeout - if self.status["stream_active"]: + if self.stream_active: self.stop = True self.thread.join() # Wait for stream thread to exit logging.debug("Waiting for stream thread to exit.") - while self.status["stream_active"]: + while self.stream_active: if time.time() > timeout_time: logging.debug("Timeout waiting for worker thread close.") raise TimeoutError("Timeout waiting for worker thread close.") @@ -352,7 +369,7 @@ class BaseCamera(metaclass=ABCMeta): self.frames_iterator = self.frames() logging.debug("Entering worker thread.") - self.status["stream_active"] = True + self.stream_active = True for frame in self.frames_iterator: self.frame = frame @@ -365,9 +382,7 @@ class BaseCamera(metaclass=ABCMeta): and ( # If using timeout time.time() - self.last_access > self.stream_timeout ) - and not self.status[ # And timeout time - "preview_active" - ] # And GPU preview is not active + and not self.preview_active # And GPU preview is not active ): self.frames_iterator.close() break @@ -383,4 +398,4 @@ class BaseCamera(metaclass=ABCMeta): logging.debug("BaseCamera worker thread exiting...") # Set stream_activate state - self.status["stream_active"] = False + self.stream_active = False diff --git a/openflexure_microscope/camera/mock.py b/openflexure_microscope/camera/mock.py index abffb221..322bae58 100644 --- a/openflexure_microscope/camera/mock.py +++ b/openflexure_microscope/camera/mock.py @@ -26,17 +26,13 @@ We override the logging settings in api.app by setting a level for PIL here. pil_logger = logging.getLogger("PIL") pil_logger.setLevel(logging.INFO) + # MAIN CLASS -class MockStreamer(BaseCamera): +class MissingCamera(BaseCamera): def __init__(self): # Run BaseCamera init BaseCamera.__init__(self) - # Store state of PiCameraStreamer - self.status.update( - {"stream_active": False, "record_active": False, "board": None} - ) - # Update config properties self.image_resolution = (1312, 976) self.stream_resolution = (640, 480) @@ -71,6 +67,16 @@ class MockStreamer(BaseCamera): image.save(self.stream, format="JPEG") + @property + def configuration(self): + """The current camera configuration.""" + return {} + + @property + def state(self): + """The current read-only camera state.""" + return {} + def initialisation(self): """Run any initialisation code when the frame iterator starts.""" pass @@ -101,7 +107,7 @@ class MockStreamer(BaseCamera): return conf_dict - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """ Write a config dictionary to the PiCameraStreamer config. @@ -117,7 +123,7 @@ class MockStreamer(BaseCamera): with self.lock: # Apply valid config params to camera object - if not self.status["record_active"]: # If not recording a video + if not self.record_active: # If not recording a video for key, value in config.items(): # For each provided setting if hasattr(self, key): diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 5cafb045..bbd04e44 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -47,6 +47,7 @@ from .base import BaseCamera, CaptureObject from .set_picamera_gain import set_analog_gain, set_digital_gain from openflexure_microscope.paths import settings_file_path +from openflexure_microscope.utilities import serialise_array_b64 # MAIN CLASS @@ -85,14 +86,7 @@ class PiCameraStreamer(BaseCamera): ) #: :py:class:`picamera.PiCamera`: Picamera object # Store status of PiCameraStreamer - self.status.update( - { - "stream_active": False, - "record_active": False, - "preview_active": False, - "board": f"picamera_{self.camera.revision}", - } - ) + self.preview_active = False # Reset variable states self.set_zoom(1.0) @@ -116,15 +110,37 @@ class PiCameraStreamer(BaseCamera): "picamera_lst.npy" ) #: str: Path of .npy lens shading table file - # Update board identifier - self.status.update({}) - # Create an empty stream self.stream = io.BytesIO() # Start streaming self.start_worker() + @property + def configuration(self): + """The current camera configuration.""" + config = { + "board": self.camera.revision, + } + + if self.read_lens_shading_table(): + b64_string, dtype, shape = serialise_array_b64(self.read_lens_shading_table()) + + config.update({ + "lens_shading_table": { + "b64_string": b64_string, + "dtype": dtype, + "shape": shape, + } + }) + + return config + + @property + def state(self): + """The current read-only camera state.""" + return {} + def initialisation(self): """Run any initialisation code when the frame iterator starts.""" pass @@ -176,7 +192,7 @@ class PiCameraStreamer(BaseCamera): logging.info("Saving picamera_lst to {}".format(self.picamera_lst_path)) self.save_lens_shading_table() - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """ Write a config dictionary to the PiCameraStreamer config. @@ -194,10 +210,10 @@ class PiCameraStreamer(BaseCamera): with self.lock: # Apply valid config params to Picamera object - if not self.status["record_active"]: # If not recording a video + if not self.record_active: # If not recording a video # Pause stream while changing settings - if self.status["stream_active"]: # If stream is active + if self.stream_active: # If stream is active logging.info("Pausing stream to update config.") self.stop_stream_recording() # Pause stream paused_stream = True # Remember to unpause stream when done @@ -365,7 +381,7 @@ class PiCameraStreamer(BaseCamera): self.camera.preview.window = window if fullscreen: self.camera.preview.fullscreen = fullscreen - self.status["preview_active"] = True + self.preview_active = True except picamera.exc.PiCameraMMALError as e: logging.error( "Suppressed a MMALError in start_preview. Exception: {}".format(e) @@ -380,7 +396,7 @@ class PiCameraStreamer(BaseCamera): def stop_preview(self): """Stop the on board GPU camera preview.""" self.camera.stop_preview() - self.status["preview_active"] = False + self.preview_active = False def start_recording(self, output, fmt: str = "h264", quality: int = 15): """Start recording. @@ -398,7 +414,7 @@ class PiCameraStreamer(BaseCamera): """ with self.lock: # Start recording method only if a current recording is not running - if not self.status["record_active"]: + if not self.record_active: # Start the camera video recording on port 2 logging.info("Recording to {}".format(output)) @@ -412,7 +428,7 @@ class PiCameraStreamer(BaseCamera): ) # Update status dictionary - self.status["record_active"] = True + self.record_active = True return output @@ -432,7 +448,7 @@ class PiCameraStreamer(BaseCamera): logging.info("Recording stopped") # Update status dictionary - self.status["record_active"] = False + self.record_active = False def stop_stream_recording( self, splitter_port: int = 1, resolution: Tuple[int, int] = None @@ -500,7 +516,7 @@ class PiCameraStreamer(BaseCamera): self.camera.resolution = resolution # If the stream should be active - if self.status["stream_active"]: + if self.stream_active: try: # Start recording on stream port self.camera.start_recording( diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index 8e613574..cfc458a1 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -7,7 +7,7 @@ from uuid import UUID import numpy as np from fractions import Fraction -from .paths import CONFIG_FILE_PATH, DEFAULT_CONFIG_FILE_PATH +from .paths import SETTINGS_FILE_PATH, DEFAULT_SETTINGS_FILE_PATH, CONFIGURATION_FILE_PATH, DEFAULT_CONFIGURATION_FILE_PATH class OpenflexureSettingsFile: @@ -19,21 +19,21 @@ class OpenflexureSettingsFile: expand (bool): Expand paths to valid auxillary config files. """ - def __init__(self, config_path: str = None): - global DEFAULT_CONFIG, CONFIG_FILE_PATH + def __init__(self, path: str, defaults: dict = {}): + global DEFAULT_SETTINGS # Set arguments - self.config_path = config_path or CONFIG_FILE_PATH + self.path = path # Initialise basic config file with defaults if it doesn't exist - initialise_file(self.config_path, populate=DEFAULT_CONFIG) + initialise_file(self.path, populate=defaults) def load(self) -> dict: """ Loads settings from a file on-disk. """ # Unexpanded config dictionary (used at load/save time) - loaded_config = load_json_file(self.config_path) + loaded_config = load_json_file(self.path) logging.debug("Reading settings from disk") return loaded_config @@ -50,11 +50,11 @@ class OpenflexureSettingsFile: save_settings = config if backup: - if os.path.isfile(self.config_path): - shutil.copyfile(self.config_path, self.config_path + ".bk") + if os.path.isfile(self.path): + shutil.copyfile(self.path, self.path + ".bk") logging.debug("Saving settings dictionary to disk") - save_json_file(self.config_path, save_settings) + save_json_file(self.path, save_settings) def merge(self, config: dict) -> dict: """ @@ -178,10 +178,17 @@ def initialise_file(config_path, populate: str = "{}\n"): outfile.write(populate) -# Load the default config -with open(DEFAULT_CONFIG_FILE_PATH, "r") as default_rc: - DEFAULT_CONFIG = default_rc.read() - +# Load the default settings +with open(DEFAULT_SETTINGS_FILE_PATH, "r") as default_settings: + DEFAULT_SETTINGS = default_settings.read() #: Default user settings object -user_settings = OpenflexureSettingsFile(config_path=CONFIG_FILE_PATH) +user_settings = OpenflexureSettingsFile(path=SETTINGS_FILE_PATH, defaults=DEFAULT_SETTINGS) + + +# Load the default configuration +with open(DEFAULT_CONFIGURATION_FILE_PATH, "r") as default_configuration: + DEFAULT_CONFIGURATION = default_configuration.read() + +#: Default user settings object +user_configuration = OpenflexureSettingsFile(path=CONFIGURATION_FILE_PATH, defaults=DEFAULT_CONFIGURATION) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 31ea505c..2f52c6d9 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -6,15 +6,20 @@ import logging import pkg_resources import uuid -from openflexure_microscope.stage.base import BaseStage -from openflexure_microscope.stage.mock import MockStage -from openflexure_microscope.camera.base import BaseCamera -from openflexure_microscope.camera.mock import MockStreamer +from openflexure_microscope.stage.mock import MissingStage +from openflexure_microscope.camera.mock import MissingCamera +from openflexure_microscope.stage.sanga import SangaStage +try: + from openflexure_microscope.camera.pi import PiCameraStreamer +except ImportError: + logging.warning("Unable to import PiCameraStreamer") +from openflexure_microscope.camera.mock import MissingCamera from openflexure_microscope.utilities import serialise_array_b64 -from openflexure_microscope.config import user_settings +from openflexure_microscope.config import user_settings, user_configuration from labthings.core.lock import CompositeLock +from labthings.core.utilities import rupdate class Microscope: @@ -24,20 +29,32 @@ class Microscope: The camera and stage objects may already be initialised, and can be passed as arguments. """ - def __init__(self): - # Initial attributes - self.id = uuid.uuid4() #: Microscope UUID - self.name = self.id #: Microscope name (modifiable) - self.fov = [0, 0] #: Microscope field-of-view in stage motor steps - self.camera = None #: Currently connected camera object - self.stage = None #: Currently connected stage object + def __init__(self, settings = user_settings, configuration = user_configuration): + # Store settings and configuration files + self.settings_file = settings + self.configuration_file = configuration # Initialise with an empty composite lock #: :py:class:`labthings.lock.CompositeLock`: Composite lock for locking both camera and stage self.lock = CompositeLock([]) + self.camera = None #: Currently connected camera object + self.stage = None #: Currently connected stage object + self.setup(self.configuration_file.load()) # Attach components + # Apply settings loaded from file - self.apply_settings(user_settings.load()) + self.update_settings(self.settings_file.load()) + + # Initial attributes + if self.configuration_file.load().get("id"): + self.id = configuration.get("id") + else: + self.id = uuid.uuid4() + self.configuration_file.save({ + "id": self.id + }) + + self.name = self.id def __enter__(self): """Create microscope on context enter.""" @@ -56,58 +73,49 @@ class Microscope: self.stage.close() logging.info("Closed {}".format(self)) - def attach(self, camera: BaseCamera, stage: BaseStage): + def setup(self, configuration): """ - Retroactively attaches a camera and stage to the microscope object. - - Allows the microscope to be created as a "dummy", with hardware communications - opened at a later time. - - Args: - camera (:py:class:`openflexure_microscope.camera.base.BaseCamera`): camera object - stage (:py:class:`openflexure_microscope.stage.base.BaseStage`): stage object + Attach microscope components based on initially passed configuration file """ - settings_full = self.read_settings() + ### Detector + if configuration.get("detector"): + detector_type = configuration["detector"].get("type") + if detector_type == "PiCamera" or detector_type == "PiCameraStreamer": + try: + self.camera = PiCameraStreamer() + except Exception as e: + logging.error(e) + logging.warning("No compatible camera hardware found.") - logging.debug("Attaching camera...") - self.camera = ( - camera - ) #: :py:class:`openflexure_microscope.camera.base.BaseCamera`: Picamera object + ### Stage + if configuration.get("stage"): + stage_type = configuration["stage"].get("type") + stage_port = configuration["stage"].get("port") + if stage_type == "SangaBoard" or detector_type == "SangaStage": + try: + self.stage = SangaStage(port=stage_port) + except Exception as e: + logging.error(e) + logging.warning("No compatible stage hardware found.") + + ### Fallbacks if not self.camera: - logging.info("No camera attached.") - else: - logging.info("Attached camera {}".format(camera)) - - if hasattr(self.camera, "lock"): # If camera has a lock - logging.info("Attaching {} to composite lock.".format(self.camera.lock)) - # Add the lock to the microscope composite lock - self.lock.locks.append(self.camera.lock) - - logging.debug("Attaching stage...") - self.stage = ( - stage - ) #: :py:class:`openflexure_microscope.stage.base.BaseStage`: OpenFlexure stage object + self.camera = MissingCamera() if not self.stage: - logging.info("No stage attached.") - else: - logging.info("Attached stage {}".format(stage)) + self.stage = MissingStage() - if hasattr(self.stage, "lock"): # If stage object has a lock - logging.info( - "Attaching lock {} to composite lock.".format(self.stage.lock) - ) - # Add the lock to the microscope composite lock - self.lock.locks.append(self.stage.lock) - - logging.info("Reapplying settings to newly attached devices") - self.apply_settings(settings_full) + ### Locks + if hasattr(self.camera, "lock"): + self.lock.locks.append(self.camera.lock) + if hasattr(self.stage, "lock"): + self.lock.locks.append(self.stage.lock) def has_real_stage(self) -> bool: """ Check if a real (non-mock) stage is currently attached. """ - if hasattr(self, "stage") and not isinstance(self.stage, MockStage): + if hasattr(self, "stage") and not isinstance(self.stage, MissingStage): return True else: return False @@ -116,27 +124,26 @@ class Microscope: """ Check if a real (non-mock) camera is currently attached. """ - if hasattr(self, "camera") and not isinstance(self.camera, MockStreamer): + if hasattr(self, "camera") and not isinstance(self.camera, MissingCamera): return True else: return False # Create unified status @property - def status(self): + def state(self): """Dictionary of the basic microscope status. Return: dict: Dictionary containing complete microscope status """ state = { - "camera": self.camera.status, - "stage": self.stage.status, - "version": pkg_resources.get_distribution("openflexure_microscope").version, + "camera": self.camera.state, + "stage": self.stage.state, } return state - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """ Applies a settings dictionary to the microscope. Missing parameters will be left untouched. """ @@ -144,21 +151,19 @@ class Microscope: # If attached to a camera if ("camera_settings" in config) and self.camera: - self.camera.apply_settings(config["camera_settings"]) + self.camera.update_settings(config["camera_settings"]) # If attached to a stage if ("stage_settings" in config) and self.stage: - self.stage.apply_settings(config["stage_settings"]) + self.stage.update_settings(config["stage_settings"]) # Todo: tidy up with some loopy goodness - if "id" in config: - self.id = config["id"] if "name" in config: self.name = config["name"] if "fov" in config: self.fov = config["fov"] - def read_settings(self, json_safe=False): + def read_settings(self, full: bool=True): """ Get an updated settings dictionary. @@ -169,7 +174,7 @@ class Microscope: don't get removed from the settings file. """ - settings_current = {"id": self.id, "name": self.name, "fov": self.fov} + settings_current = {"name": self.name, "fov": self.fov} # If attached to a camera if self.camera: @@ -181,9 +186,12 @@ class Microscope: settings_current_stage = self.stage.read_settings() settings_current["stage_settings"] = settings_current_stage - settings_full = user_settings.merge(settings_current) + settings_full = self.settings_file.merge(settings_current) - return settings_full + if full: + return settings_full + else: + return settings_current def save_settings(self): """ @@ -196,7 +204,7 @@ class Microscope: self.camera.save_settings() if self.stage: self.stage.save_settings() - user_settings.save(current_config, backup=True) + self.settings_file.save(current_config, backup=True) @property def metadata(self): @@ -204,10 +212,10 @@ class Microscope: Microscope system metadata, to be applied to basically all captures """ system_metadata = { - "microscope_settings": self.read_settings(), - "microscope_state": self.status, - "microscope_id": self.id, - "microscope_name": self.name, + "@ID": self.id, + "settings": self.read_settings(full=False), + "state": self.state, + "configuration": self.configuration } # Store an encoded copy of the PiCamera lens shading table, if it exists @@ -217,10 +225,32 @@ class Microscope: b64_string, dtype, shape = serialise_array_b64(lst_arr) - system_metadata["lens_shading_table"] = { + system_metadata["configuration"]["detector"]["lens_shading_table"] = { "b64_string": b64_string, "dtype": dtype, "shape": shape, } return system_metadata + + @property + def configuration(self): + initial_configuration = self.configuration_file.load() + + current_configuration = { + "@application": { + "name": "openflexure_microscope", + "version": pkg_resources.get_distribution("openflexure_microscope").version + }, + "stage": { + "type": self.stage.__class__.__name__, + **self.stage.configuration + }, + "detector": { + "type": self.camera.__class__.__name__, + **self.camera.configuration + } + } + + initial_configuration.update(current_configuration) + return initial_configuration diff --git a/openflexure_microscope/microscope_configuration.default.json b/openflexure_microscope/microscope_configuration.default.json new file mode 100644 index 00000000..d7129bc8 --- /dev/null +++ b/openflexure_microscope/microscope_configuration.default.json @@ -0,0 +1,17 @@ +{ + "microscope": { + "stepsPerView": [4100, 3146] + }, + "detector": { + "type": "PiCamera" + }, + "stage": { + "type": "SangaStage", + "port": null + }, + "lightSource": { + "type": "LED" + }, + "objective": { + } +} \ No newline at end of file diff --git a/openflexure_microscope/microscope_settings.default.json b/openflexure_microscope/microscope_settings.default.json index bde62f47..fa0fcb28 100644 --- a/openflexure_microscope/microscope_settings.default.json +++ b/openflexure_microscope/microscope_settings.default.json @@ -1,7 +1,3 @@ { - "fov": [ - 4100, - 3146 - ], - "jpeg_quality": 75 + "jpeg_quality": 100 } \ No newline at end of file diff --git a/openflexure_microscope/paths.py b/openflexure_microscope/paths.py index a261d618..8fbf9b55 100644 --- a/openflexure_microscope/paths.py +++ b/openflexure_microscope/paths.py @@ -32,8 +32,9 @@ def logs_file_path(filename: str): HERE = os.path.abspath(os.path.dirname(__file__)) #: Path of default (first-run) microscope settings -DEFAULT_CONFIG_FILE_PATH = os.path.join(HERE, "microscope_settings.default.json") - +DEFAULT_SETTINGS_FILE_PATH = os.path.join(HERE, "microscope_settings.default.json") +#: Path of default (first-run) microscope configuration +DEFAULT_CONFIGURATION_FILE_PATH = os.path.join(HERE, "microscope_configuration.default.json") # BASE PATHS @@ -60,7 +61,9 @@ else: # SERVER PATHS -#: Path of microscope settings directory -CONFIG_FILE_PATH = settings_file_path("microscope_settings.json") +#: Path of microscope settings file +SETTINGS_FILE_PATH = settings_file_path("microscope_settings.json") +#: Path of microscope configuration file +CONFIGURATION_FILE_PATH = settings_file_path("microscope_configuration.json") #: Path of microscope extensions directory OPENFLEXURE_EXTENSIONS_PATH = extensions_file_path("microscope_extensions") diff --git a/openflexure_microscope/stage/base.py b/openflexure_microscope/stage/base.py index d5ef31f3..564ba1a6 100644 --- a/openflexure_microscope/stage/base.py +++ b/openflexure_microscope/stage/base.py @@ -14,7 +14,7 @@ class BaseStage(metaclass=ABCMeta): self.lock = StrictLock(timeout=5) @abstractmethod - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """Update settings from a config dictionary""" pass @@ -29,12 +29,14 @@ class BaseStage(metaclass=ABCMeta): @property @abstractmethod - def status(self): - """The general state dictionary of the board. - Should at least contain 'position', and 'board' keys. - Note: A None/Null value for 'board' will disable stage - movement in the OpenFlexure eV client software, - """ + def state(self): + """The general state dictionary of the board.""" + pass + + @property + @abstractmethod + def configuration(self): + """The general stage configuration.""" pass @property diff --git a/openflexure_microscope/stage/mock.py b/openflexure_microscope/stage/mock.py index e85fa43b..e4810f3c 100644 --- a/openflexure_microscope/stage/mock.py +++ b/openflexure_microscope/stage/mock.py @@ -7,7 +7,7 @@ import time import logging -class MockStage(BaseStage): +class MissingStage(BaseStage): def __init__(self, port=None, **kwargs): BaseStage.__init__(self) self._position = [0, 0, 0] @@ -17,19 +17,17 @@ class MockStage(BaseStage): self.axis_names = ["x", "y", "z"] # Assume all sangaboards are 3 axis @property - def status(self): + def state(self): """The general status dictionary of the board.""" - status = { - "position": self.position_map, - "board": None, - "firmware": None, - "version": None, - } + status = {"position": self.position_map} return status - def apply_settings(self, config: dict): - """Update settings from a config dictionary""" + @property + def configuration(self): + return {} + def update_settings(self, config: dict): + """Update settings from a config dictionary""" # Set backlash. Expects a dictionary with axis labels if "backlash" in config: # Construct backlash array diff --git a/openflexure_microscope/stage/sanga.py b/openflexure_microscope/stage/sanga.py index 2c4fa6d9..11ffbbc6 100644 --- a/openflexure_microscope/stage/sanga.py +++ b/openflexure_microscope/stage/sanga.py @@ -23,6 +23,7 @@ class SangaStage(BaseStage): """Class managing serial communications with the motors for an Openflexure stage""" BaseStage.__init__(self) + self.port = port self.board = Sangaboard(port, **kwargs) self._backlash = ( @@ -31,14 +32,17 @@ class SangaStage(BaseStage): self.axis_names = ["x", "y", "z"] # Assume all sangaboards are 3 axis @property - def status(self): + def state(self): """The general status dictionary of the board.""" - status = { - "position": self.position_map, + return {"position": self.position_map} + + @property + def configuration(self): + return { + "port": self.port, "board": self.board.board, "firmware": self.board.firmware, } - return status @property def n_axes(self): @@ -81,7 +85,7 @@ class SangaStage(BaseStage): else: self._backlash = np.array([int(blsh)] * self.n_axes, dtype=np.int) - def apply_settings(self, config: dict): + def update_settings(self, config: dict): """Update settings from a config dictionary""" # Set backlash. Expects a dictionary with axis labels From 5d65c62001ce7cd4717f5982f9aa9838d108d86a Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 15:36:46 +0000 Subject: [PATCH 2/7] Updated dependencies --- poetry.lock | 537 ++++++++----------------------------------------- pyproject.toml | 1 + 2 files changed, 82 insertions(+), 456 deletions(-) diff --git a/poetry.lock b/poetry.lock index 603d03f8..923babf7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,14 +14,6 @@ optional = false python-versions = ">=3.5" version = "3.2.0" -[package.extras] -dev = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"] -docs = ["marshmallow (>=2.19.2)", "pyyaml (5.2)", "sphinx (2.3.0)", "sphinx-issues (1.2.0)", "sphinx-rtd-theme (0.4.3)"] -lint = ["flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"] -tests = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock"] -validation = ["prance (>=0.11)"] -yaml = ["PyYAML (>=3.10)"] - [[package]] category = "dev" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." @@ -55,12 +47,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "19.3.0" -[package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] - [[package]] category = "dev" description = "Internationalization utilities" @@ -86,9 +72,6 @@ attrs = ">=17.4.0" click = ">=6.5" toml = ">=0.9.4" -[package.extras] -d = ["aiohttp (>=3.3.2)"] - [[package]] category = "dev" description = "Python package for providing Mozilla's CA Bundle." @@ -144,11 +127,6 @@ Werkzeug = ">=0.15" click = ">=5.1" itsdangerous = ">=0.24" -[package.extras] -dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] -docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] -dotenv = ["python-dotenv"] - [[package]] category = "main" description = "A Flask extension adding a decorator for CORS support" @@ -185,12 +163,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "4.3.21" -[package.extras] -pipfile = ["pipreqs", "requirementslib"] -pyproject = ["toml"] -requirements = ["pipreqs", "pip-api"] -xdg_home = ["appdirs (>=1.4.0)"] - [[package]] category = "main" description = "Various helpers to pass data to untrusted environments and back." @@ -204,15 +176,12 @@ category = "main" description = "A very fast and expressive template engine." name = "jinja2" optional = false -python-versions = "*" -version = "2.10.3" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.0" [package.dependencies] MarkupSafe = ">=0.23" -[package.extras] -i18n = ["Babel (>=0.8)"] - [[package]] category = "main" description = "" @@ -229,7 +198,7 @@ marshmallow = "^3.3.0" webargs = "^5.5.2" [package.source] -reference = "23732b17074486367541439a1caa2eb61b9cc22f" +reference = "cb1ef6a1f8849f4d58601a7a9617ffea12703f39" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -256,12 +225,6 @@ optional = false python-versions = ">=3.5" version = "3.3.0" -[package.extras] -dev = ["pytest", "pytz", "simplejson", "mypy (0.750)", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"] -docs = ["sphinx (2.2.2)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"] -lint = ["mypy (0.750)", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"] -tests = ["pytest", "pytz", "simplejson"] - [[package]] category = "dev" description = "McCabe checker, plugin for flake8" @@ -298,11 +261,6 @@ optional = true python-versions = "*" version = "1.13.1b0" -[package.extras] -array = ["numpy"] -doc = ["sphinx"] -test = ["coverage", "pytest", "mock", "pillow", "numpy"] - [package.source] reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb" type = "git" @@ -353,6 +311,17 @@ optional = false python-versions = "*" version = "3.4" +[[package]] +category = "main" +description = "Extensions to the standard Python datetime module" +name = "python-dateutil" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +version = "2.8.1" + +[package.dependencies] +six = ">=1.5" + [[package]] category = "dev" description = "World timezone definitions, modern and historical" @@ -383,10 +352,6 @@ chardet = ">=3.0.2,<3.1.0" idna = ">=2.5,<2.9" urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" -[package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] - [[package]] category = "dev" description = "a python refactoring library..." @@ -414,6 +379,14 @@ version = "1.3.0" [package.dependencies] numpy = ">=1.13.3" +[[package]] +category = "main" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +name = "simplejson" +optional = false +python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" +version = "3.17.0" + [[package]] category = "main" description = "Python 2 and 3 compatibility utilities" @@ -457,10 +430,6 @@ sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = "*" -[package.extras] -docs = ["sphinxcontrib-websupport"] -test = ["pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"] - [[package]] category = "dev" description = "" @@ -469,9 +438,6 @@ optional = false python-versions = "*" version = "1.0.1" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "" @@ -480,9 +446,6 @@ optional = false python-versions = "*" version = "1.0.1" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "" @@ -491,9 +454,6 @@ optional = false python-versions = "*" version = "1.0.2" -[package.extras] -test = ["pytest", "flake8", "mypy", "html5lib"] - [[package]] category = "dev" description = "Sphinx domain for documenting HTTP APIs" @@ -514,9 +474,6 @@ optional = false python-versions = ">=3.5" version = "1.0.1" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "" @@ -525,9 +482,6 @@ optional = false python-versions = "*" version = "1.0.2" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "" @@ -536,9 +490,6 @@ optional = false python-versions = "*" version = "1.1.3" -[package.extras] -test = ["pytest", "flake8", "mypy"] - [[package]] category = "dev" description = "Python Library for Tom's Obvious, Minimal Language" @@ -564,28 +515,17 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" version = "1.25.8" -[package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] - [[package]] category = "main" description = "Declarative parsing and validation of HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, webapp2, Falcon, and aiohttp." name = "webargs" optional = false python-versions = "*" -version = "5.5.2" +version = "5.5.3" [package.dependencies] marshmallow = ">=2.15.2" - -[package.extras] -dev = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "tox", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"] -docs = ["Sphinx (2.2.0)", "sphinx-issues (1.2.0)", "sphinx-typlog-theme (0.7.3)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"] -frameworks = ["Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"] -lint = ["flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"] -tests = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)"] +simplejson = ">=2.1.0" [[package]] category = "main" @@ -593,12 +533,7 @@ description = "The comprehensive WSGI web application library." name = "werkzeug" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.16.0" - -[package.extras] -dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] -termcolor = ["termcolor"] -watchdog = ["watchdog"] +version = "0.16.1" [[package]] category = "dev" @@ -612,373 +547,63 @@ version = "1.11.2" rpi = ["picamera", "RPi.GPIO"] [metadata] -content-hash = "cd4e5d59e9a63ae053a05c994a61a46be87a29ec6867bcd47f6ef9b39bb785d3" +content-hash = "34da76b3341563a0bebfa3e4a3fca90f34b857027e90811fed92b106ad06c2ba" python-versions = "^3.6" -[metadata.files] -alabaster = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, -] -apispec = [ - {file = "apispec-3.2.0-py2.py3-none-any.whl", hash = "sha256:f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"}, - {file = "apispec-3.2.0.tar.gz", hash = "sha256:c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a"}, -] -appdirs = [ - {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, - {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, -] -astroid = [ - {file = "astroid-2.3.3-py3-none-any.whl", hash = "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"}, - {file = "astroid-2.3.3.tar.gz", hash = "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a"}, -] -attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, -] -babel = [ - {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, - {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, -] -black = [ - {file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"}, - {file = "black-18.9b0.tar.gz", hash = "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"}, -] -certifi = [ - {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, - {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, -] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, -] -click = [ - {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, - {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, -] -colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, -] -docutils = [ - {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, - {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, -] -flask = [ - {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, - {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, -] -flask-cors = [ - {file = "Flask-Cors-3.0.8.tar.gz", hash = "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16"}, - {file = "Flask_Cors-3.0.8-py2.py3-none-any.whl", hash = "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"}, -] -idna = [ - {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, - {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, -] -imagesize = [ - {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, - {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, -] -isort = [ - {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, - {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, -] -itsdangerous = [ - {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, - {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, -] -jinja2 = [ - {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"}, - {file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"}, -] +[metadata.hashes] +alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"] +apispec = ["c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a", "f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"] +appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] +astroid = ["71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", "840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"] +attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"] +babel = ["1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", "d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"] +black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"] +certifi = ["017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", "25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"] +chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] +click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] +colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"] +docutils = ["0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", "c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"] +flask = ["13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", "45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"] +flask-cors = ["72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16", "f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"] +idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] +imagesize = ["6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", "b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"] +isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"] +itsdangerous = ["321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"] +jinja2 = ["6e7a3c2934694d59ad334c93dd1b6c96699cf24c53fdb8ec848ac6b23e685734", "d6609ae5ec3d56212ca7d802eda654eaf2310000816ce815361041465b108be4"] labthings = [] -lazy-object-proxy = [ - {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, -] -markupsafe = [ - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, -] -marshmallow = [ - {file = "marshmallow-3.3.0-py2.py3-none-any.whl", hash = "sha256:3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"}, - {file = "marshmallow-3.3.0.tar.gz", hash = "sha256:0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -numpy = [ - {file = "numpy-1.18.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc"}, - {file = "numpy-1.18.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121"}, - {file = "numpy-1.18.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e"}, - {file = "numpy-1.18.1-cp35-cp35m-win32.whl", hash = "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"}, - {file = "numpy-1.18.1-cp35-cp35m-win_amd64.whl", hash = "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6"}, - {file = "numpy-1.18.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480"}, - {file = "numpy-1.18.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572"}, - {file = "numpy-1.18.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57"}, - {file = "numpy-1.18.1-cp36-cp36m-win32.whl", hash = "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc"}, - {file = "numpy-1.18.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd"}, - {file = "numpy-1.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa"}, - {file = "numpy-1.18.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca"}, - {file = "numpy-1.18.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec"}, - {file = "numpy-1.18.1-cp37-cp37m-win32.whl", hash = "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73"}, - {file = "numpy-1.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971"}, - {file = "numpy-1.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07"}, - {file = "numpy-1.18.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26"}, - {file = "numpy-1.18.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474"}, - {file = "numpy-1.18.1-cp38-cp38-win32.whl", hash = "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3"}, - {file = "numpy-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a"}, - {file = "numpy-1.18.1.zip", hash = "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77"}, -] -packaging = [ - {file = "packaging-20.1-py2.py3-none-any.whl", hash = "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73"}, - {file = "packaging-20.1.tar.gz", hash = "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"}, -] +lazy-object-proxy = ["0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", "194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", "1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", "4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", "48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", "5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", "59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", "8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", "9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", "9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", "97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", "9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", "a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", "a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", "ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", "cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", "d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", "d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", "eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", "efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"] +markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] +marshmallow = ["0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e", "3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"] +mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] +numpy = ["1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", "17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", "20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", "2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", "39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", "56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", "590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", "70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", "77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", "9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", "9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", "ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", "b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", "b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", "b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", "c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", "cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", "d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", "e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", "e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", "f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"] +packaging = ["170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", "e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"] picamera = [] -pillow = [ - {file = "Pillow-5.4.1-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1"}, - {file = "Pillow-5.4.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7"}, - {file = "Pillow-5.4.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e"}, - {file = "Pillow-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c"}, - {file = "Pillow-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f"}, - {file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c"}, - {file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc"}, - {file = "Pillow-5.4.1-cp34-cp34m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1"}, - {file = "Pillow-5.4.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8"}, - {file = "Pillow-5.4.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055"}, - {file = "Pillow-5.4.1-cp34-cp34m-win32.whl", hash = "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6"}, - {file = "Pillow-5.4.1-cp34-cp34m-win_amd64.whl", hash = "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf"}, - {file = "Pillow-5.4.1-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239"}, - {file = "Pillow-5.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3"}, - {file = "Pillow-5.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697"}, - {file = "Pillow-5.4.1-cp35-cp35m-win32.whl", hash = "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7"}, - {file = "Pillow-5.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe"}, - {file = "Pillow-5.4.1-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca"}, - {file = "Pillow-5.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"}, - {file = "Pillow-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad"}, - {file = "Pillow-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a"}, - {file = "Pillow-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356"}, - {file = "Pillow-5.4.1-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1"}, - {file = "Pillow-5.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9"}, - {file = "Pillow-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3"}, - {file = "Pillow-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f"}, - {file = "Pillow-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3"}, - {file = "Pillow-5.4.1-pp260-pypy_41-win32.whl", hash = "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494"}, - {file = "Pillow-5.4.1-pp360-pp360-win32.whl", hash = "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000"}, - {file = "Pillow-5.4.1-py2.7-macosx-10.13-x86_64.egg", hash = "sha256:634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca"}, - {file = "Pillow-5.4.1-py2.7-win-amd64.egg", hash = "sha256:0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d"}, - {file = "Pillow-5.4.1-py2.7-win32.egg", hash = "sha256:f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28"}, - {file = "Pillow-5.4.1-py3.4-win-amd64.egg", hash = "sha256:01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1"}, - {file = "Pillow-5.4.1-py3.4-win32.egg", hash = "sha256:4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964"}, - {file = "Pillow-5.4.1-py3.5-win-amd64.egg", hash = "sha256:f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15"}, - {file = "Pillow-5.4.1-py3.5-win32.egg", hash = "sha256:e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838"}, - {file = "Pillow-5.4.1-py3.6-win-amd64.egg", hash = "sha256:07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa"}, - {file = "Pillow-5.4.1-py3.6-win32.egg", hash = "sha256:87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9"}, - {file = "Pillow-5.4.1-py3.7-win-amd64.egg", hash = "sha256:c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e"}, - {file = "Pillow-5.4.1-py3.7-win32.egg", hash = "sha256:5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f"}, - {file = "Pillow-5.4.1.tar.gz", hash = "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f"}, - {file = "Pillow-5.4.1.win-amd64-py2.7.exe", hash = "sha256:e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e"}, - {file = "Pillow-5.4.1.win-amd64-py3.4.exe", hash = "sha256:52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2"}, - {file = "Pillow-5.4.1.win-amd64-py3.5.exe", hash = "sha256:ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2"}, - {file = "Pillow-5.4.1.win-amd64-py3.6.exe", hash = "sha256:39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c"}, - {file = "Pillow-5.4.1.win-amd64-py3.7.exe", hash = "sha256:5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4"}, - {file = "Pillow-5.4.1.win32-py2.7.exe", hash = "sha256:db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0"}, - {file = "Pillow-5.4.1.win32-py3.4.exe", hash = "sha256:ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28"}, - {file = "Pillow-5.4.1.win32-py3.5.exe", hash = "sha256:f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b"}, - {file = "Pillow-5.4.1.win32-py3.6.exe", hash = "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d"}, - {file = "Pillow-5.4.1.win32-py3.7.exe", hash = "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522"}, -] -pygments = [ - {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"}, - {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"}, -] -pylint = [ - {file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"}, - {file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"}, -] -pyparsing = [ - {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, - {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, -] -pyserial = [ - {file = "pyserial-3.4-py2.py3-none-any.whl", hash = "sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"}, - {file = "pyserial-3.4.tar.gz", hash = "sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627"}, -] -pytz = [ - {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, - {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, -] -pyyaml = [ - {file = "PyYAML-3.13-cp27-cp27m-win32.whl", hash = "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f"}, - {file = "PyYAML-3.13-cp27-cp27m-win_amd64.whl", hash = "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537"}, - {file = "PyYAML-3.13-cp34-cp34m-win32.whl", hash = "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3"}, - {file = "PyYAML-3.13-cp34-cp34m-win_amd64.whl", hash = "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04"}, - {file = "PyYAML-3.13-cp35-cp35m-win32.whl", hash = "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1"}, - {file = "PyYAML-3.13-cp35-cp35m-win_amd64.whl", hash = "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613"}, - {file = "PyYAML-3.13-cp36-cp36m-win32.whl", hash = "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a"}, - {file = "PyYAML-3.13-cp36-cp36m-win_amd64.whl", hash = "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b"}, - {file = "PyYAML-3.13-cp37-cp37m-win32.whl", hash = "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"}, - {file = "PyYAML-3.13-cp37-cp37m-win_amd64.whl", hash = "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1"}, - {file = "PyYAML-3.13.tar.gz", hash = "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf"}, -] -requests = [ - {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, - {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, -] -rope = [ - {file = "rope-0.14.0-py2-none-any.whl", hash = "sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969"}, - {file = "rope-0.14.0-py3-none-any.whl", hash = "sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"}, - {file = "rope-0.14.0.tar.gz", hash = "sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf"}, -] -"rpi.gpio" = [ - {file = "RPi.GPIO-0.6.5.tar.gz", hash = "sha256:a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"}, -] -scipy = [ - {file = "scipy-1.3.0-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340"}, - {file = "scipy-1.3.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba"}, - {file = "scipy-1.3.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef"}, - {file = "scipy-1.3.0-cp35-cp35m-win32.whl", hash = "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351"}, - {file = "scipy-1.3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae"}, - {file = "scipy-1.3.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7"}, - {file = "scipy-1.3.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785"}, - {file = "scipy-1.3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"}, - {file = "scipy-1.3.0-cp36-cp36m-win32.whl", hash = "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb"}, - {file = "scipy-1.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d"}, - {file = "scipy-1.3.0-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e"}, - {file = "scipy-1.3.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06"}, - {file = "scipy-1.3.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921"}, - {file = "scipy-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1"}, - {file = "scipy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e"}, - {file = "scipy-1.3.0.tar.gz", hash = "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a"}, -] -six = [ - {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, - {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, -] -snowballstemmer = [ - {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, - {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, -] -sphinx = [ - {file = "Sphinx-2.3.1-py3-none-any.whl", hash = "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159"}, - {file = "Sphinx-2.3.1.tar.gz", hash = "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"}, -] -sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"}, - {file = "sphinxcontrib_applehelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"}, -] -sphinxcontrib-devhelp = [ - {file = "sphinxcontrib-devhelp-1.0.1.tar.gz", hash = "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34"}, - {file = "sphinxcontrib_devhelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"}, -] -sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-1.0.2.tar.gz", hash = "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422"}, - {file = "sphinxcontrib_htmlhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"}, -] -sphinxcontrib-httpdomain = [ - {file = "sphinxcontrib-httpdomain-1.7.0.tar.gz", hash = "sha256:ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"}, - {file = "sphinxcontrib_httpdomain-1.7.0-py2.py3-none-any.whl", hash = "sha256:1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e"}, -] -sphinxcontrib-jsmath = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] -sphinxcontrib-qthelp = [ - {file = "sphinxcontrib-qthelp-1.0.2.tar.gz", hash = "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"}, - {file = "sphinxcontrib_qthelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20"}, -] -sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.3.tar.gz", hash = "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227"}, - {file = "sphinxcontrib_serializinghtml-1.1.3-py2.py3-none-any.whl", hash = "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"}, -] -toml = [ - {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, - {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, - {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, -] -typed-ast = [ - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, - {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, - {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, - {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, - {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, -] -urllib3 = [ - {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, - {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, -] -webargs = [ - {file = "webargs-5.5.2-py2-none-any.whl", hash = "sha256:3f9dc15de183d356c9a0acc159c100ea0506c0c240c1e6f1d8b308c5fed4dbbd"}, - {file = "webargs-5.5.2-py3-none-any.whl", hash = "sha256:fa4ad3ad9b38bedd26c619264fdc50d7ae014b49186736bca851e5b5228f2a1b"}, - {file = "webargs-5.5.2.tar.gz", hash = "sha256:3beca296598067cec24a0b6f91c0afcc19b6e3c4d84ab026b931669628bb47b4"}, -] -werkzeug = [ - {file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"}, - {file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"}, -] -wrapt = [ - {file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"}, -] +pillow = ["01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", "051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", "07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", "0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", "0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", "0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", "163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", "18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", "24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", "267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", "3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", "39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", "4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", "4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", "4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", "4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", "505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", "5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", "52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", "5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", "5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", "5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", "634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", "75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", "7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", "801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", "825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", "87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", "9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", "a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", "a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", "a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", "a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", "ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", "b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", "ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", "ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", "c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", "cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", "cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", "d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", "d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", "db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", "e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", "e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", "e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", "f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", "f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", "f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", "f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", "fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"] +pygments = ["2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", "98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"] +pylint = ["3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", "886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"] +pyparsing = ["4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", "c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"] +pyserial = ["6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627", "e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"] +python-dateutil = ["73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"] +pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"] +pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"] +requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] +rope = ["6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969", "c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf", "f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"] +"rpi.gpio" = ["a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"] +scipy = ["03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", "09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", "10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", "1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", "409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", "4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", "6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", "826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", "a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", "a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", "adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", "b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", "c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", "c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", "c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", "db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"] +simplejson = ["0fe3994207485efb63d8f10a833ff31236ed27e3b23dadd0bf51c9900313f8f2", "17163e643dbf125bb552de17c826b0161c68c970335d270e174363d19e7ea882", "1d1e929cdd15151f3c0b2efe953b3281b2fd5ad5f234f77aca725f28486466f6", "1d346c2c1d7dd79c118f0cc7ec5a1c4127e0c8ffc83e7b13fc5709ff78c9bb84", "1ea59f570b9d4916ae5540a9181f9c978e16863383738b69a70363bc5e63c4cb", "1fbba86098bbfc1f85c5b69dc9a6d009055104354e0d9880bb00b692e30e0078", "229edb079d5dd81bf12da952d4d825bd68d1241381b37d3acf961b384c9934de", "22a7acb81968a7c64eba7526af2cf566e7e2ded1cb5c83f0906b17ff1540f866", "2b4b2b738b3b99819a17feaf118265d0753d5536049ea570b3c43b51c4701e81", "4cf91aab51b02b3327c9d51897960c554f00891f9b31abd8a2f50fd4a0071ce8", "4fd5f79590694ebff8dc980708e1c182d41ce1fda599a12189f0ca96bf41ad70", "5cfd495527f8b85ce21db806567de52d98f5078a8e9427b18e251c68bd573a26", "60aad424e47c5803276e332b2a861ed7a0d46560e8af53790c4c4fb3420c26c2", "7739940d68b200877a15a5ff5149e1599737d6dd55e302625650629350466418", "7cce4bac7e0d66f3a080b80212c2238e063211fe327f98d764c6acbc214497fc", "8027bd5f1e633eb61b8239994e6fc3aba0346e76294beac22a892eb8faa92ba1", "86afc5b5cbd42d706efd33f280fec7bd7e2772ef54e3f34cf6b30777cd19a614", "87d349517b572964350cc1adc5a31b493bbcee284505e81637d0174b2758ba17", "8de378d589eccbc75941e480b4d5b4db66f22e4232f87543b136b1f093fff342", "926bcbef9eb60e798eabda9cd0bbcb0fca70d2779aa0aa56845749d973eb7ad5", "9a126c3a91df5b1403e965ba63b304a50b53d8efc908a8c71545ed72535374a3", "ad8dd3454d0c65c0f92945ac86f7b9efb67fa2040ba1b0189540e984df904378", "d140e9376e7f73c1f9e0a8e3836caf5eec57bbafd99259d56979da05a6356388", "da00675e5e483ead345429d4f1374ab8b949fba4429d60e71ee9d030ced64037", "daaf4d11db982791be74b23ff4729af2c7da79316de0bebf880fa2d60bcc8c5a", "f4b64a1031acf33e281fd9052336d6dad4d35eee3404c95431c8c6bc7a9c0588", "fc046afda0ed8f5295212068266c92991ab1f4a50c6a7144b69364bdee4a0159", "fc9051d249dd5512e541f20330a74592f7a65b2d62e18122ca89bf71f94db748"] +six = ["236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", "8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"] +snowballstemmer = ["209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", "df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"] +sphinx = ["298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159", "e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"] +sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"] +sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"] +sphinxcontrib-htmlhelp = ["4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"] +sphinxcontrib-httpdomain = ["1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e", "ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"] +sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"] +sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"] +sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"] +toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] +typed-ast = ["0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", "0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", "249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", "24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", "269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", "4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", "498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", "4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", "6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", "715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", "73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", "8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", "8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", "aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", "bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", "c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", "d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", "d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", "d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", "fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", "fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"] +urllib3 = ["2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", "87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"] +webargs = ["4f04918864c7602886335d8099f9b8960ee698b6b914f022736ed50be6b71235", "871642a2e0c62f21d5b78f357750ac7a87e6bc734c972f633aa5fb6204fbf29a", "fc81c9f9d391acfbce406a319217319fd8b2fd862f7fdb5319ad06944f36ed25"] +werkzeug = ["1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2", "b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04"] +wrapt = ["565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"] diff --git a/pyproject.toml b/pyproject.toml index fa4bb194..f9ccc42e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ picamera = { git = "https://github.com/rwb27/picamera.git", branch = "lens-shadi pyserial = "^3.4" # Used for sangaboard (basic_serial_instrument) until we move to sangaboard pip library labthings = { git = "https://github.com/labthings/python-labthings.git", branch = "master"} # TODO: Pin to a fixed version before 2.0.0 stable +python-dateutil = "^2.8" [tool.poetry.extras] rpi = ["picamera", "RPi.GPIO"] From 7458d278d8ed1c155ef81d651185b9c92965db59 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 15:36:57 +0000 Subject: [PATCH 3/7] Simplified new configuration and metadata --- openflexure_microscope/api/app.py | 8 +- .../api/default_extensions/scan.py | 101 ++++++-------- .../api/v2/views/actions/camera.py | 7 +- .../api/v2/views/captures.py | 22 +-- openflexure_microscope/api/v2/views/state.py | 32 ++++- openflexure_microscope/camera/capture.py | 84 +++++------ openflexure_microscope/camera/pi.py | 17 +-- openflexure_microscope/config.py | 19 ++- openflexure_microscope/microscope.py | 130 +++++++++--------- .../microscope_configuration.default.json | 10 +- 10 files changed, 214 insertions(+), 216 deletions(-) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index 7f688fe0..e3aa1404 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -19,7 +19,7 @@ from openflexure_microscope.paths import ( OPENFLEXURE_VAR_PATH, OPENFLEXURE_EXTENSIONS_PATH, settings_file_path, - logs_file_path + logs_file_path, ) from labthings.server.quick import create_app @@ -92,7 +92,7 @@ labthing.add_root_link(views.CaptureList, "captures") labthing.add_view(views.CaptureView, f"/captures/") labthing.add_view(views.CaptureDownload, f"/captures//download/") labthing.add_view(views.CaptureTags, f"/captures//tags") -labthing.add_view(views.CaptureMetadata, f"/captures//metadata") +labthing.add_view(views.CaptureAnnotations, f"/captures//annotations") # Attach settings and status resources labthing.add_view(views.SettingsProperty, f"/settings") @@ -101,6 +101,10 @@ labthing.add_view(views.NestedSettingsProperty, "/settings/") labthing.add_view(views.StatusProperty, "/status") labthing.add_view(views.NestedStatusProperty, "/status/") labthing.add_root_link(views.StatusProperty, "status") +labthing.add_view(views.ConfigurationProperty, "/configuration") +labthing.add_view(views.NestedConfigurationProperty, "/configuration/") +labthing.add_root_link(views.ConfigurationProperty, "configuration") + # Attach streams resources labthing.add_view(views.MjpegStream, f"/streams/mjpeg") diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index 26a7dd6f..261b15b3 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -1,6 +1,7 @@ import itertools import logging import uuid +import datetime from typing import Tuple from functools import reduce @@ -71,12 +72,12 @@ def progress(): def capture( microscope, basename, - scan_id, temporary: bool = False, use_video_port: bool = False, resize: Tuple[int, int] = None, bayer: bool = False, metadata: dict = {}, + annotations: dict = {}, tags: list = [], ): @@ -94,16 +95,14 @@ def capture( output.file, use_video_port=use_video_port, resize=resize, bayer=bayer ) - # Affix metadata - if "scan" not in tags: - tags.append("scan") - # Inject system metadata - output.put_metadata(microscope.metadata, system=True) + output.put_metadata({"instrument": microscope.metadata}) # Insert custom metadata output.put_metadata(metadata) + # Insert custom metadata + output.put_annotations(annotations) # Insert custom tags output.put_tags(tags) @@ -115,7 +114,7 @@ def tile( microscope, basename: str = None, temporary: bool = False, - step_size: int = [2000, 1500, 100], + stride_size: int = [2000, 1500, 100], grid: list = [3, 3, 5], style="raster", autofocus_dz: int = 50, @@ -124,13 +123,13 @@ def tile( bayer: bool = False, fast_autofocus=False, metadata: dict = {}, + annotations: dict = {}, tags: list = [], ): global _images_to_be_captured global _images_captured_so_far # Keep task progress - # TODO: Make this line not nasty _images_to_be_captured = reduce((lambda x, y: x * y), grid) _images_captured_so_far = 0 @@ -138,28 +137,22 @@ def tile( if not basename: basename = generate_basename() - # Generate a stack ID - scan_id = uuid.uuid4() - # Store initial position initial_position = microscope.stage.position - # Add scan metadata - if "time" not in metadata: - metadata["time"] = generate_basename() - - metadata.update( - { - "scan_id": scan_id, - "basename": basename, - "scan_parameters": { - "step_size": step_size, - "grid": grid, - "style": style, - "autofocus_dz": autofocus_dz, - }, + # Add dataset metadata + dataset_d = { + "dataset": { + "id": uuid.uuid4(), + "type": "xyzScan", + "name": basename, + "acquisitionDate": datetime.datetime.now().isoformat(), + "strideSize": stride_size, + "grid": grid, + "style": style, + "autofocusDz": autofocus_dz, } - ) + } # Check if autofocus is enabled autofocus_extension = find_extension("org.openflexure.autofocus") @@ -174,11 +167,11 @@ def tile( autofocus_enabled = False z_stack_dz = ( - grid[2] * step_size[2] if grid[2] > 1 else 0 + grid[2] * stride_size[2] if grid[2] > 1 else 0 ) # shorthand for Z stack range # Construct an x-y grid (worry about z later) - x_y_grid = construct_grid(initial_position, step_size[:2], grid[:2], style=style) + x_y_grid = construct_grid(initial_position, stride_size[:2], grid[:2], style=style) # Keep the initial Z position the same as our current position next_z = initial_position[2] @@ -209,26 +202,25 @@ def tile( target_z=-z_stack_dz / 2.0, # Finish below the focus initial_move_up=False, # We're already at the top of the scan ) - # TODO: save the focus data for future reference? Use it for diagnostics? else: logging.debug("Running autofocus") autofocus_extension.autofocus( range(-3 * autofocus_dz, 4 * autofocus_dz, autofocus_dz) ) logging.debug("Finished autofocus") - time.sleep(1) # TODO: Remove + time.sleep(1) # If we're not doing a z-stack, just capture if grid[2] <= 1: capture( microscope, basename, - scan_id, temporary=temporary, use_video_port=use_video_port, resize=resize, bayer=bayer, - metadata=metadata, + metadata=dataset_d, + annotations=annotations, tags=tags, ) # Update task progress @@ -240,15 +232,14 @@ def tile( microscope=microscope, basename=basename, temporary=temporary, - scan_id=scan_id, - step_size=step_size[2], + step_size=stride_size[2], steps=grid[2], - center=not fast_autofocus, # fast_autofocus does this for us! return_to_start=not fast_autofocus, use_video_port=use_video_port, resize=resize, bayer=bayer, - metadata=metadata, + metadata=dataset_d, + annotations=annotations, tags=tags, ) # Make sure we use our current best estimate of focus (i.e. the current position) next point @@ -259,7 +250,7 @@ def tile( ) # Fast autofocus requires us to start at the top of the range if grid[2] > 1: next_z -= int( - grid[2] / 2.0 * step_size[2] + grid[2] / 2.0 * stride_size[2] ) # Z stacking means we're higher up to start with logging.debug("Returning to {}".format(initial_position)) @@ -270,39 +261,25 @@ def stack( microscope, basename: str = None, temporary: bool = False, - scan_id: str = None, step_size: int = 100, steps: int = 5, - center: bool = True, return_to_start: bool = True, use_video_port: bool = False, resize: Tuple[int, int] = None, bayer: bool = False, metadata: dict = {}, + annotations: dict = {}, tags: list = [], ): global _images_captured_so_far - # Generate a basename if none given - if not basename: - basename = generate_basename() - - # Generate a stack ID - if not scan_id: - scan_id = uuid.uuid4() - - # Add scan metadata - if not "time" in metadata: - metadata["time"] = generate_basename() - # Store initial position initial_position = microscope.stage.position with microscope.lock: # Move to center scan - if center: - logging.debug("Moving to starting position") - microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)]) + logging.debug("Moving to starting position") + microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)]) for i in range(steps): time.sleep(0.1) @@ -310,12 +287,12 @@ def stack( capture( microscope, basename, - scan_id, temporary=temporary, use_video_port=use_video_port, resize=resize, bayer=bayer, metadata=metadata, + annotations=annotations, tags=tags, ) # Update task progress @@ -337,16 +314,18 @@ def stack( class TileScanAPI(View): @use_args( { - "filename": fields.String(), + "filename": fields.String(missing=None, example=None), "temporary": fields.Boolean(missing=False), - "step_size": fields.List(fields.Integer, missing=[2000, 1500, 100]), - "grid": fields.List(fields.Integer, missing=[3, 3, 5]), + "stride_size": fields.List( + fields.Integer, missing=[2000, 1500, 100], example=[2000, 1500, 100] + ), + "grid": fields.List(fields.Integer, missing=[3, 3, 3], example=[3, 3, 3]), "style": fields.String(missing="raster"), "autofocus_dz": fields.Integer(missing=50), "fast_autofocus": fields.Boolean(missing=False), "use_video_port": fields.Boolean(missing=False), "bayer": fields.Boolean(missing=False), - "metadata": fields.Dict(missing={}), + "annotations": fields.Dict(missing={}, example={"Foo": "Bar"}), "tags": fields.List(fields.String, missing=[]), "resize": fields.Dict(missing=None), # TODO: Validate keys } @@ -373,7 +352,7 @@ class TileScanAPI(View): microscope, basename=args.get("filename"), temporary=args.get("temporary"), - step_size=args.get("step_size"), + stride_size=args.get("stride_size"), grid=args.get("grid"), style=args.get("style"), autofocus_dz=args.get("autofocus_dz"), @@ -381,7 +360,7 @@ class TileScanAPI(View): resize=resize, bayer=args.get("bayer"), fast_autofocus=args.get("fast_autofocus"), - metadata=args.get("metadata"), + annotations=args.get("annotations"), tags=args.get("tags"), ) diff --git a/openflexure_microscope/api/v2/views/actions/camera.py b/openflexure_microscope/api/v2/views/actions/camera.py index e9912ad9..8b618193 100644 --- a/openflexure_microscope/api/v2/views/actions/camera.py +++ b/openflexure_microscope/api/v2/views/actions/camera.py @@ -36,7 +36,7 @@ class CaptureAPI(View): "bayer": fields.Boolean( missing=False, description="Store raw bayer data in file" ), - "metadata": fields.Dict(missing={}, example={"Client": "SwaggerUI"}), + "annotations": fields.Dict(missing={}, example={"Client": "SwaggerUI"}), "tags": fields.List(fields.String, missing=[], example=["docs"]), "resize": fields.Dict( missing=None, example={"width": 640, "height": 480} @@ -75,11 +75,10 @@ class CaptureAPI(View): ) # Inject system metadata - output.put_metadata(microscope.metadata, system=True) + output.put_metadata({"instrument": microscope.metadata}) # Insert custom metadata - output.put_metadata(args.get("metadata")) - + output.put_annotations(args.get("annotations")) # Insert custom tags output.put_tags(args.get("tags")) diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index f4831aa3..c5e00623 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -39,10 +39,12 @@ class CaptureSchema(Schema): "mimetype": "application/json", **description_from_view(CaptureTags), }, - "metadata": { - "href": url_for(CaptureMetadata.endpoint, id=data.id, _external=True), + "annotations": { + "href": url_for( + CaptureAnnotations.endpoint, id=data.id, _external=True + ), "mimetype": "application/json", - **description_from_view(CaptureMetadata), + **description_from_view(CaptureAnnotations), }, "download": { "href": url_for( @@ -62,6 +64,9 @@ capture_schema = CaptureSchema() capture_list_schema = CaptureSchema(many=True) +from pprint import pprint + + @ThingProperty @Tag("captures") class CaptureList(View): @@ -197,10 +202,10 @@ class CaptureTags(View): @Tag("captures") -class CaptureMetadata(View): +class CaptureAnnotations(View): def get(self, id): """ - Get metadata associated with a single image capture + Get annotations associated with a single image capture """ microscope = find_component("org.openflexure.microscope") capture_obj = microscope.camera.image_from_id(id) @@ -208,7 +213,7 @@ class CaptureMetadata(View): if not capture_obj: return abort(404) # 404 Not Found - return jsonify(capture_obj.metadata) + return jsonify(capture_obj.annotations) def put(self, id): """ @@ -226,7 +231,6 @@ class CaptureMetadata(View): if type(data_dict) != dict: return abort(400) - # TODO: Allow putting system metadata maybe? - capture_obj.put_metadata(data_dict) + capture_obj.put_annotations(data_dict) - return jsonify(capture_obj.metadata) + return jsonify(capture_obj.annotations) diff --git a/openflexure_microscope/api/v2/views/state.py b/openflexure_microscope/api/v2/views/state.py index 34637754..7f22323c 100644 --- a/openflexure_microscope/api/v2/views/state.py +++ b/openflexure_microscope/api/v2/views/state.py @@ -78,7 +78,7 @@ class StatusProperty(View): Show current read-only state of the microscope """ microscope = find_component("org.openflexure.microscope") - return jsonify(microscope.status) + return jsonify(microscope.state) @Tag("properties") @@ -92,7 +92,35 @@ class NestedStatusProperty(View): keys = route.split("/") try: - value = get_by_path(microscope.status, keys) + value = get_by_path(microscope.state, keys) + except KeyError: + return abort(404) + + return jsonify(value) + + +@ThingProperty +class ConfigurationProperty(View): + def get(self): + """ + Show current read-only state of the microscope + """ + microscope = find_component("org.openflexure.microscope") + return jsonify(microscope.configuration) + + +@Tag("properties") +class NestedConfigurationProperty(View): + @doc_response(404, description="Configuration key cannot be found") + def get(self, route): + """ + Show a nested section of the current microscope state + """ + microscope = find_component("org.openflexure.microscope") + keys = route.split("/") + + try: + value = get_by_path(microscope.configuration, keys) except KeyError: return abort(404) diff --git a/openflexure_microscope/camera/capture.py b/openflexure_microscope/camera/capture.py index 20ec71da..e7264b6c 100644 --- a/openflexure_microscope/camera/capture.py +++ b/openflexure_microscope/camera/capture.py @@ -8,6 +8,7 @@ import yaml import json import logging from PIL import Image +import dateutil.parser import atexit from openflexure_microscope.camera import piexif @@ -93,18 +94,18 @@ def capture_from_exif(path, exif_dict): # Build file path information capture.split_file_path(capture.file) - # Populate capture parameters - capture.id = exif_dict["id"] - capture.timestring = exif_dict["time"] - capture.format = exif_dict["format"] + # Image metadata + image_metadata = exif_dict.pop("image") - capture.custom_metadata = ( - exif_dict["custom"] if "custom" in exif_dict.keys() else {} - ) - capture.system_metadata = ( - exif_dict["system"] if "system" in exif_dict.keys() else {} - ) - capture.tags = exif_dict["tags"] + # Populate capture parameters + capture.id = image_metadata.get("id") + capture.datetime = dateutil.parser.isoparse(image_metadata.get("acquisitionDate")) + capture.format = image_metadata.get("format") + capture.tags = image_metadata.get("tags") + capture.annotations = image_metadata.get("annotations") + + # Since we popped the "image" key, we dump whatever is left in _metadata + capture._metadata = exif_dict return capture @@ -113,16 +114,6 @@ class CaptureObject(object): """ StreamObject used to store and process on-disk capture data, and metadata. Serves to simplify modifying properties of on-disk capture data. - - Attributes: - timestring (str): Timestring of capture creation time - custom_metadata (dict): Dictionary of custom metadata to be included in metadata file - tags (list): List of tags. Essentially just as extra custom metadata field, but useful for quick organisation - filefolder (str): Folder in which the capture file will be stored - filename (str): Full name of the capture file - basename (str): Filename of the capture, without a file extension - format (str): Format of the capture data - """ def __init__(self, filepath) -> None: @@ -131,17 +122,20 @@ class CaptureObject(object): # Store a nice ID self.id = uuid.uuid4() #: str: Unique capture ID logging.debug("Created StreamObject {}".format(self.id)) - self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + self.datetime = datetime.datetime.now() # Create file name. Default to UUID self.file = filepath self.split_file_path(self.file) - # Dictionary for storing custom metadata - self.custom_metadata = {} - # Dictionary for adding top-level metadata (cannmot be accessed through web API) - self.system_metadata = {} + if not os.path.exists(self.filefolder): + os.makedirs(self.filefolder) + # Dictionary for adding top-level metadata (cannmot be accessed through web API) + self._metadata = {} + + # Dictionary for storing custom annotations + self.annotations = {} # List for storing tags self.tags = [] @@ -164,10 +158,6 @@ class CaptureObject(object): self.basename = os.path.splitext(self.filename)[0] self.format = self.filename.split(".")[-1] - # Create folder and file - if not os.path.exists(self.filefolder): - os.makedirs(self.filefolder) - @property def exists(self) -> bool: """Check if capture data file exists on disk.""" @@ -204,17 +194,24 @@ class CaptureObject(object): # HANDLE METADATA - def put_metadata(self, data: dict, system: bool = False) -> None: + def put_annotations(self, data: dict) -> None: """ - Merge metadata from a passed dictionary into the capture metadata, and saves. + Merge annotations from a passed dictionary into the capture metadata, and saves. Args: data (dict): Dictionary of metadata to be added """ - if system: - self.system_metadata.update(data) - else: - self.custom_metadata.update(data) + self.annotations.update(data) + self.save_metadata() + + def put_metadata(self, data: dict) -> None: + """ + Merge root metadata from a passed dictionary into the capture metadata, and saves. + + Args: + data (dict): Dictionary of metadata to be added + """ + self._metadata.update(data) self.save_metadata() def save_metadata(self) -> None: @@ -244,12 +241,15 @@ class CaptureObject(object): and any added custom metadata and tags. """ d = { - "id": self.id, - "time": self.timestring, - "format": self.format, - "tags": self.tags, - "custom": self.custom_metadata, - "system": self.system_metadata, + "image": { + "id": self.id, + "name": self.filename, + "acquisitionDate": self.datetime.isoformat(), + "format": self.format, + "tags": self.tags, + "annotations": self.annotations, + }, + **self._metadata, } # Add custom metadata to dictionary diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index bbd04e44..8f2a4d94 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -119,22 +119,7 @@ class PiCameraStreamer(BaseCamera): @property def configuration(self): """The current camera configuration.""" - config = { - "board": self.camera.revision, - } - - if self.read_lens_shading_table(): - b64_string, dtype, shape = serialise_array_b64(self.read_lens_shading_table()) - - config.update({ - "lens_shading_table": { - "b64_string": b64_string, - "dtype": dtype, - "shape": shape, - } - }) - - return config + return {"board": self.camera.revision} @property def state(self): diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index cfc458a1..5172a07c 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -1,4 +1,5 @@ import json +import flask import os import errno import logging @@ -7,7 +8,12 @@ from uuid import UUID import numpy as np from fractions import Fraction -from .paths import SETTINGS_FILE_PATH, DEFAULT_SETTINGS_FILE_PATH, CONFIGURATION_FILE_PATH, DEFAULT_CONFIGURATION_FILE_PATH +from .paths import ( + SETTINGS_FILE_PATH, + DEFAULT_SETTINGS_FILE_PATH, + CONFIGURATION_FILE_PATH, + DEFAULT_CONFIGURATION_FILE_PATH, +) class OpenflexureSettingsFile: @@ -71,7 +77,7 @@ class OpenflexureSettingsFile: return settings -class JSONEncoder(json.JSONEncoder): +class JSONEncoder(flask.json.JSONEncoder): """ A custom JSON encoder, with type conversions for PiCamera fractions, Numpy integers, and Numpy arrays """ @@ -183,7 +189,9 @@ with open(DEFAULT_SETTINGS_FILE_PATH, "r") as default_settings: DEFAULT_SETTINGS = default_settings.read() #: Default user settings object -user_settings = OpenflexureSettingsFile(path=SETTINGS_FILE_PATH, defaults=DEFAULT_SETTINGS) +user_settings = OpenflexureSettingsFile( + path=SETTINGS_FILE_PATH, defaults=DEFAULT_SETTINGS +) # Load the default configuration @@ -191,4 +199,7 @@ with open(DEFAULT_CONFIGURATION_FILE_PATH, "r") as default_configuration: DEFAULT_CONFIGURATION = default_configuration.read() #: Default user settings object -user_configuration = OpenflexureSettingsFile(path=CONFIGURATION_FILE_PATH, defaults=DEFAULT_CONFIGURATION) +user_configuration = OpenflexureSettingsFile( + path=CONFIGURATION_FILE_PATH, defaults=DEFAULT_CONFIGURATION +) + diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 2f52c6d9..b061bf32 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -9,6 +9,7 @@ import uuid from openflexure_microscope.stage.mock import MissingStage from openflexure_microscope.camera.mock import MissingCamera from openflexure_microscope.stage.sanga import SangaStage + try: from openflexure_microscope.camera.pi import PiCameraStreamer except ImportError: @@ -29,7 +30,12 @@ class Microscope: The camera and stage objects may already be initialised, and can be passed as arguments. """ - def __init__(self, settings = user_settings, configuration = user_configuration): + def __init__(self, settings=user_settings, configuration=user_configuration): + self.id = uuid.uuid4() + self.name = self.id + + self.fov = [0, 0] #: Microscope field-of-view in stage motor steps + # Store settings and configuration files self.settings_file = settings self.configuration_file = configuration @@ -45,17 +51,6 @@ class Microscope: # Apply settings loaded from file self.update_settings(self.settings_file.load()) - # Initial attributes - if self.configuration_file.load().get("id"): - self.id = configuration.get("id") - else: - self.id = uuid.uuid4() - self.configuration_file.save({ - "id": self.id - }) - - self.name = self.id - def __enter__(self): """Create microscope on context enter.""" return self @@ -79,9 +74,9 @@ class Microscope: """ ### Detector - if configuration.get("detector"): - detector_type = configuration["detector"].get("type") - if detector_type == "PiCamera" or detector_type == "PiCameraStreamer": + if configuration.get("camera"): + camera_type = configuration["camera"].get("type") + if camera_type == "PiCamera" or camera_type == "PiCameraStreamer": try: self.camera = PiCameraStreamer() except Exception as e: @@ -92,7 +87,7 @@ class Microscope: if configuration.get("stage"): stage_type = configuration["stage"].get("type") stage_port = configuration["stage"].get("port") - if stage_type == "SangaBoard" or detector_type == "SangaStage": + if stage_type == "SangaBoard" or camera_type == "SangaStage": try: self.stage = SangaStage(port=stage_port) except Exception as e: @@ -137,33 +132,32 @@ class Microscope: Return: dict: Dictionary containing complete microscope status """ - state = { - "camera": self.camera.state, - "stage": self.stage.state, - } + state = {"camera": self.camera.state, "stage": self.stage.state} return state - def update_settings(self, config: dict): + def update_settings(self, settings: dict): """ Applies a settings dictionary to the microscope. Missing parameters will be left untouched. """ - logging.debug("Microscope: Applying config: {}".format(config)) + logging.debug("Microscope: Applying settings: {}".format(settings)) # If attached to a camera - if ("camera_settings" in config) and self.camera: - self.camera.update_settings(config["camera_settings"]) + if ("camera" in settings) and self.camera: + self.camera.update_settings(settings["camera"]) # If attached to a stage - if ("stage_settings" in config) and self.stage: - self.stage.update_settings(config["stage_settings"]) + if ("stage" in settings) and self.stage: + self.stage.update_settings(settings["stage"]) - # Todo: tidy up with some loopy goodness - if "name" in config: - self.name = config["name"] - if "fov" in config: - self.fov = config["fov"] + # Microscope settings + if "id" in settings: + self.id = settings["id"] + if "name" in settings: + self.name = settings["name"] + if "fov" in settings: + self.fov = settings["fov"] - def read_settings(self, full: bool=True): + def read_settings(self, full: bool = True): """ Get an updated settings dictionary. @@ -174,17 +168,30 @@ class Microscope: don't get removed from the settings file. """ - settings_current = {"name": self.name, "fov": self.fov} + settings_current = {"id": self.id, "name": self.name, "fov": self.fov} # If attached to a camera if self.camera: settings_current_camera = self.camera.read_settings() - settings_current["camera_settings"] = settings_current_camera + settings_current["camera"] = settings_current_camera + + # Store an encoded copy of the PiCamera lens shading table, if it exists + if hasattr(self.camera, "read_lens_shading_table"): + # Read LST. Returns None if no LST is active + lst_arr = self.camera.read_lens_shading_table() + + b64_string, dtype, shape = serialise_array_b64(lst_arr) + + settings_current["camera"]["lens_shading_table"] = { + "b64_string": b64_string, + "dtype": dtype, + "shape": shape, + } # If attached to a stage if self.stage: settings_current_stage = self.stage.read_settings() - settings_current["stage_settings"] = settings_current_stage + settings_current["stage"] = settings_current_stage settings_full = self.settings_file.merge(settings_current) @@ -206,33 +213,6 @@ class Microscope: self.stage.save_settings() self.settings_file.save(current_config, backup=True) - @property - def metadata(self): - """ - Microscope system metadata, to be applied to basically all captures - """ - system_metadata = { - "@ID": self.id, - "settings": self.read_settings(full=False), - "state": self.state, - "configuration": self.configuration - } - - # Store an encoded copy of the PiCamera lens shading table, if it exists - if self.camera and hasattr(self.camera, "read_lens_shading_table"): - # Read LST. Returns None if no LST is active - lst_arr = self.camera.read_lens_shading_table() - - b64_string, dtype, shape = serialise_array_b64(lst_arr) - - system_metadata["configuration"]["detector"]["lens_shading_table"] = { - "b64_string": b64_string, - "dtype": dtype, - "shape": shape, - } - - return system_metadata - @property def configuration(self): initial_configuration = self.configuration_file.load() @@ -240,17 +220,33 @@ class Microscope: current_configuration = { "@application": { "name": "openflexure_microscope", - "version": pkg_resources.get_distribution("openflexure_microscope").version + "version": pkg_resources.get_distribution( + "openflexure_microscope" + ).version, }, "stage": { "type": self.stage.__class__.__name__, - **self.stage.configuration + **self.stage.configuration, }, - "detector": { + "camera": { "type": self.camera.__class__.__name__, - **self.camera.configuration - } + **self.camera.configuration, + }, } initial_configuration.update(current_configuration) return initial_configuration + + @property + def metadata(self): + """ + Microscope system metadata, to be applied to basically all captures + """ + system_metadata = { + "id": self.id, + "settings": self.read_settings(full=False), + "state": self.state, + "configuration": self.configuration, + } + + return system_metadata diff --git a/openflexure_microscope/microscope_configuration.default.json b/openflexure_microscope/microscope_configuration.default.json index d7129bc8..b9cbfdce 100644 --- a/openflexure_microscope/microscope_configuration.default.json +++ b/openflexure_microscope/microscope_configuration.default.json @@ -1,17 +1,9 @@ { - "microscope": { - "stepsPerView": [4100, 3146] - }, - "detector": { + "camera": { "type": "PiCamera" }, "stage": { "type": "SangaStage", "port": null - }, - "lightSource": { - "type": "LED" - }, - "objective": { } } \ No newline at end of file From ebdd63f37b2543122504b1027e1ab14d598b4be2 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 15:43:29 +0000 Subject: [PATCH 4/7] Added ndarray type to serialised LST (can be used in custom decoder) --- openflexure_microscope/microscope.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index b061bf32..d65a8fcb 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -183,6 +183,7 @@ class Microscope: b64_string, dtype, shape = serialise_array_b64(lst_arr) settings_current["camera"]["lens_shading_table"] = { + "@type": "ndarray", "b64_string": b64_string, "dtype": dtype, "shape": shape, From 265eed7bfac79ea03140cca1c540e9f3a9738d84 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 16:16:09 +0000 Subject: [PATCH 5/7] Fixed picamera setup --- .../picamera_autocalibrate/extension.py | 4 ++-- .../api/v2/views/actions/stage.py | 2 +- openflexure_microscope/camera/pi.py | 10 ++++---- openflexure_microscope/microscope.py | 23 ++++++++----------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py index 37cbe8cb..343a2c02 100644 --- a/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py +++ b/openflexure_microscope/api/default_extensions/picamera_autocalibrate/extension.py @@ -21,8 +21,8 @@ def recalibrate(microscope): """ scamera = microscope.camera with scamera.lock: - assert not scamera.status["record_active"], "Can't recalibrate while recording!" - streaming = scamera.status["stream_active"] + assert not scamera.record_active, "Can't recalibrate while recording!" + streaming = scamera.stream_active if streaming: logging.info("Stopping stream before recalibration") scamera.stop_stream_recording(resolution=(640, 480)) diff --git a/openflexure_microscope/api/v2/views/actions/stage.py b/openflexure_microscope/api/v2/views/actions/stage.py index 8b1020eb..6aa969bb 100644 --- a/openflexure_microscope/api/v2/views/actions/stage.py +++ b/openflexure_microscope/api/v2/views/actions/stage.py @@ -53,7 +53,7 @@ class MoveStageAPI(View): logging.warning("Unable to move. No stage found.") # TODO: Make schema for microscope status - return jsonify(microscope.status["stage"]["position"]) + return jsonify(microscope.state["stage"]["position"]) @ThingAction diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 8f2a4d94..d1b71141 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -333,13 +333,13 @@ class PiCameraStreamer(BaseCamera): Change the camera zoom, handling re-centering and scaling. """ with self.lock: - self.status["zoom_value"] = float(zoom_value) - if self.status["zoom_value"] < 1: - self.status["zoom_value"] = 1 + self.zoom_value = float(zoom_value) + if self.zoom_value < 1: + self.zoom_value = 1 # Richard's code for zooming ! fov = self.camera.zoom centre = np.array([fov[0] + fov[2] / 2.0, fov[1] + fov[3] / 2.0]) - size = 1.0 / self.status["zoom_value"] + size = 1.0 / self.zoom_value # If the new zoom value would be invalid, move the centre to # keep it within the camera's sensor (this is only relevant # when zooming out, if the FoV is not centred on (0.5, 0.5) @@ -552,7 +552,7 @@ class PiCameraStreamer(BaseCamera): if isinstance(output, CaptureObject): target = output.file else: - target = target + target = output with self.lock: logging.info("Capturing to {}".format(output)) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index b061bf32..f1023e13 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -76,7 +76,7 @@ class Microscope: ### Detector if configuration.get("camera"): camera_type = configuration["camera"].get("type") - if camera_type == "PiCamera" or camera_type == "PiCameraStreamer": + if camera_type in ("PiCamera", "PiCameraStreamer"): try: self.camera = PiCameraStreamer() except Exception as e: @@ -87,12 +87,8 @@ class Microscope: if configuration.get("stage"): stage_type = configuration["stage"].get("type") stage_port = configuration["stage"].get("port") - if stage_type == "SangaBoard" or camera_type == "SangaStage": - try: - self.stage = SangaStage(port=stage_port) - except Exception as e: - logging.error(e) - logging.warning("No compatible stage hardware found.") + if stage_type in ("SangaBoard", "SangaStage"): + self.stage = SangaStage(port=stage_port) ### Fallbacks if not self.camera: @@ -180,13 +176,14 @@ class Microscope: # Read LST. Returns None if no LST is active lst_arr = self.camera.read_lens_shading_table() - b64_string, dtype, shape = serialise_array_b64(lst_arr) + if lst_arr: + b64_string, dtype, shape = serialise_array_b64(lst_arr) - settings_current["camera"]["lens_shading_table"] = { - "b64_string": b64_string, - "dtype": dtype, - "shape": shape, - } + settings_current["camera"]["lens_shading_table"] = { + "b64_string": b64_string, + "dtype": dtype, + "shape": shape, + } # If attached to a stage if self.stage: From e9e2419fe4648f3ef8508a63a873b1da18d7f79c Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 16:21:38 +0000 Subject: [PATCH 6/7] Renamed capture.filename to capture.name --- openflexure_microscope/api/v2/views/captures.py | 4 ++-- openflexure_microscope/camera/capture.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index c5e00623..097ff665 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -20,7 +20,7 @@ class CaptureSchema(Schema): data_key="path", description="Path of file on microscope device" ) exists = fields.Bool(data_key="available") - filename = fields.String() + name = fields.String() metadata = fields.Dict() links = fields.Dict() @@ -50,7 +50,7 @@ class CaptureSchema(Schema): "href": url_for( CaptureDownload.endpoint, id=data.id, - filename=data.filename, + filename=data.name, _external=True, ), "mimetype": "image/jpeg", diff --git a/openflexure_microscope/camera/capture.py b/openflexure_microscope/camera/capture.py index e7264b6c..81edff1a 100644 --- a/openflexure_microscope/camera/capture.py +++ b/openflexure_microscope/camera/capture.py @@ -152,11 +152,11 @@ class CaptureObject(object): Args: filepath (str): String of the full file path, including file format extension """ - # Split the full file path into a folder and a filename - self.filefolder, self.filename = os.path.split(filepath) - # Split the filename out from it's file extension - self.basename = os.path.splitext(self.filename)[0] - self.format = self.filename.split(".")[-1] + # Split the full file path into a folder and a name + self.filefolder, self.name = os.path.split(filepath) + # Split the name out from it's file extension + self.basename = os.path.splitext(self.name)[0] + self.format = self.name.split(".")[-1] @property def exists(self) -> bool: @@ -243,7 +243,7 @@ class CaptureObject(object): d = { "image": { "id": self.id, - "name": self.filename, + "name": self.name, "acquisitionDate": self.datetime.isoformat(), "format": self.format, "tags": self.tags, @@ -262,7 +262,7 @@ class CaptureObject(object): """ # Create basic state dictionary - d = {"path": self.file, "filename": self.filename, "metadata": self.metadata} + d = {"path": self.file, "name": self.name, "metadata": self.metadata} # Combined availability of data if self.exists: From 9b4bf4c8fceb24584e97b767dcbebd666e5f0088 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 17:43:52 +0000 Subject: [PATCH 7/7] Moved status routes to instrument/state --- .../example_extension/03_marshaling_data.py | 2 +- .../example_extension/04_properties.py | 2 +- .../example_extension/05_actions.py | 2 +- docs/source/extensions/marshaling.rst | 2 +- openflexure_microscope/api/app.py | 22 ++++++++++--------- .../api/default_extensions/zip_builder.py | 14 +++++------- .../api/v2/views/__init__.py | 2 +- .../api/v2/views/actions/camera.py | 8 +++---- .../api/v2/views/actions/stage.py | 6 ++--- .../api/v2/views/{state.py => instrument.py} | 4 ++-- openflexure_microscope/camera/base.py | 14 ------------ openflexure_microscope/camera/pi.py | 17 +++++++------- openflexure_microscope/config.py | 5 ++++- openflexure_microscope/microscope.py | 8 +++---- openflexure_microscope/stage/mock.py | 6 ++--- openflexure_microscope/stage/sanga.py | 2 +- 16 files changed, 52 insertions(+), 64 deletions(-) rename openflexure_microscope/api/v2/views/{state.py => instrument.py} (98%) diff --git a/docs/source/extensions/example_extension/03_marshaling_data.py b/docs/source/extensions/example_extension/03_marshaling_data.py index f9ad668d..d3179c10 100644 --- a/docs/source/extensions/example_extension/03_marshaling_data.py +++ b/docs/source/extensions/example_extension/03_marshaling_data.py @@ -14,7 +14,7 @@ from labthings.server import fields class MicroscopeIdentifySchema(Schema): name = fields.String() # Microscopes name id = fields.UUID() # Microscopes unique ID - status = fields.Dict() # Status dictionary + state = fields.Dict() # Status dictionary camera = fields.String() # Camera object (represented as a string) stage = fields.String() # Stage object (represented as a string) diff --git a/docs/source/extensions/example_extension/04_properties.py b/docs/source/extensions/example_extension/04_properties.py index 45ee6c18..922270a5 100644 --- a/docs/source/extensions/example_extension/04_properties.py +++ b/docs/source/extensions/example_extension/04_properties.py @@ -19,7 +19,7 @@ from labthings.server import fields class MicroscopeIdentifySchema(Schema): name = fields.String() # Microscopes name id = fields.UUID() # Microscopes unique ID - status = fields.Dict() # Status dictionary + state = fields.Dict() # Status dictionary camera = fields.String() # Camera object (represented as a string) stage = fields.String() # Stage object (represented as a string) diff --git a/docs/source/extensions/example_extension/05_actions.py b/docs/source/extensions/example_extension/05_actions.py index 027580e8..dc3826a0 100644 --- a/docs/source/extensions/example_extension/05_actions.py +++ b/docs/source/extensions/example_extension/05_actions.py @@ -24,7 +24,7 @@ import io # Used in our capture action class MicroscopeIdentifySchema(Schema): name = fields.String() # Microscopes name id = fields.UUID() # Microscopes unique ID - status = fields.Dict() # Status dictionary + state = fields.Dict() # Status dictionary camera = fields.String() # Camera object (represented as a string) stage = fields.String() # Stage object (represented as a string) diff --git a/docs/source/extensions/marshaling.rst b/docs/source/extensions/marshaling.rst index 10ab188e..1f09162a 100644 --- a/docs/source/extensions/marshaling.rst +++ b/docs/source/extensions/marshaling.rst @@ -107,7 +107,7 @@ We start by creating a schema to describe how to serialise a :py:class:`openflex class MicroscopeIdentifySchema(Schema): name = fields.String() # Microscopes name id = fields.UUID() # Microscopes unique ID - status = fields.Dict() # Status dictionary + state = fields.Dict() # Status dictionary camera = fields.String() # Camera object (represented as a string) stage = fields.String() # Stage object (represented as a string) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index e3aa1404..ce0d1068 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -94,16 +94,18 @@ labthing.add_view(views.CaptureDownload, f"/captures//download/") labthing.add_view(views.CaptureTags, f"/captures//tags") labthing.add_view(views.CaptureAnnotations, f"/captures//annotations") -# Attach settings and status resources -labthing.add_view(views.SettingsProperty, f"/settings") -labthing.add_root_link(views.SettingsProperty, "settings") -labthing.add_view(views.NestedSettingsProperty, "/settings/") -labthing.add_view(views.StatusProperty, "/status") -labthing.add_view(views.NestedStatusProperty, "/status/") -labthing.add_root_link(views.StatusProperty, "status") -labthing.add_view(views.ConfigurationProperty, "/configuration") -labthing.add_view(views.NestedConfigurationProperty, "/configuration/") -labthing.add_root_link(views.ConfigurationProperty, "configuration") +# Attach settings and state resources +labthing.add_view(views.SettingsProperty, f"/instrument/settings") +labthing.add_root_link(views.SettingsProperty, "instrumentSettings") +labthing.add_view(views.NestedSettingsProperty, "/instrument/settings/") +labthing.add_view(views.StateProperty, "/instrument/state") +labthing.add_view(views.NestedStateProperty, "/instrument/state/") +labthing.add_root_link(views.StateProperty, "instrumentState") +labthing.add_view(views.ConfigurationProperty, "/instrument/configuration") +labthing.add_view( + views.NestedConfigurationProperty, "/instrument/configuration/" +) +labthing.add_root_link(views.ConfigurationProperty, "instrumentConfiguration") # Attach streams resources diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index 02032c65..a57a3ad1 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -73,17 +73,15 @@ class ZipManager: # Update task progress update_task_progress(int((index / n_files) * 100)) - session_id = uuid.uuid4() - session_key = str(session_id) - # self.session_zips[session_id] = fp - self.session_zips[session_key] = { + session_id = str(uuid.uuid4()) + self.session_zips[session_id] = { "id": session_id, "fp": fp, "data_size": data_size_megabytes, "zip_size": os.path.getsize(fp.name) * 1e-6, } - return self.session_zips[session_key] + return self.session_zips[session_id] def zip_from_id(self, session_id): return self.session_zips[session_id]["fp"] @@ -103,13 +101,13 @@ class ZipBuilderAPIView(View): task = taskify(default_zip_manager.build_zip_from_capture_ids)(microscope, ids) # Return a handle on the autofocus task - return jsonify(task.state), 201 + return task.state, 201 @ThingProperty class ZipListAPIView(View): def get(self): - return jsonify(default_zip_manager.session_zips) + return default_zip_manager.session_zips class ZipGetterAPIView(View): @@ -141,7 +139,7 @@ class ZipGetterAPIView(View): del default_zip_manager.session_zips[session_id] - return jsonify({"return": session_id}) + return {"return": session_id} zip_extension_v2 = BaseExtension("org.openflexure.zipbuilder", version="2.0.0-beta.1") diff --git a/openflexure_microscope/api/v2/views/__init__.py b/openflexure_microscope/api/v2/views/__init__.py index a998c703..3c1fae87 100644 --- a/openflexure_microscope/api/v2/views/__init__.py +++ b/openflexure_microscope/api/v2/views/__init__.py @@ -1,4 +1,4 @@ from .actions import enabled_root_actions from .captures import * -from .state import * +from .instrument import * from .streams import * diff --git a/openflexure_microscope/api/v2/views/actions/camera.py b/openflexure_microscope/api/v2/views/actions/camera.py index 8b618193..62f777d7 100644 --- a/openflexure_microscope/api/v2/views/actions/camera.py +++ b/openflexure_microscope/api/v2/views/actions/camera.py @@ -163,8 +163,8 @@ class GPUPreviewStartAPI(View): microscope.camera.start_preview(fullscreen=fullscreen, window=window) - # TODO: Make schema for microscope status - return jsonify(microscope.status) + # TODO: Make schema for microscope state + return jsonify(microscope.state) @ThingAction @@ -175,5 +175,5 @@ class GPUPreviewStopAPI(View): """ microscope = find_component("org.openflexure.microscope") microscope.camera.stop_preview() - # TODO: Make schema for microscope status - return jsonify(microscope.status) + # TODO: Make schema for microscope state + return jsonify(microscope.state) diff --git a/openflexure_microscope/api/v2/views/actions/stage.py b/openflexure_microscope/api/v2/views/actions/stage.py index 6aa969bb..5cf4059e 100644 --- a/openflexure_microscope/api/v2/views/actions/stage.py +++ b/openflexure_microscope/api/v2/views/actions/stage.py @@ -52,7 +52,7 @@ class MoveStageAPI(View): else: logging.warning("Unable to move. No stage found.") - # TODO: Make schema for microscope status + # TODO: Make schema for microscope state return jsonify(microscope.state["stage"]["position"]) @@ -66,5 +66,5 @@ class ZeroStageAPI(View): microscope = find_component("org.openflexure.microscope") microscope.stage.zero_position() - # TODO: Make schema for microscope status - return jsonify(microscope.status["stage"]) + # TODO: Make schema for microscope state + return jsonify(microscope.state["stage"]) diff --git a/openflexure_microscope/api/v2/views/state.py b/openflexure_microscope/api/v2/views/instrument.py similarity index 98% rename from openflexure_microscope/api/v2/views/state.py rename to openflexure_microscope/api/v2/views/instrument.py index 7f22323c..633d73ff 100644 --- a/openflexure_microscope/api/v2/views/state.py +++ b/openflexure_microscope/api/v2/views/instrument.py @@ -72,7 +72,7 @@ class NestedSettingsProperty(View): @ThingProperty -class StatusProperty(View): +class StateProperty(View): def get(self): """ Show current read-only state of the microscope @@ -82,7 +82,7 @@ class StatusProperty(View): @Tag("properties") -class NestedStatusProperty(View): +class NestedStateProperty(View): @doc_response(404, description="Status key cannot be found") def get(self, route): """ diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index e823cfd0..2dd16dc4 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -91,20 +91,6 @@ class CameraEvent(object): class BaseCamera(metaclass=ABCMeta): """ Base implementation of StreamingCamera. - - Attributes: - thread: Background thread reading frames from camera - camera: Camera object - lock (:py:class:`labthings.lock.StrictLock`): Strict lock controlling thread - access to stage hardware - frame (bytes): Current frame is stored here by background thread - last_access (time): Time of last client access to the camera - stream_timeout (int): Number of inactive seconds before timing out the stream - stream_timeout_enabled (bool): Enable or disable timing out the stream - status (dict): Dictionary for capture state - paths (dict): Dictionary of capture paths - images (list): List of image capture objects - videos (list): List of video capture objects """ def __init__(self): diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index d1b71141..85628042 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -85,7 +85,7 @@ class PiCameraStreamer(BaseCamera): picamera.PiCamera() ) #: :py:class:`picamera.PiCamera`: Picamera object - # Store status of PiCameraStreamer + # Store state of PiCameraStreamer self.preview_active = False # Reset variable states @@ -157,7 +157,7 @@ class PiCameraStreamer(BaseCamera): "picamera_lst_path": self.picamera_lst_path if os.path.isfile(self.picamera_lst_path) else None, - "picamera_settings": {}, + "picamera": {}, } ) @@ -166,7 +166,7 @@ class PiCameraStreamer(BaseCamera): try: value = getattr(self.camera, key) logging.debug("Reading PiCamera().{}: {}".format(key, value)) - conf_dict["picamera_settings"][key] = value + conf_dict["picamera"][key] = value except AttributeError: logging.debug("Unable to read PiCamera attribute {}".format(key)) @@ -204,14 +204,14 @@ class PiCameraStreamer(BaseCamera): paused_stream = True # Remember to unpause stream when done # PiCamera parameters - if "picamera_settings" in config: # If new settings are given + if "picamera" in config: # If new settings are given self.apply_picamera_settings( - config["picamera_settings"], pause_for_effect=True + config["picamera"], pause_for_effect=True ) # PiCameraStreamer parameters for key, value in config.items(): # For each provided setting - if (key != "picamera_settings") and hasattr(self, key): + if (key != "picamera") and hasattr(self, key): setattr(self, key, value) # Handle lens shading if camera supports it @@ -412,7 +412,7 @@ class PiCameraStreamer(BaseCamera): quality=quality, ) - # Update status dictionary + # Update state self.record_active = True return output @@ -432,7 +432,7 @@ class PiCameraStreamer(BaseCamera): self.camera.stop_recording(splitter_port=2) logging.info("Recording stopped") - # Update status dictionary + # Update state self.record_active = False def stop_stream_recording( @@ -681,7 +681,6 @@ class PiCameraStreamer(BaseCamera): # Start stream recording (and set resolution) self.start_stream_recording() - # Update status logging.debug("STREAM ACTIVE") # While the iterator is not closed diff --git a/openflexure_microscope/config.py b/openflexure_microscope/config.py index 5172a07c..8bbe796a 100644 --- a/openflexure_microscope/config.py +++ b/openflexure_microscope/config.py @@ -94,11 +94,14 @@ class JSONEncoder(flask.json.JSONEncoder): # Numpy arrays elif isinstance(o, np.ndarray): return o.tolist() + # UUIDs + elif isinstance(o, UUID): + return str(o) else: # call base class implementation which takes care of # raising exceptions for unsupported types try: - return json.JSONEncoder.default(self, o) + return flask.json.JSONEncoder.default(self, o) # if it's some mystery object, just return a string representation except TypeError: return str(o) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 19c01be7..1e397339 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -120,13 +120,13 @@ class Microscope: else: return False - # Create unified status + # Create unified state @property def state(self): - """Dictionary of the basic microscope status. + """Dictionary of the basic microscope state. Return: - dict: Dictionary containing complete microscope status + dict: Dictionary containing complete microscope state """ state = {"camera": self.camera.state, "stage": self.stage.state} return state @@ -216,7 +216,7 @@ class Microscope: initial_configuration = self.configuration_file.load() current_configuration = { - "@application": { + "application": { "name": "openflexure_microscope", "version": pkg_resources.get_distribution( "openflexure_microscope" diff --git a/openflexure_microscope/stage/mock.py b/openflexure_microscope/stage/mock.py index e4810f3c..fe523c64 100644 --- a/openflexure_microscope/stage/mock.py +++ b/openflexure_microscope/stage/mock.py @@ -18,9 +18,9 @@ class MissingStage(BaseStage): @property def state(self): - """The general status dictionary of the board.""" - status = {"position": self.position_map} - return status + """The general state dictionary of the board.""" + state = {"position": self.position_map} + return state @property def configuration(self): diff --git a/openflexure_microscope/stage/sanga.py b/openflexure_microscope/stage/sanga.py index 11ffbbc6..d5ffcf15 100644 --- a/openflexure_microscope/stage/sanga.py +++ b/openflexure_microscope/stage/sanga.py @@ -33,7 +33,7 @@ class SangaStage(BaseStage): @property def state(self): - """The general status dictionary of the board.""" + """The general state dictionary of the board.""" return {"position": self.position_map} @property