{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","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}],"dockerImageVersionId":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport pydicom\nfrom tqdm import tqdm\n\nDATA_DIR    = '/kaggle/input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection'\nCSV_PATH    = os.path.join(DATA_DIR, 'stage_2_train.csv')\nDCM_DIR     = os.path.join(DATA_DIR, 'stage_2_train')\nPNG_ROOT    = '/kaggle/working/png_data'\nOUTPUT_DIR  = '/kaggle/working/results'\n\nfor d in [PNG_ROOT, OUTPUT_DIR]:\n    os.makedirs(d, exist_ok=True)\n\nBATCH_SIZE    = 16  \nNUM_EPOCHS    = 20  \nLEARNING_RATE = 5e-5 \nPATIENCE      = 5 \nDEVICE        = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nCLASS_NAMES   = ['epidural', 'subdural', 'subarachnoid', 'intraparenchymal', 'intraventricular']\nBALANCE_N     = 1000\n\ndef convert_dcm_to_png(dcm_path, png_path):\n    dcm = pydicom.dcmread(dcm_path)\n    img = dcm.pixel_array\n    def apply_window(image, center, width):\n        img = (image - (center - width / 2)) / width\n        return np.clip(img, 0, 1)\n    b, s, bo = apply_window(img, 40, 80), apply_window(img, 80, 200), apply_window(img, 600, 2800)\n    combined = (np.stack([b, s, bo], axis=-1) * 255).astype(np.uint8)\n    Image.fromarray(combined).save(png_path)\n\ndf = pd.read_csv(CSV_PATH).drop_duplicates()\ndf[['image','subtype']] = df['ID'].str.rsplit('_', n=1, expand=True)\ndf = df[df['subtype'] != 'any'].pivot(index='image', columns='subtype', values='Label').reset_index()\ndf[CLASS_NAMES] = df[CLASS_NAMES].fillna(0).astype(int)\n\npos_imgs = []\nfor cls in CLASS_NAMES:\n    cls_pos = df[df[cls] == 1]['image'].unique()\n    pos_imgs.extend(cls_pos[:min(BALANCE_N, len(cls_pos))])\npos_imgs = list(set(pos_imgs))\nneg_imgs = np.random.choice(df[df[CLASS_NAMES].sum(axis=1) == 0]['image'].unique(), len(pos_imgs), replace=False)\nselected = list(pos_imgs) + list(neg_imgs)\n\nfor img_id in tqdm(selected, desc=\"Converting\"):\n    dcm_p, png_p = os.path.join(DCM_DIR, img_id + '.dcm'), os.path.join(PNG_ROOT, img_id + '.png')\n    if os.path.exists(dcm_p) and not os.path.exists(png_p): convert_dcm_to_png(dcm_p, png_p)\n\nbalanced_df = df[df['image'].isin(selected)]\ntrain_df, val_df = train_test_split(balanced_df, test_size=0.2, random_state=42)\n\npos_counts = torch.tensor(train_df[CLASS_NAMES].sum().values, dtype=torch.float32)\nneg_counts = len(train_df) - pos_counts\nclass_weights = (neg_counts / (pos_counts + 1e-6)).to(DEVICE)\n\nclass MultiHemoDataset(Dataset):\n    def __init__(self, df, root, tf):\n        self.df, self.root, self.tf = df.reset_index(drop=True), root, tf\n    def __len__(self): return len(self.df)\n    def __getitem__(self, i):\n        r = self.df.iloc[i]\n        img = Image.open(os.path.join(self.root, r['image'] + '.png')).convert('RGB')\n        img = self.tf(img)\n        lbl = torch.from_numpy(r[CLASS_NAMES].astype(np.float32).to_numpy())\n        return img, lbl\n\ntf_train = transforms.Compose([\n    transforms.Resize((224, 224)), \n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(15),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\ntf_val = transforms.Compose([\n    transforms.Resize((224, 224)), \n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\ntrain_loader = DataLoader(MultiHemoDataset(train_df, PNG_ROOT, tf_train), batch_size=BATCH_SIZE, shuffle=True)\nval_loader = DataLoader(MultiHemoDataset(val_df, PNG_ROOT, tf_val), batch_size=BATCH_SIZE, shuffle=False)\n\ndef get_model(arch, nc):\n    if arch == 'vgg16':\n        m = models.vgg16(weights=models.VGG16_Weights.IMAGENET1K_V1)\n        m.classifier[6] = nn.Linear(m.classifier[6].in_features, nc)\n    elif arch == 'vgg19':\n        m = models.vgg19(weights=models.VGG19_Weights.IMAGENET1K_V1)\n        m.classifier[6] = nn.Linear(m.classifier[6].in_features, nc)\n    elif arch == 'resnet50':\n        m = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)\n        m.fc = nn.Linear(m.fc.in_features, nc)\n    elif arch == 'densenet121':\n        m = models.densenet121(weights=models.DenseNet121_Weights.IMAGENET1K_V1)\n        m.classifier = nn.Linear(m.classifier.in_features, nc)\n    return m.to(DEVICE)\n\ndef run_experiment(arch):\n    print(f\"\\nSTARTING: {arch}\")\n    model = get_model(arch, len(CLASS_NAMES))\n    crit = nn.BCEWithLogitsLoss(pos_weight=class_weights)\n    val_crit = nn.BCEWithLogitsLoss()\n    opt = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE, weight_decay=1e-4)\n    sched = torch.optim.lr_scheduler.ReduceLROnPlateau(opt, mode='min', factor=0.5, patience=2)\n\n    best_l, count, h = np.inf, 0, {'t': [], 'v': []}\n\n    for ep in range(1, NUM_EPOCHS + 1):\n        model.train()\n        tl = 0\n        for x, y in train_loader:\n            x, y = x.to(DEVICE), y.to(DEVICE)\n            opt.zero_grad()\n            l = crit(model(x), y)\n            l.backward()\n            opt.step()\n            tl += l.item() * x.size(0)\n        \n        model.eval()\n        vl = 0\n        with torch.no_grad():\n            for x, y in val_loader:\n                x, y = x.to(DEVICE), y.to(DEVICE)\n                vl += val_crit(model(x), y).item() * x.size(0)\n        \n        atl, avl = tl/len(train_df), vl/len(val_df)\n        h['t'].append(atl); h['v'].append(avl)\n        sched.step(avl)\n        print(f\"Ep {ep} - Train Loss: {atl:.4f}, Val Loss: {avl:.4f} | LR: {opt.param_groups[0]['lr']:.7f}\")\n        \n        if avl < best_l:\n            best_l, count = avl, 0\n            torch.save(model.state_dict(), f'best_{arch}.pth')\n        else:\n            count += 1\n            if count >= PATIENCE: \n                print(\"Early stopping triggered.\")\n                break\n\n    plt.figure()\n    plt.plot(h['t'], label='Train Loss')\n    plt.plot(h['v'], label='Val Loss')\n    plt.title(f'{arch} Training History')\n    plt.legend()\n    plt.savefig(os.path.join(OUTPUT_DIR, f'{arch}_loss_plot.png'))\n    plt.show()\n\n    model.load_state_dict(torch.load(f'best_{arch}.pth', weights_only=True))\n    model.eval()\n    probs, true = [], []\n    with torch.no_grad():\n        for x, y in val_loader:\n            probs.extend(torch.sigmoid(model(x.to(DEVICE))).cpu().numpy())\n            true.extend(y.numpy())\n    \n    preds = (np.array(probs) >= 0.5).astype(int)\n    print(f\"\\nClassification Report for {arch}:\")\n    print(classification_report(true, preds, target_names=CLASS_NAMES, zero_division=0))\n    for i, c in enumerate(CLASS_NAMES):\n        print(f\"Confusion Matrix for {c}:\")\n        print(confusion_matrix(np.array(true)[:, i], preds[:, i]))\n\nfor a in ['vgg16', 'vgg19', 'resnet50', 'densenet121']:\n    run_experiment(a)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-21T08:47:25.447555Z","iopub.execute_input":"2025-12-21T08:47:25.448103Z","iopub.status.idle":"2025-12-21T12:26:45.306787Z","shell.execute_reply.started":"2025-12-21T08:47:25.448077Z","shell.execute_reply":"2025-12-21T12:26:45.306021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nclass_names = ['epidural', 'subdural', 'subarachnoid', 'intraparenchymal', 'intraventricular']\n\nall_cms = {\n    'vgg16': {\n        'epidural': [[1229, 152], [39, 166]], 'subdural': [[1033, 272], [102, 179]],\n        'subarachnoid': [[947, 389], [72, 178]], 'intraparenchymal': [[1107, 219], [68, 192]],\n        'intraventricular': [[1247, 131], [38, 170]]\n    },\n    'vgg19': {\n        'epidural': [[1273, 108], [53, 152]], 'subdural': [[948, 357], [74, 207]],\n        'subarachnoid': [[1012, 324], [94, 156]], 'intraparenchymal': [[1099, 227], [53, 207]],\n        'intraventricular': [[1267, 111], [47, 161]]\n    },\n    'resnet50': {\n        'epidural': [[1074, 307], [42, 163]], 'subdural': [[1143, 162], [158, 123]],\n        'subarachnoid': [[1065, 271], [118, 132]], 'intraparenchymal': [[1206, 120], [96, 164]],\n        'intraventricular': [[1298, 80], [61, 147]]\n    },\n    'densenet121': {\n        'epidural': [[1278, 103], [41, 164]], 'subdural': [[1142, 163], [140, 141]],\n        'subarachnoid': [[1123, 213], [104, 146]], 'intraparenchymal': [[1185, 141], [64, 196]],\n        'intraventricular': [[1307, 71], [39, 169]]\n    }\n}\n\nf1_scores = {'vgg16': 0.55, 'vgg19': 0.56, 'resnet50': 0.51, 'densenet121': 0.60}\n\ndef calculate_model_metrics(model_name, model_data):\n    class_accuracies = []\n    for cls in class_names:\n        tn, fp = model_data[cls][0]\n        fn, tp = model_data[cls][1]\n        # Accuracy = (Correct Predictions) / (Total Predictions)\n        acc = (tp + tn) / (tp + tn + fp + fn)\n        class_accuracies.append(acc)\n    \n    avg_acc = np.mean(class_accuracies)\n    return avg_acc\n\nresults = []\nfor model_name in all_cms.keys():\n    acc = calculate_model_metrics(model_name, all_cms[model_name])\n    results.append({\n        'Model': model_name,\n        'Hamming Accuracy': acc,\n        'Weighted F1': f1_scores[model_name]\n    })\n\ndf_results = pd.DataFrame(results)\nprint(\"--- FULL MODEL PERFORMANCE TABLE ---\")\nprint(df_results)\n\nplt.figure(figsize=(12, 7))\nx = np.arange(len(df_results['Model']))\nwidth = 0.35\n\nplt.bar(x - width/2, df_results['Hamming Accuracy'], width, label='Hamming Accuracy', color='#3498db')\nplt.bar(x + width/2, df_results['Weighted F1'], width, label='Weighted F1-Score', color='#e74c3c')\n\nplt.xlabel('Model Architecture', fontsize=12, fontweight='bold')\nplt.ylabel('Score (0.0 - 1.0)', fontsize=12, fontweight='bold')\nplt.title('Hemorrhage Detection: Model Comparison (Accuracy vs F1)', fontsize=14)\nplt.xticks(x, df_results['Model'], fontweight='bold')\nplt.legend()\n\nfor i, val in enumerate(df_results['Hamming Accuracy']):\n    plt.text(i - width/2, val + 0.01, f'{val:.3f}', ha='center', va='bottom', fontsize=10)\nfor i, val in enumerate(df_results['Weighted F1']):\n    plt.text(i + width/2, val + 0.01, f'{val:.3f}', ha='center', va='bottom', fontsize=10)\n\nplt.grid(axis='y', linestyle='--', alpha=0.6)\nplt.tight_layout()\nplt.savefig('all_models_comparison.png')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-21T12:39:26.56005Z","iopub.execute_input":"2025-12-21T12:39:26.560372Z","iopub.status.idle":"2025-12-21T12:39:26.912564Z","shell.execute_reply.started":"2025-12-21T12:39:26.560346Z","shell.execute_reply":"2025-12-21T12:39:26.911713Z"}},"outputs":[],"execution_count":null}]}