Merge pull request #1083 from adamfeuer/import_export_pil_image_objects

pil_plugin can import and export PIL image objects from memory
This commit is contained in:
Juan Nunez-Iglesias
2014-08-09 19:48:31 -05:00
3 changed files with 85 additions and 27 deletions
+3
View File
@@ -182,3 +182,6 @@
- Axel Donath
Blob Detection
- Adam Feuer
PIL Image import and export improvements
+53 -21
View File
@@ -19,9 +19,28 @@ from six import string_types
def imread(fname, dtype=None):
"""Load an image from file.
Parameters
----------
fname : str
File name.
dtype : numpy dtype object or string specifier
Specifies data type of array elements.
"""
im = Image.open(fname)
fp = im.fp
return pil_to_ndarray(im, dtype)
def pil_to_ndarray(im, dtype=None):
"""Import a PIL Image object to an ndarray, in memory.
Parameters
----------
Refer to ``imread``.
"""
fp = im.fp if hasattr(im, 'fp') else None
if im.mode == 'P':
if _palette_is_grayscale(im):
im = im.convert('L')
@@ -37,7 +56,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 is not None:
fp.close()
return im
@@ -65,24 +85,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 ndarray to a 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.
Refer to ``imsave``.
"""
arr = np.asarray(arr).squeeze()
@@ -109,10 +117,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 +124,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)
+29 -6
View File
@@ -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 pil_to_ndarray, ndarray_to_pil, _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 = ndarray_to_pil(x)
y = pil_to_ndarray(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 = ndarray_to_pil(image)
out = pil_to_ndarray(pil_image)
assert out.shape == shape
if __name__ == "__main__":
run_module_suite()