406 lines
12 KiB
Python
406 lines
12 KiB
Python
"""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')
|
|
|
|
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
|