{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":6927,"databundleVersionId":45059}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# 0) SETTINGS (FAST TRAINING)\n# ============================================================\nimport os, zipfile, random\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nfrom torch import optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\nimport torchvision.transforms as T\nfrom tqdm import tqdm\n\n# ---------- Reproducibility ----------\nSEED = 25\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\ntorch.cuda.manual_seed_all(SEED)\n\n# ---------- Device ----------\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(\"Device:\", device)\n\n# GPU speed tricks (only helpful if device == cuda)\nif device == \"cuda\":\n    torch.backends.cudnn.benchmark = True  # faster convs for fixed image sizes\n\n# ---------- Training config ----------\nDEBUG = False            # True = very fast test run (few images)\nIMG_SIZE = 256           # use 256 for speed; later you can set 512\nBATCH_SIZE = 16 if device==\"cuda\" else 2\nEPOCHS = 8               # start with 5-8; later increase\nLEARNING_RATE = 3e-4\n\nprint(\"IMG_SIZE:\", IMG_SIZE, \"| BATCH_SIZE:\", BATCH_SIZE, \"| EPOCHS:\", EPOCHS, \"| DEBUG:\", DEBUG)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-06T11:22:39.986900Z","iopub.execute_input":"2026-03-06T11:22:39.987182Z","iopub.status.idle":"2026-03-06T11:22:48.549596Z","shell.execute_reply.started":"2026-03-06T11:22:39.987150Z","shell.execute_reply":"2026-03-06T11:22:48.548850Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 1) EXTRACT DATA (no manual download needed)\n# ============================================================\nDATASET_DIR = \"/kaggle/input/competitions/carvana-image-masking-challenge/\"\nWORKING_DIR = \"/kaggle/working/carvana/\"\nos.makedirs(WORKING_DIR, exist_ok=True)\n\nprint(\"Files in competition folder:\")\nprint(os.listdir(DATASET_DIR))\n\ntrain_dir = os.path.join(WORKING_DIR, \"train\")\nmask_dir  = os.path.join(WORKING_DIR, \"train_masks\")\n\nif not os.path.isdir(train_dir):\n    print(\"Extracting train.zip ...\")\n    with zipfile.ZipFile(os.path.join(DATASET_DIR, \"train.zip\"), \"r\") as z:\n        z.extractall(WORKING_DIR)\n\nif not os.path.isdir(mask_dir):\n    print(\"Extracting train_masks.zip ...\")\n    with zipfile.ZipFile(os.path.join(DATASET_DIR, \"train_masks.zip\"), \"r\") as z:\n        z.extractall(WORKING_DIR)\n\nprint(\"✅ Done extracting.\")\nprint(\"Train images:\", len(os.listdir(train_dir)))\nprint(\"Masks:\", len(os.listdir(mask_dir)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-06T11:22:48.551345Z","iopub.execute_input":"2026-03-06T11:22:48.551667Z","iopub.status.idle":"2026-03-06T11:22:55.032182Z","shell.execute_reply.started":"2026-03-06T11:22:48.551644Z","shell.execute_reply":"2026-03-06T11:22:55.031322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 2) DATA UNDERSTANDING (EDA) \n# ============================================================\n\n# --- List files ---\ntrain_files = sorted(os.listdir(train_dir))\nmask_files  = sorted(os.listdir(mask_dir))\n\nprint(\"Example train file:\", train_files[0])\nprint(\"Example mask file:\", mask_files[0])\n\nprint(\"Unique train filenames:\", len(set(train_files)))\nprint(\"Unique mask filenames :\", len(set(mask_files)))\n\n# --- Check mapping: image -> mask name ---\n# In Carvana: \"abc.jpg\" -> \"abc_mask.gif\"\ndef img_to_mask_name(img_name):\n    base = os.path.splitext(img_name)[0]\n    return base + \"_mask.gif\"\n\n# How many images have masks?\nmissing = []\nfor f in train_files[:1000]:  # sample to be fast; remove [:1000] to check all\n    if img_to_mask_name(f) not in set(mask_files):\n        missing.append(f)\n\nprint(\"Missing masks in first 1000 images:\", len(missing))\nif len(missing) > 0:\n    print(\"Example missing:\", missing[:5])\n\n# --- Check corrupted images quickly ---\ndef is_image_ok(path):\n    try:\n        with Image.open(path) as im:\n            im.verify()  # checks file integrity\n        return True\n    except Exception:\n        return False\n\n# sample check\nsample_check = 200 if DEBUG else 1000\nbad_imgs = 0\nbad_masks = 0\n\nfor f in random.sample(train_files, sample_check):\n    if not is_image_ok(os.path.join(train_dir, f)):\n        bad_imgs += 1\n    if not is_image_ok(os.path.join(mask_dir, img_to_mask_name(f))):\n        bad_masks += 1\n\nprint(f\"Corrupted images (sample {sample_check}):\", bad_imgs)\nprint(f\"Corrupted masks  (sample {sample_check}):\", bad_masks)\n\n# --- Inspect dimensions of a few ---\ndef get_size(path):\n    with Image.open(path) as im:\n        return im.size  # (W,H)\n\nsizes = [get_size(os.path.join(train_dir, f)) for f in random.sample(train_files, 30)]\nprint(\"Some image sizes:\", sizes[:5])\nprint(\"Unique sizes in sample:\", len(set(sizes)))\n\n# --- Visualize a few image+mask pairs ---\ndef show_samples(n=3):\n    idxs = random.sample(range(len(train_files)), n)\n    for i in idxs:\n        img_name = train_files[i]\n        mask_name = img_to_mask_name(img_name)\n\n        img = Image.open(os.path.join(train_dir, img_name)).convert(\"RGB\")\n        mask = Image.open(os.path.join(mask_dir, mask_name)).convert(\"L\")\n\n        img_np = np.array(img)\n        mask_np = np.array(mask)\n\n        # mask coverage (how much foreground)\n        coverage = (mask_np > 0).mean()\n\n        plt.figure(figsize=(12,4))\n        plt.suptitle(f\"{img_name} | mask coverage={coverage:.4f}\")\n\n        plt.subplot(1,3,1)\n        plt.imshow(img_np)\n        plt.axis(\"off\")\n        plt.title(\"Image\")\n\n        plt.subplot(1,3,2)\n        plt.imshow(mask_np, cmap=\"gray\")\n        plt.axis(\"off\")\n        plt.title(\"Mask (raw)\")\n\n        plt.subplot(1,3,3)\n        plt.imshow(img_np)\n        plt.imshow(mask_np, alpha=0.4, cmap=\"Reds\")\n        plt.axis(\"off\")\n        plt.title(\"Overlay\")\n\n        plt.show()\n\nshow_samples(n=4)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-06T12:18:42.527507Z","iopub.execute_input":"2026-03-06T12:18:42.528254Z","iopub.status.idle":"2026-03-06T12:18:47.468966Z","shell.execute_reply.started":"2026-03-06T12:18:42.528208Z","shell.execute_reply":"2026-03-06T12:18:47.468267Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 3) DATASET CLASS (correct masks + fast reading)\n# How the DataLoader feeds batches of images into the U-Net during training\n# ============================================================\n\nclass CarvanaDataset(Dataset):\n    def __init__(self, train_dir, mask_dir, limit=None, size=256):\n        self.train_dir = train_dir\n        self.mask_dir = mask_dir\n        self.size = size\n\n        self.images = sorted(os.listdir(train_dir))\n        if limit is not None:\n            self.images = self.images[:limit]\n\n        self.img_tf = T.Compose([\n            T.Resize((size, size)),\n            T.ToTensor(),\n        ])\n\n        # IMPORTANT: NEAREST for mask resize (keeps labels)\n        self.mask_tf = T.Compose([\n            T.Resize((size, size), interpolation=T.InterpolationMode.NEAREST),\n            T.ToTensor(),\n        ])\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.train_dir, img_name)\n\n        base = os.path.splitext(img_name)[0]\n        mask_name = base + \"_mask.gif\"\n        mask_path = os.path.join(self.mask_dir, mask_name)\n\n        img = Image.open(img_path).convert(\"RGB\")\n        mask = Image.open(mask_path).convert(\"L\")\n\n        img = self.img_tf(img)\n        mask = self.mask_tf(mask)\n\n        # Binary mask 0/1\n        mask = (mask > 0.5).float()\n\n        return img, mask, img_name\n\n\n# Use limit in debug mode\nlimit = 400 if DEBUG else None\ndataset = CarvanaDataset(train_dir, mask_dir, limit=limit, size=IMG_SIZE)\n\nprint(\"Dataset size:\", len(dataset))\nimg, mask, name = dataset[0]\nprint(\"One sample -> img:\", img.shape, \"mask:\", mask.shape, \"name:\", name)\nprint(\"Mask unique values:\", torch.unique(mask))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-06T11:23:08.200841Z","iopub.execute_input":"2026-03-06T11:23:08.201076Z","iopub.status.idle":"2026-03-06T11:23:08.332349Z","shell.execute_reply.started":"2026-03-06T11:23:08.201053Z","shell.execute_reply":"2026-03-06T11:23:08.331583Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 4) SPLIT + DATALOADERS (optimized)\n# divide the dataset into training, validation, and test sets(80, 10,10)\n# ============================================================\n\ngenerator = torch.Generator().manual_seed(SEED)\ntrain_size = int(0.8 * len(dataset))\nvaltest_size = len(dataset) - train_size\n\ntrain_dataset, valtest_dataset = random_split(dataset, [train_size, valtest_size], generator=generator)\n\nval_size = valtest_size // 2\ntest_size = valtest_size - val_size\n\nval_dataset, test_dataset = random_split(valtest_dataset, [val_size, test_size], generator=generator)\n\n# workers\nnum_workers = 2 if device==\"cpu\" else 4\npin_memory = (device==\"cuda\")\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True,\n                          num_workers=num_workers, pin_memory=pin_memory,\n                          persistent_workers=(num_workers > 0))\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False,\n                        num_workers=num_workers, pin_memory=pin_memory,\n                        persistent_workers=(num_workers > 0))\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False,\n                         num_workers=num_workers, pin_memory=pin_memory,\n                         persistent_workers=(num_workers > 0))\n\nprint(\"Train/Val/Test:\", len(train_dataset), len(val_dataset), len(test_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-06T11:23:08.333392Z","iopub.execute_input":"2026-03-06T11:23:08.333924Z","iopub.status.idle":"2026-03-06T11:23:08.342295Z","shell.execute_reply.started":"2026-03-06T11:23:08.333898Z","shell.execute_reply":"2026-03-06T11:23:08.341558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 5) MODEL\n# ============================================================\n\nclass DoubleConv(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super().__init__()\n        self.conv_op = nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, 3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_channels, out_channels, 3, padding=1),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.conv_op(x)\n\nclass DownSample(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super().__init__()\n        self.conv = DoubleConv(in_channels, out_channels)\n        self.pool = nn.MaxPool2d(2,2)\n\n    def forward(self, x):\n        down = self.conv(x)\n        p = self.pool(down)\n        return down, p\n\nclass UpSample(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super().__init__()\n        self.up = nn.ConvTranspose2d(in_channels, in_channels//2, 2,2)\n        self.conv = DoubleConv(in_channels, out_channels)\n\n    def forward(self, x1, x2):\n        x1 = self.up(x1)\n        x = torch.cat([x1, x2], dim=1)\n        return self.conv(x)\n\nclass UNet(nn.Module):\n    def __init__(self, in_channels=3, num_classes=1):\n        super().__init__()\n        self.down1 = DownSample(in_channels, 64)\n        self.down2 = DownSample(64, 128)\n        self.down3 = DownSample(128, 256)\n        self.down4 = DownSample(256, 512)\n        self.bottleneck = DoubleConv(512, 1024)\n        self.up1 = UpSample(1024, 512)\n        self.up2 = UpSample(512, 256)\n        self.up3 = UpSample(256, 128)\n        self.up4 = UpSample(128, 64)\n        self.out = nn.Conv2d(64, num_classes, 1)\n\n    def forward(self, x):\n        d1, p1 = self.down1(x)\n        d2, p2 = self.down2(p1)\n        d3, p3 = self.down3(p2)\n        d4, p4 = self.down4(p3)\n        b = self.bottleneck(p4)\n        u1 = self.up1(b, d4)\n        u2 = self.up2(u1, d3)\n        u3 = self.up3(u2, d2)\n        u4 = self.up4(u3, d1)\n        return self.out(u4)\n\nmodel = UNet(3,1).to(device)\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE)\n\ndef dice_coefficient(logits, target, eps=1e-7):\n    probs = torch.sigmoid(logits)\n    preds = (probs > 0.5).float()\n    intersection = (preds * target).sum(dim=(1,2,3))\n    union = preds.sum(dim=(1,2,3)) + target.sum(dim=(1,2,3))\n    dice = (2*intersection + eps) / (union + eps)\n    return dice.mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-06T11:23:08.343345Z","iopub.execute_input":"2026-03-06T11:23:08.343593Z","iopub.status.idle":"2026-03-06T11:23:08.757186Z","shell.execute_reply.started":"2026-03-06T11:23:08.343571Z","shell.execute_reply":"2026-03-06T11:23:08.756393Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 6) TRAINING (FAST: AMP on GPU)\n# ============================================================\n\nscaler = torch.cuda.amp.GradScaler(enabled=(device==\"cuda\"))\n\ntrain_losses, train_dices = [], []\nval_losses, val_dices = [], []\n\nfor epoch in range(EPOCHS):\n    # ---- TRAIN ----\n    model.train()\n    running_loss = 0.0\n    running_dice = 0.0\n\n    pbar = tqdm(train_loader, desc=f\"Train {epoch+1}/{EPOCHS}\", leave=False)\n    for imgs, masks, _ in pbar:\n        imgs = imgs.to(device, non_blocking=True)\n        masks = masks.to(device, non_blocking=True)\n\n        optimizer.zero_grad(set_to_none=True)\n\n        with torch.cuda.amp.autocast(enabled=(device==\"cuda\")):\n            logits = model(imgs)\n            loss = criterion(logits, masks)\n            dice = dice_coefficient(logits, masks)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        running_loss += loss.item()\n        running_dice += dice.item()\n        pbar.set_postfix(loss=loss.item(), dice=dice.item())\n\n    train_loss = running_loss / len(train_loader)\n    train_dice = running_dice / len(train_loader)\n    train_losses.append(train_loss)\n    train_dices.append(train_dice)\n\n    # ---- VALIDATION ----\n    model.eval()\n    running_loss = 0.0\n    running_dice = 0.0\n\n    with torch.no_grad():\n        for imgs, masks, _ in tqdm(val_loader, desc=f\"Val {epoch+1}/{EPOCHS}\", leave=False):\n            imgs = imgs.to(device, non_blocking=True)\n            masks = masks.to(device, non_blocking=True)\n\n            logits = model(imgs)\n            loss = criterion(logits, masks)\n            dice = dice_coefficient(logits, masks)\n\n            running_loss += loss.item()\n            running_dice += dice.item()\n\n    val_loss = running_loss / len(val_loader)\n    val_dice = running_dice / len(val_loader)\n    val_losses.append(val_loss)\n    val_dices.append(val_dice)\n\n    print(\"-\"*50)\n    print(f\"Epoch {epoch+1}/{EPOCHS}\")\n    print(f\"Train Loss: {train_loss:.5f} | Train Dice: {train_dice:.5f}\")\n    print(f\"Val   Loss: {val_loss:.5f} | Val   Dice: {val_dice:.5f}\")\n    print(\"-\"*50)\n\n# Save checkpoint\nckpt_path = os.path.join(WORKING_DIR, \"my_checkpoint.pth\")\ntorch.save(model.state_dict(), ckpt_path)\nprint(\"✅ Saved:\", ckpt_path)\n\n# Plot curves\nepochs_list = list(range(1, EPOCHS+1))\nplt.figure(figsize=(12,5))\n\nplt.subplot(1,2,1)\nplt.plot(epochs_list, train_losses, label=\"Train Loss\")\nplt.plot(epochs_list, val_losses, label=\"Val Loss\")\nplt.title(\"Loss\")\nplt.xlabel(\"Epoch\")\nplt.grid(True)\nplt.legend()\n\nplt.subplot(1,2,2)\nplt.plot(epochs_list, train_dices, label=\"Train Dice\")\nplt.plot(epochs_list, val_dices, label=\"Val Dice\")\nplt.title(\"Dice\")\nplt.xlabel(\"Epoch\")\nplt.grid(True)\nplt.legend()\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-06T11:23:08.759187Z","iopub.execute_input":"2026-03-06T11:23:08.759737Z","iopub.status.idle":"2026-03-06T11:37:16.472165Z","shell.execute_reply.started":"2026-03-06T11:23:08.759714Z","shell.execute_reply":"2026-03-06T11:37:16.471182Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 7) TEST + VISUALIZE PREDICTIONS\n# ============================================================\n\ntrained_model = UNet(3,1).to(device)\ntrained_model.load_state_dict(torch.load(ckpt_path, map_location=device))\ntrained_model.eval()\n\ntest_loss, test_dice = 0.0, 0.0\nwith torch.no_grad():\n    for imgs, masks, _ in tqdm(test_loader, desc=\"Testing\"):\n        imgs = imgs.to(device, non_blocking=True)\n        masks = masks.to(device, non_blocking=True)\n\n        logits = trained_model(imgs)\n        loss = criterion(logits, masks)\n        dice = dice_coefficient(logits, masks)\n\n        test_loss += loss.item()\n        test_dice += dice.item()\n\ntest_loss /= len(test_loader)\ntest_dice /= len(test_loader)\nprint(f\"✅ Test Loss: {test_loss:.5f}\")\nprint(f\"✅ Test Dice: {test_dice:.5f}\")\n\n\n# --- show random predictions ---\ndef show_random_predictions(dataset_subset, model, device, n=5):\n    model.eval()\n    idxs = np.random.choice(len(dataset_subset), n, replace=False)\n\n    for i in idxs:\n        img, mask, img_name = dataset_subset[i]\n        img_in = img.unsqueeze(0).to(device)\n\n        with torch.no_grad():\n            logits = model(img_in)\n            probs = torch.sigmoid(logits).squeeze().cpu().numpy()\n            pred = (probs > 0.5).astype(np.float32)\n\n        img_np = img.permute(1,2,0).cpu().numpy()\n        mask_np = mask.squeeze(0).cpu().numpy()\n\n        plt.figure(figsize=(14,4))\n        plt.suptitle(img_name)\n\n        plt.subplot(1,3,1)\n        plt.imshow(img_np)\n        plt.title(\"Image\")\n        plt.axis(\"off\")\n\n        plt.subplot(1,3,2)\n        plt.imshow(pred, cmap=\"gray\")\n        plt.title(\"Prediction\")\n        plt.axis(\"off\")\n\n        plt.subplot(1,3,3)\n        plt.imshow(mask_np, cmap=\"gray\")\n        plt.title(\"Ground Truth\")\n        plt.axis(\"off\")\n\n        plt.show()\n\nshow_random_predictions(test_dataset, trained_model, device, n=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-06T11:37:16.474083Z","iopub.execute_input":"2026-03-06T11:37:16.474473Z","iopub.status.idle":"2026-03-06T11:37:29.616604Z","shell.execute_reply.started":"2026-03-06T11:37:16.474434Z","shell.execute_reply":"2026-03-06T11:37:29.615757Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}