{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# HuBMAP-Intro to Segmentation using Tensorflow: My approach\n* Hi everyone, this is my first segmentation based problem and it has been an excellent learning oppportunity so far. I referred some public kernels and discussions that helped me to get started with this competition.\n* If you guys like this approach then do consider upvoting this notebook and feel free to leave a comment if you have any better ideas. ","metadata":{}},{"cell_type":"markdown","source":"# Installing Prerequisites","metadata":{}},{"cell_type":"code","source":"!pip install -U segmentation-models","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-08-13T08:36:19.721929Z","iopub.execute_input":"2022-08-13T08:36:19.725201Z","iopub.status.idle":"2022-08-13T08:36:29.506997Z","shell.execute_reply.started":"2022-08-13T08:36:19.725159Z","shell.execute_reply":"2022-08-13T08:36:29.505822Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Imports","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport math\nfrom typing import Union, List, Tuple, Dict\nimport cv2\nimport random\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom PIL import Image, ImageOps\nplt.style.use('Solarize_Light2')\nimport os\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'\nimport gc\ngc.enable()\nfrom tqdm.auto import tqdm\nfrom sklearn.model_selection import train_test_split\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras.utils import Sequence","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-08-13T08:36:29.509440Z","iopub.execute_input":"2022-08-13T08:36:29.509850Z","iopub.status.idle":"2022-08-13T08:36:36.132757Z","shell.execute_reply.started":"2022-08-13T08:36:29.509809Z","shell.execute_reply":"2022-08-13T08:36:36.131638Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cfg = {\n    'train_df': '../input/hubmap-organ-segmentation/train.csv',\n    'train_img': '../input/hubmap-organ-segmentation/train_images/',\n    'train_annot': '../input/hubmap-organ-segmentation/train_annotations/',\n    'slices': 4, #Batch size = slices ** 2\n    'epochs': 3,\n    'img_shape': 2304,\n    'lr': 1e-03\n}\n\nunet_cfg = {\n    'backbone_name': 'resnet50',\n    'input_shape': (int(cfg['img_shape'] / cfg['slices']), int(cfg['img_shape'] / cfg['slices']), 3), #the H and W of the image should be divisible by 32\n    'classes': 1,\n    'activation': 'sigmoid',\n    'weights': None, #optional path to weigths file\n    'encoder_weights': 'imagenet',#'imagenet'\n    'encoder_freeze': True,\n    'encoder_features': 'default',\n    'decoder_block_type': 'upsampling',\n    'decoder_filters': (256, 128, 64, 32, 16),\n    'decoder_use_batchnorm': True\n}","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:17.589157Z","iopub.execute_input":"2022-08-13T08:37:17.589849Z","iopub.status.idle":"2022-08-13T08:37:17.597398Z","shell.execute_reply.started":"2022-08-13T08:37:17.589810Z","shell.execute_reply":"2022-08-13T08:37:17.596211Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.read_csv(cfg['train_df'])\ndf['paths'] = [f'../input/hubmap-organ-segmentation/train_images/{str(i)}.tiff' for i in tqdm(df['id'])]\ndf.shape","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:20.407717Z","iopub.execute_input":"2022-08-13T08:37:20.408233Z","iopub.status.idle":"2022-08-13T08:37:20.859569Z","shell.execute_reply.started":"2022-08-13T08:37:20.408172Z","shell.execute_reply":"2022-08-13T08:37:20.858609Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(df.img_height.unique())\nprint(df.img_width.unique())\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:21.415949Z","iopub.execute_input":"2022-08-13T08:37:21.416419Z","iopub.status.idle":"2022-08-13T08:37:21.450688Z","shell.execute_reply.started":"2022-08-13T08:37:21.416374Z","shell.execute_reply":"2022-08-13T08:37:21.449563Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df, valid_df = train_test_split(df, test_size=0.2, shuffle=True, random_state=20)\ntrain_df.reset_index(drop=True, inplace=True)\nvalid_df.reset_index(drop=True, inplace=True)\ntrain_df.head()\n#del df; gc.collect()","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:22.208898Z","iopub.execute_input":"2022-08-13T08:37:22.209351Z","iopub.status.idle":"2022-08-13T08:37:22.244032Z","shell.execute_reply.started":"2022-08-13T08:37:22.209312Z","shell.execute_reply":"2022-08-13T08:37:22.242886Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Helper Functions:\n#### These are some functions that will be used further in this notebook.","metadata":{}},{"cell_type":"code","source":"def mask2rle(img):\n    '''\n    img: numpy array, 1 - mask, 0 - background\n    Returns run length as string formated\n    '''\n    pixels= img.T.flatten()\n    pixels = np.concatenate([[0], pixels, [0]])\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n    runs[1::2] -= runs[::2]\n    return ' '.join(str(x) for x in runs)\n\ndef rle2mask(mask_rle, shape=(1600,256)):\n    '''\n    mask_rle: run-length as string formated (start length)\n    shape: (width,height) of array to return \n    Returns numpy array, 1 - mask, 0 - background\n\n    '''\n    s = mask_rle.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)\n    for lo, hi in zip(starts, ends):\n        img[lo:hi] = 1\n    return img.reshape(shape).T\n\n\ndef generate_tile(img, mask, slices=4, display=True):\n    '''\n    Generates tiles of images and its corresponding mask\n    Returns: Two lists, image tiles and mask tiles of length slices**2 \n    '''\n    if img.shape[0] == mask.shape[0] and img.shape[1] == mask.shape[1]:\n        factor = int(img.shape[0] / slices)\n        #print(f'Size of each frame/tile: {factor, factor}')\n        img_tiles = []\n        mask_tiles = []\n        temp_img = None\n        temp_tile = None\n        for x in range(slices):\n            for y in range(slices):\n                temp_img = img[factor*x : factor*(x+1), factor*y: factor*(y+1), ...]\n                img_tiles.append(temp_img)\n                temp_mask = mask[factor*x:factor*(x+1), factor*y: factor*(y+1)]\n                mask_tiles.append(temp_mask)\n                del temp_img; del temp_mask; gc.collect()\n                \n        if display:\n            fig, ax = plt.subplots(nrows=slices, ncols=slices, figsize=(10,10))\n            for i in range(slices):\n                for j in range(slices):\n                    ax[i, j].set_axis_off()\n                    ax[i, j].imshow(img_tiles[slices*i+j])\n                    ax[i, j].imshow(mask_tiles[slices*i+j], cmap='coolwarm', alpha=0.5)\n            fig.tight_layout()\n        return img_tiles, mask_tiles\n    \n    else:\n        print('Shapes do not match.')\n        return None\n\n    \n    \ndef img_transpose(idx: int):\n    '''\n    Helps visualizing image and its corresponding transpose image\n    '''\n    idx = os.listdir(cfg['train_img'])[idx]\n    img = Image.open(f'../input/hubmap-organ-segmentation/train_images/{idx}')\n    img = np.array(img)\n\n    R = img[:, :, 0]\n    G = img[:, :, 1]\n    B = img[:, :, 2]\n\n    R, G, B = R.T, G.T, B.T\n    R, G, B = R.reshape(3000, 3000,1), G.reshape(3000, 3000,1), B.reshape(3000, 3000,1)\n    new_img = np.dstack((R, G, B))\n\n    fig = plt.figure(figsize=(10, 7))\n    rows = 1\n    columns = 2\n\n    fig.add_subplot(rows, columns, 1)\n    plt.imshow(img)\n    plt.axis('off')\n    plt.title(\"Original\")\n\n    fig.add_subplot(rows, columns, 2)\n    plt.imshow(new_img)\n    plt.axis('off')\n    plt.title(\"Transposed\")\n    \n\n    \ndef display_img(idx: int):\n    '''\n    Displays image along with its mask\n    '''\n    idx = os.listdir(cfg['train_img'])[idx]\n    sample = idx[:-5]\n    row = train_df.loc[train_df['id'] == int(sample)]\n    h, w = row['img_height'].iloc[-1], row['img_width'].iloc[-1]\n    img = Image.open(row['paths'].iloc[-1])\n    img = np.array(img)\n    sample = row['rle'].iloc[-1]\n    mask = rle2mask(sample, shape=(int(h), int(w)))\n    print(f'mask shape: {mask.shape}, image shape: {img.shape}')\n    plt.figure(figsize=(8,8))\n    plt.axis('off')\n    plt.imshow(img)\n    plt.imshow(mask, cmap='coolwarm', alpha=0.5)\n    return img, mask\n\n\ndef tiles2img(img_tiles: list, mask_tiles: list, display = True):\n    '''\n    Converts tiles of images and masks to one array\n    '''\n    slices = int(math.sqrt(len(img_tiles)))\n    img = []\n    mask = []\n    temp_img = None\n    temp_mask = None\n    for i in tqdm(range(slices)):\n        temp_img = img_tiles[slices*i: slices*(i+1)]\n        temp_img = np.hstack([i for i in temp_img])\n        temp_mask = mask_tiles[slices*i: slices*(i+1)]\n        temp_mask = np.hstack([i for i in temp_mask])\n        img.append(temp_img)\n        mask.append(temp_mask)\n        del temp_img, temp_mask; gc.collect()\n    img = np.vstack([i for i in img])\n    mask = np.vstack([i for i in mask])\n    if display:\n        plt.imshow(img)\n        plt.imshow(mask, cmap='coolwarm', alpha=0.5)\n        plt.axis('off')\n        print(img.shape)\n        print(mask.shape)\n    return img, mask","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:23.775214Z","iopub.execute_input":"2022-08-13T08:37:23.775718Z","iopub.status.idle":"2022-08-13T08:37:23.823755Z","shell.execute_reply.started":"2022-08-13T08:37:23.775674Z","shell.execute_reply":"2022-08-13T08:37:23.822498Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Exploratory Data Analysis","metadata":{}},{"cell_type":"code","source":"df.describe()","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:24.623517Z","iopub.execute_input":"2022-08-13T08:37:24.624002Z","iopub.status.idle":"2022-08-13T08:37:24.672944Z","shell.execute_reply.started":"2022-08-13T08:37:24.623958Z","shell.execute_reply":"2022-08-13T08:37:24.671897Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.info()","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:24.890139Z","iopub.execute_input":"2022-08-13T08:37:24.890722Z","iopub.status.idle":"2022-08-13T08:37:24.923512Z","shell.execute_reply.started":"2022-08-13T08:37:24.890678Z","shell.execute_reply":"2022-08-13T08:37:24.922311Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"H = df.img_height.value_counts().to_dict()\nW = df.img_width.value_counts().to_dict()\nprint(H)\nprint(W)","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:25.149966Z","iopub.execute_input":"2022-08-13T08:37:25.150530Z","iopub.status.idle":"2022-08-13T08:37:25.163900Z","shell.execute_reply.started":"2022-08-13T08:37:25.150445Z","shell.execute_reply":"2022-08-13T08:37:25.162485Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img, mask = display_img(65)","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:25.624187Z","iopub.execute_input":"2022-08-13T08:37:25.624715Z","iopub.status.idle":"2022-08-13T08:37:29.058732Z","shell.execute_reply.started":"2022-08-13T08:37:25.624670Z","shell.execute_reply":"2022-08-13T08:37:29.057803Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_tiles, mask_tiles = generate_tile(img, mask)","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:29.062638Z","iopub.execute_input":"2022-08-13T08:37:29.063386Z","iopub.status.idle":"2022-08-13T08:37:34.301510Z","shell.execute_reply.started":"2022-08-13T08:37:29.063345Z","shell.execute_reply":"2022-08-13T08:37:34.300525Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img, mask = tiles2img(img_tiles, mask_tiles)","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:34.303298Z","iopub.execute_input":"2022-08-13T08:37:34.303665Z","iopub.status.idle":"2022-08-13T08:37:36.998687Z","shell.execute_reply.started":"2022-08-13T08:37:34.303632Z","shell.execute_reply":"2022-08-13T08:37:36.997521Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"vals = df.organ.value_counts().to_dict()\nplt.figure(figsize=(20,5))\nax = sns.barplot(x = list(vals.keys()), y = [i for i in vals.values()])\nax.bar_label(ax.containers[0], label_type='center')\nplt.title(\"Images per organ\")\nplt.ylabel(\"Number of images\")\nplt.xlabel(\"Organs\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:37.001126Z","iopub.execute_input":"2022-08-13T08:37:37.001623Z","iopub.status.idle":"2022-08-13T08:37:37.256449Z","shell.execute_reply.started":"2022-08-13T08:37:37.001581Z","shell.execute_reply":"2022-08-13T08:37:37.255450Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Tensorflow Data Pipeline","metadata":{}},{"cell_type":"code","source":"class DataSetGen(Sequence):\n    def __init__(self, df, img_dir=cfg['train_img'], img_shape=cfg['img_shape'], slices=cfg['slices'], shuffle=True):\n        self.df = df\n        self.img_dir = img_dir\n        self.img_shape = img_shape\n        self.slices = slices\n        self.shuffle = shuffle\n        if self.shuffle:\n            self.on_epoch_end()\n        \n    def on_epoch_end(self):\n        '''\n        Called after the end of every epoch, here it shuffles the dataframe\n        '''\n        gc.collect()\n        self.df = self.df.sample(frac=1)\n        self.df.reset_index(inplace=True)\n        return self.df.drop('index', inplace=True, axis=1)\n    \n    def __len__(self):\n        '''\n        Denotes the number of batches per epoch\n        '''\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        '''\n        Generates one batch of data\n        '''\n        row = self.df.loc[idx]\n        img_id = str(row['id']) + '.tiff'\n        img_path = os.path.join(self.img_dir, img_id)\n        rle = row['rle']\n        del row; del img_id; gc.collect()\n        return self.__get_data(img_path, rle)\n    \n    def __get_data(self, img_path, rle):\n        '''\n        Reads image and masks, generates tiles\n        '''\n        img_batch = None\n        masks_batch = None\n        img = Image.open(img_path)\n        img = np.array(img)\n        mask = rle2mask(rle, (img.shape[0], img.shape[1]))\n        img = cv2.resize(img, (self.img_shape, self.img_shape))\n        img = img / 255.0\n        mask = cv2.resize(mask, (self.img_shape, self.img_shape)).reshape((self.img_shape, self.img_shape, 1))\n        img_batch, masks_batch = generate_tile(img, mask, self.slices, display=False)\n        del mask; del img; gc.collect()\n        return np.asarray(img_batch).astype(np.float32), np.asarray(masks_batch).astype(np.float32)","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:37.257938Z","iopub.execute_input":"2022-08-13T08:37:37.259125Z","iopub.status.idle":"2022-08-13T08:37:37.272562Z","shell.execute_reply.started":"2022-08-13T08:37:37.259056Z","shell.execute_reply":"2022-08-13T08:37:37.271499Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_ds = DataSetGen(train_df)\nvalid_ds = DataSetGen(valid_df)","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:37.275599Z","iopub.execute_input":"2022-08-13T08:37:37.276197Z","iopub.status.idle":"2022-08-13T08:37:37.612224Z","shell.execute_reply.started":"2022-08-13T08:37:37.276160Z","shell.execute_reply":"2022-08-13T08:37:37.610959Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training Model","metadata":{}},{"cell_type":"code","source":"import segmentation_models as sm\nsm.set_framework('tf.keras')\nsm.framework()\nfrom segmentation_models import Unet\nfrom segmentation_models.utils import set_trainable\nunet = Unet(**unet_cfg)\nmodel = tf.keras.Sequential([\n    tf.keras.layers.experimental.preprocessing.RandomContrast(0.1),\n    unet\n])\nmodel.build(input_shape=(None, unet_cfg['input_shape'][0], unet_cfg['input_shape'][1], unet_cfg['input_shape'][2]))\nmodel.summary()","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:48.333084Z","iopub.execute_input":"2022-08-13T08:37:48.333511Z","iopub.status.idle":"2022-08-13T08:37:57.370664Z","shell.execute_reply.started":"2022-08-13T08:37:48.333450Z","shell.execute_reply":"2022-08-13T08:37:57.369535Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Metrics and loss functions","metadata":{}},{"cell_type":"code","source":"from keras import backend as K\nfrom keras.losses import binary_crossentropy\nimport tensorflow as tf\n\ndef dice_coef(y_true, y_pred, smooth=1):\n    y_true_f = K.flatten(y_true)\n    y_pred_f = K.flatten(y_pred)\n    intersection = K.sum(y_true_f * y_pred_f)\n    return (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)\n\ndef iou_coef(y_true, y_pred, smooth=1):\n    intersection = K.sum(K.abs(y_true * y_pred), axis=[1,2,3])\n    union = K.sum(y_true,[1,2,3])+K.sum(y_pred,[1,2,3])-intersection\n    iou = K.mean((intersection + smooth) / (union + smooth), axis=0)\n    return iou\n\ndef dice_loss(y_true, y_pred):\n    smooth = 1.\n    y_true_f = K.flatten(y_true)\n    y_pred_f = K.flatten(y_pred)\n    intersection = y_true_f * y_pred_f\n    score = (2. * K.sum(intersection) + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)\n    return 1. - score\n\ndef bce_dice_loss(y_true, y_pred):\n    return binary_crossentropy(tf.cast(y_true, tf.float32), y_pred) + 0.5 * dice_loss(tf.cast(y_true, tf.float32), y_pred)","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:57.372925Z","iopub.execute_input":"2022-08-13T08:37:57.373339Z","iopub.status.idle":"2022-08-13T08:37:57.384018Z","shell.execute_reply.started":"2022-08-13T08:37:57.373299Z","shell.execute_reply":"2022-08-13T08:37:57.382907Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model compilation and training","metadata":{}},{"cell_type":"code","source":"model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.01), loss=bce_dice_loss, metrics = [dice_coef,iou_coef])\n\ncheckpoint = tf.keras.callbacks.ModelCheckpoint(\n    filepath='/kaggle/working/best_model.h5',\n    monitor='val_dice_coef',\n    verbose=0,\n    save_best_only=True,\n    mode='min'\n)\n\nreduce_lr = tf.keras.callbacks.ReduceLROnPlateau(\n    monitor='val_dice_coef',\n    factor=0.25,\n    patience=3,\n    verbose=0,\n    mode='min'\n)\n\nes = tf.keras.callbacks.EarlyStopping(\n    patience=3,\n    min_delta=0,\n    monitor='val_dice_coef',\n    restore_best_weights=True,\n    verbose=0,\n    mode='min',\n    baseline=None\n)\n\nhistory = model.fit(train_ds, \n                    validation_data=valid_ds,\n                    workers=4,\n                    epochs = cfg['epochs'],\n                    callbacks=[es,reduce_lr,checkpoint],\n                    verbose=1\n                   )","metadata":{"execution":{"iopub.status.busy":"2022-08-13T08:37:57.385430Z","iopub.execute_input":"2022-08-13T08:37:57.386773Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history.history.keys()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.plot(history.history['loss'])\nplt.plot(history.history['val_loss'])\nplt.title('model loss')\nplt.ylabel('loss')\nplt.xlabel('epoch')\nplt.legend(['train', 'test'], loc='upper left')\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.plot(history.history['dice_coef'])\nplt.plot(history.history['val_dice_coef'])\nplt.title('model dice coef')\nplt.ylabel('loss')\nplt.xlabel('epoch')\nplt.legend(['train', 'test'], loc='upper left')\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.plot(history.history['iou_coef'])\nplt.plot(history.history['val_iou_coef'])\nplt.title('iou coef')\nplt.ylabel('loss')\nplt.xlabel('epoch')\nplt.legend(['train', 'test'], loc='upper left')\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.plot(history.history['lr'])\nplt.title('model lr')\nplt.ylabel('lr')\nplt.xlabel('epoch')\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# References:\n1) https://www.kaggle.com/competitions/hubmap-organ-segmentation/discussion/332838: For understanding rle, rle to mask conversion and vice-versa <br />\n2) https://www.youtube.com/watch?v=AZr64OxshLo: For understanding image segmentation loss functions IoU, Dice coefficient  <br />\n3) Jirka Borovec's notebook for using tiled images: https://www.kaggle.com/code/jirkaborovec/ftus-segm-baseline-flash-unet-tiled-aug-images <br />\n4) Code Breaker's Notebook for Metrics and loss function: https://www.kaggle.com/code/muki2003/hubmap-hpa-deep-learning-unet-cnn","metadata":{}}]}