{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":117682,"databundleVersionId":14443416,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport glob\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nfrom tqdm import tqdm\nimport cv2\nimport zipfile\nimport shutil\nimport random\nfrom torch.amp import autocast, GradScaler","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T15:43:47.384314Z","iopub.execute_input":"2025-12-05T15:43:47.385055Z","iopub.status.idle":"2025-12-05T15:43:47.389690Z","shell.execute_reply.started":"2025-12-05T15:43:47.385022Z","shell.execute_reply":"2025-12-05T15:43:47.389013Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#CONFIGURATION\nclass Config:\n    DATA_DIR = Path(\"/kaggle/input/vesuvius-challenge-surface-detection\")\n    TRAIN_IMAGES = DATA_DIR / \"train_images\"\n    TEST_IMAGES = DATA_DIR / \"test_images\"\n    TRAIN_LABELS = DATA_DIR / \"train_labels\"\n    OUTPUT_ZIP = \"submission.zip\"\n\n    IMAGE_SIZE = 256\n    BATCH_SIZE = 16\n    EPOCHS = 10\n    LEARNING_RATE = 1e-4\n    DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\n    THRESHOLD = 0.5\n\n    SLICES_PER_VOLUME = 30 \n    VAL_SPLIT = 0.2 \n    NUM_WORKERS = 2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T15:43:52.946623Z","iopub.execute_input":"2025-12-05T15:43:52.947136Z","iopub.status.idle":"2025-12-05T15:43:52.951934Z","shell.execute_reply.started":"2025-12-05T15:43:52.947115Z","shell.execute_reply":"2025-12-05T15:43:52.951230Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def dice_coef(y_pred, y_true, smooth=1):\n    y_pred = torch.sigmoid(y_pred).view(-1)\n    y_true = y_true.view(-1)\n    intersection = (y_pred * y_true).sum()\n    return (2. * intersection + smooth) / (y_pred.sum() + y_true.sum() + smooth)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T15:38:29.687932Z","iopub.execute_input":"2025-12-05T15:38:29.688176Z","iopub.status.idle":"2025-12-05T15:38:29.692571Z","shell.execute_reply.started":"2025-12-05T15:38:29.688160Z","shell.execute_reply":"2025-12-05T15:38:29.691883Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#DATASET\nclass VesuviusDataset(Dataset):\n    def __init__(self, mode='train', split_type='all'):\n        self.mode = mode\n        self.split_type = split_type \n        \n        csv_file = 'train.csv' if mode == 'train' else 'test.csv'\n        csv_path = Config.DATA_DIR / csv_file\n        \n        self.ids = []\n        if csv_path.exists():\n            df = pd.read_csv(csv_path)\n            all_ids = df['id'].tolist()\n            \n            if mode == 'train':\n                if len(all_ids) <= 1:\n                    self.ids = all_ids\n                else:\n                    split_idx = int(len(all_ids) * (1 - Config.VAL_SPLIT))\n                    if split_type == 'train':\n                        self.ids = all_ids[:split_idx]\n                    elif split_type == 'val':\n                        self.ids = all_ids[split_idx:]\n                    else: \n                        self.ids = all_ids\n            else:\n                self.ids = all_ids\n            \n            if mode == 'train' and split_type == 'train':\n                print(f\"[{mode.upper()}] Training on {len(self.ids)} volumes.\")\n\n    def __len__(self):\n        if self.mode == 'train' and self.split_type == 'train':\n            return len(self.ids) * Config.SLICES_PER_VOLUME\n        return len(self.ids)\n\n    def __getitem__(self, idx):\n        vol_idx = idx % len(self.ids)\n        vol_id = str(self.ids[vol_idx])\n        \n        if self.mode == 'train':\n            img_dir = Config.TRAIN_IMAGES\n            mask_dir = Config.TRAIN_LABELS\n        else:\n            img_dir = Config.TEST_IMAGES\n            mask_dir = None\n\n        img_path = img_dir / f\"{vol_id}.tif\"\n        \n        try:\n            with Image.open(img_path) as img_obj:\n                n_frames = img_obj.n_frames\n                \n                if self.mode == 'train':\n                    pad = int(n_frames * 0.1) \n                    if pad == 0: pad = 1\n                    slice_idx = np.random.randint(pad, max(pad+1, n_frames - pad))\n                    \n                    img_obj.seek(slice_idx)\n                    img = np.array(img_obj).astype('float32')\n                    \n                    mask_path = mask_dir / f\"{vol_id}.tif\"\n                    with Image.open(mask_path) as mask_obj:\n                        mask_obj.seek(slice_idx)\n                        mask = np.array(mask_obj).astype('float32')\n                    \n                    img = cv2.resize(img, (Config.IMAGE_SIZE, Config.IMAGE_SIZE))\n                    mask = cv2.resize(mask, (Config.IMAGE_SIZE, Config.IMAGE_SIZE))\n                    \n                    # Augmentation\n                    if self.split_type == 'train': \n                        if random.random() > 0.5:\n                            img = np.flip(img, axis=1)\n                            mask = np.flip(mask, axis=1)\n                        if random.random() > 0.5:\n                            img = np.flip(img, axis=0)\n                            mask = np.flip(mask, axis=0)\n                        k = random.randint(0, 3)\n                        if k > 0:\n                            img = np.rot90(img, k)\n                            mask = np.rot90(mask, k)\n\n                    img = img / 65535.0\n                    mask[mask == 2] = 0 \n                    mask = mask / 1.0 \n                    \n                    img = img.copy()\n                    mask = mask.copy()\n                    \n                    return torch.tensor(img).unsqueeze(0), torch.tensor(mask).unsqueeze(0)\n                \n                else:\n                    return torch.zeros((1, Config.IMAGE_SIZE, Config.IMAGE_SIZE)), img_path\n\n        except Exception as e:\n            return torch.zeros((1, Config.IMAGE_SIZE, Config.IMAGE_SIZE)), torch.zeros((1, Config.IMAGE_SIZE, Config.IMAGE_SIZE))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T15:43:57.746948Z","iopub.execute_input":"2025-12-05T15:43:57.747652Z","iopub.status.idle":"2025-12-05T15:43:57.758705Z","shell.execute_reply.started":"2025-12-05T15:43:57.747626Z","shell.execute_reply":"2025-12-05T15:43:57.758012Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#RESUNET MODEL\nclass ResidualBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, stride=1):\n        super().__init__()\n        \n        self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_channels)\n        self.relu = nn.ReLU(inplace=True)\n        \n        self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n        \n        # Skip connection\n        self.skip = nn.Sequential()\n        if stride != 1 or in_channels != out_channels:\n            self.skip = nn.Sequential(\n                nn.Conv2d(in_channels, out_channels, 1, stride=stride, bias=False),\n                nn.BatchNorm2d(out_channels)\n            )\n    \n    def forward(self, x):\n        identity = self.skip(x)\n        \n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n        \n        out = self.conv2(out)\n        out = self.bn2(out)\n        \n        out += identity\n        out = self.relu(out)\n        \n        return out\n\n\nclass ResUNet(nn.Module):\n    def __init__(self, in_channels=1, out_channels=1):\n        super().__init__()\n        \n        # Encoder\n        self.enc1 = ResidualBlock(in_channels, 64)\n        self.enc2 = ResidualBlock(64, 128, stride=2)\n        self.enc3 = ResidualBlock(128, 256, stride=2)\n        self.enc4 = ResidualBlock(256, 512, stride=2)\n        \n        # Bridge\n        self.bridge = ResidualBlock(512, 1024, stride=2)\n        \n        # Decoder\n        self.up4 = nn.ConvTranspose2d(1024, 512, 2, stride=2)\n        self.dec4 = ResidualBlock(1024, 512)\n        \n        self.up3 = nn.ConvTranspose2d(512, 256, 2, stride=2)\n        self.dec3 = ResidualBlock(512, 256)\n        \n        self.up2 = nn.ConvTranspose2d(256, 128, 2, stride=2)\n        self.dec2 = ResidualBlock(256, 128)\n        \n        self.up1 = nn.ConvTranspose2d(128, 64, 2, stride=2)\n        self.dec1 = ResidualBlock(128, 64)\n        \n        self.out = nn.Conv2d(64, out_channels, 1)\n    \n    def forward(self, x):\n        e1 = self.enc1(x)\n        e2 = self.enc2(e1)\n        e3 = self.enc3(e2)\n        e4 = self.enc4(e3)\n        \n        b = self.bridge(e4)\n        \n        d4 = self.up4(b)\n        d4 = torch.cat([d4, e4], dim=1)\n        d4 = self.dec4(d4)\n        \n        d3 = self.up3(d4)\n        d3 = torch.cat([d3, e3], dim=1)\n        d3 = self.dec3(d3)\n        \n        d2 = self.up2(d3)\n        d2 = torch.cat([d2, e2], dim=1)\n        d2 = self.dec2(d2)\n        \n        d1 = self.up1(d2)\n        d1 = torch.cat([d1, e1], dim=1)\n        d1 = self.dec1(d1)\n        \n        out = self.out(d1)\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T15:44:00.777131Z","iopub.execute_input":"2025-12-05T15:44:00.777839Z","iopub.status.idle":"2025-12-05T15:44:00.788910Z","shell.execute_reply.started":"2025-12-05T15:44:00.777812Z","shell.execute_reply":"2025-12-05T15:44:00.788159Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#TRAINING\nprint(f\" Vesuvius Challenge \")\nprint(f\"Using Device: {Config.DEVICE}\")\nif Config.DEVICE == 'cuda':\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n    print(\"Mixed Precision (AMP): ENABLED\")\n\ntif_output_dir = Path(\"submission_tifs\")\nif tif_output_dir.exists(): \n    shutil.rmtree(tif_output_dir)\ntif_output_dir.mkdir(exist_ok=True)\n\n#TRAINING LOOP\ntrain_csv = Config.DATA_DIR / 'train.csv'\nif train_csv.exists():\n    train_ds = VesuviusDataset(mode='train', split_type='train')\n    val_ds = VesuviusDataset(mode='train', split_type='val')\n    \n    if len(train_ds) > 0:\n        train_loader = DataLoader(\n            train_ds, \n            batch_size=Config.BATCH_SIZE, \n            shuffle=True,\n            num_workers=Config.NUM_WORKERS,\n            pin_memory=True if Config.DEVICE == 'cuda' else False\n        )\n        \n        val_loader = DataLoader(\n            val_ds, \n            batch_size=Config.BATCH_SIZE, \n            shuffle=False,\n            num_workers=2,\n            pin_memory=True if Config.DEVICE == 'cuda' else False\n        ) if len(val_ds) > 0 else None\n        \n        # Initialize model\n        model = ResUNet(in_channels=1, out_channels=1).to(Config.DEVICE)\n        optimizer = optim.Adam(model.parameters(), lr=Config.LEARNING_RATE)\n        criterion = nn.BCEWithLogitsLoss()\n        \n        # FIX: Use old GradScaler syntax without parameters\n        scaler = GradScaler() if Config.DEVICE == 'cuda' else None\n        \n        print(\"\\n--- Training Started (ResUNet) ---\")\n        best_dice = 0.0\n        \n        for epoch in range(Config.EPOCHS):\n            model.train()\n            epoch_loss = 0\n            epoch_dice = 0\n            \n            pbar = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{Config.EPOCHS}\")\n            for imgs, masks in pbar:\n                imgs, masks = imgs.to(Config.DEVICE), masks.to(Config.DEVICE)\n                \n                optimizer.zero_grad()\n                \n                # FIX: Use old autocast syntax\n                if Config.DEVICE == 'cuda' and scaler is not None:\n                    with autocast('cuda'):\n                        outputs = model(imgs)\n                        loss = criterion(outputs, masks)\n                    \n                    scaler.scale(loss).backward()\n                    scaler.step(optimizer)\n                    scaler.update()\n                else:\n                    outputs = model(imgs)\n                    loss = criterion(outputs, masks)\n                    loss.backward()\n                    optimizer.step()\n                \n                with torch.no_grad():\n                    dice = dice_coef(outputs, masks)\n                \n                epoch_loss += loss.item()\n                epoch_dice += dice.item()\n                \n                pbar.set_postfix({'loss': f\"{loss.item():.4f}\", 'dice': f\"{dice.item():.4f}\"})\n            \n            avg_train_loss = epoch_loss / len(train_loader)\n            avg_train_dice = epoch_dice / len(train_loader)\n            print(f\"   Train Loss: {avg_train_loss:.4f} | Train Dice: {avg_train_dice:.4f}\")\n            \n            if val_loader:\n                model.eval()\n                val_loss = 0\n                val_dice = 0\n                with torch.no_grad():\n                    for imgs, masks in val_loader:\n                        imgs, masks = imgs.to(Config.DEVICE), masks.to(Config.DEVICE)\n                        \n                        if Config.DEVICE == 'cuda':\n                            with autocast('cuda'):\n                                outputs = model(imgs)\n                                loss = criterion(outputs, masks)\n                        else:\n                            outputs = model(imgs)\n                            loss = criterion(outputs, masks)\n                            \n                        dice = dice_coef(outputs, masks)\n                        val_loss += loss.item()\n                        val_dice += dice.item()\n                \n                avg_val_loss = val_loss / len(val_loader)\n                avg_val_dice = val_dice / len(val_loader)\n                print(f\"   Val Loss:   {avg_val_loss:.4f} | Val Dice:   {avg_val_dice:.4f}\")\n                \n                if avg_val_dice > best_dice:\n                    best_dice = avg_val_dice\n                    torch.save(model.state_dict(), \"vesuvius_resunet_best.pth\")\n                    print(f\"   ✓ Best model saved! (Dice: {best_dice:.4f})\")\n            else:\n                torch.save(model.state_dict(), \"vesuvius_resunet.pth\")\n\nelse:\n    print(\"[WARNING] train.csv not found.\")\n    model = ResUNet().to(Config.DEVICE)\n    torch.save(model.state_dict(), \"vesuvius_resunet.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T15:44:04.027067Z","iopub.execute_input":"2025-12-05T15:44:04.027657Z","iopub.status.idle":"2025-12-05T20:59:11.306000Z","shell.execute_reply.started":"2025-12-05T15:44:04.027631Z","shell.execute_reply":"2025-12-05T20:59:11.305239Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#INFERENCE\nprint(\"\\n--- Starting Inference ---\")\nmodel = ResUNet().to(Config.DEVICE)\nif os.path.exists(\"vesuvius_resunet_best.pth\"):\n    model.load_state_dict(torch.load(\"vesuvius_resunet_best.pth\"))\n    print(\"Loaded best model\")\nelif os.path.exists(\"vesuvius_resunet.pth\"):\n    model.load_state_dict(torch.load(\"vesuvius_resunet.pth\"))\n    print(\"Loaded last model\")\nmodel.eval()\n\nprocessed_count = 0\ntest_csv_path = Config.DATA_DIR / 'test.csv'\n\nif test_csv_path.exists():\n    test_df = pd.read_csv(test_csv_path)\n    test_ids = test_df['id'].tolist()\n    \n    for vol_id in tqdm(test_ids, desc=\"Processing Volumes\"):\n        vol_id = str(vol_id)\n        img_path = Config.TEST_IMAGES / f\"{vol_id}.tif\"\n        \n        if not img_path.exists(): \n            print(f\"Warning: {img_path} not found\")\n            continue\n            \n        try:\n            with Image.open(img_path) as img_obj:\n                n_frames = img_obj.n_frames\n                w, h = img_obj.size\n                \n                # Validate dimensions\n                if w <= 0 or h <= 0:\n                    print(f\"Invalid dimensions for {vol_id}: {w}x{h}\")\n                    continue\n                \n                print(f\"Processing {vol_id}: {n_frames} frames, size {w}x{h}\")\n                pred_volume = np.zeros((n_frames, h, w), dtype=np.uint8)\n                \n                for i in range(n_frames):\n                    img_obj.seek(i)\n                    slice_img = np.array(img_obj).astype('float32')\n                    \n                    # Validate slice dimensions\n                    if slice_img.size == 0:\n                        print(f\"Empty slice at frame {i}\")\n                        continue\n                    \n                    # Resize input\n                    slice_resized = cv2.resize(slice_img, (Config.IMAGE_SIZE, Config.IMAGE_SIZE))\n                    slice_resized = slice_resized / 65535.0\n                    slice_tensor = torch.tensor(slice_resized).unsqueeze(0).unsqueeze(0).float().to(Config.DEVICE)\n                    \n                    # Model prediction\n                    with torch.no_grad():\n                        if Config.DEVICE == 'cuda':\n                            with autocast('cuda'):\n                                output = model(slice_tensor)\n                        else:\n                            output = model(slice_tensor)\n                            \n                        pred = torch.sigmoid(output).cpu().numpy()[0, 0]\n                    \n                    # Validate prediction before resize\n                    if pred.shape != (Config.IMAGE_SIZE, Config.IMAGE_SIZE):\n                        print(f\"Unexpected prediction shape: {pred.shape}\")\n                        continue\n                    \n                    # Ensure pred is contiguous and proper dtype\n                    pred = np.ascontiguousarray(pred, dtype=np.float32)\n                    \n                    # Resize back to original dimensions\n                    pred_full = cv2.resize(pred, (w, h), interpolation=cv2.INTER_LINEAR)\n                    \n                    # Threshold and convert to binary mask\n                    mask_binary = (pred_full > Config.THRESHOLD).astype(np.uint8) * 255\n                    pred_volume[i, :, :] = mask_binary\n            \n            # Save the prediction volume\n            output_path = tif_output_dir / f\"{vol_id}.tif\"\n            save_imgs = [Image.fromarray(pred_volume[i]) for i in range(n_frames)]\n            save_imgs[0].save(\n                output_path, \n                save_all=True, \n                append_images=save_imgs[1:], \n                compression=\"tiff_deflate\"\n            )\n            processed_count += 1\n            print(f\"Successfully processed {vol_id}\")\n            \n        except Exception as e:\n            print(f\"Failed to process {vol_id}: {e}\")\n            import traceback\n            traceback.print_exc()\nelse:\n    print(\"[SIMULATION] Creating dummy output...\")\n    dummy_data = np.zeros((10, 100, 100), dtype=np.uint8)\n    imgs = [Image.fromarray(dummy_data[i]) for i in range(10)]\n    imgs[0].save(tif_output_dir / \"test_dummy.tif\", save_all=True, append_images=imgs[1:])\n    processed_count = 1\n    \nprint(f\"\\nProcessed {processed_count} volumes total\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#SUBMISSION\nif processed_count > 0:\n    tif_files = sorted(tif_output_dir.glob(\"*.tif\"))\n    \n    print(f\"\\nZipping {len(tif_files)} files...\")\n    with zipfile.ZipFile(Config.OUTPUT_ZIP, 'w', zipfile.ZIP_DEFLATED) as zipf:\n        for tif_file in tif_files:\n            zipf.write(tif_file, arcname=tif_file.name)\n    \n    print(f\"\\n=== COMPLETED ===\\nSubmission ready: {Config.OUTPUT_ZIP}\")\nelse:\n    print(\"No predictions generated.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}