123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- """
- A collection of image utilities using the Python Imaging Library (PIL).
- Note that PIL is not a dependency of SciPy and this module is not
- available on systems that don't have PIL installed.
- """
- from __future__ import division, print_function, absolute_import
- # Functions which need the PIL
- import numpy
- import tempfile
- from numpy import (amin, amax, ravel, asarray, arange, ones, newaxis,
- transpose, iscomplexobj, uint8, issubdtype, array)
- try:
- from PIL import Image, ImageFilter
- except ImportError:
- import Image
- import ImageFilter
- if not hasattr(Image, 'frombytes'):
- Image.frombytes = Image.fromstring
- __all__ = ['fromimage', 'toimage', 'imsave', 'imread', 'bytescale',
- 'imrotate', 'imresize', 'imshow', 'imfilter']
- @numpy.deprecate(message="`bytescale` is deprecated in SciPy 1.0.0, "
- "and will be removed in 1.2.0.")
- def bytescale(data, cmin=None, cmax=None, high=255, low=0):
- """
- Byte scales an array (image).
- Byte scaling means converting the input image to uint8 dtype and scaling
- the range to ``(low, high)`` (default 0-255).
- If the input image already has dtype uint8, no scaling is done.
- This function is only available if Python Imaging Library (PIL) is installed.
- Parameters
- ----------
- data : ndarray
- PIL image data array.
- cmin : scalar, optional
- Bias scaling of small values. Default is ``data.min()``.
- cmax : scalar, optional
- Bias scaling of large values. Default is ``data.max()``.
- high : scalar, optional
- Scale max value to `high`. Default is 255.
- low : scalar, optional
- Scale min value to `low`. Default is 0.
- Returns
- -------
- img_array : uint8 ndarray
- The byte-scaled array.
- Examples
- --------
- >>> from scipy.misc import bytescale
- >>> img = np.array([[ 91.06794177, 3.39058326, 84.4221549 ],
- ... [ 73.88003259, 80.91433048, 4.88878881],
- ... [ 51.53875334, 34.45808177, 27.5873488 ]])
- >>> bytescale(img)
- array([[255, 0, 236],
- [205, 225, 4],
- [140, 90, 70]], dtype=uint8)
- >>> bytescale(img, high=200, low=100)
- array([[200, 100, 192],
- [180, 188, 102],
- [155, 135, 128]], dtype=uint8)
- >>> bytescale(img, cmin=0, cmax=255)
- array([[91, 3, 84],
- [74, 81, 5],
- [52, 34, 28]], dtype=uint8)
- """
- if data.dtype == uint8:
- return data
- if high > 255:
- raise ValueError("`high` should be less than or equal to 255.")
- if low < 0:
- raise ValueError("`low` should be greater than or equal to 0.")
- if high < low:
- raise ValueError("`high` should be greater than or equal to `low`.")
- if cmin is None:
- cmin = data.min()
- if cmax is None:
- cmax = data.max()
- cscale = cmax - cmin
- if cscale < 0:
- raise ValueError("`cmax` should be larger than `cmin`.")
- elif cscale == 0:
- cscale = 1
- scale = float(high - low) / cscale
- bytedata = (data - cmin) * scale + low
- return (bytedata.clip(low, high) + 0.5).astype(uint8)
- @numpy.deprecate(message="`imread` is deprecated in SciPy 1.0.0, "
- "and will be removed in 1.2.0.\n"
- "Use ``imageio.imread`` instead.")
- def imread(name, flatten=False, mode=None):
- """
- Read an image from a file as an array.
- This function is only available if Python Imaging Library (PIL) is installed.
- Parameters
- ----------
- name : str or file object
- The file name or file object to be read.
- flatten : bool, optional
- If True, flattens the color layers into a single gray-scale layer.
- mode : str, optional
- Mode to convert image to, e.g. ``'RGB'``. See the Notes for more
- details.
- Returns
- -------
- imread : ndarray
- The array obtained by reading the image.
- Notes
- -----
- `imread` uses the Python Imaging Library (PIL) to read an image.
- The following notes are from the PIL documentation.
- `mode` can be one of the following strings:
- * 'L' (8-bit pixels, black and white)
- * 'P' (8-bit pixels, mapped to any other mode using a color palette)
- * 'RGB' (3x8-bit pixels, true color)
- * 'RGBA' (4x8-bit pixels, true color with transparency mask)
- * 'CMYK' (4x8-bit pixels, color separation)
- * 'YCbCr' (3x8-bit pixels, color video format)
- * 'I' (32-bit signed integer pixels)
- * 'F' (32-bit floating point pixels)
- PIL also provides limited support for a few special modes, including
- 'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa'
- (true color with premultiplied alpha).
- When translating a color image to black and white (mode 'L', 'I' or
- 'F'), the library uses the ITU-R 601-2 luma transform::
- L = R * 299/1000 + G * 587/1000 + B * 114/1000
- When `flatten` is True, the image is converted using mode 'F'.
- When `mode` is not None and `flatten` is True, the image is first
- converted according to `mode`, and the result is then flattened using
- mode 'F'.
- """
- im = Image.open(name)
- return fromimage(im, flatten=flatten, mode=mode)
- @numpy.deprecate(message="`imsave` is deprecated in SciPy 1.0.0, "
- "and will be removed in 1.2.0.\n"
- "Use ``imageio.imwrite`` instead.")
- def imsave(name, arr, format=None):
- """
- Save an array as an image.
- This function is only available if Python Imaging Library (PIL) is installed.
- .. warning::
- This function uses `bytescale` under the hood to rescale images to use
- the full (0, 255) range if ``mode`` is one of ``None, 'L', 'P', 'l'``.
- It will also cast data for 2-D images to ``uint32`` for ``mode=None``
- (which is the default).
- Parameters
- ----------
- name : str or file object
- Output file name or file object.
- arr : ndarray, MxN or MxNx3 or MxNx4
- Array containing image values. If the shape is ``MxN``, the array
- represents a grey-level image. Shape ``MxNx3`` stores the red, green
- and blue bands along the last dimension. An alpha layer may be
- included, specified as the last colour band of an ``MxNx4`` array.
- format : str
- Image format. If omitted, the format to use is determined from the
- file name extension. If a file object was used instead of a file name,
- this parameter should always be used.
- Examples
- --------
- Construct an array of gradient intensity values and save to file:
- >>> from scipy.misc import imsave
- >>> x = np.zeros((255, 255))
- >>> x = np.zeros((255, 255), dtype=np.uint8)
- >>> x[:] = np.arange(255)
- >>> imsave('gradient.png', x)
- Construct an array with three colour bands (R, G, B) and store to file:
- >>> rgb = np.zeros((255, 255, 3), dtype=np.uint8)
- >>> rgb[..., 0] = np.arange(255)
- >>> rgb[..., 1] = 55
- >>> rgb[..., 2] = 1 - np.arange(255)
- >>> imsave('rgb_gradient.png', rgb)
- """
- im = toimage(arr, channel_axis=2)
- if format is None:
- im.save(name)
- else:
- im.save(name, format)
- return
- @numpy.deprecate(message="`fromimage` is deprecated in SciPy 1.0.0. "
- "and will be removed in 1.2.0.\n"
- "Use ``np.asarray(im)`` instead.")
- def fromimage(im, flatten=False, mode=None):
- """
- Return a copy of a PIL image as a numpy array.
- This function is only available if Python Imaging Library (PIL) is installed.
- Parameters
- ----------
- im : PIL image
- Input image.
- flatten : bool
- If true, convert the output to grey-scale.
- mode : str, optional
- Mode to convert image to, e.g. ``'RGB'``. See the Notes of the
- `imread` docstring for more details.
- Returns
- -------
- fromimage : ndarray
- The different colour bands/channels are stored in the
- third dimension, such that a grey-image is MxN, an
- RGB-image MxNx3 and an RGBA-image MxNx4.
- """
- if not Image.isImageType(im):
- raise TypeError("Input is not a PIL image.")
- if mode is not None:
- if mode != im.mode:
- im = im.convert(mode)
- elif im.mode == 'P':
- # Mode 'P' means there is an indexed "palette". If we leave the mode
- # as 'P', then when we do `a = array(im)` below, `a` will be a 2-D
- # containing the indices into the palette, and not a 3-D array
- # containing the RGB or RGBA values.
- if 'transparency' in im.info:
- im = im.convert('RGBA')
- else:
- im = im.convert('RGB')
- if flatten:
- im = im.convert('F')
- elif im.mode == '1':
- # Workaround for crash in PIL. When im is 1-bit, the call array(im)
- # can cause a seg. fault, or generate garbage. See
- # https://github.com/scipy/scipy/issues/2138 and
- # https://github.com/python-pillow/Pillow/issues/350.
- #
- # This converts im from a 1-bit image to an 8-bit image.
- im = im.convert('L')
- a = array(im)
- return a
- _errstr = "Mode is unknown or incompatible with input array shape."
- @numpy.deprecate(message="`toimage` is deprecated in SciPy 1.0.0, "
- "and will be removed in 1.2.0.\n"
- "Use Pillow's ``Image.fromarray`` directly instead.")
- def toimage(arr, high=255, low=0, cmin=None, cmax=None, pal=None,
- mode=None, channel_axis=None):
- """Takes a numpy array and returns a PIL image.
- This function is only available if Python Imaging Library (PIL) is installed.
- The mode of the PIL image depends on the array shape and the `pal` and
- `mode` keywords.
- For 2-D arrays, if `pal` is a valid (N,3) byte-array giving the RGB values
- (from 0 to 255) then ``mode='P'``, otherwise ``mode='L'``, unless mode
- is given as 'F' or 'I' in which case a float and/or integer array is made.
- .. warning::
- This function uses `bytescale` under the hood to rescale images to use
- the full (0, 255) range if ``mode`` is one of ``None, 'L', 'P', 'l'``.
- It will also cast data for 2-D images to ``uint32`` for ``mode=None``
- (which is the default).
- Notes
- -----
- For 3-D arrays, the `channel_axis` argument tells which dimension of the
- array holds the channel data.
- For 3-D arrays if one of the dimensions is 3, the mode is 'RGB'
- by default or 'YCbCr' if selected.
- The numpy array must be either 2 dimensional or 3 dimensional.
- """
- data = asarray(arr)
- if iscomplexobj(data):
- raise ValueError("Cannot convert a complex-valued array.")
- shape = list(data.shape)
- valid = len(shape) == 2 or ((len(shape) == 3) and
- ((3 in shape) or (4 in shape)))
- if not valid:
- raise ValueError("'arr' does not have a suitable array shape for "
- "any mode.")
- if len(shape) == 2:
- shape = (shape[1], shape[0]) # columns show up first
- if mode == 'F':
- data32 = data.astype(numpy.float32)
- image = Image.frombytes(mode, shape, data32.tostring())
- return image
- if mode in [None, 'L', 'P']:
- bytedata = bytescale(data, high=high, low=low,
- cmin=cmin, cmax=cmax)
- image = Image.frombytes('L', shape, bytedata.tostring())
- if pal is not None:
- image.putpalette(asarray(pal, dtype=uint8).tostring())
- # Becomes a mode='P' automagically.
- elif mode == 'P': # default gray-scale
- pal = (arange(0, 256, 1, dtype=uint8)[:, newaxis] *
- ones((3,), dtype=uint8)[newaxis, :])
- image.putpalette(asarray(pal, dtype=uint8).tostring())
- return image
- if mode == '1': # high input gives threshold for 1
- bytedata = (data > high)
- image = Image.frombytes('1', shape, bytedata.tostring())
- return image
- if cmin is None:
- cmin = amin(ravel(data))
- if cmax is None:
- cmax = amax(ravel(data))
- data = (data*1.0 - cmin)*(high - low)/(cmax - cmin) + low
- if mode == 'I':
- data32 = data.astype(numpy.uint32)
- image = Image.frombytes(mode, shape, data32.tostring())
- else:
- raise ValueError(_errstr)
- return image
- # if here then 3-d array with a 3 or a 4 in the shape length.
- # Check for 3 in datacube shape --- 'RGB' or 'YCbCr'
- if channel_axis is None:
- if (3 in shape):
- ca = numpy.flatnonzero(asarray(shape) == 3)[0]
- else:
- ca = numpy.flatnonzero(asarray(shape) == 4)
- if len(ca):
- ca = ca[0]
- else:
- raise ValueError("Could not find channel dimension.")
- else:
- ca = channel_axis
- numch = shape[ca]
- if numch not in [3, 4]:
- raise ValueError("Channel axis dimension is not valid.")
- bytedata = bytescale(data, high=high, low=low, cmin=cmin, cmax=cmax)
- if ca == 2:
- strdata = bytedata.tostring()
- shape = (shape[1], shape[0])
- elif ca == 1:
- strdata = transpose(bytedata, (0, 2, 1)).tostring()
- shape = (shape[2], shape[0])
- elif ca == 0:
- strdata = transpose(bytedata, (1, 2, 0)).tostring()
- shape = (shape[2], shape[1])
- if mode is None:
- if numch == 3:
- mode = 'RGB'
- else:
- mode = 'RGBA'
- if mode not in ['RGB', 'RGBA', 'YCbCr', 'CMYK']:
- raise ValueError(_errstr)
- if mode in ['RGB', 'YCbCr']:
- if numch != 3:
- raise ValueError("Invalid array shape for mode.")
- if mode in ['RGBA', 'CMYK']:
- if numch != 4:
- raise ValueError("Invalid array shape for mode.")
- # Here we know data and mode is correct
- image = Image.frombytes(mode, shape, strdata)
- return image
- @numpy.deprecate(message="`imrotate` is deprecated in SciPy 1.0.0, "
- "and will be removed in 1.2.0.\n"
- "Use ``skimage.transform.rotate`` instead.")
- def imrotate(arr, angle, interp='bilinear'):
- """
- Rotate an image counter-clockwise by angle degrees.
- This function is only available if Python Imaging Library (PIL) is installed.
- .. warning::
- This function uses `bytescale` under the hood to rescale images to use
- the full (0, 255) range if ``mode`` is one of ``None, 'L', 'P', 'l'``.
- It will also cast data for 2-D images to ``uint32`` for ``mode=None``
- (which is the default).
- Parameters
- ----------
- arr : ndarray
- Input array of image to be rotated.
- angle : float
- The angle of rotation.
- interp : str, optional
- Interpolation
- - 'nearest' : for nearest neighbor
- - 'bilinear' : for bilinear
- - 'lanczos' : for lanczos
- - 'cubic' : for bicubic
- - 'bicubic' : for bicubic
- Returns
- -------
- imrotate : ndarray
- The rotated array of image.
- """
- arr = asarray(arr)
- func = {'nearest': 0, 'lanczos': 1, 'bilinear': 2, 'bicubic': 3, 'cubic': 3}
- im = toimage(arr)
- im = im.rotate(angle, resample=func[interp])
- return fromimage(im)
- @numpy.deprecate(message="`imshow` is deprecated in SciPy 1.0.0, "
- "and will be removed in 1.2.0.\n"
- "Use ``matplotlib.pyplot.imshow`` instead.")
- def imshow(arr):
- """
- Simple showing of an image through an external viewer.
- This function is only available if Python Imaging Library (PIL) is installed.
- Uses the image viewer specified by the environment variable
- SCIPY_PIL_IMAGE_VIEWER, or if that is not defined then `see`,
- to view a temporary file generated from array data.
- .. warning::
- This function uses `bytescale` under the hood to rescale images to use
- the full (0, 255) range if ``mode`` is one of ``None, 'L', 'P', 'l'``.
- It will also cast data for 2-D images to ``uint32`` for ``mode=None``
- (which is the default).
- Parameters
- ----------
- arr : ndarray
- Array of image data to show.
- Returns
- -------
- None
- Examples
- --------
- >>> a = np.tile(np.arange(255), (255,1))
- >>> from scipy import misc
- >>> misc.imshow(a)
- """
- im = toimage(arr)
- fnum, fname = tempfile.mkstemp('.png')
- try:
- im.save(fname)
- except Exception:
- raise RuntimeError("Error saving temporary image data.")
- import os
- os.close(fnum)
- cmd = os.environ.get('SCIPY_PIL_IMAGE_VIEWER', 'see')
- status = os.system("%s %s" % (cmd, fname))
- os.unlink(fname)
- if status != 0:
- raise RuntimeError('Could not execute image viewer.')
- @numpy.deprecate(message="`imresize` is deprecated in SciPy 1.0.0, "
- "and will be removed in 1.3.0.\n"
- "Use Pillow instead: ``numpy.array(Image.fromarray(arr).resize())``.")
- def imresize(arr, size, interp='bilinear', mode=None):
- """
- Resize an image.
- This function is only available if Python Imaging Library (PIL) is installed.
- .. warning::
- This function uses `bytescale` under the hood to rescale images to use
- the full (0, 255) range if ``mode`` is one of ``None, 'L', 'P', 'l'``.
- It will also cast data for 2-D images to ``uint32`` for ``mode=None``
- (which is the default).
- Parameters
- ----------
- arr : ndarray
- The array of image to be resized.
- size : int, float or tuple
- * int - Percentage of current size.
- * float - Fraction of current size.
- * tuple - Size of the output image (height, width).
- interp : str, optional
- Interpolation to use for re-sizing ('nearest', 'lanczos', 'bilinear',
- 'bicubic' or 'cubic').
- mode : str, optional
- The PIL image mode ('P', 'L', etc.) to convert `arr` before resizing.
- If ``mode=None`` (the default), 2-D images will be treated like
- ``mode='L'``, i.e. casting to long integer. For 3-D and 4-D arrays,
- `mode` will be set to ``'RGB'`` and ``'RGBA'`` respectively.
- Returns
- -------
- imresize : ndarray
- The resized array of image.
- See Also
- --------
- toimage : Implicitly used to convert `arr` according to `mode`.
- scipy.ndimage.zoom : More generic implementation that does not use PIL.
- """
- im = toimage(arr, mode=mode)
- ts = type(size)
- if issubdtype(ts, numpy.signedinteger):
- percent = size / 100.0
- size = tuple((array(im.size)*percent).astype(int))
- elif issubdtype(type(size), numpy.floating):
- size = tuple((array(im.size)*size).astype(int))
- else:
- size = (size[1], size[0])
- func = {'nearest': 0, 'lanczos': 1, 'bilinear': 2, 'bicubic': 3, 'cubic': 3}
- imnew = im.resize(size, resample=func[interp])
- return fromimage(imnew)
- @numpy.deprecate(message="`imfilter` is deprecated in SciPy 1.0.0, "
- "and will be removed in 1.2.0.\n"
- "Use Pillow filtering functionality directly.")
- def imfilter(arr, ftype):
- """
- Simple filtering of an image.
- This function is only available if Python Imaging Library (PIL) is installed.
- .. warning::
- This function uses `bytescale` under the hood to rescale images to use
- the full (0, 255) range if ``mode`` is one of ``None, 'L', 'P', 'l'``.
- It will also cast data for 2-D images to ``uint32`` for ``mode=None``
- (which is the default).
- Parameters
- ----------
- arr : ndarray
- The array of Image in which the filter is to be applied.
- ftype : str
- The filter that has to be applied. Legal values are:
- 'blur', 'contour', 'detail', 'edge_enhance', 'edge_enhance_more',
- 'emboss', 'find_edges', 'smooth', 'smooth_more', 'sharpen'.
- Returns
- -------
- imfilter : ndarray
- The array with filter applied.
- Raises
- ------
- ValueError
- *Unknown filter type.* If the filter you are trying
- to apply is unsupported.
- """
- _tdict = {'blur': ImageFilter.BLUR,
- 'contour': ImageFilter.CONTOUR,
- 'detail': ImageFilter.DETAIL,
- 'edge_enhance': ImageFilter.EDGE_ENHANCE,
- 'edge_enhance_more': ImageFilter.EDGE_ENHANCE_MORE,
- 'emboss': ImageFilter.EMBOSS,
- 'find_edges': ImageFilter.FIND_EDGES,
- 'smooth': ImageFilter.SMOOTH,
- 'smooth_more': ImageFilter.SMOOTH_MORE,
- 'sharpen': ImageFilter.SHARPEN
- }
- im = toimage(arr)
- if ftype not in _tdict:
- raise ValueError("Unknown filter type.")
- return fromimage(im.filter(_tdict[ftype]))
|