Commit 10472572 authored by Joel Collins's avatar Joel Collins
Browse files

Moved task routes into base labthing

parent f2af359b
Loading
Loading
Loading
Loading
+46 −21
Original line number Diff line number Diff line
@@ -16,16 +16,21 @@ from flask_cors import CORS
from openflexure_microscope.api.exceptions import JSONExceptionHandler
from openflexure_microscope.api.utilities import list_routes

from openflexure_microscope.config import settings_file_path, JSONEncoder
from openflexure_microscope.config import (
    settings_file_path,
    JSONEncoder,
    USER_PLUGINS_PATH,
)
from openflexure_microscope.api import v2

from openflexure_microscope.common.labthings.labthing import LabThing
from openflexure_microscope.common.labthings.find import registered_plugins
from openflexure_microscope.common.labthings.plugins import find_plugins
from openflexure_microscope.config import USER_PLUGINS_PATH

from openflexure_microscope.api.microscope import default_microscope as api_microscope

from openflexure_microscope.api.v2 import views

# Handle logging
is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "")

@@ -78,33 +83,53 @@ CORS(app, resources=r"*")
# Make errors more API friendly
handler = JSONExceptionHandler(app)

# Attach lab devices
# Build a labthing
labthing = LabThing(app, prefix="/api/v2")
labthing.description = "Test LabThing-based API for OpenFlexure Microscope"

# Attach lab devices
labthing.register_device(api_microscope, "openflexure_microscope")

# Attach plugins
for _plugin in find_plugins(USER_PLUGINS_PATH):
    labthing.register_plugin(_plugin)

from openflexure_microscope.api.v2.views.captures import add_captures_to_labthing

add_captures_to_labthing(labthing, prefix="")
from openflexure_microscope.api.v2.views.state import add_states_to_labthing

add_states_to_labthing(labthing, prefix="")

from openflexure_microscope.api.v2.views.tasks import add_tasks_to_labthing

add_tasks_to_labthing(labthing, prefix="")

from openflexure_microscope.api.v2.views.streams import add_streams_to_labthing

add_streams_to_labthing(labthing, prefix="")

from openflexure_microscope.api.v2.views.actions import add_actions_to_labthing
# Attach captures resources
labthing.add_resource(views.CaptureList, f"/captures", endpoint="CaptureList")
labthing.register_property(views.CaptureList)
labthing.add_resource(
    views.CaptureResource, f"/captures/<id>", endpoint="CaptureResource"
)
labthing.add_resource(
    views.CaptureDownload,
    f"/captures/<id>/download/<filename>",
    endpoint="CaptureDownload",
)
labthing.add_resource(views.CaptureTags, f"/captures/<id>/tags", endpoint="CaptureTags")
labthing.add_resource(
    views.CaptureMetadata, f"/captures/<id>/metadata", endpoint="CaptureMetadata"
)

add_actions_to_labthing(labthing)
# Attach settings and status resources
labthing.add_resource(views.SettingsProperty, f"/settings")
labthing.register_property(views.SettingsProperty)
labthing.add_resource(views.NestedSettingsProperty, "/settings/<path:route>")
labthing.add_resource(views.StatusProperty, "/status")
labthing.register_property(views.StatusProperty)
labthing.add_resource(views.NestedStatusProperty, "/status/<path:route>")

# Attach streams resources
labthing.add_resource(views.MjpegStream, f"/streams/mjpeg")
labthing.register_property(views.MjpegStream)
labthing.add_resource(views.SnapshotStream, f"/streams/snapshot")
labthing.register_property(views.SnapshotStream)

# Attach microscope action resources
for name, action in views.enabled_root_actions().items():
    view_class = action["view_class"]
    rule = action["rule"]
    labthing.add_resource(view_class, "/actions{rule}")
    labthing.register_action(view_class)


@app.route("/routes")
+0 −1
Original line number Diff line number Diff line
from . import blueprints
+0 −1
Original line number Diff line number Diff line
from . import root, captures, settings, status, tasks, streams, actions
+0 −98
Original line number Diff line number Diff line
"""
Top-level representation of enabled actions
"""

from flask import Blueprint, url_for, jsonify

from openflexure_microscope.api.utilities import blueprint_for_module
from openflexure_microscope.utilities import get_docstring, description_from_view
from openflexure_microscope.api.views import MicroscopeView

from . import camera, stage, system


class ActionsAPI(MicroscopeView):
    def get(self):
        return jsonify(actions_representation())


_actions = {
    "capture": {
        "rule": "/camera/capture/",
        "view_class": camera.CaptureAPI,
        "conditions": True,
    },
    "previewStart": {
        "rule": "/camera/preview/start",
        "view_class": camera.GPUPreviewStartAPI,
        "conditions": True,
    },
    "previewStop": {
        "rule": "/camera/preview/stop",
        "view_class": camera.GPUPreviewStopAPI,
        "conditions": True,
    },
    "move": {
        "rule": "/stage/move/",
        "view_class": stage.MoveStageAPI,
        "conditions": True,
    },
    "zeroStage": {
        "rule": "/stage/zero/",
        "view_class": stage.ZeroStageAPI,
        "conditions": True,
    },
    "shutdown": {
        "rule": "/system/shutdown/",
        "view_class": system.ShutdownAPI,
        "conditions": system.is_raspberrypi(),
    },
    "reboot": {
        "rule": "/system/reboot/",
        "view_class": system.RebootAPI,
        "conditions": system.is_raspberrypi(),
    },
}


def enabled_actions():
    global _actions
    return {k: v for k, v in _actions.items() if v["conditions"]}


def actions_representation():
    global _actions

    actions = {}
    for name, action in enabled_actions().items():
        d = {
            "links": {"self": url_for(f".{name}")},
            "rule": action["rule"],
            "view_class": str(action["view_class"]),
        }

        d.update(description_from_view(action["view_class"]))

        actions[name] = d

    return actions


def construct_blueprint(microscope_obj):
    global _actions

    blueprint = blueprint_for_module(__name__)

    # For each enabled action route defined in our dictionary above
    for name, action in enabled_actions().items():
        # Add the action to our blueprint
        blueprint.add_url_rule(
            action["rule"],
            view_func=action["view_class"].as_view(name, microscope=microscope_obj),
        )

    blueprint.add_url_rule(
        "/", view_func=ActionsAPI.as_view("actions", microscope=microscope_obj)
    )

    return blueprint
+0 −172
Original line number Diff line number Diff line
from openflexure_microscope.api.utilities import get_bool, JsonResponse
from openflexure_microscope.api.views import MicroscopeView
from openflexure_microscope.utilities import filter_dict

import logging
from flask import jsonify, request, abort, url_for, redirect, send_file


class CaptureAPI(MicroscopeView):
    """
    Create a new image capture. 
    """

    def post(self):
        """
        Create a new image capture.

        .. :quickref: Actions; New capture

        **Example request**:

        .. sourcecode:: http

          POST /actions/camera/capture HTTP/1.1
          Accept: application/json

          {
            "filename": "myfirstcapture",
            "temporary": false,
            "use_video_port": true,
            "bayer": true,
            "size": {
                "width": 640,
                "height": 480
            }
          }

        :>header Accept: application/json

        :<json string filename: filename of stored capture
        :<json boolean temporary: delete the capture file on microscope after closing
        :<json boolean use_video_port: capture still image from the video port
        :<json boolean bayer: keep raw capture data in the image file
        :<json json size:   - **x** *(int)*: x-axis resize
                            - **y** *(int)*: y-axis resize

        :>json boolean available: availability of capture data
        :>json string filename: filename of capture
        :>json string id: unique id of the capture object
        :>json boolean temporary: delete the capture file on microscope after closing
        :>json boolean locked: file locked for modifications (mostly used for video recording)
        :>json string path: path on pi storage to the capture file, if available
        :>json boolean stream: capture stored in-memory as a BytesIO stream
        :>json json uri: - **download** *(string)*: api uri to the capture file download
                         - **state** *(string)*: api uri to the capture json representation

        :<header Content-Type: application/json
        :status 200: capture created
        """
        payload = JsonResponse(request)

        filename = payload.param("filename")
        temporary = payload.param("temporary", default=False, convert=bool)
        use_video_port = payload.param("use_video_port", default=False, convert=bool)
        bayer = payload.param("bayer", default=True, convert=bool)
        metadata = payload.param("metadata", default={}, convert=dict)
        tags = payload.param("tags", default=[], convert=list)

        resize = payload.param("size", default=None)
        if resize:
            if ("width" in resize) and ("height" in resize):
                resize = (
                    int(resize["width"]),
                    int(resize["height"]),
                )  # Convert dict to tuple
            else:
                abort(404)

        # Explicitally acquire lock (prevents empty files being created if lock is unavailable)
        with self.microscope.camera.lock:
            output = self.microscope.camera.new_image(
                temporary=temporary, filename=filename
            )

            self.microscope.camera.capture(
                output.file, use_video_port=use_video_port, resize=resize, bayer=bayer
            )

            # Inject system metadata
            output.put_metadata(self.microscope.metadata, system=True)

            # Insert custom metadata
            output.put_metadata(metadata)

            # Insert custom tags
            output.put_tags(tags)

        return jsonify(output.state)


class GPUPreviewStartAPI(MicroscopeView):
    """
    Start the onboard GPU preview.
    """

    def post(self):
        """
        Start the onboard GPU preview.
        Optional "window" parameter can be passed to control the position and size of the preview window,
        in the format ``[x, y, width, height]``.

        .. :quickref: Actions; Start on-board preview

        **Example requests**:

        .. sourcecode:: http

          POST /actions/camera/preview/start HTTP/1.1
          Accept: application/json

          {
            "window": [0, 0, 480, 320],
          }

        :>header Accept: application/json

        :<header Content-Type: application/json
        :status 200: preview started/stopped
        """
        payload = JsonResponse(request)

        window = payload.param("window", default=[])
        logging.debug(window)

        if len(window) != 4:
            fullscreen = True
            window = None
        else:
            fullscreen = False
            window = [int(w) for w in window]

        self.microscope.camera.start_preview(fullscreen=fullscreen, window=window)

        return jsonify(self.microscope.state)


class GPUPreviewStopAPI(MicroscopeView):
    """
    Stop the onboard GPU preview.
    """

    def post(self):
        """
        Stop the onboard GPU preview.

        .. :quickref: Actions; Stop on-board preview

        **Example requests**:

        .. sourcecode:: http

          POST /actions/camera/preview/stop HTTP/1.1
          Accept: application/json

        :>header Accept: application/json

        :<header Content-Type: application/json
        :status 200: preview started/stopped
        """

        self.microscope.camera.stop_preview()
        return jsonify(self.microscope.state)
Loading