{"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":"gpu","dataSources":[{"sourceId":10338,"databundleVersionId":862042,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n'''\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n'''\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:23.855193Z","iopub.execute_input":"2025-06-10T08:07:23.855457Z","iopub.status.idle":"2025-06-10T08:07:23.863946Z","shell.execute_reply.started":"2025-06-10T08:07:23.855435Z","shell.execute_reply":"2025-06-10T08:07:23.863293Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:23.865117Z","iopub.execute_input":"2025-06-10T08:07:23.865363Z","iopub.status.idle":"2025-06-10T08:07:24.133813Z","shell.execute_reply.started":"2025-06-10T08:07:23.865340Z","shell.execute_reply":"2025-06-10T08:07:24.133269Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"csv_path = Path(\"/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_labels.csv\")\ndf = pd.read_csv(csv_path)\n\n# quick peek\nprint(df.head())\n# ➜ patientId x y width height Target\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:24.134944Z","iopub.execute_input":"2025-06-10T08:07:24.135579Z","iopub.status.idle":"2025-06-10T08:07:24.222770Z","shell.execute_reply.started":"2025-06-10T08:07:24.135552Z","shell.execute_reply":"2025-06-10T08:07:24.222000Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# collapse the rows belonging to the same study\nper_image = (\n    df.groupby(\"patientId\")[\"Target\"]\n      .max()                     # “max” is 1 if any row has a box\n      .rename(\"has_box\")\n)\n\nnum_images      = len(per_image)\nnum_positives   = per_image.sum()\nfraction_pos    = num_positives / num_images\n\nprint(f\"{num_positives} / {num_images} images \"\n      f\"({fraction_pos:.1%}) contain at least one bounding box.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:24.223764Z","iopub.execute_input":"2025-06-10T08:07:24.224088Z","iopub.status.idle":"2025-06-10T08:07:24.259815Z","shell.execute_reply.started":"2025-06-10T08:07:24.224064Z","shell.execute_reply":"2025-06-10T08:07:24.259073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"boxes_per_image = (\n    df.query(\"Target == 1\")\n      .groupby(\"patientId\").size()\n)\n\nboxes_per_image.hist(bins=range(1, 10), rwidth=0.8)\nplt.xlabel(\"# boxes in the study\")\nplt.ylabel(\"count of studies\")\nplt.title(\"Lesion count distribution\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:24.261704Z","iopub.execute_input":"2025-06-10T08:07:24.261932Z","iopub.status.idle":"2025-06-10T08:07:24.555742Z","shell.execute_reply.started":"2025-06-10T08:07:24.261913Z","shell.execute_reply":"2025-06-10T08:07:24.555056Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\n\npos = df.query(\"Target == 1\")\nareas      = (pos[\"width\"] * pos[\"height\"]).values\naspect_rat = (pos[\"width\"] / pos[\"height\"]).values\n\nfig, ax = plt.subplots()\nax.hist(np.sqrt(areas), bins=40)        # use sqrt so the axis is in “pixels”\nax.set_xlabel(\"√area (≈ lesion diameter in pixels)\")\nax.set_title(\"Lesion size distribution\")\nplt.show()\n\nplt.figure()\nplt.hist(aspect_rat, bins=40)\nplt.xlabel(\"width / height\")\nplt.title(\"Aspect-ratio distribution\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:24.556511Z","iopub.execute_input":"2025-06-10T08:07:24.556783Z","iopub.status.idle":"2025-06-10T08:07:24.955166Z","shell.execute_reply.started":"2025-06-10T08:07:24.556758Z","shell.execute_reply":"2025-06-10T08:07:24.954278Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2, pydicom, matplotlib.patches as patches\n\nsample_ids = boxes_per_image.sample(4, random_state=0).index\nroot = Path(\"/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_images\")\n\nfor pid in sample_ids:\n    dcm = pydicom.dcmread(root/f\"{pid}.dcm\")\n    img = dcm.pixel_array\n    fig, ax = plt.subplots(1, figsize=(5,5))\n    ax.imshow(img, cmap=\"gray\")\n    for _, row in pos[pos.patientId == pid].iterrows():\n        rect = patches.Rectangle(\n            (row.x, row.y), row.width, row.height,\n            linewidth=2, edgecolor=\"lime\", facecolor=\"none\")\n        ax.add_patch(rect)\n    ax.set_title(pid)\n    ax.axis(\"off\")\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:24.956160Z","iopub.execute_input":"2025-06-10T08:07:24.956435Z","iopub.status.idle":"2025-06-10T08:07:26.648207Z","shell.execute_reply.started":"2025-06-10T08:07:24.956409Z","shell.execute_reply":"2025-06-10T08:07:26.647345Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2, pydicom, torch.nn.functional as F\nfrom torch.utils.data import Dataset\nRAW = Path(\"/kaggle/input/rsna-pneumonia-detection-challenge\")  # read-only\n\nLABELS = RAW/\"stage_2_train_labels.csv\"\ndf = pd.read_csv(LABELS)\n\nclass RSNADataset(Dataset):\n    def __init__(self, df, root, transforms=None):\n        self.img_ids = df.patientId.unique()\n        self.df   = df\n        self.root = Path(root)\n        self.tfm  = transforms\n\n    def __getitem__(self, idx):\n        pid = self.img_ids[idx]\n\n        # -------- image ----------\n        ds  = pydicom.dcmread(self.root / f\"{pid}.dcm\")\n        img = ds.pixel_array.astype(\"float32\")\n        img = (img - img.min()) / (img.max() - img.min() + 1e-6)\n        img = torch.as_tensor(np.stack([img, img, img]), dtype=torch.float32)\n\n        # -------- target ----------\n        recs = self.df[self.df.patientId == pid]\n        pos  = recs[recs.Target == 1]\n\n        if len(pos):\n            xyxy = np.column_stack(\n                [pos.x, pos.y, pos.x + pos.width, pos.y + pos.height]\n            )\n            boxes = torch.as_tensor(xyxy, dtype=torch.float32)\n            labels = torch.ones((len(boxes),), dtype=torch.int64)  # class 1\n        else:\n            boxes  = torch.zeros((0, 4), dtype=torch.float32)      # 2-D!\n            labels = torch.zeros((0,),  dtype=torch.int64)\n\n        target = {\n            \"boxes\":   boxes,\n            \"labels\":  labels,\n            \"image_id\": torch.tensor([idx]),\n        }\n\n        if self.tfm:                       # (no transforms in this baseline)\n            img = self.tfm(img)\n\n        return img, target\n\n    def __len__(self):\n        return len(self.img_ids)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:26.649190Z","iopub.execute_input":"2025-06-10T08:07:26.649454Z","iopub.status.idle":"2025-06-10T08:07:30.684136Z","shell.execute_reply.started":"2025-06-10T08:07:26.649433Z","shell.execute_reply":"2025-06-10T08:07:30.683368Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch, torchvision, platform, subprocess, os, random, numpy as np, pandas as pd\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)\n\nfrom pathlib import Path\n\nRAW = Path(\"/kaggle/input/rsna-pneumonia-detection-challenge\")\nTRAIN_IMG_DIR = RAW / \"stage_2_train_images\"\nTEST_IMG_DIR  = RAW / \"stage_2_test_images\"\n\nprint(\"sample train file:\", next(TRAIN_IMG_DIR.glob(\"*.dcm\")))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:30.684961Z","iopub.execute_input":"2025-06-10T08:07:30.685212Z","iopub.status.idle":"2025-06-10T08:07:34.657787Z","shell.execute_reply.started":"2025-06-10T08:07:30.685195Z","shell.execute_reply":"2025-06-10T08:07:34.657001Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------------------------------------------------------\n# 1)  Train / validation split (stratified by Target = 0/1)\n# ---------------------------------------------------------\nfrom sklearn.model_selection import StratifiedKFold\nimport pandas as pd\n\nRAW = Path(\"/kaggle/input/rsna-pneumonia-detection-challenge\")\nTRAIN_IMG_DIR = RAW / \"stage_2_train_images\"\n\ndf   = pd.read_csv(RAW / \"stage_2_train_labels.csv\")\nids  = df.groupby(\"patientId\")[\"Target\"].max().reset_index()\n\nDEBUG = False\nPOS   = 100       # ← how many positive studies you want\nNEG   = 100       # ← how many negative studies you want\n\nids   = df.groupby(\"patientId\")[\"Target\"].max().reset_index()   # 0/1 label\nif DEBUG:\n    pos_ids = ids[ids.Target == 1].sample(POS,  random_state=0)\n    neg_ids = ids[ids.Target == 0].sample(NEG,  random_state=0)\n    ids = pd.concat([pos_ids, neg_ids]).reset_index(drop=True)\n\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\ntrain_idx, val_idx = next(skf.split(ids.patientId, ids.Target))\n\ntrain_df = df[df.patientId.isin(ids.patientId.iloc[train_idx])]\nval_df   = df[df.patientId.isin(ids.patientId.iloc[val_idx])]\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\ntrain_idx, val_idx = next(skf.split(ids.patientId, ids.Target))\n\ntrain_df = df[df.patientId.isin(ids.patientId.iloc[train_idx])]\nval_df   = df[df.patientId.isin(ids.patientId.iloc[val_idx])]\n\n# ---------------------------------------------------------\n# 2)  Datasets (root points directly at the DICOM folders)\n# ---------------------------------------------------------\ntrain_ds = RSNADataset(train_df, TRAIN_IMG_DIR)\nval_ds   = RSNADataset(val_df,   TRAIN_IMG_DIR)\n\n# ---------------------------------------------------------\n# 3)  DataLoaders  (keyword args → no “sampler vs shuffle” clash)\n# ---------------------------------------------------------\ndef collate(batch):\n    return tuple(zip(*batch))\n\ntrain_dl = torch.utils.data.DataLoader(\n    train_ds,\n    batch_size=12,\n    shuffle=True,\n    num_workers=4,\n    persistent_workers=True,\n    collate_fn=collate,\n)\n\nval_dl = torch.utils.data.DataLoader(\n    val_ds,\n    batch_size=4,\n    shuffle=False,\n    num_workers=4,\n    persistent_workers=True,\n    collate_fn=collate,\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:34.658752Z","iopub.execute_input":"2025-06-10T08:07:34.659123Z","iopub.status.idle":"2025-06-10T08:07:35.329927Z","shell.execute_reply.started":"2025-06-10T08:07:34.659081Z","shell.execute_reply":"2025-06-10T08:07:35.329068Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.models.detection import fasterrcnn_resnet50_fpn\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor  # ← NEW path\nmodel = fasterrcnn_resnet50_fpn(weights=\"DEFAULT\")\nin_ch = model.roi_heads.box_predictor.cls_score.in_features\nmodel.roi_heads.box_predictor = FastRCNNPredictor(in_ch, num_classes=2)\n\n# ⬅︎ anchors: lesions tiny → include 16 px, 32 px\nmodel.rpn.anchor_generator.sizes = ((16, 32, 64, 128, 256),)\n\nmodel.to(\"cuda\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:35.331687Z","iopub.execute_input":"2025-06-10T08:07:35.331954Z","iopub.status.idle":"2025-06-10T08:07:37.204826Z","shell.execute_reply.started":"2025-06-10T08:07:35.331937Z","shell.execute_reply":"2025-06-10T08:07:37.203862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === 0.  Imports === -------------------------------------------------------\nfrom pathlib import Path\nimport torch, math, os\nfrom tqdm.auto import tqdm\n\n# Preferred AMP spelling in PyTorch ≥2.3\nfrom torch.amp import autocast          #  ← NEW import path\nfrom torch.cuda.amp import GradScaler\n\n# === 1.  Optimizer / LR / AMP scaler === ----------------------------------\noptimizer = torch.optim.SGD(\n    model.parameters(), lr=0.005, momentum=0.9, weight_decay=1e-4\n)\nlr_sched  = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)\nscaler    = GradScaler()\n\n# === 2.  Epoch runner with tqdm  (loss-dict even in val) === --------------\ndef run_epoch(loader, train=True, desc=\"train\"):\n    \"\"\"\n    One full pass over `loader`.\n    If `train=False` we disable gradients, but still keep the model in\n    training *mode* so Faster R-CNN returns a loss dict instead of detections.\n    \"\"\"\n    model.train(True)                        # <-- ALWAYS train() for loss\n    torch.set_grad_enabled(train)            # gradients only in train phase\n\n    running, seen = 0.0, 0\n    bar = tqdm(loader, desc=desc, leave=False)\n\n    for imgs, tgts in bar:\n        imgs = [img.cuda(non_blocking=True) for img in imgs]\n        tgts = [{k: v.cuda(non_blocking=True) for k, v in t.items()} for t in tgts]\n\n        with autocast(device_type=\"cuda\"):\n            loss_dict = model(imgs, tgts)    # guaranteed dict\n            loss = sum(loss_dict.values())\n\n        if train:\n            scaler.scale(loss).backward()\n            scaler.step(optimizer)\n            scaler.update()\n            optimizer.zero_grad(set_to_none=True)\n\n        bs = len(imgs)\n        running += loss.item() * bs\n        seen    += bs\n        bar.set_postfix(loss=f\"{loss.item():.3f}\",\n                        avg=f\"{running/seen:.3f}\")\n\n    return running / seen\n\n# === 3.  Checkpoint helpers === -------------------------------------------\nCKPT_DIR = Path(\"/kaggle/working/checkpoints\")\nCKPT_DIR.mkdir(exist_ok=True)\n\ndef save_ckpt(epoch):\n    fname = CKPT_DIR / f\"{epoch:03d}.pth\"\n    torch.save({\n        \"epoch\":     epoch,\n        \"model\":     model.state_dict(),\n        \"optimizer\": optimizer.state_dict(),\n        \"scaler\":    scaler.state_dict(),\n    }, fname)\n    print(f\"✔️  Saved checkpoint → {fname}\")\n\ndef load_ckpt(path):\n    ckpt = torch.load(path, map_location=\"cpu\")\n    model.load_state_dict(ckpt[\"model\"])\n    optimizer.load_state_dict(ckpt[\"optimizer\"])\n    scaler.load_state_dict(ckpt[\"scaler\"])\n    print(f\"🔄  Resumed from {path} (epoch {ckpt['epoch']})\")\n    return ckpt[\"epoch\"] + 1   # resume at next epoch\n\n# === 4.  Optional resume === ----------------------------------------------\nlatest = sorted(CKPT_DIR.glob(\"*.pth\"))[-1:]   # pick the newest file, if any\nstart_epoch = load_ckpt(latest[0]) if latest else 1\n\n# === 5.  Training loop === -------------------------------------------------\nEPOCHS = 3          # change as you like\n\nfor epoch in range(start_epoch, EPOCHS + 1):\n    tr_loss  = run_epoch(train_dl, train=True,  desc=f\"Ep{epoch:02d}[train]\")\n    val_loss = run_epoch(val_dl,   train=False, desc=f\"Ep{epoch:02d}[val]  \")\n\n    lr_sched.step()\n    print(f\"Epoch {epoch:02d} | train {tr_loss:.4f} | val {val_loss:.4f}\")\n\n    save_ckpt(epoch)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:07:37.205640Z","iopub.execute_input":"2025-06-10T08:07:37.205895Z","iopub.status.idle":"2025-06-10T08:55:56.417194Z","shell.execute_reply.started":"2025-06-10T08:07:37.205868Z","shell.execute_reply":"2025-06-10T08:55:56.415938Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================================================================\n#   Inference, simple stats, and 4-image visual sanity-check\n# ================================================================\nfrom pathlib import Path\nimport torch, pydicom, cv2, numpy as np, pandas as pd, random, matplotlib.pyplot as plt\nfrom tqdm.auto import tqdm\nplt.rcParams[\"figure.figsize\"] = (5, 5)\n\n# ---------- 0. Locate & load checkpoint ---------- #\nCKPT_DIR = Path(\"/kaggle/working/checkpoints\")\nlatest   = sorted(CKPT_DIR.glob(\"*.pth\"))[-1]\nckpt     = torch.load(latest, map_location=\"cpu\")\nmodel.load_state_dict(ckpt[\"model\"])\nmodel.to(\"cuda\").eval()\nprint(f\"✔️  Restored epoch {ckpt['epoch']} weights from {latest.name}\")\n\n# ---------- 1. Build test file list & helper ---------- #\nRAW           = Path(\"/kaggle/input/rsna-pneumonia-detection-challenge\")\nTEST_IMG_DIR  = RAW / \"stage_2_test_images\"\ntest_files    = sorted(TEST_IMG_DIR.glob(\"*.dcm\"))\nBATCH_SIZE    = 6\nSCORE_THR     = 0.30          # tweak to taste\n\ndef dcms_to_tensor(paths):\n    \"\"\"Read list[Path] → list[torch.Tensor] (3×H×W, 0-1).\"\"\"\n    out = []\n    for p in paths:\n        dcm  = pydicom.dcmread(p)\n        img  = dcm.pixel_array.astype(\"float32\")\n        img  = (img - img.min()) / (img.max() - img.min() + 1e-6)\n        out.append(torch.as_tensor(np.stack([img, img, img]), dtype=torch.float32))\n    return out\n\n# ---------- 2. Run inference over test set ---------- #\nrows, all_scores, box_counts = [], [], []\nfor i in tqdm(range(0, len(test_files), BATCH_SIZE), desc=\"Infer\"):\n    batch_paths = test_files[i : i + BATCH_SIZE]\n    imgs  = [t.cuda(non_blocking=True) for t in dcms_to_tensor(batch_paths)]\n    with torch.no_grad(), torch.amp.autocast(device_type=\"cuda\"):\n        preds = model(imgs)\n\n    for pth, pred in zip(batch_paths, preds):\n        keep = pred[\"scores\"] > SCORE_THR\n        n_kept = int(keep.sum())\n        box_counts.append(n_kept)\n\n        if n_kept == 0:\n            pred_str = \"0 0 1 1 0.1\"\n        else:\n            boxes  = pred[\"boxes\"][keep].cpu().numpy()\n            scores = pred[\"scores\"][keep].cpu().numpy()\n            all_scores.extend(scores.tolist())\n            parts = [\n                f\"{x1:.0f} {y1:.0f} {x2-x1:.0f} {y2-y1:.0f} {s:.2f}\"\n                for (x1, y1, x2, y2), s in zip(boxes, scores)\n            ]\n            pred_str = \" \".join(parts)\n\n        rows.append({\"patientId\": pth.stem, \"PredictionString\": pred_str})\n\nsub = pd.DataFrame(rows)\nsub.to_csv(\"submission.csv\", index=False)\nprint(f\"\\nSaved submission.csv with {len(sub)} rows\")\n\n# ---------- 3. Quick test-set stats ---------- #\ntotal_imgs   = len(test_files)\nwith_boxes   = sum(b > 0 for b in box_counts)\navg_boxes    = np.mean(box_counts)\nprint(f\"\\nTEST-SET STATS  (score ≥ {SCORE_THR})\")\nprint(\"-\" * 40)\nprint(f\"Images processed     : {total_imgs}\")\nprint(f\"Images w/ detections : {with_boxes} ({with_boxes/total_imgs:.1%})\")\nprint(f\"Avg boxes / image    : {avg_boxes:.2f}\")\nif all_scores:\n    print(f\"Score range          : {min(all_scores):.2f} – {max(all_scores):.2f}\")\n    print(f\"Median score         : {np.median(all_scores):.2f}\")\n\n# ---------- 4. Visualise 4 random predictions ---------- #\nsample_paths = random.sample(test_files, 4)\nfig, axes = plt.subplots(2, 2, figsize=(10, 10))\naxes = axes.flatten()\n\nfor ax, pth in zip(axes, sample_paths):\n    dcm  = pydicom.dcmread(pth)\n    img  = dcm.pixel_array\n    ax.imshow(img, cmap=\"gray\")\n    ax.set_title(pth.stem, fontsize=8)\n    ax.axis(\"off\")\n\n    # Get prediction we just computed\n    pred_row   = sub[sub.patientId == pth.stem].iloc[0]\n    if pred_row[\"PredictionString\"].startswith(\"0 0 1 1\"):\n        continue  # no detection\n\n    vals = list(map(float, pred_row[\"PredictionString\"].split()))\n    for x1, y1, w, h, sc in np.array(vals).reshape(-1, 5):\n        x2, y2 = x1 + w, y1 + h\n        ax.add_patch(plt.Rectangle(\n            (x1, y1), w, h, fill=False, edgecolor=\"lime\", linewidth=2))\n        ax.text(x1, y1, f\"{sc:.2f}\", color=\"yellow\", fontsize=6,\n                bbox=dict(facecolor=\"black\", alpha=0.5, pad=1))\n\nplt.tight_layout(); plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T08:56:03.664654Z","iopub.execute_input":"2025-06-10T08:56:03.664969Z","iopub.status.idle":"2025-06-10T09:00:05.553501Z","shell.execute_reply.started":"2025-06-10T08:56:03.664942Z","shell.execute_reply":"2025-06-10T09:00:05.552648Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================  VISUAL CHECK: GT vs. PRED  ==================\nimport random, torch, numpy as np, matplotlib.pyplot as plt, matplotlib.patches as patches\nimport pydicom, pandas as pd\nfrom pathlib import Path\n\nNUM_SAMPLES = 4     # how many images to display\nSCORE_THR   = 0.30  # draw preds with score >= this\n\n# -----------------------------------------------------------------\n# 1)  Restore latest checkpoint\n# -----------------------------------------------------------------\nCKPT_DIR = Path(\"/kaggle/working/checkpoints\")\nlatest   = sorted(CKPT_DIR.glob(\"*.pth\"))[-1]           # newest file\nckpt     = torch.load(latest, map_location=\"cpu\")\nmodel.load_state_dict(ckpt[\"model\"])\nmodel.to(\"cuda\").eval()\nprint(f\"Loaded epoch {ckpt['epoch']} checkpoint ➜ {latest.name}\")\n\n# -----------------------------------------------------------------\n# 2)  Pick random *positive* validation studies\n# -----------------------------------------------------------------\nids_with_box = (val_df.groupby(\"patientId\")[\"Target\"].max() == 1)\npos_ids      = ids_with_box[ids_with_box].index.tolist()\nsample_ids   = random.sample(pos_ids, NUM_SAMPLES)\n\nRAW   = Path(\"/kaggle/input/rsna-pneumonia-detection-challenge\")\nIMG_DIR = RAW / \"stage_2_train_images\"                  # same folder as training\n\ndef load_img_tensor(pid):\n    dcm = pydicom.dcmread(IMG_DIR/f\"{pid}.dcm\")\n    img = dcm.pixel_array.astype(\"float32\")\n    img = (img - img.min()) / (img.max() - img.min() + 1e-6)\n    tens = torch.as_tensor(np.stack([img]*3), dtype=torch.float32)\n    return img, tens\n\n# -----------------------------------------------------------------\n# 3)  Plot\n# -----------------------------------------------------------------\nfig, axes = plt.subplots(1, NUM_SAMPLES, figsize=(5*NUM_SAMPLES, 5))\nif NUM_SAMPLES == 1: axes = [axes]\n\nfor ax, pid in zip(axes, sample_ids):\n    # --- load image & GT boxes --------------------------\n    img_np, tensor = load_img_tensor(pid)\n    gt_rows = val_df[(val_df.patientId == pid) & (val_df.Target == 1)]\n    gt_boxes = gt_rows[['x', 'y', 'width', 'height']].values\n\n    # --- model prediction -------------------------------\n    with torch.no_grad(), torch.amp.autocast(device_type=\"cuda\"):\n        pred = model([tensor.cuda()])[0]\n    keep   = pred['scores'] > SCORE_THR\n    boxes  = pred['boxes'][keep].cpu().numpy()\n    scores = pred['scores'][keep].cpu().numpy()\n\n    # --- draw -------------------------------------------\n    ax.imshow(img_np, cmap=\"gray\")\n    ax.set_title(pid, fontsize=8); ax.axis(\"off\")\n\n    # ground-truth (red)\n    for (x, y, w, h) in gt_boxes:\n        ax.add_patch(patches.Rectangle((x, y), w, h, linewidth=2,\n                                       edgecolor=\"red\", facecolor=\"none\"))\n\n    # predictions (lime)\n    for (x1, y1, x2, y2), sc in zip(boxes, scores):\n        ax.add_patch(patches.Rectangle((x1, y1), x2-x1, y2-y1, linewidth=2,\n                                       edgecolor=\"lime\", facecolor=\"none\"))\n        ax.text(x1, y1, f\"{sc:.2f}\", color=\"yellow\", fontsize=6,\n                bbox=dict(facecolor=\"black\", alpha=0.4, pad=1))\n\nplt.tight_layout(); plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T09:01:10.778688Z","iopub.execute_input":"2025-06-10T09:01:10.779354Z","iopub.status.idle":"2025-06-10T09:01:12.205709Z","shell.execute_reply.started":"2025-06-10T09:01:10.779332Z","shell.execute_reply":"2025-06-10T09:01:12.205014Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.ops import box_iou\nimport pandas as pd\nimport torch\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom tqdm.auto import tqdm\nfrom pathlib import Path\nimport pydicom\n\n# === Setup ===\nRAW = Path(\"/kaggle/input/rsna-pneumonia-detection-challenge\")\nTRAIN_IMG_DIR = RAW / \"stage_2_train_images\"\nLABELS_CSV = RAW / \"stage_2_train_labels.csv\"\nBATCH_SIZE = 6\nSCORE_THR = 0.30\n\n# === Load Ground Truth ===\ndf = pd.read_csv(LABELS_CSV)\ndf_pos = df[df.Target == 1]\ngt_patients = df_pos.patientId.unique()\ntest_files = [f for f in sorted(TRAIN_IMG_DIR.glob(\"*.dcm\")) if f.stem in gt_patients]\n\n# === Helper ===\ndef get_gt_boxes(pid):\n    rows = df_pos[df_pos.patientId == pid]\n    return torch.tensor([\n        [row.x, row.y, row.x + row.width, row.y + row.height]\n        for _, row in rows.iterrows()\n    ], dtype=torch.float32)\n\ndef dcms_to_tensor(paths):\n    out = []\n    for p in paths:\n        dcm = pydicom.dcmread(p)\n        img = dcm.pixel_array.astype(\"float32\")\n        img = (img - img.min()) / (img.max() - img.min() + 1e-6)\n        out.append(torch.as_tensor(np.stack([img, img, img]), dtype=torch.float32))\n    return out\n\n# === Inference & IoU ===\nrows, ious, iou_per_patient = [], [], {}\n\nfor i in tqdm(range(0, len(test_files), BATCH_SIZE), desc=\"Inference\"):\n    batch_paths = test_files[i : i + BATCH_SIZE]\n    imgs = [t.cuda(non_blocking=True) for t in dcms_to_tensor(batch_paths)]\n    \n    with torch.no_grad(), torch.amp.autocast(device_type=\"cuda\"):\n        preds = model(imgs)\n\n    for pth, pred in zip(batch_paths, preds):\n        pid = pth.stem\n        gt_boxes = get_gt_boxes(pid)\n        keep = pred[\"scores\"] > SCORE_THR\n\n        if keep.sum() == 0 or gt_boxes.numel() == 0:\n            rows.append({\"patientId\": pid, \"PredictionString\": \"0 0 1 1 0.1\"})\n            continue\n\n        boxes = pred[\"boxes\"][keep].cpu()\n        scores = pred[\"scores\"][keep].cpu().numpy()\n        pred_str = \" \".join([\n            f\"{x1:.0f} {y1:.0f} {x2-x1:.0f} {y2-y1:.0f} {s:.2f}\"\n            for (x1, y1, x2, y2), s in zip(boxes, scores)\n        ])\n        rows.append({\"patientId\": pid, \"PredictionString\": pred_str})\n\n        iou_matrix = box_iou(boxes, gt_boxes)\n        best_ious = iou_matrix.max(dim=1)[0].tolist()\n        ious.extend(best_ious)\n        iou_per_patient[pid] = np.mean(best_ious)\n\n# Save predictions\nsub = pd.DataFrame(rows)\nsub.to_csv(\"submission_trainset.csv\", index=False)\n\n# IoU stats\nprint(f\"\\nIoU Statistics on {len(ious)} predictions:\")\nprint(\"-\" * 40)\nprint(f\"Mean IoU       : {np.mean(ious):.3f}\")\nprint(f\"Median IoU     : {np.median(ious):.3f}\")\nprint(f\"IoU > 0.5 count      : {sum(i > 0.5 for i in ious)} ({sum(i > 0.5 for i in ious)/len(ious)*100:.1f}%)\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-10T09:21:42.370702Z","iopub.execute_input":"2025-06-10T09:21:42.371399Z","iopub.status.idle":"2025-06-10T09:28:31.984064Z","shell.execute_reply.started":"2025-06-10T09:21:42.371378Z","shell.execute_reply":"2025-06-10T09:28:31.983389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}