# !pip install tensorflow
# !pip install patchify
# !pip install segmentation-models

# %env SM_FRAMEWORK=tf.keras
import segmentation_models as sm
import tensorflow as tf
import keras
import albumentations as A
import torch
import csv
from torch import nn
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
import numpy as np
import random
from random import randrange, shuffle
from matplotlib import pyplot as plt
import matplotlib.patches as patches
from torchvision import transforms
from torchvision.transforms.functional import rotate
import torchvision
import cv2
import time, glob
import PIL
import pandas as pd 
import torch.utils.data as data
import csv
import glob
import os
from sklearn.model_selection import train_test_split
import pickle
from tqdm.notebook import tqdm
from PIL import Image
from patchify import patchify, unpatchify


os.environ['CUDA_VISIBLE_DEVICES'] = '0'
ROOT_DIR = "../input/airbus-ship-detection"
test_folder = os.path.join(ROOT_DIR, 'test_v2')
train_folder = os.path.join(ROOT_DIR, 'train_v2')

def rle_to_pixels(rle_code):
    '''
    Transforms a RLE code string into a list of pixels of a (768, 768) canvas
    '''
    rle_code = [int(i) for i in rle_code.split()]
    pixels = [(pixel_position % 768, pixel_position // 768) 
                 for start, length in list(zip(rle_code[0:-1:2], rle_code[1::2])) 
                 for pixel_position in range(start, start + length)]
    return pixels

class Dataloder(keras.utils.Sequence):
    def __init__(self, dataset, batch_size=1, shuffle=False):
        self.dataset = dataset
        self.batch_size = batch_size
        self.shuffle = shuffle
        self.indexes = np.arange(len(dataset))

        self.on_epoch_end()

    def __getitem__(self, i):    
        start = i * self.batch_size
        stop = (i + 1) * self.batch_size
        data = []
        for j in range(start, stop):
            data.append(self.dataset[j])
        
        batch = [np.stack(samples, axis=0) for samples in zip(*data)]
        return batch
    
    def __len__(self):
        return len(self.indexes) // self.batch_size
    
    def on_epoch_end(self):
        if self.shuffle:
            self.indexes = np.random.permutation(self.indexes)


class AirbusShipDataset():
    
    def __init__(self, root_paths, paths, labels_csv=None, transform=None,train=True,size=768,preprocessing_fn=None, augmentation=None):
        self.root_paths = root_paths
        self.paths = paths

        if labels_csv:
            self.masks = pd.read_csv(labels_csv)
            self.masks = self.masks[~self.masks.EncodedPixels.isna()] 
        else:
            self.masks = pd.DataFrame()
        self.transform = transform
        self.train = train
        self.size = size

        self.preprocessing_fn = preprocessing_fn
        self.augmentation = augmentation

    def __len__(self):
        return len(self.paths)
    
    def getranditem(self):
        ImageId, qi, qj = self.paths[randrange(self.__len__())]
         
        all_masks = np.zeros((768, 768))
        img_masks = rle_to_pixels(' '.join(df['EncodedPixels'][ImageId]))
        all_masks[tuple(zip(*img_masks))] = 1
        
        img_path = f"{ROOT_DIR}/{self.root_paths}/{ImageId}"
        image = cv2.imread(img_path)

        step = 128
        patch_img = patchify(image, (step,step, 3), step=step)
        patch_mask = patchify(all_masks, (step,step), step=step)
        return patch_img[qi][qj][0], patch_mask[qi][qj]
    
    def __getitem__(self, idx):
        ImageId, qi, qj = self.paths[idx]

        all_masks = np.zeros((768, 768))
        img_masks = rle_to_pixels(' '.join(df['EncodedPixels'][ImageId]))
        all_masks[tuple(zip(*img_masks))] = 1

        img_path = f"{ROOT_DIR}/{self.root_paths}/{ImageId}"
        image = cv2.imread(img_path)
        image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
        
        step = 128
        patch_img = patchify(image, (step,step, 3), step=step)
        patch_mask = patchify(all_masks, (step,step), step=step)
        image, all_masks = patch_img[qi][qj][0], patch_mask[qi][qj]
                
        if self.augmentation and self.train:
            sample = self.augmentation(image=image, mask=all_masks)
            image, all_masks = sample['image'], sample['mask']
        
        if not self.train:
            x =  image.transpose([2, 0, 1]).astype(np.float32)
            x = x.astype(np.float32)
            tf.cast(x, tf.float32)
            return x, ImageId
        
        if self.preprocessing_fn is not None:
            image = np.array(image)
            sample = self.preprocessing_fn(image = image, mask=all_masks)
            image, all_masks = sample['image'], sample['mask']
        
        image = image.astype(np.float32)
        all_masks = all_masks.astype(np.float32)
        tf.cast(image, tf.float32)
        tf.cast(all_masks, tf.float32)     
        return image, all_masks
    

def round_clip_0_1(x, **kwargs):
    return x.round().clip(0, 1)

# define heavy augmentations
def get_training_augmentation():
    train_transform = [
        A.HorizontalFlip(p=0.5),
        A.ShiftScaleRotate(scale_limit=0.0, rotate_limit=120, shift_limit=0.0, p=0.5),
#         A.PadIfNeeded(min_height=320, min_width=320, always_apply=True, border_mode=0),
#         A.RandomCrop(height=320, width=320, always_apply=True),
        A.IAAAdditiveGaussianNoise(p=0.2),
        A.IAAPerspective(p=0.5),
        A.OneOf(
            [
                A.CLAHE(p=1),
                A.RandomBrightness(p=1),
                A.RandomGamma(p=1),
            ],
            p=0.9,
        ),
        A.OneOf(
            [
                A.IAASharpen(p=1),
                A.Blur(blur_limit=3, p=1),
                A.MotionBlur(blur_limit=3, p=1),
            ],
            p=0.9,
        ),
        A.OneOf(
            [
                A.RandomContrast(p=1),
                A.HueSaturationValue(p=1),
            ],
            p=0.9,
        ),
        A.Lambda(mask=round_clip_0_1)
    ]
    return A.Compose(train_transform)


def get_validation_augmentation():
    test_transform = []
    return A.Compose(test_transform)

def get_preprocessing(preprocessing_fn):
    _transform = [
        A.Lambda(image=preprocessing_fn),
    ]
    return A.Compose(_transform)

    
if __name__ == "__main__":
    with open('/kaggle/input/airbus-loads/train_imgs.pkl', 'rb') as f:
        train_imgs = pickle.load(f)
    with open('/kaggle/input/airbus-loads/val_imgs.pkl', 'rb') as f:
        val_imgs = pickle.load(f)
        
    df = pd.read_csv("../input/airbus-ship-detection/train_ship_segmentations_v2.csv", index_col=0).dropna()
    BACKBONE = 'efficientnetb3'
    preprocess_input = sm.get_preprocessing(BACKBONE)
    trainAirbusShipDataset = AirbusShipDataset("train_v2",
                                           train_imgs[:], 
                                           f"{ROOT_DIR}/train_ship_segmentations_v2.csv", 
                                           transform = None,
                                           train=True,
                                           preprocessing_fn=get_preprocessing(preprocess_input),
                                           augmentation=get_training_augmentation()
                                          )
    validAirbusShipDataset = AirbusShipDataset("train_v2",
                                           val_imgs[:], 
                                           f"{ROOT_DIR}/train_ship_segmentations_v2.csv", 
                                           transform = None,
                                           train=True,
                                           preprocessing_fn=get_preprocessing(preprocess_input),
                                           augmentation=get_validation_augmentation(),
                                          )
    BATCH_SIZE = 128
    LR = 3e-4
    EPOCHS = 4
    n_classes = 1
    activation = 'sigmoid'
    model = sm.Unet(BACKBONE, classes=n_classes, activation=activation, encoder_weights='imagenet', encoder_freeze=True)
    
    optim = keras.optimizers.Adam(LR)
    total_loss = sm.losses.DiceLoss()
    metrics = [sm.metrics.IOUScore(threshold=0.5), sm.losses.DiceLoss(), sm.losses.BinaryFocalLoss(1.)]

    model.compile(optim, total_loss, metrics)
    
    train_dataset = trainAirbusShipDataset
    valid_dataset = validAirbusShipDataset

    train_dataloader = Dataloder(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
    valid_dataloader = Dataloder(valid_dataset, batch_size=BATCH_SIZE, shuffle=False)

    callbacks = [
        keras.callbacks.ModelCheckpoint('./best_model.h5', save_weights_only=True, save_best_only=True, mode='min'),
        keras.callbacks.ReduceLROnPlateau(),
        tf.keras.callbacks.TensorBoard(update_freq="batch")
    ]
    
    history = model.fit(
        train_dataloader, 
        steps_per_epoch=len(train_dataloader), 
        epochs=EPOCHS, 
        callbacks=callbacks, 
        validation_data=valid_dataloader, 
        validation_steps=len(valid_dataloader),
    )
    
    model.save_weights(f'{EPOCHS}_{total}.h5')