{"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":13851420,"sourceType":"competition"},{"sourceId":13368699,"sourceType":"datasetVersion","datasetId":8480841},{"sourceId":13387088,"sourceType":"datasetVersion","datasetId":8494374},{"sourceId":260045097,"sourceType":"kernelVersion"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install /kaggle/input/ultrayltics-whl/ultralytics-8.3.203-py3-none-any.whl --no-deps","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import 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\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\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 scipy import ndimage\n\n# ML/DL\nimport torch\nimport torch.nn as nn\nfrom torch.cuda.amp import autocast\nimport timm\nfrom ultralytics import YOLO\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}\")\n\n# ====================================================\n# Competition 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# ====================================================\n# Configuration\n# ====================================================\nclass InferenceConfig:\n    # YOLO settings - Multiple models\n    yolo_model_paths = [\n        '/kaggle/input/yolo-0-8-13c/fold_03/weights/best.pt',\n        # Add more YOLO model paths here\n        # '/kaggle/input/yolo-fold-01/weights/best.pt',\n        # '/kaggle/input/yolo-fold-02/weights/best.pt',\n    ]\n    yolo_weights = [1.0]  # Weights for each YOLO model (should sum to 1.0 or will be normalized)\n    yolo_img_size = 512\n    yolo_conf_threshold = 0.01\n    yolo_batch_size = 32\n    yolo_num_workers = 4\n    yolo_aggregation_method = 'noisy_or'\n    yolo_background_prior = 0.005\n    \n    # EfficientNet settings\n    effnet_model_name = \"tf_efficientnetv2_s.in21k_ft_in1k\"\n    effnet_size = 512\n    effnet_in_chans = 32\n    effnet_target_shape = (32, 512, 512)\n    effnet_fold_paths = {\n        1: \"/kaggle/input/32ch-2d-train-512-augs-focal/efficientnetv2_fold1.pth\",\n        2: \"/kaggle/input/32ch-2d-train-512-augs-focal/efficientnetv2_fold2.pth\", \n        3: \"/kaggle/input/32ch-2d-train-512-augs-focal/efficientnetv2_fold3.pth\",\n        4: \"/kaggle/input/32ch-2d-train-512-augs-focal/efficientnetv2_fold4.pth\",\n        5: \"/kaggle/input/32ch-2d-train-512-augs-focal/efficientnetv2_fold5.pth\"\n    }\n    effnet_trn_folds = [1, 2, 3, 4, 5]\n    \n    # Ensemble settings\n    ensemble_method = 'weighted_average'  # 'weighted_average', 'geometric_mean', 'max'\n    yolo_weight = 0.8  # Weight for YOLO predictions\n    effnet_weight = 0.2  # Weight for EfficientNet predictions\n    \n    # Class mapping\n    location_to_class = {\n        'Left Infraclinoid Internal Carotid Artery': 0,\n        'Right Infraclinoid Internal Carotid Artery': 1,\n        'Left Supraclinoid Internal Carotid Artery': 2,\n        'Right Supraclinoid Internal Carotid Artery': 3,\n        'Left Middle Cerebral Artery': 4,\n        'Right Middle Cerebral Artery': 5,\n        'Anterior Communicating Artery': 6,\n        'Left Anterior Cerebral Artery': 7,\n        'Right Anterior Cerebral Artery': 8,\n        'Left Posterior Communicating Artery': 9,\n        'Right Posterior Communicating Artery': 10,\n        'Basilar Tip': 11,\n        'Other Posterior Circulation': 12\n    }\n    \n    num_classes = 14\n\nCFG = InferenceConfig()\n\n# ====================================================\n# Unified DICOM Preprocessor (for both YOLO and EfficientNet)\n# ====================================================\nclass UnifiedDICOMPreprocessor:\n    \"\"\"Processes DICOM once and provides data for both YOLO and EfficientNet\"\"\"\n    \n    def __init__(self, target_shape: Tuple[int, int, int] = (32, 512, 512)):\n        self.effnet_depth, self.effnet_height, self.effnet_width = target_shape\n        \n    def load_and_process_series(self, series_path: str) -> Tuple[List[np.ndarray], np.ndarray, List[float]]:\n        \"\"\"\n        Load DICOM series and process for both models.\n        Returns:\n            - yolo_images: List of RGB images for YOLO (512x512x3)\n            - effnet_volume: Preprocessed volume for EfficientNet (D, H, W)\n            - z_positions: Z-positions for ordering\n        \"\"\"\n        series_path = Path(series_path)\n        \n        # Get all DICOM files\n        dicom_files = []\n        for root, _, files in os.walk(series_path):\n            for file in files:\n                if file.endswith('.dcm'):\n                    dicom_files.append(os.path.join(root, file))\n        \n        if not dicom_files:\n            raise ValueError(f\"No DICOM files found in {series_path}\")\n        \n        # Load and process in parallel\n        slice_data = []\n        \n        def load_and_process_slice(filepath):\n            try:\n                ds = pydicom.dcmread(filepath, force=True)\n                img = ds.pixel_array.astype(np.float32)\n                \n                # Handle multi-frame\n                if img.ndim == 3:\n                    img = img[img.shape[0] // 2]\n                \n                # Handle RGB\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                # Apply rescale (but override for stability)\n                slope = getattr(ds, 'RescaleSlope', 1)\n                intercept = getattr(ds, 'RescaleIntercept', 0)\n                slope, intercept = 1, 0\n                if slope != 1 or intercept != 0:\n                    img = img * float(slope) + float(intercept)\n                \n                # Windowing/normalization\n                p1, p99 = 0, 500\n                if p99 > p1:\n                    normalized = np.clip(img, p1, p99)\n                    normalized = (normalized - p1) / (p99 - p1)\n                    img_uint8 = (normalized * 255).astype(np.uint8)\n                else:\n                    img_min, img_max = img.min(), img.max()\n                    if img_max > img_min:\n                        img_uint8 = ((img - img_min) / (img_max - img_min) * 255).astype(np.uint8)\n                    else:\n                        img_uint8 = np.zeros_like(img, dtype=np.uint8)\n                \n                # Get z-position\n                instance_num = getattr(ds, 'InstanceNumber', 0)\n                position = getattr(ds, 'ImagePositionPatient', None)\n                z_pos = float(position[2]) if position and len(position) >= 3 else float(instance_num)\n                \n                return {'image': img_uint8, 'z_position': z_pos}\n                \n            except Exception as e:\n                return None\n        \n        # Parallel loading\n        with ThreadPoolExecutor(max_workers=CFG.yolo_num_workers) as executor:\n            futures = [executor.submit(load_and_process_slice, f) for f in dicom_files]\n            for future in as_completed(futures):\n                result = future.result()\n                if result is not None:\n                    slice_data.append(result)\n        \n        if not slice_data:\n            raise ValueError(f\"No valid DICOM files in {series_path}\")\n        \n        # Sort by z-position\n        slice_data.sort(key=lambda x: x['z_position'])\n        \n        # Extract data\n        images_2d = [s['image'] for s in slice_data]\n        z_positions = [s['z_position'] for s in slice_data]\n        \n        # Prepare YOLO images (RGB conversion)\n        yolo_images = [cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) for img in images_2d]\n        \n        # Prepare EfficientNet volume\n        effnet_volume = self._prepare_effnet_volume(images_2d)\n        \n        return yolo_images, effnet_volume, z_positions\n    \n    def _prepare_effnet_volume(self, images_2d: List[np.ndarray]) -> np.ndarray:\n        \"\"\"Prepare volume for EfficientNet from already-processed 2D slices\"\"\"\n        \n        # Resize to target dimensions if needed\n        resized_slices = []\n        for img in images_2d:\n            if img.shape != (self.effnet_height, self.effnet_width):\n                resized = cv2.resize(img, (self.effnet_width, self.effnet_height))\n            else:\n                resized = img\n            resized_slices.append(resized)\n        \n        # Stack into volume\n        volume = np.stack(resized_slices, axis=0)\n        \n        # Resize depth dimension\n        if volume.shape[0] != self.effnet_depth:\n            zoom_factors = [self.effnet_depth / volume.shape[0], 1, 1]\n            volume = ndimage.zoom(volume, zoom_factors, order=1, mode='nearest')\n            \n            # Ensure exact size\n            volume = volume[:self.effnet_depth, :self.effnet_height, :self.effnet_width]\n            \n            # Pad if necessary\n            if volume.shape[0] < self.effnet_depth:\n                pad_width = [(0, self.effnet_depth - volume.shape[0]), (0, 0), (0, 0)]\n                volume = np.pad(volume, pad_width, mode='edge')\n        \n        return volume.astype(np.uint8)\n\n# ====================================================\n# YOLO Detection Converter\n# ====================================================\nclass DetectionToProbabilityConverter:\n    def __init__(self, method: str = 'noisy_or', background_prior: float = 0.01, top_k: int = 3):\n        self.method = method\n        self.background_prior = background_prior\n        self.top_k = top_k\n    \n    def aggregate_detections(self, detections: List[Dict]) -> np.ndarray:\n        class_probs = np.full(13, self.background_prior, dtype=float)\n        \n        if len(detections) > 0:\n            class_detections = {i: [] for i in range(13)}\n            \n            for det in detections:\n                class_id = det['class_id']\n                confidence = det['confidence']\n                if 0 <= class_id < 13:\n                    class_detections[class_id].append(confidence)\n            \n            for class_id in range(13):\n                confidences = class_detections[class_id]\n                \n                if len(confidences) > 0:\n                    confidences = np.array(confidences)\n                    \n                    if self.method == 'noisy_or':\n                        class_probs[class_id] = self._noisy_or(confidences)\n                    elif self.method == 'weighted_max':\n                        class_probs[class_id] = self._weighted_max(confidences)\n                    elif self.method == 'top_k_avg':\n                        class_probs[class_id] = self._top_k_average(confidences, self.top_k)\n                    elif self.method == 'max':\n                        class_probs[class_id] = confidences.max()\n        \n        class_probs = np.clip(class_probs, 0.0, 1.0)\n        aneurysm_prob = class_probs.max()\n        \n        result = np.zeros(14, dtype=float)\n        result[:13] = class_probs\n        result[13] = aneurysm_prob\n        \n        return result\n    \n    def _noisy_or(self, confidences: np.ndarray) -> float:\n        if len(confidences) == 0:\n            return self.background_prior\n        p_all_wrong = np.prod(1 - confidences)\n        return float(1 - p_all_wrong)\n    \n    def _weighted_max(self, confidences: np.ndarray) -> float:\n        if len(confidences) == 0:\n            return self.background_prior\n        sorted_confs = np.sort(confidences)[::-1]\n        weights = np.exp(-np.arange(len(sorted_confs)) * 0.5)\n        weights = weights / weights.sum()\n        return float(np.sum(sorted_confs * weights))\n    \n    def _top_k_average(self, confidences: np.ndarray, k: int = 3) -> float:\n        if len(confidences) == 0:\n            return self.background_prior\n        sorted_confs = np.sort(confidences)[::-1]\n        top_k = sorted_confs[:min(k, len(sorted_confs))]\n        return float(top_k.mean())\n\n# ====================================================\n# EfficientNet Model\n# ====================================================\nclass EfficientNetV2MultiLabel(nn.Module):\n    def __init__(self, num_classes=14):\n        super().__init__()\n        self.backbone = timm.create_model(\"tf_efficientnetv2_s_in21k\", pretrained=False, in_chans=32)\n        in_features = self.backbone.classifier.in_features\n        self.backbone.classifier = nn.Linear(in_features, num_classes)\n\n    def forward(self, x):\n        return self.backbone(x)\n\n# ====================================================\n# Global Model Variables\n# ====================================================\nYOLO_MODELS = []  # List of YOLO models\nEFFNET_MODELS = {}\nYOLO_CONVERTER = None\nEFFNET_TRANSFORM = None\nUNIFIED_PREPROCESSOR = None\n\n# ====================================================\n# Model Loading\n# ====================================================\ndef load_yolo_models():\n    global YOLO_MODELS, YOLO_CONVERTER\n    \n    # print(\"Loading YOLO models...\")\n    \n    # Normalize weights\n    weights = np.array(CFG.yolo_weights)\n    if len(weights) != len(CFG.yolo_model_paths):\n        weights = np.ones(len(CFG.yolo_model_paths))\n    weights = weights / weights.sum()\n    CFG.yolo_weights = weights.tolist()\n    \n    for i, model_path in enumerate(CFG.yolo_model_paths):\n        if not Path(model_path).exists():\n            # print(f\"Warning: YOLO model not found: {model_path}, skipping...\")\n            continue\n        \n        model = YOLO(model_path)\n        model.to(device)\n        YOLO_MODELS.append({'model': model, 'weight': CFG.yolo_weights[i]})\n        # print(f\"  Loaded YOLO model {i+1}/{len(CFG.yolo_model_paths)} (weight: {CFG.yolo_weights[i]:.3f})\")\n    \n    if not YOLO_MODELS:\n        raise ValueError(\"No YOLO models loaded successfully\")\n    \n    YOLO_CONVERTER = DetectionToProbabilityConverter(\n        method=CFG.yolo_aggregation_method,\n        background_prior=CFG.yolo_background_prior\n    )\n    \n    # Warm up\n    dummy_image = np.random.randint(0, 255, (CFG.yolo_img_size, CFG.yolo_img_size, 3), dtype=np.uint8)\n    for yolo_dict in YOLO_MODELS:\n        _ = yolo_dict['model'].predict(dummy_image, conf=CFG.yolo_conf_threshold, imgsz=CFG.yolo_img_size, verbose=False)\n    \n    # print(f\"YOLO models loaded successfully ({len(YOLO_MODELS)} models)\")\n\ndef load_effnet_models():\n    global EFFNET_MODELS, EFFNET_TRANSFORM\n    \n    # print(\"Loading EfficientNet models...\")\n    \n    for fold in CFG.effnet_trn_folds:\n        model_path = Path(CFG.effnet_fold_paths[fold])\n        \n        if not model_path.exists():\n            # print(f\"Warning: EfficientNet fold {fold} not found, skipping...\")\n            continue\n        \n        state_dict = torch.load(model_path, map_location=device)\n        model = EfficientNetV2MultiLabel(num_classes=CFG.num_classes).to(device)\n        model.load_state_dict(state_dict)\n        model.eval()\n        \n        EFFNET_MODELS[fold] = model\n    \n    if not EFFNET_MODELS:\n        raise ValueError(\"No EfficientNet models loaded successfully\")\n    \n    # Initialize transform\n    EFFNET_TRANSFORM = A.Compose([\n        A.Resize(CFG.effnet_size, CFG.effnet_size),\n        A.Normalize(mean=0.5, std=0.5),\n        ToTensorV2(),\n    ])\n    \n    # Warm up\n    dummy_image = torch.randn(1, CFG.effnet_in_chans, CFG.effnet_size, CFG.effnet_size).to(device)\n    with torch.no_grad():\n        for model in EFFNET_MODELS.values():\n            _ = model(dummy_image)\n    \n    # print(f\"EfficientNet models loaded successfully ({len(EFFNET_MODELS)} folds)\")\n\ndef load_all_models():\n    global UNIFIED_PREPROCESSOR\n    \n    load_yolo_models()\n    load_effnet_models()\n    \n    # Initialize unified preprocessor\n    UNIFIED_PREPROCESSOR = UnifiedDICOMPreprocessor(target_shape=CFG.effnet_target_shape)\n    \n    # print(\"All models ready for inference!\")\n\n# ====================================================\n# YOLO Inference Functions\n# ====================================================\ndef run_yolo_inference_batch(images: List[np.ndarray], model_dict: Dict) -> List[Dict]:\n    \"\"\"Run YOLO inference on a batch of images\"\"\"\n    all_detections = []\n    model = model_dict['model']\n    \n    for i in range(0, len(images), CFG.yolo_batch_size):\n        batch_images = images[i:i+CFG.yolo_batch_size]\n        \n        try:\n            results = model.predict(\n                batch_images,\n                conf=CFG.yolo_conf_threshold,\n                imgsz=CFG.yolo_img_size,\n                device=device,\n                verbose=False\n            )\n            \n            for result in results:\n                if result.boxes is not None:\n                    boxes = result.boxes\n                    \n                    for j in range(len(boxes)):\n                        detection = {\n                            'class_id': int(boxes.cls[j].item()),\n                            'confidence': float(boxes.conf[j].item()),\n                            'bbox': boxes.xyxy[j].cpu().numpy().tolist()\n                        }\n                        all_detections.append(detection)\n        \n        except Exception as e:\n            # print(f\"YOLO batch inference failed: {e}\")\n            continue\n    \n    return all_detections\n\ndef get_yolo_ensemble_predictions(yolo_images: List[np.ndarray]) -> np.ndarray:\n    \"\"\"Get ensemble predictions from multiple YOLO models\"\"\"\n    \n    if len(yolo_images) == 0:\n        return np.full(14, CFG.yolo_background_prior, dtype=float)\n    \n    # Get predictions from each YOLO model\n    all_model_probs = []\n    \n    for model_dict in YOLO_MODELS:\n        detections = run_yolo_inference_batch(yolo_images, model_dict)\n        probabilities = YOLO_CONVERTER.aggregate_detections(detections)\n        all_model_probs.append(probabilities * model_dict['weight'])\n    \n    # Weighted sum of all YOLO models\n    ensemble_probs = np.sum(all_model_probs, axis=0)\n    \n    return ensemble_probs\n\n# ====================================================\n# EfficientNet Inference Functions\n# ====================================================\ndef get_effnet_predictions(effnet_volume: np.ndarray) -> np.ndarray:\n    \"\"\"Get EfficientNet ensemble predictions from preprocessed volume\"\"\"\n    try:\n        # Transform volume (already in correct shape from unified preprocessor)\n        volume = effnet_volume.transpose(1, 2, 0)  # (D,H,W) -> (H,W,D)\n        transformed = EFFNET_TRANSFORM(image=volume)\n        image_tensor = transformed['image'].unsqueeze(0).to(device)\n        \n        # Ensemble predictions\n        all_preds = []\n        with torch.no_grad():\n            with autocast(enabled=True):\n                for model in EFFNET_MODELS.values():\n                    output = model(image_tensor)\n                    pred = torch.sigmoid(output).cpu().numpy().squeeze()\n                    all_preds.append(pred)\n        \n        # Average predictions\n        return np.mean(all_preds, axis=0)\n        \n    except Exception as e:\n        # print(f\"EfficientNet prediction error: {e}\")\n        return np.full(14, 0.1, dtype=float)\n\n# ====================================================\n# Ensemble Functions\n# ====================================================\ndef ensemble_predictions(yolo_preds: np.ndarray, effnet_preds: np.ndarray) -> np.ndarray:\n    \"\"\"Ensemble YOLO and EfficientNet predictions\"\"\"\n    \n    if CFG.ensemble_method == 'weighted_average':\n        ensemble = CFG.yolo_weight * yolo_preds + CFG.effnet_weight * effnet_preds\n        \n    elif CFG.ensemble_method == 'geometric_mean':\n        ensemble = np.sqrt(yolo_preds * effnet_preds)\n        \n    elif CFG.ensemble_method == 'max':\n        ensemble = np.maximum(yolo_preds, effnet_preds)\n    \n    else:\n        raise ValueError(f\"Unknown ensemble method: {CFG.ensemble_method}\")\n    \n    return np.clip(ensemble, 0.0, 1.0)\n\n# ====================================================\n# Main Prediction Function\n# ====================================================\ndef _predict_inner(series_path: str) -> pl.DataFrame:\n    global YOLO_MODELS, EFFNET_MODELS, UNIFIED_PREPROCESSOR\n    \n    if not YOLO_MODELS or not EFFNET_MODELS or UNIFIED_PREPROCESSOR is None:\n        load_all_models()\n    \n    series_id = os.path.basename(series_path)\n    \n    try:\n        # print(f\"Processing {series_id}...\")\n        \n        # UNIFIED PREPROCESSING - Process DICOM once for both models\n        yolo_images, effnet_volume, z_positions = UNIFIED_PREPROCESSOR.load_and_process_series(series_path)\n        # print(f\"  Loaded {len(yolo_images)} slices\")\n        \n        # YOLO ensemble predictions (using preprocessed images)\n        yolo_preds = get_yolo_ensemble_predictions(yolo_images)\n        # print(f\"  YOLO Ensemble: {yolo_preds[13]:.3f} (aneurysm)\")\n        \n        # EfficientNet predictions (using preprocessed volume)\n        effnet_preds = get_effnet_predictions(effnet_volume)\n        # print(f\"  EfficientNet: {effnet_preds[13]:.3f} (aneurysm)\")\n        \n        # Final ensemble\n        final_preds = ensemble_predictions(yolo_preds, effnet_preds)\n        # print(f\"  Final Ensemble: {final_preds[13]:.3f} (aneurysm)\")\n        \n        # Create output dataframe\n        predictions_df = pl.DataFrame(\n            data=[final_preds.tolist()],\n            schema=LABEL_COLS,\n            orient='row'\n        )\n        \n        return predictions_df\n        \n    except Exception as e:\n        # print(f\"Error processing {series_id}: {e}\")\n        conservative_preds = [0.1] * 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\ndef predict(series_path: str) -> pl.DataFrame:\n    try:\n        return _predict_inner(series_path)\n    except Exception as e:\n        # print(f\"Error during prediction for {os.path.basename(series_path)}: {e}\")\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    finally:\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\n# ====================================================\n# Main Execution\n# ====================================================\n\n# Load all models at startup\nload_all_models()\n\n# Initialize the inference server\ninference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n\n# Run the server\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway()\n    \n    submission_df = pl.read_parquet('/kaggle/working/submission.parquet')\n    display(submission_df)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-14T23:42:03.116034Z","iopub.execute_input":"2025-10-14T23:42:03.116366Z","iopub.status.idle":"2025-10-14T23:44:26.636686Z","shell.execute_reply.started":"2025-10-14T23:42:03.116338Z","shell.execute_reply":"2025-10-14T23:44:26.635848Z"}},"outputs":[],"execution_count":null}]}