{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":18647,"databundleVersionId":1126921},{"sourceType":"datasetVersion","sourceId":16159083,"datasetId":10361343,"databundleVersionId":17134697},{"sourceType":"modelInstanceVersion","sourceId":863235,"databundleVersionId":17138767,"modelInstanceId":656094,"modelId":668071},{"sourceType":"kernelVersion","sourceId":32832152}],"dockerImageVersionId":31329,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install \"/kaggle/input/datasets/teunvang/imagecodecs/imagecodecs-2026.3.6-cp311-abi3-manylinux_2_28_x86_64.whl\"","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-06-09T16:54:37.496547Z","iopub.execute_input":"2026-06-09T16:54:37.497134Z","iopub.status.idle":"2026-06-09T16:54:42.779792Z","shell.execute_reply.started":"2026-06-09T16:54:37.497105Z","shell.execute_reply":"2026-06-09T16:54:42.779125Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, random, cv2\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport timm\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import cohen_kappa_score\nfrom scipy import stats\nimport albumentations as A\nimport skimage.io\nimport imagecodecs\nfrom albumentations.pytorch import ToTensorV2\nimport warnings\nimport tifffile\nwarnings.filterwarnings(\"ignore\")\n\nsz = 128  # Tile size (e.g., 128 or 256)\nN = 16    # Number of tiles (e.g., 12, 16, or 36)\nbs = 8    # Batch size\nnfolds = 3\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\n# Directories\ntest_csv = '/kaggle/input/competitions/prostate-cancer-grade-assessment/test.csv'\ntest_dir = '/kaggle/input/competitions/prostate-cancer-grade-assessment/test_images/'\nweights_dir = '/kaggle/input/models/teunvang/aimi-v1/pytorch/default/6' ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T16:56:56.657639Z","iopub.execute_input":"2026-06-09T16:56:56.658234Z","iopub.status.idle":"2026-06-09T16:56:56.667313Z","shell.execute_reply.started":"2026-06-09T16:56:56.658193Z","shell.execute_reply":"2026-06-09T16:56:56.666474Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AdaptiveConcatPool2d(nn.Module):\n    def forward(self, x):\n        avg = nn.functional.adaptive_avg_pool2d(x, 1)\n        mx  = nn.functional.adaptive_max_pool2d(x, 1)\n        return torch.cat([avg, mx], dim=1)\n\nclass Model(nn.Module):\n    def __init__(self, backbone='efficientnet_b4', n_classes=5, pretrained=True):\n        super().__init__()\n        self.enc = timm.create_model(backbone, pretrained=pretrained, num_classes=0)\n        nc = self.enc.num_features\n        self.pool = nn.Sequential(\n            AdaptiveConcatPool2d(),\n            nn.Flatten(),\n            nn.Linear(nc*2, 512),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.4) \n        )\n\n        self.cls_head = nn.Linear(512, n_classes)\n\n        self.reg_head = nn.Linear(512, 1)\n\n    def forward(self, x):\n        x = self.enc.forward_features(x)\n        x = self.pool(x)\n\n        logits = self.cls_head(x)\n\n        raw_reg = self.reg_head(x)\n        reg_out = torch.sigmoid(raw_reg) * (6.0 - (-1.0)) + (-1.0)\n        return logits, reg_out.squeeze(-1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T16:55:29.664588Z","iopub.execute_input":"2026-06-09T16:55:29.665124Z","iopub.status.idle":"2026-06-09T16:55:29.672561Z","shell.execute_reply.started":"2026-06-09T16:55:29.665097Z","shell.execute_reply":"2026-06-09T16:55:29.671801Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_tiles_from_tiff(img_path, n_tiles=12, tile_size=128):\n    with tifffile.TiffFile(img_path) as tif:\n        if len(tif.series[0].levels) > 1:\n            img = tif.series[0].levels[-1].asarray()\n        else:\n            img = tif.asarray()\n            \n    img = np.array(img)\n    \n    h, w, c = img.shape\n    pad_h = (tile_size - h % tile_size) % tile_size\n    pad_w = (tile_size - w % tile_size) % tile_size\n    img = np.pad(img, [\n        [pad_h // 2, pad_h - pad_h // 2], \n        [pad_w // 2, pad_w - pad_w // 2], \n        [0, 0]\n    ], constant_values=255)\n    \n    img = img.reshape(img.shape[0] // tile_size, tile_size, \n                      img.shape[1] // tile_size, tile_size, 3)\n    img = img.transpose(0, 2, 1, 3, 4).reshape(-1, tile_size, tile_size, 3)\n    \n    if len(img) < n_tiles:\n        img = np.pad(img, [[0, n_tiles - len(img)], [0, 0], [0, 0], [0, 0]], constant_values=255)\n        \n    tissue_sums = img.reshape(img.shape[0], -1).sum(axis=-1)\n    idxs = np.argsort(tissue_sums)[:n_tiles]\n    \n    img = img[idxs]\n    \n    n_cols = 4\n    n_rows = int(np.ceil(n_tiles / n_cols))\n    \n    rows = []\n    for r in range(n_rows):\n        row_tiles = img[r*n_cols : (r+1)*n_cols]\n        while len(row_tiles) < n_cols:\n             row_tiles = np.append(row_tiles, [np.ones((tile_size, tile_size, 3), dtype=np.uint8) * 255], axis=0)\n        rows.append(np.concatenate(row_tiles, axis=1))\n    \n    final_image = np.concatenate(rows, axis=0)\n    return final_image\n\nclass PANDATestDataset(Dataset):\n    def __init__(self, df, test_dir, transform=None):\n        self.df = df\n        self.test_dir = test_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_id = self.df.iloc[idx].image_id\n        img_path = os.path.join(self.test_dir, f'{img_id}.tiff')\n        \n        image = get_tiles_from_tiff(img_path)\n\n        if self.transform:\n            image = self.transform(image=image)['image']\n\n        return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T16:55:29.674454Z","iopub.execute_input":"2026-06-09T16:55:29.674798Z","iopub.status.idle":"2026-06-09T16:55:29.725459Z","shell.execute_reply.started":"2026-06-09T16:55:29.67474Z","shell.execute_reply":"2026-06-09T16:55:29.724894Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Test time augmentation, averaging predictions across multiple image augmentations\ndef tta_predict(model, x):\n    preds = []\n    \n    # Original\n    preds.append(model(x)[0])\n    \n    # Horizontal flip\n    preds.append(model(torch.flip(x, dims=[-1]))[0])\n\n    # Vertical flip\n    preds.append(model(torch.flip(x, dims=[-2]))[0])\n    \n    # Both flips\n    preds.append(model(torch.flip(x, dims=[-1, -2]))[0])\n    \n    return torch.stack(preds).mean(0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T16:56:27.678214Z","iopub.execute_input":"2026-06-09T16:56:27.678505Z","iopub.status.idle":"2026-06-09T16:56:27.684422Z","shell.execute_reply.started":"2026-06-09T16:56:27.678478Z","shell.execute_reply":"2026-06-09T16:56:27.6834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_transforms = A.Compose([\n    A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    ToTensorV2()\n])\n\n# Dummy check for hidden test set\nif os.path.exists(test_dir):\n    df_test = pd.read_csv(test_csv)\nelse:\n    print(\"Test set not found. Using train set for a dummy run...\")\n    test_dir = '/kaggle/input/competitions/prostate-cancer-grade-assessment/train_images/'\n    df_test = pd.read_csv('/kaggle/input/competitions/prostate-cancer-grade-assessment/train.csv').head(3)\n\ntest_ds = PANDATestDataset(df_test, test_dir, transform=val_transforms)\ntest_dl = DataLoader(test_ds, batch_size=bs, shuffle=False, num_workers=2)\n\n# Ensemble all folds\nmodel = Model(pretrained=False)\nall_fold_logits = []\n\nfor fold_i in range(0, nfolds):\n    weight_path = os.path.join(weights_dir, f'model_fold{fold_i}.pth')\n    model.load_state_dict(torch.load(weight_path, map_location=device))\n    model.to(device)\n    model.eval()\n\n    fold_logits = []\n    with torch.no_grad():\n        for x in test_dl:\n            x = x.to(device)\n            with torch.cuda.amp.autocast():\n                logits = tta_predict(model, x)\n            fold_logits.append(logits.cpu())\n    all_fold_logits.append(torch.cat(fold_logits))\n\navg_logits = torch.stack(all_fold_logits).mean(0)\npreds = (avg_logits.sigmoid() > 0.5).sum(1).numpy().astype(int)\n\n# Submit\ndf_test['isup_grade'] = preds\nsubmission = df_test[['image_id', 'isup_grade']]\nsubmission.to_csv('submission.csv', index=False)\nprint(\"Submission saved successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T16:56:59.410813Z","iopub.execute_input":"2026-06-09T16:56:59.411561Z","iopub.status.idle":"2026-06-09T16:57:02.353897Z","shell.execute_reply.started":"2026-06-09T16:56:59.411527Z","shell.execute_reply":"2026-06-09T16:57:02.353098Z"}},"outputs":[],"execution_count":null}]}