From c887f990b58041041a66c6cf5e6fce01bd4053ee Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Fri, 23 Nov 2018 12:14:10 +0000 Subject: [PATCH] Split loading, saving, and applying camera config file --- openflexure_microscope/camera/base.py | 2 +- openflexure_microscope/camera/config.py | 13 ++- openflexure_microscope/camera/pi.py | 102 ++++++++++++------------ 3 files changed, 62 insertions(+), 55 deletions(-) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index 04a28649..785b47a5 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -97,7 +97,7 @@ class BaseCamera(object): self.event = CameraEvent() self.state = {} #: dict: Dictionary for capture state - self.settings = {} #: dict: Dictionary of camera settings + self.config = {} #: dict: Dictionary of camera settings self.paths = { 'image': BASE_CAPTURE_PATH, 'video': BASE_CAPTURE_PATH, diff --git a/openflexure_microscope/camera/config.py b/openflexure_microscope/camera/config.py index 93cf25e7..4c4300cb 100644 --- a/openflexure_microscope/camera/config.py +++ b/openflexure_microscope/camera/config.py @@ -17,18 +17,23 @@ TYPES = { } -def convert_config(config): +def convert_config(config: dict) -> dict: """Convert datatype of config based on type dictionary.""" global TYPES for key in config: if key in TYPES: - config[key] = TYPES[key](config[key]) + config[key] = TYPES[key](config[key]) return config -def load_config(yaml_path): +def load_config(yaml_path: str) -> dict: """Load YAML file, pass through dictionary conversion, and return.""" with open(yaml_path) as config_file: - return convert_config(yaml.load(config_file)) + return yaml.load(config_file) + + +def save_config(config: dict, yaml_path: str): + with open('yaml_path', 'w') as outfile: + yaml.dump(config, outfile) \ No newline at end of file diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index d799365b..7ba2291c 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -48,7 +48,7 @@ from .base import BaseCamera, StreamObject # Richard's fix gain from .set_picamera_gain import set_analog_gain, set_digital_gain # Manage config -from .config import load_config +from .config import load_config, save_config, convert_config HERE = os.path.abspath(os.path.dirname(__file__)) DEFAULT_CONFIG = os.path.join(HERE, 'config_picamera.yaml') @@ -62,8 +62,8 @@ class StreamingCamera(BaseCamera): # Attach to Pi camera self.camera = picamera.PiCamera() #: :py:class:`picamera.PiCamera`: Picamera object - # Camera settings - self.settings.update({ + # Default camera config + self.config.update({ 'video_resolution': (832, 624), 'image_resolution': (2592, 1944), 'numpy_resolution': (1312, 976), @@ -92,53 +92,55 @@ class StreamingCamera(BaseCamera): self.camera.close() # HANDLE SETTINGS - - def update_settings(self, config_path: str=None) -> None: + def load_config(self, config_path: str=None): """ Open config_picamera.yaml file and write to camera. - Todo: - 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 + Args: + config_path (str): Path to the config YAML file. """ global DEFAULT_CONFIG + if not config_path: + config_data = load_config(DEFAULT_CONFIG) + else: + config_data = load_config(config_path) + + self.update_settings(config_data) + + def save_config(self, config_path: str=None): + """ + Open config_picamera.yaml file and write to camera. + + Args: + config_path (str): Path to the config YAML file. + """ + global DEFAULT_CONFIG + if not config_path: + config_path = DEFAULT_CONFIG + + save_config(self.config, config_path) + + def update_settings(self, config: dict) -> None: + """ + Write a config dictionary to the StreamingCamera config. + + Args: + config (dict): Dictionary of config parameters. + """ + + self.config = convert_config(config) # Convert loaded dictionary to valid config dictionary + logging.debug(self.config) paused_stream = False + # Apply valid config params to Picamera object if not self.state['record_active']: # If not recording a video if self.state['stream_active']: # If stream is active - logging.info("Pausing stream to update settings.") + logging.info("Pausing stream to update config.") self.pause_stream_for_capture() # Pause stream paused_stream = True # Remember to unpause stream when done - if not config_path: - config = load_config(DEFAULT_CONFIG) - else: - config = load_config(config_path) - - logging.debug(config) - - # StreamingCamera settings - if 'video_resolution' in config: - self.settings['video_resolution'] = config['video_resolution'] - if 'image_resolution' in config: - self.settings['image_resolution'] = config['image_resolution'] - if 'numpy_resolution' in config: - self.settings['numpy_resolution'] = config['numpy_resolution'] - - if 'jpeg_quality' in config: - self.settings['jpeg_quality'] = config['jpeg_quality'] - if 'framerate' in config: - self.settings['framerate'] = config['framerate'] - # Camera AWB if 'awb_mode' in config: self.camera.awb_mode = config['awb_mode'] @@ -163,17 +165,17 @@ class StreamingCamera(BaseCamera): if 'digital_gain' in config: set_digital_gain(self.camera, config['digital_gain']) - if paused_stream: # If stream was paused to update settings + if paused_stream: # If stream was paused to update config logging.info("Resuming stream.") self.resume_stream_for_capture() else: raise Exception( - "Cannot update camera settings while recording is active.") + "Cannot update camera config while recording is active.") def change_zoom(self, zoom_value: int=1) -> None: """Change the camera zoom, handling recentering and scaling. - + Todo: TODO: Needs to be re-implemented @@ -259,7 +261,7 @@ class StreamingCamera(BaseCamera): target, format=fmt, splitter_port=2, - resize=self.settings['video_resolution'], + resize=self.config['video_resolution'], quality=quality) # Update state dictionary @@ -297,7 +299,7 @@ class StreamingCamera(BaseCamera): logging.debug("Pausing stream") # If no resolution is specified, default to image_resolution if not resolution: - resolution = self.settings['image_resolution'] + resolution = self.config['image_resolution'] # Stop the camera video recording on port 1 self.camera.stop_recording(splitter_port=splitter_port) @@ -318,7 +320,7 @@ class StreamingCamera(BaseCamera): """ logging.debug("Unpausing stream") if not resolution: - resolution = self.settings['video_resolution'] + resolution = self.config['video_resolution'] # Reduce the resolution for video streaming self.camera.resolution = resolution @@ -327,7 +329,7 @@ class StreamingCamera(BaseCamera): self.camera.start_recording( self.stream, format='mjpeg', - quality=self.settings['jpeg_quality'], + quality=self.config['jpeg_quality'], splitter_port=splitter_port) def capture( @@ -416,9 +418,9 @@ class StreamingCamera(BaseCamera): resize ((int, int)): Resize the captured image. """ if use_video_port: - resolution = self.settings['video_resolution'] + resolution = self.config['video_resolution'] else: - resolution = self.settings['numpy_resolution'] + resolution = self.config['numpy_resolution'] if resize: size = resize @@ -462,10 +464,10 @@ class StreamingCamera(BaseCamera): self.wait_for_camera() - # Settings config - self.update_settings() + # Load config file + self.load_config() # Set stream resolution - self.camera.resolution = self.settings['video_resolution'] + self.camera.resolution = self.config['video_resolution'] # Create stream self.stream = io.BytesIO() @@ -474,7 +476,7 @@ class StreamingCamera(BaseCamera): self.camera.start_recording( self.stream, format='mjpeg', - quality=self.settings['jpeg_quality'], + quality=self.config['jpeg_quality'], splitter_port=1) # Update state @@ -488,7 +490,7 @@ class StreamingCamera(BaseCamera): self.stream.seek(0) self.stream.truncate() # to stream, read the new frame - time.sleep(1/self.settings['framerate']*0.1) + time.sleep(1/self.config['framerate']*0.1) # yield the result to be read frame = self.stream.getvalue()