tensorlayer3/tensorlayer/dataflow/image/mindspore_image.py

1540 lines
53 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import numpy as np
from PIL import Image, ImageOps, ImageEnhance, __version__
import random
import colorsys
import numbers
import math
import io
__all__ = [
'CentralCrop',
'HsvToRgb',
'AdjustBrightness',
'AdjustContrast',
'AdjustHue',
'AdjustSaturation',
'Crop',
'FlipHorizontal',
'FlipVertical',
'GrayToRgb',
'Standardization',
'RgbToGray',
'PadToBoundingbox',
'Pad',
'RandomBrightness',
'RandomContrast',
'RandomHue',
'RandomSaturation',
'RandomCrop',
'Resize',
'CropAndResize',
'CropOrPad',
'ResizeAndPad',
'RgbToHsv',
'Transpose',
'RandomRotation',
'RandomShift',
'RandomShear',
'RandomZoom',
'Rescale',
'RandomFlipVertical',
'RandomFlipHorizontal',
'HWC2CHW',
'CHW2HWC',
]
augment_error_message = 'img should be PIL image. Got {}.'
def ToTensor(image):
image = np.asarray(image).astype(np.float32)
return image
def ToPIL(image):
"""
Convert the input image to PIL format.
Args:
img: Image to be converted.
Returns:
img (PIL image), Converted image.
"""
return Image.fromarray(np.array(image).astype(np.uint8))
def Decode(image):
"""
Decode the input image to PIL image format in RGB mode.
Args:
img: Image to be decoded.
Returns:
img (PIL image), Decoded image in RGB mode.
"""
try:
data = io.BytesIO(image)
img = Image.open(data)
return img.convert('RGB')
except IOError as e:
raise ValueError("{0}\nWARNING: Failed to decode given image.".format(e))
except AttributeError as e:
raise ValueError("{0}\nWARNING: Failed to decode, Image might already be decoded.".format(e))
def Crop(image, offset_height, offset_width, target_height, target_width, is_hwc=True):
'''
Parameters
----------
image:
A image or a batch of images
offset_height:
Vertical coordinate of the top-left corner of the result in the input.
offset_width:
Horizontal coordinate of the top-left corner of the result in the input.
target_height:
Height of the result.
target_width:
Width of the result.
is_hwc:
If is_hwc is True, the order of image channels is [B,H,W,C] or [H,W,C]. If is_hwc is False, the order of image channels is [B,C,H,W] or [C,H,W,]
Returns:
Output [batch, target_height, target_width, channels] or [target_height, target_width, channels]
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
shape_size = len(image.shape)
if not shape_size in (3, 4):
raise TypeError(
'img shape should be (H, W, C)/(N, H, W, C)/(C, H, W)/(N, C, H, W). \
Got {}'.format(image.shape)
)
if shape_size == 3:
if is_hwc:
height, width, channels = image.shape
else:
channels, height, width = image.shape
else:
if is_hwc:
batch, height, width, channels = image.shape
else:
batch, channels, height, width = image.shape
if offset_width < 0:
raise ValueError('offset_width must be >0.')
if offset_height < 0:
raise ValueError('offset_height must be >0.')
if target_height < 0:
raise ValueError('target_height must be >0.')
if target_width < 0:
raise ValueError('target_width must be >0.')
if offset_width + target_width > width:
raise ValueError('offset_width + target_width must be <= image width.')
if offset_height + target_height > height:
raise ValueError('offset_height + target_height must be <= image height.')
if shape_size == 3:
if is_hwc:
return ToTensor(
image[offset_height:offset_height + target_height, offset_width:offset_width + target_width, :]
)
else:
return ToTensor(
image[:, offset_height:offset_height + target_height, offset_width:offset_width + target_width]
)
else:
if is_hwc:
return ToTensor(
image[:, offset_height:offset_height + target_height, offset_width:offset_width + target_width, :]
)
else:
return ToTensor(
image[:, :, offset_height:offset_height + target_height, offset_width:offset_width + target_width]
)
def CentralCrop(image, central_fraction=None, size=None, is_hwc=True):
'''
Parameters
----------
image :
input Either a 3-D float Tensor of shape [height, width, depth] or a 4-D Tensor of shape [batch, height, width, depth],
central_fraction :
float (0, 1], fraction of size to crop
size:
size (Union[int, sequence]) The output size of the cropped image. If size is an integer, a square crop of size (size, size) is returned.
If size is a sequence of length 2, it should be (height, width).
Returns :
3-D float Tensor or 4-D float Tensor, as per the input.
-------
If backend is tensorflow, central_fraction will be used preferentially. if size is used, the height-width ratio will be equivalent to original ratio..
If backend is mindspore, size will be used preferentially.
'''
if size is None and central_fraction is None:
raise ValueError('central_fraction and size can not be both None')
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
shape_size = len(image.shape)
if not shape_size in (3, 4):
raise TypeError(
'img shape should be (H, W, C)/(N, H, W, C)/(C, H, W)/(N, C, H, W). \
Got {}'.format(image.shape)
)
if shape_size == 3:
if is_hwc:
height, width, channels = image.shape
else:
channels, height, width = image.shape
else:
if is_hwc:
batch, height, width, channels = image.shape
else:
batch, channels, height, width = image.shape
if size is None:
if central_fraction > 1 or central_fraction <= 0:
raise ValueError('central_fraction must be in (0,1].')
target_height = int(round(height * central_fraction))
target_width = int(round(width * central_fraction))
size = (target_height, target_width)
if isinstance(size, int):
size = (size, size)
crop_height, crop_width = size
crop_top = int(round((height - crop_height) / 2.))
crop_left = int(round((width - crop_width) / 2.))
return Crop(image, crop_top, crop_left, crop_height, crop_width, is_hwc)
def hsv_to_rgb(np_hsv_img, is_hwc):
"""
Convert HSV img to RGB img.
Args:
np_hsv_img (numpy.ndarray): NumPy HSV image array of shape (H, W, C) or (C, H, W) to be converted.
is_hwc (Bool): If True, the shape of np_hsv_img is (H, W, C), otherwise must be (C, H, W).
Returns:
np_rgb_img (numpy.ndarray), NumPy HSV image with same shape of np_hsv_img.
"""
if is_hwc:
h, s, v = np_hsv_img[:, :, 0], np_hsv_img[:, :, 1], np_hsv_img[:, :, 2]
else:
h, s, v = np_hsv_img[0, :, :], np_hsv_img[1, :, :], np_hsv_img[2, :, :]
to_rgb = np.vectorize(colorsys.hsv_to_rgb)
r, g, b = to_rgb(h, s, v)
if is_hwc:
axis = 2
else:
axis = 0
np_rgb_img = np.stack((r, g, b), axis=axis)
return np_rgb_img
def HsvToRgb(image, is_hwc=True):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
shape_size = len(image.shape)
if not shape_size in (3, 4):
raise TypeError(
'img shape should be (H, W, C)/(N, H, W, C)/(C, H, W)/(N, C, H, W). \
Got {}'.format(image.shape)
)
if shape_size == 3:
batch_size = 0
if is_hwc:
num_channels = image.shape[2]
else:
num_channels = image.shape[0]
else:
batch_size = image.shape[0]
if is_hwc:
num_channels = image.shape[3]
else:
num_channels = image.shape[1]
if num_channels != 3:
raise TypeError('img should be 3 channels RGB img. Got {} channels'.format(num_channels))
if batch_size == 0:
return hsv_to_rgb(image, is_hwc)
return ToTensor([hsv_to_rgb(img, is_hwc) for img in image])
def AdjustBrightness(image, factor):
'''
Parameters
----------
image:
input NumPy image array or PIL image
factor:
factor should be in the range (-1,1)
Returns:
-------
np darray image
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
if factor >= 1 or factor <= -1:
raise ValueError('factor must be in (-1,1).')
image = image + factor * 255
image = np.clip(image, 0, 255)
return ToTensor(image)
def AdjustContrast(image, factor):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
image = ImageEnhance.Contrast(image).enhance(factor)
return ToTensor(image)
def AdjustHue(image, factor):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
image_hue_factor = factor
if not -1 <= image_hue_factor <= 1:
raise ValueError('image_hue_factor {} is not in [-1, 1].'.format(image_hue_factor))
mode = image.mode
if mode in {'L', '1', 'I', 'F'}:
return image
hue, saturation, value = image.convert('HSV').split()
np_hue = np.array(hue, dtype=np.uint8)
with np.errstate(over='ignore'):
np_hue += np.uint8(image_hue_factor * 255)
hue = Image.fromarray(np_hue, 'L')
image = Image.merge('HSV', (hue, saturation, value)).convert(mode)
return ToTensor(image)
def AdjustSaturation(image, factor):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
enhancer = ImageEnhance.Color(image)
image = enhancer.enhance(factor)
return ToTensor(image)
def FlipHorizontal(image):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
image = np.fliplr(image)
return image
def FlipVertical(image):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
image = np.flipud(image)
return image
def GrayToRgb(image):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
shape = image.shape
output_image = np.zeros((shape[0], shape[1], 3), dtype=np.uint8)
if len(shape) == 3:
for i in range(3):
output_image[:, :, i] = image[:, :, 1]
elif len(shape) == 2:
for i in range(3):
output_image[:, :, i] = image
return ToTensor(output_image)
def RgbToGray(image):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
'''
将彩色图像转换为灰度模式“L”库使用ITU-R 601-2 Luma转换
L = R * 299/1000 + G * 587/1000 + B * 114/1000
'''
image = image.convert('L')
return ToTensor(image)
def PadToBoundingbox(image, offset_height, offset_width, target_height, target_width, padding_value=0, is_hwc=True):
'''
Parameters
----------
image:
A 3-D numpy ndarray or 4-D numpy ndarray image
offset_height:
Number of rows of zeros to add on top.
offset_width:
Number of columns of zeros to add on the left.
target_height:
Height of output image.
target_width
Width of output image.
Returns
A numpy ndarray image
-------
'''
if offset_height < 0:
raise ValueError("offset_height must be >= 0")
if offset_width < 0:
raise ValueError("offset_width must be >= 0")
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
shape_size = len(image.shape)
if not shape_size in (3, 4):
raise TypeError(
'img shape should be (H, W, C)/(N, H, W, C)/(C, H, W)/(N, C, H, W). \
Got {}'.format(image.shape)
)
if shape_size == 3:
if is_hwc:
height, width, channels = image.shape
else:
channels, height, width = image.shape
else:
if is_hwc:
batch, height, width, channels = image.shape
else:
batch, channels, height, width = image.shape
top = offset_height
bottom = target_height - height - top
left = offset_width
right = target_width - width - left
if bottom < 0:
raise ValueError("target_height must be >= offset_height + height")
if right < 0:
raise ValueError("target_width must be >= offset_width + width")
if shape_size == 3:
if is_hwc:
return ToTensor(
np.pad(
image, ((top, bottom), (left, right), (0, 0)), mode='constant',
constant_values=(padding_value, padding_value)
)
)
else:
return ToTensor(
np.pad(
image, ((0, 0), (top, bottom), (left, right)), mode='constant',
constant_values=(padding_value, padding_value)
)
)
else:
if is_hwc:
return ToTensor(
np.pad(
image, ((0, 0), (top, bottom), (left, right), (0, 0)), mode='constant',
constant_values=(padding_value, padding_value)
)
)
else:
return ToTensor(
np.pad(
image, ((0, 0), (0, 0), (top, bottom), (left, right)), mode='constant',
constant_values=(padding_value, padding_value)
)
)
def Pad(image, padding, padding_value=0, mode='constant', is_hwc=True):
'''
Parameters
----------
image:
A 3-D or 4-D Tensor.
padding:
An integer or a list/tuple. If a single number is provided, pad all borders with this value.
If a tuple or list of 2 values is provided, pad the left and top with the first value and the right and bottom with the second value.
If 4 values are provided as a list or tuple, pad the left, top, right and bottom respectively.
padding_value:
In "CONSTANT" mode, the scalar pad value to use. Must be same type as tensor.
mode:
One of "CONSTANT", "REFLECT", or "SYMMETRIC" (case-insensitive)
Returns:
A padded Tensor. Has the same type as tensor.
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
shape_size = image.shape
if len(shape_size) == 3:
batch_size = 0
elif len(shape_size) == 4:
batch_size = shape_size[0]
else:
raise TypeError(
'img shape should be (H, W, C)/(N, H, W, C)/(C, H, W)/(N, C, H, W). \
Got {}'.format(image.shape)
)
if mode not in ('constant', 'edge', 'reflect', 'symmetric'):
raise TypeError('mode should be one of (constant,edge,reflect,symmetric).')
if isinstance(padding, int):
padding = ((padding, padding), (padding, padding))
elif isinstance(padding, list) or isinstance(padding, tuple):
if len(padding) == 2:
padding = ((padding[0], padding[0]), (padding[1], padding[1]))
elif len(padding) == 4:
padding = ((padding[0], padding[1]), (padding[2], padding[3]))
else:
raise ValueError('The length of padding should be 2 or 4, but got {}.'.format(len(padding)))
else:
raise TypeError('Padding should be an integer or a list/tuple, but got {}.'.format(type(padding)))
if batch_size == 0:
if is_hwc:
padding = (padding[0], padding[1], (0, 0))
else:
padding = (
(0, 0),
padding[0],
padding[1],
)
else:
if is_hwc:
padding = ((0, 0), padding[0], padding[1], (0, 0))
else:
padding = ((0, 0), (0, 0), padding[0], padding[1])
if mode == 'constant':
return ToTensor(np.pad(image, padding, mode=mode, constant_values=(padding_value, padding_value)))
else:
return ToTensor(np.pad(image, padding, mode=mode))
def Standardization(image, mean=None, std=None, channel_mode=False, is_hwc=True):
'''
Parameters
----------
image:
An n-D Tensor with at least 3 dimensions, the last 3 of which are the dimensions of each image.
mean:
List or tuple of mean values for each channel, with respect to channel order.
std:
List or tuple of standard deviations for each channel.
channel_mode:
Decide to implement standardization on whole image or each channel of image.
Returns:
A Tensor with the same shape and dtype as image.
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
num_shape = image.shape
if is_hwc:
height, width, channels = 0, 1, 2
else:
channels, height, width = 0, 1, 2
if mean is not None and std is not None:
if len(mean) != len(std):
raise ValueError("Length of mean and std must be equal")
if len(mean) == 1:
mean = [mean[0]] * num_shape[channels]
std = [std[0]] * num_shape[channels]
mean = np.array(mean, dtype=image.dtype)
std = np.array(std, dtype=image.dtype)
return ToTensor((image - mean[:, None, None]) / std[:, None, None])
elif mean is None and std is None:
if channel_mode:
num_pixels = num_shape[height] * num_shape[width]
image_mean = np.mean(image, axis=(height, width))
stddev = np.std(image, axis=(height, width))
min_sttdev = 1 / np.sqrt(num_pixels)
min_sttdev = [min_sttdev] * num_shape[channels]
adjusted_sttdev = np.maximum(stddev, min_sttdev)
image -= image_mean
image = np.divide(image, adjusted_sttdev)
return ToTensor(image)
else:
num_pixels = num_shape[height] * num_shape[width] * num_shape[channels]
image_mean = np.mean(image, axis=(0, 1, 2))
image_mean = [image_mean] * 3
stddev = np.std(image, axis=(0, 1, 2))
min_sttdev = 1 / np.sqrt(num_pixels)
adjusted_sttdev = np.maximum(stddev, min_sttdev)
adjusted_sttdev = [adjusted_sttdev] * 3
image -= image_mean
image = np.divide(image, adjusted_sttdev)
return ToTensor(image)
else:
raise ValueError('std and mean must both be None or not None')
def RandomBrightness(image, factor):
'''
Parameters
----------
image:
An image or images to adjust
factor:
Float, must be non-negative. Factor must be (0,1). Random range will be [-factor, factor).
Returns:
The brightness-adjusted image(s).
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
if factor < 0 or factor > 1:
raise ValueError('factor should be in [0,1].')
delta = random.uniform(-factor, factor)
image = image + delta * 255
image = np.clip(image, 0, 255)
return image
def RandomContrast(image, lower, upper, seed=None):
'''
Parameters
----------
image:
An image tensor with 3 or more dimensions.
lower:
float. Lower bound for the random contrast factor.
upper:
float. Upper bound for the random contrast factor.
seed:
A Python integer. Used to create a random seed.
Returns:
The contrast-adjusted image(s).
-------
'''
if upper <= lower:
raise ValueError('upper must be > lower')
if lower < 0:
raise ValueError('lower must be non-negative')
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
factor = random.uniform(lower, upper)
image = ImageEnhance.Contrast(image).enhance(factor)
return ToTensor(image)
def RandomHue(image, factor, seed=None):
'''
Parameters
----------
image:
RGB image or images. The size of the last dimension must be 3.
factor:
float. The maximum value for the random factor.
seed:
An operation-specific seed. I
Returns:
Adjusted numpy ndarrry image(s).
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
if factor > 0.5 or factor < 0:
raise ValueError('factor should be in [0,0.5].')
image_hue_factor = random.uniform(-factor, factor)
mode = image.mode
if mode in {'L', '1', 'I', 'F'}:
return image
hue, saturation, value = image.convert('HSV').split()
np_hue = np.array(hue, dtype=np.uint8)
with np.errstate(over='ignore'):
np_hue += np.uint8(image_hue_factor * 255)
hue = Image.fromarray(np_hue, 'L')
image = Image.merge('HSV', (hue, saturation, value)).convert(mode)
return ToTensor(image)
def RandomSaturation(image, lower, upper, seed=None):
'''
Parameters
----------
image:
RGB image or images. The size of the last dimension must be 3.
lower:
float. Lower bound for the random saturation factor.
upper:
float. Upper bound for the random saturation factor.
seed:
An operation-specific seed.
Returns
Adjusted numpy ndarray image(s).
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
if upper <= lower:
raise ValueError('upper must be > lower.')
if lower < 0:
raise ValueError('lower must be non-negative.')
factor = random.uniform(lower, upper)
enhancer = ImageEnhance.Color(image)
image = enhancer.enhance(factor)
return ToTensor(image)
def RandomCrop(image, size, is_hwc=True):
'''
Parameters
----------
image:
Input an image to crop.
size:
if size is an integer, shape of cropped image will be [size, size, 3]. if length of size is 2.
shape of cropped image will be [height, width, 3].
Returns:
A cropped image of the same rank as image and shape size.
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
if isinstance(size, int):
size = (size, size)
elif isinstance(size, (tuple, list)) and len(size) == 2:
size = size
else:
raise ValueError("Size should be a single integer or a list/tuple (h, w) of length 2.")
def _input_to_factor_(image, size, is_hwc):
if len(image.shape) == 3:
if is_hwc:
height, width, channels = image.shape
else:
channels, height, width = image.shape
else:
if is_hwc:
batch, height, width, channels = image.shape
else:
batch, channels, height, width = image.shape
target_height, target_width = size
if target_height > height or target_width > width:
raise ValueError("Crop size {} is larger than input image size {}".format(size, (height, width)))
if target_height == height and target_width == width:
return 0, 0, height, width
top = random.randint(0, height - target_height)
left = random.randint(0, width - target_width)
return top, left, target_height, target_width
top, left, height, width = _input_to_factor_(image, size, is_hwc)
return Crop(image, top, left, height, width, is_hwc)
def Resize(image, size, method='bilinear', preserve_aspect_ratio=False, antialias=False):
'''
Parameters
----------
images:
Input an image to resize
size:
if size is an integer, shape of resized image will be [size, size, 3]. if length of size is 2.
shape of resized image will be [height, width, 3].
method:
An image.ResizeMethod, or string equivalent. Defaults to bilinear.
preserve_aspect_ratio:
Whether to preserve the aspect ratio.
antialias:
Whether to use an anti-aliasing filter when downsampling an image.
Returns:
an resized image
-------
'''
DE_PY_INTER_MODE = {
'nearest': Image.NEAREST,
'bilinear': Image.BILINEAR,
'cubic': Image.CUBIC,
'lanczos': Image.LANCZOS,
'bicubic': Image.BICUBIC
}
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
if isinstance(size, int):
size = (size, size)
elif isinstance(size, (tuple, list)) or len(size) == 2:
target_height, target_width = size
size = (target_width, target_height)
else:
raise ValueError("Size should be a single integer or a list/tuple (h, w) of length 2.")
if method not in ('nearest', 'bilinear', 'cubic', 'lanczos', 'bicubic'):
raise TypeError('Unknown resize method! resize method must be in (nearest bilinear cubic lanczos bicubic)')
if preserve_aspect_ratio:
width, height = image.size
target_width, target_height = size
scale_factor_height = float(target_height / height)
scale_factor_width = float(target_width / width)
scale_factor = np.minimum(scale_factor_height, scale_factor_width)
new_target_height = int(scale_factor * height)
new_target_width = int(scale_factor * width)
size = (new_target_width, new_target_height)
interpolation = DE_PY_INTER_MODE[method]
image = image.resize(size, interpolation)
if antialias:
image = image.resize(size, Image.ANTIALIAS)
return ToTensor(image)
def CropAndResize(image, boxes, box_indices, crop_size, method='bilinear', extrapolation_value=0, is_hwc=True):
'''
Parameters
----------
image:
A 4-D tensor of shape [batch, image_height, image_width, depth]. Both image_height and image_width need to be positive.
boxes:
A 2-D tensor of shape [num_boxes, 4].
box_indices:
A 1-D tensor of shape [num_boxes] with int32 values in [0,batch).
The value of box_ind[i] specifies the image that the i-th box refers to.
crop_size:
A 1-D tensor of 2 elements, size = [crop_height, crop_width]. All cropped image patches are resized to this size.
The aspect ratio of the image content is not preserved. Both crop_height and crop_width need to be positive.
method:
An optional string specifying the sampling method for resizing.
It can be either "bilinear" or "nearest" and default to "bilinear".
extrapolation_value:
An optional float. Defaults to 0. Value used for extrapolation, when applicable.
Returns:
A 4-D tensor of shape [num_boxes, crop_height, crop_width, depth].
-------
'''
if method not in ["bilinear", "nearest"]:
raise ValueError('method must be bilinear or nearest.')
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
boxes = np.asarray(boxes)
box_indices = np.asarray(box_indices)
image_shape = image.shape
if len(image_shape) == 4:
batch_size = image_shape[0]
elif len(image_shape) == 3:
image = np.expand_dims(image, axis=0)
else:
raise ValueError('Input must be a 3-D or 4-D image Tensor.')
box_num = boxes.shape[0] # boxes.shape is [n,4]. n is the number of boxes.
if not is_hwc: # 判断通道顺序,为了便于后续计算将通道顺序调整为HWC or BHWC
image = np.transpose(image, (0, 2, 3, 1))
batch_size, height, width, channels = image.shape
return_image = np.zeros((box_num, crop_size[0], crop_size[1], 3))
for i in range(box_num):
y1, x1, y2, x2 = boxes[i] # 首先判断图像是否需要翻转 若y1>y2 需要垂直翻转, 若x1>x2 需要水平翻转
cur_image = image[box_indices[i]]
if y1 > y2:
cur_image = FlipVertical(cur_image)
y1, y2 = y2, y1
if x1 > x2:
cur_image = FlipHorizontal(cur_image)
x1, x2 = x2, x1
top_padding = 0 if y1 > 0 else int(round(height * (-y1)))
left_padding = 0 if x1 > 0 else int(round(width * (-x1)))
bottom_padding = 0 if y2 < 1 else int(round(height * (y2 - 1)))
right_padding = 0 if x2 < 1 else int(round(width * (x2 - 1)))
# 判断是否需要padding
target_height = top_padding + height + bottom_padding
target_width = left_padding + width + right_padding
if target_height != height or target_width != width:
cur_image = PadToBoundingbox(
cur_image, offset_height=top_padding, offset_width=left_padding, target_height=target_height,
target_width=target_width, padding_value=extrapolation_value, is_hwc=is_hwc
)
offset_height = 0 if y1 < 0 else int(round(height * y1))
offset_width = 0 if x1 < 0 else int(round(width * x1))
target_height = int(round(height * (y2 - y1)))
target_width = int(round(width * (x2 - x1)))
crop_image = Crop(cur_image, offset_height, offset_width, target_height, target_width, is_hwc)
resized_image = Resize(crop_image, crop_size, method=method)
return_image[i] = resized_image
if not is_hwc:
return_image = np.transpose(return_image, (0, 3, 1, 2))
return ToTensor(return_image)
def CropOrPad(image, target_height, target_width, is_hwc=True):
'''
Resizes an image to a target width and height by either centrally cropping the image or padding it evenly with zeros.
Parameters
----------
image:
3-D Tensor of shape [height, width, channels].
target_height:
Target height.
target_width:
Target width.
Returns:
Cropped and/or padded image.
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
shape_size = len(image.shape)
if not shape_size in (3, 4):
raise TypeError(
'img shape should be (H, W, C)/(N, H, W, C)/(C, H, W)/(N, C, H, W). \
Got {}'.format(image.shape)
)
if target_height < 0:
raise ValueError('target_height must be >0.')
if target_width < 0:
raise ValueError('target_width must be >0.')
if shape_size == 3:
if is_hwc:
height, width, channels = image.shape
else:
channels, height, width = image.shape
else:
if is_hwc:
batch, height, width, channels = image.shape
else:
batch, channels, height, width = image.shape
offset_height = height - target_height
offset_width = width - target_width
offset_crop_height = max(offset_height // 2, 0)
offset_crop_width = max(offset_width // 2, 0)
offset_pad_height = max(-offset_height // 2, 0)
offset_pad_width = max(-offset_width // 2, 0)
cropped = Crop(
image, offset_crop_height, offset_crop_width, min(height, target_height), min(width, target_width), is_hwc
)
padded = PadToBoundingbox(cropped, offset_pad_height, offset_pad_width, target_height, target_width, is_hwc=is_hwc)
return ToTensor(padded)
def ResizeAndPad(image, target_height, target_width, method='bilinear', antialias=False, is_hwc=True):
'''
Parameters
----------
image:
4-D Tensor of shape [batch, height, width, channels] or 3-D Tensor of shape [height, width, channels].
target_height:
Target height.
target_width:
Target height.
is_hwc:
The flag of image shape, (H, W, C) or (N, H, W, C) if True and (C, H, W) or (N, C, H, W) if False (default=True).
Returns:
Resized and padded image. If images was 4-D, a 4-D float Tensor of shape [batch, new_height, new_width, channels].
If images was 3-D, a 3-D float Tensor of shape [new_height, new_width, channels].
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
shape_size = len(image.shape)
if not shape_size in (3, 4):
raise TypeError(
'img shape should be (H, W, C)/(N, H, W, C)/(C, H, W)/(N, C, H, W). \
Got {}'.format(image.shape)
)
if target_height < 0:
raise ValueError('target_height must be >0.')
if target_width < 0:
raise ValueError('target_width must be >0.')
if shape_size == 3:
if is_hwc:
height, width, channels = image.shape
else:
channels, height, width = image.shape
else:
if is_hwc:
batch, height, width, channels = image.shape
else:
batch, channels, height, width = image.shape
height = float(height)
width = float(width)
ratio = max(height / target_height, width / target_width)
resized_height = int(round(height / ratio))
resized_width = int(round(width / ratio))
padding_height = max(0, int(round((target_height - resized_height) / 2)))
padding_width = max(0, int(round((target_width - resized_width) / 2)))
resized = Resize(
image, size=(resized_height, resized_width), method=method, antialias=antialias
) #需要解决 batch images的resize
padded = PadToBoundingbox(resized, padding_height, padding_width, target_height, target_width, is_hwc=is_hwc)
return ToTensor(padded)
def rgb_to_hsv(np_rgb_img, is_hwc):
"""
Convert RGB img to HSV img.
Args:
np_rgb_img (numpy.ndarray): NumPy RGB image array of shape (H, W, C) or (C, H, W) to be converted.
is_hwc (Bool): If True, the shape of np_hsv_img is (H, W, C), otherwise must be (C, H, W).
Returns:
np_hsv_img (numpy.ndarray), NumPy HSV image with same type of np_rgb_img.
"""
if is_hwc:
r, g, b = np_rgb_img[:, :, 0], np_rgb_img[:, :, 1], np_rgb_img[:, :, 2]
else:
r, g, b = np_rgb_img[0, :, :], np_rgb_img[1, :, :], np_rgb_img[2, :, :]
to_hsv = np.vectorize(colorsys.rgb_to_hsv)
h, s, v = to_hsv(r, g, b)
if is_hwc:
axis = 2
else:
axis = 0
np_hsv_img = np.stack((h, s, v), axis=axis)
return np_hsv_img
def RgbToHsv(image, is_hwc=True):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
shape_size = len(image.shape)
if not shape_size in (3, 4):
raise TypeError(
'img shape should be (H, W, C)/(N, H, W, C)/(C ,H, W)/(N, C, H, W). \
Got {}'.format(image.shape)
)
if shape_size == 3:
batch_size = 0
if is_hwc:
num_channels = image.shape[2]
else:
num_channels = image.shape[0]
else:
batch_size = image.shape[0]
if is_hwc:
num_channels = image.shape[3]
else:
num_channels = image.shape[1]
if num_channels != 3:
raise TypeError('img should be 3 channels RGB img. Got {} channels'.format(num_channels))
if batch_size == 0:
return ToTensor(rgb_to_hsv(image, is_hwc))
return ToTensor([rgb_to_hsv(img, is_hwc) for img in image])
def Transpose(image, order):
"""
Transpose the input image with order
"""
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
if len(image.shape) == 3:
if len(order) != 3:
raise ValueError('if image is 3-D tensor, order should be a list/tuple with length of 3')
return ToTensor(np.transpose(image, order))
elif len(image.shape) == 4:
if len(order) != 3:
raise ValueError('if image is 3-D tensor, order should be a list/tuple with length of 3')
return ToTensor(np.transpose(image, order))
else:
raise ValueError('\'image\' must have either 3 or 4 dimensions.')
def RandomRotation(
image, degrees, fill_mode='nearest', fill_value=0, center=None, expand=False, is_hwc=True, interpolation_order=1
):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
if isinstance(degrees, numbers.Number):
if degrees < 0:
raise ValueError("If degrees is a single number, it cannot be negative.")
degrees = (-degrees, degrees)
elif isinstance(degrees, (list, tuple)):
if len(degrees) != 2:
raise ValueError("If degrees is a sequence, the length must be 2.")
else:
raise TypeError("Degrees must be a single non-negative number or a sequence")
DE_PY_INTER_MODE = {
'nearest': Image.NEAREST,
'bilinear': Image.BILINEAR,
'antialias': Image.ANTIALIAS,
'bicubic': Image.BICUBIC
}
if fill_mode not in ('nearest', 'bilinear', 'antialias', 'bicubic'):
raise TypeError('Fill_mode must be in (nearest,bilinear, antialias,bicubic)')
if isinstance(fill_value, int):
fill_value = tuple([fill_value] * 3)
angle = random.uniform(degrees[0], degrees[1])
fill_mode = DE_PY_INTER_MODE[fill_mode]
return ToTensor(image.rotate(angle, fill_mode, expand, center, fillcolor=fill_value))
def RandomShift(image, shift, fill_mode='nearest', fill_value=0, is_hwc=True, interpolation_order=1):
'''
Parameters
----------
image
Input tensor. Must be 3D.
shift:
int or list/tuple, if shift is int, Width shift range will equal to height shift range.
if shift is list/tuple, shift range will be [width fraction, height fraction]
is_hwc:
The flag of image shape, (H, W, C) or (N, H, W, C) if True and (C, H, W) or (N, C, H, W) if False (default=True).
fill_mode:
Points outside the boundaries of the input are filled according to the given mode (one of {'constant', 'nearest', 'reflect', 'wrap'}).
fill_value:
Value used for points outside the boundaries of the input if mode='constant'.
interpolation_order
int, order of spline interpolation. see ndimage.interpolation.affine_transform
Returns
Shifted Numpy image tensor.
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
if isinstance(shift, numbers.Number):
width_fraction = shift
height_fraction = shift
elif isinstance(shift, list) or isinstance(shift, tuple):
if len(shift) == 2:
width_fraction = shift[0]
height_fraction = shift[1]
else:
raise ValueError('shift must be int or list/tuple of length 2')
DE_PY_INTER_MODE = {'nearest': Image.NEAREST, 'bilinear': Image.BILINEAR, 'bicubic': Image.BICUBIC}
if fill_mode not in ('nearest', 'bilinear', 'bicubic'):
raise TypeError('Fill_mode must be in (nearest,bilinear,bicubic)')
fill_mode = DE_PY_INTER_MODE[fill_mode]
width, height = image.size
max_dx = width_fraction * width
max_dy = height_fraction * height
translations = (np.round(random.uniform(-max_dx, max_dx)), np.round(random.uniform(-max_dy, max_dy)))
scale = 1.0
shear = 0.0
output_size = image.size
center = (width * 0.5 + 0.5, height * 0.5 + 0.5)
angle = math.radians(0)
shear = math.radians(shear)
shear = [shear, 0]
scale = 1.0 / scale
d = math.cos(angle + shear[0]) * math.cos(angle + shear[1]) + \
math.sin(angle + shear[0]) * math.sin(angle + shear[1])
matrix = [
math.cos(angle + shear[0]),
math.sin(angle + shear[0]), 0, -math.sin(angle + shear[1]),
math.cos(angle + shear[1]), 0
]
matrix = [scale / d * m for m in matrix]
matrix[2] += matrix[0] * (-center[0] - translations[0]) + matrix[1] * (-center[1] - translations[1])
matrix[5] += matrix[3] * (-center[0] - translations[0]) + matrix[4] * (-center[1] - translations[1])
# Apply center translation: C * RSS^-1 * C^-1 * T^-1
matrix[2] += center[0]
matrix[5] += center[1]
if __version__ >= '5':
kwargs = {"fillcolor": fill_value}
else:
kwargs = {}
return ToTensor(image.transform(output_size, Image.AFFINE, matrix, fill_mode, **kwargs))
def RandomShear(image, degree, fill_mode='nearest', fill_value=0, is_hwc=True, interpolation_order=1):
'''
Parameters
----------
image
Input tensor. Must be 3D.
shift:
int or list/tuple, if shift is int, Width shift range will equal to height shift range.
if shift is list/tuple, shift range will be [width fraction, height fraction]
is_hwc:
The flag of image shape, (H, W, C) or (N, H, W, C) if True and (C, H, W) or (N, C, H, W) if False (default=True).
fill_mode:
Points outside the boundaries of the input are filled according to the given mode (one of {'constant', 'nearest', 'reflect', 'wrap'}).
fill_value:
Value used for points outside the boundaries of the input if mode='constant'.
interpolation_order
int, order of spline interpolation. see ndimage.interpolation.affine_transform
Returns
Shifted Numpy image tensor.
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
DE_PY_INTER_MODE = {'nearest': Image.NEAREST, 'bilinear': Image.BILINEAR, 'bicubic': Image.BICUBIC}
if fill_mode not in ('nearest', 'bilinear', 'bicubic'):
raise TypeError('Fill_mode must be in (nearest,bilinear,bicubic)')
fill_mode = DE_PY_INTER_MODE[fill_mode]
width, height = image.size
translations = (0, 0)
scale = 1.0
shear = degree
output_size = image.size
center = (width * 0.5 + 0.5, height * 0.5 + 0.5)
angle = math.radians(0)
if shear is not None:
if isinstance(shear, numbers.Number):
shear = (-1 * shear, shear)
shear = [random.uniform(shear[0], shear[1]), random.uniform(shear[0], shear[1])]
elif len(shear) == 2 or len(shear) == 4:
if len(shear) == 2:
shear = [shear[0], shear[1], shear[0], shear[1]]
elif len(shear) == 4:
shear = [s for s in shear]
shear = [random.uniform(shear[0], shear[1]), random.uniform(shear[2], shear[3])]
else:
raise ValueError(
"Shear should be a single value or a tuple/list containing " + "two values. Got {}".format(shear)
)
shear = [math.radians(s) for s in shear]
else:
shear = [0, 0]
d = math.cos(angle + shear[0]) * math.cos(angle + shear[1]) + \
math.sin(angle + shear[0]) * math.sin(angle + shear[1])
matrix = [
math.cos(angle + shear[0]),
math.sin(angle + shear[0]), 0, -math.sin(angle + shear[1]),
math.cos(angle + shear[1]), 0
]
matrix = [scale / d * m for m in matrix]
matrix[2] += matrix[0] * (-center[0] - translations[0]) + matrix[1] * (-center[1] - translations[1])
matrix[5] += matrix[3] * (-center[0] - translations[0]) + matrix[4] * (-center[1] - translations[1])
# Apply center translation: C * RSS^-1 * C^-1 * T^-1
matrix[2] += center[0]
matrix[5] += center[1]
if __version__ >= '5':
kwargs = {"fillcolor": fill_value}
else:
kwargs = {}
return ToTensor(image.transform(output_size, Image.AFFINE, matrix, fill_mode, **kwargs))
def RandomZoom(image, zoom_range, fill_mode='nearest', fill_value=0, is_hwc=True, interpolation_order=1):
'''
Parameters
----------
image:
Input tensor. Must be 3D.
zoom_range:
Tuple of floats; zoom range for width and height.
is_hwc:
The flag of image shape, (H, W, C) or (N, H, W, C) if True and (C, H, W) or (N, C, H, W) if False (default=True).
fill_mode:
Points outside the boundaries of the input are filled according to the given mode (one of {'constant', 'nearest', 'reflect', 'wrap'}).
fill_value:
Value used for points outside the boundaries of the input if mode='constant'.
interpolation_order:
int, order of spline interpolation. see ndimage.interpolation.affine_transform
Returns
Zoomed Numpy image tensor.
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, np.ndarray):
image = ToPIL(image)
if not isinstance(image, Image.Image):
raise TypeError(augment_error_message.format(type(image)))
DE_PY_INTER_MODE = {'nearest': Image.NEAREST, 'bilinear': Image.BILINEAR, 'bicubic': Image.BICUBIC}
if isinstance(zoom_range, list) or isinstance(zoom_range, tuple):
if len(zoom_range) == 2:
scale = random.uniform(zoom_range[0], zoom_range[1])
else:
raise ValueError('The length of zoom_range must be 2')
else:
raise ValueError(
"Zoom_range should be a single value or a tuple/list containing " + "two values. Got {}".format(zoom_range)
)
if fill_mode not in ('nearest', 'bilinear', 'bicubic'):
raise TypeError('Fill_mode must be in (nearest,bilinear,bicubic)')
fill_mode = DE_PY_INTER_MODE[fill_mode]
width, height = image.size
translations = (0, 0)
shear = (0, 0)
output_size = image.size
center = (width * 0.5 + 0.5, height * 0.5 + 0.5)
angle = math.radians(0)
d = math.cos(angle + shear[0]) * math.cos(angle + shear[1]) + \
math.sin(angle + shear[0]) * math.sin(angle + shear[1])
matrix = [
math.cos(angle + shear[0]),
math.sin(angle + shear[0]), 0, -math.sin(angle + shear[1]),
math.cos(angle + shear[1]), 0
]
matrix = [scale / d * m for m in matrix]
matrix[2] += matrix[0] * (-center[0] - translations[0]) + matrix[1] * (-center[1] - translations[1])
matrix[5] += matrix[3] * (-center[0] - translations[0]) + matrix[4] * (-center[1] - translations[1])
# Apply center translation: C * RSS^-1 * C^-1 * T^-1
matrix[2] += center[0]
matrix[5] += center[1]
if __version__ >= '5':
kwargs = {"fillcolor": fill_value}
else:
kwargs = {}
return ToTensor(image.transform(output_size, Image.AFFINE, matrix, fill_mode, **kwargs))
def Rescale(image, scale, offset=0):
'''
Parameters
----------
image:
3-D image or 4-D images
scale:
Float, the scale to apply to the inputs.
offset:
Float, the offset to apply to the inputs.
Returns:
rescaled images
-------
'''
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
return ToTensor(image * scale + offset)
def RandomFlipVertical(image, prob=0.5):
if prob > random.random():
image = FlipVertical(image)
return image
def RandomFlipHorizontal(image, prob=0.5):
if prob > random.random():
image = FlipHorizontal(image)
return image
def HWC2CHW(image):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
image_shape = image.shape
if (len(image_shape) == 3):
return Transpose(image, (2, 0, 1))
elif (len(image_shape) == 4):
return Transpose(image, (0, 3, 1, 2))
else:
raise ValueError('\'image\' must have either 3 or 4 dimensions.')
def CHW2HWC(image):
if not isinstance(image, np.ndarray) and not isinstance(image, Image.Image):
image = Decode(image)
if isinstance(image, Image.Image) or isinstance(image, np.ndarray):
image = ToTensor(image)
if not isinstance(image, np.ndarray):
raise TypeError('img should be NumPy image. Got {}'.format(type(image)))
image_shape = image.shape
if (len(image_shape) == 3):
return Transpose(image, (1, 2, 0))
elif (len(image_shape) == 4):
return Transpose(image, (0, 2, 3, 1))
else:
raise ValueError('\'image\' must have either 3 or 4 dimensions.')