{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"},{"sourceId":13366483,"sourceType":"datasetVersion","datasetId":8479133}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"pip install /kaggle/input/rsna-2025-weights-4xbase/packages/dicomsdl-0.109.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:33:59.837612Z","iopub.execute_input":"2025-10-14T06:33:59.837888Z","iopub.status.idle":"2025-10-14T06:34:03.965727Z","shell.execute_reply.started":"2025-10-14T06:33:59.837839Z","shell.execute_reply":"2025-10-14T06:34:03.965041Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install /kaggle/input/rsna-2025-weights-4xbase/packages/timm-1.0.20-py3-none-any.whl --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:03.967239Z","iopub.execute_input":"2025-10-14T06:34:03.967476Z","iopub.status.idle":"2025-10-14T06:34:06.747346Z","shell.execute_reply.started":"2025-10-14T06:34:03.967446Z","shell.execute_reply":"2025-10-14T06:34:06.746668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import psutil\nworkers = psutil.cpu_count(logical=True)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:06.748387Z","iopub.execute_input":"2025-10-14T06:34:06.748656Z","iopub.status.idle":"2025-10-14T06:34:06.752671Z","shell.execute_reply.started":"2025-10-14T06:34:06.748619Z","shell.execute_reply":"2025-10-14T06:34:06.752039Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import sys\nimport kaggle_evaluation.rsna_inference_server\n\nimport os\nimport shutil\nfrom collections import defaultdict\n\nimport pandas as pd\nimport polars as pl\n\nimport dicomsdl as dicom\nimport cv2\nimport numpy as np\nimport torch\nimport torch.nn as nn\n\nimport timm\nimport torch.nn as nn\nfrom transformers.models.deberta_v2 import DebertaV2Model\n\nimport math\nimport pickle\n\nfrom torch.amp import autocast\n\nimport threading\nimport gc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:06.753545Z","iopub.execute_input":"2025-10-14T06:34:06.753943Z","iopub.status.idle":"2025-10-14T06:34:35.632937Z","shell.execute_reply.started":"2025-10-14T06:34:06.753915Z","shell.execute_reply":"2025-10-14T06:34:35.632106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ID_COL = 'SeriesInstanceUID'\n\nLABEL_COLS = [\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    'Aneurysm Present',                            # 13\n]\n\n# All tags (other than PixelData and SeriesInstanceUID) that may be in a test set dcm file\nDICOM_TAG_ALLOWLIST = [\n    'BitsAllocated',\n    'BitsStored',\n    'Columns',\n    'FrameOfReferenceUID',\n    'HighBit',\n    'ImageOrientationPatient',\n    'ImagePositionPatient',\n    'InstanceNumber',\n    'Modality',\n    'PatientID',\n    'PhotometricInterpretation',\n    'PixelRepresentation',\n    'PixelSpacing',\n    'PlanarConfiguration',\n    'RescaleIntercept',\n    'RescaleSlope',\n    'RescaleType',\n    'Rows',\n    'SOPClassUID',\n    'SOPInstanceUID',\n    'SamplesPerPixel',\n    'SliceThickness',\n    'SpacingBetweenSlices',\n    'StudyInstanceUID',\n    'TransferSyntaxUID',\n]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:35.634642Z","iopub.execute_input":"2025-10-14T06:34:35.635146Z","iopub.status.idle":"2025-10-14T06:34:35.640013Z","shell.execute_reply.started":"2025-10-14T06:34:35.635125Z","shell.execute_reply":"2025-10-14T06:34:35.639312Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def normalize(img, modality = None):\n    \"\"\"\n    Apply statistical normalization\n    \"\"\"\n    \n    img = img.astype(np.float32)\n    \n    if modality == 'CT':\n        \n        # Statistical normalization (for CT as well)\n        # Normalize using 1-99 percentiles\n        p1, p99 = 0, 600\n        \n        img = np.clip(img, p1, p99)\n        \n        normalized = (img - p1) / ((p99 - p1) * 0.2)\n\n    else:\n        p1, p99 = -1200, 4000\n        \n        img = np.clip(img, p1, p99)\n        \n        normalized = (img - p1) / ((p99 - p1) * 0.2)\n        \n    return normalized","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:35.640767Z","iopub.execute_input":"2025-10-14T06:34:35.641152Z","iopub.status.idle":"2025-10-14T06:34:35.701258Z","shell.execute_reply.started":"2025-10-14T06:34:35.641128Z","shell.execute_reply":"2025-10-14T06:34:35.700531Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract(series_path):  \n        \n   \n    series_name = os.path.basename(series_path)\n    \n    # Search for 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    # Load DICOM datasets\n    if len(dicom_files) == 1:\n        \n        ds = dicom.open(dicom_files[0])\n        \n        info = ds.getPixelDataInfo()\n        num_frames = info['NumberOfFrames']\n        \n        modality = getattr(ds, 'Modality', None)\n        \n        slice_uids_sort = [getattr(ds, 'SOPInstanceUID', '')]\n        \n        slope = getattr(ds, 'RescaleSlope', None)\n        intercept = getattr(ds, 'RescaleIntercept', None)\n                \n\n        slices = []    \n        for i in range(num_frames):\n            \n            img = ds.pixelData(i)\n            \n            shape = img.shape\n            \n            if slope and intercept:\n                img = img * slope - intercept\n            \n            img = normalize(img, modality = modality)\n        \n            img = cv2.resize(img, dsize=(512, 512), interpolation=cv2.INTER_LINEAR_EXACT)\n            \n            # Center Crop to 448x448\n            img = img[32:-32, 32:-32]\n\n            slices.append(img)\n            \n        images = np.stack(slices, axis=0)\n        \n        ds.close()\n        \n        \n          \n    else:\n        \n        slices = []\n    \n        slice_uid = []\n        slice_orientation = []\n        slice_position = []\n        \n        \n        for i, file in enumerate(dicom_files):\n            \n            ds = dicom.open(file)\n            \n            info = ds.getPixelDataInfo()\n            num_frames = info['NumberOfFrames']\n            \n            modality = getattr(ds, 'Modality', None)\n            \n            \n            slice_uid.append(getattr(ds, 'SOPInstanceUID', ''))\n            slice_orientation.append(getattr(ds, 'ImageOrientationPatient', None))\n            \n            \n            position = getattr(ds, 'ImagePositionPatient', None)\n            \n            \n            if position is None:\n                position = float(i)\n            else:\n                if len(position) >= 3:\n                    position = float(position[2])\n                else:\n                    position = float(getattr(ds, 'InstanceNumber', i))\n            \n    \n            slice_position.append(position)\n            \n\n            if num_frames > 1:\n                j = num_frames // 2\n            else:\n                j = 0\n                    \n            img = ds.pixelData(j)\n            \n            shape = img.shape\n            \n            slope = getattr(ds, 'RescaleSlope', None)\n            intercept = getattr(ds, 'RescaleIntercept', None)\n\n                \n            if slope and intercept:\n                img = img * slope - intercept\n                \n\n            img = normalize(img, modality = modality)\n            \n            img = cv2.resize(img, dsize=(512, 512), interpolation=cv2.INTER_LINEAR_EXACT)\n\n            # Center Crop to 448x448\n            img = img[32:-32, 32:-32]\n            \n            slices.append(img)\n            \n            ds.close()\n                \n        images = np.stack(slices, axis=0)\n        \n\n        idx = zip(list(range(len(slice_position))), slice_position)\n        \n        \n        sorted_slices = sorted(idx, key=lambda x: x[1])\n        \n\n        idx_sort = np.array([x[0] for x in sorted_slices])\n        \n\n        images = images[idx_sort]\n\n\n    images = images - 2.5\n\n    images = torch.from_numpy(images)\n\n\n    left2 = torch.roll(images, 2, 0)\n    left1 = torch.roll(images, 1, 0)\n    \n    right1 = torch.roll(images, -1, 0)\n    right2 = torch.roll(images, -2, 0)    \n    \n    images1 = torch.stack([left1, images, right1], dim=1)\n    images2 = torch.stack([left2, images, right2], dim=1)\n\n\n    images = [images1, images2]\n    \n    return images","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:35.702159Z","iopub.execute_input":"2025-10-14T06:34:35.702422Z","iopub.status.idle":"2025-10-14T06:34:35.71567Z","shell.execute_reply.started":"2025-10-14T06:34:35.702397Z","shell.execute_reply":"2025-10-14T06:34:35.714904Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AttentionPooling(nn.Module):\n    def __init__(self):\n        super(AttentionPooling, self).__init__()\n        \n    def forward(self, last_hidden_state, attention_mask):\n        return last_hidden_state[:, 0, :] \n\nclass GemPooling(nn.Module):\n    def __init__(self, p=3, eps=1e-6):\n        super(GemPooling, self).__init__()\n        \n        self.p = nn.Parameter(torch.ones(1)*p)\n        self.eps = eps\n        \n    def forward(self, last_hidden_state, attention_mask):\n        \n        last_hidden_state = last_hidden_state.clamp(min=self.eps).pow(self.p)\n        \n        attention_mask = attention_mask.unsqueeze(-1).float()\n        sum_embeddings = torch.sum(last_hidden_state * attention_mask, 1)\n        sum_mask = attention_mask.sum(1)\n        sum_mask = torch.clamp(sum_mask, min=1e-9)\n        mean_embeddings = sum_embeddings / sum_mask\n        \n        mean_embeddings = mean_embeddings.pow(1./self.p)\n        \n        return mean_embeddings\n\nclass Model(torch.nn.Module):\n\n    def __init__(self,\n                 encoder_name,\n                 feature_size,\n                 transformer_config,\n                 ):\n        \n        super().__init__()\n        \n        self.image_encoder = timm.create_model(encoder_name,\n                                               pretrained=False,\n                                               num_classes=0) \n\n        self.transformer = DebertaV2Model(transformer_config) \n\n        hidden_size = transformer_config.hidden_size\n        \n        scale = hidden_size ** -0.5\n        self.cls_embedding = nn.Parameter(scale * torch.randn(1, 1, hidden_size))\n        \n        n = hidden_size  \n        self.fc = torch.nn.Sequential(\n                                  torch.nn.LayerNorm(n*2),\n                                  torch.nn.Linear(hidden_size*2, n),\n                                  torch.nn.LayerNorm(n),\n                                  torch.nn.GELU(),\n                                  torch.nn.Dropout(0.0),\n                                  torch.nn.Linear(n, 14)\n                                  )  \n                                        \n\n        self.pool = GemPooling()\n\n      \n    def forward_encoder(self, x):\n        \n        f = self.image_encoder(x)\n        \n        return f             \n      \n        \n    def forward_transformer(self, x, a):\n        \n        b, t, c = x.shape\n\n        cls_emb = self.cls_embedding.repeat(b, 1, 1)\n               \n        a_add = torch.ones((b, 1), dtype=a.dtype, device=a.device)\n        \n        \n        x = torch.cat([cls_emb, x], dim=1)\n        a = torch.cat([a_add, a], dim=1)\n        \n        \n        output = self.transformer(inputs_embeds=x, attention_mask=a)\n        \n        x = output.last_hidden_state\n        \n\n        x = torch.cat([self.pool(x, a), x[:, 0, :]], dim=-1)\n        \n        \n        x = self.fc(x)\n\n        return x    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:35.716526Z","iopub.execute_input":"2025-10-14T06:34:35.717042Z","iopub.status.idle":"2025-10-14T06:34:35.731621Z","shell.execute_reply.started":"2025-10-14T06:34:35.717022Z","shell.execute_reply":"2025-10-14T06:34:35.731096Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_model(models,\n                  images,\n                  stream0,\n                  stream1,\n                  batch_size):\n\n\n    length = len(images[0])\n    \n    step = int(length / 192)\n                    \n    if step > 1:\n        \n        images0_0 = images[0][0::step]\n        images0_1 = images[0][1::step]\n        \n        images1_0 = images[1][0::step]\n        images1_1 = images[1][1::step]\n    \n    else:\n        images0_0 = images[0]\n        images0_1 = images[0]\n        \n        images1_0 = images[1]\n        images1_1 = images[1]\n\n\n    \n    batches0 = math.ceil(len(images0_0) / batch_size)\n    batches1 = math.ceil(len(images1_0) / batch_size)\n\n\n    probs = [None, None, None, None]\n\n    def run_on_stream0():\n        with torch.cuda.stream(stream0):\n            \n            features_list0_0 = [] \n            features_list0_1 = [] \n            \n            for b1 in range(batches0):\n                \n                start1 = b1 * batch_size\n                end1 = start1 + batch_size\n            \n                with autocast(device_type=\"cuda\"), torch.no_grad():\n                    \n                    batch0_0 = images0_0[start1:end1].clone().to(torch.device(\"cuda:0\"), non_blocking=True)\n                    batch0_1 = images0_1[start1:end1].clone().to(torch.device(\"cuda:0\"), non_blocking=True)\n\n                    # Forward pass\n                    features0_0 = models[0].forward_encoder(batch0_0)\n                    features0_1 = models[1].forward_encoder(batch0_1)\n\n \n                    features_list0_0.append(features0_0)\n                    features_list0_1.append(features0_1)\n          \n            if batches1 == 1:\n                features0_0 = features_list0_0[0] \n                features0_1 = features_list0_1[0] \n            else:\n                features0_0 = torch.cat(features_list0_0)\n                features0_1 = torch.cat(features_list0_1)\n        \n            #print(\"Features:\", features1.shape)\n            \n            # Batch Size 1\n            features0_0 = features0_0.unsqueeze(0)\n            mask0_0 = torch.ones(1, features0_0.shape[1], dtype=features0_0.dtype, device=features0_0.device)\n\n            features0_1 = features0_1.unsqueeze(0)\n            mask0_1 = torch.ones(1, features0_1.shape[1], dtype=features0_1.dtype, device=features0_1.device)\n        \n            with autocast(device_type=\"cuda\"), torch.no_grad():    \n                logits0_0 = models[0].forward_transformer(features0_0, mask0_0)\n                probs[0] = logits0_0[0].sigmoid().cpu()\n\n                logits0_1 = models[1].forward_transformer(features0_1, mask0_1)\n                probs[1] = logits0_1[0].sigmoid().cpu()\n\n\n\n    def run_on_stream1():\n        with torch.cuda.stream(stream1):\n            \n            features_list1_0 = [] \n            features_list1_1 = [] \n            \n            for b1 in range(batches1):\n                \n                start1 = b1 * batch_size\n                end1 = start1 + batch_size\n            \n                with autocast(device_type=\"cuda\"), torch.no_grad():\n                    \n                    batch1_0 = images1_0[start1:end1].clone().to(torch.device(\"cuda:1\"), non_blocking=True)\n                    batch1_1 = images1_1[start1:end1].clone().to(torch.device(\"cuda:1\"), non_blocking=True)\n\n                    # Forward pass\n                    features1_0 = models[2].forward_encoder(batch1_0)\n                    features1_1 = models[3].forward_encoder(batch1_1)\n\n \n                    features_list1_0.append(features1_0)\n                    features_list1_1.append(features1_1)\n          \n            if batches1 == 1:\n                features1_0 = features_list1_0[0] \n                features1_1 = features_list1_1[0] \n            else:\n                features1_0 = torch.cat(features_list1_0)\n                features1_1 = torch.cat(features_list1_1)\n        \n            #print(\"Features:\", features1.shape)\n            \n            # Batch Size 1\n            features1_0 = features1_0.unsqueeze(0)\n            mask1_0 = torch.ones(1, features1_0.shape[1], dtype=features1_0.dtype, device=features1_0.device)\n\n            features1_1 = features1_1.unsqueeze(0)\n            mask1_1 = torch.ones(1, features1_1.shape[1], dtype=features1_1.dtype, device=features1_1.device)\n        \n            with autocast(device_type=\"cuda\"), torch.no_grad():    \n                logits1_0 = models[2].forward_transformer(features1_0, mask1_0)\n                probs[2] = logits1_0[0].sigmoid().cpu()\n\n                logits1_1 = models[3].forward_transformer(features1_1, mask1_1)\n                probs[3] = logits1_1[0].sigmoid().cpu()\n\n    # Start both in parallel\n    t1 = threading.Thread(target=run_on_stream0)\n    t2 = threading.Thread(target=run_on_stream1)\n    \n    t1.start()\n    t2.start()\n    t1.join()\n    t2.join()\n    \n    # Synchronize streams to ensure completion\n    stream0.synchronize()\n    stream1.synchronize()\n\n    return probs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:35.732316Z","iopub.execute_input":"2025-10-14T06:34:35.732559Z","iopub.status.idle":"2025-10-14T06:34:35.749987Z","shell.execute_reply.started":"2025-10-14T06:34:35.732519Z","shell.execute_reply":"2025-10-14T06:34:35.749387Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Replace this function with your inference code.\n# You can return either a Pandas or Polars dataframe, though Polars is recommended.\n# Each prediction (except the very first) must be returned within 30 minutes of the series being provided.\n\ndef predict_inner(series_path: str) -> pl.DataFrame | pd.DataFrame:\n    \"\"\"Make a prediction.\"\"\"\n\n    series_id = os.path.basename(series_path)\n    \n    try:\n        images = extract(series_path)\n\n \n        #print(images.shape, images1.shape, images2.shape)\n        \n        probs = predict_model(models,\n                              images,\n                              stream_0,\n                              stream_1,\n                              batch_size=64)\n    \n        probs_ensemble = 0.25 * probs[0] + 0.25 * probs[1] + 0.25 * probs[2] + 0.25 * probs[3] \n    \n        #print(probs_ensemble.shape, probs_ensemble)\n    \n        predictions = pl.DataFrame(\n            data=[[series_id] + probs_ensemble.tolist()],\n            schema=[ID_COL, *LABEL_COLS],\n            orient='row',\n        )\n\n        return predictions.drop(ID_COL)\n\n    except Exception as e:\n        #print(f\"Error processing {series_id}: {e}\")\n        # Return conservative predictions\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:35.750614Z","iopub.execute_input":"2025-10-14T06:34:35.750832Z","iopub.status.idle":"2025-10-14T06:34:35.761449Z","shell.execute_reply.started":"2025-10-14T06:34:35.750817Z","shell.execute_reply":"2025-10-14T06:34:35.76071Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(series_path: str) -> pl.DataFrame:\n    \"\"\"\n    Top-level prediction function passed to the server.\n    It calls the core logic and guarantees cleanup in a `finally` block.\n    \"\"\"\n    \n    try:\n        # Call the 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        # Return a fallback dataframe with the correct schema\n        conservative_preds = [0.1] * len(LABEL_COLS)\n        predictions = pl.DataFrame(\n            data=[conservative_preds],\n            schema=LABEL_COLS,\n            orient='row'\n        )\n        return predictions\n    \n    finally:\n        # This code is required to prevent \"out of disk space\" and \"directory not empty\" errors.\n        # It deletes the shared folder and then immediately recreates it, ensuring it's\n        # empty and ready for the next prediction.\n        shared_dir = '/kaggle/shared'\n        shutil.rmtree(shared_dir, ignore_errors=True)\n        os.makedirs(shared_dir, exist_ok=True)\n        \n        # Also perform memory cleanup here\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n        gc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:35.762156Z","iopub.execute_input":"2025-10-14T06:34:35.762373Z","iopub.status.idle":"2025-10-14T06:34:35.77643Z","shell.execute_reply.started":"2025-10-14T06:34:35.76236Z","shell.execute_reply":"2025-10-14T06:34:35.775707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"######################################################################\nmodel_0_path = \"/kaggle/input/rsna-2025-weights-4xbase/weights/fold-5\"\n\nwith open('{}/config_stage2.pkl'.format(model_0_path), \"rb\") as f:\n    transformer_config0 = pickle.load(f)\n\ntransformer_config0.output_attentions = False\n\nmodel_0 = Model(encoder_name='convnext_base.dinov3_lvd1689m',\n                feature_size=1024,\n                transformer_config=transformer_config0)\n\n# Load Weights\nmodel_0_checkpoint = '{}/weights_inference.pth'.format(model_0_path)\nmodel_0_state_dict = torch.load(model_0_checkpoint)\nmodel_0.load_state_dict(model_0_state_dict, strict=True)\n\n# To GPU\nmodel_0.to(torch.device(\"cuda:0\"))\nmodel_0.eval()\n\n######################################################################\n\nmodel_1_path = \"/kaggle/input/rsna-2025-weights-4xbase/weights/fold-2\"\n\nwith open('{}/config_stage2.pkl'.format(model_1_path), \"rb\") as f:\n    transformer_config1 = pickle.load(f)\n\ntransformer_config1.output_attentions = False\n\nmodel_1 = Model(encoder_name='convnext_base.dinov3_lvd1689m',\n                feature_size=1024,\n                transformer_config=transformer_config1)\n\n# Load Weights\nmodel_1_checkpoint = '{}/weights_inference.pth'.format(model_1_path)\nmodel_1_state_dict = torch.load(model_1_checkpoint)\nmodel_1.load_state_dict(model_1_state_dict, strict=True)\n\n# To GPU\nmodel_1.to(torch.device(\"cuda:0\"))\nmodel_1.eval()\n\n######################################################################\n\nmodel_2_path = \"/kaggle/input/rsna-2025-weights-4xbase/weights/fold-1\"\n\nwith open('{}/config_stage2.pkl'.format(model_2_path), \"rb\") as f:\n    transformer_config2 = pickle.load(f)\n\ntransformer_config2.output_attentions = False\n\nmodel_2 = Model(encoder_name='convnext_base.dinov3_lvd1689m',\n                feature_size=1024,\n                transformer_config=transformer_config2)\n\n# Load Weights\nmodel_2_checkpoint = '{}/weights_inference.pth'.format(model_2_path)\nmodel_2_state_dict = torch.load(model_2_checkpoint)\nmodel_2.load_state_dict(model_2_state_dict, strict=True)\n\n# To GPU\nmodel_2.to(torch.device(\"cuda:1\"))\nmodel_2.eval()\n\n######################################################################\n\nmodel_3_path = \"/kaggle/input/rsna-2025-weights-4xbase/weights/fold-0\"\n\nwith open('{}/config_stage2.pkl'.format(model_3_path), \"rb\") as f:\n    transformer_config3 = pickle.load(f)\n\ntransformer_config3.output_attentions = False\n\nmodel_3 = Model(encoder_name='convnext_base.dinov3_lvd1689m',\n                feature_size=1024,\n                transformer_config=transformer_config3)\n\n# Load Weights\nmodel_3_checkpoint = '{}/weights_inference.pth'.format(model_3_path)\nmodel_3_state_dict = torch.load(model_3_checkpoint)\nmodel_3.load_state_dict(model_3_state_dict, strict=True)\n\n# To GPU\nmodel_3.to(torch.device(\"cuda:1\"))\nmodel_3.eval()\n\n######################################################################\n\nmodels = [model_0, model_1, model_2, model_3]\n\nstream_0 = torch.cuda.Stream(device=\"cuda:0\")\nstream_1 = torch.cuda.Stream(device=\"cuda:1\")\n\nshutil.rmtree('/kaggle/shared', ignore_errors=True)\n\n######################################################################\n\ninference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway()\n    display(pl.read_parquet('/kaggle/working/submission.parquet'))\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T06:34:35.777148Z","iopub.execute_input":"2025-10-14T06:34:35.777341Z","iopub.status.idle":"2025-10-14T06:35:36.229133Z","shell.execute_reply.started":"2025-10-14T06:34:35.777325Z","shell.execute_reply":"2025-10-14T06:35:36.228346Z"}},"outputs":[],"execution_count":null}]}