Added basic thing description to root
This commit is contained in:
parent
d52453849c
commit
f2af359b8b
24 changed files with 677 additions and 183 deletions
|
|
@ -6,7 +6,7 @@ import logging
|
|||
import sys
|
||||
import os
|
||||
|
||||
from flask import Flask, jsonify, send_file
|
||||
from flask import Flask, jsonify, send_file, url_for
|
||||
|
||||
from serial import SerialException
|
||||
from datetime import datetime
|
||||
|
|
@ -79,7 +79,7 @@ CORS(app, resources=r"*")
|
|||
handler = JSONExceptionHandler(app)
|
||||
|
||||
# Attach lab devices
|
||||
labthing = LabThing(app, prefix="/api/v2b")
|
||||
labthing = LabThing(app, prefix="/api/v2")
|
||||
labthing.description = "Test LabThing-based API for OpenFlexure Microscope"
|
||||
|
||||
labthing.register_device(api_microscope, "openflexure_microscope")
|
||||
|
|
@ -88,41 +88,23 @@ for _plugin in find_plugins(USER_PLUGINS_PATH):
|
|||
labthing.register_plugin(_plugin)
|
||||
|
||||
from openflexure_microscope.api.v2.views.captures import add_captures_to_labthing
|
||||
|
||||
add_captures_to_labthing(labthing, prefix="")
|
||||
from openflexure_microscope.api.v2.views.state import add_states_to_labthing
|
||||
|
||||
# WEBAPP ROUTES
|
||||
add_states_to_labthing(labthing, prefix="")
|
||||
|
||||
### V2
|
||||
# Root routes
|
||||
v2_root_blueprint = v2.blueprints.root.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(v2_root_blueprint, url_prefix=uri("/", "v2"))
|
||||
from openflexure_microscope.api.v2.views.tasks import add_tasks_to_labthing
|
||||
|
||||
v2_streams_blueprint = v2.blueprints.streams.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(v2_streams_blueprint, url_prefix=uri("/streams", "v2"))
|
||||
add_tasks_to_labthing(labthing, prefix="")
|
||||
|
||||
# Captures routes
|
||||
v2_captures_blueprint = v2.blueprints.captures.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(v2_captures_blueprint, url_prefix=uri("/captures", "v2"))
|
||||
from openflexure_microscope.api.v2.views.streams import add_streams_to_labthing
|
||||
|
||||
# Settings routes
|
||||
v2_settings_blueprint = v2.blueprints.settings.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(v2_settings_blueprint, url_prefix=uri("/settings", "v2"))
|
||||
add_streams_to_labthing(labthing, prefix="")
|
||||
|
||||
# Status routes
|
||||
v2_status_blueprint = v2.blueprints.status.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(v2_status_blueprint, url_prefix=uri("/status", "v2"))
|
||||
from openflexure_microscope.api.v2.views.actions import add_actions_to_labthing
|
||||
|
||||
# Plugins routes
|
||||
# v2_plugin_blueprint = v2.blueprints.plugins.construct_blueprint(api_microscope)
|
||||
# app.register_blueprint(v2_plugin_blueprint, url_prefix=uri("/plugins", "v2"))
|
||||
|
||||
# Tasks routes
|
||||
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"))
|
||||
add_actions_to_labthing(labthing)
|
||||
|
||||
|
||||
@app.route("/routes")
|
||||
|
|
@ -139,6 +121,26 @@ def routes():
|
|||
return jsonify(list_routes(app))
|
||||
|
||||
|
||||
@app.route("/props")
|
||||
def props():
|
||||
p = {}
|
||||
for key, prop in labthing.properties.items():
|
||||
p[key] = {}
|
||||
p[key]["view"] = prop
|
||||
p[key]["url"] = labthing.url_for(prop, _external=True)
|
||||
return jsonify(p)
|
||||
|
||||
|
||||
@app.route("/ac")
|
||||
def actions():
|
||||
p = {}
|
||||
for key, prop in labthing.actions.items():
|
||||
p[key] = {}
|
||||
p[key]["view"] = prop
|
||||
p[key]["url"] = labthing.url_for(prop, _external=True)
|
||||
return jsonify(p)
|
||||
|
||||
|
||||
@app.route("/log")
|
||||
def err_log():
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
from . import root, captures, settings, status, tasks, streams, plugins, actions
|
||||
from . import root, captures, settings, status, tasks, streams, actions
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
"""
|
||||
Top-level representation of attached and enabled plugins
|
||||
"""
|
||||
|
||||
from openflexure_microscope.api.views import MicroscopeViewPlugin
|
||||
from openflexure_microscope.utilities import get_docstring, description_from_view
|
||||
from openflexure_microscope.api.utilities import blueprint_for_module
|
||||
|
||||
from openflexure_microscope.common.labthings.plugins import find_plugins
|
||||
from openflexure_microscope.config import USER_PLUGINS_PATH
|
||||
|
||||
from flask import Blueprint, jsonify, url_for
|
||||
from openflexure_microscope.api.views import MicroscopeView
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
_plugins = find_plugins(USER_PLUGINS_PATH)
|
||||
print(_plugins)
|
||||
|
||||
|
||||
def plugins_representation(plugin_list):
|
||||
"""
|
||||
Generate a dictionary representation of all plugins, including Flask route URLs
|
||||
|
||||
Args:
|
||||
plugin_loader_object (:py:class:`openflexure_microscope.plugins.PluginLoader`): Microscope plugin loader
|
||||
|
||||
Returns:
|
||||
dict: Dictionary representation of all plugins
|
||||
"""
|
||||
plugins = {}
|
||||
|
||||
for plugin in plugin_list:
|
||||
logging.debug(f"Representing plugin {plugin._name}")
|
||||
d = {
|
||||
"python_name": plugin._name_python_safe,
|
||||
"plugin": str(plugin),
|
||||
"views": {},
|
||||
"gui": plugin.gui,
|
||||
"description": get_docstring(plugin),
|
||||
}
|
||||
|
||||
for view_id, view_data in plugin.views.items():
|
||||
logging.debug(f"Representing view {view_id}")
|
||||
uri = url_for(f"v2_plugins_blueprint.plugins") + view_data["rule"][1:]
|
||||
# uri = view_data["rule"]
|
||||
# Make links dictionary if it doesn't yet exist
|
||||
view_d = {"links": {"self": uri}}
|
||||
|
||||
view_d.update(description_from_view(view_data["view"]))
|
||||
|
||||
d["views"][view_id] = view_d
|
||||
|
||||
plugins[plugin._name] = d
|
||||
|
||||
return plugins
|
||||
|
||||
|
||||
class PluginFormAPI(MicroscopeView):
|
||||
def get(self):
|
||||
"""
|
||||
Return the current plugin forms
|
||||
|
||||
.. :quickref: Plugin; Get forms
|
||||
|
||||
Returns an array of present plugin forms (describing plugin user interfaces.)
|
||||
Please note, this is *not* a list of all enabled plugins, only those with associated
|
||||
user interface forms.
|
||||
|
||||
A complete list of enabled plugins can be found in the microscope state.
|
||||
|
||||
"""
|
||||
global _plugins
|
||||
return jsonify(plugins_representation(_plugins))
|
||||
|
||||
|
||||
def construct_blueprint(microscope_obj):
|
||||
|
||||
blueprint = blueprint_for_module(__name__)
|
||||
|
||||
for plugin_obj in _plugins:
|
||||
for plugin_view_id, plugin_view in plugin_obj.views.items():
|
||||
# Add route to the plugins blueprint
|
||||
blueprint.add_url_rule(
|
||||
plugin_view["rule"],
|
||||
view_func=plugin_view["view"].as_view(
|
||||
f"{plugin_obj._name_python_safe}_{plugin_view_id}"
|
||||
),
|
||||
**plugin_view["kwargs"],
|
||||
)
|
||||
|
||||
# Create a base route to return plugin API forms, if any exist
|
||||
blueprint.add_url_rule(
|
||||
"/", view_func=PluginFormAPI.as_view("plugins", microscope=microscope_obj)
|
||||
)
|
||||
|
||||
# For each plugin attached to the microscope object
|
||||
for plugin in microscope_obj.plugins.active:
|
||||
|
||||
for plugin_view_id, plugin_view in plugin.views.items():
|
||||
# Add route to the plugins blueprint
|
||||
blueprint.add_url_rule(
|
||||
plugin_view["rule"],
|
||||
view_func=plugin_view["view"].as_view(
|
||||
f"{plugin._name_python_safe}_{plugin_view_id}",
|
||||
microscope=microscope_obj,
|
||||
plugin=plugin,
|
||||
),
|
||||
**plugin_view["kwargs"],
|
||||
)
|
||||
|
||||
return blueprint
|
||||
|
|
@ -7,7 +7,6 @@ from openflexure_microscope.api.utilities import blueprint_name_for_module
|
|||
from openflexure_microscope.api.v2.blueprints import (
|
||||
settings,
|
||||
status,
|
||||
plugins,
|
||||
captures,
|
||||
actions,
|
||||
streams,
|
||||
|
|
|
|||
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")
|
||||
|
||||
|
|
@ -150,4 +150,4 @@ class Hyperlinks(Field):
|
|||
Field.__init__(self, **kwargs)
|
||||
|
||||
def _serialize(self, value, attr, obj):
|
||||
return _rapply(self.schema, _url_val, key=attr, obj=obj)
|
||||
return _rapply(self.schema, _url_val, key=attr, obj=obj)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from flask import current_app
|
|||
from . import EXTENSION_NAME
|
||||
|
||||
|
||||
def _current_labthing():
|
||||
def current_labthing():
|
||||
app = current_app._get_current_object()
|
||||
if not app:
|
||||
return None
|
||||
|
|
@ -17,31 +17,32 @@ def _current_labthing():
|
|||
|
||||
def registered_plugins(labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
labthing_instance = current_labthing()
|
||||
return labthing_instance.plugins
|
||||
|
||||
|
||||
def registered_devices(labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
labthing_instance = current_labthing()
|
||||
return labthing_instance.devices
|
||||
|
||||
|
||||
def find_device(device_name, labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
labthing_instance = current_labthing()
|
||||
|
||||
if device_name in labthing_instance.devices:
|
||||
return labthing_instance.devices[device_name]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def find_plugin(plugin_name, labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
labthing_instance = current_labthing()
|
||||
|
||||
logging.debug("Current labthing:")
|
||||
logging.debug(_current_labthing())
|
||||
logging.debug(current_labthing())
|
||||
|
||||
if plugin_name in labthing_instance.plugins:
|
||||
return labthing_instance.plugins[plugin_name]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
from flask import current_app, _app_ctx_stack, request
|
||||
from flask import current_app, _app_ctx_stack, request, url_for, jsonify
|
||||
|
||||
from .plugins import BasePlugin
|
||||
from .views.plugins import PluginListResource
|
||||
|
||||
from .resource import Resource
|
||||
|
||||
from ..utilities import get_docstring
|
||||
|
||||
from . import EXTENSION_NAME
|
||||
|
||||
|
||||
class LabThing(object):
|
||||
def __init__(self, app=None, prefix="", description=""):
|
||||
def __init__(self, app=None, prefix="", title="", description=""):
|
||||
self.app = app
|
||||
|
||||
self.devices = {}
|
||||
|
|
@ -15,10 +19,14 @@ class LabThing(object):
|
|||
self.plugins = {}
|
||||
|
||||
self.resources = []
|
||||
self.properties = {}
|
||||
self.actions = {}
|
||||
|
||||
self.endpoints = set()
|
||||
|
||||
self.url_prefix = prefix
|
||||
self.description = description
|
||||
self.title = title
|
||||
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
|
@ -33,10 +41,17 @@ class LabThing(object):
|
|||
|
||||
self._create_base_routes()
|
||||
|
||||
if len(self.resources) > 0:
|
||||
for resource, urls, endpoint, kwargs in self.resources:
|
||||
self._register_view(app, resource, *urls, endpoint=endpoint, **kwargs)
|
||||
|
||||
def teardown(self, exception):
|
||||
print(f"Tearing down devices: {self.devices}")
|
||||
|
||||
def _create_base_routes(self):
|
||||
# Add thing description to root
|
||||
self.app.add_url_rule(self._complete_url("/", ""), "td", self.td)
|
||||
# Add plugin overview
|
||||
self.add_resource(PluginListResource, "/plugins")
|
||||
|
||||
### Device stuff
|
||||
|
|
@ -51,8 +66,6 @@ class LabThing(object):
|
|||
else:
|
||||
raise TypeError("Plugin object must be an instance of BasePlugin")
|
||||
|
||||
# TODO: Add plugin routes
|
||||
|
||||
for plugin_view_id, plugin_view in plugin_object.views.items():
|
||||
# Add route to the plugins blueprint
|
||||
self.add_resource(
|
||||
|
|
@ -61,6 +74,12 @@ class LabThing(object):
|
|||
**plugin_view["kwargs"],
|
||||
)
|
||||
|
||||
for prop in plugin_object.properties:
|
||||
self.register_property(prop)
|
||||
|
||||
for action in plugin_object.actions:
|
||||
self.register_action(action)
|
||||
|
||||
### Resource stuff
|
||||
|
||||
def _complete_url(self, url_part, registration_prefix):
|
||||
|
|
@ -73,7 +92,23 @@ class LabThing(object):
|
|||
parts = [registration_prefix, self.url_prefix, url_part]
|
||||
return "".join([part for part in parts if part])
|
||||
|
||||
def add_resource(self, resource, *urls, **kwargs):
|
||||
def register_property(self, resource):
|
||||
if hasattr(resource, "endpoint"):
|
||||
self.properties[resource.endpoint] = resource
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Resource {resource} has not yet been added. Cannot set as a property."
|
||||
)
|
||||
|
||||
def register_action(self, resource):
|
||||
if hasattr(resource, "endpoint"):
|
||||
self.actions[resource.endpoint] = resource
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Resource {resource} has not yet been added. Cannot set as an action."
|
||||
)
|
||||
|
||||
def add_resource(self, resource, *urls, endpoint=None, **kwargs):
|
||||
"""Adds a resource to the api.
|
||||
:param resource: the class name of your resource
|
||||
:type resource: :class:`Type[Resource]`
|
||||
|
|
@ -81,7 +116,7 @@ class LabThing(object):
|
|||
flask routing rules apply. Any url variables will be
|
||||
passed to the resource method as args.
|
||||
:type urls: str
|
||||
:param endpoint: endpoint name (defaults to :meth:`Resource.__name__.lower`
|
||||
:param endpoint: endpoint name (defaults to :meth:`Resource.__name__`
|
||||
Can be used to reference this route in :class:`fields.Url` fields
|
||||
:type endpoint: str
|
||||
:param resource_class_args: args to be forwarded to the constructor of
|
||||
|
|
@ -97,10 +132,11 @@ class LabThing(object):
|
|||
api.add_resource(Foo, '/foo', endpoint="foo")
|
||||
api.add_resource(FooSpecial, '/special/foo', endpoint="foo")
|
||||
"""
|
||||
endpoint = endpoint or resource.__name__
|
||||
if self.app is not None:
|
||||
self._register_view(self.app, resource, *urls, **kwargs)
|
||||
self._register_view(self.app, resource, *urls, endpoint=endpoint, **kwargs)
|
||||
else:
|
||||
self.resources.append((resource, urls, kwargs))
|
||||
self.resources.append((resource, urls, endpoint, kwargs))
|
||||
|
||||
def resource(self, *urls, **kwargs):
|
||||
"""Wraps a :class:`~flask_restful.Resource` class, adding it to the
|
||||
|
|
@ -120,8 +156,8 @@ class LabThing(object):
|
|||
|
||||
return decorator
|
||||
|
||||
def _register_view(self, app, resource, *urls, **kwargs):
|
||||
endpoint = kwargs.pop("endpoint", None) or resource.__name__.lower()
|
||||
def _register_view(self, app, resource, *urls, endpoint=None, **kwargs):
|
||||
endpoint = endpoint or resource.__name__
|
||||
self.endpoints.add(endpoint)
|
||||
resource_class_args = kwargs.pop("resource_class_args", ())
|
||||
resource_class_kwargs = kwargs.pop("resource_class_kwargs", {})
|
||||
|
|
@ -147,3 +183,36 @@ class LabThing(object):
|
|||
rule = self._complete_url(url, "")
|
||||
# Add the url to the application or blueprint
|
||||
app.add_url_rule(rule, view_func=resource_func, **kwargs)
|
||||
|
||||
### Utilities
|
||||
|
||||
def url_for(self, resource, **values):
|
||||
"""Generates a URL to the given resource.
|
||||
Works like :func:`flask.url_for`."""
|
||||
endpoint = resource.endpoint
|
||||
return url_for(endpoint, **values)
|
||||
|
||||
### Description
|
||||
def td(self):
|
||||
props = {}
|
||||
for key, prop in self.properties.items():
|
||||
props[key] = {}
|
||||
props[key]["title"] = prop.__name__
|
||||
props[key]["description"] = get_docstring(prop)
|
||||
props[key]["links"] = [{"href": self.url_for(prop, _external=True)}]
|
||||
|
||||
actions = {}
|
||||
for key, prop in self.actions.items():
|
||||
actions[key] = {}
|
||||
actions[key]["title"] = prop.__name__
|
||||
actions[key]["description"] = get_docstring(prop)
|
||||
actions[key]["links"] = [{"href": self.url_for(prop, _external=True)}]
|
||||
|
||||
td = {
|
||||
"title": self.title,
|
||||
"description": self.description,
|
||||
"properties": props,
|
||||
"actions": actions,
|
||||
}
|
||||
|
||||
return jsonify(td)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ class BasePlugin:
|
|||
self._rules = {} # Key: Original rule. Val: View class
|
||||
self._gui = None
|
||||
|
||||
self.actions = []
|
||||
self.properties = []
|
||||
|
||||
self.name = name
|
||||
|
||||
self.methods = {}
|
||||
|
|
@ -35,7 +38,7 @@ class BasePlugin:
|
|||
def views(self):
|
||||
return self._views
|
||||
|
||||
def add_view(self, rule, view_class, **kwargs):
|
||||
def add_view(self, view_class, rule, **kwargs):
|
||||
# Remove all leading slashes from view route
|
||||
cleaned_rule = rule
|
||||
while cleaned_rule[0] == "/":
|
||||
|
|
@ -57,6 +60,12 @@ class BasePlugin:
|
|||
# Store the rule expansion information
|
||||
self._rules[rule] = self._views[view_id]
|
||||
|
||||
def register_action(self, view_class):
|
||||
self.actions.append(view_class)
|
||||
|
||||
def register_property(self, view_class):
|
||||
self.properties.append(view_class)
|
||||
|
||||
@property
|
||||
def gui(self):
|
||||
print(self._gui)
|
||||
|
|
@ -104,13 +113,16 @@ class BasePlugin:
|
|||
def _name_uri_safe(self):
|
||||
return snake_to_spine(self._name_python_safe)
|
||||
|
||||
def add_method(self, method_name, method):
|
||||
def add_method(self, method, method_name):
|
||||
self.methods[method_name] = method
|
||||
|
||||
if not hasattr(self, method_name):
|
||||
setattr(self, method_name, method)
|
||||
else:
|
||||
logging.warning("Unable to bind method to plugin. Method name already exists.")
|
||||
logging.warning(
|
||||
"Unable to bind method to plugin. Method name already exists."
|
||||
)
|
||||
|
||||
|
||||
def find_plugins(plugin_path, module_name="plugins"):
|
||||
print(f"Loading plugins from {plugin_path}")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
__all__ = [
|
||||
"taskify",
|
||||
"tasks",
|
||||
"dict",
|
||||
"states",
|
||||
"current_task",
|
||||
"update_task_progress",
|
||||
|
|
@ -12,6 +13,7 @@ __all__ = [
|
|||
|
||||
from .pool import (
|
||||
tasks,
|
||||
dict,
|
||||
states,
|
||||
current_task,
|
||||
update_task_progress,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,21 @@ from .thread import TaskThread
|
|||
|
||||
from flask import copy_current_request_context
|
||||
|
||||
|
||||
class TaskMaster:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._tasks = []
|
||||
|
||||
@property
|
||||
def tasks(self):
|
||||
"""
|
||||
Returns:
|
||||
list: List of TaskThread objects.
|
||||
"""
|
||||
return self._tasks
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
"""
|
||||
Returns:
|
||||
dict: Dictionary of TaskThread objects. Key is TaskThread ID.
|
||||
|
|
@ -28,7 +37,9 @@ class TaskMaster:
|
|||
|
||||
def new(self, f, *args, **kwargs):
|
||||
# copy_current_request_context allows threads to access flask current_app
|
||||
task = TaskThread(target=copy_current_request_context(f), args=args, kwargs=kwargs)
|
||||
task = TaskThread(
|
||||
target=copy_current_request_context(f), args=args, kwargs=kwargs
|
||||
)
|
||||
self._tasks.append(task)
|
||||
return task
|
||||
|
||||
|
|
@ -47,13 +58,23 @@ class TaskMaster:
|
|||
|
||||
|
||||
def tasks():
|
||||
"""
|
||||
List of tasks in default taskmaster
|
||||
Returns:
|
||||
list: List of tasks in default taskmaster
|
||||
"""
|
||||
global _default_task_master
|
||||
return _default_task_master.tasks
|
||||
|
||||
|
||||
def dict():
|
||||
"""
|
||||
Dictionary of tasks in default taskmaster
|
||||
Returns:
|
||||
dict: Dictionary of tasks in default taskmaster
|
||||
"""
|
||||
global _default_task_master
|
||||
return _default_task_master.tasks
|
||||
return _default_task_master.dict
|
||||
|
||||
|
||||
def states():
|
||||
|
|
|
|||
6
openflexure_microscope/common/utilities.py
Normal file
6
openflexure_microscope/common/utilities.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def get_docstring(obj):
|
||||
ds = obj.__doc__
|
||||
if ds:
|
||||
return ds.strip()
|
||||
else:
|
||||
return ""
|
||||
|
|
@ -147,7 +147,6 @@ class Microscope:
|
|||
else:
|
||||
return False
|
||||
|
||||
|
||||
# Create unified status
|
||||
@property
|
||||
def status(self):
|
||||
|
|
@ -239,7 +238,7 @@ class Microscope:
|
|||
"""
|
||||
system_metadata = {
|
||||
"microscope_settings": self.read_settings(),
|
||||
"microscope_state": self.state,
|
||||
"microscope_state": self.status,
|
||||
"microscope_id": self.id,
|
||||
"microscope_name": self.name,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -356,9 +356,10 @@ class FastAutofocusAPI(MethodView):
|
|||
|
||||
autofocus_plugin_v2 = BasePlugin("autofocus")
|
||||
|
||||
autofocus_plugin_v2.add_method("fast_autofocus", fast_autofocus)
|
||||
autofocus_plugin_v2.add_method("autofocus", autofocus)
|
||||
autofocus_plugin_v2.add_method(fast_autofocus, "fast_autofocus")
|
||||
autofocus_plugin_v2.add_method(autofocus, "autofocus")
|
||||
|
||||
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("/measure_sharpness", MeasureSharpnessAPI)
|
||||
autofocus_plugin_v2.add_view("/autofocus", AutofocusAPI)
|
||||
autofocus_plugin_v2.add_view("/fast_autofocus", FastAutofocusAPI)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import time
|
|||
|
||||
### Grid construction
|
||||
|
||||
|
||||
def construct_grid(initial, step_sizes, n_steps, style="raster"):
|
||||
"""
|
||||
Given an initial position, step sizes, and number of steps,
|
||||
|
|
@ -64,6 +65,7 @@ def flatten_grid(grid):
|
|||
_images_to_be_captured: int = 1
|
||||
_images_captured_so_far: int = 0
|
||||
|
||||
|
||||
def progress():
|
||||
progress = (_images_captured_so_far / _images_to_be_captured) * 100
|
||||
logging.info(progress)
|
||||
|
|
@ -72,6 +74,7 @@ def progress():
|
|||
|
||||
### Capturing
|
||||
|
||||
|
||||
def capture(
|
||||
microscope,
|
||||
basename,
|
||||
|
|
@ -114,6 +117,7 @@ def capture(
|
|||
|
||||
### Scanning
|
||||
|
||||
|
||||
def tile(
|
||||
microscope,
|
||||
basename: str = None,
|
||||
|
|
@ -181,9 +185,7 @@ def tile(
|
|||
) # shorthand for Z stack range
|
||||
|
||||
# Construct an x-y grid (worry about z later)
|
||||
x_y_grid = construct_grid(
|
||||
initial_position, step_size[:2], grid[:2], style=style
|
||||
)
|
||||
x_y_grid = construct_grid(initial_position, step_size[:2], grid[:2], style=style)
|
||||
|
||||
# Keep the initial Z position the same as our current position
|
||||
next_z = initial_position[2]
|
||||
|
|
@ -270,6 +272,7 @@ def tile(
|
|||
logging.debug("Returning to {}".format(initial_position))
|
||||
microscope.stage.move_abs(initial_position)
|
||||
|
||||
|
||||
def stack(
|
||||
microscope,
|
||||
basename: str = None,
|
||||
|
|
@ -336,6 +339,7 @@ def stack(
|
|||
|
||||
### Web views
|
||||
|
||||
|
||||
class TileScanAPI(MethodView):
|
||||
def post(self):
|
||||
payload = JsonResponse(request)
|
||||
|
|
@ -396,4 +400,5 @@ class TileScanAPI(MethodView):
|
|||
|
||||
scan_plugin_v2 = BasePlugin("scan")
|
||||
|
||||
scan_plugin_v2.add_view("/tile", TileScanAPI)
|
||||
scan_plugin_v2.add_view(TileScanAPI, "/tile")
|
||||
scan_plugin_v2.register_action(TileScanAPI)
|
||||
|
|
|
|||
14
poetry.lock
generated
14
poetry.lock
generated
|
|
@ -190,6 +190,14 @@ optional = false
|
|||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
version = "1.1.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
|
||||
name = "marshmallow"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "3.3.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "McCabe checker, plugin for flake8"
|
||||
|
|
@ -230,6 +238,7 @@ version = "1.13.1b0"
|
|||
reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb"
|
||||
type = "git"
|
||||
url = "https://github.com/rwb27/picamera.git"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
|
|
@ -473,7 +482,7 @@ version = "1.11.2"
|
|||
rpi = ["picamera", "RPi.GPIO"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "ea3fcd1909a76b04efa8ebefdcb77f0b449642820a94429fbbbf20bb4f9a64ab"
|
||||
content-hash = "254e455bd1b5a5482f296f8afccf5bd10d109206e8350b9e8ec304576e0d0ae5"
|
||||
python-versions = "^3.6"
|
||||
|
||||
[metadata.hashes]
|
||||
|
|
@ -497,6 +506,7 @@ itsdangerous = ["321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f1
|
|||
jinja2 = ["74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", "9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"]
|
||||
lazy-object-proxy = ["0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", "194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", "1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", "4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", "48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", "5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", "59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", "8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", "9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", "9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", "97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", "9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", "a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", "a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", "ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", "cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", "d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", "d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", "eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", "efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"]
|
||||
markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
|
||||
marshmallow = ["0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e", "3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"]
|
||||
mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
|
||||
numpy = ["0a7a1dd123aecc9f0076934288ceed7fd9a81ba3919f11a855a7887cbe82a02f", "0c0763787133dfeec19904c22c7e358b231c87ba3206b211652f8cbe1241deb6", "3d52298d0be333583739f1aec9026f3b09fdfe3ddf7c7028cb16d9d2af1cca7e", "43bb4b70585f1c2d153e45323a886839f98af8bfa810f7014b20be714c37c447", "475963c5b9e116c38ad7347e154e5651d05a2286d86455671f5b1eebba5feb76", "64874913367f18eb3013b16123c9fed113962e75d809fca5b78ebfbb73ed93ba", "683828e50c339fc9e68720396f2de14253992c495fdddef77a1e17de55f1decc", "6ca4000c4a6f95a78c33c7dadbb9495c10880be9c89316aa536eac359ab820ae", "75fd817b7061f6378e4659dd792c84c0b60533e867f83e0d1e52d5d8e53df88c", "7d81d784bdbed30137aca242ab307f3e65c8d93f4c7b7d8f322110b2e90177f9", "8d0af8d3664f142414fd5b15cabfd3b6cc3ef242a3c7a7493257025be5a6955f", "9679831005fb16c6df3dd35d17aa31dc0d4d7573d84f0b44cc481490a65c7725", "a8f67ebfae9f575d85fa859b54d3bdecaeece74e3274b0b5c5f804d7ca789fe1", "acbf5c52db4adb366c064d0b7c7899e3e778d89db585feadd23b06b587d64761", "ada4805ed51f5bcaa3a06d3dd94939351869c095e30a2b54264f5a5004b52170", "c7354e8f0eca5c110b7e978034cd86ed98a7a5ffcf69ca97535445a595e07b8e", "e2e9d8c87120ba2c591f60e32736b82b67f72c37ba88a4c23c81b5b8fa49c018", "e467c57121fe1b78a8f68dd9255fbb3bb3f4f7547c6b9e109f31d14569f490c3", "ede47b98de79565fcd7f2decb475e2dcc85ee4097743e551fe26cfc7eb3ff143", "f58913e9227400f1395c7b800503ebfdb0772f1c33ff8cb4d6451c06cabdf316", "fe39f5fd4103ec4ca3cb8600b19216cd1ff316b4990f4c0b6057ad982c0a34d5"]
|
||||
packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"]
|
||||
|
|
@ -522,7 +532,7 @@ sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c
|
|||
sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"]
|
||||
sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"]
|
||||
toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
|
||||
typed-ast = ["1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"]
|
||||
typed-ast = ["18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"]
|
||||
urllib3 = ["a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", "f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"]
|
||||
werkzeug = ["7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7", "e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"]
|
||||
wrapt = ["565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ scipy = "1.3.0" # Exact version until we can guarantee a wheel
|
|||
|
||||
picamera = { git = "https://github.com/rwb27/picamera.git", branch = "lens-shading", optional = true } # Lens shading requires >1.14 or RWB fork, lens-shading branch.
|
||||
"RPi.GPIO" = { version = "^0.6.5", optional = true }
|
||||
marshmallow = "^3.3"
|
||||
|
||||
[tool.poetry.extras]
|
||||
rpi = ["picamera", "RPi.GPIO"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue