Commit d79fd553 authored by jtc42's avatar jtc42
Browse files

Drafted actions routes

parent 65816123
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -143,7 +143,7 @@ task_blueprint = blueprints.task.construct_blueprint(api_microscope)
app.register_blueprint(task_blueprint, url_prefix=uri("/task", "v1"))

### V2
# Tasks routes
# Root routes
v2_stream_blueprint = v2.blueprints.stream.construct_blueprint(api_microscope)
app.register_blueprint(v2_stream_blueprint, url_prefix=uri("/", "v2"))

@@ -167,6 +167,10 @@ app.register_blueprint(v2_plugin_blueprint, url_prefix=uri("/plugins", "v2"))
v2_tasks_blueprint = v2.blueprints.tasks.construct_blueprint(api_microscope)
app.register_blueprint(v2_tasks_blueprint, url_prefix=uri("/tasks", "v2"))

# Actions routes
v2_actions_blueprint = v2.blueprints.actions.construct_blueprint(api_microscope)
app.register_blueprint(v2_actions_blueprint, url_prefix=uri("/actions", "v2"))


@app.route("/routes")
def routes():
+87 −0
Original line number Diff line number Diff line
from flask import Blueprint, url_for, jsonify
from sys import platform

from . import camera, stage, system

_actions = {
    "capture": {
        "rule": "/camera/capture/",
        "view_class": camera.CaptureAPI,
        "description": "Take a single still capture",
        "conditions": True
    },
    "preview_start": {
        "rule": "/camera/preview/start",
        "view_class": camera.GPUPreviewStartAPI,
        "description": "Start the on-board GPU preview",
        "conditions": True
    },
    "preview_stop": {
        "rule": "/camera/preview/stop",
        "view_class": camera.GPUPreviewStopAPI,
        "description": "Stop the on-board GPU preview",
        "conditions": True
    },
    "move": {
        "rule": "/stage/move/",
        "view_class": stage.MoveStageAPI,
        "description": "Move the microscope stage",
        "conditions": True
    },
    "shutdown": {
        "rule": "/system/shutdown/",
        "view_class": system.ShutdownAPI,
        "description": "Shutdown the device",
        "conditions": (platform == "linux")
    },
    "reboot": {
        "rule": "/system/reboot/",
        "view_class": system.RebootAPI,
        "description": "Reboot the device",
        "conditions": (platform == "linux")
    }
}


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}")},
            "description": action["description"],
            "rule": action["rule"],
            "view_class": str(action["view_class"])
        }

        actions[name] = d

    return actions


def construct_blueprint(microscope_obj):
    global _actions

    blueprint = Blueprint("actions_blueprint", __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.route("/")
    def representation():
        return jsonify(actions_representation())

    return blueprint
+166 −0
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):
    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
            system_metadata = {
                    "microscope_settings": self.microscope.read_settings(),
                    "microscope_state": self.microscope.state,
                    "microscope_id": self.microscope.id,
                    "microscope_name": self.microscope.name,
                }
            output.system_metadata.update(system_metadata)

            # Insert custom metadata
            output.put_metadata(metadata)

            # Insert custom tags
            output.put_tags(tags)

        return jsonify(output.state)


class GPUPreviewStartAPI(MicroscopeView):
    def post(self, operation):
        """
        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):
    def post(self, operation):
        """
        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)
+54 −0
Original line number Diff line number Diff line
from openflexure_microscope.api.utilities import JsonResponse
from openflexure_microscope.api.views import MicroscopeView
from openflexure_microscope.utilities import axes_to_array, filter_dict

from flask import Blueprint, jsonify, request

import logging


class MoveStageAPI(MicroscopeView):
    def post(self):
        """
        Set x, y and z positions of the stage.

        .. :quickref: Position; Update current position

        :reqheader Accept: application/json
        :<json boolean absolute: (true) move to absolute position, (false) move by relative amount
        :<json boolean force: allow moving by more than programmed limit
        :<json int x: x steps
        :<json int y: y steps
        :<json int z: z steps

        """
        # Create response object
        payload = JsonResponse(request)
        logging.debug(payload.json)

        # Handle absolute positioning (calculate a relative move from current position and target)
        if (payload.param("absolute") is True) and (
            self.microscope.stage
        ):  # Only if stage exists
            target_position = axes_to_array(payload.json, ["x", "y", "z"])
            logging.debug("TARGET: {}".format(target_position))
            position = [
                target_position[i] - self.microscope.stage.position[i] for i in range(3)
            ]
            logging.debug("DELTA: {}".format(position))

        else:
            # Get coordinates from payload
            position = axes_to_array(payload.json, ["x", "y", "z"], [0, 0, 0])

        logging.debug(position)

        # Move if stage exists
        if self.microscope.stage:
            # Explicitally acquire lock
            with self.microscope.stage.lock:
                self.microscope.stage.move_rel(position)

        out = filter_dict(self.microscope.state, ["stage", "position"])

        return jsonify(out)
+33 −0
Original line number Diff line number Diff line
from openflexure_microscope.api.views import MicroscopeView

from flask import jsonify

import subprocess
import logging


# TODO: Make robust against different host OS
class ShutdownAPI(MicroscopeView):
    def post(self):
        """
        Attempt to shutdown the device

        .. :quickref: Actions; Shutdown

        """
        subprocess.Popen(['shutdown', '-h', 'now'])

        return '', 202


class RebootAPI(MicroscopeView):
    def post(self):
        """
        Attempt to shutdown the device

        .. :quickref: Actions; Shutdown

        """
        subprocess.Popen(['sudo', 'shutdown', '-r', 'now'])

        return '', 202
Loading