{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":97838,"databundleVersionId":11654191,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":575255,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":430338,"modelId":447289}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n!pip install torchviz\nfrom torchviz import make_dot\nimport cv2\nimport torch.nn as nn\nimport torch.nn.functional as F\n!pip install -U albumentations","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T22:58:48.548776Z","iopub.execute_input":"2025-09-10T22:58:48.549038Z","iopub.status.idle":"2025-09-10T23:00:20.517826Z","shell.execute_reply.started":"2025-09-10T22:58:48.549018Z","shell.execute_reply":"2025-09-10T23:00:20.516711Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:20.519618Z","iopub.execute_input":"2025-09-10T23:00:20.520225Z","iopub.status.idle":"2025-09-10T23:00:20.524562Z","shell.execute_reply.started":"2025-09-10T23:00:20.520199Z","shell.execute_reply":"2025-09-10T23:00:20.523824Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:20.525682Z","iopub.execute_input":"2025-09-10T23:00:20.525933Z","iopub.status.idle":"2025-09-10T23:00:20.594604Z","shell.execute_reply.started":"2025-09-10T23:00:20.525910Z","shell.execute_reply":"2025-09-10T23:00:20.593875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ISICDataset(Dataset):\n    def __init__(self, images_dir, masks_dir, transform=None):\n        self.images_dir = images_dir\n        self.masks_dir = masks_dir\n        self.transform = transform\n        self.images = os.listdir(images_dir)\n\n    def __len__(self):\n        return len(self.images)\n    \n    def __getitem__(self, idx):\n        img_name = self.images[idx]\n        img_path = os.path.join(self.images_dir, img_name)\n\n        # Match mask name\n        mask_name = img_name.replace(\".jpg\", \".png\")\n        mask_path = os.path.join(self.masks_dir, mask_name)\n\n        # Load image and mask using cv2 for Albumentations\n        image = cv2.imread(img_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)\n        \n        # Resize raw image to match the transform size\n        raw_image = cv2.resize(image, (128, 128))\n        raw_image = torch.tensor(raw_image, dtype=torch.float32).permute(2, 0, 1) / 255.0\n        \n        # Apply Albumentations transform\n        if self.transform:\n            augmented = self.transform(image=image, mask=mask)\n            image = augmented[\"image\"]\n            mask = augmented[\"mask\"]\n        \n        # Convert mask to binary (0 and 1) and add channel dimension\n        mask = (mask > 0).float().unsqueeze(0)\n        \n        return image, mask, raw_image\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:20.596180Z","iopub.execute_input":"2025-09-10T23:00:20.596398Z","iopub.status.idle":"2025-09-10T23:00:20.611704Z","shell.execute_reply.started":"2025-09-10T23:00:20.596381Z","shell.execute_reply":"2025-09-10T23:00:20.611075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\ntrain_transform = A.Compose([\n    A.Resize(128,128),\n    A.HorizontalFlip(p=0.5),\n    A.ElasticTransform(alpha=120, sigma=6.0, p=1.0),\n    A.GridDistortion(num_steps=5, distort_limit=0.3, p=1.0),\n    A.OpticalDistortion(distort_limit=2.0, p=1.0),\n    A.Normalize(\n        mean=(0.72183126, 0.61805737, 0.5664776),\n        std=(0.1031459,  0.12656619, 0.14789347)\n    ),\n    ToTensorV2()\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:20.612571Z","iopub.execute_input":"2025-09-10T23:00:20.612834Z","iopub.status.idle":"2025-09-10T23:00:21.765117Z","shell.execute_reply.started":"2025-09-10T23:00:20.612816Z","shell.execute_reply":"2025-09-10T23:00:21.764545Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndata_dir = \"/kaggle/input/a0-2025-medical-image-segmentation/Dataset/Train\"\n\nimages_dir = f\"{data_dir}/Image\"\nmasks_dir = f\"{data_dir}/Mask\"\n\n# Initialize dataset\ndataset = ISICDataset(images_dir=images_dir, masks_dir=masks_dir, transform=train_transform)\n\n# Initialize dataloader\ndataloader = DataLoader(dataset, batch_size=16, shuffle=True)\n# Get images and masks batch shapes\nimages, masks, raw= next(iter(dataloader))\nprint(f\"Image batch shape: {images.shape}\")\nprint(f\"Mask batch shape: {masks.shape}\")\nprint(f\"Raw batch shape: {raw.shape}\")\n# # Calculate mean and std\n# mean = 0.0\n# std = 0.0\n# nb_samples = 0.0\n\n# for data, _, _ in dataloader:\n#     batch_samples = data.size(0)\n#     data = data.view(batch_samples, data.size(1), -1)\n#     mean += data.mean(2).sum(0)\n#     std += data.std(2).sum(0)\n#     nb_samples += batch_samples\n\n# mean /= nb_samples\n# std /= nb_samples\n\n# print(\"Mean:\", mean.numpy())\n# print(\"Std:\", std.numpy())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:21.765811Z","iopub.execute_input":"2025-09-10T23:00:21.766080Z","iopub.status.idle":"2025-09-10T23:00:22.915093Z","shell.execute_reply.started":"2025-09-10T23:00:21.766064Z","shell.execute_reply":"2025-09-10T23:00:22.914391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# Transform for images\nimage_transform = transforms.Compose([\n    transforms.Resize((128, 128)),\n    transforms.ToTensor(),\n])\n\n\n# Initialize dataset\ndataset = ISICDataset(images_dir=images_dir, masks_dir=masks_dir, transform = image_transform)\n\n# Create dataloader\ndataloader = DataLoader(dataset, batch_size=64, shuffle=True)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:22.915808Z","iopub.execute_input":"2025-09-10T23:00:22.916042Z","iopub.status.idle":"2025-09-10T23:00:22.919493Z","shell.execute_reply.started":"2025-09-10T23:00:22.916023Z","shell.execute_reply":"2025-09-10T23:00:22.918835Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport matplotlib.pyplot as plt\n\n# function that displays batch of images and masks\ndef show_batch(images, masks, raw):\n    fig, ax = plt.subplots(3, 8, figsize=(20, 7))\n    for i in range(8):\n        ax[0][i].imshow(images[i].permute(1, 2, 0).clamp(0, 1))\n        ax[0][i].set_title(\"Normalized Image\")\n        ax[0][i].axis(\"off\")\n        ax[1][i].imshow(raw[i].permute(1, 2, 0))\n        ax[1][i].set_title(\"Image\")\n        ax[1][i].axis(\"off\")\n        ax[2][i].imshow(masks[i][0], cmap=\"gray\")\n        ax[2][i].set_title(\"Mask\")\n        ax[2][i].axis(\"off\")\n    plt.show()\n\nshow_batch(images, masks, raw)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:22.920247Z","iopub.execute_input":"2025-09-10T23:00:22.920559Z","iopub.status.idle":"2025-09-10T23:00:24.325807Z","shell.execute_reply.started":"2025-09-10T23:00:22.920539Z","shell.execute_reply":"2025-09-10T23:00:24.325076Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DoubleConv(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super(DoubleConv, self).__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True)\n        )\n\n    def forward(self, x):\n        return self.conv(x)\n    \nclass UNet(nn.Module):\n    def __init__(self, in_channels=3, out_channels=1):\n        super(UNet, self).__init__()\n\n        # Encoder: Downsampling path\n        self.encoder1 = DoubleConv(in_channels, 64)\n        self.encoder2 = DoubleConv(64, 128)\n        self.encoder3 = DoubleConv(128, 256)\n        self.encoder4 = DoubleConv(256, 512)\n\n        # Bottleneck\n        self.bottleneck = DoubleConv(512, 1024)\n\n        # Decoder: Upsampling path\n        self.upconv4 = nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2)\n        self.decoder4 = DoubleConv(1024, 512)\n        self.upconv3 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2)\n        self.decoder3 = DoubleConv(512, 256)\n        self.upconv2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2)\n        self.decoder2 = DoubleConv(256, 128)\n        self.upconv1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)\n        self.decoder1 = DoubleConv(128, 64)\n\n        # Output layer\n        self.output_conv = nn.Conv2d(64, out_channels, kernel_size=1)\n\n    def forward(self, x):\n        # Encoder\n        enc1 = self.encoder1(x)\n        enc2 = self.encoder2(F.max_pool2d(enc1, 2))\n        enc3 = self.encoder3(F.max_pool2d(enc2, 2))\n        enc4 = self.encoder4(F.max_pool2d(enc3, 2))\n\n        # Bottleneck\n        bottleneck = self.bottleneck(F.max_pool2d(enc4, 2))\n\n        # Decoder with skip connections\n        dec4 = self.upconv4(bottleneck)\n        dec4 = torch.cat((dec4, enc4), dim=1)\n        dec4 = self.decoder4(dec4)\n\n        dec3 = self.upconv3(dec4)\n        dec3 = torch.cat((dec3, enc3), dim=1)\n        dec3 = self.decoder3(dec3)\n\n        dec2 = self.upconv2(dec3)\n        dec2 = torch.cat((dec2, enc2), dim=1)\n        dec2 = self.decoder2(dec2)\n\n        dec1 = self.upconv1(dec2)\n        dec1 = torch.cat((dec1, enc1), dim=1)\n        dec1 = self.decoder1(dec1)\n\n        # Output layer\n        output = self.output_conv(dec1)\n        return output\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:24.326708Z","iopub.execute_input":"2025-09-10T23:00:24.326923Z","iopub.status.idle":"2025-09-10T23:00:24.336913Z","shell.execute_reply.started":"2025-09-10T23:00:24.326906Z","shell.execute_reply":"2025-09-10T23:00:24.336167Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n\n\n# # -------------------- DoubleConv --------------------\n# class DoubleConv(nn.Module):\n#     def __init__(self, in_channels, out_channels):\n#         super(DoubleConv, self).__init__()\n#         self.double_conv = nn.Sequential(\n#             nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),\n#             nn.BatchNorm2d(out_channels),\n#             nn.ReLU(inplace=True),\n\n#             nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),\n#             nn.BatchNorm2d(out_channels),\n#             nn.ReLU(inplace=True)\n#         )\n\n#     def forward(self, x):\n#         return self.double_conv(x)\n\n\n# # -------------------- Down (Encoder block) --------------------\n# class Down(nn.Module):\n#     def __init__(self, in_channels, out_channels):\n#         super(Down, self).__init__()\n#         self.encoder = nn.Sequential(\n#             nn.MaxPool2d(2, 2),\n#             DoubleConv(in_channels, out_channels)\n#         )\n\n#     def forward(self, x):\n#         return self.encoder(x)\n\n\n# # -------------------- Up (Decoder block) --------------------\n# class Up(nn.Module):\n#     def __init__(self, in_channels, out_channels, bilinear=True):\n#         super(Up, self).__init__()\n\n#         if bilinear:\n#             self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)\n#         else:\n#             self.up = nn.ConvTranspose2d(in_channels // 2, in_channels // 2, kernel_size=2, stride=2)\n\n#         self.conv = DoubleConv(in_channels, out_channels)\n\n#     def forward(self, x1, x2):\n#         # Upsample\n#         x1 = self.up(x1)\n\n#         # Handle size mismatch (for odd input dimensions)\n#         diffY = x2.size()[2] - x1.size()[2]\n#         diffX = x2.size()[3] - x1.size()[3]\n\n#         x1 = F.pad(x1, (diffX // 2, diffX - diffX // 2,\n#                         diffY // 2, diffY - diffY // 2))\n\n#         # Concatenate encoder feature maps with upsampled output\n#         x = torch.cat([x2, x1], dim=1)\n#         return self.conv(x)\n\n\n# # -------------------- Out --------------------\n# class Out(nn.Module):\n#     def __init__(self, in_channels, out_channels):\n#         super(Out, self).__init__()\n#         self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)\n\n#     def forward(self, x):\n#         return self.conv(x)\n\n\n# # -------------------- Final U-Net (UNETR style) --------------------\n# class UNetModular(nn.Module):\n#     def __init__(self, in_channels=3, out_channels=1, n_channels=64, bilinear=True):\n#         super(UNetModular, self).__init__()\n\n#         # Encoder\n#         self.inc = DoubleConv(in_channels, n_channels)\n#         self.down1 = Down(n_channels, 2 * n_channels)\n#         self.down2 = Down(2 * n_channels, 4 * n_channels)\n#         self.down3 = Down(4 * n_channels, 8 * n_channels)\n#         self.down4 = Down(8 * n_channels, 16 * n_channels)\n\n#         # Decoder\n#         self.up1 = Up(16 * n_channels + 8 * n_channels, 8 * n_channels, bilinear)\n#         self.up2 = Up(8 * n_channels + 4 * n_channels, 4 * n_channels, bilinear)\n#         self.up3 = Up(4 * n_channels + 2 * n_channels, 2 * n_channels, bilinear)\n#         self.up4 = Up(2 * n_channels + n_channels, n_channels, bilinear)\n\n#         # Output\n#         self.outc = Out(n_channels, out_channels)\n\n#     def forward(self, x):\n#         # Encoder path\n#         x1 = self.inc(x)\n#         x2 = self.down1(x1)\n#         x3 = self.down2(x2)\n#         x4 = self.down3(x3)\n#         x5 = self.down4(x4)\n\n#         # Decoder path\n#         x = self.up1(x5, x4)\n#         x = self.up2(x, x3)\n#         x = self.up3(x, x2)\n#         x = self.up4(x, x1)\n\n#         # Final segmentation output\n#         return self.outc(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:24.339165Z","iopub.execute_input":"2025-09-10T23:00:24.339695Z","iopub.status.idle":"2025-09-10T23:00:24.357657Z","shell.execute_reply.started":"2025-09-10T23:00:24.339677Z","shell.execute_reply":"2025-09-10T23:00:24.356845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = UNet(in_channels=3, out_channels=1)\n\n# Create a dummy input tensor and pass it through the model\ninput_tensor = torch.randn(64, 3, 128, 128)\noutput = model(input_tensor)\n\nprint(f\"Output shape: {output.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:24.358589Z","iopub.execute_input":"2025-09-10T23:00:24.358851Z","iopub.status.idle":"2025-09-10T23:00:37.201969Z","shell.execute_reply.started":"2025-09-10T23:00:24.358835Z","shell.execute_reply":"2025-09-10T23:00:37.201254Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\n# Instantiate and run one forward pass\nmodel = UNet(in_channels=3, out_channels=1)\nx = torch.randn(1, 3, 64, 64)  # dummy input\ny = model(x)\n\n# Visualize computation graph\nmake_dot(y, params=dict(model.named_parameters())).render(\"unet_architecture\", format=\"png\")\n\nimg = mpimg.imread(\"unet_architecture.png\")\nplt.figure(figsize=(12, 25))\nplt.imshow(img)\nplt.axis('off')  # hide axes\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:37.202726Z","iopub.execute_input":"2025-09-10T23:00:37.202954Z","iopub.status.idle":"2025-09-10T23:00:43.308679Z","shell.execute_reply.started":"2025-09-10T23:00:37.202936Z","shell.execute_reply":"2025-09-10T23:00:43.307924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import random_split, DataLoader\n\ntrain_images_dir = f\"{data_dir}/Image\"\ntrain_masks_dir = f\"{data_dir}/Mask\"\n\nfull_dataset = ISICDataset(images_dir=train_images_dir, masks_dir=train_masks_dir,\n                           transform=train_transform)\n\nval_ratio = 0.2\nval_size = int(val_ratio * len(full_dataset))\ntrain_size = len(full_dataset) - val_size\ntrain_dataset, val_dataset = random_split(full_dataset, [train_size, val_size])\n\n# DataLoaders\ntrain_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=128, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:43.309577Z","iopub.execute_input":"2025-09-10T23:00:43.309806Z","iopub.status.idle":"2025-09-10T23:00:43.316440Z","shell.execute_reply.started":"2025-09-10T23:00:43.309788Z","shell.execute_reply":"2025-09-10T23:00:43.315882Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass CombinedLoss(nn.Module):\n    def __init__(self, alpha=0.1, beta=0.3, gamma=0.6, eps=1e-8):\n        super(CombinedLoss, self).__init__()\n        self.alpha = alpha\n        self.beta = beta\n        self.gamma = gamma\n        self.eps = eps\n        \n        # Verify weights sum to 1\n        assert abs(alpha + beta + gamma - 1.0) < 1e-6, \n    \n    def forward(self, pred, target):\n        # Ensure target is in the same format as pred\n        if target.dim() == 3:\n            target = target.unsqueeze(1)\n        \n        # Apply sigmoid to predictions if not already applied\n        pred = torch.sigmoid(pred)\n        \n        # Calculate individual losses\n        bce_loss = self.bce_loss(pred, target)\n        boundary_loss = self.active_boundary_loss(pred, target)\n        dice_loss = self.dice_loss(pred, target)\n        \n        # Combine losses with weights\n        total_loss = (self.alpha * bce_loss + \n                     self.beta * boundary_loss + \n                     self.gamma * dice_loss)\n        \n        return total_loss\n    \n    def bce_loss(self, pred, target):\n        # Use BCE with logits for numerical stability, but since we already applied sigmoid, use regular BCE\n        return F.binary_cross_entropy(pred, target)\n    \n    def dice_loss(self, pred, target):\n        # Flatten predictions and targets\n        pred_flat = pred.view(pred.size(0), -1)\n        target_flat = target.view(target.size(0), -1)\n        \n        # Calculate intersection and union\n        intersection = (pred_flat * target_flat).sum(1)\n        union = pred_flat.sum(1) + target_flat.sum(1)\n        \n        # Dice coefficient\n        dice = (2. * intersection + self.eps) / (union + self.eps)\n        \n        # Return 1 - Dice (loss)\n        return 1 - dice.mean()\n    \n    def active_boundary_loss(self, pred, target):\n        # Calculate gradients to identify boundaries\n        pred_grad_x = self._compute_gradient_x(pred)\n        pred_grad_y = self._compute_gradient_y(pred)\n        target_grad_x = self._compute_gradient_x(target)\n        target_grad_y = self._compute_gradient_y(target)\n        \n        # Magnitude of gradients\n        pred_grad_magnitude = torch.sqrt(pred_grad_x**2 + pred_grad_y**2 + self.eps)\n        target_grad_magnitude = torch.sqrt(target_grad_x**2 + target_grad_y**2 + self.eps)\n        \n        # Boundary loss - encourage alignment of boundaries\n        boundary_loss = F.l1_loss(pred_grad_magnitude, target_grad_magnitude)\n        \n        return boundary_loss\n    \n    def _compute_gradient_x(self, img):\n        sobel_x = torch.tensor([[-1, 0, 1], \n                               [-2, 0, 2], \n                               [-1, 0, 1]], dtype=torch.float32, device=img.device)\n        sobel_x = sobel_x.view(1, 1, 3, 3)\n        return F.conv2d(img, sobel_x, padding=1)\n    \n    def _compute_gradient_y(self, img):\n        sobel_y = torch.tensor([[-1, -2, -1], \n                               [0, 0, 0], \n                               [1, 2, 1]], dtype=torch.float32, device=img.device)\n        sobel_y = sobel_y.view(1, 1, 3, 3)\n        return F.conv2d(img, sobel_y, padding=1)\n\n# Alternative version with better numerical stability\nclass CombinedLossStable(nn.Module):\n    def __init__(self, alpha=0.1, beta=0.3, gamma=0.6, eps=1e-8):\n        super(CombinedLossStable, self).__init__()\n        self.alpha = alpha\n        self.beta = beta\n        self.gamma = gamma\n        self.eps = eps\n        \n        # Verify weights sum to 1\n        assert abs(alpha + beta + gamma - 1.0) < 1e-6, \"Weights should sum to 1\"\n    \n    def forward(self, pred, target):\n        # Ensure target is in the same format as pred\n        if target.dim() == 3:\n            target = target.unsqueeze(1)\n        \n        # Calculate individual losses (pred assumed to be logits)\n        bce_loss = self.bce_loss(pred, target)\n        boundary_loss = self.active_boundary_loss(torch.sigmoid(pred), target)\n        dice_loss = self.dice_loss(torch.sigmoid(pred), target)\n        \n        # Combine losses with weights\n        total_loss = (self.alpha * bce_loss + \n                     self.beta * boundary_loss + \n                     self.gamma * dice_loss)\n        \n        return total_loss\n    \n    def bce_loss(self, pred, target):\n        # Use BCE with logits for better numerical stability\n        return F.binary_cross_entropy_with_logits(pred, target)\n    \n    def dice_loss(self, pred, target):\n        # Flatten predictions and targets\n        pred_flat = pred.view(pred.size(0), -1)\n        target_flat = target.view(target.size(0), -1)\n        \n        # Calculate intersection and union\n        intersection = (pred_flat * target_flat).sum(1)\n        union = pred_flat.sum(1) + target_flat.sum(1)\n        \n        # Dice coefficient\n        dice = (2. * intersection + self.eps) / (union + self.eps)\n        \n        # Return 1 - Dice (loss)\n        return 1 - dice.mean()\n    \n    def active_boundary_loss(self, pred, target):\n        # Calculate gradients to identify boundaries\n        pred_grad_x = self._compute_gradient_x(pred)\n        pred_grad_y = self._compute_gradient_y(pred)\n        target_grad_x = self._compute_gradient_x(target)\n        target_grad_y = self._compute_gradient_y(target)\n        \n        # Magnitude of gradients\n        pred_grad_magnitude = torch.sqrt(pred_grad_x**2 + pred_grad_y**2 + self.eps)\n        target_grad_magnitude = torch.sqrt(target_grad_x**2 + target_grad_y**2 + self.eps)\n        \n        # Boundary loss - encourage alignment of boundaries\n        boundary_loss = F.l1_loss(pred_grad_magnitude, target_grad_magnitude)\n        \n        return boundary_loss\n    \n    def _compute_gradient_x(self, img):\n        sobel_x = torch.tensor([[-1, 0, 1], \n                               [-2, 0, 2], \n                               [-1, 0, 1]], dtype=torch.float32, device=img.device)\n        sobel_x = sobel_x.view(1, 1, 3, 3)\n        return F.conv2d(img, sobel_x, padding=1)\n    \n    def _compute_gradient_y(self, img):\n        sobel_y = torch.tensor([[-1, -2, -1], \n                               [0, 0, 0], \n                               [1, 2, 1]], dtype=torch.float32, device=img.device)\n        sobel_y = sobel_y.view(1, 1, 3, 3)\n        return F.conv2d(img, sobel_y, padding=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:43.317398Z","iopub.execute_input":"2025-09-10T23:00:43.318147Z","iopub.status.idle":"2025-09-10T23:00:43.340637Z","shell.execute_reply.started":"2025-09-10T23:00:43.318129Z","shell.execute_reply":"2025-09-10T23:00:43.339957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport torch.optim as optim\n\n# Model, loss function, and optimizer\nmodel = UNet(in_channels=3, out_channels=1)\ncriterion = CombinedLossStable(alpha=0.1, beta=0.3, gamma=0.6) \noptimizer = optim.AdamW(model.parameters(), lr=0.001)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:43.341352Z","iopub.execute_input":"2025-09-10T23:00:43.341767Z","iopub.status.idle":"2025-09-10T23:00:43.618609Z","shell.execute_reply.started":"2025-09-10T23:00:43.341751Z","shell.execute_reply":"2025-09-10T23:00:43.617814Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(\"Files in /kaggle/working/:\")\nprint(os.listdir(\"/kaggle/working/\"))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:07:45.634999Z","iopub.execute_input":"2025-09-10T23:07:45.635751Z","iopub.status.idle":"2025-09-10T23:07:45.639900Z","shell.execute_reply.started":"2025-09-10T23:07:45.635724Z","shell.execute_reply":"2025-09-10T23:07:45.639217Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"checkpoint = torch.load(\"/kaggle/input/unet/pytorch/default/1\", map_location=device)\n\nmodel.load_state_dict(checkpoint['model_state_dict'])\noptimizer.load_state_dict(checkpoint['optimizer_state_dict'])\nstart_epoch = checkpoint['epoch']\ntraining_losses = checkpoint['training_losses']\nvalidation_losses = checkpoint['validation_losses']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:12:57.538785Z","iopub.execute_input":"2025-09-10T23:12:57.539482Z","iopub.status.idle":"2025-09-10T23:12:57.556916Z","shell.execute_reply.started":"2025-09-10T23:12:57.539458Z","shell.execute_reply":"2025-09-10T23:12:57.555988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.to(device)\n\nnum_epochs = 100\ntraining_losses = []\nvalidation_losses = []\nbest_val_loss = float(\"inf\")  # Initialize before the loop\n\nfor epoch in range(num_epochs):\n    model.train()\n    train_loss = 0\n\n    for images, masks, _ in train_loader:\n        images, masks = images.to(device), masks.to(device)\n\n        outputs = model(images)\n        loss = criterion(outputs, masks)\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n\n    avg_train_loss = train_loss / len(train_loader)\n\n    # Validation step\n    model.eval()\n    val_loss = 0\n    with torch.no_grad():\n        for images, masks, _ in val_loader:\n            images, masks = images.to(device), masks.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, masks)\n            val_loss += loss.item()\n\n    avg_val_loss = val_loss / len(val_loader)\n\n    print(f\"Epoch [{epoch+1}/{num_epochs}], \"\n          f\"Train Loss: {avg_train_loss:.4f}, Val Loss: {avg_val_loss:.4f}\")\n\n    # Save if this is the best model so far\n    if avg_val_loss < best_val_loss:\n        best_val_loss = avg_val_loss\n        torch.save({\n            'epoch': epoch+1,\n            'model_state_dict': model.state_dict(),\n            'optimizer_state_dict': optimizer.state_dict(),\n            'training_losses': training_losses,\n            'validation_losses': validation_losses\n        }, \"/kaggle/working/best_model_unet_300.pth\")\n        print(f\"Saved new best model at epoch {epoch+1} with val loss {best_val_loss:.4f}\")\n\n    # Append losses for visualization later\n    training_losses.append(avg_train_loss)\n    validation_losses.append(avg_val_loss)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:43.676502Z","iopub.status.idle":"2025-09-10T23:00:43.676757Z","shell.execute_reply.started":"2025-09-10T23:00:43.676646Z","shell.execute_reply":"2025-09-10T23:00:43.676656Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figsize=(10, 5)\nplt.plot(training_losses, label=\"Training Loss\")\nplt.plot(validation_losses, label=\"Validation Loss\")\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:43.678232Z","iopub.status.idle":"2025-09-10T23:00:43.678569Z","shell.execute_reply.started":"2025-09-10T23:00:43.678390Z","shell.execute_reply":"2025-09-10T23:00:43.678404Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def dice_coefficient(outputs, masks, threshold=0.5, epsilon=1e-6):\n    # Convert logits/probabilities to binary mask\n    outputs = (outputs > threshold).float()\n    \n    outputs = outputs.view(-1)\n    masks = masks.view(-1)\n\n    intersection = (outputs * masks).sum()\n    dice = (2. * intersection + epsilon) / (outputs.sum() + masks.sum() + epsilon)\n    return dice\n\n\ndef intersection_over_union(outputs, masks, threshold=0.5, epsilon=1e-6):\n    outputs = (outputs > threshold).float()\n    \n    outputs = outputs.view(-1)\n    masks = masks.view(-1)\n\n    intersection = (outputs * masks).sum()\n    union = outputs.sum() + masks.sum() - intersection\n    iou = (intersection + epsilon) / (union + epsilon)\n    return iou\n\n\ntest_dice, test_iou = 0.0, 0.0\ncheckpoint = torch.load(\"best_model_unet_300.pth\", map_location=device)\n\nmodel.load_state_dict(checkpoint['model_state_dict'])\nmodel.eval()\n\nwith torch.no_grad():\n    for images, masks, _ in val_loader:\n        images = images.to(device)\n        masks = masks.to(device)\n\n        outputs = model(images)\n        outputs = torch.sigmoid(outputs)   # convert to probabilities\n\n        test_dice += dice_coefficient(outputs, masks).item()\n        test_iou += intersection_over_union(outputs, masks).item()\n\navg_dice = test_dice / len(val_loader)\navg_iou = test_iou / len(val_loader)\nprint(f\"Average Dice Coefficient: {avg_dice:.4f}\")\nprint(f\"Average Intersection over Union: {avg_iou:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:43.679661Z","iopub.status.idle":"2025-09-10T23:00:43.679942Z","shell.execute_reply.started":"2025-09-10T23:00:43.679789Z","shell.execute_reply":"2025-09-10T23:00:43.679805Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save(model.state_dict(), \"/kaggle/working/unet_model.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-10T23:00:43.680890Z","iopub.status.idle":"2025-09-10T23:00:43.681174Z","shell.execute_reply.started":"2025-09-10T23:00:43.681007Z","shell.execute_reply":"2025-09-10T23:00:43.681022Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}