{"cells":[{"metadata":{},"cell_type":"markdown","source":"**Description**\n\nStarter Keras code: simple end-to-end solution to start with.\n\n\nTraining data: pre-processed Dataset - https://www.kaggle.com/iafoss/256x256-images/.\n\nModel: UNET network from segmentation_models with ResNet-34 backbone.\n\nWorks fine with public and private dataset by usage of CPU instead GPU."},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nimport cv2\nimport tifffile\nimport matplotlib.pyplot as plt\nimport gc\n\n%env SM_FRAMEWORK=tf.keras\nimport keras\nfrom keras.losses import binary_crossentropy\nfrom keras.callbacks import EarlyStopping, ReduceLROnPlateau\nfrom keras.optimizers import Adam\n\nfrom tqdm.notebook import tqdm","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"BASE_PATH = \"../input/hubmap-kidney-segmentation/\"\nTRAIN_PATH = os.path.join(BASE_PATH, \"train\")\n\nsubmission_df = pd.read_csv(os.path.join(BASE_PATH, 'sample_submission.csv'))\nsubmission_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#functions to convert encoding to mask and mask to encoding\n# taken from @iafoss notebook\ndef enc2mask(encs, shape):\n    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)\n    for m,enc in enumerate(encs):\n        if isinstance(enc,np.float) and np.isnan(enc): continue\n        s = enc.split()\n        for i in range(len(s)//2):\n            start = int(s[2*i]) - 1\n            length = int(s[2*i+1])\n            img[start:start+length] = 1 + m\n    return img.reshape(shape).T\n\n#https://www.kaggle.com/bguberfain/memory-aware-rle-encoding\n#with bug fix from @iafoss\ndef rle_encode_less_memory(pixels):\n    #watch out for the bug\n    #pixels = pixels.T.flatten()\n    \n    # This simplified method requires first and last pixel to be zero\n    pixels[0] = 0\n    pixels[-1] = 0\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 2\n    runs[1::2] -= runs[::2]\n    \n    return ' '.join(str(x) for x in runs)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Model**"},{"metadata":{"trusted":true},"cell_type":"code","source":"BACKBONE = 'resnet34'\nunet_in_shape = (256, 256, 3)\nmask_shape = (256, 256)\nBATCHSIZE = 32\n\ntrain_model = False","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"if train_model:\n    !pip install segmentation-models --quiet\n    import segmentation_models as sm\n\n    model = sm.Unet(\n        BACKBONE, \n        encoder_weights='imagenet',\n        classes=1,\n        input_shape=unet_in_shape,\n        activation='sigmoid',\n        encoder_freeze=False\n    )\n\n    model.compile(optimizer=Adam(lr=0.0001), loss=binary_crossentropy)\n\n    rlrop = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=3, verbose=1, mode='min', min_delta=0.0001)\n    es = EarlyStopping(monitor='val_loss', min_delta=0.0001, patience=5, verbose=1, mode='min', restore_best_weights=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Datagenerator**"},{"metadata":{"trusted":true},"cell_type":"code","source":"class DataGenerator_Train_256(keras.utils.Sequence):\n    'Generates data for Keras'\n    def __init__(self, names, base_path='../input/train_images',\n                 dim_in=(256,256), batch_size=32, n_channels=3, random_state=12, shuffle=True):\n        self.names = names\n        self.dim_in = dim_in\n        self.batch_size = batch_size\n        self.base_path  = base_path\n        self.n_channels = n_channels\n        self.shuffle = shuffle\n        self.random_state = random_state\n        self.on_epoch_end()\n        np.random.seed(self.random_state)\n        \n    def __len__(self):\n        'Denotes the number of batches per epoch'\n        return int(np.floor(len(self.names) / self.batch_size))\n        \n    def __getitem__(self, index):\n        'Generate one batch of data'\n        # Generate indexes of the batch\n        names = self.names[index*self.batch_size:(index+1)*self.batch_size]\n        # Generate data\n        X, y = self.__data_generation(names)\n        return X, y\n\n    def on_epoch_end(self):\n        'Updates indexes after each epoch'\n        if self.shuffle == True:\n            np.random.seed(self.random_state)\n            np.random.shuffle(self.names)\n            \n    def __data_generation(self, names):\n        'Generates data containing batch_size samples'\n        # Initialization\n        X = np.empty((self.batch_size, *self.dim_in, self.n_channels), dtype=np.uint8)\n        y = np.empty((self.batch_size, *self.dim_in, 1), dtype=np.uint8)\n\n        # Generate data\n        for i, name in enumerate(names):\n            X[i,] = cv2.imread(os.path.join(self.base_path, 'train', name))\n            y[i,:,:,0] = cv2.imread(os.path.join(self.base_path, 'masks', name))[:,:,0]\n        \n        # im pre-proc\n        X = X.astype(np.float32)/255\n        \n        return X, y","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\nnames = os.listdir('../input/hubmap-256-original/train/')\nprint(len(names), names[0])\n\ntrain_names, val_names = train_test_split(\n        names, random_state=42, test_size=0.2)\n\nfor name in names:\n    image = cv2.imread(os.path.join('../input/hubmap-256-original/train/', name))\n    mask = cv2.imread(os.path.join('../input/hubmap-256-original/masks/', name))\n    if np.sum(mask):\n        break\n\nplt.figure()\nplt.subplot(1,2,1)\nplt.imshow(image)\nplt.subplot(1,2,2)\nplt.imshow(mask*255)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Training**"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_generator = DataGenerator_Train_256(\n    train_names,\n    base_path = '../input/hubmap-256-original/',\n    batch_size=BATCHSIZE,\n    dim_in = unet_in_shape[:2],\n    shuffle=True,\n)\n    \nval_generator = DataGenerator_Train_256(\n    val_names,\n    base_path = '../input/hubmap-256-original/',\n    batch_size=BATCHSIZE,\n    dim_in = unet_in_shape[:2],\n    shuffle=True,\n)\n\n\nif train_model:\n    history = model.fit_generator(\n        train_generator,\n        validation_data=val_generator,\n        callbacks=[rlrop, es],\n        epochs=20)\n\n    history_df = pd.DataFrame(history.history)\n    history_df[['loss', 'val_loss']].plot()\n\n    model.save('hubmap_modelresnet34_2211.h5')\nelse:\n    from keras.models import load_model\n    model = load_model('../input/hubmap-nets/hubmap_modelresnet34_2211.h5')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def predict_on_batch(test_in_image, test_in_id, mask_out, size):\n    pred_mask = model.predict(np.array(test_in_image).astype(np.float32)/255)   \n    for mask_id in range(len(test_in_image)):\n        tiid = test_in_id[mask_id]\n        yid = int(tiid%(mask_out.shape[1]/size))\n        xid = int(tiid//(mask_out.shape[1]/size))\n        mask_out[xid*size:(xid+1)*size, yid*size:(yid+1)*size] = pred_mask[mask_id,:,:,0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Prediction**"},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\n# image pre-processing taken from https://www.kaggle.com/iafoss/256x256-images\nsz = 256   #the size of tiles\nreduce = 4 #reduce the original images by 4 times \ns_th = 40  #saturation blancking threshold\np_th = 200*sz//256 #threshold for the minimum number of pixels\n\nPREDBATCHSIZE = 128\n\nfor index, row in tqdm(submission_df.iterrows(),total=len(submission_df)):\n    test_in_image, test_in_id = [], []\n    #read image and generate the mask\n    img = tifffile.imread(os.path.join(BASE_PATH, 'test', row.id +'.tiff'))\n    if len(img.shape) == 5:img = np.transpose(img.squeeze(), (1,2,0))\n\n    # INPUT IMAGES PRE-PROCESSING\n    #add padding to make the image dividable into tiles\n    shape = img.shape\n    pad0 = (reduce*sz - shape[0]%(reduce*sz))%(reduce*sz)\n    pad1 = (reduce*sz - shape[1]%(reduce*sz))%(reduce*sz)\n    img = np.pad(img,[[pad0//2,pad0-pad0//2],[pad1//2,pad1-pad1//2],[0,0]],\n                constant_values=0)\n    #split image and mask into tiles using the reshape+transpose trick\n    img = cv2.resize(img,(img.shape[1]//reduce,img.shape[0]//reduce),\n                         interpolation = cv2.INTER_AREA)\n    \n    # create array for mask gathering\n    mask_out = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)\n    \n    img = img.reshape(img.shape[0]//sz,sz,img.shape[1]//sz,sz,3)\n    img = img.transpose(0,2,1,3,4).reshape(-1,sz,sz,3)\n\n    for i, im in enumerate(img):\n        #remove black or gray images based on saturation check\n        hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)\n        h, s, v = cv2.split(hsv)\n        if (s>s_th).sum() <= p_th or im.sum() <= p_th: continue\n        \n        test_in_image.append(im)\n        test_in_id.append(i)\n        #predict for test batch\n        if len(test_in_image) == PREDBATCHSIZE:\n            predict_on_batch(test_in_image, test_in_id, mask_out, sz)\n            test_in_image, test_in_id = [], []\n    # predict for tail\n    if len(test_in_image) > 0:\n        predict_on_batch(test_in_image, test_in_id, mask_out, sz)\n    \n    del img, test_in_image, test_in_id\n\n    # zoom out and crop padding \n    mask_out = cv2.resize(mask_out, (mask_out.shape[1]*reduce, mask_out.shape[0]*reduce),\n                          interpolation = cv2.INTER_LINEAR)\n    mask_out = mask_out[pad0//2:-(pad0-pad0//2), pad1//2:-(pad1-pad1//2)]\n    \n    # round\n    mask_out = (mask_out > 0.5).astype(np.int8)\n    mask_out = mask_out.T.flatten()\n    \n    # encode mask\n    enc_mask = rle_encode_less_memory(mask_out)\n    print(np.sum(mask_out), len(enc_mask))\n    submission_df.loc[index, 'predicted'] = enc_mask\n    del enc_mask, mask_out\n    gc.collect()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"filename = 'submission.csv'\nsubmission_df.to_csv(filename,index=False)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}