{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":13451,"datasetId":654585,"databundleVersionId":1188070},{"sourceType":"modelInstanceVersion","sourceId":816451,"databundleVersionId":16424261,"modelInstanceId":620100,"modelId":631925}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport random\nimport cv2\nimport pydicom\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# ============================================================\n# 1. الإعدادات والمسارات (Paths & Config)\n# ============================================================\nSEED = 42\n# تم التعديل لـ 384 لضمان السرعة وعدم تجمد كاجل مع WebP\nIMG_SIZE = 384  \n\n# المسارات (تأكد من صحتها في Kaggle)\nINPUT_DIR = \"/kaggle/input/competitions/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection\"\nTRAIN_DICOM_PATH = os.path.join(INPUT_DIR, \"stage_2_train\")\nLABELS_CSV = os.path.join(INPUT_DIR, \"stage_2_train.csv\")\n\n# مسار الحفظ الجديد (WebP)\nSAVE_DIR = \"/kaggle/working/processed_data_v384_webp\"\nIMG_DIR = os.path.join(SAVE_DIR, \"images\")\nos.makedirs(IMG_DIR, exist_ok=True)\n\n# Labels\nLABELS = ['epidural', 'intraparenchymal', 'intraventricular', 'subarachnoid', 'subdural']\n\n# ============================================================\n# 2. Seeding\n# ============================================================\ndef seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n\nseed_everything(SEED)\n\n# ============================================================\n# 3. Windowing طبي احترافي (3 قنوات)\n# ============================================================\ndef window_image(img, center, width):\n    min_val = center - width // 2\n    max_val = center + width // 2\n    img = np.clip(img, min_val, max_val)\n    return (img - min_val) / (max_val - min_val)\n\ndef get_medical_stack(img):\n    # CH1: Brain (40, 80)\n    ch1 = window_image(img, 40, 80)\n    # CH2: Subdural (80, 250)\n    ch2 = window_image(img, 80, 250)\n    # CH3: Bone (600, 2800)\n    ch3 = window_image(img, 600, 2800)\n    stacked = np.stack([ch1, ch2, ch3], axis=-1)\n    return stacked.astype(np.float32)\n\n# ============================================================\n# 5. تحضير وتوازن البيانات\n# ============================================================\nprint(\"📊 Preparing Balanced Metadata...\")\ndf = pd.read_csv(LABELS_CSV)\n\n# تفكيك الـ ID\nparts = df['ID'].str.split('_', expand=True)\ndf['Image'] = parts[0] + \"_\" + parts[1]\ndf['Type'] = parts[2]\n\n# عمل الـ Pivot\ndf_pivot = df.pivot_table(index=\"Image\", columns=\"Type\", values=\"Label\", aggfunc=\"max\").fillna(0).reset_index()\n\nif 'any' in df_pivot.columns:\n    df_pivot = df_pivot.drop(columns=['any'])\n\ndf_pivot = df_pivot[['Image'] + LABELS]\n\nfinal_list = []\n\n# ============================================================\n# 5. تحضير وتوازن البيانات (نسخة الـ 500 صورة فقط)\n# ============================================================\nfinal_list = []\n\n# سحب 80 صورة من كل نوع من أنواع النزيف الخمسة (المجموع 400)\nfor label in LABELS:\n    type_df = df_pivot[df_pivot[label] == 1]\n    final_list.append(type_df.sample(n=min(80, len(type_df)), random_state=SEED))\n\n# إضافة 100 صورة سليمة (Normal) ليكتمل المجموع إلى 500\nnormal_cases = df_pivot[df_pivot[LABELS].sum(axis=1) == 0].sample(n=100, random_state=SEED)\nfinal_list.append(normal_cases)\n\n# الدمج النهائي والتأكد من عدم تخطي الـ 500\ndf_final = pd.concat(final_list).drop_duplicates(subset='Image').head(500).sample(frac=1, random_state=SEED).reset_index(drop=True)\n\n# التقسيم (بنسبة 10% للفحص كما في كودك الأصلي)\ntrain_df, val_df = train_test_split(df_final, test_size=0.10, random_state=SEED)\n\n# ============================================================\n# 6. وظيفة المعالجة والحفظ\n# ============================================================\ndef process_and_save(row, is_train=True):\n    img_id = row['Image'] \n    dcm_path = os.path.join(TRAIN_DICOM_PATH, f\"{img_id}.dcm\")\n    results = []\n    \n    try:\n        dcm = pydicom.dcmread(dcm_path)\n        img = dcm.pixel_array.astype(np.float32)\n        img = img * getattr(dcm, \"RescaleSlope\", 1) + getattr(dcm, \"RescaleIntercept\", 0)\n        \n        # 1. Windowing & Resize\n        img_stacked = cv2.resize(get_medical_stack(img), (IMG_SIZE, IMG_SIZE))\n        \n        # 2. تحويل لـ uint8 (0-255) للحفظ\n        img_to_save = (img_stacked * 255).astype(np.uint8)\n        \n        # 3. حفظ WebP (سرعة وجودة)\n        fname = f\"{img_id}.webp\"\n        cv2.imwrite(os.path.join(IMG_DIR, fname), img_to_save, [int(cv2.IMWRITE_WEBP_QUALITY), 95])\n        results.append({**row.to_dict(), 'file_path': fname})\n        \n        # 4. Augmentation للـ EPI (نادرة)\n        if is_train and row['epidural'] == 1:\n            for i in range(3): # قللنا لـ 3 لتقليل وقت المعالجة وتجنب التجمد\n                aug_img = augmenter_save_only(image=img_to_save)['image'] \n                aug_fname = f\"{img_id}_epi_aug_{i}.webp\"\n                cv2.imwrite(os.path.join(IMG_DIR, aug_fname), aug_img, [int(cv2.IMWRITE_WEBP_QUALITY), 95])\n                results.append({**row.to_dict(), 'file_path': aug_fname})\n                \n        return results\n    except Exception as e:\n        return []\n\n# Augmenter مبسط لتقليل حمل الـ CPU\naugmenter_save_only = A.Compose([\n    A.HorizontalFlip(p=0.5),\n    A.Affine(\n    translate_percent={\"x\": (-0.1, 0.1), \"y\": (-0.1, 0.1)},\n    scale=(0.9, 1.1),\n    rotate=(-15, 15),\n    p=0.8\n    ),\n])\n\n# ============================================================\n# 7. التنفيذ\n# ============================================================\nfinal_train_meta = []\nprint(f\"\\n🚀 Processing {len(train_df)} Training Images...\")\nfor _, row in tqdm(train_df.iterrows(), total=len(train_df)):\n    res = process_and_save(row, is_train=True)\n    if res:\n        final_train_meta.extend(res)\n\nfinal_val_meta = []\nprint(f\"\\n🧪 Processing {len(val_df)} Validation Images...\")\nfor _, row in tqdm(val_df.iterrows(), total=len(val_df)):\n    res = process_and_save(row, is_train=False)\n    if res:\n        final_val_meta.extend(res)\n\n# ============================================================\n# 8. حفظ CSV النهائي\n# ============================================================\npd.DataFrame(final_train_meta).to_csv(os.path.join(SAVE_DIR, \"train_ready.csv\"), index=False)\npd.DataFrame(final_val_meta).to_csv(os.path.join(SAVE_DIR, \"val_ready.csv\"), index=False)\n\nprint(f\"\\n✅ SUCCESS!\")\nprint(f\"Total Images in Folder: {len(os.listdir(IMG_DIR))}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-04-27T20:44:42.202737Z","iopub.execute_input":"2026-04-27T20:44:42.203577Z","iopub.status.idle":"2026-04-27T20:45:21.706564Z","shell.execute_reply.started":"2026-04-27T20:44:42.203543Z","shell.execute_reply":"2026-04-27T20:45:21.705685Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torch\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport pydicom\nimport timm\nimport torch.nn as nn\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport matplotlib.pyplot as plt\n\n# ============================================================\n# 1. هيكل الموديل (CustomModel)\n# ============================================================\nclass CustomModel(nn.Module):\n    def __init__(self, model_name, num_classes=5):\n        super().__init__()\n        self.model = timm.create_model(model_name, pretrained=False, num_classes=0, global_pool='avg')\n        n_features = self.model.num_features\n        self.head = nn.Sequential(\n            nn.Linear(n_features, 512), nn.BatchNorm1d(512), nn.GELU(),\n            nn.Dropout(0.3), nn.Linear(512, num_classes)\n        )\n    def forward(self, x): return self.head(self.model(x))\n\n# ============================================================\n# 2. المحرك الرئيسي (Inference Engine)\n# ============================================================\nclass RSNAInference:\n    def __init__(self, model_path, model_name='convnextv2_base.fcmae_ft_in22k_in1k_384', device='cuda'):\n        self.device = torch.device(device if torch.cuda.is_available() else 'cpu')\n        self.labels = ['epidural', 'intraparenchymal', 'intraventricular', 'subarachnoid', 'subdural']\n        self.img_size = 384\n        \n        self.model = CustomModel(model_name).to(self.device)\n        checkpoint = torch.load(model_path, map_location=self.device, weights_only=False)\n        self.model.load_state_dict(checkpoint['model_state_dict'])\n        self.model.eval()\n        \n        self.thresholds = checkpoint.get('thresholds', [0.5] * 5)\n        \n        self.transform = A.Compose([\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n            ToTensorV2()\n        ])\n\n    def predict_from_webp(self, webp_path):\n        image = cv2.imread(webp_path)\n        if image is None: raise ValueError(f\"Image not found: {webp_path}\")\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        input_tensor = self.transform(image=image)['image'].unsqueeze(0).to(self.device)\n        \n        with torch.no_grad():\n            logits = self.model(input_tensor)\n            probs = torch.sigmoid(logits).cpu().numpy()[0]\n            \n        decisions = {label: (prob > thr) for label, prob, thr in zip(self.labels, probs, self.thresholds)}\n        return probs, decisions\n\n    def show_final_report(self, webp_path, df_labels):\n        # 1. التوقعات\n        probs, decisions = self.predict_from_webp(webp_path)\n        img_id = os.path.basename(webp_path).replace('.webp', '')\n        \n        # 2. الحقيقة\n        real_row = df_labels[df_labels['Image'] == img_id]\n        if real_row.empty:\n            print(\"⚠️ ID not in CSV\"); return\n        \n        # 3. الصورة الطبيعية (Grayscale)\n        processed_img = cv2.imread(webp_path)\n        natural_ct = processed_img[:, :, 0]\n        \n        # 4. العرض المرئي\n        plt.figure(figsize=(10, 8))\n        plt.imshow(natural_ct, cmap='gray')\n        plt.axis('off')\n        plt.title(f\"Scan ID: {img_id}\\nNatural View\", fontsize=12, pad=15)\n        \n        # === تحضير النصوص (بدون إيموجي لتجنب الـ Error) ===\n        active_real = [L.upper() for L in self.labels if real_row.iloc[0][L] == 1]\n        reality_str = \" + \".join(active_real) if active_real else \"NORMAL\"\n        \n        active_pred = [L.upper() for L in self.labels if decisions[L]]\n        pred_str = \" + \".join(active_pred) if active_pred else \"NORMAL\"\n        \n        is_correct = (set(active_real) == set(active_pred))\n        match_status = \"[ MATCH: OK ]\" if is_correct else \"[ MATCH: ERROR ]\"\n        status_color = 'green' if is_correct else 'red'\n        \n        # ملخص التوقعات السطري\n        summary_text = (\n            f\"REALITY:        {reality_str}\\n\"\n            f\"MODEL DECISION: {pred_str}\\n\"\n            f\"STATUS:         {match_status}\\n\"\n            + \"-\"*60 + \"\\n\"\n            f\"{'Type':<18} | {'Prob':<8} | {'Thr':<8} | {'Result'}\"\n        )\n        \n        # إضافة تفاصيل الـ Probabilities و الـ Thresholds لكل نوع\n        detail_lines = []\n        for i, label in enumerate(self.labels):\n            p = probs[i]\n            thr = self.thresholds[i]\n            res = \"POS\" if decisions[label] else \"neg\"\n            detail_lines.append(f\"{label.upper():<18} | {p:.4f} | {thr:.4f} | {res}\")\n            \n        final_text = summary_text + \"\\n\" + \"\\n\".join(detail_lines)\n        \n        # عرض التقرير في المخطط\n        plt.figtext(0.5, -0.15, final_text, fontfamily='monospace', fontsize=10, \n                    ha='center', fontweight='bold',\n                    bbox={'facecolor':'white', 'alpha':1, 'pad':12, \n                          'edgecolor':status_color, 'linewidth':2})\n        \n        plt.show()\n\n# ============================================================\n# 3. التشغيل\n# ============================================================\nMODEL_FILE = \"/kaggle/input/models/bassel1221/convnext-v2-model/pytorch/default/1/ConvNeXt_V2_Model.pth\"\nCSV_PATH = \"/kaggle/working/processed_data_v384_webp/val_ready.csv\"\nTEST_IMAGE = \"/kaggle/working/processed_data_v384_webp/images/ID_776b8213a.webp\"\n\nif os.path.exists(MODEL_FILE) and os.path.exists(CSV_PATH):\n    engine = RSNAInference(model_path=MODEL_FILE)\n    val_df = pd.read_csv(CSV_PATH)\n    engine.show_final_report(TEST_IMAGE, val_df)\nelse:\n    print(\"❌ Files not found. Check paths.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-27T20:45:21.708006Z","iopub.execute_input":"2026-04-27T20:45:21.7087Z","iopub.status.idle":"2026-04-27T20:45:24.336411Z","shell.execute_reply.started":"2026-04-27T20:45:21.708668Z","shell.execute_reply":"2026-04-27T20:45:24.335413Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport cv2\nimport matplotlib.pyplot as plt\nimport os\n\n# 1. إعداد المسارات\nVAL_CSV = \"/kaggle/working/processed_data_v384_webp/val_ready.csv\"\nIMG_DIR = \"/kaggle/working/processed_data_v384_webp/images\"\n\n# 2. قراءة ملف الـ Validation\nval_df = pd.read_csv(VAL_CSV)\nlabels = ['epidural', 'intraparenchymal', 'intraventricular', 'subarachnoid', 'subdural']\n\n# 3. فلترة الحالات التي تحتوي على أكثر من نوع نزيف (Multi-bleed cases)\n# بنجمع الصفوف، ولو المجموع > 1 يبقى فيه أكتر من نوع\nmulti_bleed_df = val_df[val_df[labels].sum(axis=1) > 1].copy()\n\nprint(f\"📊 Total Multi-bleed images found: {len(multi_bleed_df)}\")\n\n# 4. عرض أول 5 حالات معقدة\nnum_samples = min(5, len(multi_bleed_df)) # حماية في حال كان العدد أقل من 5\nif num_samples > 0:\n    plt.figure(figsize=(20, 10))\n\n    for i in range(num_samples):\n        row = multi_bleed_df.iloc[i]\n        img_path = os.path.join(IMG_DIR, row['file_path'])\n        \n        # قراءة الصورة (WebP)\n        img = cv2.imread(img_path)\n        if img is None: continue\n        \n        # تحويل للشكل الطبيعي (Brain Window)\n        img_gray = img[:, :, 0] \n        \n        # معرفة الأنواع الموجودة فعلياً (Reality)\n        active_labels = [label.upper() for label in labels if row[label] == 1]\n        reality_text = \" + \\n\".join(active_labels)\n        \n        # الرسم\n        plt.subplot(1, num_samples, i+1)\n        plt.imshow(img_gray, cmap='gray')\n        plt.title(f\"REALITY (Multi):\\n{reality_text}\", fontsize=10, color='red', fontweight='bold')\n        plt.axis('off')\n\n    plt.tight_layout()\n    plt.show()\n\n    # عرض الجدول للحالات دي\n    print(\"\\n📋 Sample of Multi-bleed rows in CSV:\")\n    display(multi_bleed_df.head())\nelse:\n    print(\"❌ No images with multiple bleeds found in this set.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-27T20:45:24.337569Z","iopub.execute_input":"2026-04-27T20:45:24.337972Z","iopub.status.idle":"2026-04-27T20:45:24.936151Z","shell.execute_reply.started":"2026-04-27T20:45:24.337943Z","shell.execute_reply":"2026-04-27T20:45:24.935321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torch\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport timm\nimport torch.nn as nn\nimport matplotlib.pyplot as plt\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nclass CustomModel(nn.Module):\n    def __init__(self, model_name, num_classes=5):\n        super().__init__()\n        self.model = timm.create_model(model_name, pretrained=False, num_classes=0, global_pool='avg')\n        n_features = self.model.num_features\n        self.head = nn.Sequential(\n            nn.Linear(n_features, 512), nn.BatchNorm1d(512), nn.GELU(),\n            nn.Dropout(0.3), nn.Linear(512, num_classes)\n        )\n    def forward(self, x): return self.head(self.model(x))\n\nclass GradCAM:\n    def __init__(self, model, target_layer):\n        self.model = model\n        self.gradients = None\n        self.activations = None\n        target_layer.register_forward_hook(self._save_activation)\n        target_layer.register_full_backward_hook(self._save_gradient)\n\n    def _save_activation(self, module, input, output):\n        self.activations = output.detach()\n\n    def _save_gradient(self, module, grad_input, grad_output):\n        self.gradients = grad_output[0].detach()\n\n    def generate(self, input_tensor, class_idx):\n        self.model.zero_grad()\n        output = self.model(input_tensor)\n        output[0, class_idx].backward()\n        weights = self.gradients.mean(dim=[2, 3], keepdim=True)\n        cam = (weights * self.activations).sum(dim=1, keepdim=True)\n        cam = torch.relu(cam)\n        cam = cam.squeeze().cpu().numpy()\n        cam = cv2.resize(cam, (384, 384))\n        cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8)\n        return cam\n\ndef show_gradcam(model_path, webp_path, val_csv,\n                 model_name='convnextv2_base.fcmae_ft_in22k_in1k_384'):\n\n    LABELS = ['epidural', 'intraparenchymal', 'intraventricular', 'subarachnoid', 'subdural']\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Using device: {device}\")\n\n    model = CustomModel(model_name).to(device)\n    checkpoint = torch.load(model_path, map_location=device, weights_only=False)\n    model.load_state_dict(checkpoint['model_state_dict'])\n    model.eval()\n    thresholds = checkpoint.get('thresholds', [0.5] * 5)\n\n    target_layer = model.model.stages[-1].blocks[-1]\n    gradcam = GradCAM(model, target_layer)\n\n    transform = A.Compose([\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ])\n\n    raw_img = cv2.imread(webp_path)\n    img_rgb = cv2.cvtColor(raw_img, cv2.COLOR_BGR2RGB)\n\n    cams = []\n    for i in range(len(LABELS)):\n        input_tensor = transform(image=img_rgb)['image'].unsqueeze(0).to(device)\n        input_tensor.requires_grad_(True)\n        cam = gradcam.generate(input_tensor, class_idx=i)\n        cams.append(cam)\n\n    with torch.no_grad():\n        input_tensor = transform(image=img_rgb)['image'].unsqueeze(0).to(device)\n        probs = torch.sigmoid(model(input_tensor)).cpu().numpy()[0]\n\n    img_id = os.path.basename(webp_path).replace('.webp', '')\n    val_df = pd.read_csv(val_csv)\n    real_row = val_df[val_df['Image'] == img_id]\n    active_real = [L for L in LABELS if not real_row.empty and real_row.iloc[0][L] == 1]\n    active_pred = [L for L in LABELS if probs[LABELS.index(L)] > thresholds[LABELS.index(L)]]\n\n    brain_window = raw_img[:, :, 0]\n    gray_3ch = cv2.cvtColor(brain_window, cv2.COLOR_GRAY2RGB)\n\n    fig = plt.figure(figsize=(28, 11), facecolor='#0a0a0a')\n\n    # --- صورة CT الأصلية ---\n    ax0 = fig.add_subplot(2, 6, 1)\n    ax0.imshow(brain_window, cmap='gray')\n    ax0.set_title(f\"SCAN ID:\\n{img_id[-8:].upper()}\", color='white',\n                  fontsize=10, fontweight='bold', pad=8)\n    ax0.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)\n    for spine in ax0.spines.values():\n        spine.set_edgecolor('#00d2ff')\n        spine.set_linewidth(2)\n\n    # تقرير CT\n    ax0r = fig.add_subplot(2, 6, 7)\n    ax0r.axis('off')\n    actual_text = '\\n'.join([f\"  {L.upper()}\" for L in active_real]) if active_real else \"  NORMAL\"\n    focus_text  = '\\n'.join([f\"  {L.upper()}\" for L in active_pred]) if active_pred else \"  NONE\"\n    report = f\"ACTUAL:\\n{actual_text}\\n{'─'*22}\\nFOCUS ON:\\n{focus_text}\"\n    ax0r.text(0.05, 0.95, report, transform=ax0r.transAxes,\n              fontsize=8, verticalalignment='top', fontfamily='monospace',\n              color='white',\n              bbox=dict(facecolor='#1a1a2e', edgecolor='#00d2ff', linewidth=1.5, pad=6))\n\n    # --- Grad-CAM لكل class ---\n    for i, label in enumerate(LABELS):\n        prob   = probs[i]\n        thr    = thresholds[i]\n        is_pos = prob > thr\n\n        overlay  = gray_3ch.copy()\n        cam_norm = cams[i]\n        hot_mask = cam_norm > 0.4\n        heatmap  = cv2.applyColorMap((cam_norm * 255).astype(np.uint8), cv2.COLORMAP_JET)\n        heatmap_rgb = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)\n        overlay[hot_mask] = heatmap_rgb[hot_mask]\n\n        ax = fig.add_subplot(2, 6, i + 2)\n        ax.imshow(overlay)\n        ax.set_title(f\"{label.upper()}\", color='white',\n                     fontsize=10, fontweight='bold', pad=8)\n        ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)\n        for spine in ax.spines.values():\n            spine.set_edgecolor('#ff4757' if is_pos else '#444')\n            spine.set_linewidth(2.5 if is_pos else 1)\n\n        # تقرير تحت كل صورة\n        axr = fig.add_subplot(2, 6, i + 8)\n        axr.axis('off')\n\n        actual_for_label = label.upper() if label in active_real else 'NOT PRESENT'\n        focus_all = ', '.join([l.upper() for l in active_pred]) if active_pred else 'NONE'\n\n        # AI CONFIDENCE\n        conf_lines = []\n        for j, lbl in enumerate(LABELS):\n            p   = probs[j]\n            dot = '*' if p > thresholds[j] else 'o'\n            conf_lines.append(f\"  {dot} {lbl.capitalize():<18}: {p*100:.1f}%\")\n\n        report_text = (\n            f\"ACTUAL: {actual_for_label}\\n\"\n            f\"FOCUS ON: {focus_all}\\n\"\n            f\"{'─'*26}\\n\"\n            f\"AI CONFIDENCE:\\n\"\n            + '\\n'.join(conf_lines)\n        )\n\n        axr.text(0.05, 0.97, report_text, transform=axr.transAxes,\n                 fontsize=7.5, verticalalignment='top', fontfamily='monospace',\n                 color='white',\n                 bbox=dict(facecolor='#111122',\n                           edgecolor='#ff4757' if is_pos else '#444444',\n                           linewidth=1.5, pad=6))\n\n    plt.suptitle(\"Clinical Grad-CAM Analysis — RSNA Hemorrhage Detection\",\n                 color='white', fontsize=14, fontweight='bold', y=1.01)\n    plt.tight_layout()\n    plt.show()\n\n# ============================================================\n# التشغيل\n# ============================================================\nMODEL_FILE = \"/kaggle/input/models/bassel1221/convnext-v2-model/pytorch/default/1/ConvNeXt_V2_Model.pth\"\nVAL_CSV    = \"/kaggle/working/processed_data_v384_webp/val_ready.csv\"\nTEST_IMAGE = \"/kaggle/working/processed_data_v384_webp/images/ID_776b8213a.webp\"\n\nshow_gradcam(MODEL_FILE, TEST_IMAGE, VAL_CSV)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-27T20:45:24.938061Z","iopub.execute_input":"2026-04-27T20:45:24.93829Z","iopub.status.idle":"2026-04-27T20:45:29.074453Z","shell.execute_reply.started":"2026-04-27T20:45:24.938267Z","shell.execute_reply":"2026-04-27T20:45:29.07375Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}