Static type analysis

This commit is contained in:
Joel Collins 2020-11-30 13:36:45 +00:00
parent 3aebb8bead
commit 7866ec0f47
63 changed files with 1825 additions and 2722 deletions

View file

@ -30,7 +30,7 @@ import logging
import time
# Type hinting
from typing import Tuple
from typing import BinaryIO, Tuple, Union
import numpy as np
@ -77,57 +77,64 @@ class PiCameraStreamer(BaseCamera):
BaseCamera.__init__(self)
#: :py:class:`picamerax.PiCamera`: Attached Picamera object
self.camera = picamerax.PiCamera()
self.picamera: picamerax.PiCamera = picamerax.PiCamera()
# Store state of PiCameraStreamer
self.preview_active = False
self.preview_active: bool = False
# Reset variable states
self.set_zoom(1.0)
#: tuple: Resolution for image captures
self.image_resolution = tuple(self.camera.MAX_RESOLUTION)
self.image_resolution: Tuple[int, int] = tuple(self.picamera.MAX_RESOLUTION)
#: tuple: Resolution for stream and video captures
self.stream_resolution = (832, 624)
self.stream_resolution: Tuple[int, int] = (832, 624)
#: tuple: Resolution for numpy array captures
self.numpy_resolution = (1312, 976)
self.numpy_resolution: Tuple[int, int] = (1312, 976)
self.jpeg_quality = 100 #: int: JPEG quality
self.mjpeg_quality = 75 #: int: MJPEG quality
self.jpeg_quality: int = 100 #: int: JPEG quality
self.mjpeg_quality: int = 75 #: int: MJPEG quality
# Start stream recording (and set resolution)
self.start_stream_recording()
self.start_stream()
# Wait until frames are available
logging.debug("Waiting for frames...")
self.stream.new_frame.wait()
logging.debug("Camera initialised")
@property
def configuration(self):
"""The current camera configuration."""
return {"board": self.camera.revision}
def camera(self):
logging.warning(
"PiCameraStreamer.camera is deprecated. Replace with PiCameraStreamer.picamera"
)
return self.picamera
@property
def state(self):
def configuration(self) -> dict:
"""The current camera configuration."""
return {"board": self.picamera.revision}
@property
def state(self) -> dict:
"""The current read-only camera state."""
return {}
def close(self):
"""Close the Raspberry Pi PiCameraStreamer."""
# Stop stream recording
self.stop_stream_recording()
self.stop_stream()
# Run BaseCamera close method
super().close()
# Detach Pi camera
if self.camera:
self.camera.close()
if self.picamera:
self.picamera.close()
# HANDLE SETTINGS
def read_settings(self) -> dict:
"""
Return config dictionary of the PiCameraStreamer.
"""
conf_dict = {
conf_dict: dict = {
"stream_resolution": self.stream_resolution,
"image_resolution": self.image_resolution,
"numpy_resolution": self.numpy_resolution,
@ -139,7 +146,7 @@ class PiCameraStreamer(BaseCamera):
# Include a subset of picamera properties. Excludes lens shading table
for key in PiCameraStreamer.picamera_settings_keys:
try:
value = getattr(self.camera, key)
value = getattr(self.picamera, key)
logging.debug("Reading PiCamera().%s: %s", key, value)
conf_dict["picamera"][key] = value
except AttributeError:
@ -147,11 +154,11 @@ class PiCameraStreamer(BaseCamera):
# Include a serialised lens shading table
if (
hasattr(self.camera, "lens_shading_table")
and getattr(self.camera, "lens_shading_table") is not None
hasattr(self.picamera, "lens_shading_table")
and getattr(self.picamera, "lens_shading_table") is not None
):
conf_dict["picamera"]["lens_shading_table"] = ndarray_to_json(
getattr(self.camera, "lens_shading_table")
getattr(self.picamera, "lens_shading_table")
)
return conf_dict
@ -179,7 +186,7 @@ class PiCameraStreamer(BaseCamera):
# Pause stream while changing settings
if self.stream_active: # If stream is active
logging.info("Pausing stream to update config.")
self.stop_stream_recording() # Pause stream
self.stop_stream() # Pause stream
paused_stream = True # Remember to unpause stream when done
# PiCamera parameters
@ -190,11 +197,11 @@ class PiCameraStreamer(BaseCamera):
# Handle lens shading if camera supports it
if (
hasattr(self.camera, "lens_shading_table")
hasattr(self.picamera, "lens_shading_table")
and "lens_shading_table" in config["picamera"]
):
try:
self.camera.lens_shading_table = json_to_ndarray(
self.picamera.lens_shading_table = json_to_ndarray(
config["picamera"].get("lens_shading_table")
)
except KeyError as e:
@ -208,7 +215,7 @@ class PiCameraStreamer(BaseCamera):
# If stream was paused to update config, unpause
if paused_stream:
logging.info("Resuming stream.")
self.start_stream_recording()
self.start_stream()
else:
raise Exception(
@ -229,47 +236,47 @@ class PiCameraStreamer(BaseCamera):
logging.debug(
"Applying exposure_mode: %s", (settings_dict["exposure_mode"])
)
self.camera.exposure_mode = settings_dict["exposure_mode"]
self.picamera.exposure_mode = settings_dict["exposure_mode"]
# Apply gains and let them settle
if "analog_gain" in settings_dict:
logging.debug("Applying analog_gain: %s", (settings_dict["analog_gain"]))
set_analog_gain(self.camera, float(settings_dict["analog_gain"]))
set_analog_gain(self.picamera, float(settings_dict["analog_gain"]))
if "digital_gain" in settings_dict:
logging.debug("Applying digital_gain: %s", (settings_dict["digital_gain"]))
set_digital_gain(self.camera, float(settings_dict["digital_gain"]))
set_digital_gain(self.picamera, float(settings_dict["digital_gain"]))
# Apply shutter speed
if "shutter_speed" in settings_dict:
logging.debug(
"Applying shutter_speed: %s", (settings_dict["shutter_speed"])
)
self.camera.shutter_speed = int(settings_dict["shutter_speed"])
self.picamera.shutter_speed = int(settings_dict["shutter_speed"])
time.sleep(0.2) # Let gains settle
# Handle AWB in a half-smart way
if "awb_gains" in settings_dict:
logging.debug("Applying awb_mode: off")
self.camera.awb_mode = "off"
self.picamera.awb_mode = "off"
logging.debug("Applying awb_gains: %s", (settings_dict["awb_gains"]))
self.camera.awb_gains = settings_dict["awb_gains"]
self.picamera.awb_gains = settings_dict["awb_gains"]
elif "awb_mode" in settings_dict:
logging.debug("Applying awb_mode: %s", (settings_dict["awb_mode"]))
self.camera.awb_mode = settings_dict["awb_mode"]
self.picamera.awb_mode = settings_dict["awb_mode"]
# Handle some properties that can be quickly applied
batched_keys = ["framerate", "saturation"]
for key in batched_keys:
if (key in settings_dict) and hasattr(self.camera, key):
if (key in settings_dict) and hasattr(self.picamera, key):
logging.debug("Applying %s: %s", key, settings_dict[key])
setattr(self.camera, key, settings_dict[key])
setattr(self.picamera, key, settings_dict[key])
# Final optional pause to settle
if pause_for_effect:
time.sleep(0.2)
def set_zoom(self, zoom_value: float = 1.0) -> None:
def set_zoom(self, zoom_value: Union[float, int] = 1.0) -> None:
"""
Change the camera zoom, handling re-centering and scaling.
"""
@ -278,7 +285,7 @@ class PiCameraStreamer(BaseCamera):
if self.zoom_value < 1:
self.zoom_value = 1
# Richard's code for zooming !
fov = self.camera.zoom
fov = self.picamera.zoom
centre = np.array([fov[0] + fov[2] / 2.0, fov[1] + fov[3] / 2.0])
size = 1.0 / self.zoom_value
# If the new zoom value would be invalid, move the centre to
@ -289,23 +296,23 @@ class PiCameraStreamer(BaseCamera):
centre[i] = 0.5 + (1.0 - size) / 2 * np.sign(centre[i] - 0.5)
logging.info("setting zoom, centre %s, size %s", centre, size)
new_fov = (centre[0] - size / 2, centre[1] - size / 2, size, size)
self.camera.zoom = new_fov
self.picamera.zoom = new_fov
# LAUNCH ACTIONS
def start_preview(self, fullscreen=True, window=None):
def start_preview(
self, fullscreen: bool = True, window: Tuple[int, int, int, int] = None
):
"""Start the on board GPU camera preview."""
with self.lock(timeout=1):
try:
if not self.camera.preview:
if not self.picamera.preview:
logging.debug("Starting preview")
self.camera.start_preview(fullscreen=fullscreen, window=window)
self.picamera.start_preview(fullscreen=fullscreen, window=window)
else:
logging.debug("Resizing preview")
if window:
self.camera.preview.window = window
self.picamera.preview.window = window
if fullscreen:
self.camera.preview.fullscreen = fullscreen
self.picamera.preview.fullscreen = fullscreen
self.preview_active = True
except picamerax.exc.PiCameraMMALError as e:
logging.error(
@ -320,11 +327,13 @@ class PiCameraStreamer(BaseCamera):
def stop_preview(self):
"""Stop the on board GPU camera preview."""
with self.lock(timeout=1):
if self.camera.preview:
self.camera.stop_preview()
if self.picamera.preview:
self.picamera.stop_preview()
self.preview_active = False
def start_recording(self, output, fmt: str = "h264", quality: int = 15):
def start_recording(
self, output: Union[str, BinaryIO], fmt: str = "h264", quality: int = 15
):
"""Start recording.
Start a new video recording, writing to a output object.
@ -345,7 +354,7 @@ class PiCameraStreamer(BaseCamera):
# Start the camera video recording on port 2
logging.info("Recording to %s", (output))
self.camera.start_recording(
self.picamera.start_recording(
output,
format=fmt,
splitter_port=2,
@ -370,80 +379,67 @@ class PiCameraStreamer(BaseCamera):
with self.lock(timeout=5):
# Stop the camera video recording on port 2
logging.info("Stopping recording")
self.camera.stop_recording(splitter_port=2)
self.picamera.stop_recording(splitter_port=2)
logging.info("Recording stopped")
# Update state
self.record_active = False
def start_stream_recording(self, splitter_port: int = 1, **kwargs) -> None:
def start_stream(self) -> None:
"""
Sets the camera resolution to the video/stream resolution, and starts recording if the stream should be active.
Args:
splitter_port (int): Splitter port to start recording on
"""
for k in kwargs.keys():
logging.warning(
"Warning, kwarg %s is invalid for stop_stream_recording.", k
)
with self.lock(timeout=None):
# Reduce the resolution for video streaming
try:
self.camera._check_recording_stopped() # pylint: disable=W0212
self.picamera._check_recording_stopped() # pylint: disable=W0212
except picamerax.exc.PiCameraRuntimeError:
logging.info(
"Error while changing resolution: Recording already running."
)
else:
self.camera.resolution = self.stream_resolution
self.picamera.resolution = self.stream_resolution
# Sprinkled a sleep to prevent camera getting confused by rapid commands
time.sleep(0.2)
# If the stream should be active
try:
# Start recording on stream port
self.camera.start_recording(
self.picamera.start_recording(
self.stream,
format="mjpeg",
quality=self.mjpeg_quality,
bitrate=-1, # RWB: disable bitrate control
# (bitrate control makes JPEG size less good as a focus
# metric)
splitter_port=splitter_port,
splitter_port=1,
)
except picamerax.exc.PiCameraAlreadyRecording:
logging.info("Error while starting preview: Recording already running.")
else:
self.stream_active = True
logging.debug(
"Started MJPEG stream at %s on port %s",
self.stream_resolution,
splitter_port,
"Started MJPEG stream at %s on port %s", self.stream_resolution, 1
)
def stop_stream_recording(self, splitter_port: int = 1, **kwargs) -> None:
def stop_stream(self) -> None:
"""
Sets the camera resolution to the still-image resolution, and stops recording if the stream is active.
Args:
splitter_port (int): Splitter port to stop recording on
"""
for k in kwargs.keys():
logging.warning(
"Warning, kwarg %s is invalid for stop_stream_recording.", k
)
with self.lock:
# Stop the camera video recording on port 1
try:
self.camera.stop_recording(splitter_port=splitter_port)
self.picamera.stop_recording(splitter_port=1)
except picamerax.exc.PiCameraNotRecording:
logging.info("Not recording on splitter_port %s", (splitter_port))
logging.info("Not recording on splitter_port %s", (1))
else:
self.stream_active = False
logging.info(
"Stopped MJPEG stream on port %s. Switching to %s.",
splitter_port,
1,
self.image_resolution,
)
@ -451,16 +447,16 @@ class PiCameraStreamer(BaseCamera):
time.sleep(
0.2
) # Sprinkled a sleep to prevent camera getting confused by rapid commands
self.camera.resolution = self.image_resolution
self.picamera.resolution = self.image_resolution
def capture(
self,
output,
output: Union[str, BinaryIO],
fmt: str = "jpeg",
use_video_port: bool = False,
resize: Tuple[int, int] = None,
bayer: bool = True,
thumbnail: tuple = None,
thumbnail: Tuple[int, int, int] = None,
):
"""
Capture a still image to a StreamObject.
@ -470,10 +466,11 @@ class PiCameraStreamer(BaseCamera):
Args:
output: String or file-like object to write capture data to
fmt (str): Format of the capture.
use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster.
resize ((int, int)): Resize the captured image.
bayer (bool): Store raw bayer data in capture
fmt: Format of the capture.
use_video_port: Capture from the video port used for streaming. Lower resolution, faster.
resize: Resize the captured image.
bayer: Store raw bayer data in capture
thumbnail: Dimensions and quality (x, y, quality) of a thumbnail to generate, if supported
Returns:
output_object (str/BytesIO): Target object.
@ -483,9 +480,9 @@ class PiCameraStreamer(BaseCamera):
# Set resolution and stop stream recording if necessary
if not use_video_port:
self.stop_stream_recording()
self.stop_stream()
self.camera.capture(
self.picamera.capture(
output,
format=fmt,
quality=self.jpeg_quality,
@ -497,11 +494,11 @@ class PiCameraStreamer(BaseCamera):
# Set resolution and start stream recording if necessary
if not use_video_port:
self.start_stream_recording()
self.start_stream()
return output
def array(self, use_video_port=True) -> np.ndarray:
def array(self, use_video_port: bool = True) -> np.ndarray:
"""Capture an uncompressed still RGB image to a Numpy array.
Args:
@ -513,7 +510,9 @@ class PiCameraStreamer(BaseCamera):
"""
with self.lock:
logging.debug("Creating PiRGBArray")
with picamerax.array.PiRGBArray(self.camera) as output:
with picamerax.array.PiRGBArray(self.picamera) as output:
logging.info("Capturing to %s", (output))
self.camera.capture(output, format="rgb", use_video_port=use_video_port)
self.picamera.capture(
output, format="rgb", use_video_port=use_video_port
)
return output.array