{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":61446,"databundleVersionId":6962461,"sourceType":"competition"},{"sourceId":407317,"sourceType":"datasetVersion","datasetId":181273}],"dockerImageVersionId":30673,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom torch.optim import Adam\n\nfrom sklearn.model_selection import train_test_split\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nimport glob\nfrom tqdm.notebook import tqdm, trange\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-03-23T23:24:06.697933Z","iopub.execute_input":"2024-03-23T23:24:06.698273Z","iopub.status.idle":"2024-03-23T23:24:23.074394Z","shell.execute_reply.started":"2024-03-23T23:24:06.698245Z","shell.execute_reply":"2024-03-23T23:24:23.073570Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 👽 [Here](https://www.kaggle.com/code/samithanawarathna/tensorflow-mri-segmentation-with-unet-resunet) is the tensorflow version of this notebook","metadata":{}},{"cell_type":"markdown","source":"# 👩‍🔧 Configurations","metadata":{}},{"cell_type":"code","source":"IMAGE_WIDTH = 256\nIMAGE_HEIGHT = 256\nNUM_CHANNELS = 3\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\ndevice","metadata":{"execution":{"iopub.status.busy":"2024-03-23T23:24:23.075895Z","iopub.execute_input":"2024-03-23T23:24:23.076310Z","iopub.status.idle":"2024-03-23T23:24:23.141274Z","shell.execute_reply.started":"2024-03-23T23:24:23.076286Z","shell.execute_reply":"2024-03-23T23:24:23.140312Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🧠 Loading & Preprocessing Data","metadata":{}},{"cell_type":"markdown","source":"## MRI Data","metadata":{}},{"cell_type":"code","source":"mask_paths = glob.glob('/kaggle/input/lgg-mri-segmentation/kaggle_3m/*/*_mask.tif')","metadata":{"execution":{"iopub.status.busy":"2024-03-23T00:22:32.751584Z","iopub.execute_input":"2024-03-23T00:22:32.751911Z","iopub.status.idle":"2024-03-23T00:22:35.048291Z","shell.execute_reply.started":"2024-03-23T00:22:32.751880Z","shell.execute_reply":"2024-03-23T00:22:35.047401Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### categorizing images with empty mask and non-empty mask to seperate arrays","metadata":{}},{"cell_type":"code","source":"empty_masks = []\nnon_empty_masks = []\n\nfor mask_path in tqdm(mask_paths):\n    mask = np.array(Image.open(mask_path))\n    if (mask == 0).all():\n        empty_masks.append(mask_path)\n    else:\n        non_empty_masks.append(mask_path)","metadata":{"execution":{"iopub.status.busy":"2024-03-23T00:22:35.050188Z","iopub.execute_input":"2024-03-23T00:22:35.051247Z","iopub.status.idle":"2024-03-23T00:23:01.224079Z","shell.execute_reply.started":"2024-03-23T00:22:35.051200Z","shell.execute_reply":"2024-03-23T00:23:01.223178Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### selecting evey non-empty masked images and only 200 empty masked images (roughly 1/4 of all selected masks) for further modeling","metadata":{}},{"cell_type":"code","source":"masks_paths = non_empty_masks + [empty_masks[idx] for idx in np.random.choice(len(empty_masks), 200)]\nimage_paths = [mask_path.replace('_mask', '') for mask_path in mask_paths]\n\npath_df = pd.DataFrame({'image':image_paths, 'mask':mask_paths})","metadata":{"execution":{"iopub.status.busy":"2024-03-23T00:25:22.862063Z","iopub.execute_input":"2024-03-23T00:25:22.862436Z","iopub.status.idle":"2024-03-23T00:25:22.871358Z","shell.execute_reply.started":"2024-03-23T00:25:22.862411Z","shell.execute_reply":"2024-03-23T00:25:22.870127Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class MRIDataset(Dataset):\n    def __init__(self, df):\n        self.df = df.reset_index(drop=True)\n        self.preprocess_image = transforms.Compose([\n            transforms.PILToTensor(),\n            transforms.Resize((IMAGE_WIDTH, IMAGE_HEIGHT)),\n            transforms.ConvertImageDtype(torch.float),\n        ])\n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        if idx >= self.__len__():\n            raise KeyError\n            \n        image_path = self.df.loc[idx, 'image']\n        image = Image.open(image_path)\n        image = self.preprocess_image(image)\n        \n        mask_path = self.df.loc[idx, 'mask']\n        mask = Image.open(mask_path)\n        mask = self.preprocess_image(mask)\n        \n        return image, mask\n","metadata":{"execution":{"iopub.status.busy":"2024-03-23T00:25:37.846865Z","iopub.execute_input":"2024-03-23T00:25:37.847977Z","iopub.status.idle":"2024-03-23T00:25:37.856635Z","shell.execute_reply.started":"2024-03-23T00:25:37.847926Z","shell.execute_reply":"2024-03-23T00:25:37.855136Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train, test_val = train_test_split(path_df, test_size = 0.3)\ntest, val = train_test_split(test_val, test_size= 0.25)\n\ntrain = MRIDataset(train)\ntest = MRIDataset(test)\nval = MRIDataset(val)\n\ntrain = DataLoader(train, batch_size=16)\ntest = DataLoader(test, batch_size=16)\nval = DataLoader(val, batch_size=16)","metadata":{"execution":{"iopub.status.busy":"2024-03-22T00:35:50.427824Z","iopub.execute_input":"2024-03-22T00:35:50.428193Z","iopub.status.idle":"2024-03-22T00:35:50.438721Z","shell.execute_reply.started":"2024-03-22T00:35:50.428163Z","shell.execute_reply":"2024-03-22T00:35:50.437844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## HOA Data","metadata":{}},{"cell_type":"code","source":"FOLDER_NAME = 'kidney_1_dense'\n\nhoa_images = glob.glob(f'/kaggle/input/blood-vessel-segmentation/train/{FOLDER_NAME}/images/*.tif')\nhoa_masks = [image_path.replace('images', 'labels') for image_path  in hoa_images]\n\nclass HOADataset(Dataset):\n    def __init__(self, image_paths):\n        super().__init__()\n        self.image_paths = image_paths\n        self.preprocess = transforms.Compose([\n            transforms.PILToTensor(),\n            transforms.Resize((IMAGE_WIDTH, IMAGE_HEIGHT)),\n            transforms.ConvertImageDtype(torch.float),\n        ])        \n    def __len__(self):\n        return len(self.image_paths)\n    \n    def __getitem__(self, idx):\n        if idx >= self.__len__():\n            raise KeyError\n            \n        image_path = self.image_paths[idx]\n        mask_path = image_path.replace('images', 'labels')\n        \n        image = Image.open(image_path)\n        image = image.resize((IMAGE_WIDTH, IMAGE_HEIGHT))\n        image = np.array(image).astype(np.float32)[np.newaxis, ...]\n        image = torch.tensor(image)\n        \n        mask = Image.open(mask_path)\n        mask = mask.resize((IMAGE_WIDTH, IMAGE_HEIGHT))\n        mask = np.array(mask).astype(np.float32)[np.newaxis, ...]\n        mask = torch.tensor(mask) / 255.\n        return image, mask\n    \ndataset = HOADataset(hoa_images)\n\ntrain, test_val = train_test_split(hoa_images, test_size = 0.3)\ntest, val = train_test_split(test_val, test_size= 0.25)\n\ntrain = HOADataset(train)\ntest = HOADataset(test)\nval = HOADataset(val)\n\ntrain = DataLoader(train, batch_size=16)\ntest = DataLoader(test, batch_size=16)\nval = DataLoader(val, batch_size=16)","metadata":{"execution":{"iopub.status.busy":"2024-03-24T02:59:10.699002Z","iopub.execute_input":"2024-03-24T02:59:10.699854Z","iopub.status.idle":"2024-03-24T02:59:11.082061Z","shell.execute_reply.started":"2024-03-24T02:59:10.699814Z","shell.execute_reply":"2024-03-24T02:59:11.081266Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 👩‍🏫 Define Model","metadata":{}},{"cell_type":"code","source":"class ResConvBlock(nn.Module):\n    def __init__(self, input_features, output_features, dropout, kernel_size=3 ):\n        super().__init__()\n        self.conv1 = nn.Conv2d(input_features, output_features, kernel_size, padding='same')\n        self.conv2 = nn.Conv2d(output_features, output_features, kernel_size, padding='same')\n        self.batch_norm = nn.BatchNorm2d(output_features)\n        \n        self.res_conv = nn.Conv2d(input_features, output_features, 1, padding='same')\n        self.res_batch_norm = nn.BatchNorm2d(output_features)\n        \n        self.relu = nn.ReLU()\n        self.dropout = nn.Dropout(dropout)\n        self.con_batch_norm = nn.BatchNorm2d(output_features)\n        \n    def forward(self, x):\n        res_x = x\n        \n        x = self.conv1(x)\n        x = self.relu(x)\n        x = self.dropout(x)\n        x = self.conv2(x)\n        x = self.relu(x)\n        x = self.batch_norm(x)\n        \n        res_x = self.res_conv(res_x)\n        x = self.relu(x)\n        res_x = self.res_batch_norm(res_x)\n        \n        x = torch.add(x, res_x)\n        x = self.con_batch_norm(x)\n        \n        del res_x\n        \n        return x\n   \n\ndef test_model():\n    x = torch.rand(16, 3, 256, 256).to(device)\n    model = ResConvBlock(3, 16, 0.1).to(device)\n    y = model(x)\n    \n    return y.shape, torch.isnan(y).sum()\n\ntest_model()","metadata":{"execution":{"iopub.status.busy":"2024-03-23T23:24:26.642389Z","iopub.execute_input":"2024-03-23T23:24:26.643229Z","iopub.status.idle":"2024-03-23T23:24:28.266934Z","shell.execute_reply.started":"2024-03-23T23:24:26.643198Z","shell.execute_reply":"2024-03-23T23:24:28.266006Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Encoder(nn.Module):\n    def __init__(self, input_features, output_features, dropout, kernel_size=3):\n        super().__init__()\n        self.conv_block = ResConvBlock(input_features, output_features, dropout, kernel_size)\n        self.downsampler = nn.MaxPool2d((2, 2))\n    \n    def forward(self, x):\n        c = self.conv_block(x)\n        x = self.downsampler(c)\n        \n        return x, c\n    \ndef test_model():\n    x = torch.rand(16, 3, 256, 256).to(device)\n    model = Encoder(3, 16, 0.1).to(device)\n    c, y = model(x)\n    \n    return y.shape, torch.isnan(y).sum(), c.shape, torch.isnan(c).sum()\n\ntest_model()    ","metadata":{"execution":{"iopub.status.busy":"2024-03-23T23:24:28.268667Z","iopub.execute_input":"2024-03-23T23:24:28.269023Z","iopub.status.idle":"2024-03-23T23:24:28.332286Z","shell.execute_reply.started":"2024-03-23T23:24:28.268991Z","shell.execute_reply":"2024-03-23T23:24:28.331391Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Decoder(nn.Module):\n    def __init__(self, input_features, output_features, dropout, kernel_size=3):\n        super().__init__()\n        self.upsampler = nn.ConvTranspose2d(input_features, input_features, 2, stride=2)\n        self.conv_block = ResConvBlock(2*input_features, output_features, dropout, kernel_size)\n   \n    def forward(self, x, c):\n        x = self.upsampler(x)\n        x = torch.concat([x, c], dim=1)\n        x = self.conv_block(x)\n        \n        return x\n    \ndef test_model():\n    x = torch.rand(1, 16, 128, 128).to(device)\n    c = torch.rand(1, 16, 256, 256).to(device)\n    model = Decoder(16, 3, 0.1).to(device)\n    y = model(x, c)\n    \n    return y.shape, torch.isnan(y).sum()\n\ntest_model()","metadata":{"execution":{"iopub.status.busy":"2024-03-23T23:24:28.457534Z","iopub.execute_input":"2024-03-23T23:24:28.457869Z","iopub.status.idle":"2024-03-23T23:24:28.760479Z","shell.execute_reply.started":"2024-03-23T23:24:28.457844Z","shell.execute_reply":"2024-03-23T23:24:28.759506Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\nclass ResUNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.encoders = nn.ModuleList([Encoder(1, 16, 0.1)]+[Encoder(16*(2**i), 16*(2**(i+1)), 0.1*i) for i in range(3)])\n        \n        self.pointwise1 = nn.Conv2d(128, 256, 1)\n        self.relu = nn.ReLU()\n        self.batch_norm1 = nn.BatchNorm2d(256)\n        \n        self.pointwise2 = nn.Conv2d(256, 128, 1)\n        self.relu = nn.ReLU()\n        self.batch_norm2 = nn.BatchNorm2d(128)\n        \n        self.decoders = nn.ModuleList([Decoder(16*(2**(3-i)), 16*(2**(2-i)), 0.1*(3-i)) for i in range(3)]+[Decoder(16, 1, 0.1)])\n        \n        self.sigmoid = nn.Sigmoid()\n        \n    def forward(self, x):\n        conv_outputs = []\n        \n        for layer in self.encoders:\n            x , c = layer(x)\n            conv_outputs.append(c)\n                    \n        x = self.pointwise1(x)\n        x = self.relu(x)\n        x = self.batch_norm1(x)\n        \n        x = self.pointwise2(x)\n        x = self.relu(x)\n        x = self.batch_norm2(x)\n        \n        for i, layer in enumerate(self.decoders):\n            c = conv_outputs[3 - i]\n            x = layer(x, c)\n        \n        del conv_outputs\n        x = self.sigmoid(x)\n        \n        return x\n    \ndef test_model():\n    model = ResUNet().to(device)\n    x = torch.rand(1, 1, 256, 256).to(device)\n    y = model(x)\n    \n    return y.shape, torch.sum(torch.isnan(y))\n\ntest_model()\n","metadata":{"execution":{"iopub.status.busy":"2024-03-23T23:24:28.859054Z","iopub.execute_input":"2024-03-23T23:24:28.859391Z","iopub.status.idle":"2024-03-23T23:24:29.113664Z","shell.execute_reply.started":"2024-03-23T23:24:28.859363Z","shell.execute_reply":"2024-03-23T23:24:29.112734Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🦾 Training","metadata":{}},{"cell_type":"code","source":"model = ResUNet().to(device)\nlr = 1e-3\noptimizer = Adam(model.parameters(), lr=lr)\ncriterion = nn.BCELoss()","metadata":{"execution":{"iopub.status.busy":"2024-03-24T02:20:11.323175Z","iopub.execute_input":"2024-03-24T02:20:11.323565Z","iopub.status.idle":"2024-03-24T02:20:11.355958Z","shell.execute_reply.started":"2024-03-24T02:20:11.323522Z","shell.execute_reply":"2024-03-24T02:20:11.355158Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### I'm using *[](http://)binary_crossentropy* loss here but you can try out *dice_loss* or *focal_loss*","metadata":{}},{"cell_type":"markdown","source":"### plot some prediction from model before training","metadata":{}},{"cell_type":"code","source":"images, masks = next(iter(test))\npreds = model(images.to(device))\n\nplt.subplots(10, 2, figsize=(10, 50))\nfor i in range(10):\n    plt.subplot(10, 2, (i+1)*2 - 1)\n    plt.imshow(preds[i, 0,:, :].detach().cpu().numpy())\n    plt.subplot(10, 2, (i+1)*2)\n    plt.imshow(masks[i, 0,:, :].detach().cpu().numpy())\nplt.show()    ","metadata":{"execution":{"iopub.status.busy":"2024-03-24T02:59:18.358946Z","iopub.execute_input":"2024-03-24T02:59:18.359779Z","iopub.status.idle":"2024-03-24T02:59:23.303373Z","shell.execute_reply.started":"2024-03-24T02:59:18.359746Z","shell.execute_reply":"2024-03-24T02:59:23.302462Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"EPOCHS = 80\nmin_val_loss = np.inf\n\nMODEL_CHK_PATH = f'/kaggle/working/{FOLDER_NAME}/'\nos.makedirs(MODEL_CHK_PATH, exist_ok=True)\n\nfor epoch in trange(EPOCHS):\n    for i, (image, mask) in enumerate(tqdm(train)):\n        image, mask = image.to(device), mask.to(device)\n        optimizer.zero_grad()\n        output = model(image)\n        loss = criterion(output, mask)\n        loss.backward()\n        optimizer.step()\n        \n\n    for j, (image, mask) in enumerate(tqdm(val)):\n        image, mask = image.to(device), mask.to(device)\n        avg_loss = []\n                \n        with torch.no_grad():\n            output = model(image)\n            loss = criterion(output, mask)\n            avg_loss.append(loss.cpu().numpy())\n    avg_loss = np.mean(avg_loss)\n    \n    if avg_loss < min_val_loss:\n        torch.save(model.state_dict(), MODEL_CHK_PATH + 'model')\n        print(f'val_loss decreased by {min_val_loss - avg_loss}')\n        min_val_loss = avg_loss\n            \n    print(f'EPOCH: {epoch}, VAL_LOSS: {avg_loss}')\n                    ","metadata":{"execution":{"iopub.status.busy":"2024-03-24T02:59:42.169334Z","iopub.execute_input":"2024-03-24T02:59:42.169703Z","iopub.status.idle":"2024-03-24T03:43:49.664346Z","shell.execute_reply.started":"2024-03-24T02:59:42.169676Z","shell.execute_reply":"2024-03-24T03:43:49.663343Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📊 Visualizing predictions","metadata":{}},{"cell_type":"code","source":"images, masks = next(iter(test))\npreds = model(images.to(device))\n\nplt.subplots(10, 2, figsize=(10, 50))\nfor i in range(10):\n    plt.subplot(10, 2, (i+1)*2 - 1)\n    plt.imshow(preds[i, 0,:, :].detach().cpu().numpy() > .5)\n    plt.subplot(10, 2, (i+1)*2)\n    plt.imshow(masks[i, 0,:, :].detach().cpu().numpy())\nplt.show() ","metadata":{"execution":{"iopub.status.busy":"2024-03-24T03:46:32.848957Z","iopub.execute_input":"2024-03-24T03:46:32.849369Z","iopub.status.idle":"2024-03-24T03:46:36.968917Z","shell.execute_reply.started":"2024-03-24T03:46:32.849336Z","shell.execute_reply":"2024-03-24T03:46:36.967941Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 💗💞 please **Upvote** if you learn someting from this notebook 💗💞","metadata":{}},{"cell_type":"code","source":"model2 = ResUNet()\nmodel2.load_state_dict(torch.load('/kaggle/working/kidney_2/model'))\n","metadata":{"execution":{"iopub.status.busy":"2024-03-24T02:19:14.783017Z","iopub.execute_input":"2024-03-24T02:19:14.783395Z","iopub.status.idle":"2024-03-24T02:19:14.852765Z","shell.execute_reply.started":"2024-03-24T02:19:14.783367Z","shell.execute_reply":"2024-03-24T02:19:14.851822Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}