Added basic thing description to root
This commit is contained in:
parent
d52453849c
commit
f2af359b8b
24 changed files with 677 additions and 183 deletions
65
openflexure_microscope/api/v2/views/actions/__init__.py
Normal file
65
openflexure_microscope/api/v2/views/actions/__init__.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
"""
|
||||
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
|
||||
|
||||
_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 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)
|
||||
82
openflexure_microscope/api/v2/views/actions/camera.py
Normal file
82
openflexure_microscope/api/v2/views/actions/camera.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
from openflexure_microscope.api.utilities import get_bool, JsonResponse
|
||||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
from openflexure_microscope.common.labthings.find import find_device
|
||||
from openflexure_microscope.utilities import filter_dict
|
||||
|
||||
from openflexure_microscope.api.v2.views.captures import capture_schema
|
||||
|
||||
import logging
|
||||
from flask import jsonify, request, abort, url_for, redirect, send_file
|
||||
|
||||
|
||||
class CaptureAPI(Resource):
|
||||
"""
|
||||
Create a new image capture.
|
||||
"""
|
||||
|
||||
def post(self):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
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 microscope.camera.lock:
|
||||
output = microscope.camera.new_image(temporary=temporary, filename=filename)
|
||||
|
||||
microscope.camera.capture(
|
||||
output.file, use_video_port=use_video_port, resize=resize, bayer=bayer
|
||||
)
|
||||
|
||||
# Inject system metadata
|
||||
output.put_metadata(microscope.metadata, system=True)
|
||||
|
||||
# Insert custom metadata
|
||||
output.put_metadata(metadata)
|
||||
|
||||
# Insert custom tags
|
||||
output.put_tags(tags)
|
||||
|
||||
return capture_schema.jsonify(output)
|
||||
|
||||
|
||||
class GPUPreviewStartAPI(Resource):
|
||||
def post(self):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
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]
|
||||
|
||||
microscope.camera.start_preview(fullscreen=fullscreen, window=window)
|
||||
|
||||
return jsonify(microscope.state)
|
||||
|
||||
|
||||
class GPUPreviewStopAPI(Resource):
|
||||
def post(self):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
microscope.camera.stop_preview()
|
||||
return jsonify(microscope.state)
|
||||
55
openflexure_microscope/api/v2/views/actions/stage.py
Normal file
55
openflexure_microscope/api/v2/views/actions/stage.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
from openflexure_microscope.common.labthings.find import find_device
|
||||
from openflexure_microscope.utilities import axes_to_array, filter_dict
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
class MoveStageAPI(Resource):
|
||||
def post(self):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
# 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 (
|
||||
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] - 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 microscope.stage:
|
||||
# Explicitally acquire lock
|
||||
with microscope.stage.lock:
|
||||
microscope.stage.move_rel(position)
|
||||
else:
|
||||
logging.warning("Unable to move. No stage found.")
|
||||
|
||||
return jsonify(microscope.status["stage"]["position"])
|
||||
|
||||
|
||||
class ZeroStageAPI(Resource):
|
||||
"""
|
||||
Zero stage coordinates
|
||||
"""
|
||||
|
||||
def post(self):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
microscope.stage.zero_position()
|
||||
|
||||
return jsonify(microscope.status["stage"])
|
||||
46
openflexure_microscope/api/v2/views/actions/system.py
Normal file
46
openflexure_microscope/api/v2/views/actions/system.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
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(Resource):
|
||||
"""
|
||||
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(Resource):
|
||||
"""
|
||||
Attempt to reboot the device
|
||||
"""
|
||||
|
||||
def post(self):
|
||||
"""
|
||||
Attempt to shutdown the device
|
||||
|
||||
.. :quickref: Actions; Shutdown
|
||||
|
||||
"""
|
||||
subprocess.Popen(["sudo", "shutdown", "-r", "now"])
|
||||
|
||||
return "{}", 201
|
||||
|
|
@ -33,9 +33,11 @@ class CaptureSchema(Schema):
|
|||
"mimetype": "application/json",
|
||||
},
|
||||
"download": {
|
||||
"href": fields.AbsoluteUrlFor("CaptureDownload", id="<id>", filename="<filename>"),
|
||||
"href": fields.AbsoluteUrlFor(
|
||||
"CaptureDownload", id="<id>", filename="<filename>"
|
||||
),
|
||||
"mimetype": "image/jpeg",
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -185,11 +187,14 @@ def add_captures_to_labthing(labthing, prefix=""):
|
|||
Add all capture resources to a labthing
|
||||
"""
|
||||
labthing.add_resource(CaptureList, f"{prefix}/captures", endpoint="CaptureList")
|
||||
labthing.register_property(CaptureList)
|
||||
labthing.add_resource(
|
||||
CaptureResource, f"{prefix}/captures/<id>", endpoint="CaptureResource"
|
||||
)
|
||||
labthing.add_resource(
|
||||
CaptureDownload, f"{prefix}/captures/<id>/download/<filename>", endpoint="CaptureDownload"
|
||||
CaptureDownload,
|
||||
f"{prefix}/captures/<id>/download/<filename>",
|
||||
endpoint="CaptureDownload",
|
||||
)
|
||||
labthing.add_resource(
|
||||
CaptureTags, f"{prefix}/captures/<id>/tags", endpoint="CaptureTags"
|
||||
|
|
|
|||
94
openflexure_microscope/api/v2/views/state.py
Normal file
94
openflexure_microscope/api/v2/views/state.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
from openflexure_microscope.utilities import get_by_path, set_by_path, create_from_path
|
||||
|
||||
from openflexure_microscope.common.labthings.find import find_device
|
||||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
|
||||
from flask import jsonify, request, abort
|
||||
import logging
|
||||
|
||||
|
||||
class SettingsProperty(Resource):
|
||||
def get(self):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
return jsonify(microscope.read_settings())
|
||||
|
||||
def put(self):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
payload = JsonResponse(request)
|
||||
|
||||
logging.debug("Updating settings from PUT request:")
|
||||
logging.debug(payload.json)
|
||||
|
||||
microscope.apply_settings(payload.json)
|
||||
microscope.save_settings()
|
||||
|
||||
return self.get()
|
||||
|
||||
|
||||
class NestedSettingsProperty(Resource):
|
||||
def get(self, route):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
keys = route.split("/")
|
||||
|
||||
try:
|
||||
value = get_by_path(microscope.read_settings(), keys)
|
||||
except KeyError:
|
||||
return abort(404)
|
||||
|
||||
return jsonify(value)
|
||||
|
||||
def put(self, route):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
keys = route.split("/")
|
||||
payload = JsonResponse(request)
|
||||
|
||||
dictionary = create_from_path(keys)
|
||||
set_by_path(dictionary, keys, payload.json)
|
||||
|
||||
microscope.apply_settings(dictionary)
|
||||
microscope.save_settings()
|
||||
|
||||
return self.get(route)
|
||||
|
||||
|
||||
class StatusProperty(Resource):
|
||||
def get(self):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
return jsonify(microscope.status)
|
||||
|
||||
|
||||
class NestedStatusProperty(Resource):
|
||||
def get(self, route):
|
||||
microscope = find_device("openflexure_microscope")
|
||||
keys = route.split("/")
|
||||
|
||||
try:
|
||||
value = get_by_path(microscope.status, keys)
|
||||
except KeyError:
|
||||
return abort(404)
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
62
openflexure_microscope/api/v2/views/streams.py
Normal file
62
openflexure_microscope/api/v2/views/streams.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from openflexure_microscope.api.utilities import gen, JsonResponse
|
||||
from openflexure_microscope.utilities import get_by_path, set_by_path, create_from_path
|
||||
|
||||
from openflexure_microscope.common.labthings.find import find_device
|
||||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
|
||||
from flask import jsonify, request, abort, Response
|
||||
import logging
|
||||
|
||||
|
||||
class MjpegStream(Resource):
|
||||
"""
|
||||
Real-time MJPEG stream from the microscope camera
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
Real-time MJPEG stream from the microscope camera
|
||||
"""
|
||||
microscope = find_device("openflexure_microscope")
|
||||
# Restart stream worker thread
|
||||
microscope.camera.start_worker()
|
||||
|
||||
return Response(
|
||||
gen(microscope.camera), mimetype="multipart/x-mixed-replace; boundary=frame"
|
||||
)
|
||||
|
||||
|
||||
class SnapshotStream(Resource):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
microscope = find_device("openflexure_microscope")
|
||||
# Restart stream worker thread
|
||||
microscope.camera.start_worker()
|
||||
|
||||
return Response(microscope.camera.get_frame(), mimetype="image/jpeg")
|
||||
|
||||
|
||||
def add_streams_to_labthing(labthing, prefix=""):
|
||||
"""
|
||||
Add all stream resources to a labthing
|
||||
"""
|
||||
labthing.add_resource(
|
||||
MjpegStream, f"{prefix}/streams/mjpeg", endpoint="MjpegStream"
|
||||
)
|
||||
labthing.register_property(MjpegStream)
|
||||
labthing.add_resource(
|
||||
SnapshotStream, f"{prefix}/streams/snapshot", endpoint="SnapshotStream"
|
||||
)
|
||||
labthing.register_property(SnapshotStream)
|
||||
71
openflexure_microscope/api/v2/views/tasks.py
Normal file
71
openflexure_microscope/api/v2/views/tasks.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
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 jsonify(task)
|
||||
|
||||
def delete(self, id):
|
||||
try:
|
||||
task = tasks.dict()[id]
|
||||
except KeyError:
|
||||
return abort(404) # 404 Not Found
|
||||
|
||||
task.terminate()
|
||||
|
||||
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")
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue