Commit ccff3c4e authored by Tom Whyntie's avatar Tom Whyntie
Browse files

Added augmentation functions (ND)

parent b38bef79
......@@ -99,7 +99,8 @@ You can then close the terminal.
## The workshop material
* `ML_workshop_notebook1.ipynb`: The Jupyter notebook for session 2 - an introduction to Machine Learning with (fake) Parkinson's data;
* `ML_workshop_notebook2.ipynb`: The Jupyter notebook for session 3 - an introduction to ML with (real!) imaging data.
* `ML_workshop_notebook2.ipynb`: The Jupyter notebook for session 3 - an introduction to ML with (real!) imaging data;
* `augmentations.py`: A Python file with various image and volume augmentation functions (ND).
## Data
......
# Nicola Dinsdale 2018
# File to add augmentations to the data to try and increase the dataset and be able to train this network!
########################################################################################################################
import random
from scipy.ndimage.interpolation import map_coordinates, shift
from scipy.ndimage.filters import gaussian_filter
from scipy.ndimage import rotate, zoom
import numpy as np
from copy import copy
from scipy.transforms import resize
########################################################################################################################
########################################################################################################################
############################################### 2d augmentations #######################################################
########################################################################################################################
def random_rotation(image_array):
# pick a random degree of rotation between 25% on the left and 25% on the right
random_degree = random.uniform(-10, 10)
image = rotate(image_array, random_degree)
return image
def clipped_zoom(img):
zoom_factor = random.uniform(0.8, 1.5)
h, w = img.shape[:2]
# Zooming out
if zoom_factor < 1:
# Bounding box of the zoomed-out image within the output array
zh = int(np.round(h * zoom_factor))
zw = int(np.round(w * zoom_factor))
top = (h - zh) // 2
left = (w - zw) // 2
# Zero-padding
out = np.zeros_like(img)
out[top:top+zh, left:left+zw] = zoom(img, zoom_factor)
# Zooming in
elif zoom_factor > 1:
# Bounding box of the zoomed-in region within the input array
zh = int(np.round(h / zoom_factor))
zw = int(np.round(w / zoom_factor))
top = (h - zh) // 2
left = (w - zw) // 2
out = zoom(img[top:top+zh, left:left+zw], zoom_factor)
# `out` might still be slightly larger than `img` due to rounding, so
# trim off any extra pixels at the edges
trim_top = ((out.shape[0] - h) // 2)
trim_left = ((out.shape[1] - w) // 2)
out = out[trim_top:trim_top+h, trim_left:trim_left+w]
# If zoom_factor == 1, just return the input array
else:
out = img
if out.shape != (256, 256): # If the zoom has failed
out = img
out = np.array(out)
return out
def random_noise(image_array):
noise = np.random.random(image_array.shape)
image_array[noise > 0.99] = 2
image_array[noise < 0.01] = 0
return image_array
def horizontal_flip(image_array):
im = image_array[:, ::-1]
return im
def elastic_transform_2d(image):
alpha = 20
sigma = 4
rng = np.random.RandomState(42)
interpolation_order = 1
# Take measurements
image_shape = image.shape
# Make random fields
dx = rng.uniform(-1, 1, image_shape) * alpha
dy = rng.uniform(-1, 1, image_shape) * alpha
# Smooth dx and dy
sdx = gaussian_filter(dx, sigma=sigma, mode='reflect')
sdy = gaussian_filter(dy, sigma=sigma, mode='reflect')
# Make mesh grid
x, y = np.meshgrid(np.arange(image_shape[1]), np.arange(image_shape[0]))
# Distort mesh grid indices
distorted_indices = (y + sdy).reshape(-1, 1), \
(x + sdx).reshape(-1, 1)
# Map coordinates from image to distorted index set
transformed_images = map_coordinates(image, distorted_indices, mode='reflect',
order=interpolation_order).reshape(image_shape)
return transformed_images
########################################################################################################################
############################################### 3d augmentations #######################################################
########################################################################################################################
def translate_it(image):
offsetx = random.randint(-5, 5)
offsety = random.randint(-5, 5)
is_seg = False
order = 0 if is_seg == True else 5
translated_im = shift(image, (offsetx, offsety, 0), order=order, mode='nearest')
return translated_im
def scale_it(image):
factor = random.uniform(0.8, 1.5)
is_seg = False
order = 0 if is_seg == True else 3
height, width, depth = image.shape
zheight = int(np.round(factor * height))
zwidth = int(np.round(factor * width))
zdepth = depth
if factor < 1.0:
newimg = np.zeros_like(image)
row = (height - zheight) // 2
col = (width - zwidth) // 2
layer = (depth - zdepth) // 2
newimg[row:row+height, col:col+zwidth, layer:layer+zdepth] = zoom(image, (float(factor), float(factor), 1.0), order=order, mode ='nearest')[0:zheight, 0:zwidth, 0:zdepth]
return newimg
elif factor > 1.0:
row = (zheight - height) // 2
col = (zwidth - width) // 2
layer = (zdepth - depth) // 2
newimg = zoom(image[row:row+zheight, col:col+zwidth, layer:layer+zdepth], (float(factor), float(factor), 1.0), order=order, mode='nearest')
extrah = (newimg.shape[0] - height) // 2
extraw = (newimg.shape[1] - width) // 2
extrad = (newimg.shape[2] - depth) // 2
newimg = newimg[extrah:extrah+height, extraw:extraw+width, extrad:extrad+depth]
return newimg
else:
return image
def rotate_it(image):
theta = random.uniform(-10, 10)
is_seg = False
order = 0 if is_seg == True else 5
new_img = rotate(image, float(theta), reshape=False, order=order, mode='nearest')
return new_img
def blur_it(image):
sigma = 0.3
new_img = gaussian_filter(image, sigma)
return new_img
########################################################################################################################
############################################### Running Function #######################################################
########################################################################################################################
# Function to apply the random augmentations
def augment(image_to_transform, label = None):
# Image applies a random number of the possible transformations to the input image. Returns the transformed image
# If label is none also applies to the image --> important for segmentation or similar
"""
:param image_to_transform: input image as array
:param label: optional: label for input image to also transform. Default = None
:return: transformed image, if label also returns transformed label
"""
# If the image is 2d
if len(image_to_transform.shape) == 2:
# Add to the available transformations any functions from 2d you want to be applied
available_transformations = {'rotate': random_rotation, 'noise': random_noise, 'elastic': elastic_transform_2d,
'zoom': clipped_zoom}
# Decide how many of these transformations o apply
num_transformations_to_apply = random.randint(1, len(available_transformations))
num_transformations = 0
transformed_image = None
transformed_label = None
while num_transformations <= num_transformations_to_apply:
# choose a random transformation to apply for a single image
key = random.choice(list(available_transformations))
transformed_image = available_transformations[key](image_to_transform)
if label:
transformed_label = available_transformations[key](transformed_image)
num_transformations += 1
if label:
return transformed_image, transformed_label
else:
return transformed_image
# If the image is 3d
if len(image_to_transform.shape) == 3:
# Add to the available transformations any functions from 3d you want to be applied
available_transformations = {'rotate': rotate_it, 'noise': blur_it, 'translate': translate_it}
# Decide how many of these transformations to apply
num_transformations_to_apply = random.randint(1, len(available_transformations))
num_transformations = 0
transformed_image = None
while num_transformations <= num_transformations_to_apply:
# choose which transformations to apply at random
key = random.choice(list(available_transformations))
transformed_image = available_transformations[key](image_to_transform)
if label:
transformed_label = available_transformations[key](label)
num_transformations += 1
if label:
return transformed_image, transformed_label
else:
return transformed_image
########################################################################################################################
############################################### Other Functions ########################################################
########################################################################################################################
def crop_around_mask(image, im_shape):
image = image / np.max(image) # Normalise the image, image has to be between -1 and 1 so have to use the max
mask = create_mask(image)
indices = np.nonzero(mask)
minX, minY, minZ = 10000, 10000, 10000
maxX, maxY, maxZ = -1, -1, -1
for (x, y, z) in set(zip(indices[0], indices[1], indices[2])):
minX, minY, minZ, maxX, maxY, maxZ = min(minX, x), min(minY, y), min(minZ, z),\
max(maxX, x), max(maxY, y), max(maxZ, z)
crop = image[int(minX): int(maxX), int(minY): int(maxY), int(minZ): int(maxZ)]
crop = resize(crop, im_shape, order=1)
return crop
def create_mask(image):
mask = copy.copy(image)
mask[mask != 0] = 1
return mask
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment