From 8049c642afa2e2ba79682f6b68e005834c090011 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 5 Oct 2014 13:06:49 -0500 Subject: [PATCH] Add tests for new behavior. Add tests for new behavior Fix utils import and make io.tests a package --- skimage/io/_plugins/pil_plugin.py | 51 +++++++++++++------ skimage/io/tests/__init__.py | 0 skimage/io/tests/test_pil.py | 13 +++++ skimage/io/tests/utils.py | 82 +++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 skimage/io/tests/__init__.py create mode 100644 skimage/io/tests/utils.py diff --git a/skimage/io/_plugins/pil_plugin.py b/skimage/io/_plugins/pil_plugin.py index 77368178..2d548533 100644 --- a/skimage/io/_plugins/pil_plugin.py +++ b/skimage/io/_plugins/pil_plugin.py @@ -29,7 +29,12 @@ def imread(fname, dtype=None): """ - if fname.lower().endswith(('tif', 'tiff')) and dtype is None: + if hasattr(fname, 'name'): + name = fname.name.lower() + else: + name = fname.lower() + + if name.endswith(('.tiff', '.tif')) and dtype is None: return tif_imread(fname) im = Image.open(fname) @@ -104,18 +109,9 @@ def ndarray_to_pil(arr, format_str=None): Refer to ``imsave``. """ - arr = np.asarray(arr).squeeze() - - if arr.ndim not in (2, 3): - raise ValueError("Invalid shape for image array: %s" % arr.shape) - - if arr.ndim == 3: - if arr.shape[2] not in (3, 4): - raise ValueError("Invalid number of channels in image array.") - if arr.ndim == 3: arr = img_as_ubyte(arr) - mode_base = mode = {3: 'RGB', 4: 'RGBA'}[arr.shape[2]] + mode = {3: 'RGB', 4: 'RGBA'}[arr.shape[2]] elif arr.dtype.kind == 'f': arr = img_as_uint(arr) @@ -137,8 +133,16 @@ def ndarray_to_pil(arr, format_str=None): mode = 'I' mode_base = 'I' - im = Image.new(mode_base, arr.T.shape) - im.fromstring(arr.tostring(), 'raw', mode) + if arr.ndim == 2: + im = Image.new(mode_base, arr.T.shape) + im.fromstring(arr.tostring(), 'raw', mode) + else: + try: + im = Image.frombytes(mode, (arr.shape[1], arr.shape[0]), + arr.tostring()) + except AttributeError: + im = Image.fromstring(mode, (arr.shape[1], arr.shape[0]), + arr.tostring()) return im @@ -169,8 +173,25 @@ def imsave(fname, arr, format_str=None): if not isinstance(fname, string_types) and format_str is None: format_str = "PNG" - if fname.lower().endswith(('.tiff', '.tif')): - tif_imsave(fname) + if arr.ndim not in (2, 3): + raise ValueError("Invalid shape for image array: %s" % arr.shape) + + if arr.ndim == 3: + if arr.shape[2] not in (3, 4): + raise ValueError("Invalid number of channels in image array.") + + arr = np.asanyarray(arr).squeeze() + + if arr.dtype.kind == 'b': + arr = arr.astype(np.uint8) + + if hasattr(fname, 'name'): + name = fname.name.lower() + else: + name = fname.lower() + + if name.endswith(('.tiff', '.tif')): + tif_imsave(fname, arr) else: img = ndarray_to_pil(arr, format_str=None) diff --git a/skimage/io/tests/__init__.py b/skimage/io/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/skimage/io/tests/test_pil.py b/skimage/io/tests/test_pil.py index 5cd36f92..20ec5b8e 100644 --- a/skimage/io/tests/test_pil.py +++ b/skimage/io/tests/test_pil.py @@ -8,6 +8,7 @@ from tempfile import NamedTemporaryFile from skimage import data_dir from skimage.io import (imread, imsave, use_plugin, reset_plugins, Image as ioImage) +from skimage.io.test.utils import ubyte_check, full_range_check from six import BytesIO @@ -174,5 +175,17 @@ def test_imexport_imimport(): assert out.shape == shape +@skip(not PIL_available) +def test_all_color(self): + ubyte_check('pil') + ubyte_check('pil', 'bmp') + + +@skip(not PIL_available) +def test_all_mono(self): + full_range_check('pil') + full_range_check('pil', 'tiff') + + if __name__ == "__main__": run_module_suite() diff --git a/skimage/io/tests/utils.py b/skimage/io/tests/utils.py new file mode 100644 index 00000000..1c4482f3 --- /dev/null +++ b/skimage/io/tests/utils.py @@ -0,0 +1,82 @@ +from tempfile import NamedTemporaryFile + +from skimage import ( + data, io, img_as_uint, img_as_bool, img_as_float, img_as_int, img_as_ubyte) +from numpy import testing +import numpy as np + + +def roundtrip(img, plugin, suffix): + """Save and read an image using a specified plugin""" + if not '.' in suffix: + suffix = '.' + suffix + temp_file = NamedTemporaryFile(suffix=suffix, delete=False) + temp_file.close() + fname = temp_file.name + io.imsave(fname, img, plugin=plugin) + return io.imread(fname, plugin=plugin) + + +def ubyte_check(plugin, fmt='png'): + """Check roundtrip behavior for images that can only be saved as uint8 + + All major input types should be handled as ubytes and read + back correctly. + """ + img = img_as_ubyte(data.chelsea()) + r1 = roundtrip(img, plugin, fmt) + testing.assert_allclose(img, r1) + + img2 = img > 128 + r2 = roundtrip(img2, plugin, fmt) + testing.assert_allclose(img2, img_as_bool(r2)) + + img3 = img_as_float(img) + r3 = roundtrip(img3, plugin, fmt) + testing.assert_allclose(r3, img) + + img4 = img_as_int(img) + r4 = roundtrip(img4, plugin, fmt) + testing.assert_allclose(r4, img) + + img5 = img_as_uint(img) + r5 = roundtrip(img5, plugin, fmt) + testing.assert_allclose(r5, img) + + +def full_range_check(plugin, fmt='png'): + """Check the roundtrip behavior for images that support most types. + + All major input types should be handled, except bool is treated + as ubyte and float can treated as uint16 or float. + """ + + img = img_as_ubyte(data.moon()) + r1 = roundtrip(img, plugin, fmt) + testing.assert_allclose(img, r1) + + img2 = img > 128 + r2 = roundtrip(img2, plugin, fmt) + testing.assert_allclose(img2.astype(np.uint8), r2) + + img3 = img_as_float(img) + r3 = roundtrip(img3, plugin, fmt) + if r3.dtype.kind == 'f': + testing.assert_allclose(img3, r3) + else: + testing.assert_allclose(r3, img_as_uint(img)) + + img4 = img_as_int(img) + r4 = roundtrip(img4, plugin, fmt) + testing.assert_allclose(r4, img4) + + img5 = img_as_uint(img) + r5 = roundtrip(img5, plugin, fmt) + testing.assert_allclose(r5, img5) + + +if __name__ == '__main__': + ubyte_check('pil') + full_range_check('pil') + ubyte_check('pil', 'bmp') + full_range_check('pil', 'tiff')