From 7458d278d8ed1c155ef81d651185b9c92965db59 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 29 Jan 2020 15:36:57 +0000 Subject: [PATCH] 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