Describe plugins using .ini files, so that we don't need to import them to know their capabilities.

This commit is contained in:
Stefan van der Walt
2009-11-03 00:09:31 +02:00
parent 0f2474a9b9
commit 447397dfa3
13 changed files with 176 additions and 165 deletions
+3
View File
@@ -0,0 +1,3 @@
[gtk]
provides = imshow, _app_show
+2 -9
View File
@@ -1,5 +1,4 @@
from util import prepare_for_display, window_manager, GuiLockError
import plugin
try:
# we try to aquire the gui lock first
@@ -41,21 +40,15 @@ else:
def destroy(self, widget, data=None):
self.mgr.remove_window(self)
def gtk_imshow(arr):
def imshow(arr):
arr = prepare_for_display(arr)
iw = ImageWindow(arr, window_manager)
iw.show()
def gtk_show():
def _app_show():
if window_manager.has_windows():
window_manager.register_callback(gtk.main_quit)
gtk.main()
else:
print 'no images to display'
plugin.register('gtk', show=gtk_imshow, appshow=gtk_show)
@@ -0,0 +1,3 @@
[matplotlib]
provides = imshow
@@ -1,8 +1,4 @@
import plugin
try:
import matplotlib.pyplot as plt
except ImportError, e:
print e
else:
plugin.register('matplotlib', show=plt.imshow, save=plt.imsave)
from matplotlib.pyplot import imshow, imsave
except ImportError:
print "Could not import Matplotlib."
+3
View File
@@ -0,0 +1,3 @@
[pil]
provides = imread
+35 -40
View File
@@ -5,50 +5,45 @@ import numpy as np
try:
from PIL import Image
has_pil = True
except ImportError:
has_pil = False
print 'Could not load Python Imaging Library'
else:
def imread(fname, as_grey=False, dtype=None):
"""Load an image from file.
def imread(fname, as_grey=False, dtype=None):
"""Load an image from file.
"""
im = Image.open(fname)
if im.mode == 'P':
if palette_is_grayscale(im):
im = im.convert('L')
else:
im = im.convert('RGB')
"""
im = Image.open(fname)
if im.mode == 'P':
if palette_is_grayscale(im):
im = im.convert('L')
else:
im = im.convert('RGB')
if as_grey and not \
im.mode in ('1', 'L', 'I', 'F', 'I;16', 'I;16L', 'I;16B'):
im = im.convert('F')
if as_grey and not \
im.mode in ('1', 'L', 'I', 'F', 'I;16', 'I;16L', 'I;16B'):
im = im.convert('F')
return np.array(im, dtype=dtype)
return np.array(im, dtype=dtype)
def palette_is_grayscale(pil_image):
"""Return True if PIL image in palette mode is grayscale.
def palette_is_grayscale(pil_image):
"""Return True if PIL image in palette mode is grayscale.
Parameters
----------
pil_image : PIL image
PIL Image that is in Palette mode.
Parameters
----------
pil_image : PIL image
PIL Image that is in Palette mode.
Returns
-------
is_grayscale : bool
True if all colors in image palette are gray.
"""
assert pil_image.mode == 'P'
# get palette as an array with R, G, B columns
palette = np.asarray(pil_image.getpalette()).reshape((256, 3))
# Not all palette colors are used; unused colors have junk values.
start, stop = pil_image.getextrema()
valid_palette = palette[start:stop]
# Image is grayscale if channel differences (R - G and G - B)
# are all zero.
return np.allclose(np.diff(valid_palette), 0)
if has_pil:
plugin.register('pil', read=imread)
Returns
-------
is_grayscale : bool
True if all colors in image palette are gray.
"""
assert pil_image.mode == 'P'
# get palette as an array with R, G, B columns
palette = np.asarray(pil_image.getpalette()).reshape((256, 3))
# Not all palette colors are used; unused colors have junk values.
start, stop = pil_image.getextrema()
valid_palette = palette[start:stop]
# Image is grayscale if channel differences (R - G and G - B)
# are all zero.
return np.allclose(np.diff(valid_palette), 0)
+67 -54
View File
@@ -2,45 +2,45 @@
"""
__all__ = ['register', 'use', 'load', 'available', 'call']
__all__ = ['use', 'load', 'available', 'call']
import warnings
from ConfigParser import ConfigParser
import os.path
from glob import glob
plugin_store = {'read': [],
'save': [],
'show': [],
'appshow': []}
plugin_store = {'imread': [],
'imsave': [],
'imshow': [],
'_app_show': []}
def register(name, **kwds):
"""Register an image I/O plugin.
plugin_provides = {}
plugin_module_name = {}
Parameters
----------
name : str
Name of this plugin.
read : callable, optional
Function with signature
``read(filename, as_grey=False, dtype=None, **plugin_specific_args)``
that reads images.
save : callable, optional
Function with signature
``write(filename, arr, **plugin_specific_args)``
that writes an image to disk.
show : callable, optional
Function with signature
``show(X, **plugin_specific_args)`` that displays an image.
def _scan_plugins():
"""Scan the plugins directory for .ini files and parse them
to gather plugin meta-data.
"""
for kind in kwds:
if kind not in plugin_store.keys():
raise ValueError('Tried to register invalid plugin method.')
pd = os.path.dirname(__file__)
ini = glob(os.path.join(pd, '*.ini'))
func = kwds[kind]
if not callable(func):
raise ValueError('Can only register functions as plugins.')
for f in ini:
cp = ConfigParser()
cp.read(f)
name = cp.sections()[0]
provides = [s.strip() for s in cp.get(name, 'provides').split(',')]
valid_provides = [p for p in provides if p in plugin_store]
plugin_store[kind].insert(0, (name, func))
for p in provides:
if not p in plugin_store:
print "Plugin `%s` wants to provide non-existent `%s`." \
" Ignoring." % (name, p)
plugin_provides[name] = valid_provides
plugin_module_name[name] = os.path.basename(f)[:-4]
_scan_plugins()
def call(kind, *args, **kwargs):
"""Find the appropriate plugin of 'kind' and execute it.
@@ -103,10 +103,16 @@ def use(name, kind=None):
kind = plugin_store.keys()
else:
kind = [kind]
if not kind in plugin_provides[name]:
raise RuntimeError("Plugin %s does not support `%s`." % \
(name, kind))
if not name in available(loaded=True):
raise RuntimeError("No plugin '%s' has been loaded." % name)
for k in kind:
if not k in plugin_store:
raise RuntimeError("Could not find plugin for '%s'" % k)
raise RuntimeError("'%s' is not a known plugin function." % k)
funcs = plugin_store[k]
@@ -115,36 +121,29 @@ def use(name, kind=None):
funcs = [(n, f) for (n, f) in funcs if n == name] + \
[(n, f) for (n, f) in funcs if n != name]
n, f = funcs[0]
if not n == name:
warnings.warn(RuntimeWarning('Could not set plugin "%s" for'
' function "%s".' % (name, k)))
plugin_store[k] = funcs
def available(kind=None):
def available(loaded=False):
"""List available plugins.
Parameters
----------
kind : {'show', 'save', 'read'}, optional
Display the plugin list for the given function type. If not
specified, return a dictionary with the plugins for all
functions.
loaded : bool
If True, show only those plugins currently loaded. By default,
all plugins are shown.
"""
if kind is None:
kind = plugin_store.keys()
else:
kind = [kind]
from copy import deepcopy
active_plugins = set()
for k in plugin_store:
for plugin, fname in plugin_store[k]:
active_plugins.add(plugin)
d = {}
for k in kind:
if not k in plugin_store:
raise ValueError('No function "%s" exists in the plugin registry.'
% kind)
d[k] = [name for (name, func) in plugin_store[k]]
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
@@ -161,7 +160,21 @@ def load(plugin):
plugins : List of available plugins
"""
try:
__import__('scikits.image.io._plugins.' + plugin + "_plugin")
except ImportError:
raise ValueError('Plugin %s not found.' % plugin)
if not plugin in plugin_module_name:
raise ValueError("Plugin %s not found." % plugin)
else:
modname = plugin + "_plugin"
plugin_module = __import__('scikits.image.io._plugins.' + modname,
fromlist=[modname])
provides = plugin_provides[plugin]
for p in provides:
if not hasattr(plugin_module, p):
print "Plugin %s does not provide %s as advertised. Ignoring." % \
(plugin, p)
else:
store = plugin_store[p]
func = getattr(plugin_module, p)
if not func in store:
store.insert(0, (plugin, func))
+3
View File
@@ -0,0 +1,3 @@
[qt]
provides = imshow, _app_show
+2 -6
View File
@@ -1,10 +1,8 @@
import plugin
from util import prepare_for_display, window_manager, GuiLockError
import numpy as np
import sys
try:
# We try to aquire the gui lock first or else the gui import might
# trample another GUI's PyOS_InputHook.
@@ -47,7 +45,7 @@ else:
# references to it
self.mgr.remove_window(self)
def qt_imshow(arr):
def imshow(arr):
global app
if not app:
@@ -58,11 +56,9 @@ else:
iw = ImageWindow(arr, window_manager)
iw.show()
def qt_show():
def _app_show():
global app
if app and window_manager.has_windows():
app.exec_()
else:
print 'No images to show. See `imshow`.'
plugin.register('qt', show=qt_imshow, appshow=qt_show)
@@ -0,0 +1,4 @@
[test]
provides = imsave, imshow, imread
+10 -4
View File
@@ -1,6 +1,12 @@
import plugin
def imread(fname, as_grey=False, dtype=None):
assert fname == 'test.png'
assert as_grey == True
assert dtype == 'i4'
def save(fname, arr):
return fname, arr
def imsave(fname, arr):
assert fname == 'test.png'
assert arr == [1, 2, 3]
plugin.register('test', save=save)
def imshow(arr, plugin_arg=None):
assert arr == [1, 2, 3]
assert plugin_arg == (1, 2)