| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- """Handle image reading, writing and plotting plugins.
- To improve performance, plugins are only loaded as needed. As a result, there
- can be multiple states for a given plugin:
- available: Defined in an *ini file located in ``skimage.io._plugins``.
- See also :func:`skimage.io.available_plugins`.
- partial definition: Specified in an *ini file, but not defined in the
- corresponding plugin module. This will raise an error when loaded.
- available but not on this system: Defined in ``skimage.io._plugins``, but
- a dependent library (e.g. Qt, PIL) is not available on your system.
- This will raise an error when loaded.
- loaded: The real availability is determined when it's explicitly loaded,
- either because it's one of the default plugins, or because it's
- loaded explicitly by the user.
- """
- import os.path
- import warnings
- from configparser import ConfigParser
- from glob import glob
- from contextlib import contextmanager
- from .._shared.utils import deprecate_func
- from .collection import imread_collection_wrapper
- __all__ = [
- 'use_plugin',
- 'call_plugin',
- 'plugin_info',
- 'plugin_order',
- 'reset_plugins',
- 'find_available_plugins',
- '_available_plugins',
- ]
- # The plugin store will save a list of *loaded* io functions for each io type
- # (e.g. 'imread', 'imsave', etc.). Plugins are loaded as requested.
- plugin_store = None
- # Dictionary mapping plugin names to a list of functions they provide.
- plugin_provides = {}
- # The module names for the plugins in `skimage.io._plugins`.
- plugin_module_name = {}
- # Meta-data about plugins provided by *.ini files.
- plugin_meta_data = {}
- # For each plugin type, default to the first available plugin as defined by
- # the following preferences.
- preferred_plugins = {
- # Default plugins for all types (overridden by specific types below).
- 'all': ['imageio', 'pil', 'matplotlib'],
- 'imshow': ['matplotlib'],
- 'imshow_collection': ['matplotlib'],
- }
- @contextmanager
- def _hide_plugin_deprecation_warnings():
- """Ignore warnings related to plugin infrastructure deprecation."""
- with warnings.catch_warnings():
- warnings.filterwarnings(
- action="ignore",
- message=".*use `imageio` or other I/O packages directly.*",
- category=FutureWarning,
- module="skimage",
- )
- yield
- def _clear_plugins():
- """Clear the plugin state to the default, i.e., where no plugins are loaded"""
- global plugin_store
- plugin_store = {
- 'imread': [],
- 'imsave': [],
- 'imshow': [],
- 'imread_collection': [],
- 'imshow_collection': [],
- '_app_show': [],
- }
- with _hide_plugin_deprecation_warnings():
- _clear_plugins()
- def _load_preferred_plugins():
- # Load preferred plugin for each io function.
- io_types = ['imsave', 'imshow', 'imread_collection', 'imshow_collection', 'imread']
- for p_type in io_types:
- _set_plugin(p_type, preferred_plugins['all'])
- plugin_types = (p for p in preferred_plugins.keys() if p != 'all')
- for p_type in plugin_types:
- _set_plugin(p_type, preferred_plugins[p_type])
- def _set_plugin(plugin_type, plugin_list):
- for plugin in plugin_list:
- if plugin not in _available_plugins:
- continue
- try:
- use_plugin(plugin, kind=plugin_type)
- break
- except (ImportError, RuntimeError, OSError):
- pass
- @deprecate_func(
- deprecated_version="0.25",
- removed_version="0.27",
- hint="The plugin infrastructure of `skimage.io` is deprecated. "
- "Instead, use `imageio` or other I/O packages directly.",
- )
- def reset_plugins():
- with _hide_plugin_deprecation_warnings():
- _clear_plugins()
- _load_preferred_plugins()
- def _parse_config_file(filename):
- """Return plugin name and meta-data dict from plugin config file."""
- parser = ConfigParser()
- parser.read(filename)
- name = parser.sections()[0]
- meta_data = {}
- for opt in parser.options(name):
- meta_data[opt] = parser.get(name, opt)
- return name, meta_data
- def _scan_plugins():
- """Scan the plugins directory for .ini files and parse them
- to gather plugin meta-data.
- """
- pd = os.path.dirname(__file__)
- config_files = glob(os.path.join(pd, '_plugins', '*.ini'))
- for filename in config_files:
- name, meta_data = _parse_config_file(filename)
- if 'provides' not in meta_data:
- warnings.warn(
- f'file {filename} not recognized as a scikit-image io plugin, skipping.'
- )
- continue
- plugin_meta_data[name] = meta_data
- provides = [s.strip() for s in meta_data['provides'].split(',')]
- valid_provides = [p for p in provides if p in plugin_store]
- for p in provides:
- if p not in plugin_store:
- print(f"Plugin `{name}` wants to provide non-existent `{p}`. Ignoring.")
- # Add plugins that provide 'imread' as provider of 'imread_collection'.
- need_to_add_collection = (
- 'imread_collection' not in valid_provides and 'imread' in valid_provides
- )
- if need_to_add_collection:
- valid_provides.append('imread_collection')
- plugin_provides[name] = valid_provides
- plugin_module_name[name] = os.path.basename(filename)[:-4]
- with _hide_plugin_deprecation_warnings():
- _scan_plugins()
- @deprecate_func(
- deprecated_version="0.25",
- removed_version="0.27",
- hint="The plugin infrastructure of `skimage.io` is deprecated. "
- "Instead, use `imageio` or other I/O packages directly.",
- )
- def find_available_plugins(loaded=False):
- """List available plugins.
- Parameters
- ----------
- loaded : bool
- If True, show only those plugins currently loaded. By default,
- all plugins are shown.
- Returns
- -------
- p : dict
- Dictionary with plugin names as keys and exposed functions as
- values.
- """
- active_plugins = set()
- for plugin_func in plugin_store.values():
- for plugin, func in plugin_func:
- active_plugins.add(plugin)
- d = {}
- for plugin in plugin_provides:
- if not loaded or plugin in active_plugins:
- d[plugin] = [f for f in plugin_provides[plugin] if not f.startswith('_')]
- return d
- with _hide_plugin_deprecation_warnings():
- _available_plugins = find_available_plugins()
- @deprecate_func(
- deprecated_version="0.25",
- removed_version="0.27",
- hint="The plugin infrastructure of `skimage.io` is deprecated. "
- "Instead, use `imageio` or other I/O packages directly.",
- )
- def call_plugin(kind, *args, **kwargs):
- """Find the appropriate plugin of 'kind' and execute it.
- Parameters
- ----------
- kind : {'imshow', 'imsave', 'imread', 'imread_collection'}
- Function to look up.
- plugin : str, optional
- Plugin to load. Defaults to None, in which case the first
- matching plugin is used.
- *args, **kwargs : arguments and keyword arguments
- Passed to the plugin function.
- """
- if kind not in plugin_store:
- raise ValueError(f'Invalid function ({kind}) requested.')
- plugin_funcs = plugin_store[kind]
- if len(plugin_funcs) == 0:
- msg = (
- f"No suitable plugin registered for {kind}.\n\n"
- "You may load I/O plugins with the `skimage.io.use_plugin` "
- "command. A list of all available plugins are shown in the "
- "`skimage.io` docstring."
- )
- raise RuntimeError(msg)
- plugin = kwargs.pop('plugin', None)
- if plugin is None:
- _, func = plugin_funcs[0]
- else:
- _load(plugin)
- try:
- func = [f for (p, f) in plugin_funcs if p == plugin][0]
- except IndexError:
- raise RuntimeError(f'Could not find the plugin "{plugin}" for {kind}.')
- return func(*args, **kwargs)
- @deprecate_func(
- deprecated_version="0.25",
- removed_version="0.27",
- hint="The plugin infrastructure of `skimage.io` is deprecated. "
- "Instead, use `imageio` or other I/O packages directly.",
- )
- def use_plugin(name, kind=None):
- """Set the default plugin for a specified operation. The plugin
- will be loaded if it hasn't been already.
- Parameters
- ----------
- name : str
- Name of plugin. See ``skimage.io.available_plugins`` for a list of available
- plugins.
- kind : {'imsave', 'imread', 'imshow', 'imread_collection', 'imshow_collection'}, optional
- Set the plugin for this function. By default,
- the plugin is set for all functions.
- Examples
- --------
- To use Matplotlib as the default image reader, you would write:
- >>> from skimage import io
- >>> io.use_plugin('matplotlib', 'imread') # doctest: +SKIP
- To see a list of available plugins run ``skimage.io.available_plugins``. Note
- that this lists plugins that are defined, but the full list may not be usable
- if your system does not have the required libraries installed.
- """
- if kind is None:
- kind = plugin_store.keys()
- else:
- if kind not in plugin_provides[name]:
- raise RuntimeError(f"Plugin {name} does not support `{kind}`.")
- if kind == 'imshow':
- kind = [kind, '_app_show']
- else:
- kind = [kind]
- _load(name)
- for k in kind:
- if k not in plugin_store:
- raise RuntimeError(f"'{k}' is not a known plugin function.")
- funcs = plugin_store[k]
- # Shuffle the plugins so that the requested plugin stands first
- # in line
- funcs = [(n, f) for (n, f) in funcs if n == name] + [
- (n, f) for (n, f) in funcs if n != name
- ]
- plugin_store[k] = funcs
- def _inject_imread_collection_if_needed(module):
- """Add `imread_collection` to module if not already present."""
- if not hasattr(module, 'imread_collection') and hasattr(module, 'imread'):
- imread = getattr(module, 'imread')
- func = imread_collection_wrapper(imread)
- setattr(module, 'imread_collection', func)
- @_hide_plugin_deprecation_warnings()
- def _load(plugin):
- """Load the given plugin.
- Parameters
- ----------
- plugin : str
- Name of plugin to load.
- See Also
- --------
- plugins : List of available plugins
- """
- if plugin in find_available_plugins(loaded=True):
- return
- if plugin not in plugin_module_name:
- raise ValueError(f"Plugin {plugin} not found.")
- else:
- modname = plugin_module_name[plugin]
- plugin_module = __import__('skimage.io._plugins.' + modname, fromlist=[modname])
- provides = plugin_provides[plugin]
- for p in provides:
- if p == 'imread_collection':
- _inject_imread_collection_if_needed(plugin_module)
- elif not hasattr(plugin_module, p):
- print(f"Plugin {plugin} does not provide {p} as advertised. Ignoring.")
- continue
- store = plugin_store[p]
- func = getattr(plugin_module, p)
- if (plugin, func) not in store:
- store.append((plugin, func))
- @deprecate_func(
- deprecated_version="0.25",
- removed_version="0.27",
- hint="The plugin infrastructure of `skimage.io` is deprecated. "
- "Instead, use `imageio` or other I/O packages directly.",
- )
- def plugin_info(plugin):
- """Return plugin meta-data.
- Parameters
- ----------
- plugin : str
- Name of plugin.
- Returns
- -------
- m : dict
- Meta data as specified in plugin ``.ini``.
- """
- try:
- return plugin_meta_data[plugin]
- except KeyError:
- raise ValueError(f'No information on plugin "{plugin}"')
- @deprecate_func(
- deprecated_version="0.25",
- removed_version="0.27",
- hint="The plugin infrastructure of `skimage.io` is deprecated. "
- "Instead, use `imageio` or other I/O packages directly.",
- )
- def plugin_order():
- """Return the currently preferred plugin order.
- Returns
- -------
- p : dict
- Dictionary of preferred plugin order, with function name as key and
- plugins (in order of preference) as value.
- """
- p = {}
- for func in plugin_store:
- p[func] = [plugin_name for (plugin_name, f) in plugin_store[func]]
- return p
|