Commit 0f47d0dc authored by Joel Collins's avatar Joel Collins
Browse files

Started building Microscope plugin system

parent c87878a7
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -8,6 +8,8 @@ import numpy as np
from openflexure_stage import OpenFlexureStage
from .camera.pi import StreamingCamera

from .plugins import PluginMount


class Microscope(object):
    """
@@ -22,6 +24,9 @@ class Microscope(object):
    def __init__(self, camera: StreamingCamera, stage: OpenFlexureStage):
        self.attach(camera, stage)

        # Create plugin mountpoint
        self.plugin = PluginMount(self)

    def __enter__(self):
        """Create microscope on context enter."""
        return self
+116 −0
Original line number Diff line number Diff line
import importlib
import os
import warnings
import logging

MAIN_MODULE = '__init__'
HERE = os.path.abspath(os.path.dirname(__file__))
DEFAULT_PLUGIN_PATH = os.path.join(HERE, 'default')


def search_plugin_dirs(plugin_dirs, include_default=True):
    """
    Search through, and load from, a list of plugin directories.

    Args:
        plugin_dirs (list): List of strings of plugin directories.
        include_default (bool): Also load plugins from the module default directory (DEFAULT_PLUGIN_PATH)
    """
    global DEFAULT_PLUGIN_PATH

    if include_default:  # If including default plugins
        plugin_dirs.append(DEFAULT_PLUGIN_PATH)  # Add default directory to the search paths

    logging.debug(plugin_dirs)

    plugins = []  # List of loaded plugins
    for plugin_dir in plugin_dirs:  # For each plugin directory
        logging.debug("Searching {}".format(plugin_dir))
        plugins.extend(find_plugins(plugin_dir))  # Find plugin folders, and load into list

    return plugins


def find_plugins(plugin_dir):
    """
    Find all plugins residing within a directory

    Args:
        plugin_dir (str): String of directory to be searched
    """
    plugins = []
    plugins_folders = os.listdir(plugin_dir)

    loader_details = (
        importlib.machinery.SourceFileLoader,
        importlib.machinery.SOURCE_SUFFIXES
    )

    for i in plugins_folders:
        plugin_folder = os.path.join(plugin_dir, i)
        logging.info(plugin_folder)

        if not os.path.isdir(plugin_folder):
            continue
        if not MAIN_MODULE + '.py' in os.listdir(plugin_folder):
            continue

        module_spec = importlib.machinery.FileFinder(plugin_folder, loader_details).find_spec(MAIN_MODULE)

        plugins.append(module_spec)
    return plugins


def load_plugin(module_spec):
    """
    Load a source file from a given spec.

    Args:
        module_spec: Module spec of module to be returned
    """
    module = importlib.util.module_from_spec(module_spec)
    module_spec.loader.exec_module(module)
    return module


class PluginMount(object):
    """
    A mount-point for all loaded plugins. Attaches to a Microscope object.

    Args:
        parent (:py:class:`openflexure_microscope.microscope.Microscope`): The parent Microscope object to attach to.
    """
    def __init__(self, parent):
        self.parent = parent
        print("Creating plugin mount")

    def attach(self, plugin_module):
        if not hasattr(plugin_module, 'PLUGINS') or not isinstance(plugin_module.PLUGINS, dict):
            raise Exception("No falid PLUGINS dictionary found in {}".format(plugin_module))

        for plugin_name, plugin_class in plugin_module.PLUGINS.items():

            plugin_object = plugin_class()
            if hasattr(self, plugin_name):
                warnings.warn("A plugin named {} has already been loaded. Skipping {}.".format(plugin_name, plugin_class))
            else:
                setattr(self, plugin_name, plugin_object)

                # Grant plugin access to the hardware
                assert(isinstance(plugin_object, MicroscopePlugin))
                plugin_object.camera = self.parent.camera
                plugin_object.stage = self.parent.stage

                print("Adding plugin: {}".format(plugin_name))


class MicroscopePlugin():
    """
    Parent class for all microscope plugins.

    Initially only defines an empty object for camera and stage. All plugins
    must be an instance of this class to successfully attach to PluginMount.
    """
    def __init__(self):
        self.camera = None  #: :py:class:`openflexure_microscope.camera.pi.StreamingCamera`: Picamera object
        self.stage = None  #: :py:class:`openflexure_stage.stage.OpenFlexureStage`: OpenFlexure stage object