diff --git a/scikits/image/io/_plugins/gtk_plugin.ini b/scikits/image/io/_plugins/gtk_plugin.ini new file mode 100644 index 00000000..554f0403 --- /dev/null +++ b/scikits/image/io/_plugins/gtk_plugin.ini @@ -0,0 +1,3 @@ +[gtk] +provides = imshow, _app_show + diff --git a/scikits/image/io/_plugins/gtk_plugin.py b/scikits/image/io/_plugins/gtk_plugin.py index bafe7865..39cd9a27 100644 --- a/scikits/image/io/_plugins/gtk_plugin.py +++ b/scikits/image/io/_plugins/gtk_plugin.py @@ -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) - - - diff --git a/scikits/image/io/_plugins/matplotlib_plugin.ini b/scikits/image/io/_plugins/matplotlib_plugin.ini new file mode 100644 index 00000000..2116ad47 --- /dev/null +++ b/scikits/image/io/_plugins/matplotlib_plugin.ini @@ -0,0 +1,3 @@ +[matplotlib] +provides = imshow + diff --git a/scikits/image/io/_plugins/matplotlib_plugin.py b/scikits/image/io/_plugins/matplotlib_plugin.py index c922b731..349bbe8d 100644 --- a/scikits/image/io/_plugins/matplotlib_plugin.py +++ b/scikits/image/io/_plugins/matplotlib_plugin.py @@ -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." diff --git a/scikits/image/io/_plugins/pil_plugin.ini b/scikits/image/io/_plugins/pil_plugin.ini new file mode 100644 index 00000000..98413e18 --- /dev/null +++ b/scikits/image/io/_plugins/pil_plugin.ini @@ -0,0 +1,3 @@ +[pil] +provides = imread + diff --git a/scikits/image/io/_plugins/pil_plugin.py b/scikits/image/io/_plugins/pil_plugin.py index d639091f..e50cbe6c 100644 --- a/scikits/image/io/_plugins/pil_plugin.py +++ b/scikits/image/io/_plugins/pil_plugin.py @@ -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) diff --git a/scikits/image/io/_plugins/plugin.py b/scikits/image/io/_plugins/plugin.py index cd887f53..9a24e669 100644 --- a/scikits/image/io/_plugins/plugin.py +++ b/scikits/image/io/_plugins/plugin.py @@ -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)) + diff --git a/scikits/image/io/_plugins/qt_plugin.ini b/scikits/image/io/_plugins/qt_plugin.ini new file mode 100644 index 00000000..f27e64e3 --- /dev/null +++ b/scikits/image/io/_plugins/qt_plugin.ini @@ -0,0 +1,3 @@ +[qt] +provides = imshow, _app_show + diff --git a/scikits/image/io/_plugins/qt_plugin.py b/scikits/image/io/_plugins/qt_plugin.py index 54fe9b8e..032d6409 100644 --- a/scikits/image/io/_plugins/qt_plugin.py +++ b/scikits/image/io/_plugins/qt_plugin.py @@ -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) diff --git a/scikits/image/io/_plugins/test_plugin.ini b/scikits/image/io/_plugins/test_plugin.ini new file mode 100644 index 00000000..0dfbb133 --- /dev/null +++ b/scikits/image/io/_plugins/test_plugin.ini @@ -0,0 +1,4 @@ +[test] +provides = imsave, imshow, imread + + diff --git a/scikits/image/io/_plugins/test_plugin.py b/scikits/image/io/_plugins/test_plugin.py index bd160829..975d55d9 100644 --- a/scikits/image/io/_plugins/test_plugin.py +++ b/scikits/image/io/_plugins/test_plugin.py @@ -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) diff --git a/scikits/image/io/io.py b/scikits/image/io/io.py index aaf826a4..66d3b82b 100644 --- a/scikits/image/io/io.py +++ b/scikits/image/io/io.py @@ -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') diff --git a/scikits/image/io/tests/test_plugin.py b/scikits/image/io/tests/test_plugin.py index b6d3c550..3ed62869 100644 --- a/scikits/image/io/tests/test_plugin.py +++ b/scikits/image/io/tests/test_plugin.py @@ -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()