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)
+19 -13
View File
@@ -44,7 +44,7 @@ def imread(fname, as_grey=False, dtype=None, plugin=None, flatten=None,
if flatten is not None:
as_grey = flatten
return call_plugin('read', fname, as_grey=as_grey, dtype=dtype,
return call_plugin('imread', fname, as_grey=as_grey, dtype=dtype,
plugin=plugin, **plugin_args)
def imsave(fname, arr, plugin=None, **plugin_args):
@@ -67,7 +67,7 @@ def imsave(fname, arr, plugin=None, **plugin_args):
Passed to the given plugin.
"""
return call_plugin('save', fname, arr, plugin=plugin, **plugin_args)
return call_plugin('imsave', fname, arr, plugin=plugin, **plugin_args)
def imshow(arr, plugin=None, **plugin_args):
"""Display an image.
@@ -87,17 +87,23 @@ def imshow(arr, plugin=None, **plugin_args):
Passed to the given plugin.
"""
return call_plugin('show', arr, plugin=plugin, **plugin_args)
return call_plugin('imshow', arr, plugin=plugin, **plugin_args)
def show():
'''Launches the event loop of the current gui plugin,
and displays all pending images. This is required,
when using imshow() from a non-interactive script.
Simply make all the calls to imshow() to queue up as many
images as you need, then call show(). After the
last window is closed, the gui event loop will exit,
and you script will continue execution.
'''Display pending images.
If this is called from the interactive terminal,
it will block until all windows are closed.'''
return call_plugin('appshow')
Launch the event loop of the current gui plugin, and display all
pending images, queued via `imshow`. This is required when using
`imshow` from non-interactive scripts.
A call to `show` will block execution of code until all windows
have been closed.
Examples
--------
>>> for i in range(4):
... imshow(np.random.random((50, 50))
>>> show()
'''
return call_plugin('_app_show')
+22 -32
View File
@@ -5,55 +5,45 @@ from scikits.image.io._plugins import plugin
from copy import deepcopy
def read(fname, as_grey=False, dtype=None):
assert fname == 'test.png'
assert as_grey == True
assert dtype == 'i4'
def save(fname, arr):
assert fname == 'test.png'
assert arr == [1, 2, 3]
def show(arr, plugin_arg=None):
assert arr == [1, 2, 3]
assert plugin_arg == (1, 2)
def show_other(arr):
return "other"
def setup_module(self):
self.backup_plugin_store = deepcopy(plugin.plugin_store)
plugin.register('testcase', read=read, save=save, show=show)
plugin.register('other', show=show_other)
plugin.load('test')
def teardown_module(self):
plugin.plugin_store = self.backup_plugin_store
class TestPlugin:
def test_read(self):
io.imread('test.png', as_grey=True, dtype='i4', plugin='testcase')
io.imread('test.png', as_grey=True, dtype='i4', plugin='test')
def test_save(self):
io.imsave('test.png', [1, 2, 3], plugin='testcase')
io.imsave('test.png', [1, 2, 3], plugin='test')
def test_show(self):
io.imshow([1, 2, 3], plugin_arg=(1, 2), plugin='testcase')
io.imshow([1, 2, 3], plugin_arg=(1, 2), plugin='test')
def test_use(self):
plugin.use('other', 'show')
assert io.imshow(None) == 'other'
plugin.use('test')
@raises(RuntimeError)
def test_failed_use(self):
plugin.use('asd')
def test_use_priority(self):
plugin.use('pil')
plug, func = plugin.plugin_store['imread'][0]
print plugin.plugin_store
assert_equal(plug, 'pil')
plugin.use('test')
plug, func = plugin.plugin_store['imread'][0]
print plugin.plugin_store
assert_equal(plug, 'test')
def test_available(self):
plugin.use('other', 'show')
d = plugin.available('show')
assert d['show'][0] == 'other'
def test_load(self):
plugin.load('test')
fname, arr = io.imsave('outfile', [1, 2, 3])
assert_equal(fname, 'outfile')
assert_equal(arr, [1, 2, 3])
assert_equal(plugin.available('save')['save'][0], 'test')
assert 'qt' in io.plugins()
assert 'test' in io.plugins(loaded=True)
if __name__ == "__main__":
run_module_suite()