From 4e67d24841425a4b3bd342311b5eaa551a1038dc Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 12 Nov 2018 12:22:06 +0000 Subject: [PATCH] Added capture state endpoint --- openflexure_microscope/api/static/main_v1.js | 71 +++++++++++++------ .../api/static/style_v1.css | 9 +++ .../api/templates/index_v1.html | 6 +- openflexure_microscope/api/utilities.py | 11 +++ openflexure_microscope/api/v1.py | 60 +++++++++++++++- openflexure_microscope/camera/capture.py | 26 ++++--- openflexure_microscope/camera/pi.py | 2 + 7 files changed, 151 insertions(+), 34 deletions(-) diff --git a/openflexure_microscope/api/static/main_v1.js b/openflexure_microscope/api/static/main_v1.js index 919bf6a8..d555db01 100644 --- a/openflexure_microscope/api/static/main_v1.js +++ b/openflexure_microscope/api/static/main_v1.js @@ -30,6 +30,19 @@ var fovY = 3146; window.onload = function() { getStagePositions() updateTextBoxes() + getCaptures() +} + +function getCaptures() { + function updateCapturesCallback(response, status) { + console.log(status); + updateCaptures(response); + } + safeRequest("GET", baseURI+"/capture", null, updateCapturesCallback, false) +} + +function updateCaptures(response) { + console.log(response) } function updateTextBoxes() { @@ -50,20 +63,20 @@ function updateStagePositions(response) { function getStagePositions() { function updatePositionCallback(response, status) { - console.log(status, response); - updateStagePositions(response); + console.log(status, response); + updateStagePositions(response); } - safeRequest("GET", baseURI+"/position", null, updatePositionCallback) + safeRequest("GET", baseURI+"/position", null, updatePositionCallback, false) } function setStagePositions(x_abs, y_abs, z_abs) { // Make a position request function absMoveCallback(response, status) { - if (status == 400) { - alert("Stage cannot be moved further than the safe range."); - } - console.log(status, response); - updateStagePositions(response); + if (status == 400) { + alert("Stage cannot be moved further than the safe range."); + } + console.log(status, response); + updateStagePositions(response); } safeRequest("POST", baseURI+"/position", { "absolute": true, "x": x_abs, "y": y_abs, "z": z_abs}, absMoveCallback) } @@ -71,11 +84,11 @@ function setStagePositions(x_abs, y_abs, z_abs) { function moveStagePositions(x_rel, y_rel, z_rel) { // Make a position request function relMoveCallback(response, status) { - if (status == 400) { - alert("Stage cannot be moved further than the safe range."); - } - console.log(status, response); - updateStagePositions(response); + if (status == 400) { + alert("Stage cannot be moved further than the safe range."); + } + console.log(status, response); + updateStagePositions(response); } safeRequest("POST", baseURI+"/position", { "absolute": false, "x": x_rel, "y": y_rel, "z": z_rel}, relMoveCallback) } @@ -169,20 +182,32 @@ addEventListener("keyup", function (e) { }, false); // Methods for making requests -function safeRequest(method, url, payload, callback) { - if (requestLock == false) { +function safeRequest(method, url, payload, callback, lock=true, force=false) { + if (force == true) { + allowRequest = true; + } + else { + allowRequest = !(requestLock); + } + if (allowRequest == true) { var xhr = new XMLHttpRequest(); // new HttpRequest instance xhr.open(method, url); - requestLock = true; + + if (lock == true) { + requestLock = true; + } xhr.onload = function (e) { - if (xhr.readyState === 4) { - if (xhr.status !== 200) { - console.error(xhr.statusText); - } - callback(JSON.parse(xhr.responseText), xhr.status); - } - requestLock = false; + if (xhr.readyState === 4) { + if (xhr.status !== 200) { + console.error(xhr.statusText); + } + callback(JSON.parse(xhr.responseText), xhr.status); + } + + if (lock == true) { + requestLock = false; + } }; if (method == "POST" || method == "PUT") { diff --git a/openflexure_microscope/api/static/style_v1.css b/openflexure_microscope/api/static/style_v1.css index d486979b..bc085f34 100644 --- a/openflexure_microscope/api/static/style_v1.css +++ b/openflexure_microscope/api/static/style_v1.css @@ -64,3 +64,12 @@ img { display: table; } +/* Data divs */ + +#captures { + font-size:12px; +} + +.capture { + background-color: #d3d3d3; +} \ No newline at end of file diff --git a/openflexure_microscope/api/templates/index_v1.html b/openflexure_microscope/api/templates/index_v1.html index 9f75dd62..46ff4972 100644 --- a/openflexure_microscope/api/templates/index_v1.html +++ b/openflexure_microscope/api/templates/index_v1.html @@ -73,7 +73,11 @@

Captures

-

Not yet implemented.

+
+

Not yet implemented.

+
Capture 1
+
+
diff --git a/openflexure_microscope/api/utilities.py b/openflexure_microscope/api/utilities.py index 40dd6c69..c559aba3 100644 --- a/openflexure_microscope/api/utilities.py +++ b/openflexure_microscope/api/utilities.py @@ -13,3 +13,14 @@ def gen(camera): yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') + + +def get_bool(get_arg): + """Convert GET request argument string to a Python bool""" + if ( + get_arg == 'true' or + get_arg == 'True' or + get_arg == '1'): + return True + else: + return False \ No newline at end of file diff --git a/openflexure_microscope/api/v1.py b/openflexure_microscope/api/v1.py index 0f5a81e2..a4c07276 100644 --- a/openflexure_microscope/api/v1.py +++ b/openflexure_microscope/api/v1.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """ +TODO: Add proper docstrings TODO: Bind to port 80 TODO: Reimplement capture methods TODO: Implement API route to cleanly shut down server @@ -18,7 +19,7 @@ from flask import ( import numpy as np -from openflexure_microscope.api.utilities import parse_payload, gen +from openflexure_microscope.api.utilities import parse_payload, gen, get_bool from openflexure_microscope import Microscope from openflexure_microscope.camera.pi import StreamingCamera @@ -59,6 +60,9 @@ def stream(): """Video streaming route. Put this in the src attribute of an img tag.""" global microscope + # Restart stream worker thread + microscope.camera.start_worker() + return Response( gen(microscope.camera), mimetype='multipart/x-mixed-replace; boundary=frame') @@ -115,5 +119,59 @@ def position(): return jsonify(microscope.state['position']) +# Capture routes +@app.route(uri('/capture'), methods=['GET', 'POST', 'PUT']) +def capture(): + """Return JSONified microscope state""" + global microscope + + if request.method == 'POST' or request.method == 'PUT': + state = parse_payload(request) + print(state) + + if 'filename' in state: + filename = state['filename'] + else: + filename = None + + if 'keep_on_disk' in state: + keep_on_disk = bool(state['keep_on_disk']) + else: + keep_on_disk = True + + if 'use_video_port' in state: + use_video_port = bool(state['use_video_port']) + else: + use_video_port = False + + if 'size' in state: + if 'width' in state['size'] and 'height' in state['size']: + resize = (state['size']['width'], state['size']['height']) + else: + raise Exception("Invalid resize parameters passed. Ensure both width and height are specified.") + else: + resize = None + + capture_obj = microscope.camera.capture( + write_to_file=True, # Always write data to disk when using API + keep_on_disk=keep_on_disk, + use_video_port=use_video_port, + filename=filename, + resize=resize) + + return jsonify(capture_obj.metadata) + + else: # GET requests + include_unavailable = get_bool(request.args.get('include_unavailable')) + + if include_unavailable: + captures = [image for image in microscope.camera.images if image.metadata['available']] + else: + captures = microscope.camera.images + + metadata_array = [capture.metadata for capture in captures] + + return jsonify(metadata_array) + if __name__ == '__main__': app.run(host='0.0.0.0', threaded=True, debug=True, use_reloader=False) diff --git a/openflexure_microscope/camera/capture.py b/openflexure_microscope/camera/capture.py index d2d0d698..5770da31 100644 --- a/openflexure_microscope/camera/capture.py +++ b/openflexure_microscope/camera/capture.py @@ -10,6 +10,7 @@ class StreamObject(object): def __init__( self, write_to_file: bool=None, + keep_on_disk: bool=True, filename: str=None, folder: str=None, fmt: str='file') -> None: @@ -20,17 +21,18 @@ class StreamObject(object): # Create file name iterator = 0 - f_name = self.build_file_path(filename, folder, fmt) + f_path, f_name = self.build_file_path(filename, folder, fmt) while os.path.isfile(f_name): # While file already exists iterator += 1 # Add a file name iterator - f_name = self.build_file_path( - filename, - folder, - fmt, + f_path, f_name = self.build_file_path( + filename, + folder, + fmt, iterator=iterator) # Rebuild file name - self.file = f_name + self.file = f_path + self.filename = f_name # Byte stream properties self.stream = io.BytesIO() # Byte stream that data will be written to @@ -45,7 +47,7 @@ class StreamObject(object): self.target = self.file # Keep on disk after close by default - self.keep_on_disk = True + self.keep_on_disk = keep_on_disk # Log if created by context manager self.context_manager = False @@ -95,7 +97,7 @@ class StreamObject(object): else: file_path = file_name - return file_path + return (file_path, file_name) def lock(self): """Set locked flag to True.""" @@ -138,7 +140,7 @@ class StreamObject(object): # Get file path if self.file_exists: - d['file'] = self.file + d['file'] = self.filename else: d['file'] = None @@ -148,6 +150,12 @@ class StreamObject(object): else: d['stream'] = False + # Combined availability of data + if self.stream_exists or self.file_exists: + d['available'] = True + else: + d['available'] = False + # Check if file was manually deleted if self.keep_on_disk and not self.file_exists: d['path'] = "{} (Deleted)".format(d['path']) diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index b9b3279f..9e631251 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -318,6 +318,7 @@ class StreamingCamera(BaseCamera): self, target=None, write_to_file: bool=False, + keep_on_disk: bool=True, use_video_port: bool=False, filename: str=None, folder: str='capture', @@ -346,6 +347,7 @@ class StreamingCamera(BaseCamera): # Create a new image and add to the image list target_obj = self.new_image(StreamObject( write_to_file=write_to_file, + keep_on_disk=keep_on_disk, filename=filename, folder=folder, fmt=fmt))