Source code for dltk.core.io.augmentation
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import numpy as np
from scipy.ndimage.interpolation import map_coordinates
from scipy.ndimage.filters import gaussian_filter
[docs]def flip(imglist, axis=1):
""" Randomly flip spatial dimensions
Parameters
----------
imglist : np.ndarray or list or tuple
image(s) to be flipped
axis : int
axis along which to flip the images
Returns
-------
np.ndarray or list or tuple
same as imglist but randomly flipped along axis
"""
was_singular = False
if isinstance(imglist, np.ndarray):
imglist = [imglist]
was_singular = True
do_flip = np.random.random(1)
if do_flip > 0.5:
for i in range(len(imglist)):
imglist[i] = np.flip(imglist[i], axis=axis)
if was_singular:
return imglist[0]
return imglist
[docs]def gaussian_offset(img, sigma=0.1):
""" Add Gaussian offset to an image
Adds the offset to each channel independently
Parameters
----------
img : np.ndarray
image to add noise to
sigma : float
stddev for normal distribution to generate noise from
Returns
-------
np.ndarray
same as image but with added offset to each channel
"""
offsets = np.random.normal(0, sigma, ([1] * (img.ndim - 1) + [img.shape[-1]]))
img += offsets
return img
[docs]def gaussian_noise(img, sigma=0.05):
""" Add Gaussian noise to an image
Parameters
----------
img : np.ndarray
image to add noise to
sigma : float
stddev for normal distribution to generate noise from
Returns
-------
np.ndarray
same as image but with added noise
"""
img += np.random.normal(0, sigma, img.shape)
return img
[docs]def elastic_transform(image, alpha, sigma):
"""Elastic deformation of images as described in [Simard2003]_.
.. [Simard2003] Simard, Steinkraus and Platt, "Best Practices for
Convolutional Neural Networks applied to Visual Document Analysis", in
Proc. of the International Conference on Document Analysis and
Recognition, 2003.
Based on gist https://gist.github.com/erniejunior/601cdf56d2b424757de5
Parameters
----------
image : np.ndarray
image to be deformed
alpha : list
scale of transformation for each dimension
larger values have more deformation
sigma : list
Gaussian window of deformation for each dimension
smaller values have more localised deformation
Returns
-------
np.ndarray
deformed image
"""
assert len(alpha) == len(sigma) , "Dimensions of alpha and sigma are different"
channelbool = image.ndim - len(alpha)
out = np.zeros( (len(alpha)+channelbool,) + image.shape )
# Generate gausian filter, leaving channel dimensions zero
for jj in range(len(alpha)):
array = (np.random.rand(*image.shape) * 2 - 1)
out[jj] = gaussian_filter(array, sigma[jj], mode="constant", cval=0) * alpha[jj]
# Map mask to indices
shapes = list(map( lambda x: slice(0, x, None), image.shape ) )
grid = np.broadcast_arrays(*np.ogrid[ shapes ] )
indices = list( map((lambda x: np.reshape( x , (-1, 1)) ), grid + np.array(out) ) )
# Transform image based on masked indices
transformed_image = map_coordinates(image, indices, order=0,
mode='reflect').reshape(image.shape)
return transformed_image
[docs]def extract_class_balanced_example_array(image, label, example_size=[1, 64, 64], n_examples=1, classes=2, class_weights=None):
"""
Extract training examples from an image (and corresponding label) subject to class balancing.
Returns an image example array and the corresponding label array.
Parameters
----------
image: np.ndarray
image to extract class-balanced patches from
label: np.ndarray
labels to use for balancing the classes
example_size: list or tuple
shape of the patches to extract
n_examples: int
number of patches to extract in total
classes : int or list or tuple
number of classes or list of classes to extract
Returns
-------
ex_imgs, ex_lbls
class-balanced patches extracted from bigger images with shape [batch, example_size..., image_channels]
"""
assert image.shape[:-1] == label.shape, 'Image and label shape must match'
assert image.ndim - 1 == len(example_size), 'Example size doesnt fit image size'
assert all([i_s >= e_s for i_s, e_s in zip(image.shape, example_size)]), \
'Image must be bigger than example shape'
rank = len(example_size)
if isinstance(classes, int):
classes = tuple(range(classes))
n_classes = len(classes)
assert n_examples >= n_classes, 'n_examples need to be bigger than n_classes'
if class_weights is None:
n_ex_per_class = np.ones(n_classes).astype(int) * int(np.round(n_examples / n_classes))
else:
assert len(class_weights) == n_classes, 'class_weights must match number of classes'
class_weights = np.array(class_weights)
n_ex_per_class = np.round((class_weights / class_weights.sum()) * n_examples).astype(int)
# compute an example radius as we are extracting centered around locations
ex_rad = np.array(list(zip(np.floor(np.array(example_size) / 2.0), np.ceil(np.array(example_size) / 2.0))),
dtype=np.int)
class_ex_imgs = []
class_ex_lbls = []
min_ratio = 1.
for c_idx, c in enumerate(classes):
# get valid, random center locations belonging to that class
idx = np.argwhere(label == c)
ex_imgs = []
ex_lbls = []
if len(idx) == 0 or n_ex_per_class[c_idx] == 0:
class_ex_imgs.append([])
class_ex_lbls.append([])
continue
# extract random locations
r_idx_idx = np.random.choice(len(idx), size=min(n_ex_per_class[c_idx], len(idx)), replace=False).astype(int)
r_idx = idx[r_idx_idx]
# add a random shift them to avoid learning a centre bias - IS THIS REALLY TRUE?
r_shift = np.array([list(a) for a in zip(
*[np.random.randint(-ex_rad[i][0] // 2, ex_rad[i][1] // 2, size=len(r_idx_idx)) for i in range(rank)]
)]).astype(int)
r_idx += r_shift
# shift them to valid locations if necessary
r_idx = np.array([np.array([max(min(r[dim], image.shape[dim] - ex_rad[dim][1]),
ex_rad[dim][0]) for dim in range(rank)]) for r in r_idx])
for i in range(len(r_idx)):
# extract class-balanced examples from the original image
slicer = [slice(r_idx[i][dim] - ex_rad[dim][0], r_idx[i][dim] + ex_rad[dim][1]) for dim in range(rank)]
ex_img = image[slicer][np.newaxis, :]
ex_lbl = label[slicer][np.newaxis, :]
# concatenate and return the examples
ex_imgs = np.concatenate((ex_imgs, ex_img), axis=0) if (len(ex_imgs) != 0) else ex_img
ex_lbls = np.concatenate((ex_lbls, ex_lbl), axis=0) if (len(ex_lbls) != 0) else ex_lbl
class_ex_imgs.append(ex_imgs)
class_ex_lbls.append(ex_lbls)
ratio = n_ex_per_class[c_idx] / len(ex_imgs)
min_ratio = ratio if ratio < min_ratio else min_ratio
indices = np.floor(n_ex_per_class * min_ratio).astype(int)
ex_imgs = np.concatenate([cimg[:idxs] for cimg, idxs in zip(class_ex_imgs, indices) if len(cimg) > 0], axis=0)
ex_lbls = np.concatenate([clbl[:idxs] for clbl, idxs in zip(class_ex_lbls, indices) if len(clbl) > 0], axis=0)
# print('returning {} samples with classes:'.format(len(ex_imgs)))
# print(' - '.join(['{}: {} samples'.format(i, len(cimg[:idxs])) for i, (cimg, idxs) in
# enumerate(zip(class_ex_imgs, indices))]))
# print('returning {} {}'.format(ex_imgs.shape, ex_lbls.shape))
return ex_imgs, ex_lbls
[docs]def extract_random_example_array(image_list, example_size=[1, 64, 64], n_examples=1):
"""
Randomly extract training examples from image (and corresponding label).
Returns an image example array and the corresponding label array.
Parameters
----------
image_list: np.ndarray or list or tuple
image(s) to extract random patches from
example_size: list or tuple
shape of the patches to extract
n_examples: int
number of patches to extract in total
Returns
-------
examples
random patches extracted from bigger images with same type as image_list with of shape
[batch, example_size..., image_channels]
"""
assert n_examples > 0
was_singular = False
if isinstance(image_list, np.ndarray):
image_list = [image_list]
was_singular = True
assert all([i_s > e_s for i_s, e_s in zip(image_list[0].shape, example_size)]), \
'Image must be bigger than example shape'
assert (image_list[0].ndim - 1 == len(example_size) or image_list[0].ndim == len(example_size)), \
'Example size doesnt fit image size'
for i in image_list:
if len(image_list) > 1:
assert (i.ndim - 1 == image_list[0].ndim or i.ndim == image_list[0].ndim or i.ndim + 1 == image_list[0].ndim), \
'Example size doesnt fit image size'
assert all([i0_s == i_s for i0_s, i_s in zip(image_list[0].shape, i.shape)]), \
'Image shapes must match'
rank = len(example_size)
# extract random examples from image and label
valid_loc_range = [image_list[0].shape[i] - example_size[i] for i in range(rank)]
rnd_loc = [np.random.randint(valid_loc_range[dim], size=n_examples)
if valid_loc_range[dim] > 0 else np.zeros(n_examples, dtype=int) for dim in range(rank)]
examples = [[]] * len(image_list)
for i in range(n_examples):
slicer = [slice(rnd_loc[dim][i], rnd_loc[dim][i] + example_size[dim]) for dim in range(rank)]
for j in range(len(image_list)):
ex_img = image_list[j][slicer][np.newaxis]
# concatenate and return the examples
examples[j] = np.concatenate((examples[j], ex_img), axis=0) if (len(examples[j]) != 0) else ex_img
if was_singular:
return examples[0]
return examples