Commit 5df9b53a authored by Joel Collins's avatar Joel Collins
Browse files

PEP-8-ified code a bit

parent 54f906f5
Loading
Loading
Loading
Loading
+35 −20
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ from .capture import StreamObject


def last_entry(object_list: list):
    """Return the last entry of a list, if the list contains items."""
    if object_list:  # If any images have been captured
        return object_list[-1]  # Return the latest captured image
    else:
@@ -23,6 +24,7 @@ def last_entry(object_list: list):


def entry_by_id(id: str, object_list: list):
    """Return an object from a list, if <object>.id matches id argument."""
    found = None
    for o in object_list:
        if o.id == id:
@@ -31,14 +33,17 @@ def entry_by_id(id: str, object_list: list):


class CameraEvent(object):
    """An Event-like class that signals all active clients when a new frame is
    available.
    """
    def __init__(self):
        """
        Create a frame-signaller object for StreamingCamera.

        An event-like class that signals all active clients
        when a new frame is available.
        """
        self.events = {}

    def wait(self, timeout: int=5):
        """Invoked from each client's thread to wait for the next frame."""
        """Wait for the next frame (invoked from each client's thread)."""
        ident = get_ident()
        if ident not in self.events:
            # this is a new client
@@ -48,7 +53,7 @@ class CameraEvent(object):
        return self.events[ident][0].wait(timeout)

    def set(self):
        """Invoked by the camera thread when a new frame is available."""
        """Signal that a new frame is available."""
        now = time.time()
        remove = None
        for ident, event in self.events.items():
@@ -68,7 +73,7 @@ class CameraEvent(object):
            del self.events[remove]

    def clear(self):
        """Invoked from each client's thread after a frame was processed."""
        """Clear frame event, once processed."""
        self.events[get_ident()][0].clear()


@@ -98,7 +103,7 @@ class BaseCamera(object):
        self.close()

    def close(self):
        """Close the BaseCamera and all attached StreamObjects"""
        """Close the BaseCamera and all attached StreamObjects."""
        # Close all StreamObjects
        for capture_list in [self.images, self.videos]:
            for stream_object in capture_list:
@@ -139,14 +144,14 @@ class BaseCamera(object):
        return True

    def stop_worker(self, timeout: int=5) -> bool:
        """Flags worker thread for stop. Waits for thread to close, or times out."""
        """Flag worker thread for stop. Waits for thread close or timeout."""
        logging.debug("Stopping worker thread")
        timeout_time = time.time() + timeout

        self.stop = True
        while self.thread:
            if time.time() > timeout_time:
                raise TimeoutError("Timeout waiting for worker thread to close.")
                raise TimeoutError("Timeout waiting for worker thread close.")
            else:
                time.sleep(0)
        return True
@@ -164,33 +169,37 @@ class BaseCamera(object):
        return self.frame

    def frames(self):
        """Generator that returns frames from the camera."""
        """Create generator that returns frames from the camera."""
        raise RuntimeError('Must be implemented by subclasses.')

    # RETURNING CAPTURES

    @property
    def image(self):
        """Return the latest captured image"""
        """Return the latest captured image."""
        return last_entry(self.images)

    @property
    def video(self):
        """Return the latest recorded video"""
        """Return the latest recorded video."""
        return last_entry(self.videos)

    def image_from_id(self, id):
        """Returns an image StreamObject with a matching ID"""
        """Return an image StreamObject with a matching ID."""
        return entry_by_id(id, self.images)

    def video_from_id(self, id):
        """Returns a video StreamObject with a matching ID"""
        """Return a video StreamObject with a matching ID."""
        return entry_by_id(id, self.videos)

    # CREATING NEW CAPTURES

    def new_stream_object(self, stream_object, target_list: list, shunt_others: bool=True):
        """Add a new capture to a list of captures, and shunt all others"""
    def new_stream_object(
            self,
            stream_object,
            target_list: list,
            shunt_others: bool=True):
        """Add a new capture to a list of captures, and shunt all others."""
        if shunt_others:  # If shunting all older captures
            for obj in target_list:  # For each older capture
                obj.shunt()  # Shunt capture from memory to storage
@@ -198,12 +207,18 @@ class BaseCamera(object):
        return stream_object

    def new_image(self, stream_object, shunt_others=True):
        """Add a new capture to the image list, and shunt all others"""
        return self.new_stream_object(stream_object, self.images, shunt_others=shunt_others)
        """Add a new capture to the image list, and shunt all others."""
        return self.new_stream_object(
            stream_object,
            self.images,
            shunt_others=shunt_others)

    def new_video(self, stream_object, shunt_others=True):
        """Add a new capture to the video list, and shunt all others"""
        return self.new_stream_object(stream_object, self.videos, shunt_others=shunt_others)
        """Add a new capture to the video list, and shunt all others."""
        return self.new_stream_object(
            stream_object,
            self.videos,
            shunt_others=shunt_others)

    # WORKER THREAD

+42 −24
Original line number Diff line number Diff line
@@ -5,8 +5,15 @@ import datetime
import copy
import logging


class StreamObject(object):
    def __init__(self, write_to_file: bool=None, filename: str=None, folder: str=None, fmt: str='file') -> None:
    def __init__(
            self,
            write_to_file: bool=None,
            filename: str=None,
            folder: str=None,
            fmt: str='file') -> None:
        """Create a new StreamObject, to manage capture data."""
        # Store a nice ID
        self.id = uuid.uuid4().hex
        logging.info("Created {}".format(self.id))
@@ -17,7 +24,11 @@ class StreamObject(object):

        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, iterator=iterator)  # Rebuild file name
            f_name = self.build_file_path(
                filename, 
                folder, 
                fmt, 
                iterator=iterator)  # Rebuild file name

        self.file = f_name

@@ -27,10 +38,10 @@ class StreamObject(object):
        # Set default write target
        self.write_to_file = write_to_file
        if self.write_to_file is False:
            logging.debug("Default target for {} set to 'stream'".format(self.id))
            logging.debug("Target for {} set to 'stream'".format(self.id))
            self.target = self.stream
        else:
            logging.debug("Default target for {} set to 'file'".format(self.id))
            logging.debug("Target for {} set to 'file'".format(self.id))
            self.target = self.file

        # Keep on disk after close by default
@@ -40,9 +51,10 @@ class StreamObject(object):
        self.context_manager = False

        # Object lock
        self.locked = False  # Is the StreamObject locked for writing? (Handled by StreamingCamera)
        self.locked = False

    def __enter__(self):
        """Create StreamObject in context, to auto-clean disk data."""
        logging.debug("Entering context for {}.\
            Stored files will be cleaned up automatically.".format(self.id))
        self.keep_on_disk = False  # Flag file to be removed on close.
@@ -50,14 +62,21 @@ class StreamObject(object):
        return self

    def __exit__(self, *args):
        """Exit StreamObject, and auto-clean disk data."""
        logging.info("Cleaning up {}".format(self.id))
        self.close()

    def build_file_path(self, filename: str, folder: str, fmt: str, iterator: int=0) -> str:
    def build_file_path(
            self,
            filename: str,
            folder: str,
            fmt: str,
            iterator: int=0) -> str:
        """
        Construct a full file path, based on filename, folder, and file format.

        Defaults to datestamp. Iterator adds a numeric increment to the file name.
        Defaults to datestamp. 
        Iterator adds a numeric increment to the file name.
        """
        if filename:
            file_name = "{}.{}".format(filename, fmt)
@@ -88,7 +107,7 @@ class StreamObject(object):

    @property
    def stream_exists(self, auto_rewind=True) -> bool:
        """Check if BytesIO stream is empty"""
        """Check if BytesIO stream is empty."""
        if auto_rewind:
            self.stream.seek(0)  # Rewind the data bytes for reading
        if self.stream.getvalue():  # If data stream contains data
@@ -100,7 +119,7 @@ class StreamObject(object):

    @property
    def file_exists(self) -> bool:
        """Check if corresponding file exists"""
        """Check if corresponding file exists."""
        if os.path.isfile(self.file):
            return True
        else:
@@ -108,6 +127,7 @@ class StreamObject(object):

    @property
    def metadata(self) -> dict:
        """Return dictionary of StreamObject properties."""
        d = {
            'id': self.id,
            'locked': self.locked,
@@ -140,16 +160,18 @@ class StreamObject(object):
        self.stream.seek(0)  # Rewind the data bytes for reading

        if self.stream_exists:  # If data stream contains data
            data = io.BytesIO(self.stream.getbuffer())  # Create a copy of the stream bytes
            # Create a copy of the stream bytes
            data = io.BytesIO(self.stream.getbuffer())

        else:  # If data stream is empty
            if self.file_exists:  # If data file exists
                # TODO: Streamline this bit
                logging.info("No stream data. Opening from file {}".format(self.file))
                logging.info("Opening from file {}".format(self.file))
                with open(self.file, 'rb') as f:
                    d = io.BytesIO(f.read())  # Load bytes from file in disk
                    d = io.BytesIO(f.read())  # Load bytes from file
                d.seek(0)  # Rewind loaded stream
                data = io.BytesIO(d.getbuffer())  # Create a copy of the stream bytes
                # Create a copy of the stream bytes
                data = io.BytesIO(d.getbuffer())
            else:
                data = None

@@ -165,16 +187,14 @@ class StreamObject(object):
        if self.file_exists:  # If data file exists
            # TODO: Streamline this bit
            with open(self.file, 'rb') as f:
                self.stream = io.BytesIO(f.read())  # Load bytes from file in disk
                self.stream = io.BytesIO(f.read())  # Load bytes from file
            self.stream.seek(0)  # Rewind data bytes again
            return True
        else:
            return False

    def save_file(self) -> bool:
        """
        Write the StreamObjects stream to a file.
        """
        """Write the StreamObjects stream to a file."""
        if self.stream_exists:  # If there's a stream to save
            with open(self.file, 'ab') as f:  # Load file as bytes
                logging.debug("Writing stream to file {}".format(self.file))
@@ -185,13 +205,11 @@ class StreamObject(object):
            return False

    def delete_stream(self):
        """Clears the BytesIO stream of the StreamObject."""
        """Clear the BytesIO stream of the StreamObject."""
        self.stream = io.BytesIO()

    def delete_file(self) -> bool:
        """
        If the StreamObject has been saved to disk, deletes the file and returns True.
        """
        """If the StreamObject has been saved, delete the file."""
        if os.path.isfile(self.file):
            logging.info("Deleting file {}".format(self.file))
            os.remove(self.file)
@@ -206,7 +224,7 @@ class StreamObject(object):
        self.delete_stream()  # Delete the stream from memory

    def close(self):
        """Both clear the stream, and delete any associated on-disk data"""
        """Both clear the stream, and delete any associated on-disk data."""
        logging.info("Closing {}".format(self.id))
        self.delete_stream()
        if not self.keep_on_disk:
+44 −27
Original line number Diff line number Diff line
@@ -84,7 +84,7 @@ class StreamingCamera(BaseCamera):
        pass

    def close(self):
        """Close the Raspberry Pi StreamingCamera"""
        """Close the Raspberry Pi StreamingCamera."""
        # Run BaseCamera close method
        BaseCamera.close(self)
        # Detatch Pi camera
@@ -135,7 +135,9 @@ class StreamingCamera(BaseCamera):
            if 'awb_mode' in config:
                self.camera.awb_mode = config['awb_mode']
            if 'red_gain' in config and 'blue_gain' in config:
                self.camera.awb_gains = (config['red_gain'], config['blue_gain'])
                self.camera.awb_gains = (
                    config['red_gain'],
                    config['blue_gain'])

            # Camera framerate
            if 'framerate' in config:
@@ -158,7 +160,8 @@ class StreamingCamera(BaseCamera):
                self.resume_stream_for_capture()

        else:
            raise Exception("Cannot update camera settings while recording is active.")
            raise Exception(
                "Cannot update camera settings while recording is active.")

    def change_zoom(self, zoom_value: int=1) -> None:
        """Change the camera zoom, handling recentering and scaling."""
@@ -213,7 +216,6 @@ class StreamingCamera(BaseCamera):
        fmt (str): Format of the capture.
            (default 'h264')
        """

        # Start recording method only if a current recording is not running
        if not self.state['record_active']:

@@ -235,14 +237,14 @@ class StreamingCamera(BaseCamera):
                target_obj = target

            # Start the camera video recording on port 2
            logging.info("Starting record at {}".format(self.settings['video_resolution']))
            logging.info("Recording to {}".format(target))

            self.camera.start_recording(
                target,
                format=fmt,
                splitter_port=2,
                resize=self.settings['video_resolution'],
                quality=quality)
            logging.debug("Recording started successfully.")

            # Update state dictionary
            self.state['record_active'] = True
@@ -250,12 +252,13 @@ class StreamingCamera(BaseCamera):
            return target_obj

        else:
            print("Cannot start a new recording until the current recording has stopped.")
            print(
                "Cannot start a new recording\
                until the current recording has stopped.")
            return None

    def stop_recording(self) -> bool:
        """Stop the last started video recording on splitter port 2."""

        # Stop the camera video recording on port 2
        logging.info("Stopping recording")
        self.camera.stop_recording(splitter_port=2)
@@ -264,7 +267,10 @@ class StreamingCamera(BaseCamera):
        # Update state dictionary
        self.state['record_active'] = False

    def pause_stream_for_capture(self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None:
    def pause_stream_for_capture(
            self,
            splitter_port: int=1,
            resolution: Tuple[int, int]=None) -> None:
        """
        Pause capture on a splitter port.

@@ -283,7 +289,10 @@ class StreamingCamera(BaseCamera):
        # Increase the resolution for taking an image
        self.camera.resolution = resolution

    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.

@@ -332,11 +341,14 @@ class StreamingCamera(BaseCamera):
            (default 'h264')
        resize ((int, int)): Resize the captured image.
        """

        # If no target is specified, store to StreamingCamera
        if not target:
            # Create a new image and add to the image list
            target_obj = self.new_image(StreamObject(write_to_file=write_to_file, filename=filename, folder=folder, fmt=fmt))
            target_obj = self.new_image(StreamObject(
                write_to_file=write_to_file,
                filename=filename,
                folder=folder,
                fmt=fmt))
            target = target_obj.target  # Store to the StreamObject BytesIO

        else:
@@ -373,13 +385,17 @@ class StreamingCamera(BaseCamera):

        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:
        """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.
        """

        if use_video_port:
            resolution = self.settings['video_resolution']
        else:
@@ -395,7 +411,8 @@ class StreamingCamera(BaseCamera):

        logging.debug("Creating PiYUVArray")
        with picamera.array.PiYUVArray(self.camera, size=size) as output:
            logging.debug("Capturing to PiYUVArray from {}".format(self.camera))

            logging.info("Capturing to {}".format(output))

            self.camera.capture(
                output,
@@ -403,8 +420,6 @@ class StreamingCamera(BaseCamera):
                format='yuv',
                use_video_port=use_video_port)

            logging.debug("Capturing complete")

            if not use_video_port:
                self.resume_stream_for_capture()

@@ -418,10 +433,10 @@ class StreamingCamera(BaseCamera):

    def frames(self):
        """
        Create iterator used by worker thread to generate stream frames.
        Create generator that returns frames from the camera.

        Holds a Pi camera in context, records video from port 1 to a 
        byte stream, and iterates sequential frames.
        Records video from port 1 to a byte stream,
        and iterates sequential frames.
        """
        # Run this initialisation method
        self.initialisation()
@@ -447,7 +462,8 @@ class StreamingCamera(BaseCamera):
        logging.debug("STREAM ACTIVE")
        self.state['stream_active'] = True

        try:  # While the iterator is not closed
        # While the iterator is not closed
        try:
            while True:
                # reset stream for next frame
                self.stream.seek(0)
@@ -462,7 +478,8 @@ class StreamingCamera(BaseCamera):
                    pass
                else:
                    yield frame
        finally:  # When GeneratorExit or StopIteration raised, run cleanup code
        # When GeneratorExit or StopIteration raised, run cleanup code
        finally:
            logging.debug("Stopping stream recording on port 1")
            self.camera.stop_recording(splitter_port=1)
            self.state['stream_active'] = False
+1 −1

File changed.

Contains only whitespace changes.