{"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":13851420,"sourceType":"competition"},{"sourceId":13825668,"sourceType":"datasetVersion","datasetId":8804761},{"sourceId":619739,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":466156,"modelId":481982},{"sourceId":656571,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":496267,"modelId":511673}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"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\nfrom torch.serialization import add_safe_globals\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\")\n\n\nUSE_3CHANNEL_INPUT = True\nUSE_METADATA = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T08:50:01.47796Z","iopub.execute_input":"2025-11-22T08:50:01.478199Z","iopub.status.idle":"2025-11-22T08:50:47.126571Z","shell.execute_reply.started":"2025-11-22T08:50:01.478174Z","shell.execute_reply":"2025-11-22T08:50:47.125913Z"}},"outputs":[{"name":"stdout","text":"Using device: cuda\nGPU: Tesla T4\nMemory: 14.7 GB\n","output_type":"stream"}],"execution_count":1},{"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    num_frames = 8\n    image_size = 224\n    num_classes = 14\n    USE_3CHANNEL_INPUT = True\n    USE_METADATA = True\n    MODEL_NAME_BACKBONE = \"tf_efficientnetv2_s.in1k\"\n    # Model path\n    model_path = \"/kaggle/input/aneurysm-rsna/pytorch/default/1/aneurysmsecond_efficientnetv2s_best.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    device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\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-11-22T08:51:28.279692Z","iopub.execute_input":"2025-11-22T08:51:28.279996Z","iopub.status.idle":"2025-11-22T08:51:28.286671Z","shell.execute_reply.started":"2025-11-22T08:51:28.279971Z","shell.execute_reply":"2025-11-22T08:51:28.285851Z"}},"outputs":[{"name":"stdout","text":"Configuration loaded:\n- Frames: 8\n- Image size: 224\n- Model path: /kaggle/input/aneurysm-rsna/pytorch/default/1/aneurysmsecond_efficientnetv2s_best.pth\n- Use windowing: True\n- Config alias created for compatibility\n","output_type":"stream"}],"execution_count":2},{"cell_type":"code","source":"class EightFrameEfficientNet(nn.Module):\n    def __init__(self, num_frames=8, num_classes=14, pretrained=True):\n        super(EightFrameEfficientNet, self).__init__()\n        self.num_frames = num_frames\n        self.num_classes = num_classes\n        self.use_3channel = Config.USE_3CHANNEL_INPUT\n        self.use_metadata = Config.USE_METADATA\n        \n        # Backbone: EfficientNetV2-S\n        print(f\"Loading backbone: {Config.MODEL_NAME_BACKBONE}\")\n        self.backbone = timm.create_model(\n            Config.MODEL_NAME_BACKBONE,\n            pretrained=pretrained,\n            num_classes=0,\n            global_pool='avg'\n        )\n        \n        self.feature_dim = self.backbone.num_features\n        print(f\"Backbone {Config.MODEL_NAME_BACKBONE}: {self.feature_dim} features\")\n        \n        # ✅ Missing temporal pooling layer added here\n        self.temporal_pool = nn.AdaptiveAvgPool1d(1)\n\n        # Metadata processing\n        if self.use_metadata:\n            self.meta_fc = nn.Sequential(\n                nn.Linear(2, 16),\n                nn.ReLU(),\n                nn.Dropout(0.2),\n                nn.Linear(16, 32),\n                nn.ReLU()\n            )\n            classifier_input_dim = self.feature_dim + 32\n        else:\n            classifier_input_dim = self.feature_dim\n        \n        # Classifier\n        self.classifier = nn.Sequential(\n            nn.Linear(classifier_input_dim, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, num_classes)\n        )\n        \n    def forward(self, x, meta=None):\n        batch_size, num_frames, channels, height, width = x.shape\n    \n        x = x.view(batch_size * num_frames, channels, height, width)\n        features = self.backbone(x)\n        features = features.view(batch_size, num_frames, self.feature_dim)\n        features = features.transpose(1, 2)\n        pooled_features = self.temporal_pool(features).squeeze(-1)\n    \n        # Include metadata if available\n        if self.use_metadata and meta is not None:\n            meta_features = self.meta_fc(meta)\n            pooled_features = torch.cat([pooled_features, meta_features], dim=1)\n    \n        output = self.classifier(pooled_features)\n        return output\n\nprint(\"Model architecture defined\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T08:51:30.638383Z","iopub.execute_input":"2025-11-22T08:51:30.638646Z","iopub.status.idle":"2025-11-22T08:51:30.647717Z","shell.execute_reply.started":"2025-11-22T08:51:30.638626Z","shell.execute_reply":"2025-11-22T08:51:30.646981Z"}},"outputs":[{"name":"stdout","text":"Model architecture defined\n","output_type":"stream"}],"execution_count":3},{"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():\n    global MODEL\n    if MODEL is not None:\n        return MODEL\n\n    model = EightFrameEfficientNet(\n        num_frames=CFG.num_frames,\n        num_classes=CFG.num_classes,\n        pretrained=False\n    )\n\n    safe_globals = [\n        np.dtype,\n        np.float32,\n        np.float64,\n        np.int32,\n        np.int64,\n        np.bool_,\n        np.core.multiarray.scalar\n    ]\n\n    for g in safe_globals:\n        torch.serialization.add_safe_globals([g])\n\n    # -----------------------\n    # Load checkpoint\n    # -----------------------\n    checkpoint = torch.load(CFG.model_path, map_location=\"cpu\", weights_only=False)\n\n    # Extract state_dict\n    state_dict = checkpoint.get(\"model_state_dict\", checkpoint) if isinstance(checkpoint, dict) else checkpoint\n\n    # -----------------------\n    # Filter layers that match model\n    # -----------------------\n    model_dict = model.state_dict()\n    filtered_dict = {}\n    skipped_layers = []\n    for k, v in state_dict.items():\n        if k in model_dict and v.size() == model_dict[k].size():\n            filtered_dict[k] = v\n        else:\n            skipped_layers.append(k)\n\n    if skipped_layers:\n        print(f\"Skipped layers due to size mismatch or missing: {skipped_layers}\")\n\n    # Load weights\n    model_dict.update(filtered_dict)\n    model.load_state_dict(model_dict)\n\n    # Move to device\n    model = model.to(CFG.device)\n    model.eval()\n    MODEL = model\n    print(\"Model loaded successfully!\")\n    return MODEL\n\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-11-22T08:51:31.911604Z","iopub.execute_input":"2025-11-22T08:51:31.912401Z","iopub.status.idle":"2025-11-22T08:51:31.92053Z","shell.execute_reply.started":"2025-11-22T08:51:31.912369Z","shell.execute_reply":"2025-11-22T08:51:31.919802Z"}},"outputs":[{"name":"stdout","text":"Model loading functions ready (with Config fix)\n","output_type":"stream"}],"execution_count":4},{"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-11-22T08:51:34.498276Z","iopub.execute_input":"2025-11-22T08:51:34.498962Z","iopub.status.idle":"2025-11-22T08:51:34.507335Z","shell.execute_reply.started":"2025-11-22T08:51:34.498913Z","shell.execute_reply":"2025-11-22T08:51:34.506387Z"}},"outputs":[{"name":"stdout","text":"DICOM processing functions ready\n","output_type":"stream"}],"execution_count":5},{"cell_type":"code","source":"# import timm\n# print([m for m in timm.list_models() if 'efficientnetv2' in m])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T08:51:35.986789Z","iopub.execute_input":"2025-11-22T08:51:35.987082Z","iopub.status.idle":"2025-11-22T08:51:35.990479Z","shell.execute_reply.started":"2025-11-22T08:51:35.987058Z","shell.execute_reply":"2025-11-22T08:51:35.98978Z"}},"outputs":[],"execution_count":6},{"cell_type":"code","source":"def predict_series(series_path: str, age: int, gender: str) -> np.ndarray:\n    \"\"\"\n    Make prediction for a single series. \n    Applies sigmoid to all 14 outputs, enforces 'Aneurysm Present' logic, \n    and returns only the first 13 location-specific predictions.\n    \"\"\"\n    global MODEL\n    \n    # Ensure model is loaded\n    if MODEL is None:\n        initialize_model()\n    \n    try:\n        # --- Data Loading and Preprocessing ---\n        path_obj = Path(series_path)\n        if path_obj.is_file() and path_obj.suffix.lower() == '.dcm':\n            series_tensor = process_single_dicom_as_series(series_path)\n        else:\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)\n        \n        # Prepare metadata\n        age_meta = min(float(age), 100.0) / 100.0\n        gender_meta = 1.0 if gender==\"M\" else 0.0\n        metadata_inp = torch.tensor([[age_meta, gender_meta]], dtype=torch.float32).to(device)\n\n        with torch.no_grad():\n            with autocast(enabled=CFG.use_amp):\n                logits = MODEL(series_tensor, metadata_inp) \n        \n        # 2. Apply LOGIT THRESHOLD only to the first 13 classes for calibration\n        # This shifts the logits for the location-specific classes downward.\n        calibrated_logits = logits.clone().squeeze(0)\n        calibrated_logits[:13] = calibrated_logits[:13] + 0.\n        \n        # 3. Apply Sigmoid to all 14 (calibrated) logits\n        probabilities = torch.sigmoid(calibrated_logits)\n        predictions = probabilities.cpu().numpy() # (14,)\n        \n        # --- 4. Custom Post-Processing Logic ---\n        # Get the first 13 location-specific probabilities (indices 0 to 12)\n        location_predictions = predictions[:13]\n        \n        # Overwrite 'Aneurysm Present' (index 13) with the maximum of the 13 locations\n        predictions[13] = np.max(location_predictions)\n        # ---------------------------------------\n        \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        \n    except Exception as e:\n        # if CFG.debug_mode:\n        #     print(f\"Error in prediction: {e}\")\n        if CFG.debug_mode:\n            print(\"Logits:\", logits.cpu().numpy()[0])\n            print(\"Probabilities:\", probabilities.cpu().numpy()[0])\n\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, age: int, gender: 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, age, gender)\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-11-22T08:51:36.340637Z","iopub.execute_input":"2025-11-22T08:51:36.341203Z","iopub.status.idle":"2025-11-22T08:51:36.351466Z","shell.execute_reply.started":"2025-11-22T08:51:36.34118Z","shell.execute_reply":"2025-11-22T08:51:36.350737Z"}},"outputs":[{"name":"stdout","text":"Prediction functions ready\n","output_type":"stream"}],"execution_count":7},{"cell_type":"code","source":"def process_single_dicom_as_series(dicom_path: str) -> torch.Tensor:\n    \"\"\"Process a single DICOM file as a pseudo-series.\"\"\"\n    try:\n        # Read DICOM and process it like a normal slice\n        modality = 'CTA'\n        img = process_single_dicom(dicom_path, modality)\n        \n        if img is None:\n            if CFG.debug_mode:\n                print(f\"Warning: failed to process single DICOM {dicom_path}\")\n            return create_dummy_tensor()\n        \n        # Apply same normalization and transform as in process_dicom_series\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        transformed = transform(image=img)['image']\n        \n        # Repeat the same frame to simulate num_frames\n        frame_tensors = [transformed for _ in range(CFG.num_frames)]\n        multi_frame_tensor = torch.stack(frame_tensors)  # (num_frames, C, H, W)\n        \n        return multi_frame_tensor\n    \n    except Exception as e:\n        if CFG.debug_mode:\n            print(f\"Error processing single DICOM: {e}\")\n        return create_dummy_tensor()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T08:51:39.725611Z","iopub.execute_input":"2025-11-22T08:51:39.726096Z","iopub.status.idle":"2025-11-22T08:51:39.731457Z","shell.execute_reply.started":"2025-11-22T08:51:39.72607Z","shell.execute_reply":"2025-11-22T08:51:39.73078Z"}},"outputs":[],"execution_count":8},{"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-11-22T08:51:40.067272Z","iopub.execute_input":"2025-11-22T08:51:40.067889Z","iopub.status.idle":"2025-11-22T08:51:40.084027Z","shell.execute_reply.started":"2025-11-22T08:51:40.067864Z","shell.execute_reply":"2025-11-22T08:51:40.083235Z"}},"outputs":[{"name":"stdout","text":"DICOM series processing ready\n","output_type":"stream"}],"execution_count":9},{"cell_type":"code","source":"def predict(series_path: str, age: int, gender: 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, age, gender)\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-11-22T08:51:44.655175Z","iopub.execute_input":"2025-11-22T08:51:44.655438Z","iopub.status.idle":"2025-11-22T08:51:44.661318Z","shell.execute_reply.started":"2025-11-22T08:51:44.655418Z","shell.execute_reply":"2025-11-22T08:51:44.660489Z"}},"outputs":[{"name":"stdout","text":"Main API function ready\n","output_type":"stream"}],"execution_count":10},{"cell_type":"code","source":"predict(\"/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.10129580404994628606227497184499173213\", 70, \"F\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T08:51:45.256545Z","iopub.execute_input":"2025-11-22T08:51:45.257041Z","iopub.status.idle":"2025-11-22T08:51:49.843681Z","shell.execute_reply.started":"2025-11-22T08:51:45.257019Z","shell.execute_reply":"2025-11-22T08:51:49.843141Z"}},"outputs":[{"name":"stdout","text":"Loading backbone: tf_efficientnetv2_s.in1k\nBackbone tf_efficientnetv2_s.in1k: 1280 features\nModel loaded successfully!\nWarming up model...\nError during prediction for 1.2.826.0.1.3680043.8.498.10129580404994628606227497184499173213: mat1 and mat2 shapes cannot be multiplied (1x1280 and 1312x512)\nUsing fallback predictions.\n","output_type":"stream"},{"execution_count":11,"output_type":"execute_result","data":{"text/plain":"shape: (1, 14)\n┌───────────┬───────────┬───────────┬───────────┬───┬───────────┬───────────┬───────────┬──────────┐\n│ Left Infr ┆ Right Inf ┆ Left Supr ┆ Right Sup ┆ … ┆ Right     ┆ Basilar   ┆ Other     ┆ Aneurysm │\n│ aclinoid  ┆ raclinoid ┆ aclinoid  ┆ raclinoid ┆   ┆ Posterior ┆ Tip       ┆ Posterior ┆ Present  │\n│ Internal  ┆ Internal  ┆ Internal  ┆ Internal  ┆   ┆ Communica ┆ ---       ┆ Circulati ┆ ---      │\n│ Car…      ┆ Ca…       ┆ Car…      ┆ Ca…       ┆   ┆ ting …    ┆ f64       ┆ on        ┆ f64      │\n│ ---       ┆ ---       ┆ ---       ┆ ---       ┆   ┆ ---       ┆           ┆ ---       ┆          │\n│ f64       ┆ f64       ┆ f64       ┆ f64       ┆   ┆ f64       ┆           ┆ f64       ┆          │\n╞═══════════╪═══════════╪═══════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪══════════╡\n│ 0.05      ┆ 0.05      ┆ 0.08      ┆ 0.08      ┆ … ┆ 0.07      ┆ 0.09      ┆ 0.11      ┆ 0.43     │\n└───────────┴───────────┴───────────┴───────────┴───┴───────────┴───────────┴───────────┴──────────┘","text/html":"<div><style>\n.dataframe > thead > tr,\n.dataframe > tbody > tr {\n  text-align: right;\n  white-space: pre-wrap;\n}\n</style>\n<small>shape: (1, 14)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>Left Infraclinoid Internal Carotid Artery</th><th>Right Infraclinoid Internal Carotid Artery</th><th>Left Supraclinoid Internal Carotid Artery</th><th>Right Supraclinoid Internal Carotid Artery</th><th>Left Middle Cerebral Artery</th><th>Right Middle Cerebral Artery</th><th>Anterior Communicating Artery</th><th>Left Anterior Cerebral Artery</th><th>Right Anterior Cerebral Artery</th><th>Left Posterior Communicating Artery</th><th>Right Posterior Communicating Artery</th><th>Basilar Tip</th><th>Other Posterior Circulation</th><th>Aneurysm Present</th></tr><tr><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td></tr></thead><tbody><tr><td>0.05</td><td>0.05</td><td>0.08</td><td>0.08</td><td>0.12</td><td>0.12</td><td>0.15</td><td>0.06</td><td>0.06</td><td>0.07</td><td>0.07</td><td>0.09</td><td>0.11</td><td>0.43</td></tr></tbody></table></div>"},"metadata":{}}],"execution_count":11},{"cell_type":"code","source":"predict(\"/kaggle/input/rsna-intracranial-aneurysm-detection/kaggle_evaluation/series/1.2.826.0.1.3680043.8.498.10028406715369553772267826812576760572\", 70, \"F\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-28T09:21:29.647477Z","iopub.execute_input":"2025-10-28T09:21:29.647775Z","iopub.status.idle":"2025-10-28T09:21:30.465964Z","shell.execute_reply.started":"2025-10-28T09:21:29.647751Z","shell.execute_reply":"2025-10-28T09:21:30.465088Z"}},"outputs":[{"execution_count":30,"output_type":"execute_result","data":{"text/plain":"shape: (1, 14)\n┌───────────┬───────────┬───────────┬───────────┬───┬───────────┬───────────┬───────────┬──────────┐\n│ Left Infr ┆ Right Inf ┆ Left Supr ┆ Right Sup ┆ … ┆ Right     ┆ Basilar   ┆ Other     ┆ Aneurysm │\n│ aclinoid  ┆ raclinoid ┆ aclinoid  ┆ raclinoid ┆   ┆ Posterior ┆ Tip       ┆ Posterior ┆ Present  │\n│ Internal  ┆ Internal  ┆ Internal  ┆ Internal  ┆   ┆ Communica ┆ ---       ┆ Circulati ┆ ---      │\n│ Car…      ┆ Ca…       ┆ Car…      ┆ Ca…       ┆   ┆ ting …    ┆ f64       ┆ on        ┆ f64      │\n│ ---       ┆ ---       ┆ ---       ┆ ---       ┆   ┆ ---       ┆           ┆ ---       ┆          │\n│ f64       ┆ f64       ┆ f64       ┆ f64       ┆   ┆ f64       ┆           ┆ f64       ┆          │\n╞═══════════╪═══════════╪═══════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪══════════╡\n│ 0.131348  ┆ 0.122131  ┆ 0.208496  ┆ 0.202637  ┆ … ┆ 0.124756  ┆ 0.14917   ┆ 0.117798  ┆ 0.208496 │\n└───────────┴───────────┴───────────┴───────────┴───┴───────────┴───────────┴───────────┴──────────┘","text/html":"<div><style>\n.dataframe > thead > tr,\n.dataframe > tbody > tr {\n  text-align: right;\n  white-space: pre-wrap;\n}\n</style>\n<small>shape: (1, 14)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>Left Infraclinoid Internal Carotid Artery</th><th>Right Infraclinoid Internal Carotid Artery</th><th>Left Supraclinoid Internal Carotid Artery</th><th>Right Supraclinoid Internal Carotid Artery</th><th>Left Middle Cerebral Artery</th><th>Right Middle Cerebral Artery</th><th>Anterior Communicating Artery</th><th>Left Anterior Cerebral Artery</th><th>Right Anterior Cerebral Artery</th><th>Left Posterior Communicating Artery</th><th>Right Posterior Communicating Artery</th><th>Basilar Tip</th><th>Other Posterior Circulation</th><th>Aneurysm Present</th></tr><tr><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td></tr></thead><tbody><tr><td>0.131348</td><td>0.122131</td><td>0.208496</td><td>0.202637</td><td>0.205322</td><td>0.203491</td><td>0.20752</td><td>0.136963</td><td>0.12146</td><td>0.134033</td><td>0.124756</td><td>0.14917</td><td>0.117798</td><td>0.208496</td></tr></tbody></table></div>"},"metadata":{}}],"execution_count":30},{"cell_type":"code","source":"predict(\"/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.11844250879964477356963545546963437171\", 42, \"F\") #ok","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-28T09:21:16.406777Z","iopub.execute_input":"2025-10-28T09:21:16.407499Z","iopub.status.idle":"2025-10-28T09:21:17.017967Z","shell.execute_reply.started":"2025-10-28T09:21:16.407466Z","shell.execute_reply":"2025-10-28T09:21:17.017087Z"}},"outputs":[{"execution_count":29,"output_type":"execute_result","data":{"text/plain":"shape: (1, 14)\n┌───────────┬───────────┬───────────┬───────────┬───┬───────────┬───────────┬───────────┬──────────┐\n│ Left Infr ┆ Right Inf ┆ Left Supr ┆ Right Sup ┆ … ┆ Right     ┆ Basilar   ┆ Other     ┆ Aneurysm │\n│ aclinoid  ┆ raclinoid ┆ aclinoid  ┆ raclinoid ┆   ┆ Posterior ┆ Tip       ┆ Posterior ┆ Present  │\n│ Internal  ┆ Internal  ┆ Internal  ┆ Internal  ┆   ┆ Communica ┆ ---       ┆ Circulati ┆ ---      │\n│ Car…      ┆ Ca…       ┆ Car…      ┆ Ca…       ┆   ┆ ting …    ┆ f64       ┆ on        ┆ f64      │\n│ ---       ┆ ---       ┆ ---       ┆ ---       ┆   ┆ ---       ┆           ┆ ---       ┆          │\n│ f64       ┆ f64       ┆ f64       ┆ f64       ┆   ┆ f64       ┆           ┆ f64       ┆          │\n╞═══════════╪═══════════╪═══════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪══════════╡\n│ 0.129761  ┆ 0.158325  ┆ 0.207642  ┆ 0.214844  ┆ … ┆ 0.132324  ┆ 0.154907  ┆ 0.115784  ┆ 0.217896 │\n└───────────┴───────────┴───────────┴───────────┴───┴───────────┴───────────┴───────────┴──────────┘","text/html":"<div><style>\n.dataframe > thead > tr,\n.dataframe > tbody > tr {\n  text-align: right;\n  white-space: pre-wrap;\n}\n</style>\n<small>shape: (1, 14)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>Left Infraclinoid Internal Carotid Artery</th><th>Right Infraclinoid Internal Carotid Artery</th><th>Left Supraclinoid Internal Carotid Artery</th><th>Right Supraclinoid Internal Carotid Artery</th><th>Left Middle Cerebral Artery</th><th>Right Middle Cerebral Artery</th><th>Anterior Communicating Artery</th><th>Left Anterior Cerebral Artery</th><th>Right Anterior Cerebral Artery</th><th>Left Posterior Communicating Artery</th><th>Right Posterior Communicating Artery</th><th>Basilar Tip</th><th>Other Posterior Circulation</th><th>Aneurysm Present</th></tr><tr><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td></tr></thead><tbody><tr><td>0.129761</td><td>0.158325</td><td>0.207642</td><td>0.214844</td><td>0.197144</td><td>0.217896</td><td>0.213867</td><td>0.119507</td><td>0.142212</td><td>0.120117</td><td>0.132324</td><td>0.154907</td><td>0.115784</td><td>0.217896</td></tr></tbody></table></div>"},"metadata":{}}],"execution_count":29},{"cell_type":"code","source":"predict(\"/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.12447797975778424066887941637869655361\", 73, \"F\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predict(\"/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.10022796280698534221758473208024838831\", 78, \"M\") # This is of present","metadata":{"trusted":true,"execution":{"execution_failed":"2025-10-28T09:27:07.15Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predict(\"/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.10005158603912009425635473100344077317\", 58, \"M\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-28T09:22:27.186582Z","iopub.execute_input":"2025-10-28T09:22:27.186876Z","iopub.status.idle":"2025-10-28T09:22:31.868258Z","shell.execute_reply.started":"2025-10-28T09:22:27.186854Z","shell.execute_reply":"2025-10-28T09:22:31.867418Z"}},"outputs":[{"execution_count":32,"output_type":"execute_result","data":{"text/plain":"shape: (1, 14)\n┌───────────┬───────────┬───────────┬───────────┬───┬───────────┬───────────┬───────────┬──────────┐\n│ Left Infr ┆ Right Inf ┆ Left Supr ┆ Right Sup ┆ … ┆ Right     ┆ Basilar   ┆ Other     ┆ Aneurysm │\n│ aclinoid  ┆ raclinoid ┆ aclinoid  ┆ raclinoid ┆   ┆ Posterior ┆ Tip       ┆ Posterior ┆ Present  │\n│ Internal  ┆ Internal  ┆ Internal  ┆ Internal  ┆   ┆ Communica ┆ ---       ┆ Circulati ┆ ---      │\n│ Car…      ┆ Ca…       ┆ Car…      ┆ Ca…       ┆   ┆ ting …    ┆ f64       ┆ on        ┆ f64      │\n│ ---       ┆ ---       ┆ ---       ┆ ---       ┆   ┆ ---       ┆           ┆ ---       ┆          │\n│ f64       ┆ f64       ┆ f64       ┆ f64       ┆   ┆ f64       ┆           ┆ f64       ┆          │\n╞═══════════╪═══════════╪═══════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪══════════╡\n│ 0.119812  ┆ 0.086792  ┆ 0.204346  ┆ 0.192749  ┆ … ┆ 0.126099  ┆ 0.130127  ┆ 0.103027  ┆ 0.204346 │\n└───────────┴───────────┴───────────┴───────────┴───┴───────────┴───────────┴───────────┴──────────┘","text/html":"<div><style>\n.dataframe > thead > tr,\n.dataframe > tbody > tr {\n  text-align: right;\n  white-space: pre-wrap;\n}\n</style>\n<small>shape: (1, 14)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>Left Infraclinoid Internal Carotid Artery</th><th>Right Infraclinoid Internal Carotid Artery</th><th>Left Supraclinoid Internal Carotid Artery</th><th>Right Supraclinoid Internal Carotid Artery</th><th>Left Middle Cerebral Artery</th><th>Right Middle Cerebral Artery</th><th>Anterior Communicating Artery</th><th>Left Anterior Cerebral Artery</th><th>Right Anterior Cerebral Artery</th><th>Left Posterior Communicating Artery</th><th>Right Posterior Communicating Artery</th><th>Basilar Tip</th><th>Other Posterior Circulation</th><th>Aneurysm Present</th></tr><tr><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td><td>f64</td></tr></thead><tbody><tr><td>0.119812</td><td>0.086792</td><td>0.204346</td><td>0.192749</td><td>0.165649</td><td>0.177856</td><td>0.155151</td><td>0.116577</td><td>0.11145</td><td>0.103577</td><td>0.126099</td><td>0.130127</td><td>0.103027</td><td>0.204346</td></tr></tbody></table></div>"},"metadata":{}}],"execution_count":32},{"cell_type":"code","source":"predict(\"/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.10004044428023505108375152878107656647\", 64, \"F\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Main execution\n# def 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: Eight-Frame EfficientNetV2\")\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\n# if __name__ == \"__main__\":\n#     main()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}