diff --git a/openflexure_microscope/api/v1/__init__.py b/openflexure_microscope/api/v1/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/openflexure_microscope/api/v1/blueprints/__init__.py b/openflexure_microscope/api/v1/blueprints/__init__.py deleted file mode 100644 index eea7c36c..00000000 --- a/openflexure_microscope/api/v1/blueprints/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import camera, stage, base, plugins, task diff --git a/openflexure_microscope/api/v1/blueprints/base.py b/openflexure_microscope/api/v1/blueprints/base.py deleted file mode 100644 index cf3b014b..00000000 --- a/openflexure_microscope/api/v1/blueprints/base.py +++ /dev/null @@ -1,253 +0,0 @@ -from openflexure_microscope.api.utilities import gen, JsonResponse -from openflexure_microscope.api.views import MicroscopeView - -from flask import Response, Blueprint, jsonify, request -import logging - - -class StreamAPI(MicroscopeView): - def get(self): - """ - Real-time MJPEG stream from the microscope camera - - .. :quickref: State; Camera stream - - :>header Accept: image/jpeg - :>header Content-Type: image/jpeg - :status 200: stream active - """ - # Restart stream worker thread - self.microscope.camera.start_worker() - - return Response( - gen(self.microscope.camera), - mimetype="multipart/x-mixed-replace; boundary=frame", - ) - - -class SnapshotAPI(MicroscopeView): - def get(self): - """ - Single snapshot from the camera stream - - .. :quickref: State; Camera snapshot - - :>header Accept: image/jpeg - :>header Content-Type: image/jpeg - :status 200: stream active - """ - # Restart stream worker thread - self.microscope.camera.start_worker() - - return Response(self.microscope.camera.get_frame(), mimetype="image/jpeg") - - -class StateAPI(MicroscopeView): - def get(self): - """ - JSON representation of the microscope object. - - .. :quickref: State; Microscope state - - **Example request**: - - .. sourcecode:: http - - GET /state/ HTTP/1.1 - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - { - "camera": { - "preview_active": false, - "record_active": false, - "stream_active": true - }, - "plugin": {}, - "stage": { - "backlash": { - "x": 128, - "y": 128, - "z": 128 - }, - "position": { - "x": -8080, - "y": 5665, - "z": -12600 - } - } - } - - :>header Accept: application/json - :>header Content-Type: application/json - :status 200: state available - """ - return jsonify(self.microscope.state) - - -class ConfigAPI(MicroscopeView): - def get(self): - """ - JSON representation of the microscope config. - - .. :quickref: Config; Get microscope config - - **Example request**: - - .. sourcecode:: http - - GET /config/ HTTP/1.1 - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - { - "id": "0e2c6fac5421429aac67c7903107bdd8", - "name": "docuscope-2000", - "fov": [4100, 3146], - "camera_settings": { - "image_resolution": [2592, 1944], - "numpy_resolution": [1312, 976], - "video_resolution": [832, 624], - "jpeg_quality": 75, - "picamera_settings": { - "analog_gain": 1.0, - "digital_gain": 1.0, - "awb_gains": [0.92578125, 2.94921875], - "awb_mode": "off", - "exposure_mode": "off", - "framerate": 24.0, - "saturation": 0, - "shutter_speed": 5378 - }, - }, - "stage_settings": { - "backlash": { - "x": 256, - "y": 256, - "z": 0 - } - } - "plugins": [ - "openflexure_microscope.plugins.default.autofocus:AutofocusPlugin", - "openflexure_microscope.plugins.default.scan:ScanPlugin", - "openflexure_microscope.plugins.default.camera_calibration:Plugin" - ] - } - - :>header Accept: application/json - :>header Content-Type: application/json - :status 200: state available - """ - return jsonify(self.microscope.read_settings(json_safe=True)) - - def post(self): - """ - Modify microscope configuration - - .. :quickref: Config; Set microscope config - - **Example request**: - - .. sourcecode:: http - - POST /config HTTP/1.1 - Accept: application/json - - { - "id": "0e2c6fac5421429aac67c7903107bdd8", - "name": "docuscope-2000", - "fov": [4100, 3146], - "camera_settings": { - "image_resolution": [2592, 1944], - "numpy_resolution": [1312, 976], - "video_resolution": [832, 624], - "jpeg_quality": 75, - "picamera_settings": { - "analog_gain": 1.0, - "digital_gain": 1.0, - "awb_gains": [0.92578125, 2.94921875], - "awb_mode": "off", - "exposure_mode": "off", - "framerate": 24.0, - "saturation": 0, - "shutter_speed": 5378 - }, - }, - "stage_settings": { - "backlash": { - "x": 256, - "y": 256, - "z": 0 - } - } - "plugins": [ - "openflexure_microscope.plugins.default.autofocus:AutofocusPlugin", - "openflexure_microscope.plugins.default.scan:ScanPlugin", - "openflexure_microscope.plugins.default.camera_calibration:Plugin" - ] - } - - :>header Accept: application/json - - :/tags", - view_func=capture.TagsAPI.as_view("capture_tags", microscope=microscope_obj), - ) - - # Capture routes - blueprint.add_url_rule( - "/capture//download/", - view_func=capture.DownloadAPI.as_view( - "capture_download", microscope=microscope_obj - ), - ) - - blueprint.add_url_rule( - "/capture//download", - view_func=capture.DownloadRedirectAPI.as_view( - "capture_download_redirect", microscope=microscope_obj - ), - ) - - blueprint.add_url_rule( - "/capture//", - view_func=capture.CaptureAPI.as_view("capture", microscope=microscope_obj), - ) - - blueprint.add_url_rule( - "/capture/", - view_func=capture.ListAPI.as_view("capture_list", microscope=microscope_obj), - ) - - # Preview routes - blueprint.add_url_rule( - "/preview/", - view_func=preview.GPUPreviewAPI.as_view( - "gpu_preview", microscope=microscope_obj - ), - ) - - # Function routes - blueprint.add_url_rule( - "/overlay", - view_func=function.OverlayAPI.as_view("overlay", microscope=microscope_obj), - ) - - blueprint.add_url_rule( - "/zoom", view_func=function.ZoomAPI.as_view("zoom", microscope=microscope_obj) - ) - - return blueprint diff --git a/openflexure_microscope/api/v1/blueprints/camera/capture.py b/openflexure_microscope/api/v1/blueprints/camera/capture.py deleted file mode 100644 index d5e66dc5..00000000 --- a/openflexure_microscope/api/v1/blueprints/camera/capture.py +++ /dev/null @@ -1,459 +0,0 @@ -from openflexure_microscope.api.utilities import get_bool, JsonResponse -from openflexure_microscope.api.views import MicroscopeView -from openflexure_microscope.utilities import filter_dict - -from flask import jsonify, request, abort, url_for, redirect, send_file - - -class ListAPI(MicroscopeView): - def get(self): - """ - Get list of image captures. - - .. :quickref: Captures; Get collection of captures - - :>header Accept: application/json - :query include_unavailable: return json representations of captures that have been completely deleted - - :>jsonarr boolean available: availability of capture data - :>jsonarr string filename: filename of capture - :>jsonarr string id: unique id of the capture object - :>jsonarr boolean keep_on_disk: keep the capture file on microscope after closing - :>jsonarr boolean locked: file locked for modifications (mostly used for video recording) - :>jsonarr string path: path on pi storage to the capture file, if available - :>jsonarr boolean stream: capture stored in-memory as a BytesIO stream - :>jsonarr 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 found - :status 404: no capture found with that id - """ - include_unavailable = get_bool(request.args.get("include_unavailable")) - - if include_unavailable: - captures = [image.state for image in self.microscope.camera.images] - else: - captures = [ - image.state - for image in self.microscope.camera.images - if image.state["available"] - ] - - return jsonify(captures) - - def delete(self): - """ - Delete all captures (not yet implemented) - - .. :quickref: Captures; Delete all captures - """ - for image in self.microscope.camera.images: - image.delete() - - captures = [image.state for image in self.microscope.camera.images] - - return jsonify(captures) - - def post(self): - """ - Create a new image capture. - - .. :quickref: Captures; New capture - - **Example request**: - - .. sourcecode:: http - - POST /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 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 - - :
json boolean available: availability of capture data - :>json string filename: filename of capture - :>json string id: unique id of the capture object - :>json boolean keep_on_disk: keep 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 - - """ - capture_obj = self.microscope.camera.image_from_id(capture_id) - - if not capture_obj: - return abort(404) # 404 Not Found - - # Get capture state - capture_metadata = capture_obj.state - - # Add API routes to returned state - uri_dict = { - "uri": { - "state": "{}".format(url_for(".capture", capture_id=capture_obj.id)) - } - } - - # If available, also add download link - if capture_metadata["available"]: - uri_dict["uri"]["download"] = "{}download/{}".format( - url_for(".capture", capture_id=capture_obj.id), capture_obj.filename - ) - - capture_metadata.update(uri_dict) - - return jsonify(capture_metadata) - - def delete(self, capture_id): - """ - Delete all capture data from the Pi, even if `keep_on_disk=true;`. - - .. :quickref: Capture; Delete capture. - """ - capture_obj = self.microscope.camera.image_from_id(capture_id) - - if not capture_obj: - return abort(404) # 404 Not Found - - capture_obj.delete() - - return jsonify({"return": capture_id}) - - def put(self, capture_id): - """ - Add arbitrary metadata to the capture - - .. :quickref: Capture; Update capture metadata - - **Example request**: - - .. sourcecode:: http - - PUT /camera/capture/d0b2067abbb946f19351e075c5e7cd5b HTTP/1.1 - Accept: application/json - - { - "user_id": "ofm_1234", - "patient_number": 1452, - } - - :>header Accept: application/json - - :
header Accept: image/jpeg - :query thumbnail: return an image thumbnail e.g. ?thumbnail=true - - :>header Content-Type: image/jpeg - :status 200: capture data found - :status 404: no capture found with that id - - """ - capture_obj = self.microscope.camera.image_from_id(capture_id) - - if not capture_obj or not capture_obj.state["available"]: - return abort(404) # 404 Not Found - - thumbnail = get_bool(request.args.get("thumbnail")) - - return redirect( - url_for( - ".capture_download", - capture_id=capture_id, - filename=capture_obj.filename, - thumbnail=thumbnail, - ), - code=307, - ) - - -class DownloadAPI(MicroscopeView): - def get(self, capture_id, filename): - - capture_obj = self.microscope.camera.image_from_id(capture_id) - - if not capture_obj or not capture_obj.state["available"]: - return abort(404) # 404 Not Found - - thumbnail = get_bool(request.args.get("thumbnail")) - - # If no filename is specified, redirect to the capture's currently set filename - if not filename: - return redirect( - url_for( - "capture_download", - capture_id=capture_id, - filename=capture_obj.filename, - thumbnail=thumbnail, - ), - code=307, - ) - - # Download the image data using the requested filename - if thumbnail: - img = capture_obj.thumbnail - else: - img = capture_obj.data - - return send_file(img, mimetype="image/jpeg") - - -class TagsAPI(MicroscopeView): - def get(self, capture_id): - """ - Return tag list for a capture. - - .. :quickref: Capture; Show capture tags - - **Example request**: - - .. sourcecode:: http - - GET /camera/capture/d0b2067abbb946f19351e075c5e7cd5b/tags HTTP/1.1 - Accept: text/json - - :>header Accept: text/json - - :>header Content-Type: text/json - :status 200: capture data found - :status 404: no capture found with that id - """ - capture_obj = self.microscope.camera.image_from_id(capture_id) - - if not capture_obj or not capture_obj.state["available"]: - return abort(404) # 404 Not Found - - metadata_tags = filter_dict(capture_obj.state, ["metadata", "tags"]) - - return jsonify(metadata_tags) - - def put(self, capture_id): - """ - Add tags to the capture - - .. :quickref: Capture; Update capture tags - - **Example request**: - - .. sourcecode:: http - - PUT /camera/capture/d0b2067abbb946f19351e075c5e7cd5b/tags HTTP/1.1 - Accept: application/json - - ["tests", "mytag", "someothertag"] - - :>header Accept: application/json - - :
header Accept: application/json - - :
header Accept: application/json - - :
header Accept: application/json - - :
header Accept: application/json - - :
header Accept: application/json - - :
header Accept: application/json - - :
header Accept: application/json - :query include_unavailable: return json representations of captures that have been completely deleted - - :>header Content-Type: application/json - """ - - data = tasks.states() - - return jsonify(data) - - def delete(self): - """ - Clean list of long-running tasks (running tasks persist). - - .. :quickref: Tasks; Clean collection of tasks - - :>header Accept: application/json - - :>header Content-Type: application/json - """ - - tasks.cleanup_tasks() - data = tasks.states() - - return jsonify(data) - - -class TaskAPI(MethodView): - def get(self, task_id): - """ - Get JSON representation of a task - - .. :quickref: Tasks; Get task - - **Example request**: - - .. sourcecode:: http - - GET /task/db13a66787e1419bb06b1504e4d80b0c/ HTTP/1.1 - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - { - "end_time": "2019-01-23 16-33-33", - "id": "db13a66787e1419bb06b1504e4d80b0c", - "return": [ - 0.848622546386467, - 0.6106785018091292, - ], - "start_time": "2019-01-23 16-33-13", - "status": "success" - } - - """ - - try: - task = tasks.tasks()[task_id] - except KeyError: - return abort(404) # 404 Not Found - - # Return task state - return jsonify(task.state) - - def delete(self, task_id): - """ - Terminate a particular task. - - .. :quickref: Tasks; Terminate a task - - :>header Accept: application/json - - :>header Content-Type: application/json - """ - - try: - task = tasks.tasks()[task_id] - except KeyError: - return abort(404) # 404 Not Found - - task.terminate() - - return jsonify(task.state) - - -def construct_blueprint(microscope_obj): - - blueprint = Blueprint("task_blueprint", __name__) - - blueprint.add_url_rule("/", view_func=TaskListAPI.as_view("task_list")) - - blueprint.add_url_rule("//", view_func=TaskAPI.as_view("task")) - - return blueprint