From b8c30bad4699e13a2c9a3eb055e11cf04990bae3 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 21:15:04 -0600 Subject: [PATCH 01/35] Move image stack to its own module --- skimage/io/__init__.py | 1 + skimage/io/_image_stack.py | 35 +++++++++++++++++++++++++++++++++++ skimage/io/_io.py | 38 ++++---------------------------------- 3 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 skimage/io/_image_stack.py diff --git a/skimage/io/__init__.py b/skimage/io/__init__.py index 5e701a51..a2553c54 100644 --- a/skimage/io/__init__.py +++ b/skimage/io/__init__.py @@ -14,6 +14,7 @@ from .sift import * from .collection import * from ._io import * +from ._image_stack import * from .video import * diff --git a/skimage/io/_image_stack.py b/skimage/io/_image_stack.py new file mode 100644 index 00000000..ca9896d5 --- /dev/null +++ b/skimage/io/_image_stack.py @@ -0,0 +1,35 @@ +import numpy as np + + +__all__ = ['image_stack', 'push', 'pop'] + + +# Shared image queue +image_stack = [] + + +def push(img): + """Push an image onto the shared image stack. + + Parameters + ---------- + img : ndarray + Image to push. + + """ + if not isinstance(img, np.ndarray): + raise ValueError("Can only push ndarrays to the image stack.") + + image_stack.append(img) + + +def pop(): + """Pop an image from the shared image stack. + + Returns + ------- + img : ndarray + Image popped from the stack. + + """ + return image_stack.pop() diff --git a/skimage/io/_io.py b/skimage/io/_io.py index eb704a6f..ae05ea77 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -1,10 +1,7 @@ -__all__ = ['Image', 'imread', 'imread_collection', 'imsave', 'imshow', 'show', - 'push', 'pop'] - try: - from urllib.request import urlopen + from urllib.request import urlopen # Python 3 except ImportError: - from urllib2 import urlopen + from urllib2 import urlopen # Python 2 import os import re @@ -19,8 +16,8 @@ from skimage.color import rgb2grey -# Shared image queue -_image_stack = [] +__all__ = ['Image', 'imread', 'imread_collection', 'imsave', 'imshow', 'show'] + URL_REGEX = re.compile(r'http://|https://|ftp://|file://|file:\\') @@ -77,33 +74,6 @@ class Image(np.ndarray): return return_str -def push(img): - """Push an image onto the shared image stack. - - Parameters - ---------- - img : ndarray - Image to push. - - """ - if not isinstance(img, np.ndarray): - raise ValueError("Can only push ndarrays to the image stack.") - - _image_stack.append(img) - - -def pop(): - """Pop an image from the shared image stack. - - Returns - ------- - img : ndarray - Image popped from the stack. - - """ - return _image_stack.pop() - - def imread(fname, as_grey=False, plugin=None, flatten=None, **plugin_args): """Load an image from file. From 312151b481aaa0342dd830ed402fde0a96cfbd2d Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 21:59:46 -0600 Subject: [PATCH 02/35] Move __init__ functions to submodules. And rename functions so we don't have to alias imports. --- skimage/io/__init__.py | 40 ++--------- skimage/io/_io.py | 2 +- skimage/io/_plugins/plugin.py | 113 ++++++++++++++++++++------------ skimage/io/tests/test_plugin.py | 22 +++---- 4 files changed, 89 insertions(+), 88 deletions(-) diff --git a/skimage/io/__init__.py b/skimage/io/__init__.py index a2553c54..e19c6fe0 100644 --- a/skimage/io/__init__.py +++ b/skimage/io/__init__.py @@ -4,12 +4,7 @@ The following plug-ins are available: """ -from ._plugins import use as use_plugin -from ._plugins import available as plugins -from ._plugins import info as plugin_info -from ._plugins import configuration as plugin_order -from ._plugins import reset_plugins as _reset_plugins - +from ._plugins import * from .sift import * from .collection import * @@ -18,34 +13,9 @@ from ._image_stack import * from .video import * -available_plugins = plugins() +reset_plugins() -def _load_preferred_plugins(): - # Load preferred plugin for each io function. - io_funcs = ['imsave', 'imshow', 'imread_collection', 'imread'] - preferred_plugins = ['matplotlib', 'pil', 'qt', 'freeimage', 'null'] - for func in io_funcs: - for plugin in preferred_plugins: - if plugin not in available_plugins: - continue - try: - use_plugin(plugin, kind=func) - break - except (ImportError, RuntimeError, OSError): - pass - - # Use PIL as the default imread plugin, since matplotlib (1.2.x) - # is buggy (flips PNGs around, returns bytes as floats, etc.) - try: - use_plugin('pil', 'imread') - except ImportError: - pass - -def reset_plugins(): - _reset_plugins() - _load_preferred_plugins() - def _update_doc(doc): """Add a list of plugins to the module docstring, formatted as a ReStructuredText table. @@ -53,7 +23,9 @@ def _update_doc(doc): """ from textwrap import wrap - info = [(p, plugin_info(p)) for p in plugins() if not p == 'test'] + info = [(p, plugin_info(p)) for p in available_plugins if not p == 'test'] + print('test:', available_plugins, info) + col_1_len = max([len(n) for (n, _) in info]) wrap_len = 73 @@ -75,5 +47,3 @@ def _update_doc(doc): return doc __doc__ = _update_doc(__doc__) - -reset_plugins() diff --git a/skimage/io/_io.py b/skimage/io/_io.py index ae05ea77..aa342c33 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -11,7 +11,7 @@ from io import BytesIO import numpy as np import six -from skimage.io._plugins import call as call_plugin +from skimage.io._plugins import call_plugin from skimage.color import rgb2grey diff --git a/skimage/io/_plugins/plugin.py b/skimage/io/_plugins/plugin.py index 5e04f320..dd6d4527 100644 --- a/skimage/io/_plugins/plugin.py +++ b/skimage/io/_plugins/plugin.py @@ -2,25 +2,26 @@ """ -__all__ = ['use', 'available', 'call', 'info', 'configuration', 'reset_plugins'] - try: - from configparser import ConfigParser + from configparser import ConfigParser # Python 3 except ImportError: - from ConfigParser import ConfigParser + from ConfigParser import ConfigParser # Python 2 import os.path from glob import glob -plugin_store = None +__all__ = ['use_plugin', 'call_plugin', 'plugin_info', 'plugin_order', + 'reset_plugins', 'find_available_plugins', 'available_plugins'] + +plugin_store = None plugin_provides = {} plugin_module_name = {} plugin_meta_data = {} -def reset_plugins(): +def _clear_plugins(): """Clear the plugin state to the default, i.e., where no plugins are loaded """ @@ -30,8 +31,34 @@ def reset_plugins(): 'imshow': [], 'imread_collection': [], '_app_show': []} +_clear_plugins() -reset_plugins() + +def _load_preferred_plugins(): + # Load preferred plugin for each io function. + io_funcs = ['imsave', 'imshow', 'imread_collection', 'imread'] + preferred_plugins = ['matplotlib', 'pil', 'qt', 'freeimage', 'null'] + for func in io_funcs: + for plugin in preferred_plugins: + if plugin not in available_plugins: + continue + try: + use_plugin(plugin, kind=func) + break + except (ImportError, RuntimeError, OSError): + pass + + # Use PIL as the default imread plugin, since matplotlib (1.2.x) + # is buggy (flips PNGs around, returns bytes as floats, etc.) + try: + use_plugin('pil', 'imread') + except ImportError: + pass + + +def reset_plugins(): + _clear_plugins() + _load_preferred_plugins() def _scan_plugins(): @@ -66,7 +93,40 @@ def _scan_plugins(): _scan_plugins() -def call(kind, *args, **kwargs): +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 + + +available_plugins = find_available_plugins() + + +def call_plugin(kind, *args, **kwargs): """Find the appropriate plugin of 'kind' and execute it. Parameters @@ -105,7 +165,7 @@ command. A list of all available plugins can be found using return func(*args, **kwargs) -def use(name, kind=None): +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. @@ -158,35 +218,6 @@ def use(name, kind=None): plugin_store[k] = funcs -def available(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 - def _load(plugin): """Load the given plugin. @@ -201,7 +232,7 @@ def _load(plugin): plugins : List of available plugins """ - if plugin in available(loaded=True): + if plugin in find_available_plugins(loaded=True): return if not plugin in plugin_module_name: raise ValueError("Plugin %s not found." % plugin) @@ -222,7 +253,7 @@ def _load(plugin): store.append((plugin, func)) -def info(plugin): +def plugin_info(plugin): """Return plugin meta-data. Parameters @@ -242,7 +273,7 @@ def info(plugin): raise ValueError('No information on plugin "%s"' % plugin) -def configuration(): +def plugin_order(): """Return the currently preferred plugin order. Returns diff --git a/skimage/io/tests/test_plugin.py b/skimage/io/tests/test_plugin.py index 5d1febe4..82d8e662 100644 --- a/skimage/io/tests/test_plugin.py +++ b/skimage/io/tests/test_plugin.py @@ -20,7 +20,7 @@ except RuntimeError: def setup_module(self): - plugin.use('test') # see ../_plugins/test_plugin.py + plugin.use_plugin('test') # see ../_plugins/test_plugin.py def teardown_module(self): @@ -41,37 +41,37 @@ class TestPlugin: io.imread_collection('*.png', conserve_memory=False, plugin='test') def test_use(self): - plugin.use('test') - plugin.use('test', 'imshow') + plugin.use_plugin('test') + plugin.use_plugin('test', 'imshow') @raises(ValueError) def test_failed_use(self): - plugin.use('asd') + plugin.use_plugin('asd') @skipif(not PIL_available and not FI_available) def test_use_priority(self): - plugin.use(priority_plugin) + plugin.use_plugin(priority_plugin) plug, func = plugin.plugin_store['imread'][0] assert_equal(plug, priority_plugin) - plugin.use('test') + plugin.use_plugin('test') plug, func = plugin.plugin_store['imread'][0] assert_equal(plug, 'test') @skipif(not PIL_available) def test_use_priority_with_func(self): - plugin.use('pil') + plugin.use_plugin('pil') plug, func = plugin.plugin_store['imread'][0] assert_equal(plug, 'pil') - plugin.use('test', 'imread') + plugin.use_plugin('test', 'imread') plug, func = plugin.plugin_store['imread'][0] assert_equal(plug, 'test') plug, func = plugin.plugin_store['imsave'][0] assert_equal(plug, 'pil') - plugin.use('test') + plugin.use_plugin('test') plug, func = plugin.plugin_store['imsave'][0] assert_equal(plug, 'test') @@ -81,8 +81,8 @@ class TestPlugin: assert 'test' in p['imread'] def test_available(self): - assert 'qt' in io.plugins() - assert 'test' in io.plugins(loaded=True) + assert 'qt' in io.available_plugins + assert 'test' in io.find_available_plugins(loaded=True) if __name__ == "__main__": run_module_suite() From 03388d49cdc403c830e03bfae93a663c9c87dd1e Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 22:02:08 -0600 Subject: [PATCH 03/35] Use explicit imports --- skimage/io/tests/test_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/io/tests/test_plugin.py b/skimage/io/tests/test_plugin.py index 82d8e662..30a44414 100644 --- a/skimage/io/tests/test_plugin.py +++ b/skimage/io/tests/test_plugin.py @@ -1,4 +1,4 @@ -from numpy.testing import * +from numpy.testing import assert_equal, raises from skimage import io from skimage.io._plugins import plugin From 79587eaf48aa256e52b3cc162e39162f6209bfad Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 22:04:21 -0600 Subject: [PATCH 04/35] Remove debugging statement --- skimage/io/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/skimage/io/__init__.py b/skimage/io/__init__.py index e19c6fe0..5fb2602b 100644 --- a/skimage/io/__init__.py +++ b/skimage/io/__init__.py @@ -24,7 +24,6 @@ def _update_doc(doc): from textwrap import wrap info = [(p, plugin_info(p)) for p in available_plugins if not p == 'test'] - print('test:', available_plugins, info) col_1_len = max([len(n) for (n, _) in info]) From c820699cd85a17de608e6518c8e13cd6d880a663 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 22:04:37 -0600 Subject: [PATCH 05/35] Remove unnecessary class --- skimage/io/tests/test_plugin.py | 91 +++++++++++++++++---------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/skimage/io/tests/test_plugin.py b/skimage/io/tests/test_plugin.py index 30a44414..d91e927c 100644 --- a/skimage/io/tests/test_plugin.py +++ b/skimage/io/tests/test_plugin.py @@ -19,70 +19,71 @@ except RuntimeError: FI_available = False -def setup_module(self): +def setup_module(): plugin.use_plugin('test') # see ../_plugins/test_plugin.py -def teardown_module(self): +def teardown_module(): io.reset_plugins() -class TestPlugin: - def test_read(self): - io.imread('test.png', as_grey=True, dtype='i4', plugin='test') +def test_read(): + io.imread('test.png', as_grey=True, dtype='i4', plugin='test') - def test_save(self): - io.imsave('test.png', [1, 2, 3], plugin='test') +def test_save(): + io.imsave('test.png', [1, 2, 3], plugin='test') - def test_show(self): - io.imshow([1, 2, 3], plugin_arg=(1, 2), plugin='test') +def test_show(): + io.imshow([1, 2, 3], plugin_arg=(1, 2), plugin='test') - def test_collection(self): - io.imread_collection('*.png', conserve_memory=False, plugin='test') +def test_collection(): + io.imread_collection('*.png', conserve_memory=False, plugin='test') - def test_use(self): - plugin.use_plugin('test') - plugin.use_plugin('test', 'imshow') +def test_use(): + plugin.use_plugin('test') + plugin.use_plugin('test', 'imshow') - @raises(ValueError) - def test_failed_use(self): - plugin.use_plugin('asd') +@raises(ValueError) +def test_failed_use(): + plugin.use_plugin('asd') - @skipif(not PIL_available and not FI_available) - def test_use_priority(self): - plugin.use_plugin(priority_plugin) - plug, func = plugin.plugin_store['imread'][0] - assert_equal(plug, priority_plugin) +@skipif(not PIL_available and not FI_available) +def test_use_priority(): + plugin.use_plugin(priority_plugin) + plug, func = plugin.plugin_store['imread'][0] + assert_equal(plug, priority_plugin) - plugin.use_plugin('test') - plug, func = plugin.plugin_store['imread'][0] - assert_equal(plug, 'test') + plugin.use_plugin('test') + plug, func = plugin.plugin_store['imread'][0] + assert_equal(plug, 'test') - @skipif(not PIL_available) - def test_use_priority_with_func(self): - plugin.use_plugin('pil') - plug, func = plugin.plugin_store['imread'][0] - assert_equal(plug, 'pil') +@skipif(not PIL_available) +def test_use_priority_with_func(): + plugin.use_plugin('pil') + plug, func = plugin.plugin_store['imread'][0] + assert_equal(plug, 'pil') - plugin.use_plugin('test', 'imread') - plug, func = plugin.plugin_store['imread'][0] - assert_equal(plug, 'test') + plugin.use_plugin('test', 'imread') + plug, func = plugin.plugin_store['imread'][0] + assert_equal(plug, 'test') - plug, func = plugin.plugin_store['imsave'][0] - assert_equal(plug, 'pil') + plug, func = plugin.plugin_store['imsave'][0] + assert_equal(plug, 'pil') - plugin.use_plugin('test') - plug, func = plugin.plugin_store['imsave'][0] - assert_equal(plug, 'test') + plugin.use_plugin('test') + plug, func = plugin.plugin_store['imsave'][0] + assert_equal(plug, 'test') - def test_plugin_order(self): - p = io.plugin_order() - assert 'imread' in p - assert 'test' in p['imread'] +def test_plugin_order(): + p = io.plugin_order() + assert 'imread' in p + assert 'test' in p['imread'] + +def test_available(): + assert 'qt' in io.available_plugins + assert 'test' in io.find_available_plugins(loaded=True) - def test_available(self): - assert 'qt' in io.available_plugins - assert 'test' in io.find_available_plugins(loaded=True) if __name__ == "__main__": + from numpy.testing import run_module_suite run_module_suite() From 552f24be8e0afdeffd3eaa32d435c140a57edaaf Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 22:07:44 -0600 Subject: [PATCH 06/35] Remove unnecessary initialization --- skimage/io/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/io/__init__.py b/skimage/io/__init__.py index 5fb2602b..350ca202 100644 --- a/skimage/io/__init__.py +++ b/skimage/io/__init__.py @@ -1,4 +1,4 @@ -__doc__ = """Utilities to read and write images in various formats. +"""Utilities to read and write images in various formats. The following plug-ins are available: From f8ea4266082fba1210be270d6ae7607717591978 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 22:27:34 -0600 Subject: [PATCH 07/35] Refactor io doc building code --- skimage/io/__init__.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/skimage/io/__init__.py b/skimage/io/__init__.py index 350ca202..ed6df313 100644 --- a/skimage/io/__init__.py +++ b/skimage/io/__init__.py @@ -15,6 +15,12 @@ from .video import * reset_plugins() +WRAP_LEN = 73 + + +def _separator(char, lengths): + return [char * separator_length for separator_length in lengths] + def _update_doc(doc): """Add a list of plugins to the module docstring, formatted as @@ -23,24 +29,21 @@ def _update_doc(doc): """ from textwrap import wrap - info = [(p, plugin_info(p)) for p in available_plugins if not p == 'test'] + info = [(p, plugin_info(p).get('description', 'no description')) + for p in available_plugins if not p == 'test'] col_1_len = max([len(n) for (n, _) in info]) - - wrap_len = 73 - col_2_len = wrap_len - 1 - col_1_len + col_2_len = WRAP_LEN - 1 - col_1_len # Insert table header - info.insert(0, ('=' * col_1_len, {'description': '=' * col_2_len})) - info.insert(1, ('Plugin', {'description': 'Description'})) - info.insert(2, ('-' * col_1_len, {'description': '-' * col_2_len})) - info.append(('=' * col_1_len, {'description': '=' * col_2_len})) + info.insert(0, _separator('=', (col_1_len, col_2_len))) + info.insert(1, ('Plugin', 'Description')) + info.insert(2, _separator('-', (col_1_len, col_2_len))) + info.append(_separator('-', (col_1_len, col_2_len))) - for (name, meta_data) in info: - wrapped_descr = wrap(meta_data.get('description', ''), - col_2_len) - doc += "%s %s\n" % (name.ljust(col_1_len), - '\n'.join(wrapped_descr)) + for (name, plugin_description) in info: + wrapped_descr = wrap(plugin_description, col_2_len) + doc += "%s %s\n" % (name.ljust(col_1_len), '\n'.join(wrapped_descr)) doc = doc.strip() return doc From a23e31b5539d6d8db268451600ebf71293c02d15 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 22:33:42 -0600 Subject: [PATCH 08/35] Factor out table formatting code. --- skimage/io/__init__.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/skimage/io/__init__.py b/skimage/io/__init__.py index ed6df313..31d22a3f 100644 --- a/skimage/io/__init__.py +++ b/skimage/io/__init__.py @@ -22,6 +22,14 @@ def _separator(char, lengths): return [char * separator_length for separator_length in lengths] +def _format_plugin_info_table(info_table, column_lengths): + """Add separators and column titles to plugin info table.""" + info_table.insert(0, _separator('=', column_lengths)) + info_table.insert(1, ('Plugin', 'Description')) + info_table.insert(2, _separator('-', column_lengths)) + info_table.append(_separator('-', column_lengths)) + + def _update_doc(doc): """Add a list of plugins to the module docstring, formatted as a ReStructuredText table. @@ -29,21 +37,17 @@ def _update_doc(doc): """ from textwrap import wrap - info = [(p, plugin_info(p).get('description', 'no description')) - for p in available_plugins if not p == 'test'] + info_table = [(p, plugin_info(p).get('description', 'no description')) + for p in available_plugins if not p == 'test'] - col_1_len = max([len(n) for (n, _) in info]) - col_2_len = WRAP_LEN - 1 - col_1_len + name_length = max([len(n) for (n, _) in info_table]) + description_length = WRAP_LEN - 1 - name_length + column_lengths = [name_length, description_length] + _format_plugin_info_table(info_table, column_lengths) - # Insert table header - info.insert(0, _separator('=', (col_1_len, col_2_len))) - info.insert(1, ('Plugin', 'Description')) - info.insert(2, _separator('-', (col_1_len, col_2_len))) - info.append(_separator('-', (col_1_len, col_2_len))) - - for (name, plugin_description) in info: - wrapped_descr = wrap(plugin_description, col_2_len) - doc += "%s %s\n" % (name.ljust(col_1_len), '\n'.join(wrapped_descr)) + for (name, plugin_description) in info_table: + wrapped_descr = wrap(plugin_description, description_length) + doc += "%s %s\n" % (name.ljust(name_length), '\n'.join(wrapped_descr)) doc = doc.strip() return doc From 1d399617575dea5d4a9ac931182a7ac53998f997 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 22:35:32 -0600 Subject: [PATCH 09/35] Minor rename --- skimage/io/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/skimage/io/__init__.py b/skimage/io/__init__.py index 31d22a3f..82a345c9 100644 --- a/skimage/io/__init__.py +++ b/skimage/io/__init__.py @@ -46,10 +46,11 @@ def _update_doc(doc): _format_plugin_info_table(info_table, column_lengths) for (name, plugin_description) in info_table: - wrapped_descr = wrap(plugin_description, description_length) - doc += "%s %s\n" % (name.ljust(name_length), '\n'.join(wrapped_descr)) + description = wrap(plugin_description, description_length) + doc += "%s %s\n" % (name.ljust(name_length), '\n'.join(description)) doc = doc.strip() return doc + __doc__ = _update_doc(__doc__) From 77ff0615d95d333feb905c15fe46b90316a7549e Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 18 Nov 2013 22:44:18 -0600 Subject: [PATCH 10/35] Fix wrapping of plugin descriptions --- skimage/io/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/skimage/io/__init__.py b/skimage/io/__init__.py index 82a345c9..02800fbd 100644 --- a/skimage/io/__init__.py +++ b/skimage/io/__init__.py @@ -46,8 +46,11 @@ def _update_doc(doc): _format_plugin_info_table(info_table, column_lengths) for (name, plugin_description) in info_table: - description = wrap(plugin_description, description_length) - doc += "%s %s\n" % (name.ljust(name_length), '\n'.join(description)) + description_lines = wrap(plugin_description, description_length) + name_column = [name] + name_column.extend(['' for _ in range(len(description_lines) - 1)]) + for name, description in zip(name_column, description_lines): + doc += "%s %s\n" % (name.ljust(name_length), description) doc = doc.strip() return doc From 8a2e88241ee31c1bdd650d8b561545f968dc64ef Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 2 Dec 2013 23:13:05 -0600 Subject: [PATCH 11/35] Factor out config-file parsing for clarity --- skimage/io/_plugins/plugin.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/skimage/io/_plugins/plugin.py b/skimage/io/_plugins/plugin.py index dd6d4527..b6e43b0f 100644 --- a/skimage/io/_plugins/plugin.py +++ b/skimage/io/_plugins/plugin.py @@ -61,25 +61,32 @@ def reset_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__) - ini = glob(os.path.join(pd, '*.ini')) + config_files = glob(os.path.join(pd, '*.ini')) - for f in ini: - cp = ConfigParser() - cp.read(f) - name = cp.sections()[0] - - meta_data = {} - for opt in cp.options(name): - meta_data[opt] = cp.get(name, opt) + for filename in config_files: + name, meta_data = _parse_config_file(filename) plugin_meta_data[name] = meta_data - provides = [s.strip() for s in cp.get(name, 'provides').split(',')] + 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: @@ -88,7 +95,7 @@ def _scan_plugins(): " Ignoring." % (name, p)) plugin_provides[name] = valid_provides - plugin_module_name[name] = os.path.basename(f)[:-4] + plugin_module_name[name] = os.path.basename(filename)[:-4] _scan_plugins() From 41e62fa0875afed3100d298cdcd7e76648f3ecad Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 2 Dec 2013 23:36:12 -0600 Subject: [PATCH 12/35] Add comment for clarification --- skimage/io/_plugins/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/skimage/io/_plugins/plugin.py b/skimage/io/_plugins/plugin.py index b6e43b0f..75b2f9ed 100644 --- a/skimage/io/_plugins/plugin.py +++ b/skimage/io/_plugins/plugin.py @@ -15,7 +15,10 @@ __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 + plugin_provides = {} plugin_module_name = {} plugin_meta_data = {} @@ -225,7 +228,6 @@ def use_plugin(name, kind=None): plugin_store[k] = funcs - def _load(plugin): """Load the given plugin. From 86abc7c9704b75242f51392189b1d33afec34041 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 3 Dec 2013 22:16:09 -0600 Subject: [PATCH 13/35] Factor out url handling for unified file/url behavior --- skimage/io/_io.py | 29 +++-------------------------- skimage/io/util.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 skimage/io/util.py diff --git a/skimage/io/_io.py b/skimage/io/_io.py index aa342c33..ce718742 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -1,11 +1,3 @@ -try: - from urllib.request import urlopen # Python 3 -except ImportError: - from urllib2 import urlopen # Python 2 - -import os -import re -import tempfile from io import BytesIO import numpy as np @@ -13,21 +5,13 @@ import six from skimage.io._plugins import call_plugin from skimage.color import rgb2grey - +from skimage._shared import six +from .util import file_or_url_context __all__ = ['Image', 'imread', 'imread_collection', 'imsave', 'imshow', 'show'] -URL_REGEX = re.compile(r'http://|https://|ftp://|file://|file:\\') - - -def is_url(filename): - """Return True if string is an http or ftp path.""" - return (isinstance(filename, six.string_types) and - URL_REGEX.match(filename) is not None) - - class Image(np.ndarray): """Class representing Image data. @@ -110,14 +94,7 @@ def imread(fname, as_grey=False, plugin=None, flatten=None, if flatten is not None: as_grey = flatten - if is_url(fname): - _, ext = os.path.splitext(fname) - with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as f: - u = urlopen(fname) - f.write(u.read()) - img = call_plugin('imread', f.name, plugin=plugin, **plugin_args) - os.remove(f.name) - else: + with file_or_url_context(fname) as fname: img = call_plugin('imread', fname, plugin=plugin, **plugin_args) if as_grey and getattr(img, 'ndim', 0) >= 3: diff --git a/skimage/io/util.py b/skimage/io/util.py new file mode 100644 index 00000000..06f5a5b5 --- /dev/null +++ b/skimage/io/util.py @@ -0,0 +1,36 @@ +try: + from urllib.request import urlopen # Python 3 +except ImportError: + from urllib2 import urlopen # Python 2 + +import os +import re +import tempfile +from contextlib import contextmanager + +from skimage._shared import six + + +URL_REGEX = re.compile(r'http://|https://|ftp://|file://|file:\\') + + +def is_url(filename): + """Return True if string is an http or ftp path.""" + return (isinstance(filename, six.string_types) and + URL_REGEX.match(filename) is not None) + + +@contextmanager +def file_or_url_context(resource_name): + """Yield name of file from the given resource (i.e. file or url).""" + if is_url(resource_name): + _, ext = os.path.splitext(resource_name) + with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as f: + u = urlopen(resource_name) + f.write(u.read()) + try: + yield f.name + finally: + os.remove(f.name) + else: + yield resource_name From 318781bca1973f34d3a6b00b5b9253cef5190f58 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 3 Dec 2013 22:27:00 -0600 Subject: [PATCH 14/35] Add test that error gets raised when no plugin available --- skimage/io/tests/test_io.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/skimage/io/tests/test_io.py b/skimage/io/tests/test_io.py index 8049860a..51b9a336 100644 --- a/skimage/io/tests/test_io.py +++ b/skimage/io/tests/test_io.py @@ -4,6 +4,7 @@ from numpy.testing import assert_array_equal, raises, run_module_suite import numpy as np import skimage.io as io +from skimage.io._plugins.plugin import plugin_store from skimage import data_dir @@ -28,5 +29,13 @@ def test_imread_url(): assert image.shape == (512, 512) +@raises(RuntimeError) +def test_imread_no_plugin(): + # tweak data path so that file URI works on both unix and windows. + image_path = os.path.join(data_dir, 'lena.png') + plugin_store['imread'] = [] + io.imread(image_path) + + if __name__ == "__main__": run_module_suite() From 90f1e791d80f2b195c7e2818c200a7016ea78afc Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 3 Dec 2013 22:30:56 -0600 Subject: [PATCH 15/35] PEP8 --- skimage/io/_plugins/plugin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skimage/io/_plugins/plugin.py b/skimage/io/_plugins/plugin.py index 75b2f9ed..ec8ed7c9 100644 --- a/skimage/io/_plugins/plugin.py +++ b/skimage/io/_plugins/plugin.py @@ -155,11 +155,11 @@ def call_plugin(kind, *args, **kwargs): plugin_funcs = plugin_store[kind] if len(plugin_funcs) == 0: - raise RuntimeError('''No suitable plugin registered for %s. - -You may load I/O plugins with the `skimage.io.use_plugin` -command. A list of all available plugins can be found using -`skimage.io.plugins()`.''' % kind) + msg = ("No suitable plugin registered for %s.\n\n" + "You may load I/O plugins with the `skimage.io.use_plugin` " + "command. A list of all available plugins can be found using " + "`skimage.io.plugins()`.") + raise RuntimeError(msg % kind) plugin = kwargs.pop('plugin', None) if plugin is None: From db45127fa93b495337df3e39a1c4622fce297ada Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 3 Dec 2013 23:16:50 -0600 Subject: [PATCH 16/35] Fix test so it doesn't have side-effects --- skimage/io/tests/test_io.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/skimage/io/tests/test_io.py b/skimage/io/tests/test_io.py index 51b9a336..6b74ae34 100644 --- a/skimage/io/tests/test_io.py +++ b/skimage/io/tests/test_io.py @@ -33,8 +33,12 @@ def test_imread_url(): def test_imread_no_plugin(): # tweak data path so that file URI works on both unix and windows. image_path = os.path.join(data_dir, 'lena.png') + plugins = plugin_store['imread'] plugin_store['imread'] = [] - io.imread(image_path) + try: + io.imread(image_path) + finally: + plugin_store['imread'] = plugins if __name__ == "__main__": From e04066de7ef7c0f1a337fa390ce2e5714b6d5952 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 3 Dec 2013 23:24:16 -0600 Subject: [PATCH 17/35] Change InheritedConfig so a key is contained if a parent key is contained --- skimage/io/inherited_config.py | 159 +++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 skimage/io/inherited_config.py diff --git a/skimage/io/inherited_config.py b/skimage/io/inherited_config.py new file mode 100644 index 00000000..6b989b66 --- /dev/null +++ b/skimage/io/inherited_config.py @@ -0,0 +1,159 @@ +class InheritedConfig(dict): + """Configuration dictionary where non-existent keys can inherit values. + + This class allows you to define parameter names that can match exactly, but + if it doesn't, parameter names will be searched based on key inheritance. + For example, the key 'size.text' will default to 'size'. + + Note that indexing into the dictionary will raise an error if it doesn't + match exactly, while `InheritedConfig.get` will look up values based on + inheritance. + + Parameters + ---------- + config_values : dict or list of (key, value) pairs + Default values for a configuration, where keys are the parameter names + and values are the associated value. + cascade_map : dict + Dictionary defining cascading defaults. If a parameter name is not + found, indexing `cascade_map` with the parameter name will return + the parameter to look for. + kwargs : dict + Keyword arguments for initializing dict. + """ + + _separator = '.' + + def __init__(self, config_values=None, **kwargs): + assert 'config_values' not in kwargs + + if config_values is None: + config_values = {} + + super(InheritedConfig, self).__init__(config_values, **kwargs) + + def get(self, key, default=None, _prev=None): + """Return best matching config value for `key`. + + Get value from configuration. The search for `key` is in the following + order: + + - `self` (Value in global configuration) + - `default` + - Alternate key specified by `self.cascade_map` + + This method supports the pattern commonly used for optional keyword + arguments to a function. For example:: + + >>> def print_value(key, **kwargs): + ... print kwargs.get(key, 0) + >>> print_value('size') + 0 + >>> print_value('size', size=1) + 1 + + Instead, you would create a config class and write:: + + >>> config = InheritedConfig(size=0) + >>> def print_value(key, **kwargs): + ... print kwargs.get(key, config.get(key)) + >>> print_value('size') + 0 + >>> print_value('size', size=1) + 1 + >>> print_value('non-existent') + None + >>> print_value('size.text') + 0 + + See examples below for a demonstration of the cascading of + configuration names. + + Parameters + ---------- + key : str + Name of config value you want. + default : object + Default value if `key` doesn't exist in instance. + + Examples + -------- + >>> config = InheritedConfig(size=0) + >>> config.get('size') + 0 + >>> top_choice={'size': 1} + >>> top_choice.get('size', config.get('size')) + 1 + >>> config.get('non-existent', 'unknown') + 'unknown' + >>> config.get('size.text') + 0 + >>> config.get('size.text', 2) + 2 + >>> top_choice.get('size', config.get('size.text')) + 1 + """ + if key in self.keys(): + return self[key] + elif default is not None: + return default + elif self._separator in key: + return self.get(self._parent(key)) + else: + return None + + def _parent(self, key): + """Return parent key.""" + parts = key.split(self._separator) + return self._separator.join(parts[:-1]) + + def __contains__(self, key): + if key in self.keys(): + return True + elif self._separator in key: + return self.__contains__(self._parent(key)) + else: + return False + + +def test_get_non_existent(): + config = InheritedConfig() + assert config.get('size') is None + + +def test_get_simple(): + config = InheritedConfig({'imread': 'matplotlib'}) + assert config.get('imread') == 'matplotlib' + + +def test_get_default(): + config = InheritedConfig() + assert config.get('size', 10) == 10 + + +def test_get_best(): + config = InheritedConfig({'size': 0, 'size.text': 1}) + assert config.get('size.text') == 1 + + +def test_get_multi_level(): + config = InheritedConfig({'size': 0}) + assert config.get('size.text.title') == 0 + config['size.text'] = 1 + assert config.get('size.text.title') == 1 + config['size.text.title'] = 2 + assert config.get('size.text.title') == 2 + + +def test_contains(): + config = InheritedConfig({'imread': 'matplotlib'}) + assert 'imread' in config + assert 'imread.jpg' in config + + +if __name__ == '__main__': + import doctest + from numpy import testing + + doctest.testmod() + testing.run_module_suite() From fabe4fa0ccb32aa3ea381e40e17c271dffcce9e6 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 3 Dec 2013 23:28:27 -0600 Subject: [PATCH 18/35] Move tests to separate file. --- skimage/io/inherited_config.py | 37 -------------------- skimage/io/tests/test_inherited_config.py | 41 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 37 deletions(-) create mode 100644 skimage/io/tests/test_inherited_config.py diff --git a/skimage/io/inherited_config.py b/skimage/io/inherited_config.py index 6b989b66..446e6db7 100644 --- a/skimage/io/inherited_config.py +++ b/skimage/io/inherited_config.py @@ -116,44 +116,7 @@ class InheritedConfig(dict): return False -def test_get_non_existent(): - config = InheritedConfig() - assert config.get('size') is None - - -def test_get_simple(): - config = InheritedConfig({'imread': 'matplotlib'}) - assert config.get('imread') == 'matplotlib' - - -def test_get_default(): - config = InheritedConfig() - assert config.get('size', 10) == 10 - - -def test_get_best(): - config = InheritedConfig({'size': 0, 'size.text': 1}) - assert config.get('size.text') == 1 - - -def test_get_multi_level(): - config = InheritedConfig({'size': 0}) - assert config.get('size.text.title') == 0 - config['size.text'] = 1 - assert config.get('size.text.title') == 1 - config['size.text.title'] = 2 - assert config.get('size.text.title') == 2 - - -def test_contains(): - config = InheritedConfig({'imread': 'matplotlib'}) - assert 'imread' in config - assert 'imread.jpg' in config - - if __name__ == '__main__': import doctest - from numpy import testing doctest.testmod() - testing.run_module_suite() diff --git a/skimage/io/tests/test_inherited_config.py b/skimage/io/tests/test_inherited_config.py new file mode 100644 index 00000000..5521da0b --- /dev/null +++ b/skimage/io/tests/test_inherited_config.py @@ -0,0 +1,41 @@ +from skimage.io.inherited_config import InheritedConfig + + +def test_get_non_existent(): + config = InheritedConfig() + assert config.get('size') is None + + +def test_get_simple(): + config = InheritedConfig({'imread': 'matplotlib'}) + assert config.get('imread') == 'matplotlib' + + +def test_get_default(): + config = InheritedConfig() + assert config.get('size', 10) == 10 + + +def test_get_best(): + config = InheritedConfig({'size': 0, 'size.text': 1}) + assert config.get('size.text') == 1 + + +def test_get_multi_level(): + config = InheritedConfig({'size': 0}) + assert config.get('size.text.title') == 0 + config['size.text'] = 1 + assert config.get('size.text.title') == 1 + config['size.text.title'] = 2 + assert config.get('size.text.title') == 2 + + +def test_contains(): + config = InheritedConfig({'imread': 'matplotlib'}) + assert 'imread' in config + assert 'imread.jpg' in config + + +if __name__ == '__main__': + from numpy import testing + testing.run_module_suite() From f1dce4be9d18a2787ef5b29b40640e3f41a01a3d Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 3 Dec 2013 23:32:06 -0600 Subject: [PATCH 19/35] Change plugin_store to use InheritedConfig --- skimage/io/_plugins/plugin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/skimage/io/_plugins/plugin.py b/skimage/io/_plugins/plugin.py index ec8ed7c9..2ff2f938 100644 --- a/skimage/io/_plugins/plugin.py +++ b/skimage/io/_plugins/plugin.py @@ -9,6 +9,7 @@ except ImportError: import os.path from glob import glob +from skimage.io.inherited_config import InheritedConfig __all__ = ['use_plugin', 'call_plugin', 'plugin_info', 'plugin_order', @@ -29,11 +30,11 @@ def _clear_plugins(): """ global plugin_store - plugin_store = {'imread': [], - 'imsave': [], - 'imshow': [], - 'imread_collection': [], - '_app_show': []} + plugin_store = InheritedConfig({'imread': [], + 'imsave': [], + 'imshow': [], + 'imread_collection': [], + '_app_show': []}) _clear_plugins() From 860c6b5cb56d1744385c37d05b7b7005709c483f Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 3 Dec 2013 23:57:19 -0600 Subject: [PATCH 20/35] Use InheritedConfig for plugin_store --- skimage/io/_io.py | 11 +++++++++-- skimage/io/inherited_config.py | 9 +++++++-- skimage/io/tests/test_inherited_config.py | 6 ++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/skimage/io/_io.py b/skimage/io/_io.py index ce718742..1a938408 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -1,3 +1,4 @@ +import os from io import BytesIO import numpy as np @@ -5,7 +6,6 @@ import six from skimage.io._plugins import call_plugin from skimage.color import rgb2grey -from skimage._shared import six from .util import file_or_url_context @@ -95,7 +95,14 @@ def imread(fname, as_grey=False, plugin=None, flatten=None, as_grey = flatten with file_or_url_context(fname) as fname: - img = call_plugin('imread', fname, plugin=plugin, **plugin_args) + function = 'imread' + # TODO: This should probably use imghdr to get the image format. + try: + _, extension = os.path.splitext(fname) + function = function + extension + except AttributeError: # Buffers don't work with splitext + pass + img = call_plugin(function, fname, plugin=plugin, **plugin_args) if as_grey and getattr(img, 'ndim', 0) >= 3: img = rgb2grey(img) diff --git a/skimage/io/inherited_config.py b/skimage/io/inherited_config.py index 446e6db7..9cbc7857 100644 --- a/skimage/io/inherited_config.py +++ b/skimage/io/inherited_config.py @@ -32,7 +32,10 @@ class InheritedConfig(dict): super(InheritedConfig, self).__init__(config_values, **kwargs) - def get(self, key, default=None, _prev=None): + def __getitem__(self, key): + return self.get(key, _raise=True) + + def get(self, key, default=None, _raise=False): """Return best matching config value for `key`. Get value from configuration. The search for `key` is in the following @@ -94,11 +97,13 @@ class InheritedConfig(dict): 1 """ if key in self.keys(): - return self[key] + return super(InheritedConfig, self).__getitem__(key) elif default is not None: return default elif self._separator in key: return self.get(self._parent(key)) + elif _raise: + raise KeyError('%r not in %s' % (key, self.__class__.__name__)) else: return None diff --git a/skimage/io/tests/test_inherited_config.py b/skimage/io/tests/test_inherited_config.py index 5521da0b..207d3cf1 100644 --- a/skimage/io/tests/test_inherited_config.py +++ b/skimage/io/tests/test_inherited_config.py @@ -36,6 +36,12 @@ def test_contains(): assert 'imread.jpg' in config +def test_getitem(): + config = InheritedConfig({'imread': 'a'}) + assert config['imread'] == 'a' + assert config['imread.png'] == 'a' + + if __name__ == '__main__': from numpy import testing testing.run_module_suite() From 6994bc53b987903946678c684ae46fc35002afa7 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 4 Dec 2013 22:54:38 -0600 Subject: [PATCH 21/35] Add test for loading preferred plugins. --- skimage/io/_plugins/plugin.py | 3 ++- skimage/io/tests/test_plugin.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/skimage/io/_plugins/plugin.py b/skimage/io/_plugins/plugin.py index 2ff2f938..5352a511 100644 --- a/skimage/io/_plugins/plugin.py +++ b/skimage/io/_plugins/plugin.py @@ -24,6 +24,8 @@ plugin_provides = {} plugin_module_name = {} plugin_meta_data = {} +preferred_plugins = ['matplotlib', 'pil', 'qt', 'freeimage', 'null'] + def _clear_plugins(): """Clear the plugin state to the default, i.e., where no plugins are loaded @@ -41,7 +43,6 @@ _clear_plugins() def _load_preferred_plugins(): # Load preferred plugin for each io function. io_funcs = ['imsave', 'imshow', 'imread_collection', 'imread'] - preferred_plugins = ['matplotlib', 'pil', 'qt', 'freeimage', 'null'] for func in io_funcs: for plugin in preferred_plugins: if plugin not in available_plugins: diff --git a/skimage/io/tests/test_plugin.py b/skimage/io/tests/test_plugin.py index d91e927c..4be829ca 100644 --- a/skimage/io/tests/test_plugin.py +++ b/skimage/io/tests/test_plugin.py @@ -30,23 +30,29 @@ def teardown_module(): def test_read(): io.imread('test.png', as_grey=True, dtype='i4', plugin='test') + def test_save(): io.imsave('test.png', [1, 2, 3], plugin='test') + def test_show(): io.imshow([1, 2, 3], plugin_arg=(1, 2), plugin='test') + def test_collection(): io.imread_collection('*.png', conserve_memory=False, plugin='test') + def test_use(): plugin.use_plugin('test') plugin.use_plugin('test', 'imshow') + @raises(ValueError) def test_failed_use(): plugin.use_plugin('asd') + @skipif(not PIL_available and not FI_available) def test_use_priority(): plugin.use_plugin(priority_plugin) @@ -57,6 +63,7 @@ def test_use_priority(): plug, func = plugin.plugin_store['imread'][0] assert_equal(plug, 'test') + @skipif(not PIL_available) def test_use_priority_with_func(): plugin.use_plugin('pil') @@ -74,16 +81,26 @@ def test_use_priority_with_func(): plug, func = plugin.plugin_store['imsave'][0] assert_equal(plug, 'test') + def test_plugin_order(): p = io.plugin_order() assert 'imread' in p assert 'test' in p['imread'] + def test_available(): assert 'qt' in io.available_plugins assert 'test' in io.find_available_plugins(loaded=True) +def test_load_preferred_plugins(): + from skimage.io._plugins import null_plugin + plugin.preferred_plugins = ['null'] + plugin.reset_plugins() + plug, func = plugin.plugin_store['imshow'][0] + assert func == null_plugin.imshow + + if __name__ == "__main__": from numpy.testing import run_module_suite run_module_suite() From ac4eb5ae7b0856712fa01c9dc4c460d25782fcb8 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 4 Dec 2013 22:58:35 -0600 Subject: [PATCH 22/35] Make preferred_plugins a dict --- skimage/io/_plugins/plugin.py | 7 ++++--- skimage/io/tests/test_plugin.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/skimage/io/_plugins/plugin.py b/skimage/io/_plugins/plugin.py index 5352a511..6657ebbc 100644 --- a/skimage/io/_plugins/plugin.py +++ b/skimage/io/_plugins/plugin.py @@ -24,8 +24,9 @@ plugin_provides = {} plugin_module_name = {} plugin_meta_data = {} -preferred_plugins = ['matplotlib', 'pil', 'qt', 'freeimage', 'null'] - +preferred_plugins = { + 'io': ['matplotlib', 'pil', 'qt', 'freeimage', 'null'] +} def _clear_plugins(): """Clear the plugin state to the default, i.e., where no plugins are loaded @@ -44,7 +45,7 @@ def _load_preferred_plugins(): # Load preferred plugin for each io function. io_funcs = ['imsave', 'imshow', 'imread_collection', 'imread'] for func in io_funcs: - for plugin in preferred_plugins: + for plugin in preferred_plugins['io']: if plugin not in available_plugins: continue try: diff --git a/skimage/io/tests/test_plugin.py b/skimage/io/tests/test_plugin.py index 4be829ca..53f105a2 100644 --- a/skimage/io/tests/test_plugin.py +++ b/skimage/io/tests/test_plugin.py @@ -95,7 +95,7 @@ def test_available(): def test_load_preferred_plugins(): from skimage.io._plugins import null_plugin - plugin.preferred_plugins = ['null'] + plugin.preferred_plugins['io'] = ['null'] plugin.reset_plugins() plug, func = plugin.plugin_store['imshow'][0] assert func == null_plugin.imshow From 0922ccf92315f0c4bdd60ac4ee71d1a84cdcea80 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 5 Dec 2013 22:56:58 -0600 Subject: [PATCH 23/35] Refactor load_preferred_plugins --- skimage/io/_plugins/null_plugin.ini | 2 +- skimage/io/_plugins/null_plugin.py | 7 ++++- skimage/io/_plugins/plugin.py | 40 +++++++++++++++++------------ skimage/io/tests/test_plugin.py | 39 ++++++++++++++++++++++++---- 4 files changed, 64 insertions(+), 24 deletions(-) diff --git a/skimage/io/_plugins/null_plugin.ini b/skimage/io/_plugins/null_plugin.ini index ddc45f33..9703aab0 100644 --- a/skimage/io/_plugins/null_plugin.ini +++ b/skimage/io/_plugins/null_plugin.ini @@ -1,3 +1,3 @@ [null] description = Default plugin that does nothing -provides = imshow, imread, _app_show +provides = imshow, imread, imsave, _app_show diff --git a/skimage/io/_plugins/null_plugin.py b/skimage/io/_plugins/null_plugin.py index 70bfd5d8..4eb7adf3 100644 --- a/skimage/io/_plugins/null_plugin.py +++ b/skimage/io/_plugins/null_plugin.py @@ -1,4 +1,4 @@ -__all__ = ['imshow', 'imread', '_app_show'] +__all__ = ['imshow', 'imread', 'imsave', '_app_show'] import warnings @@ -17,4 +17,9 @@ def imshow(*args, **kwargs): def imread(*args, **kwargs): warnings.warn(RuntimeWarning(message)) + +def imsave(*args, **kwargs): + warnings.warn(RuntimeWarning(message)) + + _app_show = imshow diff --git a/skimage/io/_plugins/plugin.py b/skimage/io/_plugins/plugin.py index 6657ebbc..42cf448e 100644 --- a/skimage/io/_plugins/plugin.py +++ b/skimage/io/_plugins/plugin.py @@ -25,9 +25,14 @@ plugin_module_name = {} plugin_meta_data = {} preferred_plugins = { - 'io': ['matplotlib', 'pil', 'qt', 'freeimage', 'null'] + 'all': ['matplotlib', 'pil', 'qt', 'freeimage', 'null'], + # Use PIL as the default imread plugin, since matplotlib (1.2.x) + # is buggy (flips PNGs around, returns bytes as floats, etc.) + 'imread': ['pil'], + 'imread.tiff': ['tifffile'], } + def _clear_plugins(): """Clear the plugin state to the default, i.e., where no plugins are loaded @@ -43,23 +48,24 @@ _clear_plugins() def _load_preferred_plugins(): # Load preferred plugin for each io function. - io_funcs = ['imsave', 'imshow', 'imread_collection', 'imread'] - for func in io_funcs: - for plugin in preferred_plugins['io']: - if plugin not in available_plugins: - continue - try: - use_plugin(plugin, kind=func) - break - except (ImportError, RuntimeError, OSError): - pass + io_types = ['imsave', 'imshow', 'imread_collection', 'imread'] + for p_type in io_types: + _set_plugin(p_type, preferred_plugins['all']) - # Use PIL as the default imread plugin, since matplotlib (1.2.x) - # is buggy (flips PNGs around, returns bytes as floats, etc.) - try: - use_plugin('pil', 'imread') - except ImportError: - pass + 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 def reset_plugins(): diff --git a/skimage/io/tests/test_plugin.py b/skimage/io/tests/test_plugin.py index 53f105a2..fba1c5d0 100644 --- a/skimage/io/tests/test_plugin.py +++ b/skimage/io/tests/test_plugin.py @@ -1,3 +1,5 @@ +from contextlib import contextmanager + from numpy.testing import assert_equal, raises from skimage import io @@ -27,6 +29,16 @@ def teardown_module(): io.reset_plugins() +@contextmanager +def protect_preferred_plugins(): + """Contexts where `preferred_plugins` can be modified w/o side-effects.""" + preferred_plugins = plugin.preferred_plugins.copy() + try: + yield + finally: + plugin.preferred_plugins = preferred_plugins + + def test_read(): io.imread('test.png', as_grey=True, dtype='i4', plugin='test') @@ -93,12 +105,29 @@ def test_available(): assert 'test' in io.find_available_plugins(loaded=True) -def test_load_preferred_plugins(): +def test_load_preferred_plugins_all(): from skimage.io._plugins import null_plugin - plugin.preferred_plugins['io'] = ['null'] - plugin.reset_plugins() - plug, func = plugin.plugin_store['imshow'][0] - assert func == null_plugin.imshow + + with protect_preferred_plugins(): + plugin.preferred_plugins = {'all': ['null']} + plugin.reset_plugins() + + for plugin_type in ('imread', 'imsave', 'imshow'): + plug, func = plugin.plugin_store[plugin_type][0] + assert func == getattr(null_plugin, plugin_type) + + +def test_load_preferred_plugins_imread(): + from skimage.io._plugins import null_plugin + + with protect_preferred_plugins(): + plugin.preferred_plugins['imread'] = ['null'] + plugin.reset_plugins() + + plug, func = plugin.plugin_store['imread'][0] + assert func == null_plugin.imread + plug, func = plugin.plugin_store['imshow'][0] + assert func != null_plugin.imshow if __name__ == "__main__": From 99aa5a823836126c08a811860b7ba39d631b83c4 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 5 Dec 2013 23:18:09 -0600 Subject: [PATCH 24/35] Move io._plugins.plugin to io.manage_plugins --- skimage/io/__init__.py | 3 +- skimage/io/_io.py | 2 +- skimage/io/_plugins/__init__.py | 1 - .../{_plugins/plugin.py => manage_plugins.py} | 4 +- skimage/io/tests/test_io.py | 2 +- skimage/io/tests/test_plugin.py | 50 +++++++++---------- 6 files changed, 32 insertions(+), 30 deletions(-) rename skimage/io/{_plugins/plugin.py => manage_plugins.py} (98%) diff --git a/skimage/io/__init__.py b/skimage/io/__init__.py index 02800fbd..316253ca 100644 --- a/skimage/io/__init__.py +++ b/skimage/io/__init__.py @@ -4,7 +4,7 @@ The following plug-ins are available: """ -from ._plugins import * +from .manage_plugins import * from .sift import * from .collection import * @@ -37,6 +37,7 @@ def _update_doc(doc): """ from textwrap import wrap + info_table = [(p, plugin_info(p).get('description', 'no description')) for p in available_plugins if not p == 'test'] diff --git a/skimage/io/_io.py b/skimage/io/_io.py index 1a938408..0f5087a1 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -4,7 +4,7 @@ from io import BytesIO import numpy as np import six -from skimage.io._plugins import call_plugin +from skimage.io.manage_plugins import call_plugin from skimage.color import rgb2grey from .util import file_or_url_context diff --git a/skimage/io/_plugins/__init__.py b/skimage/io/_plugins/__init__.py index 48aad58e..e69de29b 100644 --- a/skimage/io/_plugins/__init__.py +++ b/skimage/io/_plugins/__init__.py @@ -1 +0,0 @@ -from .plugin import * diff --git a/skimage/io/_plugins/plugin.py b/skimage/io/manage_plugins.py similarity index 98% rename from skimage/io/_plugins/plugin.py rename to skimage/io/manage_plugins.py index 42cf448e..ebc55b6a 100644 --- a/skimage/io/_plugins/plugin.py +++ b/skimage/io/manage_plugins.py @@ -9,6 +9,7 @@ except ImportError: import os.path from glob import glob + from skimage.io.inherited_config import InheritedConfig @@ -25,6 +26,7 @@ plugin_module_name = {} plugin_meta_data = {} preferred_plugins = { + # Default plugins for all types (overridden by specific types below). 'all': ['matplotlib', 'pil', 'qt', 'freeimage', 'null'], # Use PIL as the default imread plugin, since matplotlib (1.2.x) # is buggy (flips PNGs around, returns bytes as floats, etc.) @@ -92,7 +94,7 @@ def _scan_plugins(): """ pd = os.path.dirname(__file__) - config_files = glob(os.path.join(pd, '*.ini')) + config_files = glob(os.path.join(pd, '_plugins', '*.ini')) for filename in config_files: name, meta_data = _parse_config_file(filename) diff --git a/skimage/io/tests/test_io.py b/skimage/io/tests/test_io.py index 6b74ae34..14879e64 100644 --- a/skimage/io/tests/test_io.py +++ b/skimage/io/tests/test_io.py @@ -4,7 +4,7 @@ from numpy.testing import assert_array_equal, raises, run_module_suite import numpy as np import skimage.io as io -from skimage.io._plugins.plugin import plugin_store +from skimage.io.manage_plugins import plugin_store from skimage import data_dir diff --git a/skimage/io/tests/test_plugin.py b/skimage/io/tests/test_plugin.py index fba1c5d0..06f3a600 100644 --- a/skimage/io/tests/test_plugin.py +++ b/skimage/io/tests/test_plugin.py @@ -3,7 +3,7 @@ from contextlib import contextmanager from numpy.testing import assert_equal, raises from skimage import io -from skimage.io._plugins import plugin +from skimage.io import manage_plugins from numpy.testing.decorators import skipif try: @@ -22,7 +22,7 @@ except RuntimeError: def setup_module(): - plugin.use_plugin('test') # see ../_plugins/test_plugin.py + manage_plugins.use_plugin('test') # see ../_plugins/test_plugin.py def teardown_module(): @@ -32,11 +32,11 @@ def teardown_module(): @contextmanager def protect_preferred_plugins(): """Contexts where `preferred_plugins` can be modified w/o side-effects.""" - preferred_plugins = plugin.preferred_plugins.copy() + preferred_plugins = manage_plugins.preferred_plugins.copy() try: yield finally: - plugin.preferred_plugins = preferred_plugins + manage_plugins.preferred_plugins = preferred_plugins def test_read(): @@ -56,41 +56,41 @@ def test_collection(): def test_use(): - plugin.use_plugin('test') - plugin.use_plugin('test', 'imshow') + manage_plugins.use_plugin('test') + manage_plugins.use_plugin('test', 'imshow') @raises(ValueError) def test_failed_use(): - plugin.use_plugin('asd') + manage_plugins.use_plugin('asd') @skipif(not PIL_available and not FI_available) def test_use_priority(): - plugin.use_plugin(priority_plugin) - plug, func = plugin.plugin_store['imread'][0] + manage_plugins.use_plugin(priority_plugin) + plug, func = manage_plugins.plugin_store['imread'][0] assert_equal(plug, priority_plugin) - plugin.use_plugin('test') - plug, func = plugin.plugin_store['imread'][0] + manage_plugins.use_plugin('test') + plug, func = manage_plugins.plugin_store['imread'][0] assert_equal(plug, 'test') @skipif(not PIL_available) def test_use_priority_with_func(): - plugin.use_plugin('pil') - plug, func = plugin.plugin_store['imread'][0] + manage_plugins.use_plugin('pil') + plug, func = manage_plugins.plugin_store['imread'][0] assert_equal(plug, 'pil') - plugin.use_plugin('test', 'imread') - plug, func = plugin.plugin_store['imread'][0] + manage_plugins.use_plugin('test', 'imread') + plug, func = manage_plugins.plugin_store['imread'][0] assert_equal(plug, 'test') - plug, func = plugin.plugin_store['imsave'][0] + plug, func = manage_plugins.plugin_store['imsave'][0] assert_equal(plug, 'pil') - plugin.use_plugin('test') - plug, func = plugin.plugin_store['imsave'][0] + manage_plugins.use_plugin('test') + plug, func = manage_plugins.plugin_store['imsave'][0] assert_equal(plug, 'test') @@ -109,11 +109,11 @@ def test_load_preferred_plugins_all(): from skimage.io._plugins import null_plugin with protect_preferred_plugins(): - plugin.preferred_plugins = {'all': ['null']} - plugin.reset_plugins() + manage_plugins.preferred_plugins = {'all': ['null']} + manage_plugins.reset_plugins() for plugin_type in ('imread', 'imsave', 'imshow'): - plug, func = plugin.plugin_store[plugin_type][0] + plug, func = manage_plugins.plugin_store[plugin_type][0] assert func == getattr(null_plugin, plugin_type) @@ -121,12 +121,12 @@ def test_load_preferred_plugins_imread(): from skimage.io._plugins import null_plugin with protect_preferred_plugins(): - plugin.preferred_plugins['imread'] = ['null'] - plugin.reset_plugins() + manage_plugins.preferred_plugins['imread'] = ['null'] + manage_plugins.reset_plugins() - plug, func = plugin.plugin_store['imread'][0] + plug, func = manage_plugins.plugin_store['imread'][0] assert func == null_plugin.imread - plug, func = plugin.plugin_store['imshow'][0] + plug, func = manage_plugins.plugin_store['imshow'][0] assert func != null_plugin.imshow From e59daf474c38aa744c3f3f3ee3cf20d930501082 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 5 Dec 2013 23:38:13 -0600 Subject: [PATCH 25/35] Modest attempt at normalizing file extensions --- skimage/io/_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/io/_io.py b/skimage/io/_io.py index 0f5087a1..d38e7ac0 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -99,7 +99,7 @@ def imread(fname, as_grey=False, plugin=None, flatten=None, # TODO: This should probably use imghdr to get the image format. try: _, extension = os.path.splitext(fname) - function = function + extension + function = function + extension.lower() except AttributeError: # Buffers don't work with splitext pass img = call_plugin(function, fname, plugin=plugin, **plugin_args) From 0f88bed41d94717ebe8cdaec355e1aef55424c6d Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Fri, 6 Dec 2013 22:26:20 -0600 Subject: [PATCH 26/35] Remove inherited config and file-type-specific code. This functionality needs to fleshed out a bit more. This commit can be reverted after the initial refactor PR. --- skimage/io/_io.py | 10 +- skimage/io/inherited_config.py | 127 ---------------------- skimage/io/manage_plugins.py | 13 +-- skimage/io/tests/test_inherited_config.py | 47 -------- 4 files changed, 6 insertions(+), 191 deletions(-) delete mode 100644 skimage/io/inherited_config.py delete mode 100644 skimage/io/tests/test_inherited_config.py diff --git a/skimage/io/_io.py b/skimage/io/_io.py index d38e7ac0..97f8718e 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -1,4 +1,3 @@ -import os from io import BytesIO import numpy as np @@ -95,14 +94,7 @@ def imread(fname, as_grey=False, plugin=None, flatten=None, as_grey = flatten with file_or_url_context(fname) as fname: - function = 'imread' - # TODO: This should probably use imghdr to get the image format. - try: - _, extension = os.path.splitext(fname) - function = function + extension.lower() - except AttributeError: # Buffers don't work with splitext - pass - img = call_plugin(function, fname, plugin=plugin, **plugin_args) + img = call_plugin('imread', fname, plugin=plugin, **plugin_args) if as_grey and getattr(img, 'ndim', 0) >= 3: img = rgb2grey(img) diff --git a/skimage/io/inherited_config.py b/skimage/io/inherited_config.py deleted file mode 100644 index 9cbc7857..00000000 --- a/skimage/io/inherited_config.py +++ /dev/null @@ -1,127 +0,0 @@ -class InheritedConfig(dict): - """Configuration dictionary where non-existent keys can inherit values. - - This class allows you to define parameter names that can match exactly, but - if it doesn't, parameter names will be searched based on key inheritance. - For example, the key 'size.text' will default to 'size'. - - Note that indexing into the dictionary will raise an error if it doesn't - match exactly, while `InheritedConfig.get` will look up values based on - inheritance. - - Parameters - ---------- - config_values : dict or list of (key, value) pairs - Default values for a configuration, where keys are the parameter names - and values are the associated value. - cascade_map : dict - Dictionary defining cascading defaults. If a parameter name is not - found, indexing `cascade_map` with the parameter name will return - the parameter to look for. - kwargs : dict - Keyword arguments for initializing dict. - """ - - _separator = '.' - - def __init__(self, config_values=None, **kwargs): - assert 'config_values' not in kwargs - - if config_values is None: - config_values = {} - - super(InheritedConfig, self).__init__(config_values, **kwargs) - - def __getitem__(self, key): - return self.get(key, _raise=True) - - def get(self, key, default=None, _raise=False): - """Return best matching config value for `key`. - - Get value from configuration. The search for `key` is in the following - order: - - - `self` (Value in global configuration) - - `default` - - Alternate key specified by `self.cascade_map` - - This method supports the pattern commonly used for optional keyword - arguments to a function. For example:: - - >>> def print_value(key, **kwargs): - ... print kwargs.get(key, 0) - >>> print_value('size') - 0 - >>> print_value('size', size=1) - 1 - - Instead, you would create a config class and write:: - - >>> config = InheritedConfig(size=0) - >>> def print_value(key, **kwargs): - ... print kwargs.get(key, config.get(key)) - >>> print_value('size') - 0 - >>> print_value('size', size=1) - 1 - >>> print_value('non-existent') - None - >>> print_value('size.text') - 0 - - See examples below for a demonstration of the cascading of - configuration names. - - Parameters - ---------- - key : str - Name of config value you want. - default : object - Default value if `key` doesn't exist in instance. - - Examples - -------- - >>> config = InheritedConfig(size=0) - >>> config.get('size') - 0 - >>> top_choice={'size': 1} - >>> top_choice.get('size', config.get('size')) - 1 - >>> config.get('non-existent', 'unknown') - 'unknown' - >>> config.get('size.text') - 0 - >>> config.get('size.text', 2) - 2 - >>> top_choice.get('size', config.get('size.text')) - 1 - """ - if key in self.keys(): - return super(InheritedConfig, self).__getitem__(key) - elif default is not None: - return default - elif self._separator in key: - return self.get(self._parent(key)) - elif _raise: - raise KeyError('%r not in %s' % (key, self.__class__.__name__)) - else: - return None - - def _parent(self, key): - """Return parent key.""" - parts = key.split(self._separator) - return self._separator.join(parts[:-1]) - - def __contains__(self, key): - if key in self.keys(): - return True - elif self._separator in key: - return self.__contains__(self._parent(key)) - else: - return False - - -if __name__ == '__main__': - import doctest - - doctest.testmod() diff --git a/skimage/io/manage_plugins.py b/skimage/io/manage_plugins.py index ebc55b6a..e68594a2 100644 --- a/skimage/io/manage_plugins.py +++ b/skimage/io/manage_plugins.py @@ -10,8 +10,6 @@ except ImportError: import os.path from glob import glob -from skimage.io.inherited_config import InheritedConfig - __all__ = ['use_plugin', 'call_plugin', 'plugin_info', 'plugin_order', 'reset_plugins', 'find_available_plugins', 'available_plugins'] @@ -31,7 +29,6 @@ preferred_plugins = { # Use PIL as the default imread plugin, since matplotlib (1.2.x) # is buggy (flips PNGs around, returns bytes as floats, etc.) 'imread': ['pil'], - 'imread.tiff': ['tifffile'], } @@ -40,11 +37,11 @@ def _clear_plugins(): """ global plugin_store - plugin_store = InheritedConfig({'imread': [], - 'imsave': [], - 'imshow': [], - 'imread_collection': [], - '_app_show': []}) + plugin_store = {'imread': [], + 'imsave': [], + 'imshow': [], + 'imread_collection': [], + '_app_show': []} _clear_plugins() diff --git a/skimage/io/tests/test_inherited_config.py b/skimage/io/tests/test_inherited_config.py deleted file mode 100644 index 207d3cf1..00000000 --- a/skimage/io/tests/test_inherited_config.py +++ /dev/null @@ -1,47 +0,0 @@ -from skimage.io.inherited_config import InheritedConfig - - -def test_get_non_existent(): - config = InheritedConfig() - assert config.get('size') is None - - -def test_get_simple(): - config = InheritedConfig({'imread': 'matplotlib'}) - assert config.get('imread') == 'matplotlib' - - -def test_get_default(): - config = InheritedConfig() - assert config.get('size', 10) == 10 - - -def test_get_best(): - config = InheritedConfig({'size': 0, 'size.text': 1}) - assert config.get('size.text') == 1 - - -def test_get_multi_level(): - config = InheritedConfig({'size': 0}) - assert config.get('size.text.title') == 0 - config['size.text'] = 1 - assert config.get('size.text.title') == 1 - config['size.text.title'] = 2 - assert config.get('size.text.title') == 2 - - -def test_contains(): - config = InheritedConfig({'imread': 'matplotlib'}) - assert 'imread' in config - assert 'imread.jpg' in config - - -def test_getitem(): - config = InheritedConfig({'imread': 'a'}) - assert config['imread'] == 'a' - assert config['imread.png'] == 'a' - - -if __name__ == '__main__': - from numpy import testing - testing.run_module_suite() From c329ff14bc024cd29264370a9fe4fc736d2a09a1 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Fri, 6 Dec 2013 22:40:37 -0600 Subject: [PATCH 27/35] Fix failing doctest on systems w/o PIL --- skimage/io/manage_plugins.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/skimage/io/manage_plugins.py b/skimage/io/manage_plugins.py index e68594a2..ace57d8f 100644 --- a/skimage/io/manage_plugins.py +++ b/skimage/io/manage_plugins.py @@ -197,15 +197,19 @@ def use_plugin(name, kind=None): See Also -------- - plugins : List of available plugins + available_plugins : List of available plugins Examples -------- - Use the Python Imaging Library to read images: + To use a plugin named 'null' as the default image reader, you would write: - >>> from skimage.io import use_plugin - >>> use_plugin('pil', 'imread') + >>> from skimage import io + >>> io.use_plugin('null', 'imread') + + To see a list of available plugins run ``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: From 99c5ba409a3a03d0a0156302b4b6e7b5324b8a40 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Fri, 6 Dec 2013 23:43:56 -0600 Subject: [PATCH 28/35] Slight clean up of tests --- skimage/io/tests/test_collection.py | 153 ++++++++------------------- skimage/io/tests/test_multi_image.py | 69 ++++++++++++ 2 files changed, 114 insertions(+), 108 deletions(-) create mode 100644 skimage/io/tests/test_multi_image.py diff --git a/skimage/io/tests/test_collection.py b/skimage/io/tests/test_collection.py index cf5ef820..1e9fea73 100644 --- a/skimage/io/tests/test_collection.py +++ b/skimage/io/tests/test_collection.py @@ -1,88 +1,72 @@ -import sys import os.path import numpy as np -from numpy.testing import (assert_raises, - assert_equal, - assert_array_almost_equal, - ) -from numpy.testing.decorators import skipif +from numpy.testing import assert_raises, assert_equal, assert_allclose from skimage import data_dir -from skimage.io import ImageCollection, MultiImage -from skimage.io.collection import alphanumeric_key -from skimage.io import Image as ioImage - -import six +from skimage.io.collection import ImageCollection, alphanumeric_key -try: - from PIL import Image -except ImportError: - PIL_available = False -else: - PIL_available = True +def test_string_split(): + test_string = 'z23a' + test_str_result = ['z', 23, 'a'] + assert_equal(alphanumeric_key(test_string), test_str_result) -class TestAlphanumericKey(): - def setUp(self): - self.test_string = 'z23a' - self.test_str_result = ['z', 23, 'a'] - self.filenames = ['f9.10.png', 'f9.9.png', 'f10.10.png', 'f10.9.png', - 'e9.png', 'e10.png', 'em.png'] - self.sorted_filenames = \ - ['e9.png', 'e10.png', 'em.png', 'f9.9.png', 'f9.10.png', - 'f10.9.png', 'f10.10.png'] - - def test_string_split(self): - assert_equal(alphanumeric_key(self.test_string), self.test_str_result) - - def test_string_sort(self): - sorted_filenames = sorted(self.filenames, key=alphanumeric_key) - assert_equal(sorted_filenames, self.sorted_filenames) +def test_string_sort(): + filenames = ['f9.10.png', 'f9.9.png', 'f10.10.png', 'f10.9.png', + 'e9.png', 'e10.png', 'em.png'] + sorted_filenames = ['e9.png', 'e10.png', 'em.png', 'f9.9.png', + 'f9.10.png', 'f10.9.png', 'f10.10.png'] + sorted_filenames = sorted(filenames, key=alphanumeric_key) + assert_equal(sorted_filenames, sorted_filenames) class TestImageCollection(): - pattern = [os.path.join(data_dir, pic) for pic in ['camera.png', - 'color.png']] - pattern_matched = [os.path.join(data_dir, pic) for pic in - ['camera.png', 'moon.png']] + + pattern = [os.path.join(data_dir, pic) + for pic in ['camera.png', 'color.png']] + + pattern_matched = [os.path.join(data_dir, pic) + for pic in ['camera.png', 'moon.png']] def setUp(self): - self.collection = ImageCollection(self.pattern) - self.collection_matched = ImageCollection(self.pattern_matched) + # Generic image collection with images of different shapes. + self.images = ImageCollection(self.pattern) + # Image collection with images having shapes that match. + self.images_matched = ImageCollection(self.pattern_matched) def test_len(self): - assert len(self.collection) == 2 + assert len(self.images) == 2 def test_getitem(self): - num = len(self.collection) + num = len(self.images) for i in range(-num, num): - assert type(self.collection[i]) is np.ndarray - assert_array_almost_equal(self.collection[0], - self.collection[-num]) + assert type(self.images[i]) is np.ndarray + assert_allclose(self.images[0], + self.images[-num]) - #assert_raises expects a callable, hence this do-very-little func + # assert_raises expects a callable, hence this thin wrapper function. def return_img(n): - return self.collection[n] + return self.images[n] assert_raises(IndexError, return_img, num) assert_raises(IndexError, return_img, -num - 1) def test_slicing(self): - assert type(self.collection[:]) is ImageCollection - assert len(self.collection[:]) == 2 - assert len(self.collection[:1]) == 1 - assert len(self.collection[1:]) == 1 - assert_array_almost_equal(self.collection[0], self.collection[:1][0]) - assert_array_almost_equal(self.collection[1], self.collection[1:][0]) - assert_array_almost_equal(self.collection[1], self.collection[::-1][0]) - assert_array_almost_equal(self.collection[0], self.collection[::-1][1]) + assert type(self.images[:]) is ImageCollection + assert len(self.images[:]) == 2 + assert len(self.images[:1]) == 1 + assert len(self.images[1:]) == 1 + assert_allclose(self.images[0], self.images[:1][0]) + assert_allclose(self.images[1], self.images[1:][0]) + assert_allclose(self.images[1], self.images[::-1][0]) + assert_allclose(self.images[0], self.images[::-1][1]) def test_files_property(self): - assert isinstance(self.collection.files, list) + assert isinstance(self.images.files, list) def set_files(f): - self.collection.files = f + self.images.files = f assert_raises(AttributeError, set_files, 'newfiles') def test_custom_load(self): @@ -95,59 +79,12 @@ class TestImageCollection(): assert_equal(ic[1], (2, 'two')) def test_concatenate(self): - ar = self.collection_matched.concatenate() - assert_equal(ar.shape, (len(self.collection_matched),) + - self.collection[0].shape) - assert_raises(ValueError, self.collection.concatenate) + array = self.images_matched.concatenate() + expected_shape = (len(self.images_matched),) + self.images[0].shape + assert_equal(array.shape, expected_shape) - -class TestMultiImage(): - - def setUp(self): - # This multipage TIF file was created with imagemagick: - # convert im1.tif im2.tif -adjoin multipage.tif - if PIL_available: - self.img = MultiImage(os.path.join(data_dir, 'multipage.tif')) - - @skipif(not PIL_available) - def test_len(self): - assert len(self.img) == 2 - - @skipif(not PIL_available) - def test_getitem(self): - num = len(self.img) - for i in range(-num, num): - assert type(self.img[i]) is np.ndarray - assert_array_almost_equal(self.img[0], - self.img[-num]) - - #assert_raises expects a callable, hence this do-very-little func - def return_img(n): - return self.img[n] - assert_raises(IndexError, return_img, num) - assert_raises(IndexError, return_img, -num - 1) - - @skipif(not PIL_available) - def test_files_property(self): - assert isinstance(self.img.filename, six.string_types) - - def set_filename(f): - self.img.filename = f - assert_raises(AttributeError, set_filename, 'newfile') - - @skipif(not PIL_available) - def test_conserve_memory_property(self): - assert isinstance(self.img.conserve_memory, bool) - - def set_mem(val): - self.img.conserve_memory = val - assert_raises(AttributeError, set_mem, True) - - @skipif(not PIL_available) - def test_concatenate(self): - ar = self.img.concatenate() - assert_equal(ar.shape, (len(self.img),) + - self.img[0].shape) + def test_concatentate_mismatched_image_shapes(self): + assert_raises(ValueError, self.images.concatenate) if __name__ == "__main__": diff --git a/skimage/io/tests/test_multi_image.py b/skimage/io/tests/test_multi_image.py new file mode 100644 index 00000000..ebaa71dc --- /dev/null +++ b/skimage/io/tests/test_multi_image.py @@ -0,0 +1,69 @@ +import os + +import numpy as np +from numpy.testing.decorators import skipif +from numpy.testing import assert_raises, assert_equal, assert_allclose + +from skimage import data_dir +from skimage.io.collection import MultiImage + +try: + from PIL import Image +except ImportError: + PIL_available = False +else: + PIL_available = True + +import six + + +class TestMultiImage(): + + def setUp(self): + # This multipage TIF file was created with imagemagick: + # convert im1.tif im2.tif -adjoin multipage.tif + if PIL_available: + self.img = MultiImage(os.path.join(data_dir, 'multipage.tif')) + + @skipif(not PIL_available) + def test_len(self): + assert len(self.img) == 2 + + @skipif(not PIL_available) + def test_getitem(self): + num = len(self.img) + for i in range(-num, num): + assert type(self.img[i]) is np.ndarray + assert_allclose(self.img[0], self.img[-num]) + + # assert_raises expects a callable, hence this thin wrapper function. + def return_img(n): + return self.img[n] + assert_raises(IndexError, return_img, num) + assert_raises(IndexError, return_img, -num - 1) + + @skipif(not PIL_available) + def test_files_property(self): + assert isinstance(self.img.filename, six.string_types) + + def set_filename(f): + self.img.filename = f + assert_raises(AttributeError, set_filename, 'newfile') + + @skipif(not PIL_available) + def test_conserve_memory_property(self): + assert isinstance(self.img.conserve_memory, bool) + + def set_mem(val): + self.img.conserve_memory = val + assert_raises(AttributeError, set_mem, True) + + @skipif(not PIL_available) + def test_concatenate(self): + array = self.img.concatenate() + assert_equal(array.shape, (len(self.img),) + self.img[0].shape) + + +if __name__ == "__main__": + from numpy.testing import run_module_suite + run_module_suite() From c09f1bfd326876e4f6523a8cee3468e18455bb09 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 7 Dec 2013 00:01:32 -0600 Subject: [PATCH 29/35] Revert previous 3 commits to test Travis CI failures. --- skimage/io/_io.py | 10 +- skimage/io/inherited_config.py | 127 ++++++++++++++++++ skimage/io/manage_plugins.py | 25 ++-- skimage/io/tests/test_collection.py | 153 +++++++++++++++------- skimage/io/tests/test_inherited_config.py | 47 +++++++ skimage/io/tests/test_multi_image.py | 69 ---------- 6 files changed, 303 insertions(+), 128 deletions(-) create mode 100644 skimage/io/inherited_config.py create mode 100644 skimage/io/tests/test_inherited_config.py delete mode 100644 skimage/io/tests/test_multi_image.py diff --git a/skimage/io/_io.py b/skimage/io/_io.py index 97f8718e..d38e7ac0 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -1,3 +1,4 @@ +import os from io import BytesIO import numpy as np @@ -94,7 +95,14 @@ def imread(fname, as_grey=False, plugin=None, flatten=None, as_grey = flatten with file_or_url_context(fname) as fname: - img = call_plugin('imread', fname, plugin=plugin, **plugin_args) + function = 'imread' + # TODO: This should probably use imghdr to get the image format. + try: + _, extension = os.path.splitext(fname) + function = function + extension.lower() + except AttributeError: # Buffers don't work with splitext + pass + img = call_plugin(function, fname, plugin=plugin, **plugin_args) if as_grey and getattr(img, 'ndim', 0) >= 3: img = rgb2grey(img) diff --git a/skimage/io/inherited_config.py b/skimage/io/inherited_config.py new file mode 100644 index 00000000..9cbc7857 --- /dev/null +++ b/skimage/io/inherited_config.py @@ -0,0 +1,127 @@ +class InheritedConfig(dict): + """Configuration dictionary where non-existent keys can inherit values. + + This class allows you to define parameter names that can match exactly, but + if it doesn't, parameter names will be searched based on key inheritance. + For example, the key 'size.text' will default to 'size'. + + Note that indexing into the dictionary will raise an error if it doesn't + match exactly, while `InheritedConfig.get` will look up values based on + inheritance. + + Parameters + ---------- + config_values : dict or list of (key, value) pairs + Default values for a configuration, where keys are the parameter names + and values are the associated value. + cascade_map : dict + Dictionary defining cascading defaults. If a parameter name is not + found, indexing `cascade_map` with the parameter name will return + the parameter to look for. + kwargs : dict + Keyword arguments for initializing dict. + """ + + _separator = '.' + + def __init__(self, config_values=None, **kwargs): + assert 'config_values' not in kwargs + + if config_values is None: + config_values = {} + + super(InheritedConfig, self).__init__(config_values, **kwargs) + + def __getitem__(self, key): + return self.get(key, _raise=True) + + def get(self, key, default=None, _raise=False): + """Return best matching config value for `key`. + + Get value from configuration. The search for `key` is in the following + order: + + - `self` (Value in global configuration) + - `default` + - Alternate key specified by `self.cascade_map` + + This method supports the pattern commonly used for optional keyword + arguments to a function. For example:: + + >>> def print_value(key, **kwargs): + ... print kwargs.get(key, 0) + >>> print_value('size') + 0 + >>> print_value('size', size=1) + 1 + + Instead, you would create a config class and write:: + + >>> config = InheritedConfig(size=0) + >>> def print_value(key, **kwargs): + ... print kwargs.get(key, config.get(key)) + >>> print_value('size') + 0 + >>> print_value('size', size=1) + 1 + >>> print_value('non-existent') + None + >>> print_value('size.text') + 0 + + See examples below for a demonstration of the cascading of + configuration names. + + Parameters + ---------- + key : str + Name of config value you want. + default : object + Default value if `key` doesn't exist in instance. + + Examples + -------- + >>> config = InheritedConfig(size=0) + >>> config.get('size') + 0 + >>> top_choice={'size': 1} + >>> top_choice.get('size', config.get('size')) + 1 + >>> config.get('non-existent', 'unknown') + 'unknown' + >>> config.get('size.text') + 0 + >>> config.get('size.text', 2) + 2 + >>> top_choice.get('size', config.get('size.text')) + 1 + """ + if key in self.keys(): + return super(InheritedConfig, self).__getitem__(key) + elif default is not None: + return default + elif self._separator in key: + return self.get(self._parent(key)) + elif _raise: + raise KeyError('%r not in %s' % (key, self.__class__.__name__)) + else: + return None + + def _parent(self, key): + """Return parent key.""" + parts = key.split(self._separator) + return self._separator.join(parts[:-1]) + + def __contains__(self, key): + if key in self.keys(): + return True + elif self._separator in key: + return self.__contains__(self._parent(key)) + else: + return False + + +if __name__ == '__main__': + import doctest + + doctest.testmod() diff --git a/skimage/io/manage_plugins.py b/skimage/io/manage_plugins.py index ace57d8f..ebc55b6a 100644 --- a/skimage/io/manage_plugins.py +++ b/skimage/io/manage_plugins.py @@ -10,6 +10,8 @@ except ImportError: import os.path from glob import glob +from skimage.io.inherited_config import InheritedConfig + __all__ = ['use_plugin', 'call_plugin', 'plugin_info', 'plugin_order', 'reset_plugins', 'find_available_plugins', 'available_plugins'] @@ -29,6 +31,7 @@ preferred_plugins = { # Use PIL as the default imread plugin, since matplotlib (1.2.x) # is buggy (flips PNGs around, returns bytes as floats, etc.) 'imread': ['pil'], + 'imread.tiff': ['tifffile'], } @@ -37,11 +40,11 @@ def _clear_plugins(): """ global plugin_store - plugin_store = {'imread': [], - 'imsave': [], - 'imshow': [], - 'imread_collection': [], - '_app_show': []} + plugin_store = InheritedConfig({'imread': [], + 'imsave': [], + 'imshow': [], + 'imread_collection': [], + '_app_show': []}) _clear_plugins() @@ -197,19 +200,15 @@ def use_plugin(name, kind=None): See Also -------- - available_plugins : List of available plugins + plugins : List of available plugins Examples -------- - To use a plugin named 'null' as the default image reader, you would write: + Use the Python Imaging Library to read images: - >>> from skimage import io - >>> io.use_plugin('null', 'imread') - - To see a list of available plugins run ``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. + >>> from skimage.io import use_plugin + >>> use_plugin('pil', 'imread') """ if kind is None: diff --git a/skimage/io/tests/test_collection.py b/skimage/io/tests/test_collection.py index 1e9fea73..cf5ef820 100644 --- a/skimage/io/tests/test_collection.py +++ b/skimage/io/tests/test_collection.py @@ -1,72 +1,88 @@ +import sys import os.path import numpy as np -from numpy.testing import assert_raises, assert_equal, assert_allclose +from numpy.testing import (assert_raises, + assert_equal, + assert_array_almost_equal, + ) +from numpy.testing.decorators import skipif from skimage import data_dir -from skimage.io.collection import ImageCollection, alphanumeric_key +from skimage.io import ImageCollection, MultiImage +from skimage.io.collection import alphanumeric_key +from skimage.io import Image as ioImage + +import six -def test_string_split(): - test_string = 'z23a' - test_str_result = ['z', 23, 'a'] - assert_equal(alphanumeric_key(test_string), test_str_result) +try: + from PIL import Image +except ImportError: + PIL_available = False +else: + PIL_available = True -def test_string_sort(): - filenames = ['f9.10.png', 'f9.9.png', 'f10.10.png', 'f10.9.png', - 'e9.png', 'e10.png', 'em.png'] - sorted_filenames = ['e9.png', 'e10.png', 'em.png', 'f9.9.png', - 'f9.10.png', 'f10.9.png', 'f10.10.png'] - sorted_filenames = sorted(filenames, key=alphanumeric_key) - assert_equal(sorted_filenames, sorted_filenames) +class TestAlphanumericKey(): + def setUp(self): + self.test_string = 'z23a' + self.test_str_result = ['z', 23, 'a'] + self.filenames = ['f9.10.png', 'f9.9.png', 'f10.10.png', 'f10.9.png', + 'e9.png', 'e10.png', 'em.png'] + self.sorted_filenames = \ + ['e9.png', 'e10.png', 'em.png', 'f9.9.png', 'f9.10.png', + 'f10.9.png', 'f10.10.png'] + + def test_string_split(self): + assert_equal(alphanumeric_key(self.test_string), self.test_str_result) + + def test_string_sort(self): + sorted_filenames = sorted(self.filenames, key=alphanumeric_key) + assert_equal(sorted_filenames, self.sorted_filenames) class TestImageCollection(): - - pattern = [os.path.join(data_dir, pic) - for pic in ['camera.png', 'color.png']] - - pattern_matched = [os.path.join(data_dir, pic) - for pic in ['camera.png', 'moon.png']] + pattern = [os.path.join(data_dir, pic) for pic in ['camera.png', + 'color.png']] + pattern_matched = [os.path.join(data_dir, pic) for pic in + ['camera.png', 'moon.png']] def setUp(self): - # Generic image collection with images of different shapes. - self.images = ImageCollection(self.pattern) - # Image collection with images having shapes that match. - self.images_matched = ImageCollection(self.pattern_matched) + self.collection = ImageCollection(self.pattern) + self.collection_matched = ImageCollection(self.pattern_matched) def test_len(self): - assert len(self.images) == 2 + assert len(self.collection) == 2 def test_getitem(self): - num = len(self.images) + num = len(self.collection) for i in range(-num, num): - assert type(self.images[i]) is np.ndarray - assert_allclose(self.images[0], - self.images[-num]) + assert type(self.collection[i]) is np.ndarray + assert_array_almost_equal(self.collection[0], + self.collection[-num]) - # assert_raises expects a callable, hence this thin wrapper function. + #assert_raises expects a callable, hence this do-very-little func def return_img(n): - return self.images[n] + return self.collection[n] assert_raises(IndexError, return_img, num) assert_raises(IndexError, return_img, -num - 1) def test_slicing(self): - assert type(self.images[:]) is ImageCollection - assert len(self.images[:]) == 2 - assert len(self.images[:1]) == 1 - assert len(self.images[1:]) == 1 - assert_allclose(self.images[0], self.images[:1][0]) - assert_allclose(self.images[1], self.images[1:][0]) - assert_allclose(self.images[1], self.images[::-1][0]) - assert_allclose(self.images[0], self.images[::-1][1]) + assert type(self.collection[:]) is ImageCollection + assert len(self.collection[:]) == 2 + assert len(self.collection[:1]) == 1 + assert len(self.collection[1:]) == 1 + assert_array_almost_equal(self.collection[0], self.collection[:1][0]) + assert_array_almost_equal(self.collection[1], self.collection[1:][0]) + assert_array_almost_equal(self.collection[1], self.collection[::-1][0]) + assert_array_almost_equal(self.collection[0], self.collection[::-1][1]) def test_files_property(self): - assert isinstance(self.images.files, list) + assert isinstance(self.collection.files, list) def set_files(f): - self.images.files = f + self.collection.files = f assert_raises(AttributeError, set_files, 'newfiles') def test_custom_load(self): @@ -79,12 +95,59 @@ class TestImageCollection(): assert_equal(ic[1], (2, 'two')) def test_concatenate(self): - array = self.images_matched.concatenate() - expected_shape = (len(self.images_matched),) + self.images[0].shape - assert_equal(array.shape, expected_shape) + ar = self.collection_matched.concatenate() + assert_equal(ar.shape, (len(self.collection_matched),) + + self.collection[0].shape) + assert_raises(ValueError, self.collection.concatenate) - def test_concatentate_mismatched_image_shapes(self): - assert_raises(ValueError, self.images.concatenate) + +class TestMultiImage(): + + def setUp(self): + # This multipage TIF file was created with imagemagick: + # convert im1.tif im2.tif -adjoin multipage.tif + if PIL_available: + self.img = MultiImage(os.path.join(data_dir, 'multipage.tif')) + + @skipif(not PIL_available) + def test_len(self): + assert len(self.img) == 2 + + @skipif(not PIL_available) + def test_getitem(self): + num = len(self.img) + for i in range(-num, num): + assert type(self.img[i]) is np.ndarray + assert_array_almost_equal(self.img[0], + self.img[-num]) + + #assert_raises expects a callable, hence this do-very-little func + def return_img(n): + return self.img[n] + assert_raises(IndexError, return_img, num) + assert_raises(IndexError, return_img, -num - 1) + + @skipif(not PIL_available) + def test_files_property(self): + assert isinstance(self.img.filename, six.string_types) + + def set_filename(f): + self.img.filename = f + assert_raises(AttributeError, set_filename, 'newfile') + + @skipif(not PIL_available) + def test_conserve_memory_property(self): + assert isinstance(self.img.conserve_memory, bool) + + def set_mem(val): + self.img.conserve_memory = val + assert_raises(AttributeError, set_mem, True) + + @skipif(not PIL_available) + def test_concatenate(self): + ar = self.img.concatenate() + assert_equal(ar.shape, (len(self.img),) + + self.img[0].shape) if __name__ == "__main__": diff --git a/skimage/io/tests/test_inherited_config.py b/skimage/io/tests/test_inherited_config.py new file mode 100644 index 00000000..207d3cf1 --- /dev/null +++ b/skimage/io/tests/test_inherited_config.py @@ -0,0 +1,47 @@ +from skimage.io.inherited_config import InheritedConfig + + +def test_get_non_existent(): + config = InheritedConfig() + assert config.get('size') is None + + +def test_get_simple(): + config = InheritedConfig({'imread': 'matplotlib'}) + assert config.get('imread') == 'matplotlib' + + +def test_get_default(): + config = InheritedConfig() + assert config.get('size', 10) == 10 + + +def test_get_best(): + config = InheritedConfig({'size': 0, 'size.text': 1}) + assert config.get('size.text') == 1 + + +def test_get_multi_level(): + config = InheritedConfig({'size': 0}) + assert config.get('size.text.title') == 0 + config['size.text'] = 1 + assert config.get('size.text.title') == 1 + config['size.text.title'] = 2 + assert config.get('size.text.title') == 2 + + +def test_contains(): + config = InheritedConfig({'imread': 'matplotlib'}) + assert 'imread' in config + assert 'imread.jpg' in config + + +def test_getitem(): + config = InheritedConfig({'imread': 'a'}) + assert config['imread'] == 'a' + assert config['imread.png'] == 'a' + + +if __name__ == '__main__': + from numpy import testing + testing.run_module_suite() diff --git a/skimage/io/tests/test_multi_image.py b/skimage/io/tests/test_multi_image.py deleted file mode 100644 index ebaa71dc..00000000 --- a/skimage/io/tests/test_multi_image.py +++ /dev/null @@ -1,69 +0,0 @@ -import os - -import numpy as np -from numpy.testing.decorators import skipif -from numpy.testing import assert_raises, assert_equal, assert_allclose - -from skimage import data_dir -from skimage.io.collection import MultiImage - -try: - from PIL import Image -except ImportError: - PIL_available = False -else: - PIL_available = True - -import six - - -class TestMultiImage(): - - def setUp(self): - # This multipage TIF file was created with imagemagick: - # convert im1.tif im2.tif -adjoin multipage.tif - if PIL_available: - self.img = MultiImage(os.path.join(data_dir, 'multipage.tif')) - - @skipif(not PIL_available) - def test_len(self): - assert len(self.img) == 2 - - @skipif(not PIL_available) - def test_getitem(self): - num = len(self.img) - for i in range(-num, num): - assert type(self.img[i]) is np.ndarray - assert_allclose(self.img[0], self.img[-num]) - - # assert_raises expects a callable, hence this thin wrapper function. - def return_img(n): - return self.img[n] - assert_raises(IndexError, return_img, num) - assert_raises(IndexError, return_img, -num - 1) - - @skipif(not PIL_available) - def test_files_property(self): - assert isinstance(self.img.filename, six.string_types) - - def set_filename(f): - self.img.filename = f - assert_raises(AttributeError, set_filename, 'newfile') - - @skipif(not PIL_available) - def test_conserve_memory_property(self): - assert isinstance(self.img.conserve_memory, bool) - - def set_mem(val): - self.img.conserve_memory = val - assert_raises(AttributeError, set_mem, True) - - @skipif(not PIL_available) - def test_concatenate(self): - array = self.img.concatenate() - assert_equal(array.shape, (len(self.img),) + self.img[0].shape) - - -if __name__ == "__main__": - from numpy.testing import run_module_suite - run_module_suite() From 27bab6390029e94626950ec88908761f55ed35d6 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 7 Dec 2013 00:22:34 -0600 Subject: [PATCH 30/35] Un-revert "Fix failing doctest on systems w/o PIL" commit --- skimage/io/manage_plugins.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/skimage/io/manage_plugins.py b/skimage/io/manage_plugins.py index ebc55b6a..c5334c4a 100644 --- a/skimage/io/manage_plugins.py +++ b/skimage/io/manage_plugins.py @@ -200,16 +200,19 @@ def use_plugin(name, kind=None): See Also -------- - plugins : List of available plugins + available_plugins : List of available plugins Examples -------- - Use the Python Imaging Library to read images: + To use a plugin named 'null' as the default image reader, you would write: - >>> from skimage.io import use_plugin - >>> use_plugin('pil', 'imread') + >>> from skimage import io + >>> io.use_plugin('null', 'imread') + To see a list of available plugins run ``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() From 6a751045f1a6d22c775754388b217182f33da3df Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 7 Dec 2013 01:04:40 -0600 Subject: [PATCH 31/35] Un-revert commits added to test Travis CI failures --- skimage/io/_io.py | 10 +- skimage/io/inherited_config.py | 127 ------------------ skimage/io/manage_plugins.py | 14 +- skimage/io/tests/test_collection.py | 153 +++++++--------------- skimage/io/tests/test_inherited_config.py | 47 ------- skimage/io/tests/test_multi_image.py | 69 ++++++++++ 6 files changed, 121 insertions(+), 299 deletions(-) delete mode 100644 skimage/io/inherited_config.py delete mode 100644 skimage/io/tests/test_inherited_config.py create mode 100644 skimage/io/tests/test_multi_image.py diff --git a/skimage/io/_io.py b/skimage/io/_io.py index d38e7ac0..97f8718e 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -1,4 +1,3 @@ -import os from io import BytesIO import numpy as np @@ -95,14 +94,7 @@ def imread(fname, as_grey=False, plugin=None, flatten=None, as_grey = flatten with file_or_url_context(fname) as fname: - function = 'imread' - # TODO: This should probably use imghdr to get the image format. - try: - _, extension = os.path.splitext(fname) - function = function + extension.lower() - except AttributeError: # Buffers don't work with splitext - pass - img = call_plugin(function, fname, plugin=plugin, **plugin_args) + img = call_plugin('imread', fname, plugin=plugin, **plugin_args) if as_grey and getattr(img, 'ndim', 0) >= 3: img = rgb2grey(img) diff --git a/skimage/io/inherited_config.py b/skimage/io/inherited_config.py deleted file mode 100644 index 9cbc7857..00000000 --- a/skimage/io/inherited_config.py +++ /dev/null @@ -1,127 +0,0 @@ -class InheritedConfig(dict): - """Configuration dictionary where non-existent keys can inherit values. - - This class allows you to define parameter names that can match exactly, but - if it doesn't, parameter names will be searched based on key inheritance. - For example, the key 'size.text' will default to 'size'. - - Note that indexing into the dictionary will raise an error if it doesn't - match exactly, while `InheritedConfig.get` will look up values based on - inheritance. - - Parameters - ---------- - config_values : dict or list of (key, value) pairs - Default values for a configuration, where keys are the parameter names - and values are the associated value. - cascade_map : dict - Dictionary defining cascading defaults. If a parameter name is not - found, indexing `cascade_map` with the parameter name will return - the parameter to look for. - kwargs : dict - Keyword arguments for initializing dict. - """ - - _separator = '.' - - def __init__(self, config_values=None, **kwargs): - assert 'config_values' not in kwargs - - if config_values is None: - config_values = {} - - super(InheritedConfig, self).__init__(config_values, **kwargs) - - def __getitem__(self, key): - return self.get(key, _raise=True) - - def get(self, key, default=None, _raise=False): - """Return best matching config value for `key`. - - Get value from configuration. The search for `key` is in the following - order: - - - `self` (Value in global configuration) - - `default` - - Alternate key specified by `self.cascade_map` - - This method supports the pattern commonly used for optional keyword - arguments to a function. For example:: - - >>> def print_value(key, **kwargs): - ... print kwargs.get(key, 0) - >>> print_value('size') - 0 - >>> print_value('size', size=1) - 1 - - Instead, you would create a config class and write:: - - >>> config = InheritedConfig(size=0) - >>> def print_value(key, **kwargs): - ... print kwargs.get(key, config.get(key)) - >>> print_value('size') - 0 - >>> print_value('size', size=1) - 1 - >>> print_value('non-existent') - None - >>> print_value('size.text') - 0 - - See examples below for a demonstration of the cascading of - configuration names. - - Parameters - ---------- - key : str - Name of config value you want. - default : object - Default value if `key` doesn't exist in instance. - - Examples - -------- - >>> config = InheritedConfig(size=0) - >>> config.get('size') - 0 - >>> top_choice={'size': 1} - >>> top_choice.get('size', config.get('size')) - 1 - >>> config.get('non-existent', 'unknown') - 'unknown' - >>> config.get('size.text') - 0 - >>> config.get('size.text', 2) - 2 - >>> top_choice.get('size', config.get('size.text')) - 1 - """ - if key in self.keys(): - return super(InheritedConfig, self).__getitem__(key) - elif default is not None: - return default - elif self._separator in key: - return self.get(self._parent(key)) - elif _raise: - raise KeyError('%r not in %s' % (key, self.__class__.__name__)) - else: - return None - - def _parent(self, key): - """Return parent key.""" - parts = key.split(self._separator) - return self._separator.join(parts[:-1]) - - def __contains__(self, key): - if key in self.keys(): - return True - elif self._separator in key: - return self.__contains__(self._parent(key)) - else: - return False - - -if __name__ == '__main__': - import doctest - - doctest.testmod() diff --git a/skimage/io/manage_plugins.py b/skimage/io/manage_plugins.py index c5334c4a..ace57d8f 100644 --- a/skimage/io/manage_plugins.py +++ b/skimage/io/manage_plugins.py @@ -10,8 +10,6 @@ except ImportError: import os.path from glob import glob -from skimage.io.inherited_config import InheritedConfig - __all__ = ['use_plugin', 'call_plugin', 'plugin_info', 'plugin_order', 'reset_plugins', 'find_available_plugins', 'available_plugins'] @@ -31,7 +29,6 @@ preferred_plugins = { # Use PIL as the default imread plugin, since matplotlib (1.2.x) # is buggy (flips PNGs around, returns bytes as floats, etc.) 'imread': ['pil'], - 'imread.tiff': ['tifffile'], } @@ -40,11 +37,11 @@ def _clear_plugins(): """ global plugin_store - plugin_store = InheritedConfig({'imread': [], - 'imsave': [], - 'imshow': [], - 'imread_collection': [], - '_app_show': []}) + plugin_store = {'imread': [], + 'imsave': [], + 'imshow': [], + 'imread_collection': [], + '_app_show': []} _clear_plugins() @@ -213,6 +210,7 @@ def use_plugin(name, kind=None): To see a list of available plugins run ``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() diff --git a/skimage/io/tests/test_collection.py b/skimage/io/tests/test_collection.py index cf5ef820..1e9fea73 100644 --- a/skimage/io/tests/test_collection.py +++ b/skimage/io/tests/test_collection.py @@ -1,88 +1,72 @@ -import sys import os.path import numpy as np -from numpy.testing import (assert_raises, - assert_equal, - assert_array_almost_equal, - ) -from numpy.testing.decorators import skipif +from numpy.testing import assert_raises, assert_equal, assert_allclose from skimage import data_dir -from skimage.io import ImageCollection, MultiImage -from skimage.io.collection import alphanumeric_key -from skimage.io import Image as ioImage - -import six +from skimage.io.collection import ImageCollection, alphanumeric_key -try: - from PIL import Image -except ImportError: - PIL_available = False -else: - PIL_available = True +def test_string_split(): + test_string = 'z23a' + test_str_result = ['z', 23, 'a'] + assert_equal(alphanumeric_key(test_string), test_str_result) -class TestAlphanumericKey(): - def setUp(self): - self.test_string = 'z23a' - self.test_str_result = ['z', 23, 'a'] - self.filenames = ['f9.10.png', 'f9.9.png', 'f10.10.png', 'f10.9.png', - 'e9.png', 'e10.png', 'em.png'] - self.sorted_filenames = \ - ['e9.png', 'e10.png', 'em.png', 'f9.9.png', 'f9.10.png', - 'f10.9.png', 'f10.10.png'] - - def test_string_split(self): - assert_equal(alphanumeric_key(self.test_string), self.test_str_result) - - def test_string_sort(self): - sorted_filenames = sorted(self.filenames, key=alphanumeric_key) - assert_equal(sorted_filenames, self.sorted_filenames) +def test_string_sort(): + filenames = ['f9.10.png', 'f9.9.png', 'f10.10.png', 'f10.9.png', + 'e9.png', 'e10.png', 'em.png'] + sorted_filenames = ['e9.png', 'e10.png', 'em.png', 'f9.9.png', + 'f9.10.png', 'f10.9.png', 'f10.10.png'] + sorted_filenames = sorted(filenames, key=alphanumeric_key) + assert_equal(sorted_filenames, sorted_filenames) class TestImageCollection(): - pattern = [os.path.join(data_dir, pic) for pic in ['camera.png', - 'color.png']] - pattern_matched = [os.path.join(data_dir, pic) for pic in - ['camera.png', 'moon.png']] + + pattern = [os.path.join(data_dir, pic) + for pic in ['camera.png', 'color.png']] + + pattern_matched = [os.path.join(data_dir, pic) + for pic in ['camera.png', 'moon.png']] def setUp(self): - self.collection = ImageCollection(self.pattern) - self.collection_matched = ImageCollection(self.pattern_matched) + # Generic image collection with images of different shapes. + self.images = ImageCollection(self.pattern) + # Image collection with images having shapes that match. + self.images_matched = ImageCollection(self.pattern_matched) def test_len(self): - assert len(self.collection) == 2 + assert len(self.images) == 2 def test_getitem(self): - num = len(self.collection) + num = len(self.images) for i in range(-num, num): - assert type(self.collection[i]) is np.ndarray - assert_array_almost_equal(self.collection[0], - self.collection[-num]) + assert type(self.images[i]) is np.ndarray + assert_allclose(self.images[0], + self.images[-num]) - #assert_raises expects a callable, hence this do-very-little func + # assert_raises expects a callable, hence this thin wrapper function. def return_img(n): - return self.collection[n] + return self.images[n] assert_raises(IndexError, return_img, num) assert_raises(IndexError, return_img, -num - 1) def test_slicing(self): - assert type(self.collection[:]) is ImageCollection - assert len(self.collection[:]) == 2 - assert len(self.collection[:1]) == 1 - assert len(self.collection[1:]) == 1 - assert_array_almost_equal(self.collection[0], self.collection[:1][0]) - assert_array_almost_equal(self.collection[1], self.collection[1:][0]) - assert_array_almost_equal(self.collection[1], self.collection[::-1][0]) - assert_array_almost_equal(self.collection[0], self.collection[::-1][1]) + assert type(self.images[:]) is ImageCollection + assert len(self.images[:]) == 2 + assert len(self.images[:1]) == 1 + assert len(self.images[1:]) == 1 + assert_allclose(self.images[0], self.images[:1][0]) + assert_allclose(self.images[1], self.images[1:][0]) + assert_allclose(self.images[1], self.images[::-1][0]) + assert_allclose(self.images[0], self.images[::-1][1]) def test_files_property(self): - assert isinstance(self.collection.files, list) + assert isinstance(self.images.files, list) def set_files(f): - self.collection.files = f + self.images.files = f assert_raises(AttributeError, set_files, 'newfiles') def test_custom_load(self): @@ -95,59 +79,12 @@ class TestImageCollection(): assert_equal(ic[1], (2, 'two')) def test_concatenate(self): - ar = self.collection_matched.concatenate() - assert_equal(ar.shape, (len(self.collection_matched),) + - self.collection[0].shape) - assert_raises(ValueError, self.collection.concatenate) + array = self.images_matched.concatenate() + expected_shape = (len(self.images_matched),) + self.images[0].shape + assert_equal(array.shape, expected_shape) - -class TestMultiImage(): - - def setUp(self): - # This multipage TIF file was created with imagemagick: - # convert im1.tif im2.tif -adjoin multipage.tif - if PIL_available: - self.img = MultiImage(os.path.join(data_dir, 'multipage.tif')) - - @skipif(not PIL_available) - def test_len(self): - assert len(self.img) == 2 - - @skipif(not PIL_available) - def test_getitem(self): - num = len(self.img) - for i in range(-num, num): - assert type(self.img[i]) is np.ndarray - assert_array_almost_equal(self.img[0], - self.img[-num]) - - #assert_raises expects a callable, hence this do-very-little func - def return_img(n): - return self.img[n] - assert_raises(IndexError, return_img, num) - assert_raises(IndexError, return_img, -num - 1) - - @skipif(not PIL_available) - def test_files_property(self): - assert isinstance(self.img.filename, six.string_types) - - def set_filename(f): - self.img.filename = f - assert_raises(AttributeError, set_filename, 'newfile') - - @skipif(not PIL_available) - def test_conserve_memory_property(self): - assert isinstance(self.img.conserve_memory, bool) - - def set_mem(val): - self.img.conserve_memory = val - assert_raises(AttributeError, set_mem, True) - - @skipif(not PIL_available) - def test_concatenate(self): - ar = self.img.concatenate() - assert_equal(ar.shape, (len(self.img),) + - self.img[0].shape) + def test_concatentate_mismatched_image_shapes(self): + assert_raises(ValueError, self.images.concatenate) if __name__ == "__main__": diff --git a/skimage/io/tests/test_inherited_config.py b/skimage/io/tests/test_inherited_config.py deleted file mode 100644 index 207d3cf1..00000000 --- a/skimage/io/tests/test_inherited_config.py +++ /dev/null @@ -1,47 +0,0 @@ -from skimage.io.inherited_config import InheritedConfig - - -def test_get_non_existent(): - config = InheritedConfig() - assert config.get('size') is None - - -def test_get_simple(): - config = InheritedConfig({'imread': 'matplotlib'}) - assert config.get('imread') == 'matplotlib' - - -def test_get_default(): - config = InheritedConfig() - assert config.get('size', 10) == 10 - - -def test_get_best(): - config = InheritedConfig({'size': 0, 'size.text': 1}) - assert config.get('size.text') == 1 - - -def test_get_multi_level(): - config = InheritedConfig({'size': 0}) - assert config.get('size.text.title') == 0 - config['size.text'] = 1 - assert config.get('size.text.title') == 1 - config['size.text.title'] = 2 - assert config.get('size.text.title') == 2 - - -def test_contains(): - config = InheritedConfig({'imread': 'matplotlib'}) - assert 'imread' in config - assert 'imread.jpg' in config - - -def test_getitem(): - config = InheritedConfig({'imread': 'a'}) - assert config['imread'] == 'a' - assert config['imread.png'] == 'a' - - -if __name__ == '__main__': - from numpy import testing - testing.run_module_suite() diff --git a/skimage/io/tests/test_multi_image.py b/skimage/io/tests/test_multi_image.py new file mode 100644 index 00000000..ebaa71dc --- /dev/null +++ b/skimage/io/tests/test_multi_image.py @@ -0,0 +1,69 @@ +import os + +import numpy as np +from numpy.testing.decorators import skipif +from numpy.testing import assert_raises, assert_equal, assert_allclose + +from skimage import data_dir +from skimage.io.collection import MultiImage + +try: + from PIL import Image +except ImportError: + PIL_available = False +else: + PIL_available = True + +import six + + +class TestMultiImage(): + + def setUp(self): + # This multipage TIF file was created with imagemagick: + # convert im1.tif im2.tif -adjoin multipage.tif + if PIL_available: + self.img = MultiImage(os.path.join(data_dir, 'multipage.tif')) + + @skipif(not PIL_available) + def test_len(self): + assert len(self.img) == 2 + + @skipif(not PIL_available) + def test_getitem(self): + num = len(self.img) + for i in range(-num, num): + assert type(self.img[i]) is np.ndarray + assert_allclose(self.img[0], self.img[-num]) + + # assert_raises expects a callable, hence this thin wrapper function. + def return_img(n): + return self.img[n] + assert_raises(IndexError, return_img, num) + assert_raises(IndexError, return_img, -num - 1) + + @skipif(not PIL_available) + def test_files_property(self): + assert isinstance(self.img.filename, six.string_types) + + def set_filename(f): + self.img.filename = f + assert_raises(AttributeError, set_filename, 'newfile') + + @skipif(not PIL_available) + def test_conserve_memory_property(self): + assert isinstance(self.img.conserve_memory, bool) + + def set_mem(val): + self.img.conserve_memory = val + assert_raises(AttributeError, set_mem, True) + + @skipif(not PIL_available) + def test_concatenate(self): + array = self.img.concatenate() + assert_equal(array.shape, (len(self.img),) + self.img[0].shape) + + +if __name__ == "__main__": + from numpy.testing import run_module_suite + run_module_suite() From f04e14272f1b45c827701b61e4a0df04a0f571fc Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 7 Dec 2013 01:07:17 -0600 Subject: [PATCH 32/35] Fix doctest which causes side-effects. I'm not sure that there's a safe way to write this example as a doctest, but I think this will pass on Travis (although it still has side-effects. --- skimage/io/manage_plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/io/manage_plugins.py b/skimage/io/manage_plugins.py index ace57d8f..08bf1703 100644 --- a/skimage/io/manage_plugins.py +++ b/skimage/io/manage_plugins.py @@ -202,10 +202,10 @@ def use_plugin(name, kind=None): Examples -------- - To use a plugin named 'null' as the default image reader, you would write: + To use Matplotlib as the default image reader, you would write: >>> from skimage import io - >>> io.use_plugin('null', 'imread') + >>> io.use_plugin('matplotlib', 'imread') To see a list of available plugins run ``io.available_plugins``. Note that this lists plugins that are defined, but the full list may not be usable From b715b79c31c891ab1e91c1ac54738131cfdb4bf3 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 7 Dec 2013 10:26:56 -0600 Subject: [PATCH 33/35] Test Image._repr_png_ --- skimage/io/tests/test_image.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/skimage/io/tests/test_image.py b/skimage/io/tests/test_image.py index 6c54695b..ef63ef95 100644 --- a/skimage/io/tests/test_image.py +++ b/skimage/io/tests/test_image.py @@ -1,7 +1,12 @@ -from skimage.io import Image +from io import BytesIO + +import numpy as np +from skimage import img_as_ubyte +from skimage.io import Image, imread from numpy.testing import assert_equal, assert_array_equal + def test_tags(): f = Image([1, 2, 3], foo='bar', sigma='delta') g = Image([3, 2, 1], sun='moon') @@ -11,7 +16,17 @@ def test_tags(): assert_array_equal((g + 2).tags['sun'], 'moon') assert_equal(h.tags, {}) + +def test_repr_png_roundtrip(): + original_array = 255 * np.ones((3, 3), dtype=np.uint8) + image = Image(original_array) + array = imread(BytesIO(image._repr_png_())) + # Force output to ubyte range for plugin compatibility. + # For example, Matplotlib will return floats even if the image is uint8. + assert_array_equal(img_as_ubyte(array), original_array) + # Note that PIL breaks with `_repr_jpeg_`. + + if __name__ == "__main__": from numpy.testing import run_module_suite run_module_suite() - From f7fa4049f55b1bfc7818ca89694127ef044a5f30 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 7 Dec 2013 10:41:03 -0600 Subject: [PATCH 34/35] Test null plugin --- skimage/io/tests/test_null.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 skimage/io/tests/test_null.py diff --git a/skimage/io/tests/test_null.py b/skimage/io/tests/test_null.py new file mode 100644 index 00000000..6d3913d9 --- /dev/null +++ b/skimage/io/tests/test_null.py @@ -0,0 +1,35 @@ +import os +import warnings + +import numpy as np +from numpy.testing import raises + +from skimage import io +from skimage import data_dir + + +@raises(Warning) +def test_null_imread(): + path = os.path.join(data_dir, 'color.png') + with warnings.catch_warnings(): # Temporarily set warnings as errors. + warnings.filterwarnings('error') + io.imread(path, plugin='null') + + +@raises(Warning) +def test_null_imsave(): + with warnings.catch_warnings(): # Temporarily set warnings as errors. + warnings.filterwarnings('error') + io.imsave('dummy.png', np.zeros((3, 3)), plugin='null') + + +@raises(Warning) +def test_null_imshow(): + with warnings.catch_warnings(): # Temporarily set warnings as errors. + warnings.filterwarnings('error') + io.imshow(np.zeros((3, 3)), plugin='null') + + +if __name__ == '__main__': + from numpy.testing import run_module_suite + run_module_suite() From d7cf60ef0b554a458a20bf8803e376a1a45a1cf4 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 7 Dec 2013 11:45:37 -0600 Subject: [PATCH 35/35] Attempt to fix failing test on Python 3 buildbot --- skimage/io/tests/test_image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/io/tests/test_image.py b/skimage/io/tests/test_image.py index ef63ef95..b74c973e 100644 --- a/skimage/io/tests/test_image.py +++ b/skimage/io/tests/test_image.py @@ -18,7 +18,8 @@ def test_tags(): def test_repr_png_roundtrip(): - original_array = 255 * np.ones((3, 3), dtype=np.uint8) + # Use RGB-like shape since some backends convert grayscale to RGB + original_array = 255 * np.ones((5, 5, 3), dtype=np.uint8) image = Image(original_array) array = imread(BytesIO(image._repr_png_())) # Force output to ubyte range for plugin compatibility.