Completely overhauled config system
This commit is contained in:
parent
cc343e2a8c
commit
9b30d1d38f
1 changed files with 92 additions and 84 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue