# All imports defined here
import csv
import os
import random
import matplotlib.pyplot as plt
import cv2
import numpy as np
import json
from scipy.misc import imresize
from scipy.ndimage import rotate
from scipy.stats import bernoulli
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle


class InputDataProvider(object):
    def __init__(self, file_location):
        self.center_images = []
        self.steering = []
        self.train_samples = []
        self.validation_samples = []
        self.folder_location = os.path.dirname(file_location)
        self.img_folder_location = os.path.join(self.folder_location, 'IMG')
        self.steering_correction = 0.25
        self.non_zero_steer_angle_index = []
        self.zero_steer_angle_index = [] 
        self.num_samples = 0
        with open(file_location) as csvfile:
            reader = csv.reader(csvfile)
            for line_run, line in enumerate(reader):
                if line_run != 0:
                    self.center_images.append(line[0].split('/')[-1])
                    steer_angle = float(line[3])
                    self.steering.append(steer_angle)
                    if steer_angle:
                        self.non_zero_steer_angle_index.append(line_run-1)
                    else:
                        self.zero_steer_angle_index.append(line_run-1)
        self.num_samples = len(self.center_images)
        self.train_samples, self.validation_samples = train_test_split(range(self.num_samples), test_size=0.2)
        #self.train_samples, self.validation_samples = train_test_split(self.non_zero_steer_angle_index, test_size=0.2)
        #train_zero_samples, valid_zero_samples = train_test_split(self.zero_steer_angle_index, test_size=0.2)
        #self.train_samples.extend(train_zero_samples)
        #self.validation_samples.extend(valid_zero_samples)
        
    def get_num_of_samples(self):
        return self.num_samples

    def get_training_samples(self):
        return self.train_samples

    def get_validation_samples(self):
        return self.validation_samples

    def get_center_image_list(self):
        return self.center_images
    
    def get_steering_values_list(self):
        return self.steering

    def get_image_angle(self, index, which_camera='center'):
        image_name = self.center_images[index]
        new_image_name = os.path.join(self.img_folder_location, 
                                      image_name.replace('center', which_camera))
        steering_value = self.steering[index]
        if which_camera == 'left':
            steering_value += self.steering_correction
        elif which_camera =='right':
            steering_value -= self.steering_correction
        return cv2.imread(new_image_name), steering_value

    def genearte_new_image(self, index):
        # 0 - center, 1 - left, 2- right 
        select_camera = np.random.randint(3)
        if select_camera == 0:
            cur_image, cur_angle = self.get_image_angle(index)
        elif select_camera == 1:
            cur_image, cur_angle = self.get_image_angle(index, 'left')
        elif select_camera == 2:
            cur_image, cur_angle = self.get_image_angle(index, 'right')
        #  crop the image
        cur_image = image_crop(cur_image)
        # randomly apply one ogf the data augmenatation techniques
        select_data_aug_process = np.random.randint(8)
        if select_data_aug_process == 0:
            cur_image = add_per_channel(cur_image)
        elif select_data_aug_process == 1:
            cur_image = gaussian_blur(cur_image)
        elif select_data_aug_process == 2:
            cur_image = change_brightness(cur_image)
        elif select_data_aug_process == 3:
            cur_image = add_gaussian_noise(cur_image)
        elif select_data_aug_process == 4:
            cur_image, stering_angle_change = shear_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 5:
            cur_image, stering_angle_change = rotate_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 6:
            cur_image = image_dropout(cur_image)
        elif select_data_aug_process == 7:
            cur_image = gamma_correction(cur_image)

       # resize the image
        cur_image = resize_image(cur_image)
        # randomly flip the image based on the output of coin flip
        if bernoulli.rvs(0.3):
            cur_image = flip_the_figure(cur_image)
            cur_angle *= -1
        return cur_image, cur_angle

def image_crop(input_image, top_percent=35, bottom_percent=10):
    image_height, image_width, _ = input_image.shape
    top_pixel_num = int((image_height*top_percent)/100)
    bottom_pixel_num = int((image_height*bottom_percent)/100)
    return input_image[top_pixel_num:image_height-bottom_pixel_num ,:,:]

def flip_the_figure(input_image):
    return cv2.flip(input_image, 1)

def add_per_channel(input_image, max_addition=50):
    copy_image = np.float32(np.copy(input_image))
    copy_image[:,:,0] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[:,:,1] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[:,:,2] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[copy_image < 0] = 0
    copy_image[copy_image > 255] = 255
    return np.uint8(copy_image)

def gaussian_blur(input_image, kernal_size=(5,5)):
    return cv2.GaussianBlur(input_image, kernal_size, 0)

def change_brightness(input_image, max_change_brightness_value=100):
    lab = cv2.cvtColor(input_image, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)
    new_l = l + float(np.random.randint(-max_change_brightness_value, max_change_brightness_value + 1))
    new_l[new_l < 0] = 0
    new_l[new_l > 255] = 255
    lab = cv2.merge((np.uint8(new_l), a, b))
    return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)

def resize_image(input_image, new_size=(64,64,3)):
    return imresize(input_image, new_size)

def add_gaussian_noise(input_image):
    num_row, num_col, num_ch= input_image.shape
    mean = 0
    var = 0.1
    sigma = var**0.5
    gaussian_noise = np.random.normal(mean, sigma, (num_row, num_col, num_ch))
    gaussian_noise = gaussian_noise.reshape(num_row, num_col, num_ch)
    new_image = input_image + gaussian_noise
    new_image[new_image < 0] = 0
    new_image[new_image > 255] = 255
    return np.uint8(new_image)

def shear_the_image(input_image, shear_range=100):
    # credits as per https://medium.com/@ksakmann/behavioral-cloning-make-a-car-drive-like-yourself-dc6021152713
    num_rows, num_cols, num_ch = input_image.shape
    change_x = np.random.randint(-shear_range, shear_range + 1)
    random_point = [num_cols / 2 + change_x, num_rows / 2]
    pt_1 = np.float32([[0, num_rows], [num_cols, num_rows], [num_cols / 2, num_rows / 2]])
    pt_2 = np.float32([[0, num_rows], [num_cols, num_rows], random_point])
    change_steering_angle = change_x / (num_rows / 2) * 360 / (2 * np.pi * 25.0) / 6.0
    tranformer = cv2.getAffineTransform(pt_1, pt_2)
    input_image = cv2.warpAffine(input_image, tranformer, (num_cols, num_rows), borderMode=1)
    return input_image, change_steering_angle

def rotate_the_image(input_image, rotation_amount=10):
    angle = np.random.uniform(-rotation_amount, rotation_amount + 1)
    change_steering_angle = (-1) * (np.pi / 180.0) * angle
    return rotate(input_image, angle, reshape=False), change_steering_angle

def image_dropout(input_image, percent_row= 0.30, percent_col= 0.30):
    copy_image = np.copy(input_image)
    # inspired from imgaug (https://github.com/aleju/imgaug)
    num_rows, num_cols, num_ch = input_image.shape
    num_dropout_pixels_row = int(percent_row*num_rows)
    num_dropout_pixels_col = int(percent_col*num_rows)
    row_rand_indexes = np.random.randint(num_rows-1, size=(num_dropout_pixels_row, num_dropout_pixels_col))
    col_rand_indexes =  np.random.randint(num_cols-1, size=(num_dropout_pixels_row, num_dropout_pixels_col))
    chan_rand_indexes =  np.random.randint(num_ch, size=(num_dropout_pixels_row, num_dropout_pixels_col))
    copy_image[row_rand_indexes, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes, col_rand_indexes+1, chan_rand_indexes] = 0
    copy_image[row_rand_indexes+1, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes+1, col_rand_indexes+1, chan_rand_indexes] = 0
    return copy_image

def gamma_correction(input_image):
    # inspired from http://www.pyimagesearch.com/2015/10/05/opencv-gamma-correction/
	inv_gamma = 1.0 / (np.random.randint(5, 16)/10.0)
	table = np.array([((i / 255.0) ** inv_gamma) * 255
		for i in np.arange(0, 256)]).astype("uint8")
 	# apply gamma correction using the lookup table
	return cv2.LUT(input_image, table)
    
def data_generator(input_data_provider_obj, mode='training', batch_size=64):
    if mode == 'training':
        samples = input_data_provider_obj.get_training_samples()
    elif mode == 'validation':
        samples = input_data_provider_obj.get_validation_samples()
    num_samples = len(samples)
    while 1: # Loop forever so the generator never terminates
        shuffle(samples)
        for offset in range(0, num_samples, batch_size):
            batch_samples = samples[offset:offset+batch_size]
            images = []
            angles = []
            for batch_sample in batch_samples:
                new_image, new_steering_angle = input_data_provider_obj.genearte_new_image(batch_sample)
                images.append(new_image)
                angles.append(new_steering_angle)
            X_train = np.array(images)
            y_train = np.array(angles)
            yield shuffle(X_train, y_train)

def plot_model_performance(history_object):
    plt.plot(history_object.history['loss'])
    plt.plot(history_object.history['val_loss'])
    plt.title('Model mean squared error loss')
    plt.ylabel('Mean squared error loss')
    plt.xlabel('Epoch')
    plt.legend(['Training set', 'Validation set'], loc='upper right')
    plt.show()

def save_model_arch(model, model_file_loc):
    delete_the_file(model_file_loc)
    model_data = model.to_json()
    with open(model_file_loc, 'w') as output_file:
        json.dump(model_data, output_file)

def delete_the_file(file):
    if os.path.exists(file):
        try:
            os.remove(file)
        except OSError as error:
            pass





49dc0e381dc88192cfd0d7a0e3cf311291d770e4
<mask token>


class InputDataProvider(object):

    def __init__(self, file_location):
        self.center_images = []
        self.steering = []
        self.train_samples = []
        self.validation_samples = []
        self.folder_location = os.path.dirname(file_location)
        self.img_folder_location = os.path.join(self.folder_location, 'IMG')
        self.steering_correction = 0.25
        self.non_zero_steer_angle_index = []
        self.zero_steer_angle_index = []
        self.num_samples = 0
        with open(file_location) as csvfile:
            reader = csv.reader(csvfile)
            for line_run, line in enumerate(reader):
                if line_run != 0:
                    self.center_images.append(line[0].split('/')[-1])
                    steer_angle = float(line[3])
                    self.steering.append(steer_angle)
                    if steer_angle:
                        self.non_zero_steer_angle_index.append(line_run - 1)
                    else:
                        self.zero_steer_angle_index.append(line_run - 1)
        self.num_samples = len(self.center_images)
        self.train_samples, self.validation_samples = train_test_split(range
            (self.num_samples), test_size=0.2)

    def get_num_of_samples(self):
        return self.num_samples

    def get_training_samples(self):
        return self.train_samples

    def get_validation_samples(self):
        return self.validation_samples

    def get_center_image_list(self):
        return self.center_images

    def get_steering_values_list(self):
        return self.steering

    def get_image_angle(self, index, which_camera='center'):
        image_name = self.center_images[index]
        new_image_name = os.path.join(self.img_folder_location, image_name.
            replace('center', which_camera))
        steering_value = self.steering[index]
        if which_camera == 'left':
            steering_value += self.steering_correction
        elif which_camera == 'right':
            steering_value -= self.steering_correction
        return cv2.imread(new_image_name), steering_value

    def genearte_new_image(self, index):
        select_camera = np.random.randint(3)
        if select_camera == 0:
            cur_image, cur_angle = self.get_image_angle(index)
        elif select_camera == 1:
            cur_image, cur_angle = self.get_image_angle(index, 'left')
        elif select_camera == 2:
            cur_image, cur_angle = self.get_image_angle(index, 'right')
        cur_image = image_crop(cur_image)
        select_data_aug_process = np.random.randint(8)
        if select_data_aug_process == 0:
            cur_image = add_per_channel(cur_image)
        elif select_data_aug_process == 1:
            cur_image = gaussian_blur(cur_image)
        elif select_data_aug_process == 2:
            cur_image = change_brightness(cur_image)
        elif select_data_aug_process == 3:
            cur_image = add_gaussian_noise(cur_image)
        elif select_data_aug_process == 4:
            cur_image, stering_angle_change = shear_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 5:
            cur_image, stering_angle_change = rotate_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 6:
            cur_image = image_dropout(cur_image)
        elif select_data_aug_process == 7:
            cur_image = gamma_correction(cur_image)
        cur_image = resize_image(cur_image)
        if bernoulli.rvs(0.3):
            cur_image = flip_the_figure(cur_image)
            cur_angle *= -1
        return cur_image, cur_angle


def image_crop(input_image, top_percent=35, bottom_percent=10):
    image_height, image_width, _ = input_image.shape
    top_pixel_num = int(image_height * top_percent / 100)
    bottom_pixel_num = int(image_height * bottom_percent / 100)
    return input_image[top_pixel_num:image_height - bottom_pixel_num, :, :]


def flip_the_figure(input_image):
    return cv2.flip(input_image, 1)


<mask token>


def gaussian_blur(input_image, kernal_size=(5, 5)):
    return cv2.GaussianBlur(input_image, kernal_size, 0)


def change_brightness(input_image, max_change_brightness_value=100):
    lab = cv2.cvtColor(input_image, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)
    new_l = l + float(np.random.randint(-max_change_brightness_value, 
        max_change_brightness_value + 1))
    new_l[new_l < 0] = 0
    new_l[new_l > 255] = 255
    lab = cv2.merge((np.uint8(new_l), a, b))
    return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)


def resize_image(input_image, new_size=(64, 64, 3)):
    return imresize(input_image, new_size)


<mask token>


def shear_the_image(input_image, shear_range=100):
    num_rows, num_cols, num_ch = input_image.shape
    change_x = np.random.randint(-shear_range, shear_range + 1)
    random_point = [num_cols / 2 + change_x, num_rows / 2]
    pt_1 = np.float32([[0, num_rows], [num_cols, num_rows], [num_cols / 2, 
        num_rows / 2]])
    pt_2 = np.float32([[0, num_rows], [num_cols, num_rows], random_point])
    change_steering_angle = change_x / (num_rows / 2) * 360 / (2 * np.pi * 25.0
        ) / 6.0
    tranformer = cv2.getAffineTransform(pt_1, pt_2)
    input_image = cv2.warpAffine(input_image, tranformer, (num_cols,
        num_rows), borderMode=1)
    return input_image, change_steering_angle


def rotate_the_image(input_image, rotation_amount=10):
    angle = np.random.uniform(-rotation_amount, rotation_amount + 1)
    change_steering_angle = -1 * (np.pi / 180.0) * angle
    return rotate(input_image, angle, reshape=False), change_steering_angle


def image_dropout(input_image, percent_row=0.3, percent_col=0.3):
    copy_image = np.copy(input_image)
    num_rows, num_cols, num_ch = input_image.shape
    num_dropout_pixels_row = int(percent_row * num_rows)
    num_dropout_pixels_col = int(percent_col * num_rows)
    row_rand_indexes = np.random.randint(num_rows - 1, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    col_rand_indexes = np.random.randint(num_cols - 1, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    chan_rand_indexes = np.random.randint(num_ch, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    copy_image[row_rand_indexes, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes, col_rand_indexes + 1, chan_rand_indexes] = 0
    copy_image[row_rand_indexes + 1, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes + 1, col_rand_indexes + 1, chan_rand_indexes
        ] = 0
    return copy_image


<mask token>


def data_generator(input_data_provider_obj, mode='training', batch_size=64):
    if mode == 'training':
        samples = input_data_provider_obj.get_training_samples()
    elif mode == 'validation':
        samples = input_data_provider_obj.get_validation_samples()
    num_samples = len(samples)
    while 1:
        shuffle(samples)
        for offset in range(0, num_samples, batch_size):
            batch_samples = samples[offset:offset + batch_size]
            images = []
            angles = []
            for batch_sample in batch_samples:
                new_image, new_steering_angle = (input_data_provider_obj.
                    genearte_new_image(batch_sample))
                images.append(new_image)
                angles.append(new_steering_angle)
            X_train = np.array(images)
            y_train = np.array(angles)
            yield shuffle(X_train, y_train)


def plot_model_performance(history_object):
    plt.plot(history_object.history['loss'])
    plt.plot(history_object.history['val_loss'])
    plt.title('Model mean squared error loss')
    plt.ylabel('Mean squared error loss')
    plt.xlabel('Epoch')
    plt.legend(['Training set', 'Validation set'], loc='upper right')
    plt.show()


<mask token>


def delete_the_file(file):
    if os.path.exists(file):
        try:
            os.remove(file)
        except OSError as error:
            pass

<mask token>


class InputDataProvider(object):

    def __init__(self, file_location):
        self.center_images = []
        self.steering = []
        self.train_samples = []
        self.validation_samples = []
        self.folder_location = os.path.dirname(file_location)
        self.img_folder_location = os.path.join(self.folder_location, 'IMG')
        self.steering_correction = 0.25
        self.non_zero_steer_angle_index = []
        self.zero_steer_angle_index = []
        self.num_samples = 0
        with open(file_location) as csvfile:
            reader = csv.reader(csvfile)
            for line_run, line in enumerate(reader):
                if line_run != 0:
                    self.center_images.append(line[0].split('/')[-1])
                    steer_angle = float(line[3])
                    self.steering.append(steer_angle)
                    if steer_angle:
                        self.non_zero_steer_angle_index.append(line_run - 1)
                    else:
                        self.zero_steer_angle_index.append(line_run - 1)
        self.num_samples = len(self.center_images)
        self.train_samples, self.validation_samples = train_test_split(range
            (self.num_samples), test_size=0.2)

    def get_num_of_samples(self):
        return self.num_samples

    def get_training_samples(self):
        return self.train_samples

    def get_validation_samples(self):
        return self.validation_samples

    def get_center_image_list(self):
        return self.center_images

    def get_steering_values_list(self):
        return self.steering

    def get_image_angle(self, index, which_camera='center'):
        image_name = self.center_images[index]
        new_image_name = os.path.join(self.img_folder_location, image_name.
            replace('center', which_camera))
        steering_value = self.steering[index]
        if which_camera == 'left':
            steering_value += self.steering_correction
        elif which_camera == 'right':
            steering_value -= self.steering_correction
        return cv2.imread(new_image_name), steering_value

    def genearte_new_image(self, index):
        select_camera = np.random.randint(3)
        if select_camera == 0:
            cur_image, cur_angle = self.get_image_angle(index)
        elif select_camera == 1:
            cur_image, cur_angle = self.get_image_angle(index, 'left')
        elif select_camera == 2:
            cur_image, cur_angle = self.get_image_angle(index, 'right')
        cur_image = image_crop(cur_image)
        select_data_aug_process = np.random.randint(8)
        if select_data_aug_process == 0:
            cur_image = add_per_channel(cur_image)
        elif select_data_aug_process == 1:
            cur_image = gaussian_blur(cur_image)
        elif select_data_aug_process == 2:
            cur_image = change_brightness(cur_image)
        elif select_data_aug_process == 3:
            cur_image = add_gaussian_noise(cur_image)
        elif select_data_aug_process == 4:
            cur_image, stering_angle_change = shear_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 5:
            cur_image, stering_angle_change = rotate_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 6:
            cur_image = image_dropout(cur_image)
        elif select_data_aug_process == 7:
            cur_image = gamma_correction(cur_image)
        cur_image = resize_image(cur_image)
        if bernoulli.rvs(0.3):
            cur_image = flip_the_figure(cur_image)
            cur_angle *= -1
        return cur_image, cur_angle


def image_crop(input_image, top_percent=35, bottom_percent=10):
    image_height, image_width, _ = input_image.shape
    top_pixel_num = int(image_height * top_percent / 100)
    bottom_pixel_num = int(image_height * bottom_percent / 100)
    return input_image[top_pixel_num:image_height - bottom_pixel_num, :, :]


def flip_the_figure(input_image):
    return cv2.flip(input_image, 1)


<mask token>


def gaussian_blur(input_image, kernal_size=(5, 5)):
    return cv2.GaussianBlur(input_image, kernal_size, 0)


def change_brightness(input_image, max_change_brightness_value=100):
    lab = cv2.cvtColor(input_image, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)
    new_l = l + float(np.random.randint(-max_change_brightness_value, 
        max_change_brightness_value + 1))
    new_l[new_l < 0] = 0
    new_l[new_l > 255] = 255
    lab = cv2.merge((np.uint8(new_l), a, b))
    return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)


def resize_image(input_image, new_size=(64, 64, 3)):
    return imresize(input_image, new_size)


<mask token>


def shear_the_image(input_image, shear_range=100):
    num_rows, num_cols, num_ch = input_image.shape
    change_x = np.random.randint(-shear_range, shear_range + 1)
    random_point = [num_cols / 2 + change_x, num_rows / 2]
    pt_1 = np.float32([[0, num_rows], [num_cols, num_rows], [num_cols / 2, 
        num_rows / 2]])
    pt_2 = np.float32([[0, num_rows], [num_cols, num_rows], random_point])
    change_steering_angle = change_x / (num_rows / 2) * 360 / (2 * np.pi * 25.0
        ) / 6.0
    tranformer = cv2.getAffineTransform(pt_1, pt_2)
    input_image = cv2.warpAffine(input_image, tranformer, (num_cols,
        num_rows), borderMode=1)
    return input_image, change_steering_angle


def rotate_the_image(input_image, rotation_amount=10):
    angle = np.random.uniform(-rotation_amount, rotation_amount + 1)
    change_steering_angle = -1 * (np.pi / 180.0) * angle
    return rotate(input_image, angle, reshape=False), change_steering_angle


def image_dropout(input_image, percent_row=0.3, percent_col=0.3):
    copy_image = np.copy(input_image)
    num_rows, num_cols, num_ch = input_image.shape
    num_dropout_pixels_row = int(percent_row * num_rows)
    num_dropout_pixels_col = int(percent_col * num_rows)
    row_rand_indexes = np.random.randint(num_rows - 1, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    col_rand_indexes = np.random.randint(num_cols - 1, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    chan_rand_indexes = np.random.randint(num_ch, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    copy_image[row_rand_indexes, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes, col_rand_indexes + 1, chan_rand_indexes] = 0
    copy_image[row_rand_indexes + 1, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes + 1, col_rand_indexes + 1, chan_rand_indexes
        ] = 0
    return copy_image


def gamma_correction(input_image):
    inv_gamma = 1.0 / (np.random.randint(5, 16) / 10.0)
    table = np.array([((i / 255.0) ** inv_gamma * 255) for i in np.arange(0,
        256)]).astype('uint8')
    return cv2.LUT(input_image, table)


def data_generator(input_data_provider_obj, mode='training', batch_size=64):
    if mode == 'training':
        samples = input_data_provider_obj.get_training_samples()
    elif mode == 'validation':
        samples = input_data_provider_obj.get_validation_samples()
    num_samples = len(samples)
    while 1:
        shuffle(samples)
        for offset in range(0, num_samples, batch_size):
            batch_samples = samples[offset:offset + batch_size]
            images = []
            angles = []
            for batch_sample in batch_samples:
                new_image, new_steering_angle = (input_data_provider_obj.
                    genearte_new_image(batch_sample))
                images.append(new_image)
                angles.append(new_steering_angle)
            X_train = np.array(images)
            y_train = np.array(angles)
            yield shuffle(X_train, y_train)


def plot_model_performance(history_object):
    plt.plot(history_object.history['loss'])
    plt.plot(history_object.history['val_loss'])
    plt.title('Model mean squared error loss')
    plt.ylabel('Mean squared error loss')
    plt.xlabel('Epoch')
    plt.legend(['Training set', 'Validation set'], loc='upper right')
    plt.show()


def save_model_arch(model, model_file_loc):
    delete_the_file(model_file_loc)
    model_data = model.to_json()
    with open(model_file_loc, 'w') as output_file:
        json.dump(model_data, output_file)


def delete_the_file(file):
    if os.path.exists(file):
        try:
            os.remove(file)
        except OSError as error:
            pass

<mask token>


class InputDataProvider(object):

    def __init__(self, file_location):
        self.center_images = []
        self.steering = []
        self.train_samples = []
        self.validation_samples = []
        self.folder_location = os.path.dirname(file_location)
        self.img_folder_location = os.path.join(self.folder_location, 'IMG')
        self.steering_correction = 0.25
        self.non_zero_steer_angle_index = []
        self.zero_steer_angle_index = []
        self.num_samples = 0
        with open(file_location) as csvfile:
            reader = csv.reader(csvfile)
            for line_run, line in enumerate(reader):
                if line_run != 0:
                    self.center_images.append(line[0].split('/')[-1])
                    steer_angle = float(line[3])
                    self.steering.append(steer_angle)
                    if steer_angle:
                        self.non_zero_steer_angle_index.append(line_run - 1)
                    else:
                        self.zero_steer_angle_index.append(line_run - 1)
        self.num_samples = len(self.center_images)
        self.train_samples, self.validation_samples = train_test_split(range
            (self.num_samples), test_size=0.2)

    def get_num_of_samples(self):
        return self.num_samples

    def get_training_samples(self):
        return self.train_samples

    def get_validation_samples(self):
        return self.validation_samples

    def get_center_image_list(self):
        return self.center_images

    def get_steering_values_list(self):
        return self.steering

    def get_image_angle(self, index, which_camera='center'):
        image_name = self.center_images[index]
        new_image_name = os.path.join(self.img_folder_location, image_name.
            replace('center', which_camera))
        steering_value = self.steering[index]
        if which_camera == 'left':
            steering_value += self.steering_correction
        elif which_camera == 'right':
            steering_value -= self.steering_correction
        return cv2.imread(new_image_name), steering_value

    def genearte_new_image(self, index):
        select_camera = np.random.randint(3)
        if select_camera == 0:
            cur_image, cur_angle = self.get_image_angle(index)
        elif select_camera == 1:
            cur_image, cur_angle = self.get_image_angle(index, 'left')
        elif select_camera == 2:
            cur_image, cur_angle = self.get_image_angle(index, 'right')
        cur_image = image_crop(cur_image)
        select_data_aug_process = np.random.randint(8)
        if select_data_aug_process == 0:
            cur_image = add_per_channel(cur_image)
        elif select_data_aug_process == 1:
            cur_image = gaussian_blur(cur_image)
        elif select_data_aug_process == 2:
            cur_image = change_brightness(cur_image)
        elif select_data_aug_process == 3:
            cur_image = add_gaussian_noise(cur_image)
        elif select_data_aug_process == 4:
            cur_image, stering_angle_change = shear_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 5:
            cur_image, stering_angle_change = rotate_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 6:
            cur_image = image_dropout(cur_image)
        elif select_data_aug_process == 7:
            cur_image = gamma_correction(cur_image)
        cur_image = resize_image(cur_image)
        if bernoulli.rvs(0.3):
            cur_image = flip_the_figure(cur_image)
            cur_angle *= -1
        return cur_image, cur_angle


def image_crop(input_image, top_percent=35, bottom_percent=10):
    image_height, image_width, _ = input_image.shape
    top_pixel_num = int(image_height * top_percent / 100)
    bottom_pixel_num = int(image_height * bottom_percent / 100)
    return input_image[top_pixel_num:image_height - bottom_pixel_num, :, :]


def flip_the_figure(input_image):
    return cv2.flip(input_image, 1)


def add_per_channel(input_image, max_addition=50):
    copy_image = np.float32(np.copy(input_image))
    copy_image[:, :, 0] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[:, :, 1] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[:, :, 2] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[copy_image < 0] = 0
    copy_image[copy_image > 255] = 255
    return np.uint8(copy_image)


def gaussian_blur(input_image, kernal_size=(5, 5)):
    return cv2.GaussianBlur(input_image, kernal_size, 0)


def change_brightness(input_image, max_change_brightness_value=100):
    lab = cv2.cvtColor(input_image, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)
    new_l = l + float(np.random.randint(-max_change_brightness_value, 
        max_change_brightness_value + 1))
    new_l[new_l < 0] = 0
    new_l[new_l > 255] = 255
    lab = cv2.merge((np.uint8(new_l), a, b))
    return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)


def resize_image(input_image, new_size=(64, 64, 3)):
    return imresize(input_image, new_size)


def add_gaussian_noise(input_image):
    num_row, num_col, num_ch = input_image.shape
    mean = 0
    var = 0.1
    sigma = var ** 0.5
    gaussian_noise = np.random.normal(mean, sigma, (num_row, num_col, num_ch))
    gaussian_noise = gaussian_noise.reshape(num_row, num_col, num_ch)
    new_image = input_image + gaussian_noise
    new_image[new_image < 0] = 0
    new_image[new_image > 255] = 255
    return np.uint8(new_image)


def shear_the_image(input_image, shear_range=100):
    num_rows, num_cols, num_ch = input_image.shape
    change_x = np.random.randint(-shear_range, shear_range + 1)
    random_point = [num_cols / 2 + change_x, num_rows / 2]
    pt_1 = np.float32([[0, num_rows], [num_cols, num_rows], [num_cols / 2, 
        num_rows / 2]])
    pt_2 = np.float32([[0, num_rows], [num_cols, num_rows], random_point])
    change_steering_angle = change_x / (num_rows / 2) * 360 / (2 * np.pi * 25.0
        ) / 6.0
    tranformer = cv2.getAffineTransform(pt_1, pt_2)
    input_image = cv2.warpAffine(input_image, tranformer, (num_cols,
        num_rows), borderMode=1)
    return input_image, change_steering_angle


def rotate_the_image(input_image, rotation_amount=10):
    angle = np.random.uniform(-rotation_amount, rotation_amount + 1)
    change_steering_angle = -1 * (np.pi / 180.0) * angle
    return rotate(input_image, angle, reshape=False), change_steering_angle


def image_dropout(input_image, percent_row=0.3, percent_col=0.3):
    copy_image = np.copy(input_image)
    num_rows, num_cols, num_ch = input_image.shape
    num_dropout_pixels_row = int(percent_row * num_rows)
    num_dropout_pixels_col = int(percent_col * num_rows)
    row_rand_indexes = np.random.randint(num_rows - 1, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    col_rand_indexes = np.random.randint(num_cols - 1, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    chan_rand_indexes = np.random.randint(num_ch, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    copy_image[row_rand_indexes, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes, col_rand_indexes + 1, chan_rand_indexes] = 0
    copy_image[row_rand_indexes + 1, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes + 1, col_rand_indexes + 1, chan_rand_indexes
        ] = 0
    return copy_image


def gamma_correction(input_image):
    inv_gamma = 1.0 / (np.random.randint(5, 16) / 10.0)
    table = np.array([((i / 255.0) ** inv_gamma * 255) for i in np.arange(0,
        256)]).astype('uint8')
    return cv2.LUT(input_image, table)


def data_generator(input_data_provider_obj, mode='training', batch_size=64):
    if mode == 'training':
        samples = input_data_provider_obj.get_training_samples()
    elif mode == 'validation':
        samples = input_data_provider_obj.get_validation_samples()
    num_samples = len(samples)
    while 1:
        shuffle(samples)
        for offset in range(0, num_samples, batch_size):
            batch_samples = samples[offset:offset + batch_size]
            images = []
            angles = []
            for batch_sample in batch_samples:
                new_image, new_steering_angle = (input_data_provider_obj.
                    genearte_new_image(batch_sample))
                images.append(new_image)
                angles.append(new_steering_angle)
            X_train = np.array(images)
            y_train = np.array(angles)
            yield shuffle(X_train, y_train)


def plot_model_performance(history_object):
    plt.plot(history_object.history['loss'])
    plt.plot(history_object.history['val_loss'])
    plt.title('Model mean squared error loss')
    plt.ylabel('Mean squared error loss')
    plt.xlabel('Epoch')
    plt.legend(['Training set', 'Validation set'], loc='upper right')
    plt.show()


def save_model_arch(model, model_file_loc):
    delete_the_file(model_file_loc)
    model_data = model.to_json()
    with open(model_file_loc, 'w') as output_file:
        json.dump(model_data, output_file)


def delete_the_file(file):
    if os.path.exists(file):
        try:
            os.remove(file)
        except OSError as error:
            pass

import csv
import os
import random
import matplotlib.pyplot as plt
import cv2
import numpy as np
import json
from scipy.misc import imresize
from scipy.ndimage import rotate
from scipy.stats import bernoulli
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle


class InputDataProvider(object):

    def __init__(self, file_location):
        self.center_images = []
        self.steering = []
        self.train_samples = []
        self.validation_samples = []
        self.folder_location = os.path.dirname(file_location)
        self.img_folder_location = os.path.join(self.folder_location, 'IMG')
        self.steering_correction = 0.25
        self.non_zero_steer_angle_index = []
        self.zero_steer_angle_index = []
        self.num_samples = 0
        with open(file_location) as csvfile:
            reader = csv.reader(csvfile)
            for line_run, line in enumerate(reader):
                if line_run != 0:
                    self.center_images.append(line[0].split('/')[-1])
                    steer_angle = float(line[3])
                    self.steering.append(steer_angle)
                    if steer_angle:
                        self.non_zero_steer_angle_index.append(line_run - 1)
                    else:
                        self.zero_steer_angle_index.append(line_run - 1)
        self.num_samples = len(self.center_images)
        self.train_samples, self.validation_samples = train_test_split(range
            (self.num_samples), test_size=0.2)

    def get_num_of_samples(self):
        return self.num_samples

    def get_training_samples(self):
        return self.train_samples

    def get_validation_samples(self):
        return self.validation_samples

    def get_center_image_list(self):
        return self.center_images

    def get_steering_values_list(self):
        return self.steering

    def get_image_angle(self, index, which_camera='center'):
        image_name = self.center_images[index]
        new_image_name = os.path.join(self.img_folder_location, image_name.
            replace('center', which_camera))
        steering_value = self.steering[index]
        if which_camera == 'left':
            steering_value += self.steering_correction
        elif which_camera == 'right':
            steering_value -= self.steering_correction
        return cv2.imread(new_image_name), steering_value

    def genearte_new_image(self, index):
        select_camera = np.random.randint(3)
        if select_camera == 0:
            cur_image, cur_angle = self.get_image_angle(index)
        elif select_camera == 1:
            cur_image, cur_angle = self.get_image_angle(index, 'left')
        elif select_camera == 2:
            cur_image, cur_angle = self.get_image_angle(index, 'right')
        cur_image = image_crop(cur_image)
        select_data_aug_process = np.random.randint(8)
        if select_data_aug_process == 0:
            cur_image = add_per_channel(cur_image)
        elif select_data_aug_process == 1:
            cur_image = gaussian_blur(cur_image)
        elif select_data_aug_process == 2:
            cur_image = change_brightness(cur_image)
        elif select_data_aug_process == 3:
            cur_image = add_gaussian_noise(cur_image)
        elif select_data_aug_process == 4:
            cur_image, stering_angle_change = shear_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 5:
            cur_image, stering_angle_change = rotate_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 6:
            cur_image = image_dropout(cur_image)
        elif select_data_aug_process == 7:
            cur_image = gamma_correction(cur_image)
        cur_image = resize_image(cur_image)
        if bernoulli.rvs(0.3):
            cur_image = flip_the_figure(cur_image)
            cur_angle *= -1
        return cur_image, cur_angle


def image_crop(input_image, top_percent=35, bottom_percent=10):
    image_height, image_width, _ = input_image.shape
    top_pixel_num = int(image_height * top_percent / 100)
    bottom_pixel_num = int(image_height * bottom_percent / 100)
    return input_image[top_pixel_num:image_height - bottom_pixel_num, :, :]


def flip_the_figure(input_image):
    return cv2.flip(input_image, 1)


def add_per_channel(input_image, max_addition=50):
    copy_image = np.float32(np.copy(input_image))
    copy_image[:, :, 0] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[:, :, 1] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[:, :, 2] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[copy_image < 0] = 0
    copy_image[copy_image > 255] = 255
    return np.uint8(copy_image)


def gaussian_blur(input_image, kernal_size=(5, 5)):
    return cv2.GaussianBlur(input_image, kernal_size, 0)


def change_brightness(input_image, max_change_brightness_value=100):
    lab = cv2.cvtColor(input_image, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)
    new_l = l + float(np.random.randint(-max_change_brightness_value, 
        max_change_brightness_value + 1))
    new_l[new_l < 0] = 0
    new_l[new_l > 255] = 255
    lab = cv2.merge((np.uint8(new_l), a, b))
    return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)


def resize_image(input_image, new_size=(64, 64, 3)):
    return imresize(input_image, new_size)


def add_gaussian_noise(input_image):
    num_row, num_col, num_ch = input_image.shape
    mean = 0
    var = 0.1
    sigma = var ** 0.5
    gaussian_noise = np.random.normal(mean, sigma, (num_row, num_col, num_ch))
    gaussian_noise = gaussian_noise.reshape(num_row, num_col, num_ch)
    new_image = input_image + gaussian_noise
    new_image[new_image < 0] = 0
    new_image[new_image > 255] = 255
    return np.uint8(new_image)


def shear_the_image(input_image, shear_range=100):
    num_rows, num_cols, num_ch = input_image.shape
    change_x = np.random.randint(-shear_range, shear_range + 1)
    random_point = [num_cols / 2 + change_x, num_rows / 2]
    pt_1 = np.float32([[0, num_rows], [num_cols, num_rows], [num_cols / 2, 
        num_rows / 2]])
    pt_2 = np.float32([[0, num_rows], [num_cols, num_rows], random_point])
    change_steering_angle = change_x / (num_rows / 2) * 360 / (2 * np.pi * 25.0
        ) / 6.0
    tranformer = cv2.getAffineTransform(pt_1, pt_2)
    input_image = cv2.warpAffine(input_image, tranformer, (num_cols,
        num_rows), borderMode=1)
    return input_image, change_steering_angle


def rotate_the_image(input_image, rotation_amount=10):
    angle = np.random.uniform(-rotation_amount, rotation_amount + 1)
    change_steering_angle = -1 * (np.pi / 180.0) * angle
    return rotate(input_image, angle, reshape=False), change_steering_angle


def image_dropout(input_image, percent_row=0.3, percent_col=0.3):
    copy_image = np.copy(input_image)
    num_rows, num_cols, num_ch = input_image.shape
    num_dropout_pixels_row = int(percent_row * num_rows)
    num_dropout_pixels_col = int(percent_col * num_rows)
    row_rand_indexes = np.random.randint(num_rows - 1, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    col_rand_indexes = np.random.randint(num_cols - 1, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    chan_rand_indexes = np.random.randint(num_ch, size=(
        num_dropout_pixels_row, num_dropout_pixels_col))
    copy_image[row_rand_indexes, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes, col_rand_indexes + 1, chan_rand_indexes] = 0
    copy_image[row_rand_indexes + 1, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes + 1, col_rand_indexes + 1, chan_rand_indexes
        ] = 0
    return copy_image


def gamma_correction(input_image):
    inv_gamma = 1.0 / (np.random.randint(5, 16) / 10.0)
    table = np.array([((i / 255.0) ** inv_gamma * 255) for i in np.arange(0,
        256)]).astype('uint8')
    return cv2.LUT(input_image, table)


def data_generator(input_data_provider_obj, mode='training', batch_size=64):
    if mode == 'training':
        samples = input_data_provider_obj.get_training_samples()
    elif mode == 'validation':
        samples = input_data_provider_obj.get_validation_samples()
    num_samples = len(samples)
    while 1:
        shuffle(samples)
        for offset in range(0, num_samples, batch_size):
            batch_samples = samples[offset:offset + batch_size]
            images = []
            angles = []
            for batch_sample in batch_samples:
                new_image, new_steering_angle = (input_data_provider_obj.
                    genearte_new_image(batch_sample))
                images.append(new_image)
                angles.append(new_steering_angle)
            X_train = np.array(images)
            y_train = np.array(angles)
            yield shuffle(X_train, y_train)


def plot_model_performance(history_object):
    plt.plot(history_object.history['loss'])
    plt.plot(history_object.history['val_loss'])
    plt.title('Model mean squared error loss')
    plt.ylabel('Mean squared error loss')
    plt.xlabel('Epoch')
    plt.legend(['Training set', 'Validation set'], loc='upper right')
    plt.show()


def save_model_arch(model, model_file_loc):
    delete_the_file(model_file_loc)
    model_data = model.to_json()
    with open(model_file_loc, 'w') as output_file:
        json.dump(model_data, output_file)


def delete_the_file(file):
    if os.path.exists(file):
        try:
            os.remove(file)
        except OSError as error:
            pass

# All imports defined here
import csv
import os
import random
import matplotlib.pyplot as plt
import cv2
import numpy as np
import json
from scipy.misc import imresize
from scipy.ndimage import rotate
from scipy.stats import bernoulli
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle


class InputDataProvider(object):
    def __init__(self, file_location):
        self.center_images = []
        self.steering = []
        self.train_samples = []
        self.validation_samples = []
        self.folder_location = os.path.dirname(file_location)
        self.img_folder_location = os.path.join(self.folder_location, 'IMG')
        self.steering_correction = 0.25
        self.non_zero_steer_angle_index = []
        self.zero_steer_angle_index = [] 
        self.num_samples = 0
        with open(file_location) as csvfile:
            reader = csv.reader(csvfile)
            for line_run, line in enumerate(reader):
                if line_run != 0:
                    self.center_images.append(line[0].split('/')[-1])
                    steer_angle = float(line[3])
                    self.steering.append(steer_angle)
                    if steer_angle:
                        self.non_zero_steer_angle_index.append(line_run-1)
                    else:
                        self.zero_steer_angle_index.append(line_run-1)
        self.num_samples = len(self.center_images)
        self.train_samples, self.validation_samples = train_test_split(range(self.num_samples), test_size=0.2)
        #self.train_samples, self.validation_samples = train_test_split(self.non_zero_steer_angle_index, test_size=0.2)
        #train_zero_samples, valid_zero_samples = train_test_split(self.zero_steer_angle_index, test_size=0.2)
        #self.train_samples.extend(train_zero_samples)
        #self.validation_samples.extend(valid_zero_samples)
        
    def get_num_of_samples(self):
        return self.num_samples

    def get_training_samples(self):
        return self.train_samples

    def get_validation_samples(self):
        return self.validation_samples

    def get_center_image_list(self):
        return self.center_images
    
    def get_steering_values_list(self):
        return self.steering

    def get_image_angle(self, index, which_camera='center'):
        image_name = self.center_images[index]
        new_image_name = os.path.join(self.img_folder_location, 
                                      image_name.replace('center', which_camera))
        steering_value = self.steering[index]
        if which_camera == 'left':
            steering_value += self.steering_correction
        elif which_camera =='right':
            steering_value -= self.steering_correction
        return cv2.imread(new_image_name), steering_value

    def genearte_new_image(self, index):
        # 0 - center, 1 - left, 2- right 
        select_camera = np.random.randint(3)
        if select_camera == 0:
            cur_image, cur_angle = self.get_image_angle(index)
        elif select_camera == 1:
            cur_image, cur_angle = self.get_image_angle(index, 'left')
        elif select_camera == 2:
            cur_image, cur_angle = self.get_image_angle(index, 'right')
        #  crop the image
        cur_image = image_crop(cur_image)
        # randomly apply one ogf the data augmenatation techniques
        select_data_aug_process = np.random.randint(8)
        if select_data_aug_process == 0:
            cur_image = add_per_channel(cur_image)
        elif select_data_aug_process == 1:
            cur_image = gaussian_blur(cur_image)
        elif select_data_aug_process == 2:
            cur_image = change_brightness(cur_image)
        elif select_data_aug_process == 3:
            cur_image = add_gaussian_noise(cur_image)
        elif select_data_aug_process == 4:
            cur_image, stering_angle_change = shear_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 5:
            cur_image, stering_angle_change = rotate_the_image(cur_image)
            cur_angle += stering_angle_change
        elif select_data_aug_process == 6:
            cur_image = image_dropout(cur_image)
        elif select_data_aug_process == 7:
            cur_image = gamma_correction(cur_image)

       # resize the image
        cur_image = resize_image(cur_image)
        # randomly flip the image based on the output of coin flip
        if bernoulli.rvs(0.3):
            cur_image = flip_the_figure(cur_image)
            cur_angle *= -1
        return cur_image, cur_angle

def image_crop(input_image, top_percent=35, bottom_percent=10):
    image_height, image_width, _ = input_image.shape
    top_pixel_num = int((image_height*top_percent)/100)
    bottom_pixel_num = int((image_height*bottom_percent)/100)
    return input_image[top_pixel_num:image_height-bottom_pixel_num ,:,:]

def flip_the_figure(input_image):
    return cv2.flip(input_image, 1)

def add_per_channel(input_image, max_addition=50):
    copy_image = np.float32(np.copy(input_image))
    copy_image[:,:,0] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[:,:,1] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[:,:,2] += np.random.randint(-max_addition, max_addition + 1)
    copy_image[copy_image < 0] = 0
    copy_image[copy_image > 255] = 255
    return np.uint8(copy_image)

def gaussian_blur(input_image, kernal_size=(5,5)):
    return cv2.GaussianBlur(input_image, kernal_size, 0)

def change_brightness(input_image, max_change_brightness_value=100):
    lab = cv2.cvtColor(input_image, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)
    new_l = l + float(np.random.randint(-max_change_brightness_value, max_change_brightness_value + 1))
    new_l[new_l < 0] = 0
    new_l[new_l > 255] = 255
    lab = cv2.merge((np.uint8(new_l), a, b))
    return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)

def resize_image(input_image, new_size=(64,64,3)):
    return imresize(input_image, new_size)

def add_gaussian_noise(input_image):
    num_row, num_col, num_ch= input_image.shape
    mean = 0
    var = 0.1
    sigma = var**0.5
    gaussian_noise = np.random.normal(mean, sigma, (num_row, num_col, num_ch))
    gaussian_noise = gaussian_noise.reshape(num_row, num_col, num_ch)
    new_image = input_image + gaussian_noise
    new_image[new_image < 0] = 0
    new_image[new_image > 255] = 255
    return np.uint8(new_image)

def shear_the_image(input_image, shear_range=100):
    # credits as per https://medium.com/@ksakmann/behavioral-cloning-make-a-car-drive-like-yourself-dc6021152713
    num_rows, num_cols, num_ch = input_image.shape
    change_x = np.random.randint(-shear_range, shear_range + 1)
    random_point = [num_cols / 2 + change_x, num_rows / 2]
    pt_1 = np.float32([[0, num_rows], [num_cols, num_rows], [num_cols / 2, num_rows / 2]])
    pt_2 = np.float32([[0, num_rows], [num_cols, num_rows], random_point])
    change_steering_angle = change_x / (num_rows / 2) * 360 / (2 * np.pi * 25.0) / 6.0
    tranformer = cv2.getAffineTransform(pt_1, pt_2)
    input_image = cv2.warpAffine(input_image, tranformer, (num_cols, num_rows), borderMode=1)
    return input_image, change_steering_angle

def rotate_the_image(input_image, rotation_amount=10):
    angle = np.random.uniform(-rotation_amount, rotation_amount + 1)
    change_steering_angle = (-1) * (np.pi / 180.0) * angle
    return rotate(input_image, angle, reshape=False), change_steering_angle

def image_dropout(input_image, percent_row= 0.30, percent_col= 0.30):
    copy_image = np.copy(input_image)
    # inspired from imgaug (https://github.com/aleju/imgaug)
    num_rows, num_cols, num_ch = input_image.shape
    num_dropout_pixels_row = int(percent_row*num_rows)
    num_dropout_pixels_col = int(percent_col*num_rows)
    row_rand_indexes = np.random.randint(num_rows-1, size=(num_dropout_pixels_row, num_dropout_pixels_col))
    col_rand_indexes =  np.random.randint(num_cols-1, size=(num_dropout_pixels_row, num_dropout_pixels_col))
    chan_rand_indexes =  np.random.randint(num_ch, size=(num_dropout_pixels_row, num_dropout_pixels_col))
    copy_image[row_rand_indexes, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes, col_rand_indexes+1, chan_rand_indexes] = 0
    copy_image[row_rand_indexes+1, col_rand_indexes, chan_rand_indexes] = 0
    copy_image[row_rand_indexes+1, col_rand_indexes+1, chan_rand_indexes] = 0
    return copy_image

def gamma_correction(input_image):
    # inspired from http://www.pyimagesearch.com/2015/10/05/opencv-gamma-correction/
	inv_gamma = 1.0 / (np.random.randint(5, 16)/10.0)
	table = np.array([((i / 255.0) ** inv_gamma) * 255
		for i in np.arange(0, 256)]).astype("uint8")
 	# apply gamma correction using the lookup table
	return cv2.LUT(input_image, table)
    
def data_generator(input_data_provider_obj, mode='training', batch_size=64):
    if mode == 'training':
        samples = input_data_provider_obj.get_training_samples()
    elif mode == 'validation':
        samples = input_data_provider_obj.get_validation_samples()
    num_samples = len(samples)
    while 1: # Loop forever so the generator never terminates
        shuffle(samples)
        for offset in range(0, num_samples, batch_size):
            batch_samples = samples[offset:offset+batch_size]
            images = []
            angles = []
            for batch_sample in batch_samples:
                new_image, new_steering_angle = input_data_provider_obj.genearte_new_image(batch_sample)
                images.append(new_image)
                angles.append(new_steering_angle)
            X_train = np.array(images)
            y_train = np.array(angles)
            yield shuffle(X_train, y_train)

def plot_model_performance(history_object):
    plt.plot(history_object.history['loss'])
    plt.plot(history_object.history['val_loss'])
    plt.title('Model mean squared error loss')
    plt.ylabel('Mean squared error loss')
    plt.xlabel('Epoch')
    plt.legend(['Training set', 'Validation set'], loc='upper right')
    plt.show()

def save_model_arch(model, model_file_loc):
    delete_the_file(model_file_loc)
    model_data = model.to_json()
    with open(model_file_loc, 'w') as output_file:
        json.dump(model_data, output_file)

def delete_the_file(file):
    if os.path.exists(file):
        try:
            os.remove(file)
        except OSError as error:
            pass




