Loading openflexure_microscope/api/app.py +5 −1 Original line number Diff line number Diff line Loading @@ -143,7 +143,7 @@ task_blueprint = blueprints.task.construct_blueprint(api_microscope) app.register_blueprint(task_blueprint, url_prefix=uri("/task", "v1")) ### V2 # Tasks routes # Root routes v2_stream_blueprint = v2.blueprints.stream.construct_blueprint(api_microscope) app.register_blueprint(v2_stream_blueprint, url_prefix=uri("/", "v2")) Loading @@ -167,6 +167,10 @@ app.register_blueprint(v2_plugin_blueprint, url_prefix=uri("/plugins", "v2")) 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")) @app.route("/routes") def routes(): Loading openflexure_microscope/api/v2/blueprints/actions/__init__.py +87 −0 Original line number Diff line number Diff line 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 openflexure_microscope/api/v2/blueprints/actions/camera.py +166 −0 Original line number Diff line number Diff line 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) openflexure_microscope/api/v2/blueprints/actions/stage.py +54 −0 Original line number Diff line number Diff line 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) openflexure_microscope/api/v2/blueprints/actions/system.py +33 −0 Original line number Diff line number Diff line 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 Loading
openflexure_microscope/api/app.py +5 −1 Original line number Diff line number Diff line Loading @@ -143,7 +143,7 @@ task_blueprint = blueprints.task.construct_blueprint(api_microscope) app.register_blueprint(task_blueprint, url_prefix=uri("/task", "v1")) ### V2 # Tasks routes # Root routes v2_stream_blueprint = v2.blueprints.stream.construct_blueprint(api_microscope) app.register_blueprint(v2_stream_blueprint, url_prefix=uri("/", "v2")) Loading @@ -167,6 +167,10 @@ app.register_blueprint(v2_plugin_blueprint, url_prefix=uri("/plugins", "v2")) 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")) @app.route("/routes") def routes(): Loading
openflexure_microscope/api/v2/blueprints/actions/__init__.py +87 −0 Original line number Diff line number Diff line 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
openflexure_microscope/api/v2/blueprints/actions/camera.py +166 −0 Original line number Diff line number Diff line 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)
openflexure_microscope/api/v2/blueprints/actions/stage.py +54 −0 Original line number Diff line number Diff line 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)
openflexure_microscope/api/v2/blueprints/actions/system.py +33 −0 Original line number Diff line number Diff line 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