parent
2e472a6f86
commit
ea46d93d61
@ -0,0 +1,187 @@ |
|||||||
|
import numpy as np |
||||||
|
from PIL import Image |
||||||
|
|
||||||
|
|
||||||
|
_errstr = "Mode is unknown or incompatible with input array shape." |
||||||
|
|
||||||
|
|
||||||
|
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 == np.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(np.uint8) |
||||||
|
|
||||||
|
|
||||||
|
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 = np.asarray(arr) |
||||||
|
if np.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(np.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(np.asarray(pal, dtype=np.uint8).tostring()) |
||||||
|
# Becomes a mode='P' automagically. |
||||||
|
elif mode == 'P': # default gray-scale |
||||||
|
pal = (np.arange(0, 256, 1, dtype=np.uint8)[:, np.newaxis] * |
||||||
|
np.ones((3,), dtype=np.uint8)[np.newaxis, :]) |
||||||
|
image.putpalette(np.asarray(pal, dtype=np.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 = np.amin(np.ravel(data)) |
||||||
|
if cmax is None: |
||||||
|
cmax = np.amax(np.ravel(data)) |
||||||
|
data = (data*1.0 - cmin)*(high - low)/(cmax - cmin) + low |
||||||
|
if mode == 'I': |
||||||
|
data32 = data.astype(np.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 = np.flatnonzero(np.asarray(shape) == 3)[0] |
||||||
|
else: |
||||||
|
ca = np.flatnonzero(np.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 = np.transpose(bytedata, (0, 2, 1)).tostring() |
||||||
|
shape = (shape[2], shape[0]) |
||||||
|
elif ca == 0: |
||||||
|
strdata = np.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 |
Loading…
Reference in new issue