Commit 8031327a authored by Joel Collins's avatar Joel Collins
Browse files

Minor style updates

parent 79974a71
Loading
Loading
Loading
Loading
+57 −45
Original line number Diff line number Diff line
#!/usr/bin/env python
#TODO: Completely rewrite for new version of StreamingCamera

from pprint import pprint
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

# Raspberry Pi camera module (requires picamera package)
#from camera.pi import Camera
from openflexure_microscope.camera.pi import StreamingCamera

app = Flask(__name__)
cam = StreamingCamera()


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()
    return state


def gen(camera):
    """Video streaming generator function."""
    while True:
@@ -29,31 +33,29 @@ def gen(camera):
        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.
    '''
    """Video streaming home page."""
    cam.start_worker()  # Start the stream
    return render_template('index.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')
    """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

    Eg. curl http://192.168.1.140:5000/capture/ -H "Content-Type: application/json" -d '{"store":true}' -X POST
    '''
    """
    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)
@@ -95,21 +97,23 @@ def capture():

        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')
        else:
            return jsonify(state)


@app.route('/record/', methods=['GET', 'POST', 'PUT'])
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

    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':
        state = request.get_json()
        print(state)
@@ -125,11 +129,11 @@ def record():
            status = bool(state['status'])

        # synchronise the arduino_time
        if status == True:
        if status is True:
            response = cam.start_recording(filename=filename)
            return str(response)

        elif status == False:
        elif status is False:
            response = cam.stop_recording()
            return str(response)

@@ -138,19 +142,27 @@ def record():

        state = cam.state

        if (download == 'true' or download == 'True' or download =='1') and cam.state_record['recent'] is not None:
            return send_file(cam.state_record['recent'],
        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
    """
    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)
@@ -158,7 +170,7 @@ def preview():
        if 'status' in state:
            status = bool(state['status'])

        if status == False:
        if status is False:
            response = cam.stop_preview()
        else:
            response = cam.start_preview()
+9 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ except ImportError:
# Slightly crappy debugger logging
DEBUG = True


def log(s):
    if DEBUG:
        print('DEBUG: {}'.format(s))
@@ -111,6 +112,11 @@ class StreamObject(object):
            f.seek(0, 0)  # Seek to the start of the 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:
        """
        If the StreamObject has been saved to disk, deletes the file and returns True.
+5 −8
Original line number Diff line number Diff line
@@ -16,8 +16,9 @@ TYPES = {
    'digital_gain': float,
}


def convert_config(config):
    """Convert datatype of config based on type dictionary"""
    """Convert datatype of config based on type dictionary."""
    global TYPES

    for key in config:
@@ -26,12 +27,8 @@ def convert_config(config):

    return config


def load_config(yaml_path):
    """Load YAML file, pass through dictionary conversion, and return."""
    with open(yaml_path) as config_file:
        return convert_config(yaml.load(config_file))

if __name__ == "__main__":
    from pprint import pprint

    config = load_config('config_picamera.yaml')
    pprint(config)
 No newline at end of file
+52 −29
Original line number Diff line number Diff line
# -*- coding: utf-8 -*-

"""
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__))
DEFAULT_CONFIG = os.path.join(HERE, 'config_picamera.yaml')

class StreamingCamera(BaseCamera):

class StreamingCamera(BaseCamera):
    """Raspberry Pi camera implementation of StreamingCamera."""
    def __init__(self):
        # Capture data
        self.image = None
@@ -79,10 +81,11 @@ class StreamingCamera(BaseCamera):
        BaseCamera.__init__(self)

    def initialisation(self):
        """Run any initialisation code when the frame iterator starts."""
        pass

    def start_preview(self) -> bool:
        """Function to start the onboard GPU camera preview."""
        """Start the onboard GPU camera preview."""
        self.start_worker()

        self.camera.start_preview()
@@ -90,7 +93,7 @@ class StreamingCamera(BaseCamera):
        return True

    def stop_preview(self) -> bool:
        """Function to start the onboard GPU camera preview."""
        """Stop the onboard GPU camera preview."""
        self.start_worker()

        self.camera.stop_preview()
@@ -98,11 +101,12 @@ class StreamingCamera(BaseCamera):
        return True

    # 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:
        """
        Opens config_picamera.yaml file and writes valid settings
        to the camera hardware object
        """
        """Open config_picamera.yaml file and write to camera."""
        global DEFAULT_CONFIG

        self.start_worker()
@@ -203,27 +207,40 @@ class StreamingCamera(BaseCamera):
            quality: int=15):
        """Start a new video recording, writing to a target object.

        target (str/BytesIO): Target object to write bytes to (default StreamObject)
        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)
        target (str/BytesIO): Target object to write bytes to.
            (default StreamObject)
        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.
        fmt (str): Format of the capture (default 'h264')
        fmt (str): Format of the capture.
            (default 'h264')
        """
        self.start_worker()

        # If no target is specified, store to StreamingCamera
        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
                target_obj = self.video  # Note the target StreamObject, used for function return
                # Store to the StreamObject BytesIO
                target = self.video.target
                # Note the target StreamObject, used for function return
                target_obj = self.video

                target_obj.lock()  # Lock the StreamObject while recording

            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
        else:
            target_obj = target
@@ -247,7 +264,8 @@ class StreamingCamera(BaseCamera):
        Pause capture on a splitter port.

        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")
        # 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:
        """
        Resume capture on a splitter port
        Resume capture on a splitter port.

        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")
        if not resolution:
@@ -291,17 +310,21 @@ class StreamingCamera(BaseCamera):
            fmt: str='jpeg',
            resize: Tuple[int, int]=None):
        """
        Captures a still image to a StreamObject.
        Capture a still image to a StreamObject.

        Defaults to JPEG format.
        Target object can be overridden for development purposes.

        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?
        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)
        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?
        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.
        fmt (str): Format of the capture (default 'h264')
        fmt (str): Format of the capture.
            (default 'h264')
        resize ((int, int)): Resize the captured image.
        """
        self.start_worker()
@@ -348,7 +371,7 @@ class StreamingCamera(BaseCamera):
        return target_obj

    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)
        resize ((int, int)): Resize the captured image.
+1 −1

File changed.

Contains only whitespace changes.