Drafted actions routes
This commit is contained in:
parent
6581612312
commit
d79fd55301
8 changed files with 360 additions and 25 deletions
|
|
@ -0,0 +1,87 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
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)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
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)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
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
|
||||
|
|
@ -26,7 +26,7 @@ def captures_representation(capture_list: list, include_unavailable: bool = Fals
|
|||
# Add API routes to returned representations
|
||||
extra_state = {
|
||||
"links": {
|
||||
"properties": "{}".format(url_for(".capture", capture_id=capture_key)),
|
||||
"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)),
|
||||
}
|
||||
|
|
@ -131,30 +131,15 @@ class CaptureAPI(MicroscopeView):
|
|||
- **state** *(string)*: api uri to the capture json representation
|
||||
|
||||
"""
|
||||
capture_obj = self.microscope.camera.image_from_id(capture_id)
|
||||
|
||||
if not capture_obj:
|
||||
all_captures = captures_representation(self.microscope.camera.images, include_unavailable=True)
|
||||
|
||||
if capture_id in all_captures:
|
||||
representation = all_captures[capture_id]
|
||||
else:
|
||||
return abort(404) # 404 Not Found
|
||||
|
||||
# Get capture state
|
||||
capture_metadata = capture_obj.state
|
||||
|
||||
# Add API routes to returned state
|
||||
uri_dict = {
|
||||
"uri": {
|
||||
"state": "{}".format(url_for(".capture", capture_id=capture_obj.id))
|
||||
}
|
||||
}
|
||||
|
||||
# If available, also add download link
|
||||
if capture_metadata["available"]:
|
||||
uri_dict["uri"]["download"] = "{}download/{}".format(
|
||||
url_for(".capture", capture_id=capture_obj.id), capture_obj.filename
|
||||
)
|
||||
|
||||
capture_metadata.update(uri_dict)
|
||||
|
||||
return jsonify(capture_metadata)
|
||||
return jsonify(representation)
|
||||
|
||||
def delete(self, capture_id):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ def tasks_representation():
|
|||
# Add API routes to returned representations
|
||||
extra_state = {
|
||||
"links": {
|
||||
"properties": "{}".format(url_for(".task", task_id=task_key)),
|
||||
"self": "{}".format(url_for(".task", task_id=task_key)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue