Plugin schema are now just plugin forms (requires eV 1.2)
This commit is contained in:
parent
0d7a5f71e0
commit
d9e07f913a
16 changed files with 106 additions and 98 deletions
|
|
@ -8,6 +8,6 @@ Developing Plugins
|
||||||
./plugins/structure.rst
|
./plugins/structure.rst
|
||||||
./plugins/routes.rst
|
./plugins/routes.rst
|
||||||
./plugins/hardware.rst
|
./plugins/hardware.rst
|
||||||
./plugins/schema.rst
|
./plugins/form.rst
|
||||||
./plugins/example.rst
|
./plugins/example.rst
|
||||||
./plugins/class.rst
|
./plugins/class.rst
|
||||||
|
|
@ -7,10 +7,10 @@ plugin.py
|
||||||
.. literalinclude:: example/plugin.py
|
.. literalinclude:: example/plugin.py
|
||||||
:language: python
|
:language: python
|
||||||
|
|
||||||
schema.json
|
form.json
|
||||||
+++++++++++
|
+++++++++++
|
||||||
|
|
||||||
.. literalinclude:: example/schema.json
|
.. literalinclude:: example/form.json
|
||||||
:language: JSON
|
:language: JSON
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
"isTask": false,
|
"isTask": false,
|
||||||
"selfUpdate": true,
|
"selfUpdate": true,
|
||||||
"route": "/hello",
|
"route": "/hello",
|
||||||
"schema": [
|
"form": [
|
||||||
{
|
{
|
||||||
"fieldType": "textInput",
|
"fieldType": "textInput",
|
||||||
"placeholder": "Enter a string",
|
"placeholder": "Enter a string",
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
"isTask": true,
|
"isTask": true,
|
||||||
"route": "/timelapse",
|
"route": "/timelapse",
|
||||||
"submitLabel": "Start timelapse",
|
"submitLabel": "Start timelapse",
|
||||||
"schema": [
|
"form": [
|
||||||
{
|
{
|
||||||
"fieldType": "numberInput",
|
"fieldType": "numberInput",
|
||||||
"name": "n_images",
|
"name": "n_images",
|
||||||
|
|
@ -9,7 +9,7 @@ import json
|
||||||
from flask import request, Response, escape, jsonify
|
from flask import request, Response, escape, jsonify
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.realpath(__file__))
|
HERE = os.path.dirname(os.path.realpath(__file__))
|
||||||
SCHEMA_PATH = os.path.join(HERE, "schema.json")
|
FORM_PATH = os.path.join(HERE, "forms.json")
|
||||||
|
|
||||||
### MICROSCOPE PLUGIN ###
|
### MICROSCOPE PLUGIN ###
|
||||||
|
|
||||||
|
|
@ -18,10 +18,10 @@ class MyPluginClass(MicroscopePlugin):
|
||||||
A set of default plugins
|
A set of default plugins
|
||||||
"""
|
"""
|
||||||
|
|
||||||
global SCHEMA_PATH
|
global FORM_PATH
|
||||||
|
|
||||||
with open(SCHEMA_PATH, 'r') as sc:
|
with open(FORM_PATH, 'r') as sc:
|
||||||
api_schema = json.load(sc)
|
api_form = json.load(sc)
|
||||||
|
|
||||||
api_views = {
|
api_views = {
|
||||||
'/identify': IdentifyAPI,
|
'/identify': IdentifyAPI,
|
||||||
|
|
|
||||||
|
|
@ -4,39 +4,39 @@ Adding a GUI
|
||||||
Introduction
|
Introduction
|
||||||
------------
|
------------
|
||||||
|
|
||||||
In order to bind user-interface elements to your plugin, an ``api_schema`` variable must be included in your microscope plugin, similar to your ``api_views``.
|
In order to bind user-interface elements to your plugin, an ``api_form`` variable must be included in your microscope plugin, similar to your ``api_views``.
|
||||||
|
|
||||||
This variable must be in the form of a dictionary, with all data able to be parsed into JSON. Because of this requirement, it is suggested that you create your API schema as a JSON file, and load that file as a dictionary into your plugin. For example:
|
This variable must be in the form of a dictionary, with all data able to be parsed into JSON. Because of this requirement, it is suggested that you create your API form as a JSON file, and load that file as a dictionary into your plugin. For example:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.realpath(__file__)) # Find the full path of your plugin Python file
|
HERE = os.path.dirname(os.path.realpath(__file__)) # Find the full path of your plugin Python file
|
||||||
SCHEMA_PATH = os.path.join(HERE, "schema.json") # Find the full path of the adjascent JSON file
|
FORM_PATH = os.path.join(HERE, "form.json") # Find the full path of the adjascent JSON file
|
||||||
|
|
||||||
class MyPluginClass(MicroscopePlugin):
|
class MyPluginClass(MicroscopePlugin):
|
||||||
|
|
||||||
with open(SCHEMA_PATH, 'r') as sc: # Open the JSON file
|
with open(FORM_PATH, 'r') as sc: # Open the JSON file
|
||||||
api_schema = json.load(sc) # Load the JSON file into the api_schema dictionary
|
api_form = json.load(sc) # Load the JSON file into the api_form dictionary
|
||||||
|
|
||||||
Throughout this documentation, all example of ``api_schema`` sections will be in JSON format. Keep in mind however that it is possible to directly define your ``api_schema`` as a dictionary, without loading an external file.
|
Throughout this documentation, all example of ``api_form`` sections will be in JSON format. Keep in mind however that it is possible to directly define your ``api_form`` as a dictionary, without loading an external file.
|
||||||
|
|
||||||
|
|
||||||
Forms from ``api_schema``
|
Forms from ``api_form``
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
The ``api_schema`` object essentially describes HTML forms, which it is up to the client to render. The form is constructed by specifying a set of components, and their values. A form can update it's values by sending a GET request to the API route bound to that form, and can send it's current values via a POST request to *this same API route*.
|
The ``api_form`` object essentially describes HTML forms, which it is up to the client to render. The form is constructed by specifying a set of components, and their values. A form can update it's values by sending a GET request to the API route bound to that form, and can send it's current values via a POST request to *this same API route*.
|
||||||
|
|
||||||
Each component in the form has a ``name`` property, which must match up to a property your API route expects in JSON POST requests, and returns in JSON GET requests.
|
Each component in the form has a ``name`` property, which must match up to a property your API route expects in JSON POST requests, and returns in JSON GET requests.
|
||||||
|
|
||||||
Structure of ``api_schema``
|
Structure of ``api_form``
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
Root level
|
Root level
|
||||||
++++++++++
|
++++++++++
|
||||||
|
|
||||||
The root of your ``api_schema`` expects 3 properties:
|
The root of your ``api_form`` expects 3 properties:
|
||||||
|
|
||||||
``id`` - A unique ID to give your client-side plugin
|
``id`` - A unique ID to give your client-side plugin
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ Each form is described by a JSON object, with the following properties:
|
||||||
|
|
||||||
``submitLabel`` *(optional)* - String to place inside of the form's submit button
|
``submitLabel`` *(optional)* - String to place inside of the form's submit button
|
||||||
|
|
||||||
``schema`` - An array of form components as described below
|
``form`` - An array of form components as described below
|
||||||
|
|
||||||
Component level
|
Component level
|
||||||
+++++++++++++++
|
+++++++++++++++
|
||||||
|
|
@ -182,11 +182,11 @@ Overview of components
|
||||||
JSON form example
|
JSON form example
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
.. literalinclude:: schema_example.json
|
.. literalinclude:: forms_example.json
|
||||||
:language: JSON
|
:language: JSON
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
./schema/json_schema.rst
|
./form/json_form.rst
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"selfUpdate": true,
|
"selfUpdate": true,
|
||||||
"route": "/do",
|
"route": "/do",
|
||||||
"submitLabel": "Do things",
|
"submitLabel": "Do things",
|
||||||
"schema": [
|
"form": [
|
||||||
{
|
{
|
||||||
"fieldType": "numberInput",
|
"fieldType": "numberInput",
|
||||||
"placeholder": "Some integer",
|
"placeholder": "Some integer",
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
"selfUpdate": true,
|
"selfUpdate": true,
|
||||||
"route": "/task",
|
"route": "/task",
|
||||||
"submitLabel": "Start task",
|
"submitLabel": "Start task",
|
||||||
"schema": [
|
"form": [
|
||||||
{
|
{
|
||||||
"fieldType": "numberInput",
|
"fieldType": "numberInput",
|
||||||
"name": "run_time",
|
"name": "run_time",
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
Full JSON Schema
|
Full JSON Form
|
||||||
================
|
================
|
||||||
|
|
||||||
.. literalinclude:: schema.json
|
.. literalinclude:: form.json
|
||||||
:language: JSON
|
:language: JSON
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"definitions": {},
|
"definitions": {},
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$form": "http://json-form.org/draft-07/form#",
|
||||||
"$id": "http://example.com/root.json",
|
"$id": "http://example.com/root.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "The Root Schema",
|
"title": "The Root Form",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"icon",
|
"icon",
|
||||||
|
|
@ -41,11 +41,11 @@
|
||||||
"items": {
|
"items": {
|
||||||
"$id": "#/properties/forms/items",
|
"$id": "#/properties/forms/items",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "The Items Schema",
|
"title": "The Items Form",
|
||||||
"required": [
|
"required": [
|
||||||
"name",
|
"name",
|
||||||
"route",
|
"route",
|
||||||
"schema"
|
"form"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
"isCollapsible": {
|
"isCollapsible": {
|
||||||
"$id": "#/properties/forms/items/properties/isCollapsible",
|
"$id": "#/properties/forms/items/properties/isCollapsible",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"title": "The isCollapsible Schema",
|
"title": "The isCollapsible Form",
|
||||||
"description": "Should the form be rendered as a collapsible item",
|
"description": "Should the form be rendered as a collapsible item",
|
||||||
"default": false,
|
"default": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
"isTask": {
|
"isTask": {
|
||||||
"$id": "#/properties/forms/items/properties/isTask",
|
"$id": "#/properties/forms/items/properties/isTask",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"title": "The isTask Schema",
|
"title": "The isTask Form",
|
||||||
"description": "Should the form's submit function be treated as a long-running task",
|
"description": "Should the form's submit function be treated as a long-running task",
|
||||||
"default": false,
|
"default": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
"selfUpdate": {
|
"selfUpdate": {
|
||||||
"$id": "#/properties/forms/items/properties/selfUpdate",
|
"$id": "#/properties/forms/items/properties/selfUpdate",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"title": "The selfUpdate Schema",
|
"title": "The selfUpdate Form",
|
||||||
"description": "Should the form's component values be automatically updated with a GET request",
|
"description": "Should the form's component values be automatically updated with a GET request",
|
||||||
"default": false,
|
"default": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
"route": {
|
"route": {
|
||||||
"$id": "#/properties/forms/items/properties/route",
|
"$id": "#/properties/forms/items/properties/route",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "The Route Schema",
|
"title": "The Route Form",
|
||||||
"description": "Form submit POST request's corresponding api_views route",
|
"description": "Form submit POST request's corresponding api_views route",
|
||||||
"default": "",
|
"default": "",
|
||||||
"examples": [
|
"examples": [
|
||||||
|
|
@ -103,7 +103,7 @@
|
||||||
"submitLabel": {
|
"submitLabel": {
|
||||||
"$id": "#/properties/forms/items/properties/submitLabel",
|
"$id": "#/properties/forms/items/properties/submitLabel",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "The submitLabel Schema",
|
"title": "The submitLabel Form",
|
||||||
"description": "String to place in submit button",
|
"description": "String to place in submit button",
|
||||||
"default": "Submit",
|
"default": "Submit",
|
||||||
"examples": [
|
"examples": [
|
||||||
|
|
@ -111,12 +111,12 @@
|
||||||
],
|
],
|
||||||
"pattern": "^(.*)$"
|
"pattern": "^(.*)$"
|
||||||
},
|
},
|
||||||
"schema": {
|
"form": {
|
||||||
"$id": "#/properties/forms/items/properties/schema",
|
"$id": "#/properties/forms/items/properties/form",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"title": "Form component array",
|
"title": "Form component array",
|
||||||
"items": {
|
"items": {
|
||||||
"$id": "#/properties/forms/items/properties/schema/items",
|
"$id": "#/properties/forms/items/properties/form/items",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "A form component",
|
"title": "A form component",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -125,7 +125,7 @@
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"fieldType": {
|
"fieldType": {
|
||||||
"$id": "#/properties/forms/items/properties/schema/items/properties/fieldType",
|
"$id": "#/properties/forms/items/properties/form/items/properties/fieldType",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Component type",
|
"title": "Component type",
|
||||||
"default": "",
|
"default": "",
|
||||||
|
|
@ -143,7 +143,7 @@
|
||||||
"pattern": "^(.*)$"
|
"pattern": "^(.*)$"
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"$id": "#/properties/forms/items/properties/schema/items/properties/placeholder",
|
"$id": "#/properties/forms/items/properties/form/items/properties/placeholder",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Component placeholder value",
|
"title": "Component placeholder value",
|
||||||
"default": "",
|
"default": "",
|
||||||
|
|
@ -153,7 +153,7 @@
|
||||||
"pattern": "^(.*)$"
|
"pattern": "^(.*)$"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"$id": "#/properties/forms/items/properties/schema/items/properties/name",
|
"$id": "#/properties/forms/items/properties/form/items/properties/name",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Unique component name",
|
"title": "Unique component name",
|
||||||
"examples": [
|
"examples": [
|
||||||
|
|
@ -162,7 +162,7 @@
|
||||||
"pattern": "^(.*)$"
|
"pattern": "^(.*)$"
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"$id": "#/properties/forms/items/properties/schema/items/properties/label",
|
"$id": "#/properties/forms/items/properties/form/items/properties/label",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Component label (if applicable)",
|
"title": "Component label (if applicable)",
|
||||||
"default": "",
|
"default": "",
|
||||||
|
|
@ -172,7 +172,7 @@
|
||||||
"pattern": "^(.*)$"
|
"pattern": "^(.*)$"
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"$id": "#/properties/forms/items/properties/schema/items/properties/value",
|
"$id": "#/properties/forms/items/properties/form/items/properties/value",
|
||||||
"type": ["array", "boolean", "integer", "number", "object", "string"],
|
"type": ["array", "boolean", "integer", "number", "object", "string"],
|
||||||
"title": "Component value",
|
"title": "Component value",
|
||||||
"pattern": "^(.*)$"
|
"pattern": "^(.*)$"
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,21 @@ from openflexure_microscope.api.v1.views import MicroscopeView
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
class PluginSchemaAPI(MicroscopeView):
|
class PluginFormAPI(MicroscopeView):
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""
|
||||||
Return the current plugin schemas
|
Return the current plugin forms
|
||||||
|
|
||||||
.. :quickref: Plugin; Get schemas
|
.. :quickref: Plugin; Get forms
|
||||||
|
|
||||||
Returns an array of present plugin schemas (describing plugin user interfaces.)
|
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
|
Please note, this is *not* a list of all enabled plugins, only those with associated
|
||||||
user interface forms.
|
user interface forms.
|
||||||
|
|
||||||
A complete list of enabled plugins can be found in the microscope state.
|
A complete list of enabled plugins can be found in the microscope state.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
out = self.microscope.plugin.schemas
|
out = self.microscope.plugin.forms
|
||||||
return jsonify(out)
|
return jsonify(out)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -28,10 +28,10 @@ def construct_blueprint(microscope_obj):
|
||||||
|
|
||||||
blueprint = Blueprint('plugin_blueprint', __name__)
|
blueprint = Blueprint('plugin_blueprint', __name__)
|
||||||
|
|
||||||
# Create a base route to return plugin API schemas, if any exist
|
# Create a base route to return plugin API forms, if any exist
|
||||||
blueprint.add_url_rule(
|
blueprint.add_url_rule(
|
||||||
'/',
|
'/',
|
||||||
view_func=PluginSchemaAPI.as_view('plugin_api_schema', microscope=microscope_obj)
|
view_func=PluginFormAPI.as_view('plugin_api_form', microscope=microscope_obj)
|
||||||
)
|
)
|
||||||
|
|
||||||
all_routes = []
|
all_routes = []
|
||||||
|
|
@ -86,20 +86,21 @@ def construct_blueprint(microscope_obj):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# If plugin includes an API schema
|
# If plugin includes an API form
|
||||||
if hasattr(plugin_obj, 'api_schema') and isinstance(plugin_obj.api_schema, dict):
|
if hasattr(plugin_obj, 'api_form') and isinstance(plugin_obj.api_form, dict):
|
||||||
schema = plugin_obj.api_schema
|
api_form_info = plugin_obj.api_form
|
||||||
# TODO: Validate schema? We need to make sure no single plugin can break all plugins.
|
# TODO: Validate form? We need to make sure no single plugin can break all plugins.
|
||||||
schema['id'] = plugin_name
|
api_form_info['id'] = plugin_name
|
||||||
if 'forms' in schema and isinstance(schema['forms'], list):
|
if 'forms' in api_form_info and isinstance(api_form_info['forms'], list):
|
||||||
for form in schema['forms']:
|
for form in api_form_info['forms']:
|
||||||
if 'route' in form and form['route'] in expanded_routes.keys():
|
if 'route' in form and form['route'] in expanded_routes.keys():
|
||||||
form['route'] = expanded_routes[form['route']]
|
form['route'] = expanded_routes[form['route']]
|
||||||
else:
|
else:
|
||||||
logging.warn("No valid expandable route found for {}".format(form['route']))
|
logging.warn("No valid expandable route found for {}".format(form['route']))
|
||||||
|
|
||||||
# Store the complete schema in Microscope().plugin.schema
|
# Store the complete form in Microscope().plugin.form
|
||||||
microscope_obj.plugin.schemas.append(schema)
|
microscope_obj.plugin.forms.append(api_form_info)
|
||||||
|
print(microscope_obj.plugin.forms)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,6 @@ class MockStreamer(BaseCamera):
|
||||||
# If stream was paused to update config, unpause
|
# If stream was paused to update config, unpause
|
||||||
if paused_stream:
|
if paused_stream:
|
||||||
logging.info("Resuming stream.")
|
logging.info("Resuming stream.")
|
||||||
self.start_stream_recording()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
|
|
|
||||||
|
|
@ -94,26 +94,32 @@ class Microscope:
|
||||||
# Maybe even attach dummy hardware at __init__, and replace with real hardware if it exists
|
# Maybe even attach dummy hardware at __init__, and replace with real hardware if it exists
|
||||||
|
|
||||||
logging.debug("Attaching camera...")
|
logging.debug("Attaching camera...")
|
||||||
self.camera = camera #: :py:class:`openflexure_microscope.camera.base.BaseCamera`: Picamera object
|
self.camera = (
|
||||||
|
camera
|
||||||
|
) #: :py:class:`openflexure_microscope.camera.base.BaseCamera`: Picamera object
|
||||||
if not self.camera:
|
if not self.camera:
|
||||||
logging.info("No camera attached.")
|
logging.info("No camera attached.")
|
||||||
else:
|
else:
|
||||||
logging.info("Attached camera {}".format(camera))
|
logging.info("Attached camera {}".format(camera))
|
||||||
|
|
||||||
if hasattr(self.camera, 'lock'): # If camera has a lock
|
if hasattr(self.camera, "lock"): # If camera has a lock
|
||||||
logging.info("Attaching {} to composite lock.".format(self.camera.lock))
|
logging.info("Attaching {} to composite lock.".format(self.camera.lock))
|
||||||
# Add the lock to the microscope composite lock
|
# Add the lock to the microscope composite lock
|
||||||
self.lock.locks.append(self.camera.lock)
|
self.lock.locks.append(self.camera.lock)
|
||||||
|
|
||||||
logging.debug("Attaching stage...")
|
logging.debug("Attaching stage...")
|
||||||
self.stage = stage #: :py:class:`openflexure_microscope.stage.base.BaseStage`: OpenFlexure stage object
|
self.stage = (
|
||||||
|
stage
|
||||||
|
) #: :py:class:`openflexure_microscope.stage.base.BaseStage`: OpenFlexure stage object
|
||||||
if not self.stage:
|
if not self.stage:
|
||||||
logging.info("No stage attached.")
|
logging.info("No stage attached.")
|
||||||
else:
|
else:
|
||||||
logging.info("Attached stage {}".format(stage))
|
logging.info("Attached stage {}".format(stage))
|
||||||
|
|
||||||
if hasattr(self.stage, 'lock'): # If stage object has a lock
|
if hasattr(self.stage, "lock"): # If stage object has a lock
|
||||||
logging.info("Attaching lock {} to composite lock.".format(self.stage.lock))
|
logging.info(
|
||||||
|
"Attaching lock {} to composite lock.".format(self.stage.lock)
|
||||||
|
)
|
||||||
# Add the lock to the microscope composite lock
|
# Add the lock to the microscope composite lock
|
||||||
self.lock.locks.append(self.stage.lock)
|
self.lock.locks.append(self.stage.lock)
|
||||||
|
|
||||||
|
|
@ -149,10 +155,10 @@ class Microscope:
|
||||||
and :py:attr:`openflexure_microscope.camera.base.BaseCamera.state`
|
and :py:attr:`openflexure_microscope.camera.base.BaseCamera.state`
|
||||||
"""
|
"""
|
||||||
state = {
|
state = {
|
||||||
'camera': self.camera.state,
|
"camera": self.camera.state,
|
||||||
'stage': self.stage.state,
|
"stage": self.stage.state,
|
||||||
'plugin': self.plugin.state,
|
"plugin": self.plugin.state,
|
||||||
'version': pkg_resources.get_distribution('openflexure_microscope').version
|
"version": pkg_resources.get_distribution("openflexure_microscope").version,
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
@ -163,22 +169,22 @@ class Microscope:
|
||||||
logging.debug("Microscope: Applying config: {}".format(config))
|
logging.debug("Microscope: Applying config: {}".format(config))
|
||||||
|
|
||||||
# If attached to a camera
|
# If attached to a camera
|
||||||
if ('camera_settings' in config) and self.camera:
|
if ("camera_settings" in config) and self.camera:
|
||||||
self.camera.apply_config(config['camera_settings'])
|
self.camera.apply_config(config["camera_settings"])
|
||||||
|
|
||||||
# If attached to a stage
|
# If attached to a stage
|
||||||
if ('stage_settings' in config) and self.stage:
|
if ("stage_settings" in config) and self.stage:
|
||||||
self.stage.apply_config(config['stage_settings'])
|
self.stage.apply_config(config["stage_settings"])
|
||||||
|
|
||||||
# Todo: tidy up with some loopy goodness
|
# Todo: tidy up with some loopy goodness
|
||||||
if 'id' in config:
|
if "id" in config:
|
||||||
self.id = config['id']
|
self.id = config["id"]
|
||||||
if 'name' in config:
|
if "name" in config:
|
||||||
self.name = config['name']
|
self.name = config["name"]
|
||||||
if 'fov' in config:
|
if "fov" in config:
|
||||||
self.fov = config['fov']
|
self.fov = config["fov"]
|
||||||
if 'plugins' in config:
|
if "plugins" in config:
|
||||||
self.plugin_maps = config['plugins']
|
self.plugin_maps = config["plugins"]
|
||||||
|
|
||||||
def read_config(self, json_safe=False):
|
def read_config(self, json_safe=False):
|
||||||
"""
|
"""
|
||||||
|
|
@ -192,21 +198,21 @@ class Microscope:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
settings_current = {
|
settings_current = {
|
||||||
'id': self.id,
|
"id": self.id,
|
||||||
'name': self.name,
|
"name": self.name,
|
||||||
'fov': self.fov,
|
"fov": self.fov,
|
||||||
'plugins': self.plugin_maps
|
"plugins": self.plugin_maps,
|
||||||
}
|
}
|
||||||
|
|
||||||
# If attached to a camera
|
# If attached to a camera
|
||||||
if self.camera:
|
if self.camera:
|
||||||
settings_current_camera = self.camera.read_config()
|
settings_current_camera = self.camera.read_config()
|
||||||
settings_current['camera_settings'] = settings_current_camera
|
settings_current["camera_settings"] = settings_current_camera
|
||||||
|
|
||||||
# If attached to a stage
|
# If attached to a stage
|
||||||
if self.stage:
|
if self.stage:
|
||||||
settings_current_stage = self.stage.read_config()
|
settings_current_stage = self.stage.read_config()
|
||||||
settings_current['stage_settings'] = settings_current_stage
|
settings_current["stage_settings"] = settings_current_stage
|
||||||
|
|
||||||
settings_full = self.settings_file.merge(settings_current)
|
settings_full = self.settings_file.merge(settings_current)
|
||||||
|
|
||||||
|
|
@ -222,7 +228,9 @@ class Microscope:
|
||||||
# Read curent config
|
# Read curent config
|
||||||
current_config = self.read_config()
|
current_config = self.read_config()
|
||||||
# Merge in server version responsible for saving the config file
|
# Merge in server version responsible for saving the config file
|
||||||
current_config['server_version'] = pkg_resources.get_distribution('openflexure_microscope').version
|
current_config["server_version"] = pkg_resources.get_distribution(
|
||||||
|
"openflexure_microscope"
|
||||||
|
).version
|
||||||
# Save config to file
|
# Save config to file
|
||||||
self.settings_file.save(current_config, backup=True)
|
self.settings_file.save(current_config, backup=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ class PluginMount(object):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.plugins = [] # List of plugin objects
|
self.plugins = [] # List of plugin objects
|
||||||
self.schemas = [] # List of plugin schemas
|
self.forms = [] # List of plugin forms
|
||||||
logging.info("Creating plugin mount")
|
logging.info("Creating plugin mount")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -182,7 +182,7 @@ class PluginMount(object):
|
||||||
logging.info(ConColors.OKGREEN + "Plugin {} loaded as {}.".format(plugin_map, plugin_name) + ConColors.ENDC)
|
logging.info(ConColors.OKGREEN + "Plugin {} loaded as {}.".format(plugin_map, plugin_name) + ConColors.ENDC)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.error("Error loading plugin. Moving on.")
|
logging.error(f"Error loading plugin {plugin_map}. Moving on.")
|
||||||
|
|
||||||
class MicroscopePlugin:
|
class MicroscopePlugin:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"selfUpdate": true,
|
"selfUpdate": true,
|
||||||
"route": "/do",
|
"route": "/do",
|
||||||
"submitLabel": "Do things",
|
"submitLabel": "Do things",
|
||||||
"schema": [
|
"form": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"fieldType": "numberInput",
|
"fieldType": "numberInput",
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
"selfUpdate": true,
|
"selfUpdate": true,
|
||||||
"route": "/task",
|
"route": "/task",
|
||||||
"submitLabel": "Start task",
|
"submitLabel": "Start task",
|
||||||
"schema": [
|
"form": [
|
||||||
{
|
{
|
||||||
"fieldType": "numberInput",
|
"fieldType": "numberInput",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
|
|
@ -7,16 +7,16 @@ from openflexure_microscope.devel import MicroscopePlugin
|
||||||
from .api import DoAPI, TaskAPI
|
from .api import DoAPI, TaskAPI
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.realpath(__file__))
|
HERE = os.path.dirname(os.path.realpath(__file__))
|
||||||
SCHEMA_PATH = os.path.join(HERE, "schema.json")
|
FORM_PATH = os.path.join(HERE, "forms.json")
|
||||||
|
|
||||||
class ExamplePlugin(MicroscopePlugin):
|
class ExamplePlugin(MicroscopePlugin):
|
||||||
"""
|
"""
|
||||||
An example plugin using a comprehensive schema
|
An example plugin using a comprehensive form
|
||||||
"""
|
"""
|
||||||
global SCHEMA_PATH
|
global FORM_PATH
|
||||||
|
|
||||||
with open(SCHEMA_PATH, 'r') as sc:
|
with open(FORM_PATH, 'r') as sc:
|
||||||
api_schema = json.load(sc)
|
api_form = json.load(sc)
|
||||||
|
|
||||||
api_views = {
|
api_views = {
|
||||||
'/do': DoAPI,
|
'/do': DoAPI,
|
||||||
|
|
@ -35,7 +35,7 @@ class ExamplePlugin(MicroscopePlugin):
|
||||||
|
|
||||||
def set_values(self, val_int, val_str, val_radio, val_check, val_select, val_disposable):
|
def set_values(self, val_int, val_str, val_radio, val_check, val_select, val_disposable):
|
||||||
"""
|
"""
|
||||||
Demonstrate a plugin with schema
|
Demonstrate a plugin with form
|
||||||
"""
|
"""
|
||||||
if val_int:
|
if val_int:
|
||||||
self.val_int = int(val_int)
|
self.val_int = int(val_int)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue