{"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":[{"sourceId":99552,"databundleVersionId":13441085,"sourceType":"competition"},{"sourceId":4696290,"sourceType":"datasetVersion","datasetId":1542467},{"sourceId":12780021,"sourceType":"datasetVersion","datasetId":8079690},{"sourceId":12949669,"sourceType":"datasetVersion","datasetId":8195119},{"sourceId":556489,"sourceType":"modelInstanceVersion","modelInstanceId":423309,"modelId":440840},{"sourceId":556510,"sourceType":"modelInstanceVersion","modelInstanceId":423320,"modelId":440850}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# RadImageNet - LSTM GRU RNN Approach","metadata":{}},{"cell_type":"code","source":"input_monaipath = \"/kaggle/input/monai-v060-deep-learning-in-healthcare-imaging/MONAI-1.0.0\"\nimport sys\nsys.path.append(input_monaipath)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:34:08.59766Z","iopub.execute_input":"2025-09-09T03:34:08.597898Z","iopub.status.idle":"2025-09-09T03:34:08.604163Z","shell.execute_reply.started":"2025-09-09T03:34:08.597879Z","shell.execute_reply":"2025-09-09T03:34:08.603472Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import monai","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:34:08.604748Z","iopub.execute_input":"2025-09-09T03:34:08.605039Z","iopub.status.idle":"2025-09-09T03:34:39.500153Z","shell.execute_reply.started":"2025-09-09T03:34:08.605013Z","shell.execute_reply":"2025-09-09T03:34:39.499572Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# efficient net combined for 12 labels, 0 and 12 by radimagent and gru\n\"\"\"\nRSNA 2025 Brain Aneurysm Detection - Robust Inference Pipeline\nMaintains exact preprocessing from training while adding robustness and optimizations\n\"\"\"\n\nimport os\nimport sys\nimport gc\nimport json\nimport shutil\nimport warnings\nimport logging\nfrom pathlib import Path\nfrom typing import List, Tuple, Dict, Optional, Union\nfrom dataclasses import dataclass\nfrom enum import Enum\n\nimport numpy as np\nimport polars as pl\nimport pandas as pd\n\nimport pydicom\nimport cv2\nfrom scipy import ndimage\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.cuda.amp import autocast\nimport timm\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport kaggle_evaluation.rsna_inference_server\n\nwarnings.filterwarnings('ignore')\n\n# Configure logging\nlogging.basicConfig(\n    level=logging.INFO,\n    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\nlogger = logging.getLogger(__name__)\n\n# ====================================================\n# Constants\n# ====================================================\nID_COL = 'SeriesInstanceUID'\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# Anatomical groupings for post-processing\nLEFT_VESSEL_INDICES = [0, 2, 4, 7, 9]\nRIGHT_VESSEL_INDICES = [1, 3, 5, 8, 10]\nMIDLINE_VESSEL_INDICES = [6, 11, 12]  # Anterior communicating, Basilar, Other posterior\n\n# ====================================================\n# Configuration\n# ====================================================\n@dataclass\nclass InferenceConfig:\n    \"\"\"Configuration for robust inference\"\"\"\n    # Model settings\n    model_name: str = \"tf_efficientnetv2_s.in21k_ft_in1k\"\n    size: int = 384\n    num_classes: int = 14\n    in_chans: int = 32\n    \n    # CRITICAL: Keep exact preprocessing from training\n    target_shape: Tuple[int, int, int] = (32, 384, 384)\n    force_rescale_identity: bool = True  # Keep slope=1, intercept=0 as in training\n    fixed_normalization_range: Tuple[int, int] = (0, 500)  # Keep hardcoded values\n    \n    # Inference settings\n    batch_size: int = 1\n    use_amp: bool = True\n    use_tta: bool = False  # No horizontal flips due to L/R anatomy\n    use_brightness_tta: bool = True  # Safe TTA that doesn't affect L/R\n    \n    # Model paths\n    model_dir: str = '/kaggle/input/rsna2025-effnetv2-32ch'\n    n_fold: int = 5\n    trn_fold: List[int] = None\n    \n    # Ensemble settings\n    ensemble_method: str = 'geometric_mean'  # Better for probabilities\n    temperature_scaling: float = 1.0\n    \n    # Robustness settings\n    fallback_predictions: float = 0.1  # Conservative but not too low\n    max_retries: int = 3\n    cleanup_after_each: bool = True\n    \n    def __post_init__(self):\n        if self.trn_fold is None:\n            self.trn_fold = list(range(self.n_fold))\n\nCFG = InferenceConfig()\n\n# Set device\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nlogger.info(f\"Using device: {device}\")\n\n# ====================================================\n# DICOM Preprocessing (Maintain exact training logic)\n# ====================================================\nclass DICOMPreprocessorRobust:\n    \"\"\"\n    Robust DICOM preprocessing that maintains exact training preprocessing\n    \"\"\"\n    \n    def __init__(self, target_shape: Tuple[int, int, int] = (32, 384, 384)):\n        self.target_depth, self.target_height, self.target_width = target_shape\n        self.stats = {}  # Collect statistics for debugging\n        \n    def load_dicom_series(self, series_path: str) -> Tuple[List[pydicom.Dataset], str]:\n        \"\"\"Load DICOM series with robust error handling\"\"\"\n        series_path = Path(series_path)\n        series_name = series_path.name\n        \n        # Find 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        logger.debug(f\"Found {len(dicom_files)} DICOM files in series {series_name}\")\n        \n        # Load DICOM datasets with error handling\n        datasets = []\n        load_errors = 0\n        \n        for filepath in dicom_files:\n            try:\n                ds = pydicom.dcmread(filepath, force=True)\n                # Verify pixel data exists\n                if hasattr(ds, 'pixel_array'):\n                    datasets.append(ds)\n                else:\n                    load_errors += 1\n            except Exception as e:\n                logger.debug(f\"Failed to load {filepath}: {e}\")\n                load_errors += 1\n        \n        if not datasets:\n            raise ValueError(f\"No valid DICOM files in {series_path} (failed: {load_errors}/{len(dicom_files)})\")\n        \n        if load_errors > 0:\n            logger.warning(f\"Loaded {len(datasets)}/{len(dicom_files)} DICOM files (errors: {load_errors})\")\n        \n        return datasets, series_name\n    \n    def extract_slice_info(self, datasets: List[pydicom.Dataset]) -> List[Dict]:\n        \"\"\"Extract slice position information\"\"\"\n        slice_info = []\n        \n        for i, ds in enumerate(datasets):\n            info = {\n                'dataset': ds,\n                'index': i,\n                'instance_number': getattr(ds, 'InstanceNumber', i),\n            }\n            \n            # Get z-coordinate with multiple fallbacks\n            try:\n                position = getattr(ds, 'ImagePositionPatient', None)\n                if position is not None and len(position) >= 3:\n                    info['z_position'] = float(position[2])\n                elif hasattr(ds, 'SliceLocation'):\n                    info['z_position'] = float(ds.SliceLocation)\n                else:\n                    info['z_position'] = float(info['instance_number'])\n            except Exception:\n                info['z_position'] = float(i)\n            \n            slice_info.append(info)\n        \n        return slice_info\n    \n    def sort_slices_by_position(self, slice_info: List[Dict]) -> List[Dict]:\n        \"\"\"Sort slices by z-coordinate and remove duplicates\"\"\"\n        # Remove duplicates based on z-position\n        seen_positions = {}\n        unique_slices = []\n        \n        for info in slice_info:\n            z_pos = round(info['z_position'], 3)\n            if z_pos not in seen_positions:\n                seen_positions[z_pos] = info\n                unique_slices.append(info)\n        \n        sorted_slices = sorted(unique_slices, key=lambda x: x['z_position'])\n        \n        if len(sorted_slices) != len(slice_info):\n            logger.debug(f\"Removed {len(slice_info) - len(sorted_slices)} duplicate slices\")\n        \n        return sorted_slices\n    \n    def get_windowing_params(self, ds: pydicom.Dataset) -> Tuple[Optional[str], Optional[str]]:\n        \"\"\"\n        CRITICAL: Return exact values used in training\n        The original code returns \"CT\", \"CT\" for CT modality\n        \"\"\"\n        modality = getattr(ds, 'Modality', 'CT')\n        \n        if modality == 'CT':\n            # Return exactly what training code expects\n            return \"CT\", \"CT\"\n        elif modality == 'MR':\n            return None, None\n        else:\n            return None, None\n    \n    def apply_windowing_or_normalize(self, img: np.ndarray, center: Optional[str], \n                                    width: Optional[str]) -> np.ndarray:\n        \"\"\"\n        CRITICAL: Apply exact normalization from training\n        Must maintain the hardcoded p1=0, p99=500 for CT\n        \"\"\"\n        if center is not None and width is not None:\n            # For CT: use hardcoded normalization (this is what the model expects)\n            p1, p99 = 0, 500  # CRITICAL: These exact values were used in training\n            \n            if p99 > p1:\n                normalized = np.clip(img, p1, p99)\n                normalized = (normalized - p1) / (p99 - p1)\n                result = (normalized * 255).astype(np.uint8)\n                return result\n            else:\n                # Fallback\n                img_min, img_max = img.min(), img.max()\n                if img_max > img_min:\n                    normalized = (img - img_min) / (img_max - img_min)\n                    result = (normalized * 255).astype(np.uint8)\n                    return result\n                else:\n                    return np.zeros_like(img, dtype=np.uint8)\n        else:\n            # For MR: statistical normalization\n            p1, p99 = np.percentile(img, [1, 99])\n            \n            if p99 > p1:\n                normalized = np.clip(img, p1, p99)\n                normalized = (normalized - p1) / (p99 - p1)\n                result = (normalized * 255).astype(np.uint8)\n                return result\n            else:\n                # Fallback\n                img_min, img_max = img.min(), img.max()\n                if img_max > img_min:\n                    normalized = (img - img_min) / (img_max - img_min)\n                    result = (normalized * 255).astype(np.uint8)\n                    return result\n                else:\n                    return np.zeros_like(img, dtype=np.uint8)\n    \n    def extract_pixel_array(self, ds: pydicom.Dataset) -> np.ndarray:\n        \"\"\"\n        Extract 2D pixel array\n        CRITICAL: Force slope=1, intercept=0 as in original training code\n        \"\"\"\n        img = ds.pixel_array.astype(np.float32)\n        \n        # Handle 3D volumes (select middle frame)\n        if img.ndim == 3:\n            frame_idx = img.shape[0] // 2\n            img = img[frame_idx]\n        \n        # Convert color to grayscale if needed\n        if img.ndim == 3 and img.shape[-1] == 3:\n            img = cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_RGB2GRAY).astype(np.float32)\n        \n        # CRITICAL: Force identity transform (this is what training used)\n        if CFG.force_rescale_identity:\n            slope, intercept = 1, 0\n        else:\n            slope = getattr(ds, 'RescaleSlope', 1)\n            intercept = getattr(ds, 'RescaleIntercept', 0)\n        \n        if slope != 1 or intercept != 0:\n            img = img * float(slope) + float(intercept)\n        \n        return img\n    \n    def resize_volume_3d(self, volume: np.ndarray) -> np.ndarray:\n        \"\"\"Resize 3D volume to target size\"\"\"\n        current_shape = volume.shape\n        target_shape = (self.target_depth, self.target_height, self.target_width)\n        \n        if current_shape == target_shape:\n            return volume\n        \n        # Calculate zoom factors\n        zoom_factors = [target_shape[i] / current_shape[i] for i in range(3)]\n        \n        # Resize with linear interpolation\n        resized_volume = ndimage.zoom(volume, zoom_factors, order=1, mode='nearest')\n        \n        # Ensure exact size\n        resized_volume = resized_volume[:self.target_depth, :self.target_height, :self.target_width]\n        \n        # Pad if necessary\n        pad_width = [\n            (0, max(0, self.target_depth - resized_volume.shape[0])),\n            (0, max(0, self.target_height - resized_volume.shape[1])),\n            (0, max(0, self.target_width - resized_volume.shape[2]))\n        ]\n        \n        if any(pw[1] > 0 for pw in pad_width):\n            resized_volume = np.pad(resized_volume, pad_width, mode='edge')\n        \n        return resized_volume.astype(np.uint8)\n    \n    def process_series(self, series_path: str) -> np.ndarray:\n        \"\"\"Process DICOM series\"\"\"\n        datasets, series_name = self.load_dicom_series(series_path)\n        \n        first_ds = datasets[0]\n        first_img = first_ds.pixel_array\n        \n        if len(datasets) == 1 and first_img.ndim == 3:\n            # Single 3D DICOM\n            return self._process_single_3d_dicom(first_ds, series_name)\n        else:\n            # Multiple 2D DICOMs\n            return self._process_multiple_2d_dicoms(datasets, series_name)\n    \n    def _process_single_3d_dicom(self, ds: pydicom.Dataset, series_name: str) -> np.ndarray:\n        \"\"\"Process single 3D DICOM file\"\"\"\n        volume = ds.pixel_array.astype(np.float32)\n        \n        # CRITICAL: Force identity transform\n        if CFG.force_rescale_identity:\n            slope, intercept = 1, 0\n        else:\n            slope = getattr(ds, 'RescaleSlope', 1)\n            intercept = getattr(ds, 'RescaleIntercept', 0)\n        \n        if slope != 1 or intercept != 0:\n            volume = volume * float(slope) + float(intercept)\n        \n        window_center, window_width = self.get_windowing_params(ds)\n        \n        # Process each slice\n        processed_slices = []\n        for i in range(volume.shape[0]):\n            slice_img = volume[i]\n            processed_img = self.apply_windowing_or_normalize(slice_img, window_center, window_width)\n            processed_slices.append(processed_img)\n        \n        volume = np.stack(processed_slices, axis=0)\n        return self.resize_volume_3d(volume)\n    \n    def _process_multiple_2d_dicoms(self, datasets: List[pydicom.Dataset], series_name: str) -> np.ndarray:\n        \"\"\"Process multiple 2D DICOM files\"\"\"\n        slice_info = self.extract_slice_info(datasets)\n        sorted_slices = self.sort_slices_by_position(slice_info)\n        \n        first_ds = sorted_slices[0]['dataset']\n        window_center, window_width = self.get_windowing_params(first_ds)\n        \n        processed_slices = []\n        for slice_data in sorted_slices:\n            ds = slice_data['dataset']\n            img = self.extract_pixel_array(ds)\n            processed_img = self.apply_windowing_or_normalize(img, window_center, window_width)\n            resized_img = cv2.resize(processed_img, (self.target_width, self.target_height))\n            processed_slices.append(resized_img)\n        \n        volume = np.stack(processed_slices, axis=0)\n        return self.resize_volume_3d(volume)\n\n# ====================================================\n# Model Loading and Ensemble\n# ====================================================\nclass ModelEnsemble:\n    \"\"\"Robust model ensemble with multiple prediction strategies\"\"\"\n    \n    def __init__(self, config: InferenceConfig):\n        self.config = config\n        self.models = {}\n        self.transform = None\n        self.device = device\n        \n    def get_inference_transform(self):\n        \"\"\"Get inference transformation\"\"\"\n        return A.Compose([\n            A.Resize(self.config.size, self.config.size),\n            A.Normalize(),\n            ToTensorV2(),\n        ])\n    \n    def load_models(self):\n        \"\"\"Load all fold models with robust error handling\"\"\"\n        logger.info(\"Loading ensemble models...\")\n        \n        successful_loads = 0\n        failed_loads = []\n        \n        for fold in self.config.trn_fold:\n            try:\n                model = self._load_single_model(fold)\n                self.models[fold] = model\n                successful_loads += 1\n                logger.info(f\"Successfully loaded fold {fold}\")\n            except Exception as e:\n                logger.warning(f\"Failed to load fold {fold}: {e}\")\n                failed_loads.append(fold)\n        \n        if successful_loads == 0:\n            raise RuntimeError(f\"Failed to load any models. Attempted folds: {self.config.trn_fold}\")\n        \n        if failed_loads:\n            logger.warning(f\"Loaded {successful_loads}/{len(self.config.trn_fold)} models. Failed: {failed_loads}\")\n        \n        # Initialize transform\n        self.transform = self.get_inference_transform()\n        \n        # Warm up models\n        self._warm_up_models()\n        \n        logger.info(f\"Model ensemble ready with {len(self.models)} models\")\n    \n    def _load_single_model(self, fold: int) -> nn.Module:\n        \"\"\"Load a single fold model with multiple path attempts\"\"\"\n        # Try different naming conventions\n        possible_paths = [\n            Path(self.config.model_dir) / f'{self.config.model_name}_fold{fold}_best.pth',\n            Path(self.config.model_dir) / f'fold{fold}_best.pth',\n            Path(self.config.model_dir) / f'model_fold{fold}.pth',\n        ]\n        \n        model_path = None\n        for path in possible_paths:\n            if path.exists():\n                model_path = path\n                break\n        \n        if model_path is None:\n            raise FileNotFoundError(f\"No model file found for fold {fold}. Tried: {possible_paths}\")\n        \n        logger.debug(f\"Loading model from {model_path}\")\n        \n        # Load checkpoint\n        checkpoint = torch.load(model_path, map_location=self.device, weights_only=False)\n        \n        # Initialize model\n        model = timm.create_model(\n            self.config.model_name,\n            num_classes=self.config.num_classes,\n            pretrained=False,\n            in_chans=self.config.in_chans\n        )\n        \n        # Handle different checkpoint formats\n        if isinstance(checkpoint, dict):\n            if 'model' in checkpoint:\n                model.load_state_dict(checkpoint['model'])\n            elif 'state_dict' in checkpoint:\n                model.load_state_dict(checkpoint['state_dict'])\n            elif 'model_state_dict' in checkpoint:\n                model.load_state_dict(checkpoint['model_state_dict'])\n            else:\n                # Assume the checkpoint is the state dict itself\n                model.load_state_dict(checkpoint)\n        else:\n            model.load_state_dict(checkpoint)\n        \n        model = model.to(self.device)\n        model.eval()\n        \n        return model\n    \n    def _warm_up_models(self):\n        \"\"\"Warm up models for consistent inference speed\"\"\"\n        logger.debug(\"Warming up models...\")\n        dummy_input = torch.randn(1, self.config.in_chans, self.config.size, self.config.size).to(self.device)\n        \n        with torch.no_grad():\n            for fold, model in self.models.items():\n                _ = model(dummy_input)\n    \n    def predict_single_model(self, model: nn.Module, image: np.ndarray, \n                           apply_tta: bool = False) -> np.ndarray:\n        \"\"\"Predict with single model, optionally with TTA\"\"\"\n        predictions = []\n        \n        # Original prediction\n        image_transposed = image.transpose(1, 2, 0)  # (D,H,W) -> (H,W,D)\n        transformed = self.transform(image=image_transposed)\n        image_tensor = transformed['image'].unsqueeze(0).to(self.device)\n        \n        with torch.no_grad():\n            with autocast(enabled=self.config.use_amp):\n                output = model(image_tensor)\n                if self.config.temperature_scaling != 1.0:\n                    output = output / self.config.temperature_scaling\n                pred = torch.sigmoid(output).cpu().numpy().squeeze()\n                predictions.append(pred)\n        \n        # Brightness TTA (safe for L/R anatomy)\n        if apply_tta and self.config.use_brightness_tta:\n            # Slightly brighter\n            image_bright = np.clip(image * 1.05, 0, 255).astype(np.uint8)\n            image_bright_t = image_bright.transpose(1, 2, 0)\n            transformed = self.transform(image=image_bright_t)\n            image_tensor = transformed['image'].unsqueeze(0).to(self.device)\n            \n            with torch.no_grad():\n                with autocast(enabled=self.config.use_amp):\n                    output = model(image_tensor)\n                    if self.config.temperature_scaling != 1.0:\n                        output = output / self.config.temperature_scaling\n                    pred = torch.sigmoid(output).cpu().numpy().squeeze()\n                    predictions.append(pred)\n            \n            # Slightly darker\n            image_dark = np.clip(image * 0.95, 0, 255).astype(np.uint8)\n            image_dark_t = image_dark.transpose(1, 2, 0)\n            transformed = self.transform(image=image_dark_t)\n            image_tensor = transformed['image'].unsqueeze(0).to(self.device)\n            \n            with torch.no_grad():\n                with autocast(enabled=self.config.use_amp):\n                    output = model(image_tensor)\n                    if self.config.temperature_scaling != 1.0:\n                        output = output / self.config.temperature_scaling\n                    pred = torch.sigmoid(output).cpu().numpy().squeeze()\n                    predictions.append(pred)\n        \n        # Average TTA predictions\n        return np.mean(predictions, axis=0)\n    \n    def predict_ensemble(self, image: np.ndarray) -> np.ndarray:\n        \"\"\"Make ensemble prediction across all loaded models\"\"\"\n        if not self.models:\n            raise RuntimeError(\"No models loaded for ensemble prediction\")\n        \n        all_predictions = []\n        \n        for fold, model in self.models.items():\n            try:\n                pred = self.predict_single_model(model, image, apply_tta=True)\n                all_predictions.append(pred)\n            except Exception as e:\n                logger.warning(f\"Prediction failed for fold {fold}: {e}\")\n                continue\n        \n        if not all_predictions:\n            raise RuntimeError(\"All model predictions failed\")\n        \n        predictions = np.array(all_predictions)\n        \n        # Apply ensemble method\n        if self.config.ensemble_method == 'average':\n            result = np.mean(predictions, axis=0)\n        elif self.config.ensemble_method == 'geometric_mean':\n            # Geometric mean (better for probabilities)\n            # Add small epsilon to avoid log(0)\n            result = np.exp(np.mean(np.log(predictions + 1e-10), axis=0))\n        elif self.config.ensemble_method == 'median':\n            result = np.median(predictions, axis=0)\n        else:\n            result = np.mean(predictions, axis=0)\n        \n        return result\n\n# ====================================================\n# Post-processing\n# ====================================================\ndef apply_anatomical_constraints(predictions: np.ndarray) -> np.ndarray:\n    \"\"\"Apply medical knowledge-based post-processing\"\"\"\n    predictions = predictions.copy()\n    \n    # 1. Ensure consistency with \"Aneurysm Present\" flag\n    aneurysm_present_idx = -1\n    location_max = np.max(predictions[:-1])\n    \n    if location_max > 0.5:\n        # If any location has high probability, ensure overall flag is high\n        predictions[aneurysm_present_idx] = max(\n            predictions[aneurysm_present_idx], \n            location_max * 0.95\n        )\n    elif predictions[aneurysm_present_idx] < 0.15:\n        # If overall probability is very low, reduce all locations\n        predictions[:-1] = predictions[:-1] * 0.7\n    \n    # 2. Apply mild bilateral symmetry boost (aneurysms can be bilateral)\n    for left_idx, right_idx in zip(LEFT_VESSEL_INDICES, RIGHT_VESSEL_INDICES):\n        left_prob = predictions[left_idx]\n        right_prob = predictions[right_idx]\n        \n        # If one side has very high probability, slightly boost the other\n        if left_prob > 0.7:\n            predictions[right_idx] = max(right_prob, left_prob * 0.2)\n        if right_prob > 0.7:\n            predictions[left_idx] = max(left_prob, right_prob * 0.2)\n    \n    # 3. Ensure minimum probability for safety (avoid exact zeros)\n    min_prob = 0.001\n    predictions = np.maximum(predictions, min_prob)\n    \n    # 4. Ensure maximum probability isn't too confident\n    max_prob = 0.999\n    predictions = np.minimum(predictions, max_prob)\n    \n    return predictions\n\n# ====================================================\n# Main Inference Pipeline\n# ====================================================\n# Global variables\nMODEL_ENSEMBLE = None\nPREPROCESSOR = None\n\ndef initialize_pipeline():\n    \"\"\"Initialize the complete inference pipeline\"\"\"\n    global MODEL_ENSEMBLE, PREPROCESSOR\n    \n    logger.info(\"Initializing inference pipeline...\")\n    \n    # Initialize preprocessor\n    PREPROCESSOR = DICOMPreprocessorRobust(CFG.target_shape)\n    \n    # Initialize and load models\n    MODEL_ENSEMBLE = ModelEnsemble(CFG)\n    MODEL_ENSEMBLE.load_models()\n    \n    logger.info(\"Pipeline initialization complete\")\n\ndef process_dicom_series_safe(series_path: str) -> np.ndarray:\n    \"\"\"Process DICOM series with error handling and retries\"\"\"\n    global PREPROCESSOR\n    \n    if PREPROCESSOR is None:\n        PREPROCESSOR = DICOMPreprocessorRobust(CFG.target_shape)\n    \n    last_error = None\n    for attempt in range(CFG.max_retries):\n        try:\n            volume = PREPROCESSOR.process_series(series_path)\n            return volume\n        except Exception as e:\n            last_error = e\n            logger.warning(f\"Attempt {attempt + 1} failed: {e}\")\n            if attempt < CFG.max_retries - 1:\n                # Clean up before retry\n                gc.collect()\n                if torch.cuda.is_available():\n                    torch.cuda.empty_cache()\n    \n    raise RuntimeError(f\"Failed to process DICOM after {CFG.max_retries} attempts: {last_error}\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:34:39.501782Z","iopub.execute_input":"2025-09-09T03:34:39.50203Z","iopub.status.idle":"2025-09-09T03:35:16.150003Z","shell.execute_reply.started":"2025-09-09T03:34:39.502012Z","shell.execute_reply":"2025-09-09T03:35:16.149249Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\n\ndef add_gaussian_noise(img, prob=0.2, mean=0.0, std=0.01):\n    if np.random.rand() < prob:\n        noise = np.random.normal(mean, std, img.shape).astype(np.float32)\n        return np.clip(img + noise, 0.0, 1.0)\n    return img\n\ndef scale_intensity(img, prob=0.3, factors=0.1):\n    if np.random.rand() < prob:\n        scale = 1.0 + np.random.uniform(-factors, factors)\n        return np.clip(img * scale, 0.0, 1.0)\n    return img\n\ndef shift_intensity(img, prob=0.3, offsets=0.1):\n    if np.random.rand() < prob:\n        shift = np.random.uniform(-offsets, offsets)\n        return np.clip(img + shift, 0.0, 1.0)\n    return img\n\ndef augment(img):\n    \"\"\" img: np.array, already normalized [0,1] \"\"\"\n    img = add_gaussian_noise(img)\n    img = scale_intensity(img)\n    img = shift_intensity(img)\n    return img\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:16.150821Z","iopub.execute_input":"2025-09-09T03:35:16.151086Z","iopub.status.idle":"2025-09-09T03:35:16.15708Z","shell.execute_reply.started":"2025-09-09T03:35:16.15106Z","shell.execute_reply":"2025-09-09T03:35:16.156387Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 1.Preprocessing","metadata":{}},{"cell_type":"code","source":"#  Approach - z index sorting->radimagenet embeddings->gru\n# GLOBALS\nALLOWED_TAGS=[\n    \"SOPClassUID\",\n    \"SOPInstanceUID\",\n    \"Modality\",\n    \"PatientID\",\n    \"SliceThickness\",\n    \"StudyInstanceUID\",\n    \"SeriesInstanceUID\",\n    \"InstanceNumber\",\n    \"ImagePositionPatient\",\n    \"ImageOrientationPatient\",\n    \"FrameOfReferenceUID\",\n    \"SamplesPerPixel\",\n    \"PhotometricInterpretation\",\n    \"Rows\",\n    \"Columns\",\n    \"PixelSpacing\",\n    \"BitsAllocated\",\n    \"BitsStored\",\n    \"HighBit\",\n    \"PixelRepresentation\",\n    \"PixelData\"\n]\nID_COL = 'SeriesInstanceUID'\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:16.157772Z","iopub.execute_input":"2025-09-09T03:35:16.158026Z","iopub.status.idle":"2025-09-09T03:35:16.207737Z","shell.execute_reply.started":"2025-09-09T03:35:16.15801Z","shell.execute_reply":"2025-09-09T03:35:16.207112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Preprocessing code for one series\n\n# Imports for z index sorting \nimport os\nimport pydicom as dcm\nimport numpy as np\nimport cv2\nimport json\n\n# Radimage net embeddings import\nimport torch\nimport torch.nn as nn\nimport monai\n# from monai.transforms import (\n#     Compose,RandGaussianNoise, RandScaleIntensity, RandShiftIntensity\n# )\n\n# Resize embeddings for lstm \nfrom torch.utils.data import Dataset\n\n# 1.Load a single scan and return np array for resnet\ndef load_series(series_path):\n    \"\"\"Load a scan and does z index sorting and with returns npy format\"\"\"\n    # Load all dicom files in series\n    dcm_files = [os.path.join(series_path, f) for f in os.listdir(series_path) if f.endswith(\".dcm\")]\n    datasets = []\n    for f in dcm_files:\n        try:\n            datasets.append(dcm.dcmread(f))\n        except:\n            continue\n            \n    # Check metadata availability\n    first_ds = datasets[0]\n    series_id = getattr(first_ds, \"SeriesInstanceUID\", \"UNKNOWN\")\n\n    # Sort  with z index\n    try:\n        datasets.sort(key=lambda ds: float(ds.ImagePositionPatient[2]))\n    except Exception:\n        datasets.sort(key=lambda ds: int(ds.InstanceNumber))\n\n    # Extract pixel data\n    slices = []\n    for ds in datasets:\n        arr = ds.pixel_array.astype(np.float32)\n        arr = cv2.resize(arr, (224, 224))  # resize for ResNet\n        arr = (arr - np.min(arr)) / (np.max(arr) - np.min(arr) + 1e-5)  # normalize\n        slices.append(arr)\n\n    volume = np.stack(slices, axis=0)  # shape = (num_slices, 224, 224)\n\n    # Save processed volume\n    return volume,series_id\n\n# 2.Rad imagenet embeddings\n\nradimagenet_path = \"/kaggle/input/radimagenet_50/pytorch/default/1/ResNet50.pt\"\n\"\"\"\nEmbedding generator with augmentation using RadImageNet ResNet50\n\"\"\"\nimport torch\nimport torch.nn as nn\nfrom torchvision.models import resnet50\n\n\ndef get_feature_extractor(radimagenet_path: str):\n    resnet = monai.networks.nets.resnet50(spatial_dims=2, n_input_channels=3)\n\n    state_dict = torch.load(radimagenet_path, map_location=\"cpu\")\n    # checkpoint wrapped like {\"state_dict\": ...}\n    if \"state_dict\" in state_dict:\n        state_dict = state_dict[\"state_dict\"]\n\n    # remove \"backbone.\" prefix\n    new_state_dict = {}\n    for k, v in state_dict.items():\n        new_k = k.replace(\"backbone.\", \"\")  # strip prefix\n        new_state_dict[new_k] = v\n\n    # load into resnet\n    resnet.load_state_dict(new_state_dict, strict=False)\n\n    feature_extractor = nn.Sequential(*list(resnet.children())[:-1])  # remove fc\n    feature_extractor.eval()\n    return feature_extractor\n\n\n\n# Init embedder\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nEmbedder =get_feature_extractor(\"/kaggle/input/radimagenet_50/pytorch/default/1/ResNet50.pt\").to(device)\n# Define augmentation pipeline\n# augment = Compose([\n#     RandGaussianNoise(prob=0.2, mean=0.0, std=0.01),               # scanner noise\n#     RandScaleIntensity(factors=0.1, prob=0.3),                     # intensity scaling\n#     RandShiftIntensity(offsets=0.1, prob=0.3),                     # intensity shifting\n# ])\n\n\n# 3. Make suitable for lsmtgru model\n\nSEQ_LEN = 800\nEMB_DIM = 2048\n\ndef embedding_resizer(raw_embedding: np.ndarray, seq_len: int = SEQ_LEN):\n    \"\"\"\n    Resize embeddings to fixed length (seq_len, EMB_DIM).\n    Also returns an attention mask (1 for real slices, 0 for padding).\n    \"\"\"\n    n_slices = raw_embedding.shape[0]\n\n    # Case 1: Too short → pad\n    if n_slices < seq_len:\n        pad_len = seq_len - n_slices\n        pad = np.zeros((pad_len, raw_embedding.shape[1]), dtype=raw_embedding.dtype)\n        resized = np.concatenate([raw_embedding, pad], axis=0)\n        mask = np.concatenate([np.ones(n_slices), np.zeros(pad_len)])\n\n    # Case 2: Too long → sample evenly\n    elif n_slices > seq_len:\n        indices = np.linspace(0, n_slices - 1, seq_len).astype(int)\n        resized = raw_embedding[indices]\n        mask = np.ones(seq_len)\n\n    # Case 3: Exact length\n    else:\n        resized = raw_embedding\n        mask = np.ones(seq_len)\n\n    return resized, mask\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:16.208416Z","iopub.execute_input":"2025-09-09T03:35:16.208588Z","iopub.status.idle":"2025-09-09T03:35:17.815759Z","shell.execute_reply.started":"2025-09-09T03:35:16.208572Z","shell.execute_reply":"2025-09-09T03:35:17.815031Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"WARM UP\"\"\"\n\n# # Warm up\n# # example data series\n# series_path='/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.99887675554378211308175946117895608384'\n\n# # loader is fine\n# volume,series_id=load_series(series_path)\n# # print(series_id)\n# # print(volume.shape)\n# # print(np.unique(volume))\n\n# # now embeddings\n# slices=volume\n# embeddings = []\n# with torch.no_grad():\n#         for s in slices:\n#             # augment slice (still single-channel)\n#             s_aug = augment(s[np.newaxis, :, :])  # (1, H, W)\n\n#             # expand to 3 channels (grayscale → RGB-like)\n#             img = np.repeat(s_aug, 3, axis=0)  # (3, 224, 224)\n#             img = torch.tensor(img, dtype=torch.float32).unsqueeze(0).to(device)  # (1, 3, 224, 224)\n\n#             # forward pass\n#             feat = Embedder(img)  # (1, 2048, 1, 1)\n#             feat = torch.flatten(feat, 1)  # (1, 2048)\n\n#             embeddings.append(feat.cpu().numpy())\n\n# # stack → (num_slices, 2048)\n# embeddings = np.vstack(embeddings)\n# # print(f\"✅ Generated embeddings for series {series_id} → {embeddings.shape}\")\n\n#  # resize\n# embedding, mask = embedding_resizer(embeddings, SEQ_LEN)\n# embedding = torch.tensor(embedding, dtype=torch.float32)   # (SEQ_LEN, EMB_DIM)\n# mask = torch.tensor(mask, dtype=torch.float32)             # (SEQ_LEN,)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:17.817684Z","iopub.execute_input":"2025-09-09T03:35:17.817927Z","iopub.status.idle":"2025-09-09T03:35:17.823661Z","shell.execute_reply.started":"2025-09-09T03:35:17.817909Z","shell.execute_reply":"2025-09-09T03:35:17.822988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# print(embedding.shape)\n# print(mask.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:17.824418Z","iopub.execute_input":"2025-09-09T03:35:17.825155Z","iopub.status.idle":"2025-09-09T03:35:17.868002Z","shell.execute_reply.started":"2025-09-09T03:35:17.825136Z","shell.execute_reply":"2025-09-09T03:35:17.867311Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Make predictions\n# embedding, mask = embedding.to(device), mask.to(device)\n# logits = model(embedding.unsqueeze(0), mask.unsqueeze(0))\n# probs = torch.sigmoid(logits).detach().cpu()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:17.868738Z","iopub.execute_input":"2025-09-09T03:35:17.868995Z","iopub.status.idle":"2025-09-09T03:35:17.883056Z","shell.execute_reply.started":"2025-09-09T03:35:17.868969Z","shell.execute_reply":"2025-09-09T03:35:17.882403Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# probs.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:17.883703Z","iopub.execute_input":"2025-09-09T03:35:17.883973Z","iopub.status.idle":"2025-09-09T03:35:17.896681Z","shell.execute_reply.started":"2025-09-09T03:35:17.883956Z","shell.execute_reply":"2025-09-09T03:35:17.896172Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2. Model Architecture","metadata":{}},{"cell_type":"code","source":"# 4. Create and load model\n\nimport torch\nimport torch.nn as nn\n\nclass AneurysmGRU(nn.Module):\n    def __init__(self,\n                 input_dim=2048,\n                 hidden_dim=512,\n                 num_layers=2,\n                 num_classes=14,\n                 bidirectional=True,\n                 dropout=0.3):\n        super(AneurysmGRU, self).__init__()\n\n        self.gru = nn.GRU(\n            input_dim,\n            hidden_dim,\n            num_layers=num_layers,\n            batch_first=True,\n            dropout=dropout if num_layers > 1 else 0,\n            bidirectional=bidirectional\n        )\n\n        self.bidirectional = bidirectional\n        self.hidden_dim = hidden_dim\n\n        # Linear head\n        out_dim = hidden_dim * (2 if bidirectional else 1)\n        self.fc = nn.Sequential(\n            nn.Linear(out_dim, 256),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Linear(256, num_classes)\n        )\n\n    def forward(self, x, mask=None):\n        \"\"\"\n        x: (batch, seq_len, input_dim)\n        mask: (batch, seq_len)   [1 = real, 0 = pad]\n        \"\"\"\n        # GRU forward\n        out, _ = self.gru(x)   # (batch, seq_len, hidden_dim*2)\n\n        if mask is not None:\n            mask = mask.unsqueeze(-1)  # (batch, seq_len, 1)\n            out = out * mask           # zero out padded timesteps\n\n        # Global average pooling (mask-aware)\n        if mask is not None:\n            summed = torch.sum(out, dim=1)             # (batch, hidden_dim*2)\n            counts = torch.sum(mask, dim=1) + 1e-6     # (batch, 1)\n            pooled = summed / counts                   # mean pooling\n        else:\n            pooled = out.mean(dim=1)\n\n        # Classifier\n        logits = self.fc(pooled)  # (batch, num_classes)\n\n        return logits\nmodel = AneurysmGRU(\n    input_dim=2048,\n    hidden_dim=800,\n    num_layers=4,\n    num_classes=14,\n    bidirectional=True,\n    dropout=0.4\n)\nlast_model_path='/kaggle/input/gru_rnn/pytorch/default/1/model_approach_2_fold0_best.pth'\ncheckpoint = torch.load(last_model_path, map_location=device)\nmodel.load_state_dict(checkpoint[\"model_state\"])\nmodel = model.to(device)\nmodel.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:17.897424Z","iopub.execute_input":"2025-09-09T03:35:17.897657Z","iopub.status.idle":"2025-09-09T03:35:22.023035Z","shell.execute_reply.started":"2025-09-09T03:35:17.897641Z","shell.execute_reply":"2025-09-09T03:35:22.02234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import polars as pl\nimport kaggle_evaluation.rsna_inference_server\n\ndef _predict_inner(series_path):\n    volume,series_id=load_series(series_path)\n    # shape = (num_slices, 224, 224)\n\n    slices=volume\n    embeddings = []\n    with torch.no_grad():\n        for s in slices:\n            # augment slice (still single-channel)\n            s_aug = augment(s)  # (1, H, W)\n\n            # expand to 3 channels (grayscale → RGB-like)\n            img = np.repeat(s_aug[np.newaxis, :, :], 3, axis=0)  # (3, 224, 224)\n            img = torch.tensor(img, dtype=torch.float32).unsqueeze(0).to(device)  # (1, 3, 224, 224)\n\n            # forward pass\n            feat = Embedder(img)  # (1, 2048, 1, 1)\n            feat = torch.flatten(feat, 1)  # (1, 2048)\n\n            embeddings.append(feat.cpu().numpy())\n\n    # stack → (num_slices, 2048)\n    embeddings = np.vstack(embeddings)\n\n    # resize\n    embedding, mask = embedding_resizer(embeddings, SEQ_LEN)\n    embedding = torch.tensor(embedding, dtype=torch.float32)   # (SEQ_LEN, EMB_DIM)\n    mask = torch.tensor(mask, dtype=torch.float32)             # (SEQ_LEN,)\n\n    # Make predictions\n    embedding, mask = embedding.to(device), mask.to(device)\n    logits = model(embedding.unsqueeze(0), mask.unsqueeze(0))\n    probs = torch.sigmoid(logits).detach().cpu()\n\n    # Now polars dataframe\n    predictions_df = pl.DataFrame(\n            data=[[series_id] + probs[0].tolist()],\n            schema=[ID_COL] + LABEL_COLS,\n            orient='row'\n        )\n    # Return without ID column, as required by the API\n    return predictions_df.drop(ID_COL)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:22.02384Z","iopub.execute_input":"2025-09-09T03:35:22.024096Z","iopub.status.idle":"2025-09-09T03:35:22.030998Z","shell.execute_reply.started":"2025-09-09T03:35:22.024074Z","shell.execute_reply":"2025-09-09T03:35:22.030339Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict1(series_path: str) -> pl.DataFrame:\n    \"\"\"Main prediction function for the inference server\"\"\"\n    global MODEL_ENSEMBLE\n    \n    # Initialize if needed\n    if MODEL_ENSEMBLE is None:\n        initialize_pipeline()\n    \n    series_id = os.path.basename(series_path)\n    \n    try:\n        # Process DICOM\n        volume = process_dicom_series_safe(series_path)\n        logger.debug(f\"Processed volume shape: {volume.shape}, dtype: {volume.dtype}\")\n        \n        # Make ensemble prediction\n        raw_predictions = MODEL_ENSEMBLE.predict_ensemble(volume)\n        \n        # Apply post-processing\n        final_predictions = apply_anatomical_constraints(raw_predictions)\n        \n        # Create output dataframe\n        predictions_df = pl.DataFrame(\n            data=[final_predictions.tolist()],\n            schema=LABEL_COLS,\n            orient='row'\n        )\n        \n        logger.info(f\"Successfully predicted for {series_id}\")\n        return predictions_df\n        \n    except Exception as e:\n        logger.error(f\"Prediction failed for {series_id}: {e}\", exc_info=True)\n        \n        # Return conservative fallback predictions\n        conservative_preds = [CFG.fallback_predictions] * len(LABEL_COLS)\n        predictions_df = pl.DataFrame(\n            data=[conservative_preds],\n            schema=LABEL_COLS,\n            orient='row'\n        )\n        return predictions_df\n        \n    finally:\n        # Clean up after each prediction\n        if CFG.cleanup_after_each:\n            shared_dir = '/kaggle/shared'\n            if os.path.exists(shared_dir):\n                shutil.rmtree(shared_dir, ignore_errors=True)\n                os.makedirs(shared_dir, exist_ok=True)\n            \n            gc.collect()\n            if torch.cuda.is_available():\n                torch.cuda.empty_cache()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:22.032018Z","iopub.execute_input":"2025-09-09T03:35:22.032275Z","iopub.status.idle":"2025-09-09T03:35:22.06741Z","shell.execute_reply.started":"2025-09-09T03:35:22.032252Z","shell.execute_reply":"2025-09-09T03:35:22.066768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import sys\nimport shutil\nimport warnings\nimport gc\nwarnings.filterwarnings('ignore')\ndef predict(series_path: str) -> pl.DataFrame:\n    \"\"\"\n    Top-level prediction function that blends EfficientNet + GRU outputs.\n    Uses EfficientNet as the main model, but overrides columns 0 and 12 with GRU's predictions.\n    \"\"\"\n    try:\n        predictions_en = predict1(series_path)   # EfficientNet output\n        predictions_gru = _predict_inner(series_path)  # GRU output\n        \n        # Try with label 2\n        label_0_name = LABEL_COLS[10]\n        # label_12_name = LABEL_COLS[12]\n        print(predictions_en)\n        print(predictions_gru)\n        predictions_en = predictions_en.with_columns([\n            predictions_gru[label_0_name].alias(label_0_name),\n            # predictions_gru[label_12_name].alias(label_12_name),\n        ])\n        \n        return predictions_en\n    \n    except Exception as e:\n        # Conservative fallback\n        conservative_preds = [0.1] * len(LABEL_COLS)\n        predictions = pl.DataFrame(\n            data=[conservative_preds],\n            schema=LABEL_COLS,\n            orient='row'\n        )\n        return predictions\n    \n    finally:\n        # Cleanup\n        shared_dir = '/kaggle/shared'\n        shutil.rmtree(shared_dir, ignore_errors=True)\n        os.makedirs(shared_dir, exist_ok=True)\n        \n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n        gc.collect()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:22.068025Z","iopub.execute_input":"2025-09-09T03:35:22.068225Z","iopub.status.idle":"2025-09-09T03:35:22.08527Z","shell.execute_reply.started":"2025-09-09T03:35:22.068203Z","shell.execute_reply":"2025-09-09T03:35:22.084544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# sample_ans","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:22.086381Z","iopub.execute_input":"2025-09-09T03:35:22.086609Z","iopub.status.idle":"2025-09-09T03:35:22.101286Z","shell.execute_reply.started":"2025-09-09T03:35:22.086594Z","shell.execute_reply":"2025-09-09T03:35:22.100763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_ans=predict('/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.99887675554378211308175946117895608384')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:37:33.322547Z","iopub.execute_input":"2025-09-09T03:37:33.323308Z","iopub.status.idle":"2025-09-09T03:37:49.122851Z","shell.execute_reply.started":"2025-09-09T03:37:33.323284Z","shell.execute_reply":"2025-09-09T03:37:49.122164Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_ans","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:40:36.712509Z","iopub.execute_input":"2025-09-09T03:40:36.712819Z","iopub.status.idle":"2025-09-09T03:40:36.718435Z","shell.execute_reply.started":"2025-09-09T03:40:36.712771Z","shell.execute_reply":"2025-09-09T03:40:36.717728Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Main execution\n\n# Initialize the inference server with our main `predict` function.\ninference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n\n# Check if the notebook is running in the competition environment or a local session.\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    # make sure to give it an empty directory\n    tmp_dir = \"/kaggle/working/tmp_gateway\"\n    os.makedirs(tmp_dir, exist_ok=True)\n    inference_server.run_local_gateway(file_share_dir=tmp_dir)\n    \n    submission_df = pl.read_parquet('/kaggle/working/submission.parquet')\n    display(submission_df)\n\n\n\n\n\n# ====================================================\n# Main Execution\n# ====================================================\nif __name__ == \"__main__\":\n    try:\n        # Initialize pipeline at startup\n        initialize_pipeline()\n        \n        # Create inference server\n        inference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n        \n        # Check environment and run appropriate mode\n        if os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n            logger.info(\"Running in competition environment\")\n            inference_server.serve()\n        else:\n            logger.info(\"Running in local test mode\")\n            inference_server.run_local_gateway()\n            \n            # Display submission for verification\n            submission_path = '/kaggle/working/submission.parquet'\n            if os.path.exists(submission_path):\n                submission_df = pl.read_parquet(submission_path)\n                print(\"\\nSubmission Preview:\")\n                print(submission_df)\n            else:\n                logger.warning(\"No submission file found\")\n                \n    except Exception as e:\n        logger.error(f\"Critical error in main execution: {e}\", exc_info=True)\n        sys.exit(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-09T03:35:22.160474Z","iopub.status.idle":"2025-09-09T03:35:22.160695Z","shell.execute_reply.started":"2025-09-09T03:35:22.160596Z","shell.execute_reply":"2025-09-09T03:35:22.160605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}