{"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":"none","dataSources":[{"sourceType":"competition","sourceId":99552,"databundleVersionId":13851420},{"sourceType":"modelInstanceVersion","sourceId":3732,"databundleVersionId":5092437,"modelInstanceId":2659},{"sourceType":"modelInstanceVersion","sourceId":519793,"databundleVersionId":13374819,"modelInstanceId":409304},{"sourceType":"modelInstanceVersion","sourceId":595797,"databundleVersionId":13934181,"modelInstanceId":446086}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"\"\"\"# submission_kaggle_safe.py\nimport os\nimport shutil\nimport gc\nfrom pathlib import Path\n\nimport pydicom\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport scipy.special\nimport kaggle_evaluation.rsna_inference_server\n\n# ===================== CONFIG =====================\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\nLOCATION_MAP = {\n    1: \"Other Posterior Circulation\",\n    2: \"Basilar Tip\",\n    3: \"Right Posterior Communicating Artery\",\n    4: \"Left Posterior Communicating Artery\",\n    5: \"Right Infraclinoid Internal Carotid Artery\",\n    6: \"Left Infraclinoid Internal Carotid Artery\",\n    7: \"Right Supraclinoid Internal Carotid Artery\",\n    8: \"Left Supraclinoid Internal Carotid Artery\",\n    9: \"Right Middle Cerebral Artery\",\n    10: \"Left Middle Cerebral Artery\",\n    11: \"Right Anterior Cerebral Artery\",\n    12: \"Left Anterior Cerebral Artery\",\n    13: \"Anterior Communicating Artery\",\n}\nID_TO_LABEL = {i - 1: v for i, v in LOCATION_MAP.items()}\n\n# ===================== HELPERS =====================\ndef adaptive_windowing(image):\n    img_flat = image.flatten()\n    img_flat = img_flat[img_flat > 0]\n    if len(img_flat) == 0:\n        return np.zeros_like(image, dtype=np.uint8)\n    low_val, high_val = np.percentile(img_flat, [5, 95])\n    img_windowed = np.clip(image, low_val, high_val)\n    img_norm = ((img_windowed - low_val) / (high_val - low_val + 1e-8) * 255).astype(np.uint8)\n    return img_norm\n\ndef process_dicom_series(series_path, image_size=512, num_slices=16):\n    dcm_files = sorted([os.path.join(root, f)\n                        for root, _, files in os.walk(series_path)\n                        for f in files if f.endswith(\".dcm\")])\n    if len(dcm_files) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n\n    dicom_data = []\n    for f in dcm_files:\n        try:\n            ds = pydicom.dcmread(f, force=True)\n            img = ds.pixel_array.astype(np.float32)\n            if img.ndim == 3 and img.shape[-1] == 3:\n                img = cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_BGR2GRAY).astype(np.float32)\n            img_resized = cv2.resize(img, (image_size, image_size))\n            dicom_data.append(img_resized)\n        except Exception as e:\n            print(f\"Skipping file {f} due to error: {e}\")\n\n    if len(dicom_data) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n\n    volume = np.stack(dicom_data, axis=0)\n    volume = adaptive_windowing(volume)\n\n    if volume.shape[0] > num_slices:\n        idx = np.linspace(0, volume.shape[0] - 1, num_slices).astype(int)\n        volume = volume[idx]\n    elif volume.shape[0] < num_slices:\n        pad = num_slices - volume.shape[0]\n        volume = np.pad(volume, ((0, pad), (0, 0), (0, 0)), mode=\"edge\")\n\n    return volume\n\ndef create_multichannel_img(volume):\n    depth, h, w = volume.shape\n    start, end = int(depth * 0.15), int(depth * 0.85)\n    mip = np.max(volume[start:end], axis=0)\n\n    slice_means = np.mean(volume, axis=(1, 2))\n    top_percentile = np.percentile(slice_means, 75)\n    high_intensity = slice_means >= top_percentile\n    weighted_avg = np.mean(volume[high_intensity], axis=0) if np.any(high_intensity) else np.mean(volume, axis=0)\n\n    std_proj = np.zeros_like(volume[0])\n    for i in range(depth - 4):\n        window_std = np.std(volume[i:i+5], axis=0)\n        std_proj = np.maximum(std_proj, window_std)\n\n    chans = []\n    for ch in [mip, weighted_avg, std_proj]:\n        if ch.max() == ch.min():\n            ch_norm = np.zeros_like(ch, dtype=np.uint8)\n        else:\n            ch_norm = ((ch - ch.min()) / (ch.max() - ch.min()) * 255).astype(np.uint8)\n        chans.append(ch_norm)\n    return np.stack(chans, axis=-1)\n\ndef preprocess_image(img, target_size=640):\n    img_resized = cv2.resize(img, (target_size, target_size))\n    img_rgb = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)\n    tensor = np.transpose(img_rgb, (2, 0, 1)).astype(np.float32) / 255.0\n    return torch.from_numpy(tensor).unsqueeze(0)\n\ndef sigmoid(x):\n    return 1 / (1 + np.exp(-x))\n\ndef tta_transforms(image):\n    transforms = [image]\n    transforms.append(cv2.flip(image, 1))\n    transforms.append(cv2.flip(image, 0))\n    transforms.append(cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE))\n    return transforms\n\n# -------------------- STABILIZATION HELPERS --------------------\ndef smooth_scores(scores_list, alpha=0.7):\n    smoothed = {label: 0.0 for label in LABEL_COLS}\n    for s in scores_list:\n        for l in LABEL_COLS:\n            smoothed[l] = alpha * smoothed[l] + (1 - alpha) * s[l]\n    return smoothed\n\ndef normalize_scores(final_scores):\n    loc_labels = LABEL_COLS[:-1]  # exclude Aneurysm Present\n    values = np.array([final_scores[l] for l in loc_labels])\n\n    norm_values = scipy.special.softmax(values)\n    norm_scores = {label: float(val) for label, val in zip(loc_labels, norm_values)}\n\n    norm_scores[\"Aneurysm Present\"] = max(norm_values.max(), final_scores[\"Aneurysm Present\"])\n    return norm_scores\n\n# ===================== MODEL =====================\nmodel_path = \"/kaggle/input/yolo8_trained_model/pytorch/default/1/best.torchscript\"\nassert os.path.exists(model_path), f\"Model not found at {model_path}\"\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = torch.jit.load(model_path, map_location=device)\nmodel.eval()\n\n# ===================== PREDICTION =====================\ndef aggregate_tta(scores_list):\n    #Confidence-weighted aggregation instead of simple smoothing.\n    agg = {label: 0.0 for label in LABEL_COLS}\n    weight_sum = 0.0\n    for s in scores_list:\n        w = s[\"Aneurysm Present\"] + 1e-6  # higher weight if more confident\n        for l in LABEL_COLS:\n            agg[l] += s[l] * w\n        weight_sum += w\n    if weight_sum > 0:\n        for l in LABEL_COLS:\n            agg[l] /= weight_sum\n    return agg\n\n\ndef predict(series_path: str) -> pd.DataFrame:\n    try:\n        series_id = os.path.basename(series_path)\n\n        # --- preprocess dicoms ---\n        volume = process_dicom_series(series_path)\n        base_image = create_multichannel_img(volume)\n\n        scores_list = []\n        for aug_img in tta_transforms(base_image):\n            try:\n                img_tensor = preprocess_image(aug_img).to(device)\n                with torch.no_grad():\n                    outputs = model(img_tensor)\n\n                # convert outputs\n                if outputs is None or len(outputs) == 0:\n                    continue\n                if isinstance(outputs, torch.Tensor):\n                    outputs = outputs.cpu().numpy()\n                elif isinstance(outputs, (list, tuple)) and len(outputs) > 0:\n                    outputs = [o.cpu().numpy() if isinstance(o, torch.Tensor) else o for o in outputs]\n                else:\n                    continue\n\n            except Exception as e:\n                print(f\"Skipping TTA image due to error: {e}\")\n                continue\n\n            # --- score dict ---\n            scores = {label: 0.0 for label in LABEL_COLS}\n            aneurysm_present = 0.0\n\n            for det in outputs[0]:\n                try:\n                    conf_prob = float(det[4])  # no sigmoid, model already calibrated\n                    cls_id = int(det[5])\n                    label = ID_TO_LABEL.get(cls_id, None)\n                    if label is not None:\n                        scores[label] = max(scores[label], conf_prob)\n                        aneurysm_present = max(aneurysm_present, conf_prob)\n                except Exception:\n                    continue\n\n            # aneurysm present is independent, not just max of others\n            scores[\"Aneurysm Present\"] = aneurysm_present\n            scores_list.append(scores)\n\n        # --- aggregation ---\n        if len(scores_list) == 0:\n            final_scores = {label: 0.0 for label in LABEL_COLS}\n        else:\n            agg_scores = aggregate_tta(scores_list)\n            final_scores = normalize_scores(agg_scores)\n\n        # --- thresholding (tune for CV score) ---\n        THRESH = 0.2\n        for l in LABEL_COLS:\n            if final_scores[l] < THRESH:\n                final_scores[l] = 0.0\n\n        del volume, base_image, scores_list\n        gc.collect()\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n\n        row = [series_id] + [round(float(final_scores[c]), 6) for c in LABEL_COLS]\n        df = pd.DataFrame([row], columns=[ID_COL, *LABEL_COLS])\n        return df\n\n    except Exception as e:\n        print(f\"Failed series {series_path} with error: {e}\")\n        row = [os.path.basename(series_path)] + [0.0] * len(LABEL_COLS)\n        return pd.DataFrame([row], columns=[ID_COL, *LABEL_COLS])\n\n# ===================== SERVER / LOCAL =====================\nshared_dir = \"/kaggle/shared\"\nshutil.rmtree(shared_dir, ignore_errors=True)\nos.makedirs(shared_dir, exist_ok=True)\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    submission_path = \"/kaggle/working/submission.parquet\"\n    if os.path.exists(submission_path):\n        df = pd.read_parquet(submission_path)\n        df.to_csv(\"/kaggle/working/submission.csv\", index=False, float_format=\"%.6f\")\n        display(df.head())\"\"\"","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:58:08.197682Z","iopub.execute_input":"2025-10-02T08:58:08.198196Z","iopub.status.idle":"2025-10-02T08:58:43.051667Z","shell.execute_reply.started":"2025-10-02T08:58:08.198159Z","shell.execute_reply":"2025-10-02T08:58:43.050719Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"# ===================== submission_kaggle_improved.py =====================\nimport os\nimport shutil\nimport gc\nfrom pathlib import Path\n\nimport pydicom\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport scipy.special\nimport kaggle_evaluation.rsna_inference_server\n\n# ===================== CONFIG =====================\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\nLOCATION_MAP = {\n    1: \"Other Posterior Circulation\",\n    2: \"Basilar Tip\",\n    3: \"Right Posterior Communicating Artery\",\n    4: \"Left Posterior Communicating Artery\",\n    5: \"Right Infraclinoid Internal Carotid Artery\",\n    6: \"Left Infraclinoid Internal Carotid Artery\",\n    7: \"Right Supraclinoid Internal Carotid Artery\",\n    8: \"Left Supraclinoid Internal Carotid Artery\",\n    9: \"Right Middle Cerebral Artery\",\n    10: \"Left Middle Cerebral Artery\",\n    11: \"Right Anterior Cerebral Artery\",\n    12: \"Left Anterior Cerebral Artery\",\n    13: \"Anterior Communicating Artery\",\n}\nID_TO_LABEL = {i - 1: v for i, v in LOCATION_MAP.items()}\n\n# ===================== HELPERS =====================\ndef adaptive_windowing(image):\n    #Multi-windowing to highlight vessels and brain structures.\n    windows = [(0, 100), (50, 150), (80, 200)]\n    windowed_images = []\n    for low, high in windows:\n        img_clip = np.clip(image, low, high)\n        img_norm = ((img_clip - low) / (high - low + 1e-8) * 255).astype(np.uint8)\n        windowed_images.append(img_norm)\n    return np.stack(windowed_images, axis=-1).mean(axis=-1).astype(np.uint8)\n\ndef process_dicom_series(series_path, image_size=512, num_slices=16):\n    dcm_files = sorted([os.path.join(root, f)\n                        for root, _, files in os.walk(series_path)\n                        for f in files if f.endswith(\".dcm\")])\n    if len(dcm_files) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n\n    dicom_data = []\n    for f in dcm_files:\n        try:\n            ds = pydicom.dcmread(f, force=True)\n            img = ds.pixel_array.astype(np.float32)\n            if img.ndim == 3 and img.shape[-1] == 3:\n                img = cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_BGR2GRAY).astype(np.float32)\n            img_resized = cv2.resize(img, (image_size, image_size))\n            dicom_data.append(img_resized)\n        except Exception as e:\n            print(f\"Skipping file {f} due to error: {e}\")\n\n    if len(dicom_data) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n\n    volume = np.stack(dicom_data, axis=0)\n    volume = adaptive_windowing(volume)\n\n    # Sample or pad slices\n    if volume.shape[0] > num_slices:\n        idx = np.linspace(0, volume.shape[0] - 1, num_slices).astype(int)\n        volume = volume[idx]\n    elif volume.shape[0] < num_slices:\n        pad = num_slices - volume.shape[0]\n        volume = np.pad(volume, ((0, pad), (0, 0), (0, 0)), mode=\"reflect\")\n\n    return volume\n\ndef create_multichannel_img(volume):\n    depth, h, w = volume.shape\n    mip = np.max(volume, axis=0)\n    slice_weights = np.percentile(volume, 75, axis=(1,2))\n    weighted_avg = np.average(volume, axis=0, weights=slice_weights)\n    std_proj = np.zeros_like(volume[0])\n    for i in range(depth - 4):\n        std_proj = np.maximum(std_proj, np.std(volume[i:i+5], axis=0))\n    chans = []\n    for ch in [mip, weighted_avg, std_proj]:\n        ch_norm = ((ch - ch.min()) / (ch.max() - ch.min() + 1e-8) * 255).astype(np.uint8)\n        chans.append(ch_norm)\n    return np.stack(chans, axis=-1)\n\ndef preprocess_image(img, target_size=640):\n    img_resized = cv2.resize(img, (target_size, target_size))\n    if img_resized.ndim == 2:\n        img_resized = cv2.cvtColor(img_resized, cv2.COLOR_GRAY2RGB)\n    else:\n        img_resized = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)\n    tensor = np.transpose(img_resized, (2,0,1)).astype(np.float32) / 255.0\n    return torch.from_numpy(tensor).unsqueeze(0)\n\ndef sigmoid(x, T=1.5):\n    return 1 / (1 + np.exp(-x / T))\n\ndef tta_transforms(image):\n    transforms = [image]\n    transforms += [cv2.flip(image, 0), cv2.flip(image, 1)]\n    transforms += [cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE),\n                   cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)]\n    transforms += [cv2.rotate(cv2.flip(image, 1), cv2.ROTATE_90_CLOCKWISE)]\n    return transforms\n\ndef smooth_scores(scores_list, alpha=0.7):\n    smoothed = {label: 0.0 for label in LABEL_COLS}\n    for s in scores_list:\n        for l in LABEL_COLS:\n            smoothed[l] = alpha * smoothed[l] + (1 - alpha) * s[l]\n    return smoothed\n\ndef normalize_scores(final_scores):\n    loc_labels = LABEL_COLS[:-1]\n    values = np.array([final_scores[l] for l in loc_labels])\n    norm_values = scipy.special.softmax(values)\n    norm_scores = {label: float(val) for label, val in zip(loc_labels, norm_values)}\n    norm_scores[\"Aneurysm Present\"] = max(norm_values.max(), final_scores[\"Aneurysm Present\"])\n    return norm_scores\n\n# ===================== MODEL =====================\nmodel_path = \"/kaggle/input/yolo8_trained_model/pytorch/default/1/best.torchscript\"\nassert os.path.exists(model_path), f\"Model not found at {model_path}\"\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = torch.jit.load(model_path, map_location=device)\nmodel.eval()\n\n# ===================== PREDICTION =====================\ndef predict(series_path: str) -> pd.DataFrame:\n    try:\n        series_id = os.path.basename(series_path)\n        volume = process_dicom_series(series_path)\n        base_image = create_multichannel_img(volume)\n\n        scores_list = []\n        for aug_img in tta_transforms(base_image):\n            try:\n                img_tensor = preprocess_image(aug_img).to(device)\n                with torch.no_grad():\n                    outputs = model(img_tensor)\n                    if outputs is None or len(outputs) == 0:\n                        continue\n                    if isinstance(outputs, torch.Tensor):\n                        outputs = outputs.cpu().numpy()\n                    elif isinstance(outputs, (list, tuple)):\n                        outputs = [o.cpu().numpy() if isinstance(o, torch.Tensor) else o for o in outputs]\n                    else:\n                        continue\n            except Exception as e:\n                print(f\"Skipping TTA image due to error: {e}\")\n                continue\n\n            scores = {label: 0.0 for label in LABEL_COLS}\n            aneurysm_present = 0.0\n\n            for det in outputs[0]:\n                try:\n                    conf_prob = sigmoid(float(det[4]))\n                    cls_id = int(det[5])\n                    label = ID_TO_LABEL.get(cls_id, None)\n                    if label is not None:\n                        scores[label] = max(scores[label], conf_prob)\n                        aneurysm_present = max(aneurysm_present, conf_prob)\n                except Exception:\n                    continue\n\n            scores[\"Aneurysm Present\"] = max(aneurysm_present, max(scores.values()))\n            scores_list.append(scores)\n\n        if len(scores_list) == 0:\n            final_scores = {label: 0.0 for label in LABEL_COLS}\n        else:\n            smoothed = smooth_scores(scores_list, alpha=0.7)\n            final_scores = normalize_scores(smoothed)\n\n        del volume, base_image, scores_list\n        gc.collect()\n\n        row = [series_id] + [round(float(final_scores[c]), 6) for c in LABEL_COLS]\n        df = pd.DataFrame([row], columns=[ID_COL, *LABEL_COLS])\n        return df\n\n    except Exception as e:\n        print(f\"Failed series {series_path} with error: {e}\")\n        row = [os.path.basename(series_path)] + [0.0]*len(LABEL_COLS)\n        return pd.DataFrame([row], columns=[ID_COL, *LABEL_COLS])\n\n# ===================== SERVER / LOCAL =====================\nshared_dir = \"/kaggle/shared\"\nshutil.rmtree(shared_dir, ignore_errors=True)\nos.makedirs(shared_dir, exist_ok=True)\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    submission_path = \"/kaggle/working/submission.parquet\"\n    if os.path.exists(submission_path):\n        df = pd.read_parquet(submission_path)\n        df.to_csv(\"/kaggle/working/submission.csv\", index=False, float_format=\"%.6f\")\n        display(df.head())\"\"\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T10:27:52.622081Z","iopub.execute_input":"2025-10-02T10:27:52.622422Z","iopub.status.idle":"2025-10-02T10:28:35.988527Z","shell.execute_reply.started":"2025-10-02T10:27:52.622397Z","shell.execute_reply":"2025-10-02T10:28:35.987353Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"import os\nimport shutil\nimport gc\nimport pydicom\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport scipy.special\nimport kaggle_evaluation.rsna_inference_server\n\n# ===================== CONFIG =====================\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\nLOCATION_MAP = {\n    1: \"Other Posterior Circulation\",\n    2: \"Basilar Tip\",\n    3: \"Right Posterior Communicating Artery\",\n    4: \"Left Posterior Communicating Artery\",\n    5: \"Right Infraclinoid Internal Carotid Artery\",\n    6: \"Left Infraclinoid Internal Carotid Artery\",\n    7: \"Right Supraclinoid Internal Carotid Artery\",\n    8: \"Left Supraclinoid Internal Carotid Artery\",\n    9: \"Right Middle Cerebral Artery\",\n    10: \"Left Middle Cerebral Artery\",\n    11: \"Right Anterior Cerebral Artery\",\n    12: \"Left Anterior Cerebral Artery\",\n    13: \"Anterior Communicating Artery\",\n}\nID_TO_LABEL = {i - 1: v for i, v in LOCATION_MAP.items()}\n\n# ===================== HELPERS =====================\ndef adaptive_windowing(image):\n    # Multi-windowing + mean aggregation\n    windows = [(0, 100), (50, 150), (80, 200)]\n    imgs = []\n    for low, high in windows:\n        img_clip = np.clip(image, low, high)\n        img_norm = ((img_clip - low) / (high - low + 1e-8) * 255).astype(np.uint8)\n        imgs.append(img_norm)\n    return np.mean(np.stack(imgs, axis=-1), axis=-1).astype(np.uint8)\n\ndef process_dicom_series(series_path, image_size=512, num_slices=16):\n    dcm_files = sorted([os.path.join(root, f)\n                        for root, _, files in os.walk(series_path)\n                        for f in files if f.endswith(\".dcm\")])\n    if len(dcm_files) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n    dicom_data = []\n    for f in dcm_files:\n        try:\n            ds = pydicom.dcmread(f, force=True)\n            img = ds.pixel_array.astype(np.float32)\n            if img.ndim == 3 and img.shape[-1] == 3:\n                img = cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_BGR2GRAY).astype(np.float32)\n            dicom_data.append(cv2.resize(img, (image_size, image_size)))\n        except:\n            continue\n    if len(dicom_data) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n    volume = np.stack(dicom_data, axis=0)\n    volume = adaptive_windowing(volume)\n    if volume.shape[0] > num_slices:\n        idx = np.linspace(0, volume.shape[0]-1, num_slices).astype(int)\n        volume = volume[idx]\n    elif volume.shape[0] < num_slices:\n        pad = num_slices - volume.shape[0]\n        volume = np.pad(volume, ((0,pad),(0,0),(0,0)), mode=\"reflect\")\n    return volume\n\ndef create_multichannel_img(volume):\n    depth = volume.shape[0]\n    mip = np.max(volume, axis=0)\n    central = volume[depth//2]\n    slice_weights = np.percentile(volume, 75, axis=(1,2))\n    weighted_avg = np.average(volume, axis=0, weights=slice_weights)\n    std_proj = np.zeros_like(volume[0])\n    for i in range(depth-4):\n        std_proj = np.maximum(std_proj, np.std(volume[i:i+5], axis=0))\n    chans = [mip, central, weighted_avg, std_proj]\n    chans_norm = [((ch - ch.min()) / (ch.max()-ch.min()+1e-8) * 255).astype(np.uint8) for ch in chans]\n    return np.stack(chans_norm, axis=-1)\n\ndef preprocess_image(img, target_size=640):\n    img_resized = cv2.resize(img, (target_size, target_size))\n    if img_resized.ndim==2: img_resized=cv2.cvtColor(img_resized, cv2.COLOR_GRAY2RGB)\n    else: img_resized=cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)\n    tensor = np.transpose(img_resized,(2,0,1)).astype(np.float32)/255\n    return torch.from_numpy(tensor).unsqueeze(0)\n\ndef sigmoid(x, T=1.2):  # tuned temperature\n    return 1 / (1 + np.exp(-x / T))\n\ndef tta_transforms(img):\n    # 8 augmentations\n    imgs = [img, cv2.flip(img,0), cv2.flip(img,1),\n            cv2.rotate(img,cv2.ROTATE_90_CLOCKWISE),\n            cv2.rotate(img,cv2.ROTATE_90_COUNTERCLOCKWISE),\n            cv2.rotate(cv2.flip(img,1),cv2.ROTATE_90_CLOCKWISE)]\n    return imgs\n\ndef smooth_scores(scores_list, alpha=0.6):\n    smoothed={l:0.0 for l in LABEL_COLS}\n    for s in scores_list:\n        for l in LABEL_COLS:\n            smoothed[l]=alpha*smoothed[l]+(1-alpha)*s[l]\n    return smoothed\n\ndef normalize_scores(final_scores):\n    loc_labels = LABEL_COLS[:-1]\n    values = np.array([final_scores[l] for l in loc_labels])\n    norm_values = scipy.special.softmax(values)\n    norm_scores = {label: float(val) for label,val in zip(loc_labels,norm_values)}\n    norm_scores[\"Aneurysm Present\"] = max(norm_values.max(), final_scores[\"Aneurysm Present\"])\n    return norm_scores\n\n# ===================== MODEL =====================\nmodel_path = \"/kaggle/input/yolo8_trained_model/pytorch/default/1/best.torchscript\"\nassert os.path.exists(model_path)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = torch.jit.load(model_path,map_location=device)\nmodel.eval()\n\n# ===================== PREDICTION =====================\ndef predict(series_path):\n    try:\n        series_id=os.path.basename(series_path)\n        vol=process_dicom_series(series_path)\n        img=create_multichannel_img(vol)\n        scores_list=[]\n        for aug in tta_transforms(img):\n            try:\n                tensor=preprocess_image(aug).to(device)\n                with torch.no_grad():\n                    out=model(tensor)\n                    if out is None or len(out)==0: continue\n                    if isinstance(out,torch.Tensor): out=[out.cpu().numpy()]\n                    elif isinstance(out,(list,tuple)): out=[o.cpu().numpy() if isinstance(o,torch.Tensor) else o for o in out]\n            except: continue\n            scores={l:0.0 for l in LABEL_COLS}\n            aneurysm=0.0\n            for det in out[0]:\n                try:\n                    conf=sigmoid(float(det[4]))\n                    cls=int(det[5])\n                    label=ID_TO_LABEL.get(cls,None)\n                    if label: scores[label]=max(scores[label],conf); aneurysm=max(aneurysm,conf)\n                except: continue\n            scores[\"Aneurysm Present\"]=max(aneurysm,max(scores.values()))\n            scores_list.append(scores)\n        if not scores_list: final={l:0.0 for l in LABEL_COLS}\n        else: final=normalize_scores(smooth_scores(scores_list))\n        row=[series_id]+[round(float(final[c]),6) for c in LABEL_COLS]\n        return pd.DataFrame([row],columns=[ID_COL,*LABEL_COLS])\n    except:\n        row=[os.path.basename(series_path)]+[0.0]*len(LABEL_COLS)\n        return pd.DataFrame([row],columns=[ID_COL,*LABEL_COLS])\n\n# ===================== SERVER / LOCAL =====================\nshared_dir=\"/kaggle/shared\"\nshutil.rmtree(shared_dir,ignore_errors=True)\nos.makedirs(shared_dir,exist_ok=True)\nserver=kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\nif os.getenv(\"KAGGLE_IS_COMPETITION_RERUN\"): server.serve()\nelse:\n    server.run_local_gateway()\n    sp=\"/kaggle/working/submission.parquet\"\n    if os.path.exists(sp):\n        df=pd.read_parquet(sp)\n        df.to_csv(\"/kaggle/working/submission.csv\",index=False,float_format=\"%.6f\")\n        display(df.head())\"\"\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T12:22:52.077195Z","iopub.execute_input":"2025-10-02T12:22:52.077515Z","iopub.status.idle":"2025-10-02T12:23:33.12459Z","shell.execute_reply.started":"2025-10-02T12:22:52.077492Z","shell.execute_reply":"2025-10-02T12:23:33.123862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===================== submission_kaggle_diffusion.py =====================\nimport os\nimport shutil\nimport gc\nfrom pathlib import Path\n\nimport pydicom\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport timm\nimport scipy.special\nimport kaggle_evaluation.rsna_inference_server\nimport matplotlib.pyplot as plt\n\n# ===================== CONFIG =====================\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\nLOCATION_MAP = {\n    1: \"Other Posterior Circulation\",\n    2: \"Basilar Tip\",\n    3: \"Right Posterior Communicating Artery\",\n    4: \"Left Posterior Communicating Artery\",\n    5: \"Right Infraclinoid Internal Carotid Artery\",\n    6: \"Left Infraclinoid Internal Carotid Artery\",\n    7: \"Right Supraclinoid Internal Carotid Artery\",\n    8: \"Left Supraclinoid Internal Carotid Artery\",\n    9: \"Right Middle Cerebral Artery\",\n    10: \"Left Middle Cerebral Artery\",\n    11: \"Right Anterior Cerebral Artery\",\n    12: \"Left Anterior Cerebral Artery\",\n    13: \"Anterior Communicating Artery\",\n}\nID_TO_LABEL = {i - 1: v for i, v in LOCATION_MAP.items()}\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# ===================== HELPERS =====================\ndef adaptive_windowing(image):\n    windows = [(0, 100), (50, 150), (80, 200)]\n    windowed_images = []\n    for low, high in windows:\n        img_clip = np.clip(image, low, high)\n        img_norm = ((img_clip - low) / (high - low + 1e-8) * 255).astype(np.uint8)\n        windowed_images.append(img_norm)\n    return np.stack(windowed_images, axis=-1).mean(axis=-1).astype(np.uint8)\n\ndef process_dicom_series(series_path, image_size=512, num_slices=16):\n    dcm_files = sorted([os.path.join(root, f)\n                        for root, _, files in os.walk(series_path)\n                        for f in files if f.endswith(\".dcm\")])\n    if len(dcm_files) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n    dicom_data = []\n    for f in dcm_files:\n        try:\n            ds = pydicom.dcmread(f, force=True)\n            img = ds.pixel_array.astype(np.float32)\n            if img.ndim == 3 and img.shape[-1] == 3:\n                img = cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_BGR2GRAY).astype(np.float32)\n            img_resized = cv2.resize(img, (image_size, image_size))\n            dicom_data.append(img_resized)\n        except Exception as e:\n            print(f\"Skipping file {f} due to error: {e}\")\n    if len(dicom_data) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n    volume = np.stack(dicom_data, axis=0)\n    volume = adaptive_windowing(volume)\n    if volume.shape[0] > num_slices:\n        idx = np.linspace(0, volume.shape[0] - 1, num_slices).astype(int)\n        volume = volume[idx]\n    elif volume.shape[0] < num_slices:\n        pad = num_slices - volume.shape[0]\n        volume = np.pad(volume, ((0, pad), (0, 0), (0, 0)), mode=\"reflect\")\n    return volume\n\ndef create_multichannel_img(volume):\n    depth, h, w = volume.shape\n    mip = np.max(volume, axis=0)\n    slice_weights = np.percentile(volume, 75, axis=(1,2))\n    weighted_avg = np.average(volume, axis=0, weights=slice_weights)\n    std_proj = np.zeros_like(volume[0])\n    for i in range(depth - 4):\n        std_proj = np.maximum(std_proj, np.std(volume[i:i+5], axis=0))\n    chans = []\n    for ch in [mip, weighted_avg, std_proj]:\n        ch_norm = ((ch - ch.min()) / (ch.max() - ch.min() + 1e-8) * 255).astype(np.uint8)\n        chans.append(ch_norm)\n    return np.stack(chans, axis=-1)\n\ndef preprocess_image(img, target_size=640):\n    img_resized = cv2.resize(img, (target_size, target_size))\n    if img_resized.ndim == 2:\n        img_resized = cv2.cvtColor(img_resized, cv2.COLOR_GRAY2RGB)\n    else:\n        img_resized = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)\n    tensor = np.transpose(img_resized, (2,0,1)).astype(np.float32) / 255.0\n    return torch.from_numpy(tensor).unsqueeze(0)\n\ndef sigmoid(x, T=1.5):\n    return 1 / (1 + np.exp(-x / T))\n\ndef tta_transforms(image):\n    transforms = [image]\n    transforms += [cv2.flip(image, 0), cv2.flip(image, 1)]\n    transforms += [cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE),\n                   cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)]\n    transforms += [cv2.rotate(cv2.flip(image, 1), cv2.ROTATE_90_CLOCKWISE)]\n    return transforms\n\ndef smooth_scores(scores_list, alpha=0.6):\n    avg_scores = {label: np.mean([s[label] for s in scores_list]) for label in LABEL_COLS}\n    smoothed = {label: alpha * avg_scores[label] + (1-alpha) * max(avg_scores[label], 0.3) for label in LABEL_COLS}\n    return smoothed\n\ndef normalize_scores(final_scores):\n    loc_labels = LABEL_COLS[:-1]\n    values = np.array([final_scores[l] for l in loc_labels])\n    norm_values = scipy.special.softmax(values)\n    norm_scores = {label: float(val) for label, val in zip(loc_labels, norm_values)}\n    norm_scores[\"Aneurysm Present\"] = max(norm_values.max(), final_scores[\"Aneurysm Present\"])\n    return norm_scores\n\n# ===================== LOAD MODELS =====================\n# YOLOv8 TorchScript\nyolo_model_path = \"/kaggle/input/yolo8_trained_model/pytorch/default/1/best.torchscript\"\nyolo_model = torch.jit.load(yolo_model_path, map_location=device)\nyolo_model.eval()\n\n# EfficientNet-B3 via timm + KaggleHub\nimport kagglehub\neff_path = kagglehub.model_download(\"timm/tf-efficientnet/pyTorch/tf-efficientnet-b3\")\nmodel_file = next((os.path.join(eff_path, f) for f in os.listdir(eff_path) if f.endswith(\".pt\") or f.endswith(\".pth\")), None)\n\n# Create model with correct num_classes\nefficientnet_model = timm.create_model(\"tf_efficientnet_b3\", pretrained=False, num_classes=len(LABEL_COLS)-1)\nstate_dict = torch.load(model_file, map_location=device)\n\n# Remove classifier weights to avoid size mismatch\nfor k in list(state_dict.keys()):\n    if \"classifier\" in k:\n        del state_dict[k]\n\nefficientnet_model.load_state_dict(state_dict, strict=False)\nefficientnet_model = efficientnet_model.to(device)\nefficientnet_model.eval()\n\n# ===================== PREDICTION =====================\nYOLO_WEIGHT = 0.6\nEFF_WEIGHT = 0.4\nTHRESHOLDS = {\"Aneurysm Present\": 0.3}\n\ndef predict(series_path: str) -> pd.DataFrame:\n    series_id = os.path.basename(series_path)\n    volume = process_dicom_series(series_path)\n    base_image = create_multichannel_img(volume)\n    scores_list = []\n\n    for aug_img in tta_transforms(base_image):\n        yolo_scores = {label:0.0 for label in LABEL_COLS}\n        eff_scores = {label:0.0 for label in LABEL_COLS}\n\n        # YOLOv8\n        try:\n            img_tensor = preprocess_image(aug_img).to(device)\n            with torch.no_grad():\n                outputs = yolo_model(img_tensor)\n                if isinstance(outputs, torch.Tensor):\n                    outputs = outputs.cpu().numpy()\n                elif isinstance(outputs, (list, tuple)):\n                    outputs = [o.cpu().numpy() if isinstance(o, torch.Tensor) else o for o in outputs]\n                for det in outputs[0]:\n                    conf_prob = sigmoid(float(det[4]))\n                    cls_id = int(det[5])\n                    label = ID_TO_LABEL.get(cls_id, None)\n                    if label:\n                        yolo_scores[label] = max(yolo_scores[label], conf_prob)\n                yolo_scores[\"Aneurysm Present\"] = max(yolo_scores.values())\n        except:\n            pass\n\n        # EfficientNet\n        try:\n            img_tensor = preprocess_image(aug_img).to(device)\n            with torch.no_grad():\n                eff_out = efficientnet_model(img_tensor)\n                eff_out = torch.sigmoid(eff_out).cpu().numpy()[0]\n                for i, label in enumerate(LABEL_COLS[:-1]):\n                    eff_scores[label] = float(eff_out[i])\n                eff_scores[\"Aneurysm Present\"] = max(eff_scores.values())\n        except:\n            pass\n\n        combined_scores = {label: YOLO_WEIGHT*yolo_scores[label]+EFF_WEIGHT*eff_scores[label] for label in LABEL_COLS}\n        scores_list.append(combined_scores)\n\n    final_scores = normalize_scores(smooth_scores(scores_list))\n    for label in THRESHOLDS:\n        final_scores[label] = max(final_scores[label], THRESHOLDS[label])\n\n    # Visualization (diffusion style)\n    plt.figure(figsize=(8,4))\n    plt.bar(range(len(LABEL_COLS)), [final_scores[l] for l in LABEL_COLS], tick_label=LABEL_COLS)\n    plt.xticks(rotation=90)\n    plt.title(f\"Diffusion Ensemble Scores: {series_id}\")\n    plt.show()\n\n    del volume, base_image, scores_list\n    gc.collect()\n    row = [series_id] + [round(float(final_scores[c]), 6) for c in LABEL_COLS]\n    return pd.DataFrame([row], columns=[ID_COL, *LABEL_COLS])\n\n# ===================== SERVER =====================\nshared_dir = \"/kaggle/shared\"\nshutil.rmtree(shared_dir, ignore_errors=True)\nos.makedirs(shared_dir, exist_ok=True)\ninference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\nif os.getenv(\"KAGGLE_IS_COMPETITION_RERUN\"):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway()\n    submission_path = \"/kaggle/working/submission.parquet\"\n    if os.path.exists(submission_path):\n        df = pd.read_parquet(submission_path)\n        df.to_csv(\"/kaggle/working/submission.csv\", index=False, float_format=\"%.6f\")\n        display(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T12:41:24.807108Z","iopub.execute_input":"2025-10-02T12:41:24.807484Z","iopub.status.idle":"2025-10-02T12:42:23.34324Z","shell.execute_reply.started":"2025-10-02T12:41:24.807457Z","shell.execute_reply":"2025-10-02T12:42:23.341946Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"# ===================== submission_kaggle_diffusion_ensemble.py =====================\nimport os\nimport shutil\nimport gc\nfrom pathlib import Path\n\nimport pydicom\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport timm\nimport scipy.special\nimport kaggle_evaluation.rsna_inference_server\nimport matplotlib.pyplot as plt\n\n# ===================== CONFIG =====================\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\nLOCATION_MAP = {\n    1: \"Other Posterior Circulation\",\n    2: \"Basilar Tip\",\n    3: \"Right Posterior Communicating Artery\",\n    4: \"Left Posterior Communicating Artery\",\n    5: \"Right Infraclinoid Internal Carotid Artery\",\n    6: \"Left Infraclinoid Internal Carotid Artery\",\n    7: \"Right Supraclinoid Internal Carotid Artery\",\n    8: \"Left Supraclinoid Internal Carotid Artery\",\n    9: \"Right Middle Cerebral Artery\",\n    10: \"Left Middle Cerebral Artery\",\n    11: \"Right Anterior Cerebral Artery\",\n    12: \"Left Anterior Cerebral Artery\",\n    13: \"Anterior Communicating Artery\",\n}\nID_TO_LABEL = {i - 1: v for i, v in LOCATION_MAP.items()}\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# ===================== HELPERS =====================\ndef adaptive_windowing(image):\n    windows = [(0, 100), (50, 150), (80, 200)]\n    windowed_images = []\n    for low, high in windows:\n        img_clip = np.clip(image, low, high)\n        img_norm = ((img_clip - low) / (high - low + 1e-8) * 255).astype(np.uint8)\n        windowed_images.append(img_norm)\n    return np.stack(windowed_images, axis=-1).mean(axis=-1).astype(np.uint8)\n\ndef process_dicom_series(series_path, image_size=512, num_slices=16):\n    dcm_files = sorted([os.path.join(root, f)\n                        for root, _, files in os.walk(series_path)\n                        for f in files if f.endswith(\".dcm\")])\n    if len(dcm_files) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n    dicom_data = []\n    for f in dcm_files:\n        try:\n            ds = pydicom.dcmread(f, force=True)\n            img = ds.pixel_array.astype(np.float32)\n            if img.ndim == 3 and img.shape[-1] == 3:\n                img = cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_BGR2GRAY).astype(np.float32)\n            img_resized = cv2.resize(img, (image_size, image_size))\n            dicom_data.append(img_resized)\n        except Exception as e:\n            print(f\"Skipping file {f} due to error: {e}\")\n    if len(dicom_data) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n    volume = np.stack(dicom_data, axis=0)\n    volume = adaptive_windowing(volume)\n    if volume.shape[0] > num_slices:\n        idx = np.linspace(0, volume.shape[0] - 1, num_slices).astype(int)\n        volume = volume[idx]\n    elif volume.shape[0] < num_slices:\n        pad = num_slices - volume.shape[0]\n        volume = np.pad(volume, ((0, pad), (0, 0), (0, 0)), mode=\"reflect\")\n    return volume\n\ndef create_multichannel_img(volume):\n    depth, h, w = volume.shape\n    mip = np.max(volume, axis=0)\n    slice_weights = np.percentile(volume, 75, axis=(1,2))\n    weighted_avg = np.average(volume, axis=0, weights=slice_weights)\n    std_proj = np.zeros_like(volume[0])\n    for i in range(depth - 4):\n        std_proj = np.maximum(std_proj, np.std(volume[i:i+5], axis=0))\n    chans = []\n    for ch in [mip, weighted_avg, std_proj]:\n        ch_norm = ((ch - ch.min()) / (ch.max() - ch.min() + 1e-8) * 255).astype(np.uint8)\n        chans.append(ch_norm)\n    return np.stack(chans, axis=-1)\n\ndef preprocess_image(img, target_size=640):\n    img_resized = cv2.resize(img, (target_size, target_size))\n    if img_resized.ndim == 2:\n        img_resized = cv2.cvtColor(img_resized, cv2.COLOR_GRAY2RGB)\n    else:\n        img_resized = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)\n    tensor = np.transpose(img_resized, (2,0,1)).astype(np.float32) / 255.0\n    return torch.from_numpy(tensor).unsqueeze(0)\n\ndef sigmoid(x, T=1.5):\n    return 1 / (1 + np.exp(-x / T))\n\ndef tta_transforms(image):\n    transforms = [image]\n    transforms += [cv2.flip(image, 0), cv2.flip(image, 1)]\n    transforms += [cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE),\n                   cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)]\n    transforms += [cv2.rotate(cv2.flip(image, 1), cv2.ROTATE_90_CLOCKWISE)]\n    return transforms\n\ndef smooth_scores(scores_list, alpha=0.6):\n    avg_scores = {label: np.mean([s[label] for s in scores_list]) for label in LABEL_COLS}\n    smoothed = {label: alpha * avg_scores[label] + (1-alpha) * max(avg_scores[label], 0.3) for label in LABEL_COLS}\n    return smoothed\n\ndef normalize_scores(final_scores):\n    loc_labels = LABEL_COLS[:-1]\n    values = np.array([final_scores[l] for l in loc_labels])\n    norm_values = scipy.special.softmax(values)\n    norm_scores = {label: float(val) for label, val in zip(loc_labels, norm_values)}\n    norm_scores[\"Aneurysm Present\"] = max(norm_values.max(), final_scores[\"Aneurysm Present\"])\n    return norm_scores\n\n# ===================== LOAD MODELS =====================\nimport kagglehub\n\n# YOLOv8\nyolo_model_path = \"/kaggle/input/yolo8_trained_model/pytorch/default/1/best.torchscript\"\nyolo_model = torch.jit.load(yolo_model_path, map_location=device)\nyolo_model.eval()\n\n# Multi EfficientNet ensemble\neff_models = [\n    \"timm/tf-efficientnet/pyTorch/tf-efficientnet-b3\",\n    \"dennisfong/rsna-2025-ia-ct-224-efficientnet/pyTorch/default\"\n]\nefficientnet_models = []\n\nfor model_name in eff_models:\n    eff_path = kagglehub.model_download(model_name)\n    model_file = next((os.path.join(eff_path, f) for f in os.listdir(eff_path) if f.endswith(\".pt\") or f.endswith(\".pth\")), None)\n    eff_model = timm.create_model(\"tf_efficientnet_b3\", pretrained=False, num_classes=len(LABEL_COLS)-1)\n    state_dict = torch.load(model_file, map_location=device)\n    for k in list(state_dict.keys()):\n        if \"classifier\" in k:\n            del state_dict[k]\n    eff_model.load_state_dict(state_dict, strict=False)\n    eff_model = eff_model.to(device)\n    eff_model.eval()\n    efficientnet_models.append(eff_model)\n\n# ===================== PREDICTION =====================\nYOLO_WEIGHT = 0.5\nEFF_WEIGHT = 0.5 / len(efficientnet_models)\nTHRESHOLDS = {\"Aneurysm Present\": 0.3}\n\ndef predict(series_path: str) -> pd.DataFrame:\n    series_id = os.path.basename(series_path)\n    volume = process_dicom_series(series_path)\n    base_image = create_multichannel_img(volume)\n    scores_list = []\n\n    for aug_img in tta_transforms(base_image):\n        yolo_scores = {label:0.0 for label in LABEL_COLS}\n        eff_scores = {label:0.0 for label in LABEL_COLS}\n\n        # YOLOv8\n        try:\n            img_tensor = preprocess_image(aug_img).to(device)\n            with torch.no_grad():\n                outputs = yolo_model(img_tensor)\n                if isinstance(outputs, torch.Tensor):\n                    outputs = outputs.cpu().numpy()\n                elif isinstance(outputs, (list, tuple)):\n                    outputs = [o.cpu().numpy() if isinstance(o, torch.Tensor) else o for o in outputs]\n                for det in outputs[0]:\n                    conf_prob = sigmoid(float(det[4]))\n                    cls_id = int(det[5])\n                    label = ID_TO_LABEL.get(cls_id, None)\n                    if label:\n                        yolo_scores[label] = max(yolo_scores[label], conf_prob)\n                yolo_scores[\"Aneurysm Present\"] = max(yolo_scores.values())\n        except:\n            pass\n\n        # EfficientNet ensemble\n        for model in efficientnet_models:\n            try:\n                img_tensor = preprocess_image(aug_img).to(device)\n                with torch.no_grad():\n                    eff_out = model(img_tensor)\n                    eff_out = torch.sigmoid(eff_out).cpu().numpy()[0]\n                    for i, label in enumerate(LABEL_COLS[:-1]):\n                        eff_scores[label] += float(eff_out[i]) * EFF_WEIGHT\n                    eff_scores[\"Aneurysm Present\"] = max(eff_scores.values())\n            except:\n                continue\n\n        combined_scores = {label: YOLO_WEIGHT*yolo_scores[label]+eff_scores[label] for label in LABEL_COLS}\n        scores_list.append(combined_scores)\n\n    final_scores = normalize_scores(smooth_scores(scores_list))\n    for label in THRESHOLDS:\n        final_scores[label] = max(final_scores[label], THRESHOLDS[label])\n\n    # Diffusion visualization\n    plt.figure(figsize=(10,5))\n    plt.bar(range(len(LABEL_COLS)), [final_scores[l] for l in LABEL_COLS], tick_label=LABEL_COLS)\n    plt.xticks(rotation=90)\n    plt.title(f\"Diffusion Ensemble Scores: {series_id}\")\n    plt.show()\n\n    del volume, base_image, scores_list\n    gc.collect()\n    row = [series_id] + [round(float(final_scores[c]), 6) for c in LABEL_COLS]\n    return pd.DataFrame([row], columns=[ID_COL, *LABEL_COLS])\n\n# ===================== SERVER =====================\nshared_dir = \"/kaggle/shared\"\nshutil.rmtree(shared_dir, ignore_errors=True)\nos.makedirs(shared_dir, exist_ok=True)\ninference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\nif os.getenv(\"KAGGLE_IS_COMPETITION_RERUN\"):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway()\n    submission_path = \"/kaggle/working/submission.parquet\"\n    if os.path.exists(submission_path):\n        df = pd.read_parquet(submission_path)\n        df.to_csv(\"/kaggle/working/submission.csv\", index=False, float_format=\"%.6f\")\n        display(df.head())\"\"\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T12:47:15.429746Z","iopub.execute_input":"2025-10-02T12:47:15.430057Z","iopub.status.idle":"2025-10-02T12:48:23.617045Z","shell.execute_reply.started":"2025-10-02T12:47:15.430036Z","shell.execute_reply":"2025-10-02T12:48:23.616058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"# ===================== submission_kaggle_ensemble.py =====================\nimport os\nimport shutil\nimport gc\nfrom pathlib import Path\nimport warnings\n\nimport pydicom\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport timm\nimport scipy.special\nimport matplotlib.pyplot as plt\nimport kagglehub\nimport kaggle_evaluation.rsna_inference_server\n\nwarnings.filterwarnings(\"ignore\")\n\n# ===================== CONFIG =====================\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\nLOCATION_MAP = {\n    1: \"Other Posterior Circulation\",\n    2: \"Basilar Tip\",\n    3: \"Right Posterior Communicating Artery\",\n    4: \"Left Posterior Communicating Artery\",\n    5: \"Right Infraclinoid Internal Carotid Artery\",\n    6: \"Left Infraclinoid Internal Carotid Artery\",\n    7: \"Right Supraclinoid Internal Carotid Artery\",\n    8: \"Left Supraclinoid Internal Carotid Artery\",\n    9: \"Right Middle Cerebral Artery\",\n    10: \"Left Middle Cerebral Artery\",\n    11: \"Right Anterior Cerebral Artery\",\n    12: \"Left Anterior Cerebral Artery\",\n    13: \"Anterior Communicating Artery\",\n}\nID_TO_LABEL = {i - 1: v for i, v in LOCATION_MAP.items()}\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# ===================== MODEL LOADING =====================\n# --- YOLOv8 Model ---\nyolo_model_path = \"/kaggle/input/yolo8_trained_model/pytorch/default/1/best.torchscript\"\nyolo_model = torch.jit.load(yolo_model_path, map_location=device)\nyolo_model.eval()\n\n# --- EfficientNet Model via KaggleHub ---\neff_path = kagglehub.model_download(\"dennisfong/rsna-2025-ia-ct-224-efficientnet/pyTorch/default\")\nmodel_file = next((os.path.join(eff_path, f) for f in os.listdir(eff_path) if f.endswith(\".pt\") or f.endswith(\".pth\")), None)\nefficientnet_model = timm.create_model(\"tf_efficientnet_b3\", pretrained=False, num_classes=len(LABEL_COLS)-1)\n\n# Load checkpoint safely\nstate_dict = torch.load(model_file, map_location=device)\nif \"state_dict\" in state_dict:  # handle kagglehub dict\n    state_dict = state_dict[\"state_dict\"]\n\n# Remove 'classifier.' prefix if exists\nnew_state_dict = {}\nfor k, v in state_dict.items():\n    key = k.replace(\"classifier.\", \"\")\n    new_state_dict[key] = v\nefficientnet_model.load_state_dict(new_state_dict, strict=False)\n\nefficientnet_model = efficientnet_model.to(device)\nefficientnet_model.eval()\n\n# ===================== HELPERS =====================\ndef adaptive_windowing(image):\n    windows = [(0, 100), (50, 150), (80, 200)]\n    windowed_images = []\n    for low, high in windows:\n        img_clip = np.clip(image, low, high)\n        img_norm = ((img_clip - low) / (high - low + 1e-8) * 255).astype(np.uint8)\n        windowed_images.append(img_norm)\n    return np.stack(windowed_images, axis=-1).mean(axis=-1).astype(np.uint8)\n\ndef process_dicom_series(series_path, image_size=512, num_slices=16):\n    dcm_files = sorted([os.path.join(root, f)\n                        for root, _, files in os.walk(series_path)\n                        for f in files if f.endswith(\".dcm\")])\n    if len(dcm_files) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n\n    dicom_data = []\n    for f in dcm_files:\n        try:\n            ds = pydicom.dcmread(f, force=True)\n            img = ds.pixel_array.astype(np.float32)\n            if img.ndim == 3 and img.shape[-1] == 3:\n                img = cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_BGR2GRAY).astype(np.float32)\n            img_resized = cv2.resize(img, (image_size, image_size))\n            dicom_data.append(img_resized)\n        except:\n            continue\n\n    if len(dicom_data) == 0:\n        return np.zeros((num_slices, image_size, image_size), dtype=np.uint8)\n\n    volume = np.stack(dicom_data, axis=0)\n    volume = adaptive_windowing(volume)\n\n    if volume.shape[0] > num_slices:\n        idx = np.linspace(0, volume.shape[0]-1, num_slices).astype(int)\n        volume = volume[idx]\n    elif volume.shape[0] < num_slices:\n        pad = num_slices - volume.shape[0]\n        volume = np.pad(volume, ((0,pad),(0,0),(0,0)), mode=\"reflect\")\n\n    return volume\n\ndef create_multichannel_img(volume):\n    depth, h, w = volume.shape\n    mip = np.max(volume, axis=0)\n    slice_weights = np.percentile(volume, 75, axis=(1,2))\n    weighted_avg = np.average(volume, axis=0, weights=slice_weights)\n    std_proj = np.zeros_like(volume[0])\n    for i in range(depth-4):\n        std_proj = np.maximum(std_proj, np.std(volume[i:i+5], axis=0))\n    chans = []\n    for ch in [mip, weighted_avg, std_proj]:\n        ch_norm = ((ch - ch.min()) / (ch.max() - ch.min() + 1e-8) * 255).astype(np.uint8)\n        chans.append(ch_norm)\n    return np.stack(chans, axis=-1)\n\ndef preprocess_image(img, target_size=640):\n    img_resized = cv2.resize(img, (target_size, target_size))\n    if img_resized.ndim == 2:\n        img_resized = cv2.cvtColor(img_resized, cv2.COLOR_GRAY2RGB)\n    else:\n        img_resized = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)\n    tensor = np.transpose(img_resized, (2,0,1)).astype(np.float32)/255.0\n    return torch.from_numpy(tensor).unsqueeze(0)\n\ndef sigmoid(x, T=1.5):\n    return 1/(1+np.exp(-x/T))\n\ndef tta_transforms(image):\n    transforms = [image]\n    transforms += [cv2.flip(image, 0), cv2.flip(image,1)]\n    transforms += [cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE), cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)]\n    transforms += [cv2.rotate(cv2.flip(image,1), cv2.ROTATE_90_CLOCKWISE)]\n    return transforms\n\ndef smooth_scores(scores_list, alpha=0.7):\n    smoothed = {label: 0.0 for label in LABEL_COLS}\n    for s in scores_list:\n        for l in LABEL_COLS:\n            smoothed[l] = alpha*smoothed[l] + (1-alpha)*s[l]\n    return smoothed\n\ndef normalize_scores(final_scores):\n    loc_labels = LABEL_COLS[:-1]\n    values = np.array([final_scores[l] for l in loc_labels])\n    norm_values = scipy.special.softmax(values)\n    norm_scores = {label: float(val) for label, val in zip(loc_labels, norm_values)}\n    norm_scores[\"Aneurysm Present\"] = max(norm_values.max(), final_scores[\"Aneurysm Present\"])\n    return norm_scores\n\n# ===================== PREDICTION =====================\ndef predict(series_path: str) -> pd.DataFrame:\n    try:\n        series_id = os.path.basename(series_path)\n        volume = process_dicom_series(series_path)\n        base_image = create_multichannel_img(volume)\n\n        scores_list = []\n\n        # TTA + Ensemble\n        for aug_img in tta_transforms(base_image):\n            # --- YOLOv8 ---\n            try:\n                img_tensor = preprocess_image(aug_img).to(device)\n                with torch.no_grad():\n                    outputs = yolo_model(img_tensor)\n                    if outputs is None or len(outputs) == 0:\n                        continue\n                    if isinstance(outputs, torch.Tensor):\n                        outputs = outputs.cpu().numpy()\n                    elif isinstance(outputs, (list,tuple)):\n                        outputs = [o.cpu().numpy() if isinstance(o, torch.Tensor) else o for o in outputs]\n                    else:\n                        continue\n            except:\n                continue\n\n            scores = {label: 0.0 for label in LABEL_COLS}\n            aneurysm_present = 0.0\n            for det in outputs[0]:\n                try:\n                    conf_prob = sigmoid(float(det[4]))\n                    cls_id = int(det[5])\n                    label = ID_TO_LABEL.get(cls_id, None)\n                    if label:\n                        scores[label] = max(scores[label], conf_prob)\n                        aneurysm_present = max(aneurysm_present, conf_prob)\n                except:\n                    continue\n            scores[\"Aneurysm Present\"] = max(aneurysm_present, max(scores.values()))\n            scores_list.append(scores)\n\n            # --- EfficientNet ---\n            try:\n                img_tensor_e = preprocess_image(aug_img, target_size=224).to(device)\n                with torch.no_grad():\n                    out = efficientnet_model(img_tensor_e)\n                    probs = torch.sigmoid(out).cpu().numpy()[0]\n                    for i, label in enumerate(LABEL_COLS[:-1]):\n                        scores[label] = max(scores[label], float(probs[i]))\n                    scores[\"Aneurysm Present\"] = max(scores[\"Aneurysm Present\"], max(probs))\n            except:\n                continue\n\n        # Smooth + normalize\n        if len(scores_list) == 0:\n            final_scores = {label:0.0 for label in LABEL_COLS}\n        else:\n            smoothed = smooth_scores(scores_list)\n            final_scores = normalize_scores(smoothed)\n\n        del volume, base_image, scores_list\n        gc.collect()\n\n        row = [series_id]+[round(float(final_scores[c]),6) for c in LABEL_COLS]\n        return pd.DataFrame([row], columns=[ID_COL,*LABEL_COLS])\n    except:\n        row = [os.path.basename(series_path)] + [0.0]*len(LABEL_COLS)\n        return pd.DataFrame([row], columns=[ID_COL,*LABEL_COLS])\n\n# ===================== SERVER / LOCAL =====================\nshared_dir = \"/kaggle/shared\"\nshutil.rmtree(shared_dir, ignore_errors=True)\nos.makedirs(shared_dir, exist_ok=True)\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    submission_path = \"/kaggle/working/submission.parquet\"\n    if os.path.exists(submission_path):\n        df = pd.read_parquet(submission_path)\n        df.to_csv(\"/kaggle/working/submission.csv\", index=False, float_format=\"%.6f\")\n        display(df.head())\"\"\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T12:55:04.730421Z","iopub.execute_input":"2025-10-02T12:55:04.730757Z","iopub.status.idle":"2025-10-02T12:55:50.718398Z","shell.execute_reply.started":"2025-10-02T12:55:04.730732Z","shell.execute_reply":"2025-10-02T12:55:50.717335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}