Moved task routes into base labthing
This commit is contained in:
parent
f2af359b8b
commit
1047257242
21 changed files with 123 additions and 1481 deletions
|
|
@ -16,16 +16,21 @@ from flask_cors import CORS
|
||||||
from openflexure_microscope.api.exceptions import JSONExceptionHandler
|
from openflexure_microscope.api.exceptions import JSONExceptionHandler
|
||||||
from openflexure_microscope.api.utilities import list_routes
|
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.api import v2
|
||||||
|
|
||||||
from openflexure_microscope.common.labthings.labthing import LabThing
|
from openflexure_microscope.common.labthings.labthing import LabThing
|
||||||
from openflexure_microscope.common.labthings.find import registered_plugins
|
from openflexure_microscope.common.labthings.find import registered_plugins
|
||||||
from openflexure_microscope.common.labthings.plugins import find_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.microscope import default_microscope as api_microscope
|
||||||
|
|
||||||
|
from openflexure_microscope.api.v2 import views
|
||||||
|
|
||||||
# Handle logging
|
# Handle logging
|
||||||
is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "")
|
is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "")
|
||||||
|
|
||||||
|
|
@ -78,33 +83,53 @@ CORS(app, resources=r"*")
|
||||||
# Make errors more API friendly
|
# Make errors more API friendly
|
||||||
handler = JSONExceptionHandler(app)
|
handler = JSONExceptionHandler(app)
|
||||||
|
|
||||||
# Attach lab devices
|
# Build a labthing
|
||||||
labthing = LabThing(app, prefix="/api/v2")
|
labthing = LabThing(app, prefix="/api/v2")
|
||||||
labthing.description = "Test LabThing-based API for OpenFlexure Microscope"
|
labthing.description = "Test LabThing-based API for OpenFlexure Microscope"
|
||||||
|
|
||||||
|
# Attach lab devices
|
||||||
labthing.register_device(api_microscope, "openflexure_microscope")
|
labthing.register_device(api_microscope, "openflexure_microscope")
|
||||||
|
|
||||||
|
# Attach plugins
|
||||||
for _plugin in find_plugins(USER_PLUGINS_PATH):
|
for _plugin in find_plugins(USER_PLUGINS_PATH):
|
||||||
labthing.register_plugin(_plugin)
|
labthing.register_plugin(_plugin)
|
||||||
|
|
||||||
from openflexure_microscope.api.v2.views.captures import add_captures_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_captures_to_labthing(labthing, prefix="")
|
# Attach settings and status resources
|
||||||
from openflexure_microscope.api.v2.views.state import add_states_to_labthing
|
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>")
|
||||||
|
|
||||||
add_states_to_labthing(labthing, prefix="")
|
# 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)
|
||||||
|
|
||||||
from openflexure_microscope.api.v2.views.tasks import add_tasks_to_labthing
|
# Attach microscope action resources
|
||||||
|
for name, action in views.enabled_root_actions().items():
|
||||||
add_tasks_to_labthing(labthing, prefix="")
|
view_class = action["view_class"]
|
||||||
|
rule = action["rule"]
|
||||||
from openflexure_microscope.api.v2.views.streams import add_streams_to_labthing
|
labthing.add_resource(view_class, "/actions{rule}")
|
||||||
|
labthing.register_action(view_class)
|
||||||
add_streams_to_labthing(labthing, prefix="")
|
|
||||||
|
|
||||||
from openflexure_microscope.api.v2.views.actions import add_actions_to_labthing
|
|
||||||
|
|
||||||
add_actions_to_labthing(labthing)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/routes")
|
@app.route("/routes")
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from . import blueprints
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from . import root, captures, settings, status, tasks, streams, actions
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
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)
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
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):
|
|
||||||
"""
|
|
||||||
Handle stage movements.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def post(self):
|
|
||||||
"""
|
|
||||||
Set x, y and z positions of the stage.
|
|
||||||
|
|
||||||
.. :quickref: Actions; Move stage
|
|
||||||
|
|
||||||
: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)
|
|
||||||
else:
|
|
||||||
logging.warning("Unable to move. No stage found.")
|
|
||||||
|
|
||||||
return jsonify(self.microscope.status["stage"]["position"])
|
|
||||||
|
|
||||||
|
|
||||||
class ZeroStageAPI(MicroscopeView):
|
|
||||||
"""
|
|
||||||
Zero stage coordinates
|
|
||||||
"""
|
|
||||||
|
|
||||||
def post(self):
|
|
||||||
"""
|
|
||||||
Set the current position to zero
|
|
||||||
|
|
||||||
.. :quickref: Actions; Zero stage
|
|
||||||
|
|
||||||
:reqheader Accept: application/json
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.microscope.stage.zero_position()
|
|
||||||
|
|
||||||
return jsonify(self.microscope.status["stage"])
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
from openflexure_microscope.api.views import MicroscopeView
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
from sys import platform
|
|
||||||
|
|
||||||
|
|
||||||
def is_raspberrypi(raise_on_errors=False):
|
|
||||||
"""
|
|
||||||
Checks if Raspberry Pi.
|
|
||||||
"""
|
|
||||||
# I mean, if it works, it works...
|
|
||||||
return os.path.exists("/usr/bin/raspi-config")
|
|
||||||
|
|
||||||
|
|
||||||
class ShutdownAPI(MicroscopeView):
|
|
||||||
"""
|
|
||||||
Attempt to shutdown the device
|
|
||||||
"""
|
|
||||||
|
|
||||||
def post(self):
|
|
||||||
"""
|
|
||||||
Attempt to shutdown the device
|
|
||||||
|
|
||||||
.. :quickref: Actions; Shutdown
|
|
||||||
|
|
||||||
"""
|
|
||||||
subprocess.Popen(["sudo", "shutdown", "-h", "now"])
|
|
||||||
|
|
||||||
return "{}", 201
|
|
||||||
|
|
||||||
|
|
||||||
class RebootAPI(MicroscopeView):
|
|
||||||
"""
|
|
||||||
Attempt to reboot the device
|
|
||||||
"""
|
|
||||||
|
|
||||||
def post(self):
|
|
||||||
"""
|
|
||||||
Attempt to shutdown the device
|
|
||||||
|
|
||||||
.. :quickref: Actions; Shutdown
|
|
||||||
|
|
||||||
"""
|
|
||||||
subprocess.Popen(["sudo", "shutdown", "-r", "now"])
|
|
||||||
|
|
||||||
return "{}", 201
|
|
||||||
|
|
@ -1,416 +0,0 @@
|
||||||
"""
|
|
||||||
Top-level representation of all acquired captures
|
|
||||||
"""
|
|
||||||
|
|
||||||
from openflexure_microscope.api.utilities import get_bool, JsonResponse
|
|
||||||
from openflexure_microscope.api.views import MicroscopeView
|
|
||||||
from openflexure_microscope.utilities import filter_dict
|
|
||||||
from openflexure_microscope.api.utilities import blueprint_for_module
|
|
||||||
|
|
||||||
from flask import jsonify, request, abort, url_for, redirect, send_file, Blueprint
|
|
||||||
|
|
||||||
|
|
||||||
def captures_representation(capture_list: list, include_unavailable: bool = False):
|
|
||||||
"""
|
|
||||||
Generate a dictionary representation of all captures, including Flask route URLs
|
|
||||||
|
|
||||||
Args:
|
|
||||||
capture_list (list): List of capture objects
|
|
||||||
include_unavailable (bool): Include unavailable captures in response?
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Dictionary representation of all captures
|
|
||||||
|
|
||||||
"""
|
|
||||||
if include_unavailable:
|
|
||||||
captures = {image.id: image.state for image in capture_list}
|
|
||||||
else:
|
|
||||||
captures = {
|
|
||||||
image.id: image.state for image in capture_list if image.state["available"]
|
|
||||||
}
|
|
||||||
|
|
||||||
for capture_key, capture_repr in captures.items():
|
|
||||||
# Add API routes to returned representations
|
|
||||||
extra_state = {
|
|
||||||
"links": {
|
|
||||||
"self": "{}".format(url_for(".capture", capture_id=capture_key)),
|
|
||||||
"download": "{}".format(
|
|
||||||
url_for(
|
|
||||||
".capture_download",
|
|
||||||
capture_id=capture_key,
|
|
||||||
filename=capture_repr["filename"],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"tags": "{}".format(url_for(".capture_tags", capture_id=capture_key)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
captures[capture_key].update(extra_state)
|
|
||||||
|
|
||||||
return captures
|
|
||||||
|
|
||||||
|
|
||||||
class ListAPI(MicroscopeView):
|
|
||||||
def get(self):
|
|
||||||
"""
|
|
||||||
Get list of image captures.
|
|
||||||
|
|
||||||
.. :quickref: Captures; Get collection of captures
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
:query include_unavailable: return json representations of captures that have been completely deleted
|
|
||||||
|
|
||||||
:>jsonarr boolean available: availability of capture data
|
|
||||||
:>jsonarr string filename: filename of capture
|
|
||||||
:>jsonarr string id: unique id of the capture object
|
|
||||||
:>jsonarr boolean keep_on_disk: keep the capture file on microscope after closing
|
|
||||||
:>jsonarr boolean locked: file locked for modifications (mostly used for video recording)
|
|
||||||
:>jsonarr string path: path on pi storage to the capture file, if available
|
|
||||||
:>jsonarr boolean stream: capture stored in-memory as a BytesIO stream
|
|
||||||
:>jsonarr 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 found
|
|
||||||
:status 404: no capture found with that id
|
|
||||||
"""
|
|
||||||
representation = captures_representation(self.microscope.camera.images)
|
|
||||||
|
|
||||||
return jsonify(representation)
|
|
||||||
|
|
||||||
def delete(self):
|
|
||||||
"""
|
|
||||||
Delete all captures
|
|
||||||
|
|
||||||
.. :quickref: Captures; Delete all captures
|
|
||||||
"""
|
|
||||||
for image in self.microscope.camera.images:
|
|
||||||
image.delete()
|
|
||||||
|
|
||||||
captures = [image.state for image in self.microscope.camera.images]
|
|
||||||
|
|
||||||
return jsonify(captures)
|
|
||||||
|
|
||||||
|
|
||||||
class CaptureAPI(MicroscopeView):
|
|
||||||
def get(self, capture_id):
|
|
||||||
"""
|
|
||||||
Get JSON representation of a capture
|
|
||||||
|
|
||||||
.. :quickref: Capture; Get capture
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
GET /camera/capture/d0b2067abbb946f19351e075c5e7cd5b/ HTTP/1.1
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
**Example response**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Vary: Accept
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"available": true,
|
|
||||||
"bytestream": false,
|
|
||||||
"keep_on_disk": false,
|
|
||||||
"locked": false,
|
|
||||||
"metadata": {
|
|
||||||
"filename": "2018-11-16_10-21-53.jpeg",
|
|
||||||
"id": "d0b2067abbb946f19351e075c5e7cd5b",
|
|
||||||
"path": "capture/2018-11-16_10-21-53.jpeg",
|
|
||||||
"time": "2018-11-16_10-21-53"
|
|
||||||
}
|
|
||||||
"uri": {
|
|
||||||
"download": "/api/v1/capture/d0b2067abbb946f19351e075c5e7cd5b/download",
|
|
||||||
"state": "/api/v1/capture/d0b2067abbb946f19351e075c5e7cd5b/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:>json boolean available: availability of capture data
|
|
||||||
:>json string filename: filename of capture
|
|
||||||
:>json string id: unique id of the capture object
|
|
||||||
:>json boolean keep_on_disk: keep 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
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
representation = captures_representation(self.microscope.camera.images)[
|
|
||||||
capture_id
|
|
||||||
]
|
|
||||||
except KeyError:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
return jsonify(representation)
|
|
||||||
|
|
||||||
def delete(self, capture_id):
|
|
||||||
"""
|
|
||||||
Delete all capture data from the Pi, even if `keep_on_disk=true;`.
|
|
||||||
|
|
||||||
.. :quickref: Capture; Delete capture.
|
|
||||||
"""
|
|
||||||
capture_obj = self.microscope.camera.image_from_id(capture_id)
|
|
||||||
|
|
||||||
if not capture_obj:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
capture_obj.delete()
|
|
||||||
|
|
||||||
return jsonify({"return": capture_id})
|
|
||||||
|
|
||||||
def put(self, capture_id):
|
|
||||||
"""
|
|
||||||
Add arbitrary metadata to the capture
|
|
||||||
|
|
||||||
.. :quickref: Capture; Update capture metadata
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
PUT /camera/capture/d0b2067abbb946f19351e075c5e7cd5b HTTP/1.1
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"user_id": "ofm_1234",
|
|
||||||
"patient_number": 1452,
|
|
||||||
}
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
|
|
||||||
:<header Content-Type: application/json
|
|
||||||
:status 200: metadata updated
|
|
||||||
"""
|
|
||||||
|
|
||||||
capture_obj = self.microscope.camera.image_from_id(capture_id)
|
|
||||||
|
|
||||||
if not capture_obj:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
data_dict = JsonResponse(request).json
|
|
||||||
|
|
||||||
capture_obj.put_metadata(data_dict)
|
|
||||||
|
|
||||||
capture_metadata = capture_obj.state
|
|
||||||
|
|
||||||
return jsonify(capture_metadata)
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadRedirectAPI(MicroscopeView):
|
|
||||||
def get(self, capture_id):
|
|
||||||
"""
|
|
||||||
Return image data for a capture.
|
|
||||||
|
|
||||||
Return capture data as an image file with the requested filename.
|
|
||||||
I.e., `/(capture_id)/download/foo.jpeg` will download the image as
|
|
||||||
`foo.jpeg`, regardless of the capture's initially set filename.
|
|
||||||
|
|
||||||
Route automatically redirects to download the capture under it's currently set filename.
|
|
||||||
I.e., `/(capture_id)/download` will
|
|
||||||
redirect to `/(capture_id)/download/(filename)`.
|
|
||||||
|
|
||||||
.. :quickref: Capture; Download capture image
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
GET /camera/capture/d0b2067abbb946f19351e075c5e7cd5b/download/2018-11-20_16-04-17.jpeg HTTP/1.1
|
|
||||||
Accept: image/jpeg
|
|
||||||
|
|
||||||
:>header Accept: image/jpeg
|
|
||||||
:query thumbnail: return an image thumbnail e.g. ?thumbnail=true
|
|
||||||
|
|
||||||
:>header Content-Type: image/jpeg
|
|
||||||
:status 200: capture data found
|
|
||||||
:status 404: no capture found with that id
|
|
||||||
|
|
||||||
"""
|
|
||||||
capture_obj = self.microscope.camera.image_from_id(capture_id)
|
|
||||||
|
|
||||||
if not capture_obj or not capture_obj.state["available"]:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
thumbnail = get_bool(request.args.get("thumbnail"))
|
|
||||||
|
|
||||||
return redirect(
|
|
||||||
url_for(
|
|
||||||
".capture_download",
|
|
||||||
capture_id=capture_id,
|
|
||||||
filename=capture_obj.filename,
|
|
||||||
thumbnail=thumbnail,
|
|
||||||
),
|
|
||||||
code=307,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadAPI(MicroscopeView):
|
|
||||||
def get(self, capture_id, filename):
|
|
||||||
|
|
||||||
capture_obj = self.microscope.camera.image_from_id(capture_id)
|
|
||||||
|
|
||||||
if not capture_obj or not capture_obj.state["available"]:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
thumbnail = get_bool(request.args.get("thumbnail"))
|
|
||||||
|
|
||||||
# If no filename is specified, redirect to the capture's currently set filename
|
|
||||||
if not filename:
|
|
||||||
return redirect(
|
|
||||||
url_for(
|
|
||||||
"capture_download",
|
|
||||||
capture_id=capture_id,
|
|
||||||
filename=capture_obj.filename,
|
|
||||||
thumbnail=thumbnail,
|
|
||||||
),
|
|
||||||
code=307,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Download the image data using the requested filename
|
|
||||||
if thumbnail:
|
|
||||||
img = capture_obj.thumbnail
|
|
||||||
else:
|
|
||||||
img = capture_obj.data
|
|
||||||
|
|
||||||
return send_file(img, mimetype="image/jpeg")
|
|
||||||
|
|
||||||
|
|
||||||
class TagsAPI(MicroscopeView):
|
|
||||||
def get(self, capture_id):
|
|
||||||
"""
|
|
||||||
Return tag list for a capture.
|
|
||||||
|
|
||||||
.. :quickref: Capture; Show capture tags
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
GET /camera/capture/d0b2067abbb946f19351e075c5e7cd5b/tags HTTP/1.1
|
|
||||||
Accept: text/json
|
|
||||||
|
|
||||||
:>header Accept: text/json
|
|
||||||
|
|
||||||
:>header Content-Type: text/json
|
|
||||||
:status 200: capture data found
|
|
||||||
:status 404: no capture found with that id
|
|
||||||
"""
|
|
||||||
capture_obj = self.microscope.camera.image_from_id(capture_id)
|
|
||||||
|
|
||||||
if not capture_obj or not capture_obj.state["available"]:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
return jsonify(capture_obj.tags)
|
|
||||||
|
|
||||||
def put(self, capture_id):
|
|
||||||
"""
|
|
||||||
Add tags to the capture
|
|
||||||
|
|
||||||
.. :quickref: Capture; Update capture tags
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
PUT /camera/capture/d0b2067abbb946f19351e075c5e7cd5b/tags HTTP/1.1
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
["tests", "mytag", "someothertag"]
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
|
|
||||||
:<header Content-Type: application/json
|
|
||||||
:status 200: metadata updated
|
|
||||||
"""
|
|
||||||
|
|
||||||
capture_obj = self.microscope.camera.image_from_id(capture_id)
|
|
||||||
|
|
||||||
if not capture_obj or not capture_obj.state["available"]:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
data_dict = JsonResponse(request).json
|
|
||||||
|
|
||||||
if type(data_dict) != list:
|
|
||||||
return abort(400)
|
|
||||||
|
|
||||||
capture_obj.put_tags(data_dict)
|
|
||||||
|
|
||||||
return jsonify(capture_obj.tags)
|
|
||||||
|
|
||||||
def delete(self, capture_id):
|
|
||||||
"""
|
|
||||||
Delete tags from the capture
|
|
||||||
|
|
||||||
.. :quickref: Capture; Delete tags.
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
DELETE /camera/capture/d0b2067abbb946f19351e075c5e7cd5b/tags HTTP/1.1
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
["tests", "mytag"]
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
|
|
||||||
:<header Content-Type: application/json
|
|
||||||
:status 200: metadata updated
|
|
||||||
"""
|
|
||||||
capture_obj = self.microscope.camera.image_from_id(capture_id)
|
|
||||||
|
|
||||||
if not capture_obj:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
data_dict = JsonResponse(request).json
|
|
||||||
|
|
||||||
if type(data_dict) != list:
|
|
||||||
return abort(400)
|
|
||||||
|
|
||||||
for tag in data_dict:
|
|
||||||
capture_obj.delete_tag(str(tag))
|
|
||||||
|
|
||||||
return jsonify(capture_obj.tags)
|
|
||||||
|
|
||||||
|
|
||||||
def construct_blueprint(microscope_obj):
|
|
||||||
|
|
||||||
blueprint = blueprint_for_module(__name__)
|
|
||||||
|
|
||||||
# Tag routes
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/<capture_id>/tags",
|
|
||||||
view_func=TagsAPI.as_view("capture_tags", microscope=microscope_obj),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Capture routes
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/<capture_id>/download/<filename>",
|
|
||||||
view_func=DownloadAPI.as_view("capture_download", microscope=microscope_obj),
|
|
||||||
)
|
|
||||||
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/<capture_id>/download",
|
|
||||||
view_func=DownloadRedirectAPI.as_view(
|
|
||||||
"capture_download_redirect", microscope=microscope_obj
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/<capture_id>/",
|
|
||||||
view_func=CaptureAPI.as_view("capture", microscope=microscope_obj),
|
|
||||||
)
|
|
||||||
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/", view_func=ListAPI.as_view("captures", microscope=microscope_obj)
|
|
||||||
)
|
|
||||||
return blueprint
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
from flask import Blueprint, jsonify, url_for
|
|
||||||
from openflexure_microscope import Microscope
|
|
||||||
from openflexure_microscope.api.views import MicroscopeView
|
|
||||||
|
|
||||||
from openflexure_microscope.utilities import get_docstring, bottom_level_name
|
|
||||||
from openflexure_microscope.api.utilities import blueprint_name_for_module
|
|
||||||
from openflexure_microscope.api.v2.blueprints import (
|
|
||||||
settings,
|
|
||||||
status,
|
|
||||||
captures,
|
|
||||||
actions,
|
|
||||||
streams,
|
|
||||||
)
|
|
||||||
|
|
||||||
# List of submodules containing create_blueprint methods using standard blueprint_for_module naming
|
|
||||||
_root_blueprint_modules = [settings, status, captures, actions, streams]
|
|
||||||
|
|
||||||
|
|
||||||
def root_representation():
|
|
||||||
"""
|
|
||||||
Generate a dictionar representation of all top-level blueprint rules
|
|
||||||
"""
|
|
||||||
global _root_blueprint_modules
|
|
||||||
d = {}
|
|
||||||
|
|
||||||
for blueprint_module in _root_blueprint_modules:
|
|
||||||
module_short_name = bottom_level_name(blueprint_module)
|
|
||||||
blueprint_name = blueprint_name_for_module(blueprint_module.__name__)
|
|
||||||
|
|
||||||
d[module_short_name] = {
|
|
||||||
"name": blueprint_module.__name__,
|
|
||||||
"description": get_docstring(blueprint_module),
|
|
||||||
"links": {"self": url_for(f"{blueprint_name}.{module_short_name}")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
class RootAPI(MicroscopeView):
|
|
||||||
def get(self):
|
|
||||||
|
|
||||||
return jsonify(root_representation())
|
|
||||||
|
|
||||||
|
|
||||||
def construct_blueprint(microscope_obj):
|
|
||||||
blueprint = Blueprint("root_blueprint", __name__)
|
|
||||||
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/", view_func=RootAPI.as_view("root_repr", microscope=microscope_obj)
|
|
||||||
)
|
|
||||||
return blueprint
|
|
||||||
|
|
@ -1,198 +0,0 @@
|
||||||
"""
|
|
||||||
Writeable settings for the microscope, and attached hardware
|
|
||||||
"""
|
|
||||||
|
|
||||||
from openflexure_microscope.api.utilities import gen, JsonResponse
|
|
||||||
from openflexure_microscope.api.views import MicroscopeView
|
|
||||||
from openflexure_microscope.api.utilities import blueprint_for_module
|
|
||||||
|
|
||||||
from openflexure_microscope.utilities import get_by_path, set_by_path, create_from_path
|
|
||||||
|
|
||||||
from flask import Blueprint, jsonify, request, abort
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
class SettingsAPI(MicroscopeView):
|
|
||||||
def get(self):
|
|
||||||
"""
|
|
||||||
JSON representation of the microscope settings.
|
|
||||||
|
|
||||||
.. :quickref: Settings; Get microscope settings
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
GET /settings/ HTTP/1.1
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
**Example response**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Vary: Accept
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "0e2c6fac5421429aac67c7903107bdd8",
|
|
||||||
"name": "docuscope-2000",
|
|
||||||
"fov": [4100, 3146],
|
|
||||||
"camera_settings": {
|
|
||||||
"image_resolution": [2592, 1944],
|
|
||||||
"numpy_resolution": [1312, 976],
|
|
||||||
"video_resolution": [832, 624],
|
|
||||||
"jpeg_quality": 75,
|
|
||||||
"picamera_settings": {
|
|
||||||
"analog_gain": 1.0,
|
|
||||||
"digital_gain": 1.0,
|
|
||||||
"awb_gains": [0.92578125, 2.94921875],
|
|
||||||
"awb_mode": "off",
|
|
||||||
"exposure_mode": "off",
|
|
||||||
"framerate": 24.0,
|
|
||||||
"saturation": 0,
|
|
||||||
"shutter_speed": 5378
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"stage_settings": {
|
|
||||||
"backlash": {
|
|
||||||
"x": 256,
|
|
||||||
"y": 256,
|
|
||||||
"z": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"plugins": [
|
|
||||||
"openflexure_microscope.plugins.default.autofocus:AutofocusPlugin",
|
|
||||||
"openflexure_microscope.plugins.default.scan:ScanPlugin",
|
|
||||||
"openflexure_microscope.plugins.default.camera_calibration:Plugin"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
:>header Content-Type: application/json
|
|
||||||
:status 200: state available
|
|
||||||
"""
|
|
||||||
return jsonify(self.microscope.read_settings())
|
|
||||||
|
|
||||||
def put(self):
|
|
||||||
"""
|
|
||||||
Modify microscope configuration
|
|
||||||
|
|
||||||
.. :quickref: Settings; Update microscope settings
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
PUT /config HTTP/1.1
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "0e2c6fac5421429aac67c7903107bdd8",
|
|
||||||
"name": "docuscope-2000",
|
|
||||||
"fov": [4100, 3146],
|
|
||||||
"camera_settings": {
|
|
||||||
"image_resolution": [2592, 1944],
|
|
||||||
"numpy_resolution": [1312, 976],
|
|
||||||
"video_resolution": [832, 624],
|
|
||||||
"jpeg_quality": 75,
|
|
||||||
"picamera_settings": {
|
|
||||||
"analog_gain": 1.0,
|
|
||||||
"digital_gain": 1.0,
|
|
||||||
"awb_gains": [0.92578125, 2.94921875],
|
|
||||||
"awb_mode": "off",
|
|
||||||
"exposure_mode": "off",
|
|
||||||
"framerate": 24.0,
|
|
||||||
"saturation": 0,
|
|
||||||
"shutter_speed": 5378
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"stage_settings": {
|
|
||||||
"backlash": {
|
|
||||||
"x": 256,
|
|
||||||
"y": 256,
|
|
||||||
"z": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"plugins": [
|
|
||||||
"openflexure_microscope.plugins.default.autofocus:AutofocusPlugin",
|
|
||||||
"openflexure_microscope.plugins.default.scan:ScanPlugin",
|
|
||||||
"openflexure_microscope.plugins.default.camera_calibration:Plugin"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
|
|
||||||
:<json string id: Unique string identifier of the microscope.
|
|
||||||
:<json string name: Friendly name for the microscope
|
|
||||||
:<json array fov: Field of view (motor steps per full width and height of frame)
|
|
||||||
:<json json camera_settings: - **image_resolution** *(array)*: Resolution of full image captures
|
|
||||||
- **numpy_resolution** *(array)*: Resolution of full numpy array captures
|
|
||||||
- **video_resolution** *(array)*: Resolution of video recordings, low res image captures, and the preview stream
|
|
||||||
- **jpeg_quality** *(int)*: Quality in which to store JPEG capture data
|
|
||||||
- **picamera_settings** *(json)*: Key-value pairs to apply directly to any attached PiCamera object
|
|
||||||
:<json json stage_settings: - **backlash** *(json)*: x, y, and z backlash compensation, in motor steps
|
|
||||||
:<json array plugins: Array of plugin paths to load. Requires reloading the microscope object after applying
|
|
||||||
|
|
||||||
:<header Content-Type: application/json
|
|
||||||
:status 200: capture created
|
|
||||||
|
|
||||||
"""
|
|
||||||
payload = JsonResponse(request)
|
|
||||||
|
|
||||||
logging.debug("Updating settings from PUT request:")
|
|
||||||
logging.debug(payload.json)
|
|
||||||
|
|
||||||
self.microscope.apply_settings(payload.json)
|
|
||||||
self.microscope.save_settings()
|
|
||||||
|
|
||||||
return jsonify(self.microscope.read_settings())
|
|
||||||
|
|
||||||
|
|
||||||
class NestedSettingsAPI(MicroscopeView):
|
|
||||||
def get(self, route):
|
|
||||||
|
|
||||||
keys = route.split("/")
|
|
||||||
|
|
||||||
try:
|
|
||||||
value = get_by_path(self.microscope.read_settings(), keys)
|
|
||||||
except KeyError:
|
|
||||||
return abort(404)
|
|
||||||
|
|
||||||
return jsonify(value)
|
|
||||||
|
|
||||||
def put(self, route):
|
|
||||||
keys = route.split("/")
|
|
||||||
|
|
||||||
payload = JsonResponse(request)
|
|
||||||
|
|
||||||
logging.debug("Updating settings from PUT request:")
|
|
||||||
logging.debug(payload.json)
|
|
||||||
|
|
||||||
dictionary = create_from_path(keys)
|
|
||||||
set_by_path(dictionary, keys, payload.json)
|
|
||||||
|
|
||||||
logging.debug(f"Applying settings: {dictionary}")
|
|
||||||
|
|
||||||
self.microscope.apply_settings(dictionary)
|
|
||||||
self.microscope.save_settings()
|
|
||||||
|
|
||||||
return self.get(route)
|
|
||||||
|
|
||||||
|
|
||||||
def construct_blueprint(microscope_obj):
|
|
||||||
|
|
||||||
blueprint = blueprint_for_module(__name__)
|
|
||||||
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/", view_func=SettingsAPI.as_view("settings", microscope=microscope_obj)
|
|
||||||
)
|
|
||||||
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/<path:route>",
|
|
||||||
view_func=NestedSettingsAPI.as_view(
|
|
||||||
"nested_settings", microscope=microscope_obj
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return blueprint
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
"""
|
|
||||||
Read-only status of the microscope, and attached hardware info
|
|
||||||
"""
|
|
||||||
|
|
||||||
from openflexure_microscope.api.views import MicroscopeView
|
|
||||||
from openflexure_microscope.api.utilities import blueprint_for_module
|
|
||||||
from openflexure_microscope.utilities import get_by_path
|
|
||||||
|
|
||||||
from flask import Blueprint, jsonify, abort
|
|
||||||
|
|
||||||
|
|
||||||
class StatusAPI(MicroscopeView):
|
|
||||||
def get(self):
|
|
||||||
"""
|
|
||||||
JSON representation of the microscope status (read-only properties, modifiable with actions)
|
|
||||||
|
|
||||||
.. :quickref: Status; Microscope status
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
GET /status/ HTTP/1.1
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
**Example response**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Vary: Accept
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"camera": {
|
|
||||||
"preview_active": false,
|
|
||||||
"record_active": false,
|
|
||||||
"stream_active": true
|
|
||||||
},
|
|
||||||
"plugin": {},
|
|
||||||
"stage": {
|
|
||||||
"backlash": {
|
|
||||||
"x": 128,
|
|
||||||
"y": 128,
|
|
||||||
"z": 128
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"x": -8080,
|
|
||||||
"y": 5665,
|
|
||||||
"z": -12600
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
:>header Content-Type: application/json
|
|
||||||
:status 200: state available
|
|
||||||
"""
|
|
||||||
return jsonify(self.microscope.status)
|
|
||||||
|
|
||||||
|
|
||||||
class NestedStatusAPI(MicroscopeView):
|
|
||||||
def get(self, route):
|
|
||||||
|
|
||||||
keys = route.split("/")
|
|
||||||
|
|
||||||
try:
|
|
||||||
value = get_by_path(self.microscope.status, keys)
|
|
||||||
except KeyError:
|
|
||||||
return abort(404)
|
|
||||||
|
|
||||||
return jsonify(value)
|
|
||||||
|
|
||||||
|
|
||||||
def construct_blueprint(microscope_obj):
|
|
||||||
|
|
||||||
blueprint = blueprint_for_module(__name__)
|
|
||||||
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/", view_func=StatusAPI.as_view("status", microscope=microscope_obj)
|
|
||||||
)
|
|
||||||
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/<path:route>",
|
|
||||||
view_func=NestedStatusAPI.as_view("nested_status", microscope=microscope_obj),
|
|
||||||
)
|
|
||||||
|
|
||||||
return blueprint
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
"""
|
|
||||||
Top-level description of routes related to live camera stream data
|
|
||||||
"""
|
|
||||||
|
|
||||||
from openflexure_microscope.api.utilities import gen, JsonResponse
|
|
||||||
from openflexure_microscope.api.views import MicroscopeView
|
|
||||||
from openflexure_microscope.api.utilities import (
|
|
||||||
blueprint_for_module,
|
|
||||||
blueprint_name_for_module,
|
|
||||||
)
|
|
||||||
from openflexure_microscope.utilities import description_from_view
|
|
||||||
|
|
||||||
from flask import Response, Blueprint, jsonify, request, url_for
|
|
||||||
|
|
||||||
|
|
||||||
class StreamAPI(MicroscopeView):
|
|
||||||
def get(self):
|
|
||||||
return jsonify(streams_representation())
|
|
||||||
|
|
||||||
|
|
||||||
class MjpegAPI(MicroscopeView):
|
|
||||||
"""
|
|
||||||
Real-time MJPEG stream from the microscope camera
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
"""
|
|
||||||
Real-time MJPEG stream from the microscope camera
|
|
||||||
|
|
||||||
.. :quickref: Streams; Camera MJPEG stream
|
|
||||||
|
|
||||||
:>header Accept: image/jpeg
|
|
||||||
:>header Content-Type: image/jpeg
|
|
||||||
:status 200: stream active
|
|
||||||
"""
|
|
||||||
# Restart stream worker thread
|
|
||||||
self.microscope.camera.start_worker()
|
|
||||||
|
|
||||||
return Response(
|
|
||||||
gen(self.microscope.camera),
|
|
||||||
mimetype="multipart/x-mixed-replace; boundary=frame",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SnapshotAPI(MicroscopeView):
|
|
||||||
"""
|
|
||||||
Single JPEG snapshot from the camera stream
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
"""
|
|
||||||
Single snapshot from the camera stream
|
|
||||||
|
|
||||||
.. :quickref: Streams; Camera snapshot
|
|
||||||
|
|
||||||
:>header Accept: image/jpeg
|
|
||||||
:>header Content-Type: image/jpeg
|
|
||||||
:status 200: stream active
|
|
||||||
"""
|
|
||||||
# Restart stream worker thread
|
|
||||||
self.microscope.camera.start_worker()
|
|
||||||
|
|
||||||
return Response(self.microscope.camera.get_frame(), mimetype="image/jpeg")
|
|
||||||
|
|
||||||
|
|
||||||
_streams = {
|
|
||||||
"mjpeg": {"rule": "/mjpeg", "view_class": MjpegAPI, "conditions": True},
|
|
||||||
"snapshot": {"rule": "/snapshot", "view_class": SnapshotAPI, "conditions": True},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def enabled_streams():
|
|
||||||
global _streams
|
|
||||||
return {k: v for k, v in _streams.items() if v["conditions"]}
|
|
||||||
|
|
||||||
|
|
||||||
def streams_representation():
|
|
||||||
global _streams
|
|
||||||
|
|
||||||
streams = {}
|
|
||||||
for name, stream in enabled_streams().items():
|
|
||||||
d = {"links": {"self": url_for(f".{name}")}}
|
|
||||||
|
|
||||||
d.update(description_from_view(stream["view_class"]))
|
|
||||||
|
|
||||||
streams[name] = d
|
|
||||||
|
|
||||||
return streams
|
|
||||||
|
|
||||||
|
|
||||||
def construct_blueprint(microscope_obj):
|
|
||||||
|
|
||||||
blueprint = blueprint_for_module(__name__)
|
|
||||||
|
|
||||||
# For each enabled stream route defined in our dictionary above
|
|
||||||
for name, stream in enabled_streams().items():
|
|
||||||
# Add the action to our blueprint
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
stream["rule"],
|
|
||||||
view_func=stream["view_class"].as_view(name, microscope=microscope_obj),
|
|
||||||
)
|
|
||||||
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
"/", view_func=StreamAPI.as_view("streams", microscope=microscope_obj)
|
|
||||||
)
|
|
||||||
|
|
||||||
return blueprint
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
from openflexure_microscope.api.views import MethodView
|
|
||||||
from flask import jsonify, abort, Blueprint, url_for
|
|
||||||
|
|
||||||
from openflexure_microscope.common import tasks
|
|
||||||
|
|
||||||
|
|
||||||
def tasks_representation():
|
|
||||||
"""
|
|
||||||
Generate a dictionary representation of all tasks, including Flask route URLs
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Dictionary representation of all tasks
|
|
||||||
"""
|
|
||||||
tasks_dict = tasks.states()
|
|
||||||
|
|
||||||
for task_key, task_repr in tasks_dict.items():
|
|
||||||
# Add API routes to returned representations
|
|
||||||
extra_state = {
|
|
||||||
"links": {"self": "{}".format(url_for(".task", task_id=task_key))}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks_dict[task_key].update(extra_state)
|
|
||||||
|
|
||||||
return tasks_dict
|
|
||||||
|
|
||||||
|
|
||||||
class TaskListAPI(MethodView):
|
|
||||||
def get(self):
|
|
||||||
"""
|
|
||||||
Get list of long-running tasks.
|
|
||||||
|
|
||||||
.. :quickref: Tasks; Get collection of tasks
|
|
||||||
|
|
||||||
**Example response**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Vary: Accept
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"end_time": "2019-01-23 16-33-33",
|
|
||||||
"id": "db13a66787e1419bb06b1504e4d80b0c",
|
|
||||||
"return": [
|
|
||||||
0.848622546386467,
|
|
||||||
0.6106785018091292,
|
|
||||||
],
|
|
||||||
"start_time": "2019-01-23 16-33-13",
|
|
||||||
"status": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"end_time": null,
|
|
||||||
"id": "df46558cc8844924821bd0181881871e",
|
|
||||||
"return": null,
|
|
||||||
"start_time": "2019-01-23 16-34-54",
|
|
||||||
"status": "running"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
:query include_unavailable: return json representations of captures that have been completely deleted
|
|
||||||
|
|
||||||
:>header Content-Type: application/json
|
|
||||||
"""
|
|
||||||
|
|
||||||
return jsonify(tasks_representation())
|
|
||||||
|
|
||||||
def delete(self):
|
|
||||||
"""
|
|
||||||
Clean list of long-running tasks (running tasks persist).
|
|
||||||
|
|
||||||
.. :quickref: Tasks; Clean collection of tasks
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
|
|
||||||
:>header Content-Type: application/json
|
|
||||||
"""
|
|
||||||
|
|
||||||
tasks.cleanup_tasks()
|
|
||||||
|
|
||||||
return jsonify(tasks_representation())
|
|
||||||
|
|
||||||
|
|
||||||
class TaskAPI(MethodView):
|
|
||||||
def get(self, task_id):
|
|
||||||
"""
|
|
||||||
Get JSON representation of a task
|
|
||||||
|
|
||||||
.. :quickref: Tasks; Get task
|
|
||||||
|
|
||||||
**Example request**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
GET /task/db13a66787e1419bb06b1504e4d80b0c/ HTTP/1.1
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
**Example response**:
|
|
||||||
|
|
||||||
.. sourcecode:: http
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Vary: Accept
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"end_time": "2019-01-23 16-33-33",
|
|
||||||
"id": "db13a66787e1419bb06b1504e4d80b0c",
|
|
||||||
"return": [
|
|
||||||
0.848622546386467,
|
|
||||||
0.6106785018091292,
|
|
||||||
],
|
|
||||||
"start_time": "2019-01-23 16-33-13",
|
|
||||||
"status": "success"
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
task = tasks_representation()[task_id]
|
|
||||||
except KeyError:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
# Return task state
|
|
||||||
return jsonify(task)
|
|
||||||
|
|
||||||
def delete(self, task_id):
|
|
||||||
"""
|
|
||||||
Terminate a particular task.
|
|
||||||
|
|
||||||
.. :quickref: Tasks; Terminate a task
|
|
||||||
|
|
||||||
:>header Accept: application/json
|
|
||||||
|
|
||||||
:>header Content-Type: application/json
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
task = tasks.tasks()[task_id]
|
|
||||||
except KeyError:
|
|
||||||
return abort(404) # 404 Not Found
|
|
||||||
|
|
||||||
task.terminate()
|
|
||||||
|
|
||||||
return jsonify(task.state)
|
|
||||||
|
|
||||||
|
|
||||||
def construct_blueprint(microscope_obj):
|
|
||||||
|
|
||||||
blueprint = Blueprint("v2_tasks_blueprint", __name__)
|
|
||||||
|
|
||||||
blueprint.add_url_rule("/", view_func=TaskListAPI.as_view("task_list"))
|
|
||||||
|
|
||||||
blueprint.add_url_rule("/<task_id>/", view_func=TaskAPI.as_view("task"))
|
|
||||||
|
|
||||||
return blueprint
|
|
||||||
5
openflexure_microscope/api/v2/views/__init__.py
Normal file
5
openflexure_microscope/api/v2/views/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
from .actions import enabled_root_actions
|
||||||
|
from .captures import *
|
||||||
|
from .state import *
|
||||||
|
from .streams import *
|
||||||
|
from .tasks import *
|
||||||
|
|
@ -49,17 +49,6 @@ _actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def enabled_actions():
|
def enabled_root_actions():
|
||||||
global _actions
|
global _actions
|
||||||
return {k: v for k, v in _actions.items() if v["conditions"]}
|
return {k: v for k, v in _actions.items() if v["conditions"]}
|
||||||
|
|
||||||
|
|
||||||
def add_actions_to_labthing(labthing, prefix=""):
|
|
||||||
"""
|
|
||||||
Add all capture resources to a labthing
|
|
||||||
"""
|
|
||||||
for name, action in enabled_actions().items():
|
|
||||||
view_class = action["view_class"]
|
|
||||||
rule = action["rule"]
|
|
||||||
labthing.add_resource(view_class, f"{prefix}/actions{rule}")
|
|
||||||
labthing.register_action(view_class)
|
|
||||||
|
|
|
||||||
|
|
@ -70,25 +70,3 @@ class NestedStatusProperty(Resource):
|
||||||
|
|
||||||
return jsonify(value)
|
return jsonify(value)
|
||||||
|
|
||||||
|
|
||||||
def add_states_to_labthing(labthing, prefix=""):
|
|
||||||
"""
|
|
||||||
Add all settings and status resources to a labthing
|
|
||||||
"""
|
|
||||||
labthing.add_resource(
|
|
||||||
SettingsProperty, f"{prefix}/settings", endpoint="SettingsProperty"
|
|
||||||
)
|
|
||||||
labthing.register_property(SettingsProperty)
|
|
||||||
labthing.add_resource(
|
|
||||||
NestedSettingsProperty,
|
|
||||||
f"{prefix}/settings/<path:route>",
|
|
||||||
endpoint="NestedSettingsProperty",
|
|
||||||
)
|
|
||||||
labthing.add_resource(StatusProperty, f"{prefix}/status", endpoint="StatusProperty")
|
|
||||||
labthing.register_property(StatusProperty)
|
|
||||||
labthing.add_resource(
|
|
||||||
NestedStatusProperty,
|
|
||||||
f"{prefix}/status/<path:route>",
|
|
||||||
endpoint="NestedStatusProperty",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,3 @@ class TaskResource(Resource):
|
||||||
task.terminate()
|
task.terminate()
|
||||||
|
|
||||||
return jsonify(task.state)
|
return jsonify(task.state)
|
||||||
|
|
||||||
|
|
||||||
def add_tasks_to_labthing(labthing, prefix=""):
|
|
||||||
"""
|
|
||||||
Add all settings and status resources to a labthing
|
|
||||||
"""
|
|
||||||
labthing.add_resource(TaskList, f"{prefix}/tasks", endpoint="TasksProperty")
|
|
||||||
labthing.register_property(TaskList)
|
|
||||||
labthing.add_resource(TaskResource, f"{prefix}/tasks/<id>", endpoint="TaskResource")
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ from flask import current_app, _app_ctx_stack, request, url_for, jsonify
|
||||||
|
|
||||||
from .plugins import BasePlugin
|
from .plugins import BasePlugin
|
||||||
from .views.plugins import PluginListResource
|
from .views.plugins import PluginListResource
|
||||||
|
from .views.tasks import TaskList, TaskResource
|
||||||
from .resource import Resource
|
|
||||||
|
|
||||||
from ..utilities import get_docstring
|
from ..utilities import get_docstring
|
||||||
|
|
||||||
|
|
@ -53,6 +52,11 @@ class LabThing(object):
|
||||||
self.app.add_url_rule(self._complete_url("/", ""), "td", self.td)
|
self.app.add_url_rule(self._complete_url("/", ""), "td", self.td)
|
||||||
# Add plugin overview
|
# Add plugin overview
|
||||||
self.add_resource(PluginListResource, "/plugins")
|
self.add_resource(PluginListResource, "/plugins")
|
||||||
|
self.register_property(PluginListResource)
|
||||||
|
# Add task routes
|
||||||
|
self.add_resource(TaskList, "/tasks", endpoint="TasksProperty")
|
||||||
|
self.register_property(TaskList)
|
||||||
|
self.add_resource(TaskResource, "/tasks/<id>", endpoint="TaskResource")
|
||||||
|
|
||||||
### Device stuff
|
### Device stuff
|
||||||
|
|
||||||
|
|
@ -209,6 +213,7 @@ class LabThing(object):
|
||||||
actions[key]["links"] = [{"href": self.url_for(prop, _external=True)}]
|
actions[key]["links"] = [{"href": self.url_for(prop, _external=True)}]
|
||||||
|
|
||||||
td = {
|
td = {
|
||||||
|
"id": url_for("td", _external=True),
|
||||||
"title": self.title,
|
"title": self.title,
|
||||||
"description": self.description,
|
"description": self.description,
|
||||||
"properties": props,
|
"properties": props,
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ def plugins_representation(plugin_dict):
|
||||||
|
|
||||||
for view_id, view_data in plugin.views.items():
|
for view_id, view_data in plugin.views.items():
|
||||||
logging.debug(f"Representing view {view_id}")
|
logging.debug(f"Representing view {view_id}")
|
||||||
uri = url_for(f"pluginlistresource") + "/" + view_data["rule"][1:]
|
uri = url_for(f"PluginListResource") + "/" + view_data["rule"][1:]
|
||||||
# uri = view_data["rule"]
|
# uri = view_data["rule"]
|
||||||
# Make links dictionary if it doesn't yet exist
|
# Make links dictionary if it doesn't yet exist
|
||||||
view_d = {"links": {"self": uri}}
|
view_d = {"links": {"self": uri}}
|
||||||
|
|
|
||||||
61
openflexure_microscope/common/labthings/views/tasks.py
Normal file
61
openflexure_microscope/common/labthings/views/tasks.py
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import logging
|
||||||
|
from flask import abort, request, redirect, url_for, send_file, jsonify
|
||||||
|
|
||||||
|
from openflexure_microscope.api.utilities import get_bool, JsonResponse
|
||||||
|
|
||||||
|
from openflexure_microscope.common.labthings.schema import Schema
|
||||||
|
from openflexure_microscope.common.labthings import fields
|
||||||
|
from openflexure_microscope.common.labthings.resource import Resource
|
||||||
|
|
||||||
|
from openflexure_microscope.common import tasks
|
||||||
|
|
||||||
|
|
||||||
|
class TaskSchema(Schema):
|
||||||
|
_ID = fields.String(data_key="id")
|
||||||
|
target_string = fields.String(data_key="function")
|
||||||
|
_status = fields.String(data_key="status")
|
||||||
|
progress = fields.String()
|
||||||
|
data = fields.Raw()
|
||||||
|
_return_value = fields.Raw(data_key="return")
|
||||||
|
_start_time = fields.String(data_key="start_time")
|
||||||
|
_end_time = fields.String(data_key="end_time")
|
||||||
|
|
||||||
|
# TODO: Add HTTP methods
|
||||||
|
links = fields.Hyperlinks(
|
||||||
|
{
|
||||||
|
"self": {
|
||||||
|
"href": fields.AbsoluteUrlFor("TaskResource", id="<id>"),
|
||||||
|
"mimetype": "application/json",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
task_schema = TaskSchema()
|
||||||
|
task_list_schema = TaskSchema(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TaskList(Resource):
|
||||||
|
def get(self):
|
||||||
|
return task_list_schema.jsonify(tasks.tasks())
|
||||||
|
|
||||||
|
|
||||||
|
class TaskResource(Resource):
|
||||||
|
def get(self, id):
|
||||||
|
try:
|
||||||
|
task = tasks.dict()[id]
|
||||||
|
except KeyError:
|
||||||
|
return abort(404) # 404 Not Found
|
||||||
|
|
||||||
|
# Return task state
|
||||||
|
return task_schema.jsonify(task)
|
||||||
|
|
||||||
|
def delete(self, id):
|
||||||
|
try:
|
||||||
|
task = tasks.dict()[id]
|
||||||
|
except KeyError:
|
||||||
|
return abort(404) # 404 Not Found
|
||||||
|
|
||||||
|
task.terminate()
|
||||||
|
|
||||||
|
return task_schema.jsonify(task)
|
||||||
|
|
@ -360,6 +360,9 @@ autofocus_plugin_v2.add_method(fast_autofocus, "fast_autofocus")
|
||||||
autofocus_plugin_v2.add_method(autofocus, "autofocus")
|
autofocus_plugin_v2.add_method(autofocus, "autofocus")
|
||||||
|
|
||||||
autofocus_plugin_v2.add_view(MeasureSharpnessAPI, "/measure_sharpness")
|
autofocus_plugin_v2.add_view(MeasureSharpnessAPI, "/measure_sharpness")
|
||||||
autofocus_plugin_v2.add_view(AutofocusAPI, "/autofocus")
|
|
||||||
autofocus_plugin_v2.add_view(FastAutofocusAPI, "/fast_autofocus")
|
|
||||||
|
|
||||||
|
autofocus_plugin_v2.add_view(AutofocusAPI, "/autofocus")
|
||||||
|
autofocus_plugin_v2.register_action(AutofocusAPI)
|
||||||
|
|
||||||
|
autofocus_plugin_v2.add_view(FastAutofocusAPI, "/fast_autofocus")
|
||||||
|
autofocus_plugin_v2.register_action(FastAutofocusAPI)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue