{"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":"none","dataSources":[{"sourceId":117682,"databundleVersionId":15062069,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14671031,"sourceType":"datasetVersion","datasetId":9372669},{"sourceId":14671778,"sourceType":"datasetVersion","datasetId":9373182}],"dockerImageVersionId":31259,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n# Model Definition\nclass DoubleConv(nn.Module):\n    def __init__(self, in_channels, out_channels, groups=8):\n        super().__init__()\n        self.conv = nn.Sequential(\n            nn.Conv3d(in_channels, out_channels, kernel_size=3, padding=1, bias=False),\n            nn.GroupNorm(groups, out_channels),\n            nn.ReLU(inplace=True),\n\n            nn.Conv3d(out_channels, out_channels, kernel_size=3, padding=1, bias=False),\n            nn.GroupNorm(groups, out_channels),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.conv(x)\n\nclass Down(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super().__init__()\n        self.down = nn.Sequential(\n            nn.MaxPool3d(2),\n            DoubleConv(in_channels, out_channels)\n        )\n\n    def forward(self, x):\n        return self.down(x)\n\n\nclass Up(nn.Module):\n    def __init__(self, in_channels, out_channels, trilinear=True):\n        super().__init__()\n\n        if trilinear:\n            self.up = nn.Upsample(scale_factor=2, mode='trilinear', align_corners=True)\n            self.conv = DoubleConv(in_channels, out_channels)\n        else:\n            self.up = nn.ConvTranspose3d(\n                in_channels // 2, in_channels // 2,\n                kernel_size=2, stride=2\n            )\n            self.conv = DoubleConv(in_channels, out_channels)\n\n    def forward(self, x1, x2):\n        x1 = self.up(x1)\n\n        diffD = x2.size(2) - x1.size(2)\n        diffH = x2.size(3) - x1.size(3)\n        diffW = x2.size(4) - x1.size(4)\n\n        x1 = F.pad(\n            x1,\n            [\n                diffW // 2, diffW - diffW // 2,\n                diffH // 2, diffH - diffH // 2,\n                diffD // 2, diffD - diffD // 2,\n            ]\n        )\n\n        x = torch.cat([x2, x1], dim=1)\n        return self.conv(x)\n\nclass OutConv(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super().__init__()\n        self.conv = nn.Conv3d(in_channels, out_channels, kernel_size=1)\n\n    def forward(self, x):\n        return self.conv(x)\n\nclass UNet3D(nn.Module):\n    def __init__(\n        self,\n        in_channels=1,\n        num_classes=1,\n        base_channels=8,\n        trilinear=True\n    ):\n        super().__init__()\n\n        self.inc = DoubleConv(in_channels, base_channels)\n        self.down1 = Down(base_channels, base_channels * 2)\n        self.down2 = Down(base_channels * 2, base_channels * 4)\n        self.down3 = Down(base_channels * 4, base_channels * 8)\n        self.down4 = Down(base_channels * 8, base_channels * 16)\n\n        self.up1 = Up(base_channels * 16 + base_channels * 8, base_channels * 8, trilinear)\n        self.up2 = Up(base_channels * 8 + base_channels * 4, base_channels * 4, trilinear)\n        self.up3 = Up(base_channels * 4 + base_channels * 2, base_channels * 2, trilinear)\n        self.up4 = Up(base_channels * 2 + base_channels, base_channels, trilinear)\n\n        self.outc = OutConv(base_channels, num_classes)\n\n    def forward(self, x):\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        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        return self.outc(x)\n\nif __name__ == \"__main__\":\n    model = UNet3D(in_channels=1, num_classes=1)\n    x = torch.randn(1, 1, 64, 128, 128)\n    y = model(x)\n    print(y.shape)\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-30T06:19:04.900742Z","iopub.execute_input":"2026-01-30T06:19:04.901267Z","iopub.status.idle":"2026-01-30T06:19:09.709466Z","shell.execute_reply.started":"2026-01-30T06:19:04.901220Z","shell.execute_reply":"2026-01-30T06:19:09.708377Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nckpt = torch.load(\"/kaggle/input/vc-ckpt/best_model.pth\", map_location=device)\nmodel.load_state_dict(ckpt)\nmodel.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-30T06:19:09.711704Z","iopub.execute_input":"2026-01-30T06:19:09.712766Z","iopub.status.idle":"2026-01-30T06:19:09.751679Z","shell.execute_reply.started":"2026-01-30T06:19:09.712730Z","shell.execute_reply":"2026-01-30T06:19:09.750471Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport numpy as np\n# !pip install --upgrade --quiet imagecodecs\n!pip install /kaggle/input/vsc-offline-installer/imagecodecs-2026.1.1-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl\n\nimport tifffile as tiff\nimport imagecodecs\nimport zipfile\nimport os\n\ndef scale_intensity(volume, a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0):\n    volume = np.clip(volume, a_min, a_max)\n    volume = (volume - a_min) / (a_max - a_min + 1e-8)\n    return (volume * (b_max - b_min) + b_min).astype(np.float32)\n\n\ndef predict_3d_volume(model, tif_path):\n    vol = tiff.imread(tif_path) \n    vol = scale_intensity(vol)\n\n    vol = torch.from_numpy(vol).unsqueeze(0).unsqueeze(0).to(device)\n\n    with torch.no_grad():\n        out = model(vol)\n\n    return out.squeeze(0).cpu().numpy()\n\n\ntest_dir = \"/kaggle/input/vesuvius-challenge-surface-detection/test_images\"\nsubmission_path = \"/kaggle/working/submission.zip\"\n\nwith zipfile.ZipFile(submission_path, \"w\") as zipf:\n    for fname in os.listdir(test_dir):\n        if fname.endswith(\".tif\"):\n            tif_path = os.path.join(test_dir, fname)\n\n            prediction = predict_3d_volume(model, tif_path)\n            prediction = prediction.astype(np.uint8)\n\n            tmp_path = os.path.join(\"/kaggle/working\", fname)\n            tiff.imwrite(tmp_path, prediction)\n\n            zipf.write(tmp_path, arcname=fname)\n\nprint(f\"Submission ready: {submission_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-30T06:19:09.753120Z","iopub.execute_input":"2026-01-30T06:19:09.753449Z","iopub.status.idle":"2026-01-30T06:19:59.080795Z","shell.execute_reply.started":"2026-01-30T06:19:09.753420Z","shell.execute_reply":"2026-01-30T06:19:59.079318Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}