From 9bec0ef8883c37d9871229df68035615f65ad6a4 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 31 Oct 2009 21:10:27 +0200 Subject: [PATCH] Plugin framework for image I/O. --- scikits/image/io/__init__.py | 6 +- scikits/image/io/io.py | 107 ++++++++++++++++++++++++++ scikits/image/io/matplotlib_plugin.py | 8 ++ scikits/image/io/pil_imread.py | 39 ---------- scikits/image/io/pil_plugin.py | 23 ++++++ scikits/image/io/plugin.py | 37 +++++++++ 6 files changed, 179 insertions(+), 41 deletions(-) create mode 100644 scikits/image/io/io.py create mode 100644 scikits/image/io/matplotlib_plugin.py delete mode 100644 scikits/image/io/pil_imread.py create mode 100644 scikits/image/io/pil_plugin.py create mode 100644 scikits/image/io/plugin.py diff --git a/scikits/image/io/__init__.py b/scikits/image/io/__init__.py index 3ef9e1e1..3107025f 100644 --- a/scikits/image/io/__init__.py +++ b/scikits/image/io/__init__.py @@ -1,7 +1,9 @@ """Utilities to read and write images in various formats.""" +import pil_plugin +import matplotlib_plugin -from pil_imread import * +from io import * +from plugin import register as register_plugin from sift import * from collection import * - diff --git a/scikits/image/io/io.py b/scikits/image/io/io.py new file mode 100644 index 00000000..e5a86a6f --- /dev/null +++ b/scikits/image/io/io.py @@ -0,0 +1,107 @@ +from scikits.image.io.plugin import plugin_store + +def _call_plugin(kind, *args, **kwargs): + if not kind in plugin_store: + raise ValueError('Invalid function (%s) requested.' % kind) + + plugin_funcs = plugin_store[kind] + if len(plugin_funcs) == 0: + raise RuntimeError('No suitable plugin registered for %s' % kind) + + plugin = kwargs.pop('plugin', None) + if plugin is None: + _, func = plugin_funcs[0] + else: + try: + func = [f for (p,f) in plugin_funcs if p == plugin][0] + except IndexError: + raise RuntimeError('Could not find the plugin "%s" for %s.' % \ + (plugin, kind)) + + return func(*args, **kwargs) + +def imread(fname, as_grey=False, dtype=None, plugin=None, flatten=None, + **plugin_args): + """Load an image from file. + + Parameters + ---------- + fname : string + Image file name, e.g. ``test.jpg``. + as_grey : bool + If True, convert color images to grey-scale. If `dtype` is not given, + converted color images are returned as 32-bit float images. + Images that are already in grey-scale format are not converted. + dtype : dtype, optional + NumPy data-type specifier. If given, the returned image has this type. + If None (default), the data-type is determined automatically. + plugin : str + Name of plugin to use. By default, the different plugins are + tried (starting with the Python Imaging Library) until a suitable + candidate is found. + + Other Parameters + ---------------- + flatten : bool + Backward compatible keyword, superseded by `as_grey`. + + Returns + ------- + img_array : ndarray + The different colour bands/channels are stored in the + third dimension, such that a grey-image is MxN, an + RGB-image MxNx3 and an RGBA-image MxNx4. + + Other parameters + ---------------- + plugin_args : keywords + Passed to the given plugin. + + """ + # Backward compatibility + if flatten is not None: + as_grey = flatten + + _call_plugin('read', as_grey, dtype, plugin=plugin, **plugin_args) + +def imsave(fname, arr, plugin=None, **plugin_args): + """Save an image to file. + + Parameters + ---------- + fname : str + Target filename. + arr : ndarray of shape (M,N) or (M,N,3) or (M,N,4) + Image data. + plugin : str + Name of plugin to use. By default, the different plugins are + tried (starting with the Python Imaging Library) until a suitable + candidate is found. + + Other parameters + ---------------- + plugin_args : keywords + Passed to the given plugin. + + """ + _call_plugin('save', fname, arr, plugin=plugin, **plugin_args) + +def imshow(arr, plugin=None, **plugin_args): + """Display an image. + + Parameters + ---------- + arr : ndarray + Image data. + plugin : str + Name of plugin to use. By default, the different plugins are + tried (starting with the Python Imaging Library) until a suitable + candidate is found. + + Other parameters + ---------------- + plugin_args : keywords + Passed to the given plugin. + + """ + _call_plugin('show', arr, plugin=None, **plugin_args) diff --git a/scikits/image/io/matplotlib_plugin.py b/scikits/image/io/matplotlib_plugin.py new file mode 100644 index 00000000..3d748fe5 --- /dev/null +++ b/scikits/image/io/matplotlib_plugin.py @@ -0,0 +1,8 @@ +import plugin + +try: + import matplotlib.pyplot as plt +except ImportError: + pass +else: + plugin.register('matplotlib', show=plt.imshow, save=plt.imsave) diff --git a/scikits/image/io/pil_imread.py b/scikits/image/io/pil_imread.py deleted file mode 100644 index 421f45c7..00000000 --- a/scikits/image/io/pil_imread.py +++ /dev/null @@ -1,39 +0,0 @@ -__all__ = ['imread'] - -import numpy as np - -def imread(fname, flatten=False, dtype=None): - """Load an image from file. - - Parameters - ---------- - fname : string - Image file name, e.g. ``test.jpg``. - flatten : bool - If True, convert color images to grey-scale. If `dtype` is not given, - converted color images are returned as 32-bit float images. - Images that are already in grey-scale format are not converted. - dtype : dtype, optional - NumPy data-type specifier. If given, the returned image has this type. - If None (default), the data-type is determined automatically. - - Returns - ------- - img_array : ndarray - The different colour bands/channels are stored in the - third dimension, such that a grey-image is MxN, an - RGB-image MxNx3 and an RGBA-image MxNx4. - - """ - try: - from PIL import Image - except ImportError: - raise ImportError("Could not import the Python Imaging Library (PIL)" - " required to load image files. Please refer to" - " http://pypi.python.org/pypi/PIL/ for installation" - " instructions.") - - im = Image.open(fname) - if flatten 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) diff --git a/scikits/image/io/pil_plugin.py b/scikits/image/io/pil_plugin.py new file mode 100644 index 00000000..2854baa3 --- /dev/null +++ b/scikits/image/io/pil_plugin.py @@ -0,0 +1,23 @@ +__all__ = ['imread'] + +import numpy as np +import plugin + +try: + from PIL import Image + has_pil = True +except ImportError: + has_pil = False + +def imread(fname, as_grey=False, dtype=None): + """Load an image from file. + + """ + im = Image.open(fname) + 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) + +if has_pil: + plugin.register('PIL', read=imread) diff --git a/scikits/image/io/plugin.py b/scikits/image/io/plugin.py new file mode 100644 index 00000000..3557e2d8 --- /dev/null +++ b/scikits/image/io/plugin.py @@ -0,0 +1,37 @@ +"""Handle image reading, writing and plotting plugins. + +""" + +plugin_store = {'read': [], + 'save': [], + 'show': []} + +def register(name, **kwds): + """Register an image I/O plugin. + + 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. + + """ + for kind in kwds: + if kind not in plugin_store.keys(): + raise ValueError('Tried to register invalid plugin method.') + + func = kwds[kind] + if not callable(func): + raise ValueError('Can only register functions as plugins.') + + plugin_store[kind].append((name, func))