From b03683d6c53f1f5234964b39cdb9513a36f2e73f Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Thu, 24 Jul 2014 21:59:06 -0700 Subject: [PATCH 01/11] pil_plugin can import and export from memory - added capability to import and export to PIL Image objects, not just files - allows for converting to and from PIL image objects without writing to disk - no unit test yet --- skimage/io/_plugins/pil_plugin.py | 62 ++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/skimage/io/_plugins/pil_plugin.py b/skimage/io/_plugins/pil_plugin.py index e6f9c491..80104f68 100644 --- a/skimage/io/_plugins/pil_plugin.py +++ b/skimage/io/_plugins/pil_plugin.py @@ -21,7 +21,14 @@ def imread(fname, dtype=None): """ im = Image.open(fname) - fp = im.fp + return pil_to_ndarray(im, dtype) + + +def pil_to_ndarray(im, dtype=None): + """import an image from a PIL Image object in memory + + """ + fp = im.fp if hasattr(im, 'fp') else None if im.mode == 'P': if _palette_is_grayscale(im): im = im.convert('L') @@ -37,7 +44,8 @@ def imread(fname, dtype=None): elif 'A' in im.mode: im = im.convert('RGBA') im = np.array(im, dtype=dtype) - fp.close() + if fp: + fp.close() return im @@ -65,24 +73,12 @@ def _palette_is_grayscale(pil_image): return np.allclose(np.diff(valid_palette), 0) -def imsave(fname, arr, format_str=None): - """Save an image to disk. +def ndarray_to_pil(arr, format_str=None): + """Export an image as PIL object Parameters ---------- - fname : str or file-like object - Name of destination file. - arr : ndarray of uint8 or float - Array (image) to save. Arrays of data-type uint8 should have - values in [0, 255], whereas floating-point arrays must be - in [0, 1]. - format_str: str - Format to save as, this is defaulted to PNG if using a file-like - object; this will be derived from the extension if fname is a string - - Notes - ----- - Currently, only 8-bit precision is supported. + See imsave """ arr = np.asarray(arr).squeeze() @@ -109,10 +105,6 @@ def imsave(fname, arr, format_str=None): # Force all integers to bytes arr = arr.astype(np.uint8) - # default to PNG if file-like object - if not isinstance(fname, string_types) and format_str is None: - format_str = "PNG" - try: img = Image.frombytes(mode, (arr.shape[1], arr.shape[0]), arr.tostring()) @@ -120,6 +112,34 @@ def imsave(fname, arr, format_str=None): img = Image.fromstring(mode, (arr.shape[1], arr.shape[0]), arr.tostring()) + return img + + +def imsave(fname, arr, format_str=None): + """Save an image to disk. + + Parameters + ---------- + fname : str or file-like object + Name of destination file. + arr : ndarray of uint8 or float + Array (image) to save. Arrays of data-type uint8 should have + values in [0, 255], whereas floating-point arrays must be + in [0, 1]. + format_str: str + Format to save as, this is defaulted to PNG if using a file-like + object; this will be derived from the extension if fname is a string + + Notes + ----- + Currently, only 8-bit precision is supported. + + """ + # default to PNG if file-like object + if not isinstance(fname, string_types) and format_str is None: + format_str = "PNG" + + img = ndarray_to_pil(arr, format_str=None) img.save(fname, format=format_str) From 1dcb60fd68fdf20b420d6d14b2549eba3d4f8781 Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Thu, 24 Jul 2014 22:49:57 -0700 Subject: [PATCH 02/11] added tests for roundtrip export and import - and corrected error in imexport - wasn't using imsave code correctly - split imsave into imsave and imexport functions to reuse imsave code --- skimage/io/_plugins/pil_plugin.py | 1 + skimage/io/tests/test_pil.py | 35 +++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/skimage/io/_plugins/pil_plugin.py b/skimage/io/_plugins/pil_plugin.py index 80104f68..9510f4d0 100644 --- a/skimage/io/_plugins/pil_plugin.py +++ b/skimage/io/_plugins/pil_plugin.py @@ -115,6 +115,7 @@ def ndarray_to_pil(arr, format_str=None): return img + def imsave(fname, arr, format_str=None): """Save an image to disk. diff --git a/skimage/io/tests/test_pil.py b/skimage/io/tests/test_pil.py index c5718388..e5e7647c 100644 --- a/skimage/io/tests/test_pil.py +++ b/skimage/io/tests/test_pil.py @@ -14,7 +14,7 @@ from six import BytesIO try: from PIL import Image - from skimage.io._plugins.pil_plugin import _palette_is_grayscale + from skimage.io._plugins.pil_plugin import imimport, imexport, _palette_is_grayscale use_plugin('pil') except ImportError: PIL_available = False @@ -113,26 +113,40 @@ def test_imread_uint16_big_endian(): class TestSave: - def roundtrip(self, dtype, x, scaling=1): + def roundtrip_file(self, x): f = NamedTemporaryFile(suffix='.png') fname = f.name f.close() imsave(fname, x) y = imread(fname) + return y + def roundtrip_pil_image(self, x): + pil_image = imexport(x) + y = imimport(pil_image) + return y + + def verify_roundtrip(self, dtype, x, y, scaling=1): assert_array_almost_equal((x * scaling).astype(np.int32), y) - @skipif(not PIL_available) - def test_imsave_roundtrip(self): + def verify_imsave_roundtrip(self, roundtrip_function): for shape in [(10, 10), (10, 10, 3), (10, 10, 4)]: for dtype in (np.uint8, np.uint16, np.float32, np.float64): x = np.ones(shape, dtype=dtype) * np.random.rand(*shape) if np.issubdtype(dtype, float): - yield self.roundtrip, dtype, x, 255 + yield self.verify_roundtrip, dtype, x, roundtrip_function(x), 255 else: x = (x * 255).astype(dtype) - yield self.roundtrip, dtype, x + yield self.verify_roundtrip, dtype, x, roundtrip_function(x) + + @skipif(not PIL_available) + def test_imsave_roundtrip_file(self): + self.verify_imsave_roundtrip(self.roundtrip_file) + + @skipif(not PIL_available) + def test_imsave_roundtrip_pil_image(self): + self.verify_imsave_roundtrip(self.roundtrip_pil_image) @skipif(not PIL_available) @@ -151,5 +165,14 @@ def test_imsave_filelike(): assert_allclose(out, image) +@skipif(not PIL_available) +def test_imexport_imimport(): + shape = (2, 2) + image = np.zeros(shape) + pil_image = imexport(image) + out = imimport(pil_image) + assert out.shape == shape + + if __name__ == "__main__": run_module_suite() From 32061fb66d49f27e78ce15a639e608f939c61e18 Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Thu, 24 Jul 2014 22:59:14 -0700 Subject: [PATCH 03/11] added a space character to trigger travis build --- skimage/io/tests/test_pil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/io/tests/test_pil.py b/skimage/io/tests/test_pil.py index e5e7647c..b84bf074 100644 --- a/skimage/io/tests/test_pil.py +++ b/skimage/io/tests/test_pil.py @@ -167,7 +167,7 @@ def test_imsave_filelike(): @skipif(not PIL_available) def test_imexport_imimport(): - shape = (2, 2) + shape = (2, 2) image = np.zeros(shape) pil_image = imexport(image) out = imimport(pil_image) From 4a0b99ad6ff2d5fd7e87a80b752598e2a8646c48 Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Fri, 25 Jul 2014 07:10:50 -0700 Subject: [PATCH 04/11] fixed bad indent --- skimage/io/tests/test_pil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/io/tests/test_pil.py b/skimage/io/tests/test_pil.py index b84bf074..e5e7647c 100644 --- a/skimage/io/tests/test_pil.py +++ b/skimage/io/tests/test_pil.py @@ -167,7 +167,7 @@ def test_imsave_filelike(): @skipif(not PIL_available) def test_imexport_imimport(): - shape = (2, 2) + shape = (2, 2) image = np.zeros(shape) pil_image = imexport(image) out = imimport(pil_image) From 1cb73c79d65fb3f093b92321fed741eea190b93e Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Sun, 3 Aug 2014 10:46:06 -0700 Subject: [PATCH 05/11] improved name of pil import/export functions - to make it clear what the functions actually do: ndarray_to_pil() and pil_to_ndarray() - as per code review feedback - updated tests to use the new function names - incoming change to pil_plugin assumed PIL Image objects will always be from files; this is not always true anymore. Now pil_plugin checks to see if the Image came from a file before trying to close its associated file. --- skimage/io/tests/test_pil.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skimage/io/tests/test_pil.py b/skimage/io/tests/test_pil.py index e5e7647c..5cd36f92 100644 --- a/skimage/io/tests/test_pil.py +++ b/skimage/io/tests/test_pil.py @@ -14,7 +14,7 @@ from six import BytesIO try: from PIL import Image - from skimage.io._plugins.pil_plugin import imimport, imexport, _palette_is_grayscale + from skimage.io._plugins.pil_plugin import pil_to_ndarray, ndarray_to_pil, _palette_is_grayscale use_plugin('pil') except ImportError: PIL_available = False @@ -122,8 +122,8 @@ class TestSave: return y def roundtrip_pil_image(self, x): - pil_image = imexport(x) - y = imimport(pil_image) + pil_image = ndarray_to_pil(x) + y = pil_to_ndarray(pil_image) return y def verify_roundtrip(self, dtype, x, y, scaling=1): @@ -169,8 +169,8 @@ def test_imsave_filelike(): def test_imexport_imimport(): shape = (2, 2) image = np.zeros(shape) - pil_image = imexport(image) - out = imimport(pil_image) + pil_image = ndarray_to_pil(image) + out = pil_to_ndarray(pil_image) assert out.shape == shape From 6d9a3c3913ff6411929c062c3939f6f0493f3ace Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Wed, 6 Aug 2014 21:14:00 -0700 Subject: [PATCH 06/11] reformat comments to be standard format - clarified function names - comments in standard format - removed extra blank line - as per code review feedback --- skimage/io/_plugins/pil_plugin.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/skimage/io/_plugins/pil_plugin.py b/skimage/io/_plugins/pil_plugin.py index 9510f4d0..9e7bb499 100644 --- a/skimage/io/_plugins/pil_plugin.py +++ b/skimage/io/_plugins/pil_plugin.py @@ -19,13 +19,25 @@ from six import string_types def imread(fname, dtype=None): """Load an image from file. + Parameters + ---------- + fname : file name as string + dtype : numpy dtype object or string specifier + Specifies data type of array elements. + + """ im = Image.open(fname) return pil_to_ndarray(im, dtype) def pil_to_ndarray(im, dtype=None): - """import an image from a PIL Image object in memory + """Import a PIL Image object to an ndarray, in memory. + + Parameters + ---------- + Refer to ``imread``. + """ fp = im.fp if hasattr(im, 'fp') else None @@ -74,11 +86,11 @@ def _palette_is_grayscale(pil_image): def ndarray_to_pil(arr, format_str=None): - """Export an image as PIL object + """Export an ndarray to a PIL object. Parameters ---------- - See imsave + Refer to ``imsave``. """ arr = np.asarray(arr).squeeze() @@ -115,7 +127,6 @@ def ndarray_to_pil(arr, format_str=None): return img - def imsave(fname, arr, format_str=None): """Save an image to disk. From 0ff30f6f4cdc34e86d46b29be1e7d0dbc78b152f Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Wed, 6 Aug 2014 21:16:21 -0700 Subject: [PATCH 07/11] added Adam Feuer (myself) to contributors file - as per code review feedback from @stefanv --- CONTRIBUTORS.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 140842ab..30f432f4 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -182,3 +182,6 @@ - Axel Donath Blob Detection + +- Adam Feuer + PIL Image import and export improvements From 05380823cc464c9edac48f198b7b7648ae9ea7eb Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Sat, 9 Aug 2014 09:01:07 -0700 Subject: [PATCH 08/11] updated imread parameters comment to standard format - with 'str' file type (instead of 'string') --- skimage/io/_plugins/pil_plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/io/_plugins/pil_plugin.py b/skimage/io/_plugins/pil_plugin.py index 9e7bb499..7e5e90b2 100644 --- a/skimage/io/_plugins/pil_plugin.py +++ b/skimage/io/_plugins/pil_plugin.py @@ -21,7 +21,8 @@ def imread(fname, dtype=None): Parameters ---------- - fname : file name as string + fname : str + File name. dtype : numpy dtype object or string specifier Specifies data type of array elements. From 25f08ff68edfd531ac7ea04c8427e6b85eaacd2d Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Sat, 9 Aug 2014 09:03:06 -0700 Subject: [PATCH 09/11] removing trailing space (PEP8 formatting) --- skimage/io/_plugins/pil_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/io/_plugins/pil_plugin.py b/skimage/io/_plugins/pil_plugin.py index 7e5e90b2..917c49dc 100644 --- a/skimage/io/_plugins/pil_plugin.py +++ b/skimage/io/_plugins/pil_plugin.py @@ -41,7 +41,7 @@ def pil_to_ndarray(im, dtype=None): """ - fp = im.fp if hasattr(im, 'fp') else None + fp = im.fp if hasattr(im, 'fp') else None if im.mode == 'P': if _palette_is_grayscale(im): im = im.convert('L') From ef7da3be4a24272539f33d0f0376acfbf79562c4 Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Sat, 9 Aug 2014 09:04:05 -0700 Subject: [PATCH 10/11] explicitly checking for None as per code review feedback - instead of implicit (non-truthy) test --- skimage/io/_plugins/pil_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/io/_plugins/pil_plugin.py b/skimage/io/_plugins/pil_plugin.py index 917c49dc..406cafba 100644 --- a/skimage/io/_plugins/pil_plugin.py +++ b/skimage/io/_plugins/pil_plugin.py @@ -57,8 +57,8 @@ def pil_to_ndarray(im, dtype=None): elif 'A' in im.mode: im = im.convert('RGBA') im = np.array(im, dtype=dtype) - if fp: - fp.close() + if fp is not None: + fp.close() return im From 8494b8081afb8362e142aa426c619aeb3f6d5e05 Mon Sep 17 00:00:00 2001 From: Adam Feuer Date: Sat, 9 Aug 2014 10:37:11 -0700 Subject: [PATCH 11/11] removed trailing blank line in comment --- skimage/io/_plugins/pil_plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/skimage/io/_plugins/pil_plugin.py b/skimage/io/_plugins/pil_plugin.py index 406cafba..eb6467df 100644 --- a/skimage/io/_plugins/pil_plugin.py +++ b/skimage/io/_plugins/pil_plugin.py @@ -39,7 +39,6 @@ def pil_to_ndarray(im, dtype=None): ---------- Refer to ``imread``. - """ fp = im.fp if hasattr(im, 'fp') else None if im.mode == 'P':