{"metadata":{"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":61446,"databundleVersionId":6962461,"sourceType":"competition"},{"sourceId":6191,"sourceType":"modelInstanceVersion","modelInstanceId":4655}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.12"},"papermill":{"default_parameters":{},"duration":3846.080383,"end_time":"2024-01-14T04:20:19.064569","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2024-01-14T03:16:12.984186","version":"2.4.0"},"widgets":{"application/vnd.jupyter.widget-state+json":{"state":{"08983a9c6aff42578980f4f7113c3ee2":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_4411aefc021d46d0ada7b645eb53ec48","placeholder":"​","style":"IPY_MODEL_09a10a8cf9334c51857397ed50398c8e","value":"Searching best thr : 100%"}},"09a10a8cf9334c51857397ed50398c8e":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"1f3989a0c01248328e16875075e9d1c4":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_08983a9c6aff42578980f4f7113c3ee2","IPY_MODEL_22cfcc0a7cc6455fbf3bb7c788c8a4e1","IPY_MODEL_c8392e8075224e3b8a020a16c1a08447"],"layout":"IPY_MODEL_6cec9a2c2fac450d87248aed8dd62f86"}},"22cfcc0a7cc6455fbf3bb7c788c8a4e1":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_dffe80502d954bdea0bbb6353dbf5515","max":20,"min":0,"orientation":"horizontal","style":"IPY_MODEL_7ce1b34a4f864a42a6619eec82311eb0","value":20}},"4411aefc021d46d0ada7b645eb53ec48":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"6cec9a2c2fac450d87248aed8dd62f86":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"7ce1b34a4f864a42a6619eec82311eb0":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"83fe40a0b8f047cc8602206909d42361":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"9384babdb7054d55aecdf3e989ddc926":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"c8392e8075224e3b8a020a16c1a08447":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_83fe40a0b8f047cc8602206909d42361","placeholder":"​","style":"IPY_MODEL_9384babdb7054d55aecdf3e989ddc926","value":" 20/20 [04:34&lt;00:00, 12.66s/it]"}},"dffe80502d954bdea0bbb6353dbf5515":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}}},"version_major":2,"version_minor":0}}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<center><img src=\"https://keras.io/img/logo-small.png\" alt=\"Keras logo\" width=\"100\"><br/>\nThis starter notebook is provided by the Keras team.</center>","metadata":{"execution":{"iopub.execute_input":"2024-01-10T05:24:31.308329Z","iopub.status.busy":"2024-01-10T05:24:31.307595Z","iopub.status.idle":"2024-01-10T05:24:31.313088Z","shell.execute_reply":"2024-01-10T05:24:31.312113Z","shell.execute_reply.started":"2024-01-10T05:24:31.308287Z"},"papermill":{"duration":0.011755,"end_time":"2024-01-14T03:16:16.447481","exception":false,"start_time":"2024-01-14T03:16:16.435726","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# SenNet + HOA - Exploring 3D Human Vasculature Segmentation with [KerasCV](https://github.com/keras-team/keras-cv) and [Keras](https://github.com/keras-team/keras)\n\n> The objective of this competition is to accurately segment blood vessels in the human kidney.\n\nThis notebook guides you through the process of training a **Convolutional Neural Network (CNN)** model, specifically DeepLabV3+, using KerasCV on the competition dataset. The focus here is on training with **2.5D** images, which are discussed further in the following sections. You can find the **inference notebook** [here](https://www.kaggle.com/code/awsaf49/sennet-hoa-kerascv-starter-notebook-infer).\n\nFun fact: This notebook is backend-agnostic, supporting TensorFlow, PyTorch, and JAX. Utilizing KerasCV and Keras allows us to choose our preferred backend. Explore more details on [Keras](https://keras.io/keras_core/announcement/).\n\nIn this notebook, you will learn:\n\n* How to create **2.5D** images\n* Loading the data efficiently using [`tf.data`](https://www.tensorflow.org/guide/data).\n* Creating the model using KerasCV presets and adapting it for our specific task.\n* Training the model.\n* Determining the threshold parameter for optimal results.\n\n**Note**: For a more in-depth understanding of KerasCV, refer to the [KerasCV guides](https://keras.io/guides/keras_cv/).","metadata":{}},{"cell_type":"markdown","source":"# 🛠 | Install Libraries ","metadata":{"papermill":{"duration":0.011416,"end_time":"2024-01-14T03:16:16.470167","exception":false,"start_time":"2024-01-14T03:16:16.458751","status":"completed"},"tags":[]}},{"cell_type":"code","source":"!pip install -q --upgrade keras-cv tensorflow\n!pip install -q --upgrade keras\n!pip install -q --upgrade tensorflow-io","metadata":{"_kg_hide-output":true,"papermill":{"duration":93.005588,"end_time":"2024-01-14T03:17:49.486824","exception":false,"start_time":"2024-01-14T03:16:16.481236","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:23:37.025627Z","iopub.execute_input":"2024-01-19T17:23:37.026522Z","iopub.status.idle":"2024-01-19T17:25:18.741786Z","shell.execute_reply.started":"2024-01-19T17:23:37.026489Z","shell.execute_reply":"2024-01-19T17:25:18.740402Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📚 | Import Libraries \nKeras is backend agnostic. This means that you can run keras on [TensorFlow](https://www.tensorflow.org/), [JAX](https://jax.readthedocs.io/en/latest/index.html), [PyTorch](https://pytorch.org/), or [Numpy](https://numpy.org/) (inference only). We will be using JAX as our backend. To switch backends set the `KERAS_BACKEND` varialbe to which backend to want.","metadata":{"papermill":{"duration":0.010878,"end_time":"2024-01-14T03:17:49.510159","exception":false,"start_time":"2024-01-14T03:17:49.499281","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nos.environ[\"KERAS_BACKEND\"] = \"jax\" # you can also use tensorflow or torch\n\nimport keras\nimport keras_cv\nimport tensorflow as tf # only for data\nimport tensorflow_io as tfio # for loading .tif files\n\nimport cv2\nimport pandas as pd\nimport numpy as np\nfrom glob import glob\nfrom tqdm.notebook import tqdm\n\nimport matplotlib.pyplot as plt ","metadata":{"papermill":{"duration":10.671979,"end_time":"2024-01-14T03:18:00.193134","exception":false,"start_time":"2024-01-14T03:17:49.521155","status":"completed"},"tags":[],"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-01-19T17:25:18.744311Z","iopub.execute_input":"2024-01-19T17:25:18.744704Z","iopub.status.idle":"2024-01-19T17:25:30.189413Z","shell.execute_reply.started":"2024-01-19T17:25:18.744669Z","shell.execute_reply":"2024-01-19T17:25:30.188418Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Library Versions","metadata":{"papermill":{"duration":0.010958,"end_time":"2024-01-14T03:18:00.215704","exception":false,"start_time":"2024-01-14T03:18:00.204746","status":"completed"},"tags":[]}},{"cell_type":"code","source":"print(\"TensorFlow:\", tf.__version__)\nprint(\"Keras:\", keras.__version__)\nprint(\"KerasCV:\", keras_cv.__version__)","metadata":{"papermill":{"duration":0.019435,"end_time":"2024-01-14T03:18:00.246368","exception":false,"start_time":"2024-01-14T03:18:00.226933","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:30.190944Z","iopub.execute_input":"2024-01-19T17:25:30.191694Z","iopub.status.idle":"2024-01-19T17:25:30.198212Z","shell.execute_reply.started":"2024-01-19T17:25:30.191641Z","shell.execute_reply":"2024-01-19T17:25:30.196926Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ⚙️ | Configuration","metadata":{"papermill":{"duration":0.010922,"end_time":"2024-01-14T03:18:00.26855","exception":false,"start_time":"2024-01-14T03:18:00.257628","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class CFG:\n    verbose = 1  # Verbosity\n    seed = 42  # Random seed\n    preset = \"deeplab_v3_plus_resnet50_pascalvoc\"  # Name of pretrained models\n    image_size = [384, 384]  # Input image size\n    epochs = 15 # Training epochs\n    batch_size = 12  # Batch size\n    drop_remainder = True  # Drop incomplete batches\n    num_classes = 1 # Number of output classes\n    cache = True # Save data into memory during training","metadata":{"papermill":{"duration":0.018795,"end_time":"2024-01-14T03:18:00.298534","exception":false,"start_time":"2024-01-14T03:18:00.279739","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:30.201268Z","iopub.execute_input":"2024-01-19T17:25:30.201722Z","iopub.status.idle":"2024-01-19T17:25:30.221747Z","shell.execute_reply.started":"2024-01-19T17:25:30.201671Z","shell.execute_reply":"2024-01-19T17:25:30.220599Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ♻️ | Reproducibility \nSets value for random seed to produce similar result in each run.","metadata":{"papermill":{"duration":0.010907,"end_time":"2024-01-14T03:18:00.32063","exception":false,"start_time":"2024-01-14T03:18:00.309723","status":"completed"},"tags":[]}},{"cell_type":"code","source":"keras.utils.set_random_seed(CFG.seed)","metadata":{"papermill":{"duration":0.018371,"end_time":"2024-01-14T03:18:00.350074","exception":false,"start_time":"2024-01-14T03:18:00.331703","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:30.222921Z","iopub.execute_input":"2024-01-19T17:25:30.223276Z","iopub.status.idle":"2024-01-19T17:25:30.234013Z","shell.execute_reply.started":"2024-01-19T17:25:30.223247Z","shell.execute_reply":"2024-01-19T17:25:30.232924Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📁 | Dataset Path ","metadata":{"papermill":{"duration":0.010888,"end_time":"2024-01-14T03:18:00.372053","exception":false,"start_time":"2024-01-14T03:18:00.361165","status":"completed"},"tags":[]}},{"cell_type":"code","source":"BASE_PATH = \"/kaggle/input/blood-vessel-segmentation\"","metadata":{"papermill":{"duration":0.017704,"end_time":"2024-01-14T03:18:00.400852","exception":false,"start_time":"2024-01-14T03:18:00.383148","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:30.235387Z","iopub.execute_input":"2024-01-19T17:25:30.235804Z","iopub.status.idle":"2024-01-19T17:25:30.247072Z","shell.execute_reply.started":"2024-01-19T17:25:30.235766Z","shell.execute_reply":"2024-01-19T17:25:30.246005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📖 | Metadata\n\nWe will only use images with `dense` labels; thus, we will not use those images that only have `sparse` labels.","metadata":{"papermill":{"duration":0.011434,"end_time":"2024-01-14T03:18:00.472401","exception":false,"start_time":"2024-01-14T03:18:00.460967","status":"completed"},"tags":[]}},{"cell_type":"code","source":"mask_paths = sorted(glob(f\"{BASE_PATH}/train/*/labels/*tif\"))\ndf = pd.DataFrame({\"mask_path\":mask_paths})\ndf['dataset'] = df.mask_path.map(lambda x: x.split('/')[-3])\ndf['slice'] = df.mask_path.map(lambda x: x.split('/')[-1].replace(\".tif\",\"\"))\n\ndf = df[~df.dataset.str.contains(\"kidney_3_sparse\")]\ndf['image_path'] = df.mask_path.str.replace(\"label\",\"image\")\ndf['image_path'] = df.image_path.str.replace(\"kidney_3_dense\",\"kidney_3_sparse\")\ndf.head()","metadata":{"papermill":{"duration":0.86264,"end_time":"2024-01-14T03:18:01.346487","exception":false,"start_time":"2024-01-14T03:18:00.483847","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:30.248391Z","iopub.execute_input":"2024-01-19T17:25:30.24874Z","iopub.status.idle":"2024-01-19T17:25:31.087012Z","shell.execute_reply.started":"2024-01-19T17:25:30.248704Z","shell.execute_reply":"2024-01-19T17:25:31.085982Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Updating for 2.5D Data\n\nIn this notebook, we will use `2.5D` images. So what exactly are `2.5D` images?\n\n- `2.5D` is a method that uses the channel dimension to encode temporal information. \n- Specifically, each channel contains a 2D slice from the 3D volume.\n- This allows us to leverage pretrained 2D models, unlike pure 3D approaches. \n- But unlike pure 2D, we gain spatial context across adjacent slices.\n- In our notebook, we will use `3` `CHANNELS` that are spaced `3` slices (`STRIDES`) apart from each other.","metadata":{"papermill":{"duration":0.011802,"end_time":"2024-01-14T03:18:01.370567","exception":false,"start_time":"2024-01-14T03:18:01.358765","status":"completed"},"tags":[]}},{"cell_type":"code","source":"CHANNELS = 3 # take 3 slices to use \"ImageNet\" weights which require 3 channels\nSTRIDE = 3 # gap between each 2D slice\n\nfor i in range(CHANNELS):\n    df[f'image_path_{i:02}'] = df.groupby(['dataset'])['image_path'].shift(-i*STRIDE).ffill()\ndf['image_paths'] = df[[f'image_path_{i:02d}' for i in range(CHANNELS)]].values.tolist()\ndf.image_paths[0]","metadata":{"papermill":{"duration":0.051334,"end_time":"2024-01-14T03:18:01.433862","exception":false,"start_time":"2024-01-14T03:18:01.382528","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:31.088087Z","iopub.execute_input":"2024-01-19T17:25:31.088349Z","iopub.status.idle":"2024-01-19T17:25:31.126744Z","shell.execute_reply.started":"2024-01-19T17:25:31.088327Z","shell.execute_reply":"2024-01-19T17:25:31.125691Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🍚 | DataLoader\n\nThis dataloader reads 3 `.tif` image files and stacks them along the channel dimension. It then applies `MinMax` scaling to normalize the values. Since each image slice has a **large pixel size**, we can't directly feed the full images into the model during training. Downsampling the images would also cause us to lose vital information. Therefore, we will train our model on **random crops** from the images and infer on the full image sizes using the **sliding window** method. This dataloader also applies typical augmentations like `flips` and `rotations` to the images and masks. Note that we need to apply the same augmentations to both the `image` and `mask` otherwise it will produce erroneous results.\n\n> Even though the `.tif` images have 4 channels, all channels contain the same data. Therefore, ultimately there is only 1 unique channel of data in each image","metadata":{"papermill":{"duration":0.011843,"end_time":"2024-01-14T03:18:01.457956","exception":false,"start_time":"2024-01-14T03:18:01.446113","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def build_decoder(with_labels=True, target_size=CFG.image_size, augment=False):\n    def decode_image(paths):\n        img_array = tf.TensorArray(dtype=tf.uint8, size=len(paths))\n        for i in range(len(paths)):\n            file_bytes = tf.io.read_file(paths[i])\n            img0 = tfio.experimental.image.decode_tiff(file_bytes)[..., 0:1]\n            img_array = img_array.write(i, img0[...,0])\n        img = tf.transpose(img_array.stack(), perm=(1, 2, 0))\n        img = tf.cast(img, tf.float32)\n        img -= tf.reduce_min(img)\n        img /= tf.reduce_max(img) + 0.001\n        del img_array\n        return img\n    \n    def decode_mask(mask_path):\n        file_bytes = tf.io.read_file(mask_path)\n        msk = tfio.experimental.image.decode_tiff(file_bytes)[...,0:1]\n        msk = tf.cast(msk, tf.float32) / 255.0\n        return msk\n\n    def decode_without_labels(img_path):\n        img = decode_image(img_path)\n        img = tf.reshape(img, [*target_size, 3])\n        return img\n    \n    def decode_with_labels(img_path, msk_path):\n        img_msk = tf.concat([decode_image(img_path), decode_mask(msk_path)], axis=-1)\n        img_msk = tf.image.random_crop(img_msk, [*target_size, 4])\n        if augment:\n            img_msk = apply_augmentations(img_msk)\n        img = tf.reshape(img_msk[...,0:3], [*target_size, 3])\n        msk = tf.reshape(img_msk[...,3:4], [*target_size, 1])\n        return (img, msk)\n    \n    def apply_augmentations(img):\n        img = tf.image.random_flip_left_right(img)\n        img = tf.image.random_flip_up_down(img)\n        img = tf.image.rot90(img, k=np.random.randint(-3, 3))\n        return img\n    \n    return decode_with_labels if with_labels else decode_without_labels\n\n\ndef build_dataset(img_paths, msk_paths=None, batch_size=32, cache=True,\n                  decode_fn=None, augment_fn=None,\n                  augment=True, repeat=True, shuffle=1024, \n                  cache_dir=\"\", drop_remainder=False):\n    if cache_dir != \"\" and cache is True:\n        os.makedirs(cache_dir, exist_ok=True)\n    \n    if decode_fn is None:\n        decode_fn = build_decoder(msk_paths is not None, augment=augment)\n    \n    AUTO = tf.data.experimental.AUTOTUNE\n    slices = img_paths if msk_paths is None else (img_paths, msk_paths)\n    \n    ds = tf.data.Dataset.from_tensor_slices(slices)\n    ds = ds.map(decode_fn, num_parallel_calls=AUTO)\n    ds = ds.cache(cache_dir) if cache else ds\n    ds = ds.repeat() if repeat else ds\n    if shuffle: \n        ds = ds.shuffle(shuffle, seed=CFG.seed)\n        opt = tf.data.Options()\n        opt.experimental_deterministic = False\n        ds = ds.with_options(opt)\n    ds = ds.batch(batch_size, drop_remainder=drop_remainder)\n    ds = ds.prefetch(AUTO)\n    return ds","metadata":{"papermill":{"duration":0.039133,"end_time":"2024-01-14T03:18:01.509017","exception":false,"start_time":"2024-01-14T03:18:01.469884","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:31.128327Z","iopub.execute_input":"2024-01-19T17:25:31.128651Z","iopub.status.idle":"2024-01-19T17:25:31.146605Z","shell.execute_reply.started":"2024-01-19T17:25:31.128606Z","shell.execute_reply":"2024-01-19T17:25:31.145672Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🔪 | Data Split\n\nWe will only use images from `kidney_3` folder for **validation** and rest of the images will be used for **training**.","metadata":{"papermill":{"duration":0.012174,"end_time":"2024-01-14T03:18:01.538524","exception":false,"start_time":"2024-01-14T03:18:01.52635","status":"completed"},"tags":[]}},{"cell_type":"code","source":"train_df = df[~df.dataset.str.contains('kidney_3')]\nvalid_df = df[df.dataset.str.contains('kidney_3')]\nprint('Num Train:', len(train_df), '| Num Valid:', len(valid_df))","metadata":{"papermill":{"duration":0.037496,"end_time":"2024-01-14T03:18:01.587924","exception":false,"start_time":"2024-01-14T03:18:01.550428","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:31.150449Z","iopub.execute_input":"2024-01-19T17:25:31.150765Z","iopub.status.idle":"2024-01-19T17:25:31.176063Z","shell.execute_reply.started":"2024-01-19T17:25:31.150739Z","shell.execute_reply":"2024-01-19T17:25:31.175076Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Build Train & Valid Dataset","metadata":{"papermill":{"duration":0.011875,"end_time":"2024-01-14T03:18:01.611955","exception":false,"start_time":"2024-01-14T03:18:01.60008","status":"completed"},"tags":[]}},{"cell_type":"code","source":"train_image_paths = train_df.image_paths.tolist()\ntrain_mask_paths = train_df.mask_path.tolist()\ntrain_ds = build_dataset(train_image_paths, train_mask_paths, batch_size=CFG.batch_size,\n                         cache=CFG.cache, augment=True)\n\nvalid_image_paths = valid_df.image_paths.tolist()\nvalid_mask_paths = valid_df.mask_path.tolist()\nvalid_ds = build_dataset(valid_image_paths, valid_mask_paths, batch_size=CFG.batch_size,\n                         cache=CFG.cache, repeat=False, shuffle=False, augment=False)","metadata":{"papermill":{"duration":1.049545,"end_time":"2024-01-14T03:18:02.673433","exception":false,"start_time":"2024-01-14T03:18:01.623888","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:31.177257Z","iopub.execute_input":"2024-01-19T17:25:31.177564Z","iopub.status.idle":"2024-01-19T17:25:32.2942Z","shell.execute_reply.started":"2024-01-19T17:25:31.177537Z","shell.execute_reply":"2024-01-19T17:25:32.29331Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Dataset Check","metadata":{"papermill":{"duration":0.011529,"end_time":"2024-01-14T03:18:02.697067","exception":false,"start_time":"2024-01-14T03:18:02.685538","status":"completed"},"tags":[]}},{"cell_type":"code","source":"batch = train_ds.take(1).get_single_element()\nkeras_cv.visualization.plot_segmentation_mask_gallery(\n    batch[0],\n    value_range=(0, 1),\n    num_classes=2,  # number of classes is 2 instead of 1 because of background.\n    y_true=batch[1],\n    scale=3,\n    rows=2,\n    cols=3,\n)","metadata":{"papermill":{"duration":35.790198,"end_time":"2024-01-14T03:18:38.498991","exception":false,"start_time":"2024-01-14T03:18:02.708793","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:25:32.295695Z","iopub.execute_input":"2024-01-19T17:25:32.296158Z","iopub.status.idle":"2024-01-19T17:26:10.903962Z","shell.execute_reply.started":"2024-01-19T17:25:32.296117Z","shell.execute_reply":"2024-01-19T17:26:10.902863Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🔍 | Loss & Metric\n\nTo optimize and evaluate our model we will use the `Dice Coefficient`. It is defined as:\n\n$$\nDice = \\frac{2|X \\cap Y|}{|X| + |Y|}\n$$\n\nWhere:\n\n- $|X \\cap Y|$ = Number of True Positives \n- $|X|$ = Number of pixels predicted as positive  \n- $|Y|$ = Number of pixels positive in ground truth\n\n> Note that during computing `metric` we are applying thresholding but during the loss calculation we are not.","metadata":{"papermill":{"duration":0.016002,"end_time":"2024-01-14T03:18:38.531064","exception":false,"start_time":"2024-01-14T03:18:38.515062","status":"completed"},"tags":[]}},{"cell_type":"code","source":"from keras import ops\n\nclass DiceLoss(keras.losses.Loss):\n    def __init__(self, smooth=1e-4, name=\"dice_loss\"):\n        super().__init__(name=name)\n        self.smooth = smooth\n\n    def call(self, y_true, y_pred):\n        # Flatten label and prediction tensors\n        y_true = ops.ravel(y_true)\n        y_pred = ops.ravel(y_pred)\n\n        # Compute the intersection & union\n        intersection = ops.sum(y_true * y_pred)\n        union = ops.sum(y_true) + ops.sum(y_pred)\n\n        # Compute the Dice coefficient\n        dice = (2. * intersection + self.smooth) / (union + self.smooth)\n        \n        # Return Dice loss\n        return 1. - dice\n    \nclass DiceCoef(keras.metrics.Metric):\n    def __init__(self, name='dice_coef', smooth=1e-4, threshold=0.5, **kwargs):\n        super().__init__(name=name, **kwargs)\n        self.smooth = smooth\n        self.threshold = threshold\n        self.intersection_sum = self.add_weight(name='intersection_sum', initializer='zeros')\n        self.union_sum = self.add_weight(name='union_sum', initializer='zeros')\n\n    def update_state(self, y_true, y_pred, sample_weight=None):\n        y_pred = ops.cast(y_pred > self.threshold, dtype=\"float32\")\n        y_true = ops.ravel(y_true)\n        y_pred = ops.ravel(y_pred)\n\n        intersection = ops.sum(y_true * y_pred)\n        union = ops.sum(y_true) + ops.sum(y_pred)\n\n        self.intersection_sum.assign_add(intersection)\n        self.union_sum.assign_add(union)\n\n    def result(self):\n        dice = (2 * self.intersection_sum + self.smooth) / (self.union_sum + self.smooth)\n        return dice\n\n    def reset_states(self):\n        self.intersection_sum.assign(0)\n        self.union_sum.assign(0)\n        \n    def get_config(self):\n        config = super().get_config()\n        config.update({'smooth': self.smooth, 'threshold': self.threshold})\n        return config","metadata":{"papermill":{"duration":0.033125,"end_time":"2024-01-14T03:18:38.58076","exception":false,"start_time":"2024-01-14T03:18:38.547635","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:26:10.90521Z","iopub.execute_input":"2024-01-19T17:26:10.905508Z","iopub.status.idle":"2024-01-19T17:26:10.920273Z","shell.execute_reply.started":"2024-01-19T17:26:10.905481Z","shell.execute_reply":"2024-01-19T17:26:10.919077Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🤖 | Modeling","metadata":{"papermill":{"duration":0.016849,"end_time":"2024-01-14T03:18:38.613991","exception":false,"start_time":"2024-01-14T03:18:38.597142","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"## Build Segmentation `Head`\n\nAs our pretrained `DeepLabV3+` model is trained on the `PASCAL VOC` dataset which contained `21` classes, we need to modify it for our dataset which contains only `1` class. Therefore, we will only use the pretrained weights for the layers before the segmentation `Head`, and declare a new `Head` layer suited for our dataset. \n\n> Note that we are using a `sigmoid` activation for the final layer, whereas the original model used `softmax` activation.","metadata":{"papermill":{"duration":0.015693,"end_time":"2024-01-14T03:18:38.645855","exception":false,"start_time":"2024-01-14T03:18:38.630162","status":"completed"},"tags":[]}},{"cell_type":"code","source":"segmentation_head = keras.Sequential(\n    [\n        keras.layers.Conv2D(\n            filters=32,\n            kernel_size=1,\n            padding=\"same\",\n            use_bias=False,\n        ),\n        keras.layers.BatchNormalization(),\n        keras.layers.ReLU(),\n        keras.layers.UpSampling2D(size=(4, 4), interpolation=\"bilinear\"),\n        keras.layers.Conv2D(\n            filters=CFG.num_classes,\n            kernel_size=1,\n            use_bias=False,\n            padding=\"same\",\n            activation=\"sigmoid\",\n            dtype=\"float32\",\n        ),\n    ], name=\"segmentation_head\",\n)","metadata":{"papermill":{"duration":0.029818,"end_time":"2024-01-14T03:18:38.691799","exception":false,"start_time":"2024-01-14T03:18:38.661981","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:26:10.921436Z","iopub.execute_input":"2024-01-19T17:26:10.921756Z","iopub.status.idle":"2024-01-19T17:26:10.943415Z","shell.execute_reply.started":"2024-01-19T17:26:10.921711Z","shell.execute_reply":"2024-01-19T17:26:10.942334Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Build `Model`","metadata":{"papermill":{"duration":0.0158,"end_time":"2024-01-14T03:18:38.723891","exception":false,"start_time":"2024-01-14T03:18:38.708091","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Load the full DeepLabV3+ model\nbackbone = keras_cv.models.DeepLabV3Plus.from_preset(\n    CFG.preset,\n    input_shape=[*CFG.image_size, 3],\n)\n\n# Take only layers from backbone before head\nneck_layer_name = backbone.layers[-2].name\nout = backbone.get_layer(neck_layer_name).output\n\n# Use newly defined head for segmentation\nout = segmentation_head(out)\n\n# Create a new model\nmodel = keras.models.Model(inputs=backbone.input, outputs=out)\n\n# Compile the model\nOPTIMIZER = keras.optimizers.Adam(learning_rate=1e-4) # learning_rate)\nMETRICS = [\n    DiceCoef(),\n    keras.metrics.BinaryAccuracy(name=\"accuracy\"),\n]\nLOSS = DiceLoss()  \nmodel.compile(optimizer=OPTIMIZER, loss=LOSS, metrics=METRICS)\n\n# Model Sumamry\nmodel.summary()","metadata":{"papermill":{"duration":10.446166,"end_time":"2024-01-14T03:18:49.186176","exception":false,"start_time":"2024-01-14T03:18:38.74001","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:26:10.94451Z","iopub.execute_input":"2024-01-19T17:26:10.944827Z","iopub.status.idle":"2024-01-19T17:26:21.923696Z","shell.execute_reply.started":"2024-01-19T17:26:10.9448Z","shell.execute_reply":"2024-01-19T17:26:21.922597Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ⚓ | LR Schedule\n\nA well-structured learning rate schedule is essential for efficient model training, ensuring optimal convergence and avoiding issues such as overshooting or stagnation.","metadata":{"papermill":{"duration":0.016209,"end_time":"2024-01-14T03:18:49.21924","exception":false,"start_time":"2024-01-14T03:18:49.203031","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import math\n\ndef get_lr_callback(batch_size=8, mode='cos', epochs=10, plot=False):\n    lr_start, lr_max, lr_min = 5e-5, 3e-5 * batch_size, 1e-5\n    lr_ramp_ep, lr_sus_ep, lr_decay = 3, 0, 0.75\n\n    def lrfn(epoch):  # Learning rate update function\n        if epoch < lr_ramp_ep: lr = (lr_max - lr_start) / lr_ramp_ep * epoch + lr_start\n        elif epoch < lr_ramp_ep + lr_sus_ep: lr = lr_max\n        elif mode == 'exp': lr = (lr_max - lr_min) * lr_decay**(epoch - lr_ramp_ep - lr_sus_ep) + lr_min\n        elif mode == 'step': lr = lr_max * lr_decay**((epoch - lr_ramp_ep - lr_sus_ep) // 2)\n        elif mode == 'cos':\n            decay_total_epochs, decay_epoch_index = epochs - lr_ramp_ep - lr_sus_ep + 3, epoch - lr_ramp_ep - lr_sus_ep\n            phase = math.pi * decay_epoch_index / decay_total_epochs\n            lr = (lr_max - lr_min) * 0.5 * (1 + math.cos(phase)) + lr_min\n        return lr\n\n    if plot:  # Plot lr curve if plot is True\n        plt.figure(figsize=(10, 5))\n        plt.plot(np.arange(epochs), [lrfn(epoch) for epoch in np.arange(epochs)], marker='o')\n        plt.xlabel('epoch'); plt.ylabel('lr')\n        plt.title('LR Scheduler')\n        plt.show()\n\n    return keras.callbacks.LearningRateScheduler(lrfn, verbose=False)  # Create lr callback","metadata":{"papermill":{"duration":0.028945,"end_time":"2024-01-14T03:18:49.264535","exception":false,"start_time":"2024-01-14T03:18:49.23559","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:26:21.925099Z","iopub.execute_input":"2024-01-19T17:26:21.92549Z","iopub.status.idle":"2024-01-19T17:26:21.937663Z","shell.execute_reply.started":"2024-01-19T17:26:21.925452Z","shell.execute_reply":"2024-01-19T17:26:21.936589Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"lr_cb = get_lr_callback(CFG.batch_size, plot=True)","metadata":{"papermill":{"duration":0.297147,"end_time":"2024-01-14T03:18:49.578089","exception":false,"start_time":"2024-01-14T03:18:49.280942","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:26:21.93902Z","iopub.execute_input":"2024-01-19T17:26:21.939444Z","iopub.status.idle":"2024-01-19T17:26:22.261729Z","shell.execute_reply.started":"2024-01-19T17:26:21.939408Z","shell.execute_reply":"2024-01-19T17:26:22.260516Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 💾 | Model Checkpointing","metadata":{"papermill":{"duration":0.017199,"end_time":"2024-01-14T03:18:49.613648","exception":false,"start_time":"2024-01-14T03:18:49.596449","status":"completed"},"tags":[]}},{"cell_type":"code","source":"ckpt_cb = keras.callbacks.ModelCheckpoint(\"best_model.keras\",\n                                         monitor='val_dice_coef',\n                                         save_best_only=True,\n                                         save_weights_only=False,\n                                         mode='max')","metadata":{"papermill":{"duration":0.024529,"end_time":"2024-01-14T03:18:49.655708","exception":false,"start_time":"2024-01-14T03:18:49.631179","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:26:22.263226Z","iopub.execute_input":"2024-01-19T17:26:22.263675Z","iopub.status.idle":"2024-01-19T17:26:22.269571Z","shell.execute_reply.started":"2024-01-19T17:26:22.263636Z","shell.execute_reply":"2024-01-19T17:26:22.268248Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🚂 | Training","metadata":{"papermill":{"duration":0.01671,"end_time":"2024-01-14T03:18:49.689354","exception":false,"start_time":"2024-01-14T03:18:49.672644","status":"completed"},"tags":[]}},{"cell_type":"code","source":"history = model.fit(\n    train_ds, \n    epochs=CFG.epochs,\n    callbacks=[lr_cb, ckpt_cb], \n    steps_per_epoch=len(train_df)//CFG.batch_size,\n    validation_data=valid_ds, \n    verbose=CFG.verbose\n)","metadata":{"papermill":{"duration":3374.692199,"end_time":"2024-01-14T04:15:04.398389","exception":false,"start_time":"2024-01-14T03:18:49.70619","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-01-19T17:26:22.270839Z","iopub.execute_input":"2024-01-19T17:26:22.271257Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🧪 | Prediction","metadata":{"papermill":{"duration":0.693309,"end_time":"2024-01-14T04:15:05.731839","exception":false,"start_time":"2024-01-14T04:15:05.03853","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"## Load Best Model","metadata":{"papermill":{"duration":0.632183,"end_time":"2024-01-14T04:15:06.991143","exception":false,"start_time":"2024-01-14T04:15:06.35896","status":"completed"},"tags":[]}},{"cell_type":"code","source":"model.load_weights(\"best_model.keras\")","metadata":{"papermill":{"duration":20.428261,"end_time":"2024-01-14T04:15:28.044401","exception":false,"start_time":"2024-01-14T04:15:07.61614","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Find Best **Threshold**  \n\nAs our submission expects a run length encoding (RLE) based on binary prediction, we need to convert our continuous predictions to discrete binary values (`0` and `1`). Therefore, we need to apply thresholding. \n\nIn the following cell, we will find the best threshold value to apply to our predictions to ensure the best results:","metadata":{"papermill":{"duration":0.638624,"end_time":"2024-01-14T04:15:29.39137","exception":false,"start_time":"2024-01-14T04:15:28.752746","status":"completed"},"tags":[]}},{"cell_type":"code","source":"thrs = np.linspace(0, 1, 20)\nresults = []\nfor thr in tqdm(thrs, desc=\"Searching best thr \"):\n    dice_coef = DiceCoef(threshold=thr)\n    for (imgs, msks) in valid_ds:\n        imgs = ops.convert_to_tensor(imgs)\n        msks = ops.convert_to_tensor(msks)\n        preds = model(imgs)\n        dice_coef.update_state(msks, preds)\n    results.append(np.array(dice_coef.result()))\nresults = np.array(results)","metadata":{"papermill":{"duration":275.438143,"end_time":"2024-01-14T04:20:05.465516","exception":false,"start_time":"2024-01-14T04:15:30.027373","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Plot Best **Threshold**","metadata":{"papermill":{"duration":0.626773,"end_time":"2024-01-14T04:20:06.784792","exception":false,"start_time":"2024-01-14T04:20:06.158019","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Find the best threshold and best score\nBEST_THR = thrs[np.argmax(results)]\nBEST_SCORE = max(results)\n\n# Create the plot\nplt.figure(figsize=(10, 4))\nplt.plot(thrs, results, marker='o', label='Results',)\nplt.axvline(BEST_THR, color='red', linestyle='--', label=f'Best Threshold = {BEST_THR:.2f}')\n\n# Calculate relative coordinates based on figure size\nx_relative = 0.5  # Adjust as needed\ny_relative = 0.9  # Adjust as needed\n\n# Convert relative coordinates to absolute coordinates\nx_absolute = x_relative * (plt.xlim()[1] - plt.xlim()[0]) + plt.xlim()[0]\ny_absolute = y_relative * (plt.ylim()[1] - plt.ylim()[0]) + plt.ylim()[0]\n\nplt.annotate(f'Best Threshold = {BEST_THR:.2f}\\nBest Score = {BEST_SCORE:.2f}', \n             xy=(BEST_THR, BEST_SCORE), xytext=(x_absolute, y_absolute),\n             arrowprops=dict(arrowstyle='->', color='red'), color='red')\nplt.xlabel('Threshold')\nplt.ylabel('Results')\nplt.legend()\nplt.show()","metadata":{"papermill":{"duration":0.993778,"end_time":"2024-01-14T04:20:08.40966","exception":false,"start_time":"2024-01-14T04:20:07.415882","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Check Prediction","metadata":{"papermill":{"duration":0.703901,"end_time":"2024-01-14T04:20:09.745279","exception":false,"start_time":"2024-01-14T04:20:09.041378","status":"completed"},"tags":[]}},{"cell_type":"code","source":"images, masks = next(iter(valid_ds.take(1)))\nimages = ops.convert_to_tensor(images)\nmasks = ops.convert_to_tensor(masks)\npreds = ops.cast(model(images) > BEST_THR, \"float32\")\n\nkeras_cv.visualization.plot_segmentation_mask_gallery(\n    images,\n    value_range=(0, 1),\n    num_classes=2,\n    y_true=masks,\n    y_pred=preds,\n    scale=5,\n    rows=4,\n    cols=3,)","metadata":{"papermill":{"duration":3.496629,"end_time":"2024-01-14T04:20:13.878909","exception":false,"start_time":"2024-01-14T04:20:10.38228","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]}]}