{"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":"gpu","dataSources":[{"sourceType":"competition","sourceId":99552,"databundleVersionId":13441085}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nimport pydicom\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score\nfrom tqdm import tqdm\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Configuration\nclass Config:\n    data_dir = '/kaggle/input/rsna-intracranial-aneurysm-detection'\n    batch_size = 4\n    learning_rate = 1e-4\n    num_epochs = 30\n    image_size = (128, 128)\n    num_slices = 32\n    num_classes = 14\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    num_workers = 2\n\n# Weighted Loss Function (missing from previous code)\nclass WeightedBCELoss(nn.Module):\n    def __init__(self, weights):\n        super(WeightedBCELoss, self).__init__()\n        self.weights = weights\n    \n    def forward(self, inputs, targets):\n        loss = nn.BCEWithLogitsLoss(reduction='none')(inputs, targets)\n        weighted_loss = loss * self.weights\n        return weighted_loss.mean()\n\n# Optimized Dataset\nclass AneurysmDataset(Dataset):\n    def __init__(self, df, base_path, transform=None, is_train=True):\n        self.df = df.reset_index(drop=True)\n        self.base_path = base_path\n        self.transform = transform\n        self.is_train = is_train\n        self.series_info = self._precompute_series_info()\n        \n    def _precompute_series_info(self):\n        series_info = []\n        for idx, row in self.df.iterrows():\n            series_info.append({\n                'series_id': row['SeriesInstanceUID'],\n                'labels': row[[\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                ]].values.astype(np.float32)\n            })\n        return series_info\n        \n    def __len__(self):\n        return len(self.series_info)\n    \n    def load_dicom_series_efficient(self, series_id):\n        series_path = os.path.join(self.base_path, 'series', series_id)\n        if not os.path.exists(series_path):\n            return np.zeros((3, Config.num_slices, Config.image_size[0], Config.image_size[1]), dtype=np.float32)\n        \n        try:\n            dicom_files = [f for f in os.listdir(series_path) if f.endswith('.dcm')]\n            if not dicom_files:\n                return np.zeros((3, Config.num_slices, Config.image_size[0], Config.image_size[1]), dtype=np.float32)\n            \n            # Sort files by instance number\n            dicom_files.sort(key=lambda x: int(pydicom.dcmread(os.path.join(series_path, x), stop_before_pixels=True).InstanceNumber))\n            \n            # Sample slices\n            num_files = len(dicom_files)\n            step = max(1, num_files // Config.num_slices)\n            selected_indices = range(0, min(num_files, Config.num_slices * step), step)\n            selected_files = [dicom_files[i] for i in selected_indices[:Config.num_slices]]\n            \n            slices = []\n            for file in selected_files:\n                try:\n                    dicom = pydicom.dcmread(os.path.join(series_path, file), stop_before_pixels=False)\n                    img = dicom.pixel_array.astype(np.float32)\n                    \n                    if hasattr(dicom, 'RescaleSlope') and hasattr(dicom, 'RescaleIntercept'):\n                        img = img * dicom.RescaleSlope + dicom.RescaleIntercept\n                    \n                    # Resize if needed\n                    if img.shape != Config.image_size:\n                        import cv2\n                        img = cv2.resize(img, Config.image_size, interpolation=cv2.INTER_AREA)\n                    \n                    # Normalize\n                    img_min, img_max = img.min(), img.max()\n                    if img_max > img_min:\n                        img = (img - img_min) / (img_max - img_min)\n                    else:\n                        img = np.zeros_like(img)\n                    \n                    slices.append(img)\n                except Exception as e:\n                    slices.append(np.zeros(Config.image_size, dtype=np.float32))\n            \n            # Pad if needed\n            while len(slices) < Config.num_slices:\n                slices.append(np.zeros(Config.image_size, dtype=np.float32))\n            \n            # Stack slices and add channel dimension\n            volume = np.stack(slices, axis=0)  # Shape: [depth, height, width]\n            volume = np.repeat(volume[np.newaxis, :, :, :], 3, axis=0)  # Shape: [channels, depth, height, width]\n            \n            return volume\n            \n        except Exception as e:\n            print(f\"Error loading series {series_id}: {e}\")\n            return np.zeros((3, Config.num_slices, Config.image_size[0], Config.image_size[1]), dtype=np.float32)\n    \n    def __getitem__(self, idx):\n        series_info = self.series_info[idx]\n        series_id = series_info['series_id']\n        labels = series_info['labels']\n        \n        # Load DICOM volume - already in correct format [channels, depth, height, width]\n        volume = self.load_dicom_series_efficient(series_id)\n        \n        if self.transform:\n            volume = self.transform(volume)\n        \n        return torch.FloatTensor(volume), torch.FloatTensor(labels)\n\n# Fixed 3D CNN Model with correct input handling\nclass SimpleAneurysm3DCNN(nn.Module):\n    def __init__(self, num_classes):\n        super(SimpleAneurysm3DCNN, self).__init__()\n        \n        self.features = nn.Sequential(\n            # Block 1\n            nn.Conv3d(3, 16, kernel_size=3, padding=1),\n            nn.BatchNorm3d(16),\n            nn.ReLU(inplace=True),\n            nn.MaxPool3d(kernel_size=2),\n            \n            # Block 2\n            nn.Conv3d(16, 32, kernel_size=3, padding=1),\n            nn.BatchNorm3d(32),\n            nn.ReLU(inplace=True),\n            nn.MaxPool3d(kernel_size=2),\n            \n            # Block 3\n            nn.Conv3d(32, 64, kernel_size=3, padding=1),\n            nn.BatchNorm3d(64),\n            nn.ReLU(inplace=True),\n            nn.MaxPool3d(kernel_size=2),\n        )\n        \n        # Calculate flattened size\n        self._calculate_flattened_size()\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(self.flattened_size, 128),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.3),\n            nn.Linear(128, num_classes)\n        )\n    \n    def _calculate_flattened_size(self):\n        with torch.no_grad():\n            # Input shape: [batch, channels, depth, height, width]\n            mock_input = torch.zeros(1, 3, Config.num_slices, Config.image_size[0], Config.image_size[1])\n            mock_output = self.features(mock_input)\n            self.flattened_size = mock_output.numel()\n    \n    def forward(self, x):\n        x = self.features(x)\n        x = x.view(x.size(0), -1)\n        x = self.classifier(x)\n        return x\n\n# Training Function\ndef train_model_memory_efficient(model, train_loader, val_loader, criterion, optimizer, scheduler, num_epochs):\n    best_score = 0\n    train_losses = []\n    val_scores = []\n    \n    for epoch in range(num_epochs):\n        model.train()\n        running_loss = 0.0\n        \n        # Training phase\n        optimizer.zero_grad()\n        \n        for batch_idx, (images, labels) in enumerate(tqdm(train_loader, desc=f'Epoch {epoch+1}/{num_epochs}')):\n            images = images.to(Config.device)\n            labels = labels.to(Config.device)\n            \n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            \n            running_loss += loss.item() * images.size(0)\n            \n            # Update weights\n            optimizer.step()\n            optimizer.zero_grad()\n                \n            # Clear cache\n            if torch.cuda.is_available():\n                torch.cuda.empty_cache()\n        \n        epoch_loss = running_loss / len(train_loader.dataset)\n        train_losses.append(epoch_loss)\n        \n        # Validation phase\n        model.eval()\n        all_preds = []\n        all_labels = []\n        \n        with torch.no_grad():\n            for images, labels in val_loader:\n                images = images.to(Config.device)\n                labels = labels.to(Config.device)\n                \n                outputs = model(images)\n                preds = torch.sigmoid(outputs)\n                \n                all_preds.append(preds.cpu().numpy())\n                all_labels.append(labels.cpu().numpy())\n        \n        all_preds = np.concatenate(all_preds)\n        all_labels = np.concatenate(all_labels)\n        \n        # Calculate weighted AUC\n        auc_scores = []\n        for i in range(Config.num_classes):\n            if len(np.unique(all_labels[:, i])) > 1:\n                try:\n                    auc = roc_auc_score(all_labels[:, i], all_preds[:, i])\n                    auc_scores.append(auc)\n                except:\n                    auc_scores.append(0.5)\n            else:\n                auc_scores.append(0.5)\n        \n        weighted_auc = (auc_scores[-1] * 13 + sum(auc_scores[:-1])) / (13 + len(auc_scores) - 1)\n        val_scores.append(weighted_auc)\n        \n        print(f'Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}, Val AUC: {weighted_auc:.4f}')\n        \n        if weighted_auc > best_score:\n            best_score = weighted_auc\n            torch.save(model.state_dict(), 'best_model.pth')\n        \n        scheduler.step()\n    \n    return train_losses, val_scores\n\n# Main execution\ndef main():\n    print(\"Loading data...\")\n    train_df = pd.read_csv(os.path.join(Config.data_dir, 'train.csv'))\n    \n    # Use smaller subset for testing\n    sample_size = min(500, len(train_df))  # Even smaller for testing\n    train_df = train_df.sample(sample_size, random_state=42).reset_index(drop=True)\n    \n    print(f\"Using {len(train_df)} samples for training/validation\")\n    \n    # Split data\n    train_data, val_data = train_test_split(\n        train_df, test_size=0.2, random_state=42, stratify=train_df['Aneurysm Present']\n    )\n    \n    # Simple transform\n    transform = transforms.Compose([\n        transforms.Lambda(lambda x: x),  # Already converted to tensor in dataset\n    ])\n    \n    # Create datasets\n    train_dataset = AneurysmDataset(train_data, Config.data_dir, transform=transform)\n    val_dataset = AneurysmDataset(val_data, Config.data_dir, transform=transform)\n    \n    # Create dataloaders\n    train_loader = DataLoader(\n        train_dataset, \n        batch_size=Config.batch_size, \n        shuffle=True, \n        num_workers=Config.num_workers,\n        pin_memory=False  # Changed to False to avoid memory issues\n    )\n    val_loader = DataLoader(\n        val_dataset, \n        batch_size=Config.batch_size, \n        shuffle=False, \n        num_workers=Config.num_workers,\n        pin_memory=False\n    )\n    \n    # Initialize model\n    model = SimpleAneurysm3DCNN(Config.num_classes).to(Config.device)\n    print(f\"Model parameters: {sum(p.numel() for p in model.parameters()):,}\")\n    \n    # Loss weights\n    loss_weights = torch.ones(Config.num_classes).to(Config.device)\n    loss_weights[-1] = 13.0\n    criterion = WeightedBCELoss(loss_weights)\n    \n    # Optimizer\n    optimizer = optim.Adam(model.parameters(), lr=Config.learning_rate)\n    scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)\n    \n    print(\"Starting training...\")\n    train_losses, val_scores = train_model_memory_efficient(\n        model, train_loader, val_loader, criterion, optimizer, scheduler, Config.num_epochs\n    )\n    \n    print(f'Best validation score: {max(val_scores):.4f}')\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-26T10:58:59.830185Z","iopub.execute_input":"2025-08-26T10:58:59.830399Z","iopub.status.idle":"2025-08-26T13:03:23.777558Z","shell.execute_reply.started":"2025-08-26T10:58:59.83038Z","shell.execute_reply":"2025-08-26T13:03:23.776679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}