{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from glob import glob\nimport pydicom as dicom #for dicom files\nimport nibabel as nib #for nii files\n\nimport os\nimport shutil\nimport gc\nfrom collections import defaultdict\nfrom typing import Tuple, List\n\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport polars as pl\nimport pydicom\nfrom scipy import ndimage\nfrom sklearn.preprocessing import StandardScaler\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torch.optim as optim\n\nimport kaggle_evaluation.rsna_inference_server\n\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:17:45.807553Z","iopub.execute_input":"2025-10-02T21:17:45.807715Z","iopub.status.idle":"2025-10-02T21:17:57.14435Z","shell.execute_reply.started":"2025-10-02T21:17:45.807697Z","shell.execute_reply":"2025-10-02T21:17:57.143592Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_images = glob(\"/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.10004044428023505108375152878107656647/*\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:17:57.145748Z","iopub.execute_input":"2025-10-02T21:17:57.146168Z","iopub.status.idle":"2025-10-02T21:17:57.210036Z","shell.execute_reply.started":"2025-10-02T21:17:57.146149Z","shell.execute_reply":"2025-10-02T21:17:57.20949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"path = '/kaggle/input/rsna-intracranial-aneurysm-detection/segmentations/1.2.826.0.1.3680043.8.498.10035643165968342618460849823699311381.nii'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:17:57.210679Z","iopub.execute_input":"2025-10-02T21:17:57.210932Z","iopub.status.idle":"2025-10-02T21:17:57.214288Z","shell.execute_reply.started":"2025-10-02T21:17:57.210913Z","shell.execute_reply":"2025-10-02T21:17:57.213574Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(\"/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv\")\nlabel_df = pd.read_csv(\"/kaggle/input/rsna-intracranial-aneurysm-detection/train_localizers.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:17:57.215019Z","iopub.execute_input":"2025-10-02T21:17:57.21551Z","iopub.status.idle":"2025-10-02T21:17:57.289432Z","shell.execute_reply.started":"2025-10-02T21:17:57.215485Z","shell.execute_reply":"2025-10-02T21:17:57.288841Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.style.use('default')\nfig, axes = plt.subplots(4,4, figsize=(12,12))\ntrain_images\nfor i, ax in enumerate(axes.reshape(-1)):\n    img_path = train_images[i]\n    img = dicom.dcmread(img_path)  \n    ax.imshow(img.pixel_array)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:17:57.290261Z","iopub.execute_input":"2025-10-02T21:17:57.290905Z","iopub.status.idle":"2025-10-02T21:17:59.861938Z","shell.execute_reply.started":"2025-10-02T21:17:57.290875Z","shell.execute_reply":"2025-10-02T21:17:59.861001Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img = nib.load(path).get_fdata()\nimg.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:17:59.86271Z","iopub.execute_input":"2025-10-02T21:17:59.863014Z","iopub.status.idle":"2025-10-02T21:18:03.17284Z","shell.execute_reply.started":"2025-10-02T21:17:59.862994Z","shell.execute_reply":"2025-10-02T21:18:03.172242Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.style.use('default')\nfig, axes = plt.subplots(4,4, figsize=(12,12))\nfor i, ax in enumerate(axes.reshape(-1)):\n    ax.imshow(img[:,:,1 + i])\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:03.174994Z","iopub.execute_input":"2025-10-02T21:18:03.175342Z","iopub.status.idle":"2025-10-02T21:18:04.990724Z","shell.execute_reply.started":"2025-10-02T21:18:03.175326Z","shell.execute_reply":"2025-10-02T21:18:04.990006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Check class imbalance\nprint(\"Aneurysm Present: 1 =\", train_df['Aneurysm Present'].mean()*100, \"%\")\n# Check modality distribution\nprint(train_df['Modality'].value_counts())\n# Check location-wise prevalence (critical for multi-label)\nlocations = [col for col in train_df.columns if 'Artery' in col or 'Communicating' in col]\nprint(train_df[locations].sum() / len(train_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:04.991432Z","iopub.execute_input":"2025-10-02T21:18:04.99164Z","iopub.status.idle":"2025-10-02T21:18:05.019475Z","shell.execute_reply.started":"2025-10-02T21:18:04.991622Z","shell.execute_reply":"2025-10-02T21:18:05.018619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.020091Z","iopub.execute_input":"2025-10-02T21:18:05.02027Z","iopub.status.idle":"2025-10-02T21:18:05.055677Z","shell.execute_reply.started":"2025-10-02T21:18:05.020256Z","shell.execute_reply":"2025-10-02T21:18:05.055018Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df['PatientAge'].describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.05644Z","iopub.execute_input":"2025-10-02T21:18:05.056711Z","iopub.status.idle":"2025-10-02T21:18:05.065332Z","shell.execute_reply.started":"2025-10-02T21:18:05.056696Z","shell.execute_reply":"2025-10-02T21:18:05.064695Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df['PatientAge'] = train_df['PatientAge'].astype(int)\nplt.figure(figsize=(10,6))\nsns.histplot(train_df['PatientAge'], bins=20, kde=False, color=sns.color_palette(\"rocket\")[4])  \nplt.xlabel('Patient Age')\nplt.ylabel('Count')\nplt.title('Distribution of Patient Age')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.066242Z","iopub.execute_input":"2025-10-02T21:18:05.066452Z","iopub.status.idle":"2025-10-02T21:18:05.286748Z","shell.execute_reply.started":"2025-10-02T21:18:05.066438Z","shell.execute_reply":"2025-10-02T21:18:05.286113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\nimport matplotlib.pyplot as plt\n\n# Create cross-tabulation with proportions or counts\nctab = pd.crosstab(train_df['PatientSex'], train_df['Aneurysm Present'])\n\n# Plot grouped bar chart\nctab.plot(kind='bar', \n          color=sns.color_palette(\"pastel\"), \n          figsize=(8, 6), \n          width=0.8)\n\n# Labels and title\nplt.xlabel('Patient Sex')\nplt.ylabel('Count')\nplt.title('Aneurysm Presence by Patient Sex')\nplt.legend(title='Aneurysm Present', labels=['No', 'Yes'])\nplt.xticks(rotation=0)\n\n# Add value labels on bars (optional, improves readability)\nfor container in plt.gca().containers:\n    plt.bar_label(container, fmt='%d', padding=3)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.287429Z","iopub.execute_input":"2025-10-02T21:18:05.2877Z","iopub.status.idle":"2025-10-02T21:18:05.565683Z","shell.execute_reply.started":"2025-10-02T21:18:05.28768Z","shell.execute_reply":"2025-10-02T21:18:05.564982Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot pie chart with counts shown on each slice\ntrain_df['Modality'].value_counts().plot(kind='pie', autopct='%d')\n\n# Optional: Improve layout and title\nplt.title('Distribution of Modality')\nplt.ylabel('')  # Hide the y-label (default is 'Modality' from pandas)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.56646Z","iopub.execute_input":"2025-10-02T21:18:05.566674Z","iopub.status.idle":"2025-10-02T21:18:05.683914Z","shell.execute_reply.started":"2025-10-02T21:18:05.566658Z","shell.execute_reply":"2025-10-02T21:18:05.683042Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ID_COL = 'SeriesInstanceUID'\n\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\nDICOM_TAG_ALLOWLIST = [\n    'BitsAllocated', 'BitsStored', 'Columns', 'FrameOfReferenceUID', 'HighBit',\n    'ImageOrientationPatient', 'ImagePositionPatient', 'InstanceNumber', 'Modality',\n    'PatientID', 'PhotometricInterpretation', 'PixelRepresentation', 'PixelSpacing',\n    'PlanarConfiguration', 'RescaleIntercept', 'RescaleSlope', 'RescaleType', 'Rows',\n    'SOPClassUID', 'SOPInstanceUID', 'SamplesPerPixel', 'SliceThickness',\n    'SpacingBetweenSlices', 'StudyInstanceUID', 'TransferSyntaxUID',\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.684828Z","iopub.execute_input":"2025-10-02T21:18:05.685024Z","iopub.status.idle":"2025-10-02T21:18:05.689314Z","shell.execute_reply.started":"2025-10-02T21:18:05.68501Z","shell.execute_reply":"2025-10-02T21:18:05.688767Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Model configuration\nTARGET_SIZE = (64, 64, 64)  # Reduced size for memory efficiency\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\nclass DICOMProcessor:\n    \"\"\"Process DICOM series into normalized 3D volumes\"\"\"\n    \n    def __init__(self, target_size: Tuple[int, int, int] = TARGET_SIZE):\n        self.target_size = target_size\n        self.scaler = StandardScaler()\n    \n    def load_dicom_series(self, series_path: str) -> np.ndarray:\n        \"\"\"Load and process a DICOM series into a 3D volume\"\"\"\n        try:\n            # Get all DICOM files\n            dicom_files = []\n            for root, _, files in os.walk(series_path):\n                for file in files:\n                    if file.endswith('.dcm'):\n                        dicom_files.append(os.path.join(root, file))\n            \n            if not dicom_files:\n                raise ValueError(f\"No DICOM files found in {series_path}\")\n            \n            # Load DICOMs \n            dicoms = []\n            # Initialize dicoms_with_position here (critical fix)\n            dicoms_with_position = []\n            for filepath in dicom_files:\n                try:\n                    ds = pydicom.dcmread(filepath, force=True)\n                    if hasattr(ds, 'PixelData'):\n                        # Extract Z-position (ImagePositionPatient[2]) with robust error handling\n                        z_pos = None\n                        if hasattr(ds, 'ImagePositionPatient') and len(ds.ImagePositionPatient) >= 3:\n                            try:\n                                z_pos = float(ds.ImagePositionPatient[2])\n                            except (ValueError, TypeError):\n                                # Handle case where ImagePositionPatient has non-numeric values\n                                pass\n                        dicoms_with_position.append((ds, filepath, z_pos))\n                except Exception as e:\n                    print(f\"Error reading {filepath}: {e}\")\n                    continue\n            \n            if not dicoms:\n                raise ValueError(f\"No valid DICOM files with pixel data in {series_path}\")\n            \n            # Sort by instance number\n            # dicoms.sort(key=lambda x: getattr(x[0], 'InstanceNumber', 0))\n            # CRITICAL FIX: Sort by physical Z-position, not InstanceNumber\n            # First, filter out any slices without valid Z-position\n            valid_slices = [(ds, filepath, z_pos) for ds, filepath, z_pos in dicoms_with_position if z_pos is not None]\n            \n            if not valid_slices:\n                # If no slices have valid Z-position, fall back to InstanceNumber as last resort\n                print(f\"Warning: No valid ImagePositionPatient found in {series_path}, using InstanceNumber\")\n                dicoms_with_position.sort(key=lambda x: getattr(x[0], 'InstanceNumber', 0))\n                dicoms = [(ds, filepath) for ds, filepath, _ in dicoms_with_position]\n            else:\n                # Primary method: Sort by physical Z-position\n                valid_slices.sort(key=lambda x: x[2])  # Sort by z_pos\n                dicoms = [(ds, filepath) for ds, filepath, _ in valid_slices]\n            \n            # Extract volume\n            volume_slices = []\n            for ds, _ in dicoms:\n                try:\n                    # Get pixel array\n                    pixel_array = ds.pixel_array.astype(np.float32)\n                    \n                    # Apply rescale if available\n                    if hasattr(ds, 'RescaleSlope') and hasattr(ds, 'RescaleIntercept'):\n                        slope = float(ds.RescaleSlope)\n                        intercept = float(ds.RescaleIntercept)\n                        pixel_array = pixel_array * slope + intercept\n                    \n                    volume_slices.append(pixel_array)\n                except Exception as e:\n                    print(f\"Error processing slice: {e}\")\n                    continue\n            \n            if not volume_slices:\n                raise ValueError(\"No valid slices extracted\")\n            \n            # Stack into 3D volume\n            volume = np.stack(volume_slices, axis=0)  # Shape: (depth, height, width)\n            \n            # Normalize and resize\n            volume = self.preprocess_volume(volume)\n            \n            return volume\n            \n        except Exception as e:\n            print(f\"Error processing series {series_path}: {e}\")\n            # Return zeros if processing fails\n            return np.zeros(self.target_size, dtype=np.float32)\n    \n    def preprocess_volume(self, volume: np.ndarray) -> np.ndarray:\n        \"\"\"Preprocess 3D volume: normalize, clip, resize\"\"\"\n        # Handle potential issues\n        if volume.size == 0:\n            return np.zeros(self.target_size, dtype=np.float32)\n        \n        # Clip extreme values (robust to outliers)\n        p1, p99 = np.percentile(volume, [1, 99])\n        volume = np.clip(volume, p1, p99)\n        \n        # Normalize to [0, 1]\n        volume_min, volume_max = volume.min(), volume.max()\n        if volume_max > volume_min:\n            volume = (volume - volume_min) / (volume_max - volume_min)\n        \n        # Resize to target size\n        if volume.shape != self.target_size:\n            zoom_factors = [\n                self.target_size[i] / volume.shape[i] for i in range(3)\n            ]\n            volume = ndimage.zoom(volume, zoom_factors, order=1)\n        \n        return volume.astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.690025Z","iopub.execute_input":"2025-10-02T21:18:05.690194Z","iopub.status.idle":"2025-10-02T21:18:05.784596Z","shell.execute_reply.started":"2025-10-02T21:18:05.69018Z","shell.execute_reply":"2025-10-02T21:18:05.78393Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Simple3DCNN(nn.Module):\n    \"\"\"Lightweight 3D CNN for aneurysm detection\"\"\"\n    \n    def __init__(self, num_classes: int = len(LABEL_COLS)):\n        super(Simple3DCNN, self).__init__()\n        \n        # 3D Convolutional layers\n        self.conv1 = nn.Conv3d(1, 16, kernel_size=3, padding=1)\n        self.pool1 = nn.MaxPool3d(2)\n        self.conv2 = nn.Conv3d(16, 32, kernel_size=3, padding=1)\n        self.pool2 = nn.MaxPool3d(2)\n        self.conv3 = nn.Conv3d(32, 64, kernel_size=3, padding=1)\n        self.pool3 = nn.MaxPool3d(2)\n        self.conv4 = nn.Conv3d(64, 128, kernel_size=3, padding=1)\n        self.pool4 = nn.MaxPool3d(2)\n        \n        # Adaptive pooling to handle variable sizes\n        self.adaptive_pool = nn.AdaptiveAvgPool3d((2, 2, 2))\n        \n        # Fully connected layers\n        self.fc1 = nn.Linear(128 * 2 * 2 * 2, 256)\n        self.dropout1 = nn.Dropout(0.5)\n        self.fc2 = nn.Linear(256, 128)\n        self.dropout2 = nn.Dropout(0.3)\n        self.fc3 = nn.Linear(128, num_classes)\n        \n        # Batch normalization\n        self.bn1 = nn.BatchNorm3d(16)\n        self.bn2 = nn.BatchNorm3d(32)\n        self.bn3 = nn.BatchNorm3d(64)\n        self.bn4 = nn.BatchNorm3d(128)\n        \n    def forward(self, x):\n        # Input shape: (batch_size, 1, depth, height, width)\n        x = self.pool1(F.relu(self.bn1(self.conv1(x))))\n        x = self.pool2(F.relu(self.bn2(self.conv2(x))))\n        x = self.pool3(F.relu(self.bn3(self.conv3(x))))\n        x = self.pool4(F.relu(self.bn4(self.conv4(x))))\n        \n        # Adaptive pooling\n        x = self.adaptive_pool(x)\n        \n        # Flatten\n        x = x.view(x.size(0), -1)\n        \n        # Fully connected layers\n        x = F.relu(self.fc1(x))\n        x = self.dropout1(x)\n        x = F.relu(self.fc2(x))\n        x = self.dropout2(x)\n        x = self.fc3(x)\n        \n        return torch.sigmoid(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.785488Z","iopub.execute_input":"2025-10-02T21:18:05.78571Z","iopub.status.idle":"2025-10-02T21:18:05.808283Z","shell.execute_reply.started":"2025-10-02T21:18:05.785693Z","shell.execute_reply":"2025-10-02T21:18:05.807746Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AneurysmDataset(Dataset):\n    \"\"\"Dataset for loading training data\"\"\"\n    \n    def __init__(self, data_df: pd.DataFrame, series_dir: str, processor: DICOMProcessor):\n        self.data_df = data_df\n        self.series_dir = series_dir\n        self.processor = processor\n        \n    def __len__(self):\n        return len(self.data_df)\n    \n    def __getitem__(self, idx):\n        row = self.data_df.iloc[idx]\n        series_id = row[ID_COL]\n        \n        # Load volume\n        series_path = os.path.join(self.series_dir, series_id)\n        volume = self.processor.load_dicom_series(series_path)\n        \n        # Get labels\n        labels = row[LABEL_COLS].values.astype(np.float32)\n        \n        # Convert to tensor and add channel dimension\n        volume_tensor = torch.from_numpy(volume).unsqueeze(0)  # Add channel dim\n        labels_tensor = torch.from_numpy(labels)\n        \n        return volume_tensor, labels_tensor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.809049Z","iopub.execute_input":"2025-10-02T21:18:05.809285Z","iopub.status.idle":"2025-10-02T21:18:05.833907Z","shell.execute_reply.started":"2025-10-02T21:18:05.809264Z","shell.execute_reply":"2025-10-02T21:18:05.833319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Global model and processor\nmodel = None\nprocessor = None\n\ndef initialize_model():\n    \"\"\"Initialize model and processor (called once)\"\"\"\n    global model, processor\n    \n    if model is not None:\n        return\n    \n    print(\"Initializing model...\")\n    processor = DICOMProcessor(TARGET_SIZE)\n    model = Simple3DCNN(num_classes=len(LABEL_COLS))\n    \n    # Load pre-trained weights if available\n    try:\n        if os.path.exists('/kaggle/input/model_weights.pth'):\n            model.load_state_dict(torch.load('/kaggle/input/model_weights.pth', map_location='cpu'))\n            print(\"Loaded pre-trained weights\")\n        else:\n            print(\"No pre-trained weights found, using random initialization\")\n    except Exception as e:\n        print(f\"Error loading weights: {e}\")\n    \n    model.to(DEVICE)\n    model.eval()\n    print(f\"Model initialized on {DEVICE}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.834751Z","iopub.execute_input":"2025-10-02T21:18:05.835032Z","iopub.status.idle":"2025-10-02T21:18:05.856341Z","shell.execute_reply.started":"2025-10-02T21:18:05.83501Z","shell.execute_reply":"2025-10-02T21:18:05.855763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_model(series_dir=\"/kaggle/input/rsna-intracranial-aneurysm-detection/series\",\n                num_epochs=2, batch_size=4, checkpoint_path=\"checkpoint.pth\"):\n    \"\"\"\n    Quick training function for Kaggle GPU (P100).\n    Trains on a small subset so you can see output quickly.\n    \"\"\"\n\n    # Load training CSV\n    train_df = pd.read_csv(\"/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv\")\n\n    # Create processor + model\n    processor = DICOMProcessor(TARGET_SIZE)\n    model = Simple3DCNN(num_classes=len(LABEL_COLS))\n    model.to(DEVICE)\n\n    # Resume training if checkpoint exists\n    if os.path.exists(checkpoint_path):\n        print(\"Resuming from checkpoint...\")\n        model.load_state_dict(torch.load(checkpoint_path, map_location=DEVICE))\n\n    # Use only 50 random samples for fast training\n    dataset = AneurysmDataset(train_df.sample(50, random_state=42), series_dir, processor)\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=0)\n\n    criterion = nn.BCELoss()\n    optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)\n\n    # Training loop\n    model.train()\n    for epoch in range(num_epochs):\n        total_loss = 0\n        for batch_idx, (volumes, labels) in enumerate(dataloader):\n            volumes, labels = volumes.to(DEVICE), labels.to(DEVICE)\n            optimizer.zero_grad()\n            outputs = model(volumes)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            total_loss += loss.item()\n\n        avg_loss = total_loss / len(dataloader)\n        print(f\"Epoch {epoch+1}/{num_epochs}, Avg Loss: {avg_loss:.4f}\")\n\n    # Save checkpoint\n    torch.save(model.state_dict(), checkpoint_path)\n    print(f\"✅ Checkpoint saved to {checkpoint_path}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.941405Z","iopub.execute_input":"2025-10-02T21:18:05.941582Z","iopub.status.idle":"2025-10-02T21:18:05.959352Z","shell.execute_reply.started":"2025-10-02T21:18:05.941569Z","shell.execute_reply":"2025-10-02T21:18:05.958818Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_model(num_epochs=2, batch_size=4)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:18:05.959968Z","iopub.execute_input":"2025-10-02T21:18:05.960168Z","iopub.status.idle":"2025-10-02T21:20:36.695751Z","shell.execute_reply.started":"2025-10-02T21:18:05.960153Z","shell.execute_reply":"2025-10-02T21:20:36.695092Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(series_path, model=None):\n    \"\"\"\n    Loads a DICOM series and outputs model predictions.\n    Returns a numpy array of probabilities.\n    \"\"\"\n    global processor\n    if model is None:\n        model = Simple3DCNN(num_classes=len(LABEL_COLS))\n        model.load_state_dict(torch.load(\"checkpoint.pth\", map_location=DEVICE))\n        model.to(DEVICE)\n        model.eval()\n\n    if processor is None:\n        processor = DICOMProcessor(TARGET_SIZE)\n\n    # Process DICOM into volume\n    try:\n        volume = processor.load_dicom_series(series_path)\n    except Exception as e:\n        print(f\"Failed to process {series_path}: {e}\")\n        volume = np.zeros(TARGET_SIZE, dtype=np.float32)\n\n    # Convert to tensor and add batch dimension\n    volume_tensor = torch.tensor(volume, dtype=torch.float32).unsqueeze(0).unsqueeze(0).to(DEVICE)\n\n    with torch.no_grad():\n        preds = model(volume_tensor).cpu().numpy().flatten()\n\n    return preds\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:20:36.696641Z","iopub.execute_input":"2025-10-02T21:20:36.697124Z","iopub.status.idle":"2025-10-02T21:20:36.70277Z","shell.execute_reply.started":"2025-10-02T21:20:36.697098Z","shell.execute_reply":"2025-10-02T21:20:36.702272Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -----------------------------\n# Quick submission to appear on leaderboard\n# -----------------------------\nimport os\nimport torch\nimport numpy as np\nimport pandas as pd\n\n# Use your existing LABEL_COLS and TARGET_SIZE\n# DEVICE already defined\n# Simple3DCNN and DICOMProcessor already defined\n\n# Load trained model\nmodel = Simple3DCNN(num_classes=len(LABEL_COLS))\nmodel.load_state_dict(torch.load(\"checkpoint.pth\", map_location=DEVICE))\nmodel.to(DEVICE)\nmodel.eval()\n\n# Initialize processor\nprocessor = DICOMProcessor(TARGET_SIZE)\n\n# Function to predict one series\ndef predict_fast(series_path):\n    try:\n        volume = processor.load_dicom_series(series_path)\n    except:\n        volume = np.zeros(TARGET_SIZE, dtype=np.float32)\n    vol_tensor = torch.tensor(volume, dtype=torch.float32).unsqueeze(0).unsqueeze(0).to(DEVICE)\n    with torch.no_grad():\n        preds = model(vol_tensor).cpu().numpy().flatten()\n    return preds\n\n# Directory with series\nseries_dir = \"/kaggle/input/rsna-intracranial-aneurysm-detection/series\"\nseries_ids = os.listdir(series_dir)\n\n# Limit for quick leaderboard submission\nLIMIT = 200  # Adjust to speed up; you can increase later if needed\nseries_ids = series_ids[:LIMIT]\n\nsubmission = []\nfor sid in series_ids:\n    spath = os.path.join(series_dir, sid)\n    preds = predict_fast(spath)\n    submission.append([sid] + preds.tolist())\n\n# Create submission DataFrame\ncols = [\"ID\"] + LABEL_COLS\nsubmission_df = pd.DataFrame(submission, columns=cols)\n\n# Save in Kaggle-required format\nsubmission_df.to_parquet(\"submission.parquet\", index=False)\nprint(f\"✅ Quick submission file saved: submission.parquet\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T21:57:20.011264Z","iopub.execute_input":"2025-10-02T21:57:20.011542Z","iopub.status.idle":"2025-10-02T22:04:00.680059Z","shell.execute_reply.started":"2025-10-02T21:57:20.011521Z","shell.execute_reply":"2025-10-02T22:04:00.679341Z"}},"outputs":[],"execution_count":null}]}