{"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":"# ============================================================\n# Cell 1: Install & Imports\n# ============================================================\nimport os, random, cv2, pydicom, torch, timm\nimport numpy as np\nimport pandas as pd\nimport torch.nn as nn\nimport albumentations as A\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom albumentations.pytorch import ToTensorV2\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    precision_score, recall_score, f1_score,\n    accuracy_score, confusion_matrix, classification_report\n)\nfrom tqdm import tqdm\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nSEED   = 42\nLABELS = ['epidural', 'intraparenchymal', 'intraventricular', 'subarachnoid', 'subdural']\nIMG_SIZE = 384\n\ndef seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n\nseed_everything(SEED)\nprint(f\"✅ Device: {DEVICE}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-11T13:39:11.528028Z","iopub.execute_input":"2026-04-11T13:39:11.528742Z","iopub.status.idle":"2026-04-11T13:39:26.107321Z","shell.execute_reply.started":"2026-04-11T13:39:11.528709Z","shell.execute_reply":"2026-04-11T13:39:26.106498Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Cell 2: Data Processing\n# ============================================================\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\")\nSAVE_DIR       = \"/kaggle/working/processed_data_v384_webp\"\nIMG_DIR        = os.path.join(SAVE_DIR, \"images\")\nos.makedirs(IMG_DIR, exist_ok=True)\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 = window_image(img, 40, 80)\n    ch2 = window_image(img, 80, 250)\n    ch3 = window_image(img, 600, 2800)\n    return np.stack([ch1, ch2, ch3], axis=-1).astype(np.float32)\n\n# Load & Balance Data\nprint(\"📊 Preparing Balanced Metadata...\")\ndf = pd.read_csv(LABELS_CSV)\nparts = df['ID'].str.split('_', expand=True)\ndf['Image'] = parts[0] + \"_\" + parts[1]\ndf['Type']  = parts[2]\ndf_pivot = df.pivot_table(index=\"Image\", columns=\"Type\", values=\"Label\", aggfunc=\"max\").fillna(0).reset_index()\nif 'any' in df_pivot.columns:\n    df_pivot = df_pivot.drop(columns=['any'])\ndf_pivot = df_pivot[['Image'] + LABELS]\n\nfinal_list = []\nfinal_list.append(df_pivot[df_pivot['epidural'] == 1])\n\nfor label in ['subdural', 'subarachnoid']:\n    type_df = df_pivot[(df_pivot[label] == 1) & (df_pivot['epidural'] == 0)]\n    final_list.append(type_df.sample(n=min(12000, len(type_df)), random_state=SEED))\n\nfor label in ['intraventricular', 'intraparenchymal']:\n    type_df = df_pivot[(df_pivot[label] == 1) & (df_pivot['epidural'] == 0)]\n    final_list.append(type_df.sample(n=min(8000, len(type_df)), random_state=SEED))\n\nnormal_cases = df_pivot[df_pivot[LABELS].sum(axis=1) == 0].sample(n=40000, random_state=SEED)\nfinal_list.append(normal_cases)\n\ndf_final = pd.concat(final_list).drop_duplicates(subset='Image').sample(frac=1, random_state=SEED).reset_index(drop=True)\ntrain_df, val_df = train_test_split(df_final, test_size=0.10, random_state=SEED)\n\naugmenter_save_only = A.Compose([\n    A.HorizontalFlip(p=0.5),\n    A.Affine(translate_percent={\"x\": (-0.1, 0.1), \"y\": (-0.1, 0.1)},\n             scale=(0.9, 1.1), rotate=(-15, 15), p=0.8),\n])\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    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        img_stacked  = cv2.resize(get_medical_stack(img), (IMG_SIZE, IMG_SIZE))\n        img_to_save  = (img_stacked * 255).astype(np.uint8)\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        if is_train and row['epidural'] == 1:\n            for i in range(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        return results\n    except:\n        return []\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: 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: final_val_meta.extend(res)\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✅ Done! Total images: {len(os.listdir(IMG_DIR))}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-11T13:39:36.614961Z","iopub.execute_input":"2026-04-11T13:39:36.615982Z","iopub.status.idle":"2026-04-11T14:40:03.219636Z","shell.execute_reply.started":"2026-04-11T13:39:36.615945Z","shell.execute_reply":"2026-04-11T14:40:03.218736Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Cell 3: Model Architecture & Load\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\nMODEL_FILE = \"/kaggle/input/models/bassel1221/convnext-v2-model/pytorch/default/1/ConvNeXt_V2_Model.pth\"\n\nprint(\"📦 Loading model...\")\nmodel = CustomModel('convnextv2_base.fcmae_ft_in22k_in1k_384').to(DEVICE)\ncheckpoint = torch.load(MODEL_FILE, map_location=DEVICE, weights_only=False)\nmodel.load_state_dict(checkpoint['model_state_dict'])\nmodel.eval()\n\nTHRESHOLDS = checkpoint.get('thresholds', [0.5] * 5)\nprint(f\"✅ Model loaded!\")\nprint(f\"📊 Thresholds: {THRESHOLDS}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-11T14:43:31.974161Z","iopub.execute_input":"2026-04-11T14:43:31.974976Z","iopub.status.idle":"2026-04-11T14:43:41.511352Z","shell.execute_reply.started":"2026-04-11T14:43:31.974943Z","shell.execute_reply":"2026-04-11T14:43:41.510343Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Cell 4: Dataset & Evaluation\n# ============================================================\nclass ICHDataset(Dataset):\n    def __init__(self, csv_file, img_dir, transform=None):\n        self.data = pd.read_csv(csv_file)\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self): return len(self.data)\n\n    def __getitem__(self, idx):\n        row = self.data.iloc[idx]\n        img_path = os.path.join(self.img_dir, row['file_path'])\n        image = cv2.imread(img_path)\n        if image is None:\n            return torch.zeros((3, IMG_SIZE, IMG_SIZE)), torch.zeros(5)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        if self.transform:\n            image = self.transform(image=image)['image']\n        label = torch.tensor(row[LABELS].values.astype(float), dtype=torch.float32)\n        return image, label\n\nval_transform = A.Compose([\n    A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    ToTensorV2()\n])\n\nVAL_CSV = os.path.join(SAVE_DIR, \"val_ready.csv\")\nval_dataset = ICHDataset(VAL_CSV, IMG_DIR, val_transform)\nval_loader  = DataLoader(val_dataset, batch_size=16, shuffle=False, num_workers=2)\nprint(f\"✅ Validation samples: {len(val_dataset)}\")\n\n# ============================================================\n# Evaluation\n# ============================================================\nall_preds  = []\nall_labels = []\nthreshold_arr = np.array(THRESHOLDS)\n\nprint(\"🔍 Running evaluation...\")\nwith torch.no_grad():\n    for images, labels_batch in tqdm(val_loader, desc=\"Evaluating\"):\n        images = images.to(DEVICE)\n        outputs = model(images)\n        probs   = torch.sigmoid(outputs).cpu().numpy()\n        all_preds.append(probs)\n        all_labels.append(labels_batch.numpy())\n\nall_preds  = np.vstack(all_preds)\nall_labels = np.vstack(all_labels)\nbinary_preds = (all_preds >= threshold_arr).astype(int)\n\n# ============================================================\n# Metrics\n# ============================================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"📊 EVALUATION RESULTS\")\nprint(\"=\"*60)\n\nfor i, label in enumerate(LABELS):\n    acc  = accuracy_score(all_labels[:, i], binary_preds[:, i])\n    prec = precision_score(all_labels[:, i], binary_preds[:, i], zero_division=0)\n    rec  = recall_score(all_labels[:, i], binary_preds[:, i], zero_division=0)\n    f1   = f1_score(all_labels[:, i], binary_preds[:, i], zero_division=0)\n    print(f\"\\n🔹 {label.upper()}\")\n    print(f\"   Accuracy : {acc*100:.2f}%\")\n    print(f\"   Precision: {prec*100:.2f}%\")\n    print(f\"   Recall   : {rec*100:.2f}%\")\n    print(f\"   F1-Score : {f1*100:.2f}%\")\n\nprint(\"\\n\" + \"=\"*60)\nprint(classification_report(all_labels, binary_preds,\n                             target_names=LABELS, zero_division=0))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-11T14:43:47.537103Z","iopub.execute_input":"2026-04-11T14:43:47.53797Z","iopub.status.idle":"2026-04-11T14:49:43.210379Z","shell.execute_reply.started":"2026-04-11T14:43:47.537936Z","shell.execute_reply":"2026-04-11T14:49:43.209378Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Cell 5: Confusion Matrix\n# ============================================================\nos.makedirs(\"/kaggle/working/results\", exist_ok=True)\n\nfig, axes = plt.subplots(2, 3, figsize=(18, 12))\naxes = axes.flatten()\n\nfor i, label in enumerate(LABELS):\n    cm = confusion_matrix(all_labels[:, i], binary_preds[:, i])\n    sns.heatmap(cm, annot=True, fmt='d', ax=axes[i],\n                cmap='Blues',\n                xticklabels=['Normal', label],\n                yticklabels=['Normal', label])\n    axes[i].set_title(f'Confusion Matrix - {label}', fontsize=13)\n    axes[i].set_ylabel('Actual')\n    axes[i].set_xlabel('Predicted')\n\naxes[-1].set_visible(False)\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/results/confusion_matrices.png\", dpi=150, bbox_inches='tight')\nplt.show()\nprint(\"✅ Confusion matrices saved!\")\n\n# ============================================================\n# Save Metrics CSV\n# ============================================================\nmetrics_data = []\nfor i, label in enumerate(LABELS):\n    metrics_data.append({\n        'Label'    : label,\n        'Accuracy' : round(accuracy_score(all_labels[:, i], binary_preds[:, i]) * 100, 2),\n        'Precision': round(precision_score(all_labels[:, i], binary_preds[:, i], zero_division=0) * 100, 2),\n        'Recall'   : round(recall_score(all_labels[:, i], binary_preds[:, i], zero_division=0) * 100, 2),\n        'F1_Score' : round(f1_score(all_labels[:, i], binary_preds[:, i], zero_division=0) * 100, 2),\n    })\n\nmetrics_df = pd.DataFrame(metrics_data)\nmetrics_df.to_csv(\"/kaggle/working/results/metrics.csv\", index=False)\nprint(\"✅ Metrics saved!\")\nprint(metrics_df.to_string(index=False))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-11T14:57:21.261753Z","iopub.execute_input":"2026-04-11T14:57:21.262509Z","iopub.status.idle":"2026-04-11T14:57:23.378052Z","shell.execute_reply.started":"2026-04-11T14:57:21.262469Z","shell.execute_reply":"2026-04-11T14:57:23.377342Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Cell 6: CT Showcase\n# ============================================================\nimport textwrap\n\ndef run_ct_showcase(model, dataset, device, num_samples=10):\n    full_names = ['Epidural', 'Intraparenchymal', 'Intraventricular', 'Subarachnoid', 'Subdural']\n    thresholds = np.array(THRESHOLDS)\n    model.eval()\n\n    diverse_indices = []\n    found_classes   = set()\n    search_range    = list(range(len(dataset)))\n    random.shuffle(search_range)\n\n    for idx in search_range:\n        _, labels_t = dataset[idx]\n        for c_idx, val in enumerate(labels_t):\n            if val == 1 and c_idx not in found_classes:\n                diverse_indices.append(idx)\n                found_classes.add(c_idx)\n        if len(diverse_indices) >= 5: break\n\n    for idx in search_range:\n        if len(diverse_indices) >= num_samples: break\n        if idx not in diverse_indices:\n            diverse_indices.append(idx)\n\n    random.shuffle(diverse_indices)\n\n    fig, axes = plt.subplots(2, 5, figsize=(45, 25))\n    axes = axes.flatten()\n\n    for i, idx in enumerate(diverse_indices):\n        image, labels_t = dataset[idx]\n        input_tensor = image.unsqueeze(0).to(device)\n\n        with torch.no_grad():\n            outputs = model(input_tensor)\n            probs   = torch.sigmoid(outputs).cpu().numpy()[0]\n            preds   = (probs >= thresholds).astype(int)\n\n        img_raw = image[0].cpu().numpy()\n        p_low, p_high = np.percentile(img_raw, (1, 99))\n        img_ct = np.clip((img_raw - p_low) / (p_high - p_low + 1e-8), 0, 1)\n\n        label_np     = labels_t.numpy()\n        actual_names = [full_names[j] for j, v in enumerate(label_np) if v == 1]\n        actual_txt   = \", \".join(actual_names) if actual_names else \"NORMAL\"\n\n        pred_names    = [full_names[j] for j, v in enumerate(preds) if v == 1]\n        ai_decision   = \", \".join(pred_names) if pred_names else \"NORMAL\"\n\n        is_correct    = np.array_equal(label_np, preds)\n        status_color  = '#2E7D32' if is_correct else '#C62828'\n\n        axes[i].imshow(img_ct, cmap='gray')\n        axes[i].set_title(f\"SCAN ID: #{idx}\", fontsize=22, fontweight='bold',\n                          color=status_color, pad=25)\n        axes[i].axis('off')\n\n        wrapped_reality = \"\\n\".join(textwrap.wrap(f\"REALITY: {actual_txt}\", width=32))\n        wrapped_pred    = \"\\n\".join(textwrap.wrap(f\"AI DECISION: {ai_decision}\", width=32))\n\n        info_text  = f\"{wrapped_reality}\\n{wrapped_pred}\\n\" + \"-\"*34 + \"\\n\"\n        info_text += \"CONFIDENCE & THRESHOLDS:\\n\"\n        for j, name in enumerate(full_names):\n            p, t   = probs[j] * 100, thresholds[j] * 100\n            marker = \"●\" if preds[j] == 1 else \"○\"\n            info_text += f\"{marker} {name:16}: {p:4.1f}% (T={t:2.0f})\\n\"\n\n        axes[i].text(0.5, -0.08, info_text, transform=axes[i].transAxes,\n                     fontsize=14, family='monospace', fontweight='bold',\n                     ha='center', va='top', linespacing=1.4,\n                     bbox=dict(facecolor='#ffffff', alpha=1.0,\n                               edgecolor=status_color, linewidth=3,\n                               boxstyle='round,pad=0.8'))\n\n    plt.subplots_adjust(hspace=0.9, wspace=0.3)\n    plt.suptitle(\"Clinical Accuracy Showcase: CT Hemorrhage Detection Report\",\n                 fontsize=40, y=0.98, fontweight='bold', color='black')\n    plt.savefig('/kaggle/working/results/ct_showcase.png',\n                bbox_inches='tight', dpi=150)\n    plt.show()\n    print(\"✅ CT Showcase saved!\")\n\nrun_ct_showcase(model, val_dataset, DEVICE, num_samples=10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-11T14:57:31.947145Z","iopub.execute_input":"2026-04-11T14:57:31.947838Z","iopub.status.idle":"2026-04-11T14:57:40.749396Z","shell.execute_reply.started":"2026-04-11T14:57:31.947807Z","shell.execute_reply":"2026-04-11T14:57:40.748511Z"}},"outputs":[],"execution_count":null}]}