{"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":"none","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"},{"sourceId":264964416,"sourceType":"kernelVersion"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Cell 1: Setup and Data Exploration\nimport os\nimport pandas as pd\nimport numpy as np\nimport pydicom\nfrom pathlib import Path\nimport matplotlib.pyplot as plt\nfrom collections import defaultdict\n\n# Load training data\ntrain_df = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\n\nprint(\"=== Dataset Overview ===\")\nprint(f\"Total training samples: {len(train_df)}\")\nprint(f\"\\nColumns: {train_df.columns.tolist()}\")\nprint(f\"\\nAneurysm prevalence: {train_df['Aneurysm Present'].mean():.3f}\")\n\n# Check class distribution\nvessel_cols = [col for col in train_df.columns if col not in ['SeriesInstanceUID', 'Modality', 'PatientAge', 'PatientSex', 'Aneurysm Present']]\nprint(f\"\\n=== Vessel Location Distribution ===\")\nfor col in vessel_cols:\n    pos_count = train_df[col].sum()\n    print(f\"{col}: {pos_count} ({pos_count/len(train_df)*100:.2f}%)\")\n\nprint(f\"\\n=== Modality Distribution ===\")\nprint(train_df['Modality'].value_counts())\n\nprint(f\"\\n=== First 3 rows ===\")\nprint(train_df.head(3))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T19:29:50.981419Z","iopub.execute_input":"2025-09-30T19:29:50.981709Z","iopub.status.idle":"2025-09-30T19:29:51.032187Z","shell.execute_reply.started":"2025-09-30T19:29:50.981689Z","shell.execute_reply":"2025-09-30T19:29:51.031459Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 2: Understand DICOM data structure\nimport random\n\n# Get a random series to examine\nsample_series = train_df.sample(1).iloc[0]['SeriesInstanceUID']\nseries_path = f'/kaggle/input/rsna-intracranial-aneurysm-detection/series/{sample_series}'\n\n# Count slices\ndcm_files = list(Path(series_path).glob('*.dcm'))\nprint(f\"Sample Series: {sample_series}\")\nprint(f\"Number of DICOM slices: {len(dcm_files)}\")\n\n# Read one DICOM\nsample_dcm = pydicom.dcmread(dcm_files[0], force=True)\nprint(f\"\\nImage shape: {sample_dcm.pixel_array.shape}\")\nprint(f\"Modality: {sample_dcm.Modality}\")\n\n# Check slice distribution across all series\nprint(\"\\n=== Analyzing slice counts per series (sample of 100) ===\")\nsample_series_ids = train_df.sample(min(100, len(train_df)))['SeriesInstanceUID'].values\nslice_counts = []\n\nfor sid in sample_series_ids:\n    path = f'/kaggle/input/rsna-intracranial-aneurysm-detection/series/{sid}'\n    if os.path.exists(path):\n        n_slices = len(list(Path(path).glob('*.dcm')))\n        slice_counts.append(n_slices)\n\nprint(f\"Slice count stats:\")\nprint(f\"  Min: {min(slice_counts)}, Max: {max(slice_counts)}\")\nprint(f\"  Mean: {np.mean(slice_counts):.1f}, Median: {np.median(slice_counts):.1f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T19:30:51.410004Z","iopub.execute_input":"2025-09-30T19:30:51.410537Z","iopub.status.idle":"2025-09-30T19:30:53.64911Z","shell.execute_reply.started":"2025-09-30T19:30:51.410512Z","shell.execute_reply":"2025-09-30T19:30:53.648515Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 3: Setup for windowing and preprocessing\nimport cv2\nfrom typing import List, Tuple\n\n# Define windowing (from 2019 winner approach)\nWINDOWS = {\n    'brain': (40, 80),\n    'subdural': (80, 200), \n    'bone': (600, 2800)\n}\n\ndef apply_window(image: np.ndarray, window: Tuple[int, int]) -> np.ndarray:\n    \"\"\"Apply CT windowing to image\"\"\"\n    window_center, window_width = window\n    img_min = window_center - window_width // 2\n    img_max = window_center + window_width // 2\n    \n    windowed = np.clip(image, img_min, img_max)\n    windowed = ((windowed - img_min) / (img_max - img_min) * 255).astype(np.uint8)\n    return windowed\n\ndef load_dicom_image(dcm_path: str, target_size: int = 256) -> np.ndarray:\n    \"\"\"Load and preprocess single DICOM slice with 3 windows\"\"\"\n    dcm = pydicom.dcmread(dcm_path, force=True)\n    img = dcm.pixel_array.astype(np.float32)\n    \n    # Apply RescaleSlope and RescaleIntercept if present\n    if hasattr(dcm, 'RescaleSlope') and hasattr(dcm, 'RescaleIntercept'):\n        img = img * dcm.RescaleSlope + dcm.RescaleIntercept\n    \n    # Create 3-channel image with different windows\n    brain_window = apply_window(img, WINDOWS['brain'])\n    subdural_window = apply_window(img, WINDOWS['subdural'])\n    bone_window = apply_window(img, WINDOWS['bone'])\n    \n    # Stack as RGB\n    img_3ch = np.stack([brain_window, subdural_window, bone_window], axis=-1)\n    \n    # Resize\n    img_resized = cv2.resize(img_3ch, (target_size, target_size), interpolation=cv2.INTER_LINEAR)\n    \n    return img_resized\n\n# Test the function\ntest_dcm_path = str(dcm_files[0])\ntest_img = load_dicom_image(test_dcm_path, target_size=256)\nprint(f\"Preprocessed image shape: {test_img.shape}\")\nprint(f\"Preprocessed image dtype: {test_img.dtype}\")\nprint(f\"Preprocessed image range: [{test_img.min()}, {test_img.max()}]\")\n\n# Visualize\nfig, axes = plt.subplots(1, 4, figsize=(16, 4))\naxes[0].imshow(test_img[:,:,0], cmap='gray')\naxes[0].set_title('Brain Window')\naxes[1].imshow(test_img[:,:,1], cmap='gray')\naxes[1].set_title('Subdural Window')\naxes[2].imshow(test_img[:,:,2], cmap='gray')\naxes[2].set_title('Bone Window')\naxes[3].imshow(test_img)\naxes[3].set_title('3-Channel Combined')\nfor ax in axes:\n    ax.axis('off')\nplt.tight_layout()\nplt.show()\n\nprint(\"\\n✓ Preprocessing pipeline ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T19:33:08.605241Z","iopub.execute_input":"2025-09-30T19:33:08.605497Z","iopub.status.idle":"2025-09-30T19:33:09.294035Z","shell.execute_reply.started":"2025-09-30T19:33:08.605479Z","shell.execute_reply":"2025-09-30T19:33:09.29321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 4: Create stratified train/validation split\nfrom sklearn.model_selection import StratifiedKFold\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Define label columns\nLABEL_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\n# Create fold column for 5-fold CV (we'll train on fold 0 for speed)\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\ntrain_df['fold'] = -1\n\n# Use 'Aneurysm Present' for stratification\nfor fold, (train_idx, val_idx) in enumerate(skf.split(train_df, train_df['Aneurysm Present'])):\n    train_df.loc[val_idx, 'fold'] = fold\n\n# For this competition, we'll use fold 0 as validation\ntrain_fold = train_df[train_df['fold'] != 0].reset_index(drop=True)\nvalid_fold = train_df[train_df['fold'] == 0].reset_index(drop=True)\n\nprint(f\"Train samples: {len(train_fold)}\")\nprint(f\"Valid samples: {len(valid_fold)}\")\nprint(f\"\\nTrain aneurysm prevalence: {train_fold['Aneurysm Present'].mean():.3f}\")\nprint(f\"Valid aneurysm prevalence: {valid_fold['Aneurysm Present'].mean():.3f}\")\n\n# Check series paths exist\ntrain_exists = 0\nfor sid in train_fold['SeriesInstanceUID'].head(10):\n    path = f'/kaggle/input/rsna-intracranial-aneurysm-detection/series/{sid}'\n    if os.path.exists(path):\n        train_exists += 1\n\nprint(f\"\\n✓ Verified {train_exists}/10 sample series paths exist\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T19:35:10.929976Z","iopub.execute_input":"2025-09-30T19:35:10.93026Z","iopub.status.idle":"2025-09-30T19:35:11.571348Z","shell.execute_reply.started":"2025-09-30T19:35:10.930239Z","shell.execute_reply":"2025-09-30T19:35:11.570574Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 5: Create efficient PyTorch dataset\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nclass RSNAAneurysmDataset(Dataset):\n    def __init__(self, df, label_cols, transform=None, max_slices=64, sample_mode='uniform'):\n        self.df = df.reset_index(drop=True)\n        self.label_cols = label_cols\n        self.transform = transform\n        self.max_slices = max_slices\n        self.sample_mode = sample_mode\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['SeriesInstanceUID']\n        series_path = f'/kaggle/input/rsna-intracranial-aneurysm-detection/series/{series_id}'\n        \n        # Get all DICOM files and sort\n        dcm_files = sorted(list(Path(series_path).glob('*.dcm')))\n        \n        # Sample slices if too many\n        if len(dcm_files) > self.max_slices:\n            if self.sample_mode == 'uniform':\n                indices = np.linspace(0, len(dcm_files)-1, self.max_slices, dtype=int)\n                dcm_files = [dcm_files[i] for i in indices]\n            elif self.sample_mode == 'random':\n                indices = sorted(np.random.choice(len(dcm_files), self.max_slices, replace=False))\n                dcm_files = [dcm_files[i] for i in indices]\n        \n        # Load middle slice (2D approach for now)\n        middle_idx = len(dcm_files) // 2\n        image = load_dicom_image(str(dcm_files[middle_idx]), target_size=256)\n        \n        # Apply transforms\n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented['image']\n        \n        # Get labels\n        labels = torch.tensor(row[self.label_cols].values.astype(np.float32))\n        \n        return image, labels\n\n# Define augmentations\ntrain_transform = A.Compose([\n    A.HorizontalFlip(p=0.5),\n    A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=15, p=0.5),\n    A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5),\n    A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    ToTensorV2()\n])\n\nvalid_transform = A.Compose([\n    A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    ToTensorV2()\n])\n\n# Create datasets\ntrain_dataset = RSNAAneurysmDataset(train_fold, LABEL_COLS, transform=train_transform)\nvalid_dataset = RSNAAneurysmDataset(valid_fold, LABEL_COLS, transform=valid_transform)\n\n# Test dataset\nsample_img, sample_labels = train_dataset[0]\nprint(f\"Sample image shape: {sample_img.shape}\")\nprint(f\"Sample labels shape: {sample_labels.shape}\")\nprint(f\"Sample labels: {sample_labels}\")\nprint(f\"\\n✓ Dataset creation successful\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T19:36:51.559889Z","iopub.execute_input":"2025-09-30T19:36:51.560794Z","iopub.status.idle":"2025-09-30T19:36:57.000875Z","shell.execute_reply.started":"2025-09-30T19:36:51.560768Z","shell.execute_reply":"2025-09-30T19:36:57.000194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 6: Define model architecture\nimport torch\nimport torch.nn as nn\nimport timm\n\nclass AneurysmClassifier(nn.Module):\n    def __init__(self, model_name='tf_efficientnet_b0_ns', num_classes=14, pretrained=True):\n        super().__init__()\n        self.backbone = timm.create_model(model_name, pretrained=pretrained, num_classes=0)\n        num_features = self.backbone.num_features\n        self.classifier = nn.Sequential(\n            nn.Dropout(0.3),\n            nn.Linear(num_features, num_classes)\n        )\n    def forward(self, x):\n        features = self.backbone(x)\n        output = self.classifier(features)\n        return output\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = AneurysmClassifier(model_name='tf_efficientnet_b0_ns', num_classes=14, pretrained=False)\nmodel = model.to(device)\n\n# Rest of Cell 6...\ntotal_params = sum(p.numel() for p in model.parameters())\ntrainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n\nprint(f\"Device: {device}\")\nprint(f\"Total parameters: {total_params:,}\")\nprint(f\"Trainable parameters: {trainable_params:,}\")\nprint(f\"\\n✓ Model architecture ready\")\n\ntest_input = torch.randn(2, 3, 256, 256).to(device)\ntest_output = model(test_input)\nprint(f\"Test output shape: {test_output.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T23:32:23.10727Z","iopub.execute_input":"2025-09-30T23:32:23.107613Z","iopub.status.idle":"2025-09-30T23:32:23.669041Z","shell.execute_reply.started":"2025-09-30T23:32:23.107588Z","shell.execute_reply":"2025-09-30T23:32:23.668206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 7: Training configuration and loss function\nfrom torch.optim import AdamW\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\nimport torch.nn.functional as F\n\n# Custom weighted loss (Aneurysm Present gets 13x weight as per competition metric)\nclass WeightedBCELoss(nn.Module):\n    def __init__(self):\n        super().__init__()\n        # Last label is \"Aneurysm Present\" - weight it 13x\n        self.weights = torch.ones(14)\n        self.weights[-1] = 13.0\n        \n    def forward(self, logits, targets):\n        self.weights = self.weights.to(logits.device)\n        bce = F.binary_cross_entropy_with_logits(logits, targets, reduction='none')\n        weighted_bce = bce * self.weights.unsqueeze(0)\n        return weighted_bce.mean()\n\n# Training configuration\nCONFIG = {\n    'epochs': 15,\n    'batch_size': 32,\n    'learning_rate': 3e-4,\n    'weight_decay': 1e-5,\n    'num_workers': 2,\n    'gradient_clip': 1.0,\n}\n\n# Setup optimizer and scheduler\ncriterion = WeightedBCELoss()\noptimizer = AdamW(model.parameters(), lr=CONFIG['learning_rate'], weight_decay=CONFIG['weight_decay'])\nscheduler = CosineAnnealingLR(optimizer, T_max=CONFIG['epochs'], eta_min=1e-6)\n\n# Create dataloaders\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=CONFIG['batch_size'],\n    shuffle=True,\n    num_workers=CONFIG['num_workers'],\n    pin_memory=True\n)\n\nvalid_loader = DataLoader(\n    valid_dataset,\n    batch_size=CONFIG['batch_size'],\n    shuffle=False,\n    num_workers=CONFIG['num_workers'],\n    pin_memory=True\n)\n\nprint(f\"✓ Training configuration:\")\nprint(f\"  Epochs: {CONFIG['epochs']}\")\nprint(f\"  Batch size: {CONFIG['batch_size']}\")\nprint(f\"  Learning rate: {CONFIG['learning_rate']}\")\nprint(f\"  Train batches: {len(train_loader)}\")\nprint(f\"  Valid batches: {len(valid_loader)}\")\nprint(f\"  Estimated time per epoch: ~{len(train_loader) * 0.5:.1f} seconds\")\nprint(f\"  Total training time: ~{CONFIG['epochs'] * len(train_loader) * 0.5 / 60:.1f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T19:40:22.255112Z","iopub.execute_input":"2025-09-30T19:40:22.255416Z","iopub.status.idle":"2025-09-30T19:40:22.268704Z","shell.execute_reply.started":"2025-09-30T19:40:22.255395Z","shell.execute_reply":"2025-09-30T19:40:22.267953Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 8: Training and validation functions\nfrom sklearn.metrics import roc_auc_score\nfrom tqdm import tqdm\n\ndef train_one_epoch(model, loader, criterion, optimizer, device, epoch):\n    model.train()\n    running_loss = 0.0\n    \n    pbar = tqdm(loader, desc=f'Epoch {epoch+1} [TRAIN]')\n    for images, labels in pbar:\n        images = images.to(device)\n        labels = labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        \n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), CONFIG['gradient_clip'])\n        optimizer.step()\n        \n        running_loss += loss.item()\n        pbar.set_postfix({'loss': f'{loss.item():.4f}'})\n    \n    return running_loss / len(loader)\n\ndef validate(model, loader, criterion, device, epoch):\n    model.eval()\n    running_loss = 0.0\n    all_preds = []\n    all_labels = []\n    \n    with torch.no_grad():\n        pbar = tqdm(loader, desc=f'Epoch {epoch+1} [VALID]')\n        for images, labels in pbar:\n            images = images.to(device)\n            labels = labels.to(device)\n            \n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            \n            preds = torch.sigmoid(outputs).cpu().numpy()\n            all_preds.append(preds)\n            all_labels.append(labels.cpu().numpy())\n            \n            running_loss += loss.item()\n            pbar.set_postfix({'loss': f'{loss.item():.4f}'})\n    \n    all_preds = np.vstack(all_preds)\n    all_labels = np.vstack(all_labels)\n    \n    # Calculate AUC for each label\n    aucs = []\n    for i in range(14):\n        try:\n            auc = roc_auc_score(all_labels[:, i], all_preds[:, i])\n            aucs.append(auc)\n        except:\n            aucs.append(0.5)\n    \n    # Competition metric: Aneurysm Present weighted 13x\n    competition_score = 0.5 * (aucs[-1] + np.mean(aucs[:-1]))\n    \n    return running_loss / len(loader), aucs, competition_score\n\nprint(\"✓ Training functions ready\")\nprint(\"\\nStarting training in next cell...\")\nprint(\"\\n⚠️ IMPORTANT: This will take ~15 minutes\")\nprint(\"Monitor GPU usage: watch -n 1 nvidia-smi\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T19:41:30.559731Z","iopub.execute_input":"2025-09-30T19:41:30.560473Z","iopub.status.idle":"2025-09-30T19:41:30.570059Z","shell.execute_reply.started":"2025-09-30T19:41:30.560451Z","shell.execute_reply":"2025-09-30T19:41:30.569247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 10: Fixed data loading with error handling\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\ndef load_dicom_image_safe(dcm_path: str, target_size: int = 256) -> np.ndarray:\n    \"\"\"Load and preprocess single DICOM slice with error handling\"\"\"\n    try:\n        dcm = pydicom.dcmread(dcm_path, force=True)\n        img = dcm.pixel_array.astype(np.float32)\n        \n        # Check if image is valid\n        if img.size == 0 or img.shape[0] == 0 or img.shape[1] == 0:\n            return None\n        \n        # Apply RescaleSlope and RescaleIntercept if present\n        if hasattr(dcm, 'RescaleSlope') and hasattr(dcm, 'RescaleIntercept'):\n            img = img * dcm.RescaleSlope + dcm.RescaleIntercept\n        \n        # Create 3-channel image with different windows\n        brain_window = apply_window(img, WINDOWS['brain'])\n        subdural_window = apply_window(img, WINDOWS['subdural'])\n        bone_window = apply_window(img, WINDOWS['bone'])\n        \n        # Stack as RGB\n        img_3ch = np.stack([brain_window, subdural_window, bone_window], axis=-1)\n        \n        # Resize\n        img_resized = cv2.resize(img_3ch, (target_size, target_size), interpolation=cv2.INTER_LINEAR)\n        \n        return img_resized\n    except Exception as e:\n        return None\n\nclass RSNAAneurysmDataset(Dataset):\n    def __init__(self, df, label_cols, transform=None, max_slices=64, sample_mode='uniform'):\n        self.df = df.reset_index(drop=True)\n        self.label_cols = label_cols\n        self.transform = transform\n        self.max_slices = max_slices\n        self.sample_mode = sample_mode\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['SeriesInstanceUID']\n        series_path = f'/kaggle/input/rsna-intracranial-aneurysm-detection/series/{series_id}'\n        \n        # Get all DICOM files and sort\n        dcm_files = sorted(list(Path(series_path).glob('*.dcm')))\n        \n        # Sample slices if too many\n        if len(dcm_files) > self.max_slices:\n            if self.sample_mode == 'uniform':\n                indices = np.linspace(0, len(dcm_files)-1, self.max_slices, dtype=int)\n                dcm_files = [dcm_files[i] for i in indices]\n        \n        # Try to load middle slice, fallback to others if fails\n        middle_idx = len(dcm_files) // 2\n        image = None\n        \n        # Try middle first, then nearby slices\n        for offset in [0, -5, 5, -10, 10, -20, 20]:\n            try_idx = middle_idx + offset\n            if 0 <= try_idx < len(dcm_files):\n                image = load_dicom_image_safe(str(dcm_files[try_idx]), target_size=256)\n                if image is not None:\n                    break\n        \n        # If all failed, create blank image\n        if image is None:\n            image = np.zeros((256, 256, 3), dtype=np.uint8)\n        \n        # Apply transforms\n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented['image']\n        \n        # Get labels\n        labels = torch.tensor(row[self.label_cols].values.astype(np.float32))\n        \n        return image, labels\n\n# Recreate datasets with fixed loader (set num_workers=0 to avoid multiprocessing issues)\ntrain_dataset = RSNAAneurysmDataset(train_fold, LABEL_COLS, transform=train_transform)\nvalid_dataset = RSNAAneurysmDataset(valid_fold, LABEL_COLS, transform=valid_transform)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=CONFIG['batch_size'],\n    shuffle=True,\n    num_workers=0,  # Changed to 0 to avoid multiprocessing errors\n    pin_memory=True\n)\n\nvalid_loader = DataLoader(\n    valid_dataset,\n    batch_size=CONFIG['batch_size'],\n    shuffle=False,\n    num_workers=0,  # Changed to 0\n    pin_memory=True\n)\n\nprint(\"✓ Fixed dataset and dataloaders created\")\nprint(f\"  Train batches: {len(train_loader)}\")\nprint(f\"  Valid batches: {len(valid_loader)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T19:46:31.835469Z","iopub.execute_input":"2025-09-30T19:46:31.835813Z","iopub.status.idle":"2025-09-30T19:46:41.87843Z","shell.execute_reply.started":"2025-09-30T19:46:31.835782Z","shell.execute_reply":"2025-09-30T19:46:41.877447Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 9: Run training loop\nimport time\n\nbest_score = 0.0\nbest_epoch = 0\nhistory = {'train_loss': [], 'valid_loss': [], 'valid_score': []}\n\nprint(\"=\" * 60)\nprint(\"TRAINING START\")\nprint(\"=\" * 60)\n\nfor epoch in range(CONFIG['epochs']):\n    start_time = time.time()\n    \n    # Train\n    train_loss = train_one_epoch(model, train_loader, criterion, optimizer, device, epoch)\n    \n    # Validate\n    valid_loss, aucs, competition_score = validate(model, valid_loader, criterion, device, epoch)\n    \n    # Update scheduler\n    scheduler.step()\n    \n    # Track history\n    history['train_loss'].append(train_loss)\n    history['valid_loss'].append(valid_loss)\n    history['valid_score'].append(competition_score)\n    \n    # Print epoch summary\n    epoch_time = time.time() - start_time\n    print(f\"\\nEpoch {epoch+1}/{CONFIG['epochs']} - {epoch_time:.1f}s\")\n    print(f\"  Train Loss: {train_loss:.4f}\")\n    print(f\"  Valid Loss: {valid_loss:.4f}\")\n    print(f\"  Competition Score: {competition_score:.4f}\")\n    print(f\"  Aneurysm Present AUC: {aucs[-1]:.4f}\")\n    print(f\"  Mean Vessel AUC: {np.mean(aucs[:-1]):.4f}\")\n    \n    # Save best model\n    if competition_score > best_score:\n        best_score = competition_score\n        best_epoch = epoch + 1\n        torch.save(model.state_dict(), 'best_model.pth')\n        print(f\"  ✓ Best model saved!\")\n    \n    print(\"-\" * 60)\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"TRAINING COMPLETE\")\nprint(\"=\" * 60)\nprint(f\"Best Score: {best_score:.4f} at Epoch {best_epoch}\")\nprint(f\"GPU Time Used: ~{CONFIG['epochs'] * 0.9:.1f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-30T19:46:54.49558Z","iopub.execute_input":"2025-09-30T19:46:54.496194Z","iopub.status.idle":"2025-09-30T21:49:36.278499Z","shell.execute_reply.started":"2025-09-30T19:46:54.496172Z","shell.execute_reply":"2025-09-30T21:49:36.27772Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 11: Submission with lazy model loading\nimport os\nimport torch\nimport torch.nn as nn\nimport timm\nimport polars as pl\nimport numpy as np\nimport cv2\nimport pydicom\nfrom pathlib import Path\nimport kaggle_evaluation.rsna_inference_server\nimport shutil\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Define label columns\nLABEL_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\n# Model class definition (lightweight)\nclass AneurysmClassifier(nn.Module):\n    def __init__(self, model_name='tf_efficientnet_b0_ns', num_classes=14, pretrained=True):\n        super().__init__()\n        self.backbone = timm.create_model(model_name, pretrained=pretrained, num_classes=0)\n        num_features = self.backbone.num_features\n        self.classifier = nn.Sequential(\n            nn.Dropout(0.3),\n            nn.Linear(num_features, num_classes)\n        )\n    \n    def forward(self, x):\n        features = self.backbone(x)\n        return self.classifier(features)\n\ndef predict(series_path: str):\n    \"\"\"Predict function with LAZY model loading\"\"\"\n    global model, device, transform\n    \n    # Load model ONLY on first prediction\n    if 'model' not in globals():\n        print(\"Loading model on first prediction...\")\n        device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n        model = AneurysmClassifier(model_name='tf_efficientnet_b0_ns', num_classes=14, pretrained=False)\n        model.load_state_dict(torch.load('best_model.pth'))\n        model = model.to(device)\n        model.eval()\n        \n        transform = A.Compose([\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n            ToTensorV2()\n        ])\n        print(\"Model loaded successfully\")\n    \n    # Helper functions\n    def apply_window(image, window):\n        window_center, window_width = window\n        img_min = window_center - window_width // 2\n        img_max = window_center + window_width // 2\n        windowed = np.clip(image, img_min, img_max)\n        windowed = ((windowed - img_min) / (img_max - img_min) * 255).astype(np.uint8)\n        return windowed\n    \n    def load_dicom_safe(dcm_path, target_size=256):\n        try:\n            dcm = pydicom.dcmread(dcm_path, force=True)\n            img = dcm.pixel_array.astype(np.float32)\n            \n            if img.size == 0:\n                return None\n            \n            if hasattr(dcm, 'RescaleSlope') and hasattr(dcm, 'RescaleIntercept'):\n                img = img * dcm.RescaleSlope + dcm.RescaleIntercept\n            \n            brain = apply_window(img, (40, 80))\n            subdural = apply_window(img, (80, 200))\n            bone = apply_window(img, (600, 2800))\n            \n            img_3ch = np.stack([brain, subdural, bone], axis=-1)\n            img_resized = cv2.resize(img_3ch, (target_size, target_size))\n            return img_resized\n        except:\n            return None\n    \n    # Get series files\n    series_id = os.path.basename(series_path)\n    dcm_files = sorted(list(Path(series_path).glob('*.dcm')))\n    \n    if len(dcm_files) == 0:\n        predictions = pl.DataFrame(\n            data=[[0.5] * 14],\n            schema=LABEL_COLS,\n            orient='row',\n        )\n        shutil.rmtree('/kaggle/shared', ignore_errors=True)\n        return predictions\n    \n    # Load middle slice\n    middle_idx = len(dcm_files) // 2\n    image = load_dicom_safe(str(dcm_files[middle_idx]), 256)\n    \n    if image is None:\n        image = np.zeros((256, 256, 3), dtype=np.uint8)\n    \n    # Transform and predict\n    transformed = transform(image=image)\n    img_tensor = transformed['image'].unsqueeze(0).to(device)\n    \n    with torch.no_grad():\n        logits = model(img_tensor)\n        probs = torch.sigmoid(logits).cpu().numpy()[0]\n    \n    predictions = pl.DataFrame(\n        data=[probs.tolist()],\n        schema=LABEL_COLS,\n        orient='row',\n    )\n    \n    shutil.rmtree('/kaggle/shared', ignore_errors=True)\n    return predictions\n\n# Create server and start IMMEDIATELY\nprint(\"Starting inference server...\")\ninference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    print(\"Competition mode - serving\")\n    inference_server.serve()  # Called within seconds\nelse:\n    print(\"Local mode - testing\")\n    inference_server.run_local_gateway()\n    \n    if os.path.exists('/kaggle/working/submission.parquet'):\n        import polars as pl\n        sub = pl.read_parquet('/kaggle/working/submission.parquet')\n        print(f\"\\nSubmission shape: {sub.shape}\")\n        print(sub.head())\n\nprint(\"Submission complete\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T08:11:44.684399Z","iopub.execute_input":"2025-10-01T08:11:44.684796Z","iopub.status.idle":"2025-10-01T08:12:02.767494Z","shell.execute_reply.started":"2025-10-01T08:11:44.68477Z","shell.execute_reply":"2025-10-01T08:12:02.766818Z"}},"outputs":[],"execution_count":null}]}