Commit 5471ffa6 authored by Joel Collins's avatar Joel Collins
Browse files

Introduced apply_config and read_config methods

parent e5c10adf
Loading
Loading
Loading
Loading
+23 −37
Original line number Diff line number Diff line
@@ -25,13 +25,7 @@ from __future__ import division

import io
import time
import datetime
import sys
import os
import yaml
import copy
import numpy as np
from PIL import Image
import logging

# Used for conversion only
@@ -44,9 +38,6 @@ import picamera.array
# Type hinting
from typing import Tuple

# Threading
import threading

from .base import BaseCamera, CaptureObject
# Richard's fix gain
from .set_picamera_gain import set_analog_gain, set_digital_gain
@@ -70,9 +61,9 @@ CONFIG_KEYS = {
    },
}


# Useful methods

# TODO: Remove this
def fractions_to_floats(value):
    """Deal with horrible, horrible PiCamera fractions"""
    result = value
@@ -85,15 +76,15 @@ def fractions_to_floats(value):
            result = float(value)
    return result

# MAIN CLASS

# MAIN CLASS
class StreamingCamera(BaseCamera):
    """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, picamera_settings: dict=None):
    def __init__(self, config: dict = None):
        global CONFIG_KEYS

        # Run BaseCamera init
@@ -111,11 +102,14 @@ class StreamingCamera(BaseCamera):
        self.set_zoom(1.0)

        # Populate config and settings with all available keys
        self._config.update(CONFIG_KEYS)
        self.config.update(CONFIG_KEYS)

        # Load config dictionary if passed
        if config:
            self.config = config
            self.apply_config(config)

        # Create an empty stream
        self.stream = io.BytesIO()

        # Start streaming
        self.start_worker()
@@ -128,7 +122,7 @@ class StreamingCamera(BaseCamera):
        """Close the Raspberry Pi StreamingCamera."""
        # Run BaseCamera close method
        BaseCamera.close(self)
        # Detatch Pi camera
        # Detach Pi camera
        if self.camera:
            self.camera.close()

@@ -146,8 +140,7 @@ class StreamingCamera(BaseCamera):
        """
        return hasattr(self.camera, "lens_shading_table")

    @property
    def config(self):
    def read_config(self):
        """
        Return config dictionary of the StreamingCamera.
        """
@@ -156,7 +149,7 @@ class StreamingCamera(BaseCamera):
        }

        # PiCamera parameters (obtained directly from PiCamera object)
        for key, _ in self._config['picamera_settings'].items():
        for key, _ in self.config['picamera_settings'].items():
            try:
                value = getattr(self.camera, key)
                value = fractions_to_floats(value)
@@ -166,13 +159,12 @@ class StreamingCamera(BaseCamera):

        # StreamingCamera parameters (obtained from StreamingCamera _config)
        for key in CONFIG_KEYS:
            if (key != 'picamera_settings') and (key in self._config):
                conf_dict[key] = self._config[key]
            if (key != 'picamera_settings') and (key in self.config):
                conf_dict[key] = self.config[key]

        return conf_dict

    @config.setter
    def config(self, config: dict) -> None:
    def apply_config(self, config: dict) -> None:
        """
        Write a config dictionary to the StreamingCamera config.

@@ -202,7 +194,7 @@ class StreamingCamera(BaseCamera):
                # PiCamera parameters (applied directly to PiCamera object)
                if 'picamera_settings' in config:  # If new settings are given
                    for key, value in config['picamera_settings'].items():  # For each given setting
                        self._config['picamera_settings'][key] = None  # Add the key to the list of returned settings
                        self.config['picamera_settings'][key] = None  # Add the key to the list of returned settings
                        logging.debug("Setting parameter {}: {}".format(key, config['picamera_settings'][key]))

                        # Handle special attributes:
@@ -217,7 +209,7 @@ class StreamingCamera(BaseCamera):
                for key, value in config.items():  # For each provided setting
                    if key != 'picamera_settings':  # We already handled this
                        logging.debug("Setting parameter {}: {}".format(key, value))
                        self._config[key] = value
                        self.config[key] = value

                # If stream was paused to update config, unpause
                if paused_stream:
@@ -230,7 +222,7 @@ class StreamingCamera(BaseCamera):

    def set_zoom(self, zoom_value: float = 1.) -> None:
        """
        Change the camera zoom, handling recentering and scaling.
        Change the camera zoom, handling re-centering and scaling.
        """
        with self.lock:
            self.state['zoom_value'] = float(zoom_value)
@@ -275,9 +267,8 @@ class StreamingCamera(BaseCamera):

        Args:
            output (CaptureObject/str): Output object to write data bytes to.
            write_to_file (bool/NoneType): Should the StreamObject write to a file?
            filename (str): Name of the stored file.  Defaults to timestamp.
            fmt (str): Format of the capture.
            quality (int): Video recording quality.

        Returns:
            output_object (str/BytesIO): Target object.
@@ -317,7 +308,7 @@ class StreamingCamera(BaseCamera):
                    until the current recording has stopped.")
                return None

    def stop_recording(self) -> bool:
    def stop_recording(self):
        """Stop the last started video recording on splitter port 2."""
        with self.lock:
            # Stop the camera video recording on port 2
@@ -364,7 +355,7 @@ class StreamingCamera(BaseCamera):
            splitter_port (int): Splitter port to start recording on
            resolution ((int, int)): Resolution to set the camera to, before starting recording. Defaults to `self.config['video_resolution']`.
        """
        # If no stream object exists
        # If stream object was destroyed
        if not hasattr(self, 'stream'):
            self.stream = io.BytesIO()  # Create a stream object

@@ -404,6 +395,7 @@ class StreamingCamera(BaseCamera):
            use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster.
            fmt (str): Format of the capture.
            resize ((int, int)): Resize the captured image.
            bayer (bool): Store raw bayer data in capture
        """

        with self.lock:
@@ -416,7 +408,7 @@ class StreamingCamera(BaseCamera):

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

            # Set resolution and stop stream recording if necesarry
            # Set resolution and stop stream recording if necessary
            if not use_video_port:
                self.stop_stream_recording()

@@ -428,7 +420,7 @@ class StreamingCamera(BaseCamera):
                bayer=(not use_video_port) and bayer,
                use_video_port=use_video_port)

            # Set resolution and start stream recording if necesarry
            # Set resolution and start stream recording if necessary
            if not use_video_port:
                self.start_stream_recording()

@@ -436,7 +428,6 @@ class StreamingCamera(BaseCamera):

    def yuv(
            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.
@@ -473,10 +464,6 @@ class StreamingCamera(BaseCamera):
                if not use_video_port:
                    self.start_stream_recording()

                if rgb:
                    logging.debug("Converting to RGB")
                    return output.rgb_array
                else:
                return output.array

    def array(
@@ -537,7 +524,6 @@ class StreamingCamera(BaseCamera):

        # Update state
        logging.debug("STREAM ACTIVE")
        #self.state['stream_active'] = True

        # While the iterator is not closed
        try:
+50 −34
Original line number Diff line number Diff line
@@ -25,22 +25,29 @@ class Microscope(object):

    Args:
        camera (:py:class:`openflexure_microscope.camera.pi.StreamingCamera`): camera object
        microscope (:py:class:`openflexure_stage.stage.OpenFlexureStage`): stage object
        config (dict): Dictionary of the runtime config to apply to the microscope. Does not automatically apply to camera and stage.
        stage (:py:class:`openflexure_stage.stage.OpenFlexureStage`): stage object
        config_path (str): Path to a microscoperc.yaml runtime-config file
        attach_plugins (bool): Should the microscope attach plugins listen in 'config' immediately.
    """
    def __init__(self, camera: StreamingCamera, stage: OpenFlexureStage, config_path: str=None, attach_plugins: bool=True):
    def __init__(
            self,
            camera: StreamingCamera,
            stage: OpenFlexureStage,
            config_path: str = None,
            attach_plugins: bool = True):

        # Create RC object
        self.rc = OpenflexureConfig(config_path=config_path, expand=True)

        # Attach initial hardware (may be NoneTypes)
        self.camera = None
        self.stage = None
        self.attach(camera, stage)

        # Create a task orchestrator
        self.task = TaskOrchestrator()  #: :py:class:`openflexure_microscope.task.TaskOrchestrator`: Threaded task orchestrator

        # Create plugin mountpoint
        # Create plugin mount-point
        self.plugin = PluginMount(self)  #: :py:class:`openflexure_microscope.plugins.PluginMount`: Mounting point for all microscope plugins

        # Attach plugins
@@ -48,16 +55,15 @@ class Microscope(object):
            self.attach_plugins()
        
        # Set default parameters
        if not 'name' in self.rc.config:
            self.rc.config['name'] = uuid.uuid4().hex
        if 'name' not in self.rc.config:
            self.rc.update({'name': uuid.uuid4().hex})
        
        if not 'fov' in self.rc.config:
            self.rc.config['fov'] = [0, 0]
        if 'fov' not in self.rc.config:
            self.rc.update({'fov': [0, 0]})
        
        # Initialise with an empty composite lock
        self.lock = CompositeLock([])  #: :py:class:`openflexure_microscope.lock.CompositeLock`: Composite lock controlling thread access to multiple pieces of hardware


    def __enter__(self):
        """Create microscope on context enter."""
        return self
@@ -82,14 +88,15 @@ class Microscope(object):

        Args:
            camera (:py:class:`openflexure_microscope.camera.pi.StreamingCamera`): camera object
            microscope (:py:class:`openflexure_stage.stage.OpenFlexureStage`): stage object
            apply_config (bool): Should the microscope config dictionary be applied to the attached hardware?
            stage (:py:class:`openflexure_stage.stage.OpenFlexureStage`): stage object
        """

        logging.debug("Attaching camera...")
        self.camera = camera  #: :py:class:`openflexure_microscope.camera.pi.StreamingCamera`: Picamera object
        if isinstance(self.camera, StreamingCamera):
            logging.info("Attached camera {}".format(camera))
            logging.info("Updating camera settings")
            self.apply_config(self.camera.read_config())
        elif not self.camera:
            logging.info("Attached dummy camera.")
        # If camera has a lock
@@ -173,8 +180,32 @@ class Microscope(object):

        return state

    @property
    def config(self):
    def apply_config(self, config: dict):
        """
        Updates  and applies a config dictionary. Missing parameters will be left untouched.
        """
        # If attached to a camera
        if self.camera:
            # Update camera config
            self.camera.config = config

        # If attached to a stage
        # TODO: Convert stage settings into a config expansion
        if self.stage:
            if 'backlash' in config:
                # Construct backlash array
                backlash = axes_to_array(config['backlash'], ['x', 'y', 'z'], [0, 0, 0])
                self.stage.backlash = backlash

        # Cache config
        self.rc.update(config)
        # Save to disk
        self.rc.save()

    def read_config(self):
        """
        Read an updated config including camera settings.
        """
        # If attached to a camera
        if self.camera:
            # Update camera config params from StreamingCamera object
@@ -196,27 +227,12 @@ class Microscope(object):

        self.rc.update(backlash)

        return self.rc.config
        return self.rc.asdict()

    @property
    def config(self) -> dict:
        return self.read_config()

    @config.setter
    def config(self, config: dict) -> None:
        """
        Technically doesn't SET the config, but rather updates it. Missing parameters
        will be left untouched.
        """
        # If attached to a camera
        if self.camera:
            # Update camera config
            self.camera.config = config
        
        # If attached to a stage
        if self.stage:
            if 'backlash' in config:
                # Construct backlash array
                backlash = axes_to_array(config['backlash'], ['x', 'y', 'z'], [0, 0, 0])
                self.stage.backlash = backlash

        # Cache config
        self.rc.update(config)
        # Save to disk
        self.rc.save()
 No newline at end of file
        self.apply_config(config)