Last active
April 9, 2020 12:21
-
-
Save Jongbhin/3d365b8409187039b2475d8d9944f138 to your computer and use it in GitHub Desktop.
[PIL to scikit-image] #pil #scikit-image
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
__all__ = ['imread', 'imsave'] | |
from distutils.version import LooseVersion | |
import warnings | |
import numpy as np | |
from PIL import Image, __version__ as pil_version | |
from ...util import img_as_ubyte, img_as_uint | |
def imread(fname, dtype=None, img_num=None, **kwargs): | |
"""Load an image from file. | |
Parameters | |
---------- | |
fname : str or file | |
File name or file-like-object. | |
dtype : numpy dtype object or string specifier | |
Specifies data type of array elements. | |
img_num : int, optional | |
Specifies which image to read in a file with multiple images | |
(zero-indexed). | |
kwargs : keyword pairs, optional | |
Addition keyword arguments to pass through. | |
Notes | |
----- | |
Files are read using the Python Imaging Library. | |
See PIL docs [1]_ for a list of supported formats. | |
References | |
---------- | |
.. [1] http://pillow.readthedocs.org/en/latest/handbook/image-file-formats.html | |
""" | |
if isinstance(fname, str): | |
with open(fname, 'rb') as f: | |
im = Image.open(f) | |
return pil_to_ndarray(im, dtype=dtype, img_num=img_num) | |
else: | |
im = Image.open(fname) | |
if im.format == 'MPO' and LooseVersion(pil_version) < '6.0.0': | |
warnings.warn("You are trying to read a MPO image. " | |
"To ensure a good support of this format, " | |
"please upgrade pillow to 6.0.0 version or later.", | |
stacklevel=2) | |
return pil_to_ndarray(im, dtype=dtype, img_num=img_num) | |
def pil_to_ndarray(image, dtype=None, img_num=None): | |
"""Import a PIL Image object to an ndarray, in memory. | |
Parameters | |
---------- | |
Refer to ``imread``. | |
""" | |
try: | |
# this will raise an IOError if the file is not readable | |
image.getdata()[0] | |
except IOError as e: | |
site = "http://pillow.readthedocs.org/en/latest/installation.html#external-libraries" | |
pillow_error_message = str(e) | |
error_message = ('Could not load "%s" \n' | |
'Reason: "%s"\n' | |
'Please see documentation at: %s' | |
% (image.filename, pillow_error_message, site)) | |
raise ValueError(error_message) | |
frames = [] | |
grayscale = None | |
i = 0 | |
while 1: | |
try: | |
image.seek(i) | |
except EOFError: | |
break | |
frame = image | |
if img_num is not None and img_num != i: | |
image.getdata()[0] | |
i += 1 | |
continue | |
if image.format == 'PNG' and image.mode == 'I' and dtype is None: | |
dtype = 'uint16' | |
if image.mode == 'P': | |
if grayscale is None: | |
grayscale = _palette_is_grayscale(image) | |
if grayscale: | |
frame = image.convert('L') | |
else: | |
if image.format == 'PNG' and 'transparency' in image.info: | |
frame = image.convert('RGBA') | |
else: | |
frame = image.convert('RGB') | |
elif image.mode == '1': | |
frame = image.convert('L') | |
elif 'A' in image.mode: | |
frame = image.convert('RGBA') | |
elif image.mode == 'CMYK': | |
frame = image.convert('RGB') | |
if image.mode.startswith('I;16'): | |
shape = image.size | |
dtype = '>u2' if image.mode.endswith('B') else '<u2' | |
if 'S' in image.mode: | |
dtype = dtype.replace('u', 'i') | |
frame = np.fromstring(frame.tobytes(), dtype) | |
frame.shape = shape[::-1] | |
else: | |
frame = np.array(frame, dtype=dtype) | |
frames.append(frame) | |
i += 1 | |
if img_num is not None: | |
break | |
if hasattr(image, 'fp') and image.fp: | |
image.fp.close() | |
if img_num is None and len(frames) > 1: | |
return np.array(frames) | |
elif frames: | |
return frames[0] | |
elif img_num: | |
raise IndexError('Could not find image #%s' % img_num) | |
def _palette_is_grayscale(pil_image): | |
"""Return True if PIL image in palette mode is grayscale. | |
Parameters | |
---------- | |
pil_image : PIL image | |
PIL Image that is in Palette mode. | |
Returns | |
------- | |
is_grayscale : bool | |
True if all colors in image palette are gray. | |
""" | |
if pil_image.mode != 'P': | |
raise ValueError('pil_image.mode must be equal to "P".') | |
# get palette as an array with R, G, B columns | |
palette = np.asarray(pil_image.getpalette()).reshape((256, 3)) | |
# Not all palette colors are used; unused colors have junk values. | |
start, stop = pil_image.getextrema() | |
valid_palette = palette[start:stop + 1] | |
# Image is grayscale if channel differences (R - G and G - B) | |
# are all zero. | |
return np.allclose(np.diff(valid_palette), 0) | |
def ndarray_to_pil(arr, format_str=None): | |
"""Export an ndarray to a PIL object. | |
Parameters | |
---------- | |
Refer to ``imsave``. | |
""" | |
if arr.ndim == 3: | |
arr = img_as_ubyte(arr) | |
mode = {3: 'RGB', 4: 'RGBA'}[arr.shape[2]] | |
elif format_str in ['png', 'PNG']: | |
mode = 'I;16' | |
mode_base = 'I' | |
if arr.dtype.kind == 'f': | |
arr = img_as_uint(arr) | |
elif arr.max() < 256 and arr.min() >= 0: | |
arr = arr.astype(np.uint8) | |
mode = mode_base = 'L' | |
else: | |
arr = img_as_uint(arr) | |
else: | |
arr = img_as_ubyte(arr) | |
mode = 'L' | |
mode_base = 'L' | |
try: | |
array_buffer = arr.tobytes() | |
except AttributeError: | |
array_buffer = arr.tostring() # Numpy < 1.9 | |
if arr.ndim == 2: | |
im = Image.new(mode_base, arr.T.shape) | |
try: | |
im.frombytes(array_buffer, 'raw', mode) | |
except AttributeError: | |
im.fromstring(array_buffer, 'raw', mode) # PIL 1.1.7 | |
else: | |
image_shape = (arr.shape[1], arr.shape[0]) | |
try: | |
im = Image.frombytes(mode, image_shape, array_buffer) | |
except AttributeError: | |
im = Image.fromstring(mode, image_shape, array_buffer) # PIL 1.1.7 | |
return im | |
def imsave(fname, arr, format_str=None, **kwargs): | |
"""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 | |
kwargs: dict | |
Keyword arguments to the Pillow save function (or tifffile save | |
function, for Tiff files). These are format dependent. For example, | |
Pillow's JPEG save function supports an integer ``quality`` argument | |
with values in [1, 95], while TIFFFile supports a ``compress`` | |
integer argument with values in [0, 9]. | |
Notes | |
----- | |
Use the Python Imaging Library. | |
See PIL docs [1]_ for a list of other supported formats. | |
All images besides single channel PNGs are converted using `img_as_uint8`. | |
Single Channel PNGs have the following behavior: | |
- Integer values in [0, 255] and Boolean types -> img_as_uint8 | |
- Floating point and other integers -> img_as_uint16 | |
References | |
---------- | |
.. [1] http://pillow.readthedocs.org/en/latest/handbook/image-file-formats.html | |
""" | |
# default to PNG if file-like object | |
if not isinstance(fname, str) and format_str is None: | |
format_str = "PNG" | |
# Check for png in filename | |
if (isinstance(fname, str) | |
and fname.lower().endswith(".png")): | |
format_str = "PNG" | |
arr = np.asanyarray(arr) | |
if arr.dtype.kind == 'b': | |
arr = arr.astype(np.uint8) | |
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.") | |
img = ndarray_to_pil(arr, format_str=format_str) | |
img.save(fname, format=format_str, **kwargs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment