Loading openflexure_microscope/api/app.py +6 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -92,7 +92,7 @@ labthing.add_root_link(views.CaptureList, "captures") labthing.add_view(views.CaptureView, f"/captures/<id>") labthing.add_view(views.CaptureDownload, f"/captures/<id>/download/<filename>") labthing.add_view(views.CaptureTags, f"/captures/<id>/tags") labthing.add_view(views.CaptureMetadata, f"/captures/<id>/metadata") labthing.add_view(views.CaptureAnnotations, f"/captures/<id>/annotations") # Attach settings and status resources labthing.add_view(views.SettingsProperty, f"/settings") Loading @@ -101,6 +101,10 @@ labthing.add_view(views.NestedSettingsProperty, "/settings/<path:route>") labthing.add_view(views.StatusProperty, "/status") labthing.add_view(views.NestedStatusProperty, "/status/<path:route>") labthing.add_root_link(views.StatusProperty, "status") labthing.add_view(views.ConfigurationProperty, "/configuration") labthing.add_view(views.NestedConfigurationProperty, "/configuration/<path:route>") labthing.add_root_link(views.ConfigurationProperty, "configuration") # Attach streams resources labthing.add_view(views.MjpegStream, f"/streams/mjpeg") Loading openflexure_microscope/api/default_extensions/scan.py +40 −61 Original line number Diff line number Diff line import itertools import logging import uuid import datetime from typing import Tuple from functools import reduce Loading Loading @@ -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 = [], ): Loading @@ -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) Loading @@ -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, Loading @@ -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 Loading @@ -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, # 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, "autofocus_dz": autofocus_dz, }, "autofocusDz": autofocus_dz, } } ) # Check if autofocus is enabled autofocus_extension = find_extension("org.openflexure.autofocus") Loading @@ -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] Loading Loading @@ -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 Loading @@ -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 Loading @@ -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)) Loading @@ -270,37 +261,23 @@ 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)]) Loading @@ -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 Loading @@ -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 } Loading @@ -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"), Loading @@ -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"), ) Loading openflexure_microscope/api/v2/views/actions/camera.py +3 −4 Original line number Diff line number Diff line Loading @@ -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} Loading Loading @@ -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")) Loading openflexure_microscope/api/v2/views/captures.py +13 −9 Original line number Diff line number Diff line Loading @@ -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( Loading @@ -62,6 +64,9 @@ capture_schema = CaptureSchema() capture_list_schema = CaptureSchema(many=True) from pprint import pprint @ThingProperty @Tag("captures") class CaptureList(View): Loading Loading @@ -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) Loading @@ -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): """ Loading @@ -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) openflexure_microscope/api/v2/views/state.py +30 −2 Original line number Diff line number Diff line Loading @@ -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") Loading @@ -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) Loading Loading
openflexure_microscope/api/app.py +6 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -92,7 +92,7 @@ labthing.add_root_link(views.CaptureList, "captures") labthing.add_view(views.CaptureView, f"/captures/<id>") labthing.add_view(views.CaptureDownload, f"/captures/<id>/download/<filename>") labthing.add_view(views.CaptureTags, f"/captures/<id>/tags") labthing.add_view(views.CaptureMetadata, f"/captures/<id>/metadata") labthing.add_view(views.CaptureAnnotations, f"/captures/<id>/annotations") # Attach settings and status resources labthing.add_view(views.SettingsProperty, f"/settings") Loading @@ -101,6 +101,10 @@ labthing.add_view(views.NestedSettingsProperty, "/settings/<path:route>") labthing.add_view(views.StatusProperty, "/status") labthing.add_view(views.NestedStatusProperty, "/status/<path:route>") labthing.add_root_link(views.StatusProperty, "status") labthing.add_view(views.ConfigurationProperty, "/configuration") labthing.add_view(views.NestedConfigurationProperty, "/configuration/<path:route>") labthing.add_root_link(views.ConfigurationProperty, "configuration") # Attach streams resources labthing.add_view(views.MjpegStream, f"/streams/mjpeg") Loading
openflexure_microscope/api/default_extensions/scan.py +40 −61 Original line number Diff line number Diff line import itertools import logging import uuid import datetime from typing import Tuple from functools import reduce Loading Loading @@ -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 = [], ): Loading @@ -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) Loading @@ -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, Loading @@ -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 Loading @@ -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, # 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, "autofocus_dz": autofocus_dz, }, "autofocusDz": autofocus_dz, } } ) # Check if autofocus is enabled autofocus_extension = find_extension("org.openflexure.autofocus") Loading @@ -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] Loading Loading @@ -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 Loading @@ -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 Loading @@ -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)) Loading @@ -270,37 +261,23 @@ 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)]) Loading @@ -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 Loading @@ -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 } Loading @@ -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"), Loading @@ -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"), ) Loading
openflexure_microscope/api/v2/views/actions/camera.py +3 −4 Original line number Diff line number Diff line Loading @@ -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} Loading Loading @@ -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")) Loading
openflexure_microscope/api/v2/views/captures.py +13 −9 Original line number Diff line number Diff line Loading @@ -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( Loading @@ -62,6 +64,9 @@ capture_schema = CaptureSchema() capture_list_schema = CaptureSchema(many=True) from pprint import pprint @ThingProperty @Tag("captures") class CaptureList(View): Loading Loading @@ -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) Loading @@ -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): """ Loading @@ -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)
openflexure_microscope/api/v2/views/state.py +30 −2 Original line number Diff line number Diff line Loading @@ -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") Loading @@ -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) Loading