Minor style updates

This commit is contained in:
Joel Collins 2018-11-06 15:57:33 +00:00
parent 79974a71e5
commit 8031327a5b
5 changed files with 124 additions and 86 deletions

View file

@ -1,25 +1,29 @@
#!/usr/bin/env python #!/usr/bin/env python
#TODO: Completely rewrite for new version of StreamingCamera
from pprint import pprint from pprint import pprint
from importlib import import_module from importlib import import_module
import time, datetime import time
import datetime
from flask import (
Flask, render_template, Response,
redirect, request, jsonify, send_file)
from flask import Flask, render_template, Response, redirect, request, jsonify, send_file
#import yaml
import numpy as np import numpy as np
# Raspberry Pi camera module (requires picamera package)
#from camera.pi import Camera
from openflexure_microscope.camera.pi import StreamingCamera from openflexure_microscope.camera.pi import StreamingCamera
app = Flask(__name__) app = Flask(__name__)
cam = StreamingCamera() cam = StreamingCamera()
def parse_payload(request): def parse_payload(request):
# TODO: Try-except for invalid JSON payloads """Convert request to JSON. Will eventually handle error-checking."""
# TODO: Handle invalid JSON payloads
state = request.get_json() state = request.get_json()
return state return state
def gen(camera): def gen(camera):
"""Video streaming generator function.""" """Video streaming generator function."""
while True: while True:
@ -29,31 +33,29 @@ 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')
@app.route('/') @app.route('/')
def index(): def index():
''' """Video streaming home page."""
Video streaming home page.
'''
cam.start_worker() # Start the stream cam.start_worker() # Start the stream
return render_template('index.html') return render_template('index.html')
@app.route('/stream') @app.route('/stream')
def stream(): 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. return Response(
''' gen(cam),
return Response(gen(cam), mimetype='multipart/x-mixed-replace; boundary=frame') mimetype='multipart/x-mixed-replace; boundary=frame')
# TODO: Be able to change the image resolution with an option # TODO: Be able to change the image resolution with an option
@app.route('/capture/', methods=['GET', 'POST', 'PUT']) @app.route('/capture/', methods=['GET', 'POST', 'PUT'])
def capture(): def capture():
''' """
POST/PUT: Capture a single image POST/PUT: Capture a single image.
GET: Return capture status, or download latest capture GET: Return capture status, or download latest capture.
"""
Eg. curl http://192.168.1.140:5000/capture/ -H "Content-Type: application/json" -d '{"store":true}' -X POST
'''
if request.method == 'POST' or request.method == 'PUT': if request.method == 'POST' or request.method == 'PUT':
state = parse_payload(request) state = parse_payload(request)
print(state) print(state)
@ -95,21 +97,23 @@ def capture():
state = cam.state state = cam.state
if (download == 'true' or download == 'True' or download =='1') and cam.image is not None: if ((
download == 'true' or
download == 'True' or
download == '1') and
cam.image is not None):
return send_file(cam.image.data, mimetype='image/jpeg') return send_file(cam.image.data, mimetype='image/jpeg')
else: else:
return jsonify(state) return jsonify(state)
@app.route('/record/', methods=['GET', 'POST', 'PUT']) @app.route('/record/', methods=['GET', 'POST', 'PUT'])
def record(): def record():
''' """
POST/PUT: Start or stop a video recording POST/PUT: Start or stop a video recording.
GET: Return recording status, or download latest recording GET: Return recording status, or download latest recording
"""
Eg.
curl http://192.168.1.140:5000/record/ -H "Content-Type: application/json" -d '{"status":true}' -X POST
curl http://192.168.1.140:5000/record/ -H "Content-Type: application/json" -d '{"status":false}' -X POST
'''
if request.method == 'POST' or request.method == 'PUT': if request.method == 'POST' or request.method == 'PUT':
state = request.get_json() state = request.get_json()
print(state) print(state)
@ -125,11 +129,11 @@ def record():
status = bool(state['status']) status = bool(state['status'])
# synchronise the arduino_time # synchronise the arduino_time
if status == True: if status is True:
response = cam.start_recording(filename=filename) response = cam.start_recording(filename=filename)
return str(response) return str(response)
elif status == False: elif status is False:
response = cam.stop_recording() response = cam.stop_recording()
return str(response) return str(response)
@ -138,19 +142,27 @@ def record():
state = cam.state state = cam.state
if (download == 'true' or download == 'True' or download =='1') and cam.state_record['recent'] is not None: if ((
return send_file(cam.state_record['recent'], 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', mimetype='video/H264',
as_attachment=True) as_attachment=True)
else: else:
return jsonify(state) return jsonify(state)
@app.route('/preview/', methods=['GET', 'POST', 'PUT']) @app.route('/preview/', methods=['GET', 'POST', 'PUT'])
def preview(): def preview():
''' """
POST/PUT: Start or stop the direct-to-GPU camera preview on the Pi POST/PUT: Start or stop the direct-to-GPU camera preview on the Pi.
GET: Return preview status GET: Return preview status
''' """
if request.method == 'POST' or request.method == 'PUT': if request.method == 'POST' or request.method == 'PUT':
state = request.get_json() state = request.get_json()
print(state) print(state)
@ -158,7 +170,7 @@ def preview():
if 'status' in state: if 'status' in state:
status = bool(state['status']) status = bool(state['status'])
if status == False: if status is False:
response = cam.stop_preview() response = cam.stop_preview()
else: else:
response = cam.start_preview() response = cam.start_preview()

View file

@ -17,6 +17,7 @@ except ImportError:
# Slightly crappy debugger logging # Slightly crappy debugger logging
DEBUG = True DEBUG = True
def log(s): def log(s):
if DEBUG: if DEBUG:
print('DEBUG: {}'.format(s)) print('DEBUG: {}'.format(s))
@ -111,6 +112,11 @@ class StreamObject(object):
f.seek(0, 0) # Seek to the start of the file f.seek(0, 0) # Seek to the start of the file
f.write(self.binary) # Write data bytes to file f.write(self.binary) # Write data bytes to file
def clear_stream(self):
"""Clears the BytesIO stream of the StreamObject."""
self.stream = io.BytesIO()
return True
def delete(self) -> bool: def delete(self) -> bool:
""" """
If the StreamObject has been saved to disk, deletes the file and returns True. If the StreamObject has been saved to disk, deletes the file and returns True.

View file

@ -16,8 +16,9 @@ TYPES = {
'digital_gain': float, 'digital_gain': float,
} }
def convert_config(config): def convert_config(config):
"""Convert datatype of config based on type dictionary""" """Convert datatype of config based on type dictionary."""
global TYPES global TYPES
for key in config: for key in config:
@ -26,12 +27,8 @@ def convert_config(config):
return config return config
def load_config(yaml_path): def load_config(yaml_path):
"""Load YAML file, pass through dictionary conversion, and return."""
with open(yaml_path) as config_file: with open(yaml_path) as config_file:
return convert_config(yaml.load(config_file)) return convert_config(yaml.load(config_file))
if __name__ == "__main__":
from pprint import pprint
config = load_config('config_picamera.yaml')
pprint(config)

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Raspberry Pi camera implementation of the StreamingCamera class. Raspberry Pi camera implementation of the StreamingCamera class.
@ -51,8 +52,9 @@ from .config import load_config
HERE = os.path.abspath(os.path.dirname(__file__)) HERE = os.path.abspath(os.path.dirname(__file__))
DEFAULT_CONFIG = os.path.join(HERE, 'config_picamera.yaml') DEFAULT_CONFIG = os.path.join(HERE, 'config_picamera.yaml')
class StreamingCamera(BaseCamera):
class StreamingCamera(BaseCamera):
"""Raspberry Pi camera implementation of StreamingCamera."""
def __init__(self): def __init__(self):
# Capture data # Capture data
self.image = None self.image = None
@ -79,10 +81,11 @@ class StreamingCamera(BaseCamera):
BaseCamera.__init__(self) BaseCamera.__init__(self)
def initialisation(self): def initialisation(self):
"""Run any initialisation code when the frame iterator starts."""
pass pass
def start_preview(self) -> bool: def start_preview(self) -> bool:
"""Function to start the onboard GPU camera preview.""" """Start the onboard GPU camera preview."""
self.start_worker() self.start_worker()
self.camera.start_preview() self.camera.start_preview()
@ -90,7 +93,7 @@ class StreamingCamera(BaseCamera):
return True return True
def stop_preview(self) -> bool: def stop_preview(self) -> bool:
"""Function to start the onboard GPU camera preview.""" """Stop the onboard GPU camera preview."""
self.start_worker() self.start_worker()
self.camera.stop_preview() self.camera.stop_preview()
@ -98,11 +101,12 @@ class StreamingCamera(BaseCamera):
return True return True
# TODO: Handle exceptions # TODO: Handle exceptions
# TODO: Have this take a dictionary (not a config file)
# TODO: Web API entry point to send settings as JSON to this method
# TODO: Separate method to store current settings back to YAML file
# TODO: API entry point to get settings to JSON
def update_settings(self, config_path: str=None) -> None: def update_settings(self, config_path: str=None) -> None:
""" """Open config_picamera.yaml file and write to camera."""
Opens config_picamera.yaml file and writes valid settings
to the camera hardware object
"""
global DEFAULT_CONFIG global DEFAULT_CONFIG
self.start_worker() self.start_worker()
@ -203,27 +207,40 @@ class StreamingCamera(BaseCamera):
quality: int=15): quality: int=15):
"""Start a new video recording, writing to a target object. """Start a new video recording, writing to a target object.
target (str/BytesIO): Target object to write bytes to (default StreamObject) target (str/BytesIO): Target object to write bytes to.
write_to_file (bool/NoneType): Should the StreamObject write to a file? (default True for video capture) (default StreamObject)
filename (str): Name of the stored file (defaults to timestamp) write_to_file (bool/NoneType): Should the StreamObject write to a file?
(default True for video capture)
filename (str): Name of the stored file.
(defaults to timestamp)
folder (str): Relative directory to store data file in. folder (str): Relative directory to store data file in.
fmt (str): Format of the capture (default 'h264') fmt (str): Format of the capture.
(default 'h264')
""" """
self.start_worker() self.start_worker()
# If no target is specified, store to StreamingCamera # If no target is specified, store to StreamingCamera
if not target: if not target:
if (self.video is None) or (not self.video.locked): # If target is not locked # If target is not locked
if (self.video is None) or (not self.video.locked):
self.video = StreamObject(write_to_file=write_to_file, filename=filename, folder=folder, fmt=fmt) # Reset/create StreamObject self.video = StreamObject(
write_to_file=write_to_file,
filename=filename,
folder=folder,
fmt=fmt) # Reset/create StreamObject
target = self.video.target # Store to the StreamObject BytesIO # Store to the StreamObject BytesIO
target_obj = self.video # Note the target StreamObject, used for function return target = self.video.target
# Note the target StreamObject, used for function return
target_obj = self.video
target_obj.lock() # Lock the StreamObject while recording target_obj.lock() # Lock the StreamObject while recording
else: else:
print("Cannot start recording a new video until the current recording has been stopped. Returning None.") print("Cannot start recording a new video \
until the current recording has been stopped. \
Returning None.")
return None return None
else: else:
target_obj = target target_obj = target
@ -247,7 +264,8 @@ class StreamingCamera(BaseCamera):
Pause capture on a splitter port. Pause capture on a splitter port.
splitter_port (int): Splitter port to stop recording on splitter_port (int): Splitter port to stop recording on
resolution ((int, int)): Resolution to set the camera to, after stopping recording. resolution ((int, int)): Resolution to set the camera to,
after stopping recording.
""" """
log("Pausing stream") log("Pausing stream")
# If no resolution is specified, default to image_resolution # If no resolution is specified, default to image_resolution
@ -262,10 +280,11 @@ class StreamingCamera(BaseCamera):
def resume_stream_for_capture(self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None: def resume_stream_for_capture(self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None:
""" """
Resume capture on a splitter port Resume capture on a splitter port.
splitter_port (int): Splitter port to start recording on splitter_port (int): Splitter port to start recording on
resolution ((int, int)): Resolution to set the camera to, before starting recording. resolution ((int, int)): Resolution to set the camera to,
before starting recording.
""" """
log("Unpausing stream") log("Unpausing stream")
if not resolution: if not resolution:
@ -291,17 +310,21 @@ class StreamingCamera(BaseCamera):
fmt: str='jpeg', fmt: str='jpeg',
resize: Tuple[int, int]=None): resize: Tuple[int, int]=None):
""" """
Captures a still image to a StreamObject. Capture a still image to a StreamObject.
Defaults to JPEG format. Defaults to JPEG format.
Target object can be overridden for development purposes. Target object can be overridden for development purposes.
target (str/BytesIO): Target object to write data bytes to target (str/BytesIO): Target object to write data bytes to.
write_to_file (bool): Should the StreamObject write to a file, instead of BytesIO stream? write_to_file (bool): Should the StreamObject write to a file,
use_video_port (bool): Capture from the video port used for streaming (lower resolution, faster) instead of BytesIO stream?
filename (str): Name of the stored file (defaults to timestamp) use_video_port (bool): Capture from the video port used for streaming.
(lower resolution, faster)
filename (str): Name of the stored file.
(defaults to timestamp)
folder (str): Relative directory to store data file in. folder (str): Relative directory to store data file in.
fmt (str): Format of the capture (default 'h264') fmt (str): Format of the capture.
(default 'h264')
resize ((int, int)): Resize the captured image. resize ((int, int)): Resize the captured image.
""" """
self.start_worker() self.start_worker()
@ -348,7 +371,7 @@ class StreamingCamera(BaseCamera):
return target_obj return target_obj
def array(self, rgb=False, use_video_port: bool=True, resize: Tuple[int, int]=None) -> np.ndarray: def array(self, rgb=False, use_video_port: bool=True, resize: Tuple[int, int]=None) -> np.ndarray:
"""Captures an uncompressed still YUV image to a Numpy array. """Capture an uncompressed still YUV image to a Numpy array.
use_video_port (bool): Capture from the video port used for streaming (lower resolution, faster) use_video_port (bool): Capture from the video port used for streaming (lower resolution, faster)
resize ((int, int)): Resize the captured image. resize ((int, int)): Resize the captured image.