Loading openflexure_microscope/api/app.py +82 −151 Original line number Diff line number Diff line #!/usr/bin/env python """ TODO: Implement API route to cleanly shut down server TODO: Implement plugin API routes somehow """ from pprint import pprint import numpy as np from importlib import import_module import time import datetime import os from flask import ( Flask, render_template, Response, redirect, request, jsonify, send_file) import numpy as np from openflexure_microscope.camera.pi import StreamingCamera app = Flask(__name__) cam = StreamingCamera() def parse_payload(request): """Convert request to JSON. Will eventually handle error-checking.""" # TODO: Handle invalid JSON payloads state = request.get_json() return state Flask, render_template, Response, url_for, redirect, request, jsonify, send_file, abort, make_response) def gen(camera): """Video streaming generator function.""" while True: # the obtained frame is a jpeg frame = camera.get_frame() from flask.views import MethodView from werkzeug.exceptions import default_exceptions yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') from openflexure_microscope.api.utilities import parse_payload, get_from_payload, gen, get_bool, list_routes from openflexure_microscope import Microscope, config from openflexure_microscope.camera.pi import StreamingCamera from openflexure_stage import OpenFlexureStage @app.route('/') def index(): """Video streaming home page.""" cam.start_worker() # Start the stream return render_template('index.html') import atexit import logging, sys @app.route('/stream') def stream(): """Video streaming route. Put this in the src attribute of an img tag.""" return Response( gen(cam), mimetype='multipart/x-mixed-replace; boundary=frame') logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) # Create a dummy microscope object, with no hardware attachments api_microscope = Microscope(None, None) logging.debug("Created an empty microscope in global.") # TODO: Be able to change the image resolution with an option @app.route('/capture/', methods=['GET', 'POST', 'PUT']) def capture(): """ POST/PUT: Capture a single image. GET: Return capture status, or download latest capture. """ if request.method == 'POST' or request.method == 'PUT': state = parse_payload(request) print(state) # Handle filename argument if 'filename' in state: filename = state['filename'] else: filename = None # Generate API URI based on version from filename def uri(suffix, api_version, base=None): if not base: base = "/api/{}".format(api_version) uri = base + suffix logging.debug("Created app route: {}".format(uri)) return uri if 'write_to_file' in state: write_to_file = bool(state['write_to_file']) else: write_to_file = False # Create flask app app = Flask(__name__) app.url_map.strict_slashes = False if 'use_video_port' in state: use_video_port = bool(state['use_video_port']) else: use_video_port = False if 'resize' in state: resize_h = int(state['resize']) resize_w = int(resize_h*(4/3)) resize = (resize_w, resize_h) else: resize = None # Make errors more API friendly response = cam.capture( write_to_file=write_to_file, use_video_port=use_video_port, filename=filename, resize=resize) def _handle_http_exception(e): return make_response( jsonify({ 'status_code': e.code, 'error': e.name, 'details': e.description }), e.code) return str(response) + "\n" for code in default_exceptions: app.errorhandler(code)(_handle_http_exception) else: # If GET request download = request.args.get('download') # After app starts, but before first request, attach hardware to global microscope @app.before_first_request def attach_microscope(): # Create the microscope object globally (common to all spawned server threads) global api_microscope logging.debug("First request made. Populating microscope with hardware...") openflexurerc = config.load_config() # Load default user config state = cam.state api_microscope.attach( StreamingCamera(config=openflexurerc), OpenFlexureStage("/dev/ttyUSB0") ) if (( download == 'true' or download == 'True' or download == '1') and cam.image is not None): logging.debug("Microscope successfully attached!") return send_file(cam.image.data, mimetype='image/jpeg') else: return jsonify(state) ##### WEBAPP ROUTES ###### @app.route('/record/', methods=['GET', 'POST', 'PUT']) def record(): """ POST/PUT: Start or stop a video recording. GET: Return recording status, or download latest recording @app.route('/') def index(): """ if request.method == 'POST' or request.method == 'PUT': state = request.get_json() print(state) # Handle filename argument if 'filename' in state: filename = state['filename'] else: filename = None # Handle status argument if 'status' in state: status = bool(state['status']) # synchronise the arduino_time if status is True: response = cam.start_recording(filename=filename) return str(response) elif status is False: response = cam.stop_recording() return str(response) else: download = request.args.get('download') state = cam.state if (( download == 'true' or download == 'True' or download == '1') and cam.state['recent_video'] is not None): return send_file( cam.state['recent_video'], mimetype='video/H264', as_attachment=True) else: return jsonify(state) @app.route('/preview/', methods=['GET', 'POST', 'PUT']) def preview(): API demo app """ POST/PUT: Start or stop the direct-to-GPU camera preview on the Pi. return render_template( 'index_v1.html' ) GET: Return preview status """ if request.method == 'POST' or request.method == 'PUT': state = request.get_json() print(state) ##### API ROUTES ###### from openflexure_microscope.api.v1 import blueprints # Base routes base_blueprint = blueprints.base.construct_blueprint(api_microscope) app.register_blueprint(base_blueprint, url_prefix=uri('', 'v1')) if 'status' in state: status = bool(state['status']) # Stage routes stage_blueprint = blueprints.stage.construct_blueprint(api_microscope) app.register_blueprint(stage_blueprint, url_prefix=uri('/stage', 'v1')) if status is False: response = cam.stop_preview() else: response = cam.start_preview() # Camera routes camera_blueprint = blueprints.camera.construct_blueprint(api_microscope) app.register_blueprint(camera_blueprint, url_prefix=uri('/camera', 'v1')) return str(response) # List all routes list_routes(app) else: # Get args passed via URL (not useful here. Just for reference.) state = cam.state return jsonify(state) # Automatically clean up microscope at exit def cleanup(): global api_microscope api_microscope.close() atexit.register(cleanup) if __name__ == '__main__': app.run(host='0.0.0.0', threaded=True, debug=True, use_reloader=False) if __name__ == "__main__": app.run(host='0.0.0.0', port="5000", threaded=True, debug=True, use_reloader=False) openflexure_microscope/api/utilities.py +8 −1 Original line number Diff line number Diff line import pprint def parse_payload(request): """Convert request to JSON. Will eventually handle error-checking.""" # TODO: Handle invalid JSON payloads Loading Loading @@ -32,3 +34,8 @@ def get_bool(get_arg): return True else: return False def list_routes(app): """Print available functions.""" pprint.pprint(list(map(lambda x: repr(x), app.url_map.iter_rules()))) No newline at end of file openflexure_microscope/api/v0.pydeleted 100644 → 0 +0 −172 Original line number Diff line number Diff line #!/usr/bin/env python from pprint import pprint from importlib import import_module import time import datetime from flask import ( Flask, render_template, Response, redirect, request, jsonify, send_file) import numpy as np from openflexure_microscope.api.utilities import parse_payload, gen from openflexure_microscope.camera.pi import StreamingCamera app = Flask(__name__) cam = StreamingCamera() @app.route('/') def index(): """Video streaming home page.""" cam.start_worker() # Start the stream return render_template('index_v0.html') @app.route('/stream') def stream(): """Video streaming route. Put this in the src attribute of an img tag.""" return Response( gen(cam), mimetype='multipart/x-mixed-replace; boundary=frame') # TODO: Be able to change the image resolution with an option @app.route('/capture/', methods=['GET', 'POST', 'PUT']) def capture(): """ POST/PUT: Capture a single image. GET: Return capture status, or download latest capture. """ if request.method == 'POST' or request.method == 'PUT': state = parse_payload(request) print(state) # Handle filename argument if 'filename' in state: filename = state['filename'] else: filename = None if 'write_to_file' in state: write_to_file = bool(state['write_to_file']) else: write_to_file = False if 'use_video_port' in state: use_video_port = bool(state['use_video_port']) else: use_video_port = False if 'resize' in state: resize_h = int(state['resize']) resize_w = int(resize_h*(4/3)) resize = (resize_w, resize_h) else: resize = None response = cam.capture( write_to_file=write_to_file, use_video_port=use_video_port, filename=filename, resize=resize) return str(response) + "\n" else: # If GET request download = request.args.get('download') state = cam.state if (( download == 'true' or download == 'True' or download == '1') and cam.image is not None): return send_file(cam.image.data, mimetype='image/jpeg') else: return jsonify(state) @app.route('/record/', methods=['GET', 'POST', 'PUT']) def record(): """ POST/PUT: Start or stop a video recording. GET: Return recording status, or download latest recording """ if request.method == 'POST' or request.method == 'PUT': state = request.get_json() print(state) # Handle filename argument if 'filename' in state: filename = state['filename'] else: filename = None # Handle status argument if 'status' in state: status = bool(state['status']) # synchronise the arduino_time if status is True: response = cam.start_recording(filename=filename) return str(response) elif status is False: response = cam.stop_recording() return str(response) else: download = request.args.get('download') state = cam.state if (( download == 'true' or download == 'True' or download == '1') and cam.state['recent_video'] is not None): return send_file( cam.state['recent_video'], mimetype='video/H264', as_attachment=True) else: return jsonify(state) @app.route('/preview/', methods=['GET', 'POST', 'PUT']) def preview(): """ POST/PUT: Start or stop the direct-to-GPU camera preview on the Pi. GET: Return preview status """ if request.method == 'POST' or request.method == 'PUT': state = request.get_json() print(state) if 'status' in state: status = bool(state['status']) if status is False: response = cam.stop_preview() else: response = cam.start_preview() return str(response) else: # Get args passed via URL (not useful here. Just for reference.) state = cam.state return jsonify(state) if __name__ == '__main__': app.run(host='0.0.0.0', threaded=True, debug=True, use_reloader=False) openflexure_microscope/api/v1/__init__.py 0 → 100644 +0 −0 Empty file added. openflexure_microscope/api/v1/blueprints/__init__.py 0 → 100644 +1 −0 Original line number Diff line number Diff line from . import camera, stage, base Loading
openflexure_microscope/api/app.py +82 −151 Original line number Diff line number Diff line #!/usr/bin/env python """ TODO: Implement API route to cleanly shut down server TODO: Implement plugin API routes somehow """ from pprint import pprint import numpy as np from importlib import import_module import time import datetime import os from flask import ( Flask, render_template, Response, redirect, request, jsonify, send_file) import numpy as np from openflexure_microscope.camera.pi import StreamingCamera app = Flask(__name__) cam = StreamingCamera() def parse_payload(request): """Convert request to JSON. Will eventually handle error-checking.""" # TODO: Handle invalid JSON payloads state = request.get_json() return state Flask, render_template, Response, url_for, redirect, request, jsonify, send_file, abort, make_response) def gen(camera): """Video streaming generator function.""" while True: # the obtained frame is a jpeg frame = camera.get_frame() from flask.views import MethodView from werkzeug.exceptions import default_exceptions yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') from openflexure_microscope.api.utilities import parse_payload, get_from_payload, gen, get_bool, list_routes from openflexure_microscope import Microscope, config from openflexure_microscope.camera.pi import StreamingCamera from openflexure_stage import OpenFlexureStage @app.route('/') def index(): """Video streaming home page.""" cam.start_worker() # Start the stream return render_template('index.html') import atexit import logging, sys @app.route('/stream') def stream(): """Video streaming route. Put this in the src attribute of an img tag.""" return Response( gen(cam), mimetype='multipart/x-mixed-replace; boundary=frame') logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) # Create a dummy microscope object, with no hardware attachments api_microscope = Microscope(None, None) logging.debug("Created an empty microscope in global.") # TODO: Be able to change the image resolution with an option @app.route('/capture/', methods=['GET', 'POST', 'PUT']) def capture(): """ POST/PUT: Capture a single image. GET: Return capture status, or download latest capture. """ if request.method == 'POST' or request.method == 'PUT': state = parse_payload(request) print(state) # Handle filename argument if 'filename' in state: filename = state['filename'] else: filename = None # Generate API URI based on version from filename def uri(suffix, api_version, base=None): if not base: base = "/api/{}".format(api_version) uri = base + suffix logging.debug("Created app route: {}".format(uri)) return uri if 'write_to_file' in state: write_to_file = bool(state['write_to_file']) else: write_to_file = False # Create flask app app = Flask(__name__) app.url_map.strict_slashes = False if 'use_video_port' in state: use_video_port = bool(state['use_video_port']) else: use_video_port = False if 'resize' in state: resize_h = int(state['resize']) resize_w = int(resize_h*(4/3)) resize = (resize_w, resize_h) else: resize = None # Make errors more API friendly response = cam.capture( write_to_file=write_to_file, use_video_port=use_video_port, filename=filename, resize=resize) def _handle_http_exception(e): return make_response( jsonify({ 'status_code': e.code, 'error': e.name, 'details': e.description }), e.code) return str(response) + "\n" for code in default_exceptions: app.errorhandler(code)(_handle_http_exception) else: # If GET request download = request.args.get('download') # After app starts, but before first request, attach hardware to global microscope @app.before_first_request def attach_microscope(): # Create the microscope object globally (common to all spawned server threads) global api_microscope logging.debug("First request made. Populating microscope with hardware...") openflexurerc = config.load_config() # Load default user config state = cam.state api_microscope.attach( StreamingCamera(config=openflexurerc), OpenFlexureStage("/dev/ttyUSB0") ) if (( download == 'true' or download == 'True' or download == '1') and cam.image is not None): logging.debug("Microscope successfully attached!") return send_file(cam.image.data, mimetype='image/jpeg') else: return jsonify(state) ##### WEBAPP ROUTES ###### @app.route('/record/', methods=['GET', 'POST', 'PUT']) def record(): """ POST/PUT: Start or stop a video recording. GET: Return recording status, or download latest recording @app.route('/') def index(): """ if request.method == 'POST' or request.method == 'PUT': state = request.get_json() print(state) # Handle filename argument if 'filename' in state: filename = state['filename'] else: filename = None # Handle status argument if 'status' in state: status = bool(state['status']) # synchronise the arduino_time if status is True: response = cam.start_recording(filename=filename) return str(response) elif status is False: response = cam.stop_recording() return str(response) else: download = request.args.get('download') state = cam.state if (( download == 'true' or download == 'True' or download == '1') and cam.state['recent_video'] is not None): return send_file( cam.state['recent_video'], mimetype='video/H264', as_attachment=True) else: return jsonify(state) @app.route('/preview/', methods=['GET', 'POST', 'PUT']) def preview(): API demo app """ POST/PUT: Start or stop the direct-to-GPU camera preview on the Pi. return render_template( 'index_v1.html' ) GET: Return preview status """ if request.method == 'POST' or request.method == 'PUT': state = request.get_json() print(state) ##### API ROUTES ###### from openflexure_microscope.api.v1 import blueprints # Base routes base_blueprint = blueprints.base.construct_blueprint(api_microscope) app.register_blueprint(base_blueprint, url_prefix=uri('', 'v1')) if 'status' in state: status = bool(state['status']) # Stage routes stage_blueprint = blueprints.stage.construct_blueprint(api_microscope) app.register_blueprint(stage_blueprint, url_prefix=uri('/stage', 'v1')) if status is False: response = cam.stop_preview() else: response = cam.start_preview() # Camera routes camera_blueprint = blueprints.camera.construct_blueprint(api_microscope) app.register_blueprint(camera_blueprint, url_prefix=uri('/camera', 'v1')) return str(response) # List all routes list_routes(app) else: # Get args passed via URL (not useful here. Just for reference.) state = cam.state return jsonify(state) # Automatically clean up microscope at exit def cleanup(): global api_microscope api_microscope.close() atexit.register(cleanup) if __name__ == '__main__': app.run(host='0.0.0.0', threaded=True, debug=True, use_reloader=False) if __name__ == "__main__": app.run(host='0.0.0.0', port="5000", threaded=True, debug=True, use_reloader=False)
openflexure_microscope/api/utilities.py +8 −1 Original line number Diff line number Diff line import pprint def parse_payload(request): """Convert request to JSON. Will eventually handle error-checking.""" # TODO: Handle invalid JSON payloads Loading Loading @@ -32,3 +34,8 @@ def get_bool(get_arg): return True else: return False def list_routes(app): """Print available functions.""" pprint.pprint(list(map(lambda x: repr(x), app.url_map.iter_rules()))) No newline at end of file
openflexure_microscope/api/v0.pydeleted 100644 → 0 +0 −172 Original line number Diff line number Diff line #!/usr/bin/env python from pprint import pprint from importlib import import_module import time import datetime from flask import ( Flask, render_template, Response, redirect, request, jsonify, send_file) import numpy as np from openflexure_microscope.api.utilities import parse_payload, gen from openflexure_microscope.camera.pi import StreamingCamera app = Flask(__name__) cam = StreamingCamera() @app.route('/') def index(): """Video streaming home page.""" cam.start_worker() # Start the stream return render_template('index_v0.html') @app.route('/stream') def stream(): """Video streaming route. Put this in the src attribute of an img tag.""" return Response( gen(cam), mimetype='multipart/x-mixed-replace; boundary=frame') # TODO: Be able to change the image resolution with an option @app.route('/capture/', methods=['GET', 'POST', 'PUT']) def capture(): """ POST/PUT: Capture a single image. GET: Return capture status, or download latest capture. """ if request.method == 'POST' or request.method == 'PUT': state = parse_payload(request) print(state) # Handle filename argument if 'filename' in state: filename = state['filename'] else: filename = None if 'write_to_file' in state: write_to_file = bool(state['write_to_file']) else: write_to_file = False if 'use_video_port' in state: use_video_port = bool(state['use_video_port']) else: use_video_port = False if 'resize' in state: resize_h = int(state['resize']) resize_w = int(resize_h*(4/3)) resize = (resize_w, resize_h) else: resize = None response = cam.capture( write_to_file=write_to_file, use_video_port=use_video_port, filename=filename, resize=resize) return str(response) + "\n" else: # If GET request download = request.args.get('download') state = cam.state if (( download == 'true' or download == 'True' or download == '1') and cam.image is not None): return send_file(cam.image.data, mimetype='image/jpeg') else: return jsonify(state) @app.route('/record/', methods=['GET', 'POST', 'PUT']) def record(): """ POST/PUT: Start or stop a video recording. GET: Return recording status, or download latest recording """ if request.method == 'POST' or request.method == 'PUT': state = request.get_json() print(state) # Handle filename argument if 'filename' in state: filename = state['filename'] else: filename = None # Handle status argument if 'status' in state: status = bool(state['status']) # synchronise the arduino_time if status is True: response = cam.start_recording(filename=filename) return str(response) elif status is False: response = cam.stop_recording() return str(response) else: download = request.args.get('download') state = cam.state if (( download == 'true' or download == 'True' or download == '1') and cam.state['recent_video'] is not None): return send_file( cam.state['recent_video'], mimetype='video/H264', as_attachment=True) else: return jsonify(state) @app.route('/preview/', methods=['GET', 'POST', 'PUT']) def preview(): """ POST/PUT: Start or stop the direct-to-GPU camera preview on the Pi. GET: Return preview status """ if request.method == 'POST' or request.method == 'PUT': state = request.get_json() print(state) if 'status' in state: status = bool(state['status']) if status is False: response = cam.stop_preview() else: response = cam.start_preview() return str(response) else: # Get args passed via URL (not useful here. Just for reference.) state = cam.state return jsonify(state) if __name__ == '__main__': app.run(host='0.0.0.0', threaded=True, debug=True, use_reloader=False)
openflexure_microscope/api/v1/blueprints/__init__.py 0 → 100644 +1 −0 Original line number Diff line number Diff line from . import camera, stage, base