Converted config dict into properties/attributes

This commit is contained in:
Joel Collins 2019-06-04 14:03:16 +01:00
parent 5e6372835e
commit 3cf166226b

View file

@ -41,34 +41,29 @@ from .set_picamera_gain import set_analog_gain, set_digital_gain
# Handle config and picamera settings # Handle config and picamera settings
CONFIG_KEYS = { CONFIG_KEYS = {
'stream_resolution': (832, 624), 'stream_resolution': tuple,
'image_resolution': (2592, 1944), # Default for PiCamera v1. Overridden in __init__ 'image_resolution': tuple,
'numpy_resolution': (1312, 976), 'numpy_resolution': tuple,
'jpeg_quality': 75, 'analog_gain': float,
'digital_gain': float,
'jpeg_quality': int,
'picamera_settings': { 'picamera_settings': {
'exposure_mode': None, 'exposure_mode': str,
'awb_mode': None, 'awb_mode': str,
'awb_gains': None, 'awb_gains': tuple,
'framerate': None, 'framerate': int,
'shutter_speed': None, 'shutter_speed': float,
'saturation': None, 'saturation': float,
'analog_gain': None, 'lens_shading_table': np.ndarray,
'digital_gain': None,
'lens_shading_table': None,
}, },
} }
# MAIN CLASS # MAIN CLASS
class StreamingCamera(BaseCamera): class StreamingCamera(BaseCamera):
"""Raspberry Pi camera implementation of StreamingCamera. """Raspberry Pi camera implementation of StreamingCamera."""
Args:
config (dict): Dictionary of config parameters to apply on init. If None, will default to basic config.
"""
def __init__(self, config: dict = None):
global CONFIG_KEYS
def __init__(self):
# Run BaseCamera init # Run BaseCamera init
BaseCamera.__init__(self) BaseCamera.__init__(self)
# Attach to Pi camera # Attach to Pi camera
@ -83,17 +78,11 @@ class StreamingCamera(BaseCamera):
# Reset variable states # Reset variable states
self.set_zoom(1.0) self.set_zoom(1.0)
# Populate config and settings with all available keys # Update config properties
self.config.update(CONFIG_KEYS) self.image_resolution = tuple(self.camera.MAX_RESOLUTION)
self.stream_resolution = (832, 624)
# Update config based on PiCamera parameters self.numpy_resolution = (1312, 976)
self.config.update({ self.jpeg_quality = 75
'image_resolution': tuple(self.camera.MAX_RESOLUTION)
})
# Load config dictionary if passed
if config:
self.apply_config(config)
# Create an empty stream # Create an empty stream
self.stream = io.BytesIO() self.stream = io.BytesIO()
@ -127,26 +116,47 @@ class StreamingCamera(BaseCamera):
""" """
return hasattr(self.camera, "lens_shading_table") return hasattr(self.camera, "lens_shading_table")
@property
def digital_gain(self):
# TODO: Handle missing picamera?
return self.camera.digital_gain
@digital_gain.setter
def digital_gain(self, value):
set_digital_gain(self.camera, value)
@property
def analog_gain(self):
# TODO: Handle missing picamera?
return self.camera.analog_gain
@analog_gain.setter
def analog_gain(self, value):
set_analog_gain(self.camera, value)
def read_config(self): def read_config(self):
""" """
Return config dictionary of the StreamingCamera. Return config dictionary of the StreamingCamera.
""" """
global CONFIG_KEYS
conf_dict = { conf_dict = {
'picamera_settings': {}, 'picamera_settings': {},
} }
# PiCamera parameters (obtained directly from PiCamera object) # PiCamera parameters
for key, _ in CONFIG_KEYS['picamera_settings'].items(): for key in CONFIG_KEYS['picamera_settings']:
try: try:
value = getattr(self.camera, key) value = getattr(self.camera, key)
logging.debug("{}: {}".format(key, value))
conf_dict['picamera_settings'][key] = value conf_dict['picamera_settings'][key] = value
except AttributeError: except AttributeError:
logging.debug("Unable to read PiCamera attribute {}".format(key)) logging.debug("Unable to read PiCamera attribute {}".format(key))
# StreamingCamera parameters (obtained from StreamingCamera _config) # StreamingCamera parameters
for key in CONFIG_KEYS: for key in CONFIG_KEYS:
if (key != 'picamera_settings') and (key in self.config): if (key != 'picamera_settings') and hasattr(self, key):
conf_dict[key] = self.config[key] conf_dict[key] = getattr(self, key)
return conf_dict return conf_dict
@ -160,8 +170,12 @@ class StreamingCamera(BaseCamera):
Args: Args:
config (dict): Dictionary of config parameters. config (dict): Dictionary of config parameters.
""" """
global CONFIG_KEYS
# TODO: Include timing and batching logic when applying PiCamera settings
paused_stream = False paused_stream = False
logging.debug("Applying config:")
logging.debug(config)
with self.lock: with self.lock:
@ -177,32 +191,22 @@ class StreamingCamera(BaseCamera):
self.stop_stream_recording() # Pause stream self.stop_stream_recording() # Pause stream
paused_stream = True # Remember to unpause stream when done paused_stream = True # Remember to unpause stream when done
# PiCamera parameters (applied directly to PiCamera object) # PiCamera parameters
if 'picamera_settings' in config: # If new settings are given if 'picamera_settings' in config: # If new settings are given
for key, value in config['picamera_settings'].items(): # For each given setting for key, value in config['picamera_settings'].items(): # For each given setting
if hasattr(self.camera, key): if hasattr(self.camera, key):
self.config['picamera_settings'][key] = None # Add the key to the list of returned settings if key not in CONFIG_KEYS['picamera_settings']:
CONFIG_KEYS['picamera_settings'][key] = type(value)
logging.debug("Setting parameter {}: {}".format(key, config['picamera_settings'][key])) logging.debug("Setting parameter {}: {}".format(key, config['picamera_settings'][key]))
setattr(self.camera, key, value) # Write setting to camera
# Handle special attributes: # StreamingCamera parameters
if key == 'digital_gain':
set_digital_gain(self.camera, value)
elif key == 'analog_gain':
set_analog_gain(self.camera, value)
elif key == "shutter_speed":
# TODO: use types from CONFIG_KEYS? @jtc42
self.camera.shutter_speed = int(value)
else:
setattr(self.camera, key, value) # Write setting to camera
# StreamingCamera parameters (applied via StreamingCamera config)
for key, value in config.items(): # For each provided setting for key, value in config.items(): # For each provided setting
if key != 'picamera_settings': # We already handled this if (key != 'picamera_settings') and hasattr(self, key):
if key not in CONFIG_KEYS.keys(): if key not in CONFIG_KEYS:
logging.warn("{} is not in the streaming camera settings dictionary - adding it.") CONFIG_KEYS[key] = type(value)
#continue #TODO: filter settings somehow?
logging.debug("Setting parameter {}: {}".format(key, value)) logging.debug("Setting parameter {}: {}".format(key, value))
self.config[key] = value setattr(self, key, value)
# If stream was paused to update config, unpause # If stream was paused to update config, unpause
if paused_stream: if paused_stream:
@ -223,21 +227,21 @@ class StreamingCamera(BaseCamera):
self.state['zoom_value'] = 1 self.state['zoom_value'] = 1
# Richard's code for zooming ! # Richard's code for zooming !
fov = self.camera.zoom fov = self.camera.zoom
centre = np.array([fov[0] + fov[2]/2.0, fov[1] + fov[3]/2.0]) centre = np.array([fov[0] + fov[2] / 2.0, fov[1] + fov[3] / 2.0])
size = 1.0/self.state['zoom_value'] size = 1.0 / self.state['zoom_value']
# If the new zoom value would be invalid, move the centre to # If the new zoom value would be invalid, move the centre to
# keep it within the camera's sensor (this is only relevant # keep it within the camera's sensor (this is only relevant
# when zooming out, if the FoV is not centred on (0.5, 0.5) # when zooming out, if the FoV is not centred on (0.5, 0.5)
for i in range(2): for i in range(2):
if np.abs(centre[i] - 0.5) + size/2 > 0.5: if np.abs(centre[i] - 0.5) + size / 2 > 0.5:
centre[i] = 0.5 + (1.0 - size)/2 * np.sign(centre[i]-0.5) centre[i] = 0.5 + (1.0 - size) / 2 * np.sign(centre[i] - 0.5)
logging.info("setting zoom, centre {}, size {}".format(centre, size)) logging.info("setting zoom, centre {}, size {}".format(centre, size))
new_fov = (centre[0] - size/2, centre[1] - size/2, size, size) new_fov = (centre[0] - size / 2, centre[1] - size / 2, size, size)
self.camera.zoom = new_fov self.camera.zoom = new_fov
# LAUNCH ACTIONS # LAUNCH ACTIONS
def start_preview(self, fullscreen=True, window=None) -> bool: def start_preview(self, fullscreen=True, window=None):
"""Start the on board GPU camera preview.""" """Start the on board GPU camera preview."""
logging.info("Starting the GPU preview") logging.info("Starting the GPU preview")
@ -260,7 +264,7 @@ class StreamingCamera(BaseCamera):
except picamera.exc.PiCameraValueError as e: except picamera.exc.PiCameraValueError as e:
logging.error("Suppressed a ValueError exception in start_preview. Exception: {}".format(e)) logging.error("Suppressed a ValueError exception in start_preview. Exception: {}".format(e))
def stop_preview(self) -> bool: def stop_preview(self):
"""Stop the on board GPU camera preview.""" """Stop the on board GPU camera preview."""
self.camera.stop_preview() self.camera.stop_preview()
self.state['preview_active'] = False self.state['preview_active'] = False
@ -289,8 +293,6 @@ class StreamingCamera(BaseCamera):
# If output is a StreamObject # If output is a StreamObject
if isinstance(output, CaptureObject): if isinstance(output, CaptureObject):
# Lock the StreamObject while recording
output.lock()
# Set target to capture stream # Set target to capture stream
output_stream = output.stream output_stream = output.stream
else: else:
@ -303,7 +305,7 @@ class StreamingCamera(BaseCamera):
output_stream, output_stream,
format=fmt, format=fmt,
splitter_port=2, splitter_port=2,
resize=self.config['stream_resolution'], resize=self.stream_resolution,
quality=quality) quality=quality)
# Update state dictionary # Update state dictionary
@ -342,7 +344,7 @@ class StreamingCamera(BaseCamera):
with self.lock: with self.lock:
# If no resolution is specified, default to image_resolution # If no resolution is specified, default to image_resolution
if not resolution: if not resolution:
resolution = self.config['image_resolution'] resolution = self.image_resolution
# Stop the camera video recording on port 1 # Stop the camera video recording on port 1
try: try:
@ -366,7 +368,7 @@ class StreamingCamera(BaseCamera):
Args: Args:
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.
Defaults to `self.config['stream_resolution']`. Defaults to `self.stream_resolution`.
""" """
with self.lock: with self.lock:
# If stream object was destroyed # If stream object was destroyed
@ -375,7 +377,7 @@ class StreamingCamera(BaseCamera):
# If no explicit resolution is passed # If no explicit resolution is passed
if not resolution: if not resolution:
resolution = self.config['stream_resolution'] # Default to video recording resolution resolution = self.stream_resolution # Default to video recording resolution
# Reduce the resolution for video streaming # Reduce the resolution for video streaming
try: try:
@ -392,7 +394,7 @@ class StreamingCamera(BaseCamera):
self.camera.start_recording( self.camera.start_recording(
self.stream, self.stream,
format='mjpeg', format='mjpeg',
quality=self.config['jpeg_quality'], quality=self.jpeg_quality,
bitrate=-1, # RWB: disable bitrate control bitrate=-1, # RWB: disable bitrate control
# (bitrate control makes JPEG size less good as a focus # (bitrate control makes JPEG size less good as a focus
# metric) # metric)
@ -463,9 +465,9 @@ class StreamingCamera(BaseCamera):
""" """
with self.lock: with self.lock:
if use_video_port: if use_video_port:
resolution = self.config['stream_resolution'] resolution = self.stream_resolution
else: else:
resolution = self.config['numpy_resolution'] resolution = self.numpy_resolution
if resize: if resize:
size = resize size = resize
@ -503,9 +505,9 @@ class StreamingCamera(BaseCamera):
""" """
with self.lock: with self.lock:
if use_video_port: if use_video_port:
resolution = self.config['stream_resolution'] resolution = self.stream_resolution
else: else:
resolution = self.config['numpy_resolution'] resolution = self.numpy_resolution
if resize: if resize:
size = resize size = resize
@ -557,7 +559,7 @@ class StreamingCamera(BaseCamera):
self.stream.seek(0) self.stream.seek(0)
self.stream.truncate() self.stream.truncate()
# to stream, read the new frame # to stream, read the new frame
time.sleep(1/self.camera.framerate*0.1) time.sleep(1 / self.camera.framerate * 0.1)
# yield the result to be read # yield the result to be read
frame = self.stream.getvalue() frame = self.stream.getvalue()