diff --git a/openflexure_microscope/api/__init__.py b/openflexure_microscope/api/__init__.py new file mode 100644 index 00000000..08fef38a --- /dev/null +++ b/openflexure_microscope/api/__init__.py @@ -0,0 +1 @@ +from . import utilities diff --git a/openflexure_microscope/api/templates/index.html b/openflexure_microscope/api/templates/index_v0.html similarity index 100% rename from openflexure_microscope/api/templates/index.html rename to openflexure_microscope/api/templates/index_v0.html diff --git a/openflexure_microscope/api/templates/index_v1.html b/openflexure_microscope/api/templates/index_v1.html new file mode 100644 index 00000000..d1f67f86 --- /dev/null +++ b/openflexure_microscope/api/templates/index_v1.html @@ -0,0 +1,9 @@ + + + Video Streaming Demonstration + + +

Video Streaming Demonstration

+ + + diff --git a/openflexure_microscope/api/utilities.py b/openflexure_microscope/api/utilities.py new file mode 100644 index 00000000..40dd6c69 --- /dev/null +++ b/openflexure_microscope/api/utilities.py @@ -0,0 +1,15 @@ +def parse_payload(request): + """Convert request to JSON. Will eventually handle error-checking.""" + # TODO: Handle invalid JSON payloads + state = request.get_json() + return state + + +def gen(camera): + """Video streaming generator function.""" + while True: + # the obtained frame is a jpeg + frame = camera.get_frame() + + yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/v0.py similarity index 89% rename from openflexure_microscope/api/app.py rename to openflexure_microscope/api/v0.py index 4a109823..5987a9c0 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/v0.py @@ -11,34 +11,19 @@ from flask import ( 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() -def parse_payload(request): - """Convert request to JSON. Will eventually handle error-checking.""" - # TODO: Handle invalid JSON payloads - state = request.get_json() - return state - - -def gen(camera): - """Video streaming generator function.""" - while True: - # the obtained frame is a jpeg - frame = camera.get_frame() - - yield (b'--frame\r\n' - b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') - @app.route('/') def index(): """Video streaming home page.""" cam.start_worker() # Start the stream - return render_template('index.html') + return render_template('index_v0.html') @app.route('/stream') diff --git a/openflexure_microscope/api/v1.py b/openflexure_microscope/api/v1.py new file mode 100644 index 00000000..1b4ae096 --- /dev/null +++ b/openflexure_microscope/api/v1.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +""" +TODO: Implement stage control methods +TODO: Reimplement capture methods +TODO: Implement API route to cleanly shut down server +TODO: Implement microscope function API routes (autofocus etc) +""" + +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 import Microscope +from openflexure_microscope.camera.pi import StreamingCamera +from openflexure_stage import OpenFlexureStage + +import logging, sys +logging.basicConfig(stream=sys.stderr, level=logging.INFO) + + +# Create the microscope object globally (common to all spawned server threads) +microscope = Microscope( + StreamingCamera(), + OpenFlexureStage("/dev/ttyUSB0") +) + +# Create flask app +app = Flask(__name__) + +# Some useful functions +def uri(suffix, base='/api/v1'): + return base + suffix + +# Define front-end routes + +@app.route('/') +def index(): + """Video streaming home page.""" + return render_template( + 'index_v1.html' + ) + +# Define API routes + +# Basic routes + +@app.route(uri('/stream')) +def stream(): + """Video streaming route. Put this in the src attribute of an img tag.""" + global microscope + + return Response( + gen(microscope.camera), + mimetype='multipart/x-mixed-replace; boundary=frame') + +@app.route(uri('/state')) +def state(): + """Return JSONified microscope state""" + global microscope + + return jsonify(microscope.state) + +# Positioning routes + +@app.route(uri('/position'), methods=['GET', 'POST', 'PUT']) +def position(): + """Return JSONified microscope state""" + global microscope + + if request.method == 'POST' or request.method == 'PUT': + logging.debug("TODO: Move stage.") + + return jsonify(microscope.state['position']) + +if __name__ == '__main__': + app.run(host='0.0.0.0', threaded=True, debug=True, use_reloader=False) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 7762a821..cb9e7418 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -24,3 +24,22 @@ class Microscope(object): """Shut down the microscope hardware.""" self.camera.close() self.stage.close() + + # Create unified state + @property + def state(self): + state = {} + + # Add stage position + position = self.stage.position + state['position'] = { + 'absolute': True, + 'x': position[0], + 'y': position[1], + 'z': position[2], + } + + # Add camera state + state.update(self.camera.state) + + return state diff --git a/start_interface b/start_interface index b4ccb02b..13b267f7 100644 --- a/start_interface +++ b/start_interface @@ -1 +1 @@ -gunicorn --threads 5 --workers 1 --bind 0.0.0.0:5000 openflexure_microscope.api.app:app \ No newline at end of file +gunicorn --threads 5 --workers 1 --bind 0.0.0.0:5000 openflexure_microscope.api.v1:app \ No newline at end of file