{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":13190393,"sourceType":"competition"},{"sourceId":12750084,"sourceType":"datasetVersion","datasetId":8059848},{"sourceId":500985,"sourceType":"modelInstanceVersion","modelInstanceId":397733,"modelId":416055}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## References\n- https://www.kaggle.com/code/dennisfong/dicom-pngs-for-rsna-intracranial-aneurysm/notebook\n- https://www.kaggle.com/code/zshashz/rsna-iad-ensemble-lb-1#8.-Fallback-and-Error-Handling\n## Notebooks\n- Train Notebook:https://www.kaggle.com/code/ichigoe/train-224x224-dicom-pngs-efficientnetb0/notebook\n- Inference:Here","metadata":{}},{"cell_type":"code","source":"# Setup and imports for RSNA Intracranial Aneurysm Detection inference\nimport os\nimport sys\nimport gc\nimport json\nimport shutil\nimport warnings\nwarnings.filterwarnings('ignore')\nfrom pathlib import Path\nfrom typing import List, Dict, Optional, Tuple\n\n# Data handling\nimport numpy as np\nimport polars as pl\nimport pandas as pd\n\n# Medical imaging\nimport pydicom\nimport cv2\nfrom pydicom.pixel_data_handlers.util import convert_color_space\n\n# ML/DL\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.cuda.amp import autocast\nimport timm\n\n# Transformations\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Competition API\nimport kaggle_evaluation.rsna_inference_server\n\n# Set device\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n    print(f\"Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T00:43:17.00102Z","iopub.execute_input":"2025-08-02T00:43:17.001253Z","iopub.status.idle":"2025-08-02T00:44:04.017085Z","shell.execute_reply.started":"2025-08-02T00:43:17.001234Z","shell.execute_reply":"2025-08-02T00:44:04.016294Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Competition constants\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\nclass InferenceConfig:\n    # Model settings (must match training)\n    num_frames = 16\n    image_size = 224\n    num_classes = 14\n    \n    # Model path\n    model_path = \"/kaggle/input/rsna-saved-models/best_png_baseline.pth\"\n    \n    # Inference settings\n    batch_size = 1\n    use_amp = True\n    use_windowing = True\n    \n    # Processing settings\n    debug_mode = False\n\nCFG = InferenceConfig()\n\n# Add alias to match training code (important for pickle loading)\nConfig = InferenceConfig\n\nprint(f\"Configuration loaded:\")\nprint(f\"- Frames: {CFG.num_frames}\")\nprint(f\"- Image size: {CFG.image_size}\")\nprint(f\"- Model path: {CFG.model_path}\")\nprint(f\"- Use windowing: {CFG.use_windowing}\")\nprint(f\"- Config alias created for compatibility\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T00:44:04.017947Z","iopub.execute_input":"2025-08-02T00:44:04.018395Z","iopub.status.idle":"2025-08-02T00:44:04.024205Z","shell.execute_reply.started":"2025-08-02T00:44:04.018373Z","shell.execute_reply":"2025-08-02T00:44:04.023288Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Multi-Frame EfficientNet model (identical to training)\nclass MultiFrameEfficientNet(nn.Module):\n    def __init__(self, num_frames=16, num_classes=14, pretrained=True):\n        super(MultiFrameEfficientNet, self).__init__()\n        self.num_frames = num_frames\n        self.num_classes = num_classes\n        \n        # Load EfficientNetB0 as backbone\n        self.backbone = timm.create_model(\n            'efficientnet_b0', \n            pretrained=pretrained,\n            num_classes=0,  # Remove classification head\n            global_pool='avg'\n        )\n        \n        # Get feature dimension from backbone\n        self.feature_dim = self.backbone.num_features  # 1280 for EfficientNetB0\n        \n        # Temporal aggregation layer\n        self.temporal_pool = nn.AdaptiveAvgPool1d(1)\n        \n        # Classification head\n        self.classifier = nn.Sequential(\n            nn.Dropout(0.2),\n            nn.Linear(self.feature_dim, num_classes)\n        )\n        \n    def forward(self, x):\n        # x shape: (batch_size, num_frames, channels, height, width)\n        batch_size, num_frames, channels, height, width = x.shape\n        \n        # Reshape to process all frames at once\n        x = x.view(batch_size * num_frames, channels, height, width)\n        \n        # Extract features using backbone\n        features = self.backbone(x)  # (batch_size * num_frames, feature_dim)\n        \n        # Reshape back to separate frames\n        features = features.view(batch_size, num_frames, self.feature_dim)\n        \n        # Temporal aggregation: average pooling across frames\n        # Transpose for AdaptiveAvgPool1d: (batch_size, feature_dim, num_frames)\n        features = features.transpose(1, 2)\n        pooled_features = self.temporal_pool(features)  # (batch_size, feature_dim, 1)\n        pooled_features = pooled_features.squeeze(-1)  # (batch_size, feature_dim)\n        \n        # Classification (output logits, not probabilities)\n        output = self.classifier(pooled_features)\n        \n        return output\n\nprint(\"Model architecture defined\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T00:44:04.026212Z","iopub.execute_input":"2025-08-02T00:44:04.026385Z","iopub.status.idle":"2025-08-02T00:44:04.095034Z","shell.execute_reply.started":"2025-08-02T00:44:04.026366Z","shell.execute_reply":"2025-08-02T00:44:04.094412Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# DICOM processing utilities\ndef apply_dicom_windowing(img: np.ndarray, window_center: float, window_width: float) -> np.ndarray:\n    \"\"\"Apply DICOM windowing to enhance image contrast\"\"\"\n    img_min = window_center - window_width // 2\n    img_max = window_center + window_width // 2\n    img = np.clip(img, img_min, img_max)\n    img = (img - img_min) / (img_max - img_min + 1e-7)\n    return (img * 255).astype(np.uint8)\n\ndef get_windowing_params(modality: str) -> Tuple[float, float]:\n    \"\"\"Get appropriate windowing for different modalities\"\"\"\n    windows = {\n        'CT': (40, 80),\n        'CTA': (50, 350),\n        'MRA': (600, 1200),\n        'MRI': (40, 80),\n        'MRI T2': (40, 80),\n        'MRI T1post': (40, 80),\n    }\n    return windows.get(modality, (50, 350))  # Default to CTA\n\ndef extract_sort_key(path: str) -> Tuple[float, float, str]:\n    \"\"\"Extract sorting key from DICOM file for proper ordering\"\"\"\n    try:\n        ds = pydicom.dcmread(path, stop_before_pixels=True, force=True)\n        instance_number = getattr(ds, 'InstanceNumber', None)\n        position = getattr(ds, 'ImagePositionPatient', [None, None, None])\n        z = position[2] if position and len(position) == 3 else None\n\n        if instance_number is not None:\n            return (int(instance_number), 0, path)\n        elif z is not None:\n            return (float('inf'), float(z), path)\n        else:\n            return (float('inf'), float('inf'), path)\n    except:\n        return (float('inf'), float('inf'), path)\n\ndef sort_dicom_paths(dcm_paths: List[str]) -> List[str]:\n    \"\"\"Sort DICOM paths by medical metadata for proper slice ordering\"\"\"\n    if not dcm_paths:\n        return []\n    \n    sort_info = []\n    for path in dcm_paths:\n        sort_info.append(extract_sort_key(path))\n    \n    sort_info.sort()\n    return [x[2] for x in sort_info]\n\nprint(\"DICOM processing functions ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T00:44:04.09573Z","iopub.execute_input":"2025-08-02T00:44:04.095971Z","iopub.status.idle":"2025-08-02T00:44:04.110155Z","shell.execute_reply.started":"2025-08-02T00:44:04.095954Z","shell.execute_reply":"2025-08-02T00:44:04.109423Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def process_single_dicom(dicom_path: str, modality: str = 'CTA') -> Optional[np.ndarray]:\n    \"\"\"Process a single DICOM file and return processed image\"\"\"\n    try:\n        # Read DICOM with force=True for better compatibility\n        dicom = pydicom.dcmread(dicom_path, force=True)\n        \n        # Check for pixel data\n        if 'PixelData' not in dicom:\n            if CFG.debug_mode:\n                print(f\"Warning: No pixel data in {dicom_path}\")\n            return None\n            \n        # Extract pixel array\n        img = dicom.pixel_array\n        \n        # Check if image is valid\n        if img is None or img.size == 0:\n            if CFG.debug_mode:\n                print(f\"Warning: Empty pixel array in {dicom_path}\")\n            return None\n            \n        # Handle photometric interpretation\n        interp = getattr(dicom, 'PhotometricInterpretation', 'MONOCHROME2')\n        \n        # Handle YBR color space conversion\n        if interp == \"YBR_FULL\":\n            try:\n                img = convert_color_space(img, 'YBR_FULL', 'RGB')\n            except:\n                pass\n        \n        # Convert to grayscale if multi-channel\n        if img.ndim == 3:\n            if interp in [\"RGB\", \"YBR_FULL\"]:\n                img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n            elif img.shape[2] == 1:\n                img = img[:, :, 0]\n            else:\n                img = img[:, :, 0]  # Take first channel\n        \n        # Ensure 2D image\n        if img.ndim != 2:\n            return None\n            \n        # Apply windowing if requested\n        if CFG.use_windowing:\n            window_center, window_width = get_windowing_params(modality)\n            img = apply_dicom_windowing(img, window_center, window_width)\n        else:\n            # Normalize without windowing\n            img = img.astype(np.float32)\n            img_min, img_max = img.min(), img.max()\n            if img_max > img_min:\n                img = ((img - img_min) / (img_max - img_min) * 255).astype(np.uint8)\n            else:\n                img = np.zeros_like(img, dtype=np.uint8)\n        \n        # Handle MONOCHROME1 (inverted grayscale)\n        if interp == \"MONOCHROME1\":\n            img = 255 - img\n            \n        # Validate before resize\n        if img.shape[0] == 0 or img.shape[1] == 0:\n            return None\n            \n        # Resize to target size\n        img = cv2.resize(img, (CFG.image_size, CFG.image_size))\n        \n        # Convert to RGB (3 channels)\n        img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n        \n        return img\n        \n    except Exception as e:\n        if CFG.debug_mode:\n            print(f\"Error processing {dicom_path}: {e}\")\n        return None\n\ndef process_dicom_series(series_path: str) -> np.ndarray:\n    \"\"\"Process DICOM series and return multi-frame tensor\"\"\"\n    series_path = Path(series_path)\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        if CFG.debug_mode:\n            print(f\"Warning: No DICOM files found in {series_path}\")\n        return create_dummy_tensor()\n    \n    # Sort files by medical metadata\n    sorted_files = sort_dicom_paths(dicom_files)\n    \n    # Get modality from first file\n    try:\n        first_dicom = pydicom.dcmread(sorted_files[0], stop_before_pixels=True)\n        modality = getattr(first_dicom, 'Modality', 'CTA')\n    except:\n        modality = 'CTA'\n    \n    # Process each DICOM file\n    processed_images = []\n    for dicom_path in sorted_files:\n        img = process_single_dicom(dicom_path, modality)\n        if img is not None:\n            processed_images.append(img)\n    \n    if not processed_images:\n        if CFG.debug_mode:\n            print(f\"Warning: No images processed successfully for {series_path}\")\n        return create_dummy_tensor()\n    \n    # Sample frames to match target number\n    sampled_images = sample_frames(processed_images, CFG.num_frames)\n    \n    # Apply normalization (match training)\n    transform = A.Compose([\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ])\n    \n    frame_tensors = []\n    for img in sampled_images:\n        try:\n            transformed = transform(image=img)\n            frame_tensors.append(transformed['image'])\n        except:\n            # Create dummy tensor on transform failure\n            dummy_img = np.zeros((CFG.image_size, CFG.image_size, 3), dtype=np.uint8)\n            transformed = transform(image=dummy_img)\n            frame_tensors.append(transformed['image'])\n    \n    # Stack frames\n    multi_frame_tensor = torch.stack(frame_tensors)  # (num_frames, C, H, W)\n    \n    return multi_frame_tensor\n\ndef sample_frames(images: List[np.ndarray], target_frames: int) -> List[np.ndarray]:\n    \"\"\"Sample frames to match target number (same logic as training)\"\"\"\n    total_frames = len(images)\n    \n    if total_frames >= target_frames:\n        # Uniform subsampling\n        indices = np.linspace(0, total_frames-1, target_frames, dtype=int)\n    else:\n        # Repeat frames to reach target number\n        repeat_factor = target_frames // total_frames\n        remainder = target_frames % total_frames\n        \n        indices = list(range(total_frames)) * repeat_factor\n        if remainder > 0:\n            indices.extend(np.linspace(0, total_frames-1, remainder, dtype=int))\n    \n    return [images[i] for i in indices[:target_frames]]\n\ndef create_dummy_tensor() -> torch.Tensor:\n    \"\"\"Create dummy tensor when processing fails\"\"\"\n    transform = A.Compose([\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ])\n    \n    dummy_images = []\n    for _ in range(CFG.num_frames):\n        dummy_img = np.zeros((CFG.image_size, CFG.image_size, 3), dtype=np.uint8)\n        transformed = transform(image=dummy_img)\n        dummy_images.append(transformed['image'])\n    \n    return torch.stack(dummy_images)\n\nprint(\"DICOM series processing ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T00:44:04.111139Z","iopub.execute_input":"2025-08-02T00:44:04.111647Z","iopub.status.idle":"2025-08-02T00:44:04.133105Z","shell.execute_reply.started":"2025-08-02T00:44:04.111628Z","shell.execute_reply":"2025-08-02T00:44:04.132408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Global variables\nMODEL = None\n\n# Add this alias to match training code's Config class\nConfig = InferenceConfig\n\ndef load_model() -> nn.Module:\n    \"\"\"Load trained model with Config compatibility fix\"\"\"\n    print(f\"Loading model from: {CFG.model_path}\")\n    \n    if not os.path.exists(CFG.model_path):\n        raise FileNotFoundError(f\"Model file not found: {CFG.model_path}\")\n    \n    # Initialize model first\n    model = MultiFrameEfficientNet(\n        num_frames=CFG.num_frames,\n        num_classes=CFG.num_classes,\n        pretrained=False  # Loading trained weights\n    )\n    \n    try:\n        # Try loading with weights_only=True first (safest)\n        checkpoint = torch.load(CFG.model_path, map_location='cpu', weights_only=True)\n        model.load_state_dict(checkpoint)\n        print(\"Loaded model weights successfully (weights_only=True)\")\n        \n    except Exception as e1:\n        print(f\"Failed with weights_only=True: {e1}\")\n        try:\n            # Fallback: load full checkpoint\n            checkpoint = torch.load(CFG.model_path, map_location='cpu', weights_only=False)\n            \n            # Load weights\n            if 'model_state_dict' in checkpoint:\n                model.load_state_dict(checkpoint['model_state_dict'])\n                if 'best_score' in checkpoint:\n                    print(f\"Loaded model with best score: {checkpoint['best_score']:.6f}\")\n                if 'epoch' in checkpoint:\n                    print(f\"Best epoch: {checkpoint['epoch']}\")\n            else:\n                model.load_state_dict(checkpoint)\n            print(\"Loaded model with full checkpoint\")\n            \n        except Exception as e2:\n            print(f\"Failed with full checkpoint: {e2}\")\n            # Last resort: try to extract only state_dict\n            try:\n                checkpoint = torch.load(CFG.model_path, map_location='cpu', weights_only=False)\n                # Extract only the model weights\n                if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:\n                    state_dict = checkpoint['model_state_dict']\n                else:\n                    state_dict = checkpoint\n                \n                model.load_state_dict(state_dict)\n                print(\"Loaded model with extracted state_dict\")\n                \n            except Exception as e3:\n                raise RuntimeError(f\"All loading methods failed: {e1}, {e2}, {e3}\")\n    \n    # Move to device and set eval mode\n    model = model.to(device)\n    model.eval()\n    \n    return model\n\ndef initialize_model():\n    \"\"\"Initialize model and warm up\"\"\"\n    global MODEL\n    \n    if MODEL is None:\n        MODEL = load_model()\n        \n        # Warm up model\n        print(\"Warming up model...\")\n        dummy_input = torch.randn(1, CFG.num_frames, 3, CFG.image_size, CFG.image_size).to(device)\n        \n        with torch.no_grad():\n            with autocast(enabled=CFG.use_amp):\n                _ = MODEL(dummy_input)\n        \n        print(\"Model ready for inference!\")\n\nprint(\"Model loading functions ready (with Config fix)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T00:44:04.133896Z","iopub.execute_input":"2025-08-02T00:44:04.134102Z","iopub.status.idle":"2025-08-02T00:44:04.150571Z","shell.execute_reply.started":"2025-08-02T00:44:04.134086Z","shell.execute_reply":"2025-08-02T00:44:04.149841Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_series(series_path: str) -> np.ndarray:\n    \"\"\"Make prediction for a single series\"\"\"\n    global MODEL\n    \n    # Initialize model if needed\n    if MODEL is None:\n        initialize_model()\n    \n    try:\n        # Process DICOM series\n        series_tensor = process_dicom_series(series_path)\n        \n        # Add batch dimension and move to device\n        series_tensor = series_tensor.unsqueeze(0).to(device)  # (1, num_frames, C, H, W)\n        \n        # Make prediction\n        with torch.no_grad():\n            with autocast(enabled=CFG.use_amp):\n                logits = MODEL(series_tensor)\n                probabilities = torch.sigmoid(logits)\n        \n        # Convert to numpy\n        predictions = probabilities.cpu().numpy()[0]\n        \n        # Validate predictions\n        predictions = np.clip(predictions, 0.0, 1.0)\n        predictions = np.nan_to_num(predictions, nan=0.1)\n        \n        return predictions\n        \n    except Exception as e:\n        if CFG.debug_mode:\n            print(f\"Error in prediction: {e}\")\n        return create_fallback_predictions()\n\ndef create_fallback_predictions() -> np.ndarray:\n    \"\"\"Create conservative fallback predictions\"\"\"\n    # Conservative predictions based on training data distribution\n    fallback_values = np.array([\n        0.05, 0.05, 0.08, 0.08,  # Carotid arteries\n        0.12, 0.12,              # Middle cerebral arteries  \n        0.15,                    # Anterior communicating\n        0.06, 0.06,              # Anterior cerebral arteries\n        0.07, 0.07,              # Posterior communicating\n        0.09,                    # Basilar tip\n        0.11,                    # Other posterior circulation\n        0.43                     # Aneurysm present (training distribution)\n    ])\n    return fallback_values\n\ndef _predict_inner(series_path: str) -> pl.DataFrame:\n    \"\"\"Internal prediction logic\"\"\"\n    # Extract series ID for logging\n    series_id = os.path.basename(series_path)\n    \n    if CFG.debug_mode:\n        print(f\"Processing series: {series_id}\")\n    \n    # Make prediction\n    predictions = predict_series(series_path)\n    \n    # Create output dataframe (API requires no SeriesInstanceUID column)\n    predictions_df = pl.DataFrame(\n        data=[predictions.tolist()],\n        schema=LABEL_COLS,\n        orient='row'\n    )\n    \n    if CFG.debug_mode:\n        print(f\"Prediction range: {predictions.min():.6f} - {predictions.max():.6f}\")\n        print(f\"Aneurysm Present: {predictions[-1]:.6f}\")\n    \n    return predictions_df\n\nprint(\"Prediction functions ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T00:44:04.151315Z","iopub.execute_input":"2025-08-02T00:44:04.151603Z","iopub.status.idle":"2025-08-02T00:44:04.166002Z","shell.execute_reply.started":"2025-08-02T00:44:04.15158Z","shell.execute_reply":"2025-08-02T00:44:04.165467Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(series_path: str) -> pl.DataFrame:\n    \"\"\"\n    Main prediction function for Kaggle API.\n    This function is called by the inference server for each test series.\n    \"\"\"\n    try:\n        # Call internal prediction logic\n        return _predict_inner(series_path)\n        \n    except Exception as e:\n        print(f\"Error during prediction for {os.path.basename(series_path)}: {e}\")\n        print(\"Using fallback predictions.\")\n        \n        # Return fallback predictions\n        fallback_preds = create_fallback_predictions()\n        predictions_df = pl.DataFrame(\n            data=[fallback_preds.tolist()],\n            schema=LABEL_COLS,\n            orient='row'\n        )\n        \n        return predictions_df\n        \n    finally:\n        # Required cleanup to prevent disk space issues\n        shared_dir = '/kaggle/shared'\n        shutil.rmtree(shared_dir, ignore_errors=True)\n        os.makedirs(shared_dir, exist_ok=True)\n        \n        # Memory cleanup\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n        gc.collect()\n\nprint(\"Main API function ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T00:44:04.16668Z","iopub.execute_input":"2025-08-02T00:44:04.166994Z","iopub.status.idle":"2025-08-02T00:44:04.18008Z","shell.execute_reply.started":"2025-08-02T00:44:04.166967Z","shell.execute_reply":"2025-08-02T00:44:04.179394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Main execution\ndef main():\n    \"\"\"Main execution function\"\"\"\n    print(\"=\"*70)\n    print(\"RSNA INTRACRANIAL ANEURYSM DETECTION - INFERENCE\")\n    print(\"=\"*70)\n    print(f\"Device: {device}\")\n    print(f\"Model: Multi-Frame EfficientNetB0\")\n    print(f\"Frames: {CFG.num_frames}\")\n    print(f\"Image size: {CFG.image_size}\")\n    print(f\"Use windowing: {CFG.use_windowing}\")\n    print(\"-\" * 70)\n    \n    try:\n        # Pre-load model\n        initialize_model()\n        \n        # Initialize inference server\n        print(\"Initializing inference server...\")\n        inference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n        \n        # Run inference\n        if os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n            print(\"Running in competition mode...\")\n            inference_server.serve()\n        else:\n            print(\"Running in local gateway mode...\")\n            inference_server.run_local_gateway()\n            \n            # Display results if available\n            submission_path = '/kaggle/working/submission.parquet'\n            if os.path.exists(submission_path):\n                try:\n                    submission_df = pl.read_parquet(submission_path)\n                    print(f\"\\nSubmission preview:\")\n                    print(f\"Shape: {submission_df.shape}\")\n                    print(submission_df.head())\n                except Exception as e:\n                    print(f\"Could not read submission file: {e}\")\n        \n        print(\"\\n\" + \"=\"*70)\n        print(\"INFERENCE COMPLETED SUCCESSFULLY!\")\n        print(\"=\"*70)\n        \n    except Exception as e:\n        print(f\"Critical error: {e}\")\n        print(\"This may indicate model loading or API configuration issues.\")\n        raise e\n\n# Run main execution\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T00:44:04.18175Z","iopub.execute_input":"2025-08-02T00:44:04.181929Z","iopub.status.idle":"2025-08-02T00:44:21.179371Z","shell.execute_reply.started":"2025-08-02T00:44:04.181916Z","shell.execute_reply":"2025-08-02T00:44:21.178574Z"}},"outputs":[],"execution_count":null}]}