{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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"},"jupytext":{"cell_metadata_filter":"-all","main_language":"python","notebook_metadata_filter":"-all"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":99552,"databundleVersionId":13762876,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport shutil\nfrom collections import defaultdict\n\nimport pandas as pd\nimport polars as pl\nimport pydicom\n\nimport kaggle_evaluation.rsna_inference_server","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T20:16:04.618248Z","iopub.execute_input":"2025-09-21T20:16:04.618608Z","iopub.status.idle":"2025-09-21T20:16:04.622596Z","shell.execute_reply.started":"2025-09-21T20:16:04.618585Z","shell.execute_reply":"2025-09-21T20:16:04.62185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport polars as pl\nimport pydicom\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T20:16:04.623792Z","iopub.execute_input":"2025-09-21T20:16:04.624009Z","iopub.status.idle":"2025-09-21T20:16:04.63878Z","shell.execute_reply.started":"2025-09-21T20:16:04.623993Z","shell.execute_reply":"2025-09-21T20:16:04.638091Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pydicom\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nimport pandas as pd\nfrom PIL import Image\nimport numpy as np\n\nclass AneurysmDataset(Dataset):\n    def __init__(self, csv_file, series_dir, transform=None):\n        self.data = pd.read_csv(csv_file)\n        self.series_dir = series_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        row = self.data.iloc[idx]\n        series_uid = row[\"SeriesInstanceUID\"]\n        label = row[\"Aneurysm Present\"]\n    \n        series_path = os.path.join(self.series_dir, series_uid)\n        slices = sorted(os.listdir(series_path))\n        mid_slice = slices[len(slices)//2]\n        dcm_path = os.path.join(series_path, mid_slice)\n    \n        try:\n            dcm = pydicom.dcmread(dcm_path, force=True)\n            img = dcm.pixel_array.astype(np.float32)\n        except Exception as e:\n            img = np.zeros((512, 512), dtype=np.float32)\n    \n\n        img = np.squeeze(img)\n\n        if img.ndim > 2:\n            img = img[..., 0]\n    \n        # Normalize [0,1]\n        img -= img.min()\n        if img.max() > 0:\n            img /= img.max()\n    \n        # Convert to PIL\n        img = Image.fromarray((img * 255).astype(np.uint8), mode=\"L\")\n    \n        if self.transform:\n            img = self.transform(img)\n    \n        return img, torch.tensor(label, dtype=torch.float32)\n\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.5], [0.5])  # grayscale\n])\n\ntrain_dataset = AneurysmDataset(\n    csv_file=\"/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv\",\n    series_dir=\"/kaggle/input/rsna-intracranial-aneurysm-detection/series\",\n    transform=transform\n)\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)\n\nfrom torchvision.models import resnet18, ResNet18_Weights\n\nmodel = resnet18(weights=None)\nmodel.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)\nmodel.fc = nn.Linear(model.fc.in_features, 1)\nmodel = model.to(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = optim.Adam(model.parameters(), lr=1e-4)\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(device)\n\ndef train_model(model, dataloader, num_epochs=2):\n    model.train()\n    for epoch in range(num_epochs):\n        running_loss = 0.0\n        for i, (imgs, labels) in enumerate(dataloader, start=1):\n            print(f\"Series {i}/{len(dataloader)}\", end=\"\\r\")\n            imgs, labels = imgs.to(device), labels.to(device).unsqueeze(1)\n\n            optimizer.zero_grad()\n            outputs = model(imgs)\n            # print(outputs)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n\n            running_loss += loss.item()\n        print(f\"Epoch {epoch+1}/{num_epochs}, Loss: {running_loss/len(dataloader):.4f}\")\n    return model\n\nmodel = train_model(model, train_loader, num_epochs=5)\ntorch.save(model.state_dict(), \"/kaggle/working/baseline_cnn.pth\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T20:16:04.639975Z","iopub.execute_input":"2025-09-21T20:16:04.640489Z","iopub.status.idle":"2025-09-21T20:44:37.182044Z","shell.execute_reply.started":"2025-09-21T20:16:04.640465Z","shell.execute_reply":"2025-09-21T20:44:37.181375Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The evaluation API requires that you set up a server which will respond to inference requests. We have already defined the server; you just need write the predict function. When we evaluate your submission on the hidden test set the client defined in `rsna_gateway` will run in a different container with direct access to the hidden test set and hand off the data series by series.\n\nYour code will always have access to the published copies of the files.","metadata":{}},{"cell_type":"code","source":"ID_COL = 'SeriesInstanceUID'\n\n# LABEL_COLS = [\n#     'Left Infraclinoid Internal Carotid Artery',\n#     'Right Infraclinoid Internal Carotid Artery',\n#     'Left Supraclinoid Internal Carotid Artery',\n#     'Right Supraclinoid Internal Carotid Artery',\n#     'Left Middle Cerebral Artery',\n#     'Right Middle Cerebral Artery',\n#     'Anterior Communicating Artery',\n#     'Left Anterior Cerebral Artery',\n#     'Right Anterior Cerebral Artery',\n#     'Left Posterior Communicating Artery',\n#     'Right Posterior Communicating Artery',\n#     'Basilar Tip',\n#     'Other Posterior Circulation',\n#     'Aneurysm Present',\n# ]\n\nLABEL_COLS = ['Aneurysm Present']\n\nDICOM_TAG_ALLOWLIST = [\n    'BitsAllocated',\n    'BitsStored',\n    'Columns',\n    'FrameOfReferenceUID',\n    'HighBit',\n    'ImageOrientationPatient',\n    'ImagePositionPatient',\n    'InstanceNumber',\n    'Modality',\n    'PatientID',\n    'PhotometricInterpretation',\n    'PixelRepresentation',\n    'PixelSpacing',\n    'PlanarConfiguration',\n    'RescaleIntercept',\n    'RescaleSlope',\n    'RescaleType',\n    'Rows',\n    'SOPClassUID',\n    'SOPInstanceUID',\n    'SamplesPerPixel',\n    'SliceThickness',\n    'SpacingBetweenSlices',\n    'StudyInstanceUID',\n    'TransferSyntaxUID',\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T20:44:37.182807Z","iopub.execute_input":"2025-09-21T20:44:37.183068Z","iopub.status.idle":"2025-09-21T20:44:37.187739Z","shell.execute_reply.started":"2025-09-21T20:44:37.18304Z","shell.execute_reply":"2025-09-21T20:44:37.187045Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_model():\n    model = resnet18(weights=None)\n    model.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)\n    model.fc = nn.Linear(model.fc.in_features, 1) \n    model = model.to(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    model_path = \"/kaggle/working/baseline_cnn.pth\"\n    if os.path.exists(model_path):\n        model.load_state_dict(torch.load(model_path, map_location=\"cpu\"))\n        model.eval()\n        print(\"Loaded trained weights\")\n    else:\n        print(\"No weights found, using random initialization\")\n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T20:44:37.189099Z","iopub.execute_input":"2025-09-21T20:44:37.189315Z","iopub.status.idle":"2025-09-21T20:44:37.207287Z","shell.execute_reply.started":"2025-09-21T20:44:37.189299Z","shell.execute_reply":"2025-09-21T20:44:37.206588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(series_path: str) -> pl.DataFrame | pd.DataFrame:\n    global _model\n    if _model is None:\n        _model = load_model()\n\n    device = next(_model.parameters()).device\n    series_id = os.path.basename(series_path)\n    \n    slice_preds = []\n    transform = transforms.Compose([\n        transforms.Resize((128, 128)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.5], [0.5])\n    ])\n\n    for root, _, files in os.walk(series_path):\n        for file in sorted(files):\n            if file.endswith(\".dcm\"):\n                ds = pydicom.dcmread(os.path.join(root, file), force=True)\n                img = Image.fromarray(ds.pixel_array.astype(np.float32)).convert(\"L\")\n                img = transform(img).unsqueeze(0).to(device) \n                with torch.no_grad():\n                    logits = _model(img)\n                    probs = torch.sigmoid(logits).cpu().numpy()[0] \n                slice_preds.append(probs)\n\n    if slice_preds:\n        mean_pred = np.mean(slice_preds, axis=0).tolist()\n    else:\n        mean_pred = [0.5] * len(LABEL_COLS)\n\n    predictions = pl.DataFrame(\n        data=[[series_id] + mean_pred],\n        schema=[ID_COL, *LABEL_COLS],\n        orient=\"row\",\n    )\n\n    shutil.rmtree(\"/kaggle/shared\", ignore_errors=True)\n    return predictions.drop(ID_COL)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T20:44:37.208139Z","iopub.execute_input":"2025-09-21T20:44:37.2085Z","iopub.status.idle":"2025-09-21T20:44:37.224088Z","shell.execute_reply.started":"2025-09-21T20:44:37.208476Z","shell.execute_reply":"2025-09-21T20:44:37.223453Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"When your notebook is run on the hidden test set, `inference_server.serve` must be called within 15 minutes of the notebook starting or the gateway will throw an error. If you need more than 15 minutes to load your model you can do so during the very first `predict` call.","metadata":{}},{"cell_type":"code","source":"inference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    shutil.rmtree(\"/kaggle/shared\", ignore_errors=True)\n    inference_server.run_local_gateway()\n    display(pl.read_parquet('/kaggle/working/submission.parquet'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T20:44:37.224781Z","iopub.execute_input":"2025-09-21T20:44:37.225011Z","iopub.status.idle":"2025-09-21T20:44:46.580591Z","shell.execute_reply.started":"2025-09-21T20:44:37.224995Z","shell.execute_reply":"2025-09-21T20:44:46.5799Z"}},"outputs":[],"execution_count":null}]}