Completely overhauled config system

This commit is contained in:
Joel Collins 2019-06-05 16:03:58 +01:00
parent cc343e2a8c
commit 9b30d1d38f

View file

@ -14,7 +14,7 @@ from .plugins import PluginMount
from .utilities import axes_to_array from .utilities import axes_to_array
from .task import TaskOrchestrator from .task import TaskOrchestrator
from .lock import CompositeLock from .lock import CompositeLock
from .config import OpenflexureConfig from .config import OpenflexureSettingsFile, settings_to_json
class Microscope: class Microscope:
@ -24,7 +24,7 @@ class Microscope:
The camera and stage should already be initialised, and passed as arguments. The camera and stage should already be initialised, and passed as arguments.
Attributes: Attributes:
rc (:py:class:`openflexure_microscope.config.OpenflexureConfig`): Runtime-config object, settings_file (:py:class:`openflexure_microscope.config.OpenflexureSettingsFile`): Runtime-config object,
automatically created if None. automatically created if None.
lock (:py:class:`openflexure_microscope.lock.CompositeLock`): Composite lock controlling thread access lock (:py:class:`openflexure_microscope.lock.CompositeLock`): Composite lock controlling thread access
to multiple pieces of hardware. to multiple pieces of hardware.
@ -36,31 +36,28 @@ class Microscope:
""" """
def __init__(self): def __init__(self):
# Create runtime config object # Initial attributes
self.rc = OpenflexureConfig(expand=True) self.id = uuid.uuid4().hex
self.name = self.id
self.fov = [0, 0]
self.plugin_maps = []
self.camera = None
self.stage = None
# Initialise with an empty composite lock # Initialise with an empty composite lock
self.lock = CompositeLock([]) self.lock = CompositeLock([])
# Attach dummy hardware initially
# TODO: Handle better. Maybe monad?
self.attach(None, None)
# Create a task orchestrator # Create a task orchestrator
self.task = TaskOrchestrator() self.task = TaskOrchestrator()
# Create plugin mount-point # Attach to a settings file
self.settings_file = OpenflexureSettingsFile(expand=True)
# Apply settings loaded from file
self.apply_config(self.settings_file.load())
# Create plugin mount-point and attach plugins from maps
self.plugin = PluginMount(self) self.plugin = PluginMount(self)
self.attach_plugins(self.plugin_maps)
# Attach plugins
self.attach_plugins()
# Set default parameters
if 'name' not in self.rc.config:
self.rc.write({'name': uuid.uuid4().hex})
if 'fov' not in self.rc.config:
self.rc.write({'fov': [0, 0]})
def __enter__(self): def __enter__(self):
"""Create microscope on context enter.""" """Create microscope on context enter."""
@ -90,59 +87,48 @@ class Microscope:
camera (:py:class:`openflexure_microscope.camera.pi.StreamingCamera`): camera object camera (:py:class:`openflexure_microscope.camera.pi.StreamingCamera`): camera object
stage (:py:class:`openflexure_stage.stage.OpenFlexureStage`): stage object stage (:py:class:`openflexure_stage.stage.OpenFlexureStage`): stage object
""" """
settings_full = self.read_config()
# TODO: Actually attach dummy hardware! # TODO: Actually attach dummy hardware!
# TODO: Use some form of Mabye monad to say arguments may be actual hardware, else dummy? # Maybe even attach dummy hardware at __init__, and replace with real hardware if it exists
logging.debug("Attaching camera...") logging.debug("Attaching camera...")
self.camera = camera #: :py:class:`openflexure_microscope.camera.pi.StreamingCamera`: Picamera object self.camera = camera #: :py:class:`openflexure_microscope.camera.pi.StreamingCamera`: Picamera object
if isinstance(self.camera, StreamingCamera): if not self.camera:
logging.info("No camera attached.")
else:
logging.info("Attached camera {}".format(camera)) logging.info("Attached camera {}".format(camera))
logging.info("Updating camera settings")
self.camera.apply_config(self.rc.read()) if hasattr(self.camera, 'lock'): # If camera has a lock
logging.info("Reading complete camera settings") logging.info("Attaching {} to composite lock.".format(self.camera.lock))
self.write_config(self.camera.read_config()) # Add the lock to the microscope composite lock
elif not self.camera: self.lock.locks.append(self.camera.lock)
logging.info("Attached dummy camera.")
# If camera has a lock
if hasattr(self.camera, 'lock'):
logging.info("Attaching {} to composite lock.".format(self.camera.lock))
# Add the lock to the microscope composite lock
self.lock.locks.append(self.camera.lock)
logging.debug("Attaching stage...") logging.debug("Attaching stage...")
self.stage = stage #: :py:class:`openflexure_stage.stage.OpenFlexureStage`: OpenFlexure stage object self.stage = stage #: :py:class:`openflexure_stage.stage.OpenFlexureStage`: OpenFlexure stage object
if isinstance(self.stage, OpenFlexureStage): # If a stage object has been attached if not self.stage:
logging.info("No stage attached.")
else:
logging.info("Attached stage {}".format(stage)) logging.info("Attached stage {}".format(stage))
self.stage.backlash = np.zeros(3, dtype=np.int)
elif not self.stage:
logging.info("Attached dummy stage.")
# If stage object has a lock
if hasattr(self.stage, 'lock'):
logging.info("Attaching lock {} to composite lock.".format(self.stage.lock))
# Add the lock to the microscope composite lock
self.lock.locks.append(self.stage.lock)
def attach_plugin_maps(self, plugin_maps): if hasattr(self.stage, 'lock'): # If stage object has a lock
""" logging.info("Attaching lock {} to composite lock.".format(self.stage.lock))
Attach plugins from a list of plugin map strings # Add the lock to the microscope composite lock
self.lock.locks.append(self.stage.lock)
Args: logging.info("Reapplying settings to newly attached devices")
plugin_maps (list): List of strings of plugin maps. self.apply_config(settings_full)
"""
logging.debug("Attaching plugins...")
for plugin_map in plugin_maps: def attach_plugins(self, plugin_maps: list):
self.plugin.attach(plugin_map)
logging.debug("Plugins attached.")
def attach_plugins(self):
""" """
Automatically search for plugin maps in self.config, and attach. Automatically search for plugin maps in self.config, and attach.
""" """
if 'plugins' in self.rc.config: if plugin_maps:
self.attach_plugin_maps(self.rc.config['plugins']) for plugin_map in plugin_maps:
self.plugin.attach(plugin_map)
else: else:
logging.warning("No plugins specified in microscope_settings.yaml. Skipping.") logging.warning("No plugins specified. Skipping.")
def reload_plugins(self): def reload_plugins(self):
""" """
@ -151,7 +137,7 @@ class Microscope:
logging.info("Tearing down existing PluginMount...") logging.info("Tearing down existing PluginMount...")
self.plugin = PluginMount(self) self.plugin = PluginMount(self)
logging.info("Repopulating PluginMount...") logging.info("Repopulating PluginMount...")
self.attach_plugins() self.attach_plugins(self.plugin_maps)
# Create unified state # Create unified state
@property @property
@ -169,65 +155,87 @@ class Microscope:
} }
return state return state
def write_config(self, config: dict): def apply_config(self, config: dict):
""" """
Writes and applies a config dictionary. Missing parameters will be left untouched. Applies a config dictionary. Missing parameters will be left untouched.
""" """
# TODO: Have stage object handle apply_config, like camera does logging.debug("Microscope: Applying config: {}".format(config))
# If attached to a camera # If attached to a camera
if self.camera: if ('camera_settings' in config) and self.camera:
# Update camera config # Update camera config
self.camera.apply_config(config) self.camera.apply_config(config['camera_settings'])
# If attached to a stage # If attached to a stage
# TODO: Convert stage settings into a config expansion if ('stage_settings' in config) and self.stage:
if self.stage:
if 'backlash' in config: if 'backlash' in config:
# Construct backlash array # Construct backlash array
backlash = axes_to_array(config['backlash'], ['x', 'y', 'z'], [0, 0, 0]) backlash = axes_to_array(config['backlash'], ['x', 'y', 'z'], [0, 0, 0])
self.stage.backlash = backlash self.stage.backlash = backlash
# Cache config # Todo: tidy up with some loopy goodness
self.rc.write(config) if 'id' in config:
self.id = config['id']
if 'name' in config:
self.name = config['name']
if 'fov' in config:
self.fov = config['fov']
if 'plugins' in config:
self.plugin_maps = config['plugins']
def read_config(self, json_safe=False): def read_config(self, json_safe=False):
""" """
Read an updated config including camera settings. Get an updated settings dictionary.
Reads current attributes and properties from connected hardware,
then merges those with the currently saved settings.
This is to ensure that settings for currently disconnected hardware
don't get removed from the settings file.
""" """
settings_current = {
'id': self.id,
'name': self.name,
'fov': self.fov,
'plugins': self.plugin_maps
}
# If attached to a camera # If attached to a camera
if self.camera: if self.camera:
# Update camera config params from StreamingCamera object settings_current_camera = self.camera.read_config()
self.rc.write(self.camera.read_config()) settings_current['camera_settings'] = settings_current_camera
# If attached to a stage # If attached to a stage
# TODO: Offload to stage object
if self.stage: if self.stage:
if hasattr(self.stage.backlash, 'tolist'): if hasattr(self.stage.backlash, 'tolist'):
backlash = self.stage.backlash.tolist() backlash = self.stage.backlash.tolist()
else: else:
backlash = self.stage.backlash backlash = self.stage.backlash
else:
backlash = [0, 0, 0]
backlash = { settings_current_stage = {
'backlash': { 'backlash': {
'x': backlash[0], 'x': backlash[0],
'y': backlash[1], 'y': backlash[1],
'z': backlash[2], 'z': backlash[2],
}
} }
}
self.rc.write(backlash) settings_current['stage_settings'] = settings_current_stage
return self.rc.read(json_safe=json_safe) settings_full = self.settings_file.merge(settings_current)
if json_safe:
settings_full = settings_to_json(settings_full, clean_keys=True)
return settings_full
def save_config(self, backup: bool = True): def save_config(self, backup: bool = True):
""" """
Saves the current runtime-config back to disk Merges the current settings back to disk
""" """
# Get any changed device settings self.settings_file.save(self.read_config(), backup=True)
self.read_config(json_safe=False)
# Save to disk
self.rc.save(backup=backup)
@property @property
def config(self) -> dict: def config(self) -> dict: