Added capture state endpoint
This commit is contained in:
parent
52d667feec
commit
4e67d24841
7 changed files with 151 additions and 34 deletions
|
|
@ -30,6 +30,19 @@ var fovY = 3146;
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
getStagePositions()
|
getStagePositions()
|
||||||
updateTextBoxes()
|
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() {
|
function updateTextBoxes() {
|
||||||
|
|
@ -50,20 +63,20 @@ function updateStagePositions(response) {
|
||||||
|
|
||||||
function getStagePositions() {
|
function getStagePositions() {
|
||||||
function updatePositionCallback(response, status) {
|
function updatePositionCallback(response, status) {
|
||||||
console.log(status, response);
|
console.log(status, response);
|
||||||
updateStagePositions(response);
|
updateStagePositions(response);
|
||||||
}
|
}
|
||||||
safeRequest("GET", baseURI+"/position", null, updatePositionCallback)
|
safeRequest("GET", baseURI+"/position", null, updatePositionCallback, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStagePositions(x_abs, y_abs, z_abs) {
|
function setStagePositions(x_abs, y_abs, z_abs) {
|
||||||
// Make a position request
|
// Make a position request
|
||||||
function absMoveCallback(response, status) {
|
function absMoveCallback(response, status) {
|
||||||
if (status == 400) {
|
if (status == 400) {
|
||||||
alert("Stage cannot be moved further than the safe range.");
|
alert("Stage cannot be moved further than the safe range.");
|
||||||
}
|
}
|
||||||
console.log(status, response);
|
console.log(status, response);
|
||||||
updateStagePositions(response);
|
updateStagePositions(response);
|
||||||
}
|
}
|
||||||
safeRequest("POST", baseURI+"/position", { "absolute": true, "x": x_abs, "y": y_abs, "z": z_abs}, absMoveCallback)
|
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) {
|
function moveStagePositions(x_rel, y_rel, z_rel) {
|
||||||
// Make a position request
|
// Make a position request
|
||||||
function relMoveCallback(response, status) {
|
function relMoveCallback(response, status) {
|
||||||
if (status == 400) {
|
if (status == 400) {
|
||||||
alert("Stage cannot be moved further than the safe range.");
|
alert("Stage cannot be moved further than the safe range.");
|
||||||
}
|
}
|
||||||
console.log(status, response);
|
console.log(status, response);
|
||||||
updateStagePositions(response);
|
updateStagePositions(response);
|
||||||
}
|
}
|
||||||
safeRequest("POST", baseURI+"/position", { "absolute": false, "x": x_rel, "y": y_rel, "z": z_rel}, relMoveCallback)
|
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);
|
}, false);
|
||||||
|
|
||||||
// Methods for making requests
|
// Methods for making requests
|
||||||
function safeRequest(method, url, payload, callback) {
|
function safeRequest(method, url, payload, callback, lock=true, force=false) {
|
||||||
if (requestLock == false) {
|
if (force == true) {
|
||||||
|
allowRequest = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
allowRequest = !(requestLock);
|
||||||
|
}
|
||||||
|
if (allowRequest == true) {
|
||||||
var xhr = new XMLHttpRequest(); // new HttpRequest instance
|
var xhr = new XMLHttpRequest(); // new HttpRequest instance
|
||||||
xhr.open(method, url);
|
xhr.open(method, url);
|
||||||
requestLock = true;
|
|
||||||
|
if (lock == true) {
|
||||||
|
requestLock = true;
|
||||||
|
}
|
||||||
|
|
||||||
xhr.onload = function (e) {
|
xhr.onload = function (e) {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
if (xhr.status !== 200) {
|
if (xhr.status !== 200) {
|
||||||
console.error(xhr.statusText);
|
console.error(xhr.statusText);
|
||||||
}
|
}
|
||||||
callback(JSON.parse(xhr.responseText), xhr.status);
|
callback(JSON.parse(xhr.responseText), xhr.status);
|
||||||
}
|
}
|
||||||
requestLock = false;
|
|
||||||
|
if (lock == true) {
|
||||||
|
requestLock = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (method == "POST" || method == "PUT") {
|
if (method == "POST" || method == "PUT") {
|
||||||
|
|
|
||||||
|
|
@ -64,3 +64,12 @@ img {
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Data divs */
|
||||||
|
|
||||||
|
#captures {
|
||||||
|
font-size:12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capture {
|
||||||
|
background-color: #d3d3d3;
|
||||||
|
}
|
||||||
|
|
@ -73,7 +73,11 @@
|
||||||
|
|
||||||
<div class="column right">
|
<div class="column right">
|
||||||
<h2>Captures</h2>
|
<h2>Captures</h2>
|
||||||
<p>Not yet implemented.</p>
|
<div id="captures">
|
||||||
|
<p>Not yet implemented.</p>
|
||||||
|
<div class="capture">Capture 1</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column middle">
|
<div class="column middle">
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,14 @@ def gen(camera):
|
||||||
|
|
||||||
yield (b'--frame\r\n'
|
yield (b'--frame\r\n'
|
||||||
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\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
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
"""
|
"""
|
||||||
|
TODO: Add proper docstrings
|
||||||
TODO: Bind to port 80
|
TODO: Bind to port 80
|
||||||
TODO: Reimplement capture methods
|
TODO: Reimplement capture methods
|
||||||
TODO: Implement API route to cleanly shut down server
|
TODO: Implement API route to cleanly shut down server
|
||||||
|
|
@ -18,7 +19,7 @@ from flask import (
|
||||||
|
|
||||||
import numpy as np
|
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 import Microscope
|
||||||
from openflexure_microscope.camera.pi import StreamingCamera
|
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."""
|
"""Video streaming route. Put this in the src attribute of an img tag."""
|
||||||
global microscope
|
global microscope
|
||||||
|
|
||||||
|
# Restart stream worker thread
|
||||||
|
microscope.camera.start_worker()
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
gen(microscope.camera),
|
gen(microscope.camera),
|
||||||
mimetype='multipart/x-mixed-replace; boundary=frame')
|
mimetype='multipart/x-mixed-replace; boundary=frame')
|
||||||
|
|
@ -115,5 +119,59 @@ def position():
|
||||||
|
|
||||||
return jsonify(microscope.state['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__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', threaded=True, debug=True, use_reloader=False)
|
app.run(host='0.0.0.0', threaded=True, debug=True, use_reloader=False)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class StreamObject(object):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
write_to_file: bool=None,
|
write_to_file: bool=None,
|
||||||
|
keep_on_disk: bool=True,
|
||||||
filename: str=None,
|
filename: str=None,
|
||||||
folder: str=None,
|
folder: str=None,
|
||||||
fmt: str='file') -> None:
|
fmt: str='file') -> None:
|
||||||
|
|
@ -20,17 +21,18 @@ class StreamObject(object):
|
||||||
|
|
||||||
# Create file name
|
# Create file name
|
||||||
iterator = 0
|
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
|
while os.path.isfile(f_name): # While file already exists
|
||||||
iterator += 1 # Add a file name iterator
|
iterator += 1 # Add a file name iterator
|
||||||
f_name = self.build_file_path(
|
f_path, f_name = self.build_file_path(
|
||||||
filename,
|
filename,
|
||||||
folder,
|
folder,
|
||||||
fmt,
|
fmt,
|
||||||
iterator=iterator) # Rebuild file name
|
iterator=iterator) # Rebuild file name
|
||||||
|
|
||||||
self.file = f_name
|
self.file = f_path
|
||||||
|
self.filename = f_name
|
||||||
|
|
||||||
# Byte stream properties
|
# Byte stream properties
|
||||||
self.stream = io.BytesIO() # Byte stream that data will be written to
|
self.stream = io.BytesIO() # Byte stream that data will be written to
|
||||||
|
|
@ -45,7 +47,7 @@ class StreamObject(object):
|
||||||
self.target = self.file
|
self.target = self.file
|
||||||
|
|
||||||
# Keep on disk after close by default
|
# 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
|
# Log if created by context manager
|
||||||
self.context_manager = False
|
self.context_manager = False
|
||||||
|
|
@ -95,7 +97,7 @@ class StreamObject(object):
|
||||||
else:
|
else:
|
||||||
file_path = file_name
|
file_path = file_name
|
||||||
|
|
||||||
return file_path
|
return (file_path, file_name)
|
||||||
|
|
||||||
def lock(self):
|
def lock(self):
|
||||||
"""Set locked flag to True."""
|
"""Set locked flag to True."""
|
||||||
|
|
@ -138,7 +140,7 @@ class StreamObject(object):
|
||||||
|
|
||||||
# Get file path
|
# Get file path
|
||||||
if self.file_exists:
|
if self.file_exists:
|
||||||
d['file'] = self.file
|
d['file'] = self.filename
|
||||||
else:
|
else:
|
||||||
d['file'] = None
|
d['file'] = None
|
||||||
|
|
||||||
|
|
@ -148,6 +150,12 @@ class StreamObject(object):
|
||||||
else:
|
else:
|
||||||
d['stream'] = False
|
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
|
# Check if file was manually deleted
|
||||||
if self.keep_on_disk and not self.file_exists:
|
if self.keep_on_disk and not self.file_exists:
|
||||||
d['path'] = "{} (Deleted)".format(d['path'])
|
d['path'] = "{} (Deleted)".format(d['path'])
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,7 @@ class StreamingCamera(BaseCamera):
|
||||||
self,
|
self,
|
||||||
target=None,
|
target=None,
|
||||||
write_to_file: bool=False,
|
write_to_file: bool=False,
|
||||||
|
keep_on_disk: bool=True,
|
||||||
use_video_port: bool=False,
|
use_video_port: bool=False,
|
||||||
filename: str=None,
|
filename: str=None,
|
||||||
folder: str='capture',
|
folder: str='capture',
|
||||||
|
|
@ -346,6 +347,7 @@ class StreamingCamera(BaseCamera):
|
||||||
# Create a new image and add to the image list
|
# Create a new image and add to the image list
|
||||||
target_obj = self.new_image(StreamObject(
|
target_obj = self.new_image(StreamObject(
|
||||||
write_to_file=write_to_file,
|
write_to_file=write_to_file,
|
||||||
|
keep_on_disk=keep_on_disk,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
folder=folder,
|
folder=folder,
|
||||||
fmt=fmt))
|
fmt=fmt))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue