{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":99552,"databundleVersionId":13190393,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":504531,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":400719,"modelId":418919}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":128.264682,"end_time":"2025-08-02T20:50:47.284372","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-08-02T20:48:39.01969","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"00ffea1a","cell_type":"code","source":"import os\nimport random\nimport numpy as np\nimport pandas as pd\nimport pydicom\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport timm\nfrom sklearn.model_selection import StratifiedKFold\nfrom tqdm import tqdm\nfrom glob import glob\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import roc_auc_score\nfrom torch.cuda.amp import GradScaler, autocast\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport ast\nimport shutil\nfrom sklearn.model_selection import train_test_split\nfrom collections import defaultdict\nimport kaggle_evaluation.rsna_inference_server\nfrom sklearn.metrics import classification_report, f1_score, roc_auc_score\nimport polars as pl\nimport kagglehub\nimport json\nimport gc","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.execute_input":"2025-08-02T20:48:43.136255Z","iopub.status.busy":"2025-08-02T20:48:43.135509Z","iopub.status.idle":"2025-08-02T20:49:33.076508Z","shell.execute_reply":"2025-08-02T20:49:33.075877Z"},"papermill":{"duration":49.948688,"end_time":"2025-08-02T20:49:33.077829","exception":false,"start_time":"2025-08-02T20:48:43.129141","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"2c436ca4","cell_type":"code","source":"import logging\nlogging.getLogger('pydicom').setLevel(logging.ERROR)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.088153Z","iopub.status.busy":"2025-08-02T20:49:33.087445Z","iopub.status.idle":"2025-08-02T20:49:33.091483Z","shell.execute_reply":"2025-08-02T20:49:33.090927Z"},"papermill":{"duration":0.009791,"end_time":"2025-08-02T20:49:33.092499","exception":false,"start_time":"2025-08-02T20:49:33.082708","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"6245f2ce","cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\") ","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.101575Z","iopub.status.busy":"2025-08-02T20:49:33.101373Z","iopub.status.idle":"2025-08-02T20:49:33.104416Z","shell.execute_reply":"2025-08-02T20:49:33.103896Z"},"papermill":{"duration":0.008754,"end_time":"2025-08-02T20:49:33.105393","exception":false,"start_time":"2025-08-02T20:49:33.096639","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"b8aaf0a2","cell_type":"code","source":"# Load detection labels\ndf_labels = pd.read_csv(\"/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv\")","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.114323Z","iopub.status.busy":"2025-08-02T20:49:33.114097Z","iopub.status.idle":"2025-08-02T20:49:33.140074Z","shell.execute_reply":"2025-08-02T20:49:33.139484Z"},"papermill":{"duration":0.031819,"end_time":"2025-08-02T20:49:33.141314","exception":false,"start_time":"2025-08-02T20:49:33.109495","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"00e19cba","cell_type":"code","source":"df_labels.head()","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.150663Z","iopub.status.busy":"2025-08-02T20:49:33.150439Z","iopub.status.idle":"2025-08-02T20:49:33.173558Z","shell.execute_reply":"2025-08-02T20:49:33.17288Z"},"papermill":{"duration":0.029214,"end_time":"2025-08-02T20:49:33.174696","exception":false,"start_time":"2025-08-02T20:49:33.145482","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"0e33fa60","cell_type":"code","source":"class CFG:\n    seed = 42\n    img_size = 224\n    model_name = 'tf_efficientnetv2_s'\n    lr = 1e-4\n    epochs = 10\n    batch_size = 16\n    num_workers = 4\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    train_csv_path = '/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv'\n    train_localizers_csv_path = '/kaggle/input/rsna-intracranial-aneurysm-detection/train_localizers.csv'\n    series_data_path = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\n    output_dir = '/kaggle/working/'\n    val_split = 0.2\n\nos.makedirs(CFG.output_dir, exist_ok=True)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.184871Z","iopub.status.busy":"2025-08-02T20:49:33.184685Z","iopub.status.idle":"2025-08-02T20:49:33.256854Z","shell.execute_reply":"2025-08-02T20:49:33.256263Z"},"papermill":{"duration":0.078814,"end_time":"2025-08-02T20:49:33.258026","exception":false,"start_time":"2025-08-02T20:49:33.179212","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"50fae57a","cell_type":"code","source":"def set_seed(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nset_seed(CFG.seed)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.268008Z","iopub.status.busy":"2025-08-02T20:49:33.267752Z","iopub.status.idle":"2025-08-02T20:49:33.277191Z","shell.execute_reply":"2025-08-02T20:49:33.276481Z"},"papermill":{"duration":0.015645,"end_time":"2025-08-02T20:49:33.278447","exception":false,"start_time":"2025-08-02T20:49:33.262802","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"daa24083","cell_type":"code","source":"def load_dicom_as_array(dcm_path, window=(0, 100), output_size=CFG.img_size):\n    dcm = pydicom.dcmread(dcm_path)\n    arr = dcm.pixel_array.astype(np.float32)\n\n    # Apply rescale slope/intercept if present\n    slope = getattr(dcm, \"RescaleSlope\", 1.0)\n    intercept = getattr(dcm, \"RescaleIntercept\", 0.0)\n    arr = arr * slope + intercept\n\n    # Handle dimensionality\n    if arr.ndim == 3:\n        arr = arr[arr.shape[0] // 2]\n    elif arr.ndim == 4:\n        arr = arr[arr.shape[0] // 2]\n        if arr.ndim == 3 and arr.shape[-1] != 3:\n            arr = arr[..., 0]\n\n    # Clip to window and normalize to [0, 1]\n    w_min, w_max = window\n    arr = np.clip(arr, w_min, w_max)\n    arr = (arr - w_min) / (w_max - w_min + 1e-5)\n\n    # Convert to 3-channel RGB and resize\n    arr = np.stack([arr] * 3, axis=-1)\n    arr = Image.fromarray((arr * 255).astype(np.uint8)).resize((output_size, output_size))\n    return np.array(arr)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.288231Z","iopub.status.busy":"2025-08-02T20:49:33.288018Z","iopub.status.idle":"2025-08-02T20:49:33.29376Z","shell.execute_reply":"2025-08-02T20:49:33.293226Z"},"papermill":{"duration":0.011818,"end_time":"2025-08-02T20:49:33.294791","exception":false,"start_time":"2025-08-02T20:49:33.282973","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"188dccd4","cell_type":"code","source":"class RSNADataset(Dataset):\n    def __init__(self, df, series_path, transform=None):\n        self.df = df.reset_index(drop=True)\n        self.series_path = series_path\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        series_id = row['series_id']\n        series_folder = os.path.join(self.series_path, str(series_id))\n        dcm_files = sorted(glob(os.path.join(series_folder, '*.dcm')))\n        \n        # Fallback for corrupted/missing DICOMs\n        if not dcm_files:\n            dummy_image = torch.zeros(3, CFG.img_size, CFG.img_size) \n            dummy_label = torch.tensor(0.0, dtype=torch.float32) \n            dummy_loc = torch.full((13,), -1.0)  # 13 locations\n            return dummy_image, dummy_label, dummy_loc\n    \n        mid_idx = (len(dcm_files) - 1) // 2\n        try:\n            image = load_dicom_as_array(dcm_files[mid_idx])\n        except Exception as e:\n            dummy_image = torch.zeros(3, CFG.img_size, CFG.img_size)\n            dummy_label = torch.tensor(0.0, dtype=torch.float32)\n            dummy_loc = torch.full((13,), -1.0)\n            return dummy_image, dummy_label, dummy_loc\n    \n        # Transform or basic normalization\n        if self.transform:\n            image = self.transform(image=image)['image']\n        else:\n            image = torch.tensor(image).permute(2, 0, 1).float() / 255.0\n    \n        # Binary class label\n        label = torch.tensor(row['present'], dtype=torch.float32)\n    \n        # Per-location labels\n        loc_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        ]\n        loc_values = row[loc_cols].values.astype(np.float32)\n        loc_values = np.nan_to_num(loc_values, nan=-1.0)\n        loc = torch.tensor(loc_values)\n    \n        return image, label, loc","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.304426Z","iopub.status.busy":"2025-08-02T20:49:33.304237Z","iopub.status.idle":"2025-08-02T20:49:33.311866Z","shell.execute_reply":"2025-08-02T20:49:33.311335Z"},"papermill":{"duration":0.013863,"end_time":"2025-08-02T20:49:33.312981","exception":false,"start_time":"2025-08-02T20:49:33.299118","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"83ca61ec","cell_type":"code","source":"train_transform = A.Compose([\n    A.Resize(CFG.img_size, CFG.img_size),\n    A.HorizontalFlip(p=0.5),\n    A.RandomBrightnessContrast(p=0.2),\n    A.Normalize(),\n    ToTensorV2()\n])\n\nval_transform = A.Compose([\n    A.Resize(CFG.img_size, CFG.img_size),\n    A.Normalize(),\n    ToTensorV2()\n])","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.322925Z","iopub.status.busy":"2025-08-02T20:49:33.322479Z","iopub.status.idle":"2025-08-02T20:49:33.329569Z","shell.execute_reply":"2025-08-02T20:49:33.328997Z"},"papermill":{"duration":0.01325,"end_time":"2025-08-02T20:49:33.33061","exception":false,"start_time":"2025-08-02T20:49:33.31736","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"e253e063","cell_type":"code","source":"class DualHeadModel(nn.Module):\n    def __init__(self, backbone_name=CFG.model_name):\n        super().__init__()\n        self.backbone = timm.create_model(backbone_name, pretrained=False, num_classes=0)\n        in_features = self.backbone.num_features\n        self.pool = nn.AdaptiveAvgPool2d(1)\n        self.classifier_head = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.ReLU(),\n            nn.BatchNorm1d(512),\n            nn.Dropout(0.3),\n            nn.Linear(512, 1)\n        )\n        \n        self.localization_head = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, 13)\n        )\n\n    def forward(self, x):\n        features = self.backbone.forward_features(x)\n        pooled = self.pool(features).view(features.size(0), -1)\n        class_out = self.classifier_head(pooled)\n        loc_out = self.localization_head(pooled)\n        return class_out[:, 0], loc_out","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.340698Z","iopub.status.busy":"2025-08-02T20:49:33.340245Z","iopub.status.idle":"2025-08-02T20:49:33.345443Z","shell.execute_reply":"2025-08-02T20:49:33.344895Z"},"papermill":{"duration":0.011385,"end_time":"2025-08-02T20:49:33.346435","exception":false,"start_time":"2025-08-02T20:49:33.33505","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"16856c21","cell_type":"code","source":"class DualLoss(nn.Module):\n    def __init__(self, alpha=1.0):\n        super().__init__()\n        self.alpha = alpha\n        self.bce = nn.BCEWithLogitsLoss()\n        self.bce_no_reduce = nn.BCEWithLogitsLoss(reduction='none')\n\n    def forward(self, class_pred, class_true, loc_pred, loc_true):\n        class_loss = self.bce(class_pred, class_true)\n        mask = (loc_true != -1).float()\n        loc_loss_raw = self.bce_no_reduce(loc_pred, loc_true)\n        masked_loc_loss = loc_loss_raw * mask\n        loc_loss = masked_loc_loss.sum() / (mask.sum() + 1e-6)\n        total_loss = class_loss + self.alpha * loc_loss\n        return total_loss","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.356382Z","iopub.status.busy":"2025-08-02T20:49:33.355813Z","iopub.status.idle":"2025-08-02T20:49:33.360494Z","shell.execute_reply":"2025-08-02T20:49:33.359997Z"},"papermill":{"duration":0.010606,"end_time":"2025-08-02T20:49:33.361483","exception":false,"start_time":"2025-08-02T20:49:33.350877","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"c1bd85ab","cell_type":"code","source":"def train_model(model, train_loader, val_loader, criterion, optimizer, scheduler, device, num_epochs=CFG.epochs):\n    best_val_loss = float('inf')\n    \n    for epoch in range(num_epochs):\n        model.train()\n        train_loss = 0.0\n        for inputs, class_targets, loc_targets in train_loader:\n            inputs = inputs.to(device)\n            class_targets = class_targets.to(device)\n            loc_targets = loc_targets.to(device)\n\n            optimizer.zero_grad()\n            class_outputs, loc_outputs = model(inputs)\n\n            loss = criterion(class_outputs, class_targets, loc_outputs, loc_targets)\n            loss.backward()\n            optimizer.step()\n\n            train_loss += loss.item()\n\n        scheduler.step()\n\n        # Validation\n        model.eval()\n        val_loss = 0.0\n        with torch.no_grad():\n            for inputs, class_targets, loc_targets in val_loader:\n                inputs = inputs.to(device)\n                class_targets = class_targets.to(device)\n                loc_targets = loc_targets.to(device)\n\n                class_outputs, loc_outputs = model(inputs)\n                loss = criterion(class_outputs, class_targets, loc_outputs, loc_targets)\n                val_loss += loss.item()\n\n        print(f\"Epoch {epoch+1}/{num_epochs}, Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}\")\n\n        # Save best model\n        if val_loss < best_val_loss:\n            best_val_loss = val_loss\n            print(\"Saved best model!\")\n            torch.save(model.state_dict(), 'best_model.pth')","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.371215Z","iopub.status.busy":"2025-08-02T20:49:33.370967Z","iopub.status.idle":"2025-08-02T20:49:33.377334Z","shell.execute_reply":"2025-08-02T20:49:33.376797Z"},"papermill":{"duration":0.012603,"end_time":"2025-08-02T20:49:33.378377","exception":false,"start_time":"2025-08-02T20:49:33.365774","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"4500855d","cell_type":"code","source":"train_df = pd.read_csv(CFG.train_csv_path)\ntrain_df.rename(columns={'Aneurysm Present': 'present'}, inplace=True)\ntrain_df.rename(columns={'SeriesInstanceUID': 'series_id'}, inplace=True)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.387844Z","iopub.status.busy":"2025-08-02T20:49:33.387638Z","iopub.status.idle":"2025-08-02T20:49:33.40766Z","shell.execute_reply":"2025-08-02T20:49:33.407112Z"},"papermill":{"duration":0.026068,"end_time":"2025-08-02T20:49:33.408903","exception":false,"start_time":"2025-08-02T20:49:33.382835","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"9e9aedb2","cell_type":"code","source":"df_train, df_val = train_test_split(\n    train_df,\n    test_size=0.2,\n    stratify=train_df['present'],\n    random_state=42\n)\n\ndf_val, df_test = train_test_split(\n    df_val,\n    test_size=0.5,\n    stratify=df_val['present'],\n    random_state=42\n)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.418649Z","iopub.status.busy":"2025-08-02T20:49:33.418416Z","iopub.status.idle":"2025-08-02T20:49:33.436557Z","shell.execute_reply":"2025-08-02T20:49:33.435992Z"},"papermill":{"duration":0.02419,"end_time":"2025-08-02T20:49:33.437621","exception":false,"start_time":"2025-08-02T20:49:33.413431","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"d7822c8d","cell_type":"code","source":"train_dataset = RSNADataset(df_train, series_path=CFG.series_data_path, transform=train_transform)\nval_dataset   = RSNADataset(df_val,   series_path=CFG.series_data_path, transform=val_transform)\ntest_dataset  = RSNADataset(df_test,  series_path=CFG.series_data_path, transform=val_transform)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.447346Z","iopub.status.busy":"2025-08-02T20:49:33.447137Z","iopub.status.idle":"2025-08-02T20:49:33.451474Z","shell.execute_reply":"2025-08-02T20:49:33.450989Z"},"papermill":{"duration":0.010211,"end_time":"2025-08-02T20:49:33.452516","exception":false,"start_time":"2025-08-02T20:49:33.442305","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"f0df8d87","cell_type":"code","source":"train_loader = DataLoader(train_dataset, batch_size=CFG.batch_size, shuffle=True, num_workers=CFG.num_workers)\nval_loader   = DataLoader(val_dataset,   batch_size=CFG.batch_size, shuffle=False, num_workers=CFG.num_workers)\ntest_loader  = DataLoader(test_dataset,  batch_size=CFG.batch_size, shuffle=False, num_workers=CFG.num_workers)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.462053Z","iopub.status.busy":"2025-08-02T20:49:33.461827Z","iopub.status.idle":"2025-08-02T20:49:33.465817Z","shell.execute_reply":"2025-08-02T20:49:33.465322Z"},"papermill":{"duration":0.009925,"end_time":"2025-08-02T20:49:33.466835","exception":false,"start_time":"2025-08-02T20:49:33.45691","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"c92a0647","cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = DualHeadModel().to(device)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:33.476329Z","iopub.status.busy":"2025-08-02T20:49:33.476156Z","iopub.status.idle":"2025-08-02T20:49:34.059382Z","shell.execute_reply":"2025-08-02T20:49:34.058714Z"},"papermill":{"duration":0.589484,"end_time":"2025-08-02T20:49:34.060705","exception":false,"start_time":"2025-08-02T20:49:33.471221","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"c8588741","cell_type":"code","source":"criterion = DualLoss(alpha=1.0)\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:34.070652Z","iopub.status.busy":"2025-08-02T20:49:34.070427Z","iopub.status.idle":"2025-08-02T20:49:34.07615Z","shell.execute_reply":"2025-08-02T20:49:34.075458Z"},"papermill":{"duration":0.012044,"end_time":"2025-08-02T20:49:34.077334","exception":false,"start_time":"2025-08-02T20:49:34.06529","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"f70c21b5","cell_type":"code","source":"# train_model(\n#     model=model,\n#     train_loader=train_loader,\n#     val_loader=val_loader,\n#     criterion=criterion,\n#     optimizer=optimizer,\n#     scheduler=scheduler,\n#     device=device\n# )","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:34.086883Z","iopub.status.busy":"2025-08-02T20:49:34.086685Z","iopub.status.idle":"2025-08-02T20:49:34.089564Z","shell.execute_reply":"2025-08-02T20:49:34.088989Z"},"papermill":{"duration":0.008772,"end_time":"2025-08-02T20:49:34.090605","exception":false,"start_time":"2025-08-02T20:49:34.081833","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"b42b345d","cell_type":"code","source":"model = DualHeadModel()\nmodel.load_state_dict(torch.load('/kaggle/input/m/umerellous/rsna-intracranial-aneurysm-detection/keras/default/1/best_model.pth',map_location=device))\nmodel.to(CFG.device) \nmodel.eval()\nprint(\"Model loaded successfully!\")","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:34.100351Z","iopub.status.busy":"2025-08-02T20:49:34.100136Z","iopub.status.idle":"2025-08-02T20:49:35.815071Z","shell.execute_reply":"2025-08-02T20:49:35.814251Z"},"papermill":{"duration":1.721519,"end_time":"2025-08-02T20:49:35.816698","exception":false,"start_time":"2025-08-02T20:49:34.095179","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"1f2c260a","cell_type":"code","source":"def evaluate_model(model, test_loader, device):\n    model.eval()\n    all_present_preds = []\n    all_present_targets = []\n    all_loc_preds = []\n    all_loc_targets = []\n\n    with torch.no_grad():\n        for images, present_targets, loc_targets in tqdm(test_loader):\n            images = images.to(device)\n            present_targets = present_targets.to(device)\n            loc_targets = loc_targets.to(device)\n\n            present_preds, loc_preds = model(images)\n\n            # Apply sigmoid to get probabilities\n            present_preds = torch.sigmoid(present_preds)\n            loc_preds = torch.sigmoid(loc_preds)\n\n            # Store results\n            all_present_preds.append(present_preds.cpu())\n            all_present_targets.append(present_targets.cpu())\n            all_loc_preds.append(loc_preds.cpu())\n            all_loc_targets.append(loc_targets.cpu())\n\n    # Stack results\n    all_present_preds = torch.cat(all_present_preds).numpy()\n    all_present_targets = torch.cat(all_present_targets).numpy()\n    all_loc_preds = torch.cat(all_loc_preds).numpy()\n    all_loc_targets = torch.cat(all_loc_targets).numpy()\n\n    print(\"Classification Report:\")\n    print(classification_report(all_present_targets, (all_present_preds > 0.5).astype(int)))","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:35.827933Z","iopub.status.busy":"2025-08-02T20:49:35.827703Z","iopub.status.idle":"2025-08-02T20:49:35.833558Z","shell.execute_reply":"2025-08-02T20:49:35.832965Z"},"papermill":{"duration":0.011957,"end_time":"2025-08-02T20:49:35.834678","exception":false,"start_time":"2025-08-02T20:49:35.822721","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"18b7cffd","cell_type":"code","source":"evaluate_model(model, test_loader, device)","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:49:35.844484Z","iopub.status.busy":"2025-08-02T20:49:35.844263Z","iopub.status.idle":"2025-08-02T20:50:05.071915Z","shell.execute_reply":"2025-08-02T20:50:05.07075Z"},"papermill":{"duration":29.234016,"end_time":"2025-08-02T20:50:05.073226","exception":false,"start_time":"2025-08-02T20:49:35.83921","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"41c89860","cell_type":"code","source":"transform = A.Compose([\n    A.Resize(CFG.img_size, CFG.img_size),\n    A.Normalize(),\n    ToTensorV2()\n])","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:50:05.0862Z","iopub.status.busy":"2025-08-02T20:50:05.085925Z","iopub.status.idle":"2025-08-02T20:50:05.091518Z","shell.execute_reply":"2025-08-02T20:50:05.090967Z"},"papermill":{"duration":0.012877,"end_time":"2025-08-02T20:50:05.09253","exception":false,"start_time":"2025-08-02T20:50:05.079653","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"dcbda588","cell_type":"code","source":"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]","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:50:05.10391Z","iopub.status.busy":"2025-08-02T20:50:05.10371Z","iopub.status.idle":"2025-08-02T20:50:05.106928Z","shell.execute_reply":"2025-08-02T20:50:05.106449Z"},"papermill":{"duration":0.010082,"end_time":"2025-08-02T20:50:05.107866","exception":false,"start_time":"2025-08-02T20:50:05.097784","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"7bc91b37","cell_type":"code","source":"def predict(series_path: str) -> pd.DataFrame:\n    try:\n        series_id = os.path.basename(series_path)\n        dicom_files = sorted([\n            os.path.join(dp, f)\n            for dp, _, filenames in os.walk(series_path)\n            for f in filenames if f.endswith(\".dcm\")\n        ], key=lambda x: int(pydicom.dcmread(x, stop_before_pixels=True).InstanceNumber))\n    \n        images = []\n        for file in dicom_files:\n            dcm = pydicom.dcmread(file)\n            img = dcm.pixel_array.astype(np.float32)\n            img = (img - img.min()) / (img.max() - img.min() + 1e-5)\n            if len(img.shape) == 2:\n                img = np.stack([img]*3, axis=-1)\n            img = transform(image=img)[\"image\"]\n            images.append(img)\n    \n        images = torch.stack(images).to(CFG.device)\n        model.to(CFG.device)\n        model.eval()\n    \n        all_present_preds = []\n        all_loc_preds = []\n    \n        with torch.no_grad():\n            for img in images:\n                img = img.unsqueeze(0)\n                present, loc = model(img)\n                present = torch.sigmoid(present).cpu().item()\n                loc = torch.sigmoid(loc).cpu().numpy().flatten()\n                all_present_preds.append(present)\n                all_loc_preds.append(loc)\n    \n        final_present = float(np.max(all_present_preds))\n        final_loc = np.max(all_loc_preds, axis=0).tolist()\n    \n        df = pd.DataFrame([{\n            \"SeriesInstanceUID\": series_id,\n            **{label: float(v) for label, v in zip(LABEL_COLS, final_loc)},\n            \"Aneurysm Present\": final_present\n        }])\n    \n        if os.getenv(\"KAGGLE_IS_COMPETITION_RERUN\"):\n            df.to_parquet(\"/kaggle/working/submission.parquet\", index=False)\n            print(\"Saved submission.parquet\")\n            \n        return df\n\n    except Exception as e:\n        print(f\"Prediction failed for {series_path}: {e}\")\n        fallback = pd.DataFrame([{\n            **{label: 0.1 for label in LABEL_COLS},\n            \"Aneurysm Present\": 0.1\n        }])\n        \n        return fallback\n\n    finally:\n        shared_dir = \"/kaggle/shared\"\n        shutil.rmtree(shared_dir, ignore_errors=True)\n        os.makedirs(shared_dir, exist_ok=True)\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n        gc.collect()","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:50:05.1192Z","iopub.status.busy":"2025-08-02T20:50:05.118871Z","iopub.status.idle":"2025-08-02T20:50:05.128145Z","shell.execute_reply":"2025-08-02T20:50:05.127614Z"},"papermill":{"duration":0.015988,"end_time":"2025-08-02T20:50:05.129088","exception":false,"start_time":"2025-08-02T20:50:05.1131","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"2cf1a35d","cell_type":"code","source":"shared_dir = '/kaggle/shared'\nif os.path.exists(shared_dir):\n    for f in os.listdir(shared_dir):\n        file_path = os.path.join(shared_dir, f)\n        try:\n            if os.path.isfile(file_path) or os.path.islink(file_path):\n                os.unlink(file_path)\n            elif os.path.isdir(file_path):\n                shutil.rmtree(file_path)\n        except Exception as e:\n            print(f'Failed to delete {file_path}')","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:50:05.140256Z","iopub.status.busy":"2025-08-02T20:50:05.140072Z","iopub.status.idle":"2025-08-02T20:50:05.14421Z","shell.execute_reply":"2025-08-02T20:50:05.143618Z"},"papermill":{"duration":0.010921,"end_time":"2025-08-02T20:50:05.145258","exception":false,"start_time":"2025-08-02T20:50:05.134337","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"1a56fa5a","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    inference_server.run_local_gateway()\n    display(pl.read_parquet('/kaggle/working/submission.parquet'))","metadata":{"execution":{"iopub.execute_input":"2025-08-02T20:50:05.156742Z","iopub.status.busy":"2025-08-02T20:50:05.15626Z","iopub.status.idle":"2025-08-02T20:50:43.917627Z","shell.execute_reply":"2025-08-02T20:50:43.916879Z"},"papermill":{"duration":38.774272,"end_time":"2025-08-02T20:50:43.924763","exception":false,"start_time":"2025-08-02T20:50:05.150491","status":"completed"},"tags":[],"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"id":"c7dbe067-43cf-4d11-b642-8129e7300d51","cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}