{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q pydicom\n!pip install -q torch torchvision","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:08:55.839339Z","iopub.execute_input":"2026-06-28T18:08:55.839626Z","iopub.status.idle":"2026-06-28T18:09:05.262448Z","shell.execute_reply.started":"2026-06-28T18:08:55.839593Z","shell.execute_reply":"2026-06-28T18:09:05.261664Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport random\nimport numpy as np\nimport pandas as pd\nimport pydicom\nimport cv2\nimport torch\nimport torchvision\n\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\n\nfrom torchvision.models.detection import fasterrcnn_resnet50_fpn\nfrom torchvision.models.detection import fasterrcnn_resnet50_fpn_v2\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\n\nfrom torch.utils.data import Dataset, DataLoader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:09:05.264093Z","iopub.execute_input":"2026-06-28T18:09:05.264467Z","iopub.status.idle":"2026-06-28T18:09:16.012434Z","shell.execute_reply.started":"2026-06-28T18:09:05.264437Z","shell.execute_reply":"2026-06-28T18:09:16.011539Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/competitions/rsna-pneumonia-detection-challenge\"\n\nIMAGE_DIR = f\"{DATA_DIR}/stage_2_train_images\"\n\nLABELS_CSV = f\"{DATA_DIR}/stage_2_train_labels.csv\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:09:16.013510Z","iopub.execute_input":"2026-06-28T18:09:16.014016Z","iopub.status.idle":"2026-06-28T18:09:16.018698Z","shell.execute_reply.started":"2026-06-28T18:09:16.013964Z","shell.execute_reply":"2026-06-28T18:09:16.017877Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels_df = pd.read_csv(LABELS_CSV)\n\nlabels_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:09:16.020667Z","iopub.execute_input":"2026-06-28T18:09:16.021003Z","iopub.status.idle":"2026-06-28T18:09:16.108637Z","shell.execute_reply.started":"2026-06-28T18:09:16.020941Z","shell.execute_reply":"2026-06-28T18:09:16.108033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Helper to inspect a single DICOM file metadata and image\ndef inspect_dicom(patient_id):\n    path = os.path.join(IMAGE_DIR, f\"{patient_id}.dcm\")\n    if not os.path.exists(path):\n        print(\"Data not downloaded yet.\")\n        return\n\n    dataset = pydicom.dcmread(path)\n    print(f\"Patient ID: {dataset.PatientID}\")\n    print(f\"Modality: {dataset.Modality}\")\n    print(f\"Image Shape: {dataset.pixel_array.shape}\")\n\n    plt.imshow(dataset.pixel_array, cmap='gray')\n    plt.title(f\"DICOM Image: {patient_id}\")\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:09:17.359821Z","iopub.execute_input":"2026-06-28T18:09:17.360590Z","iopub.status.idle":"2026-06-28T18:09:17.365110Z","shell.execute_reply.started":"2026-06-28T18:09:17.360556Z","shell.execute_reply":"2026-06-28T18:09:17.364394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"patient_ids = labels_df[\"patientId\"].unique()\n\ntrain_ids, val_ids = train_test_split(\n    patient_ids,\n    test_size=0.2,\n    random_state=42\n)\ntrain_ids = train_ids[:2000]\nval_ids = val_ids[:500]\nprint(len(train_ids))\nprint(len(val_ids))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:09:27.754368Z","iopub.execute_input":"2026-06-28T18:09:27.754810Z","iopub.status.idle":"2026-06-28T18:09:27.769663Z","shell.execute_reply.started":"2026-06-28T18:09:27.754781Z","shell.execute_reply":"2026-06-28T18:09:27.769021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RSNADataset(Dataset):\n\n    def __init__(\n        self,\n        patient_ids,\n        labels_df,\n        image_dir,\n        image_size=512\n    ):\n\n        self.patient_ids = patient_ids\n        self.labels_df = labels_df\n        self.image_dir = image_dir\n        self.image_size = image_size\n\n    def __len__(self):\n        return len(self.patient_ids)\n\n    def __getitem__(self, idx):\n\n        patient_id = self.patient_ids[idx]\n\n        image_path = os.path.join(\n            self.image_dir,\n            f\"{patient_id}.dcm\"\n        )\n\n        dcm = pydicom.dcmread(image_path)\n\n        image = dcm.pixel_array.astype(np.float32)\n\n        original_h, original_w = image.shape\n\n        image = cv2.resize(\n            image,\n            (self.image_size, self.image_size)\n        )\n\n        max_val = image.max()\n\n        if max_val > 0:\n            image = image / max_val\n\n        image = np.stack(\n            [image, image, image],\n            axis=-1\n        )\n\n        image = torch.tensor(\n            image.transpose(2, 0, 1),\n            dtype=torch.float32\n        )\n\n        scale_x = self.image_size / original_w\n        scale_y = self.image_size / original_h\n\n        rows = self.labels_df[\n            self.labels_df[\"patientId\"] == patient_id\n        ]\n\n        boxes = []\n\n        for _, row in rows.iterrows():\n\n            if row[\"Target\"] == 1:\n\n                x1 = row[\"x\"] * scale_x\n                y1 = row[\"y\"] * scale_y\n\n                x2 = (\n                    row[\"x\"] + row[\"width\"]\n                ) * scale_x\n\n                y2 = (\n                    row[\"y\"] + row[\"height\"]\n                ) * scale_y\n\n                boxes.append(\n                    [x1, y1, x2, y2]\n                )\n\n        if len(boxes) > 0:\n\n            boxes = torch.tensor(\n                boxes,\n                dtype=torch.float32\n            )\n\n            labels = torch.ones(\n                (len(boxes),),\n                dtype=torch.int64\n            )\n\n        else:\n\n            boxes = torch.zeros(\n                (0, 4),\n                dtype=torch.float32\n            )\n\n            labels = torch.zeros(\n                (0,),\n                dtype=torch.int64\n            )\n\n        target = {\n            \"boxes\": boxes,\n            \"labels\": labels,\n            \"image_id\": torch.tensor([idx])\n        }\n\n        return image, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:09:34.047144Z","iopub.execute_input":"2026-06-28T18:09:34.047798Z","iopub.status.idle":"2026-06-28T18:09:34.057254Z","shell.execute_reply.started":"2026-06-28T18:09:34.047766Z","shell.execute_reply":"2026-06-28T18:09:34.056193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def collate_fn(batch):\n    return tuple(zip(*batch))\n\ntrain_ds = RSNADataset(\n    train_ids,\n    labels_df,\n    IMAGE_DIR\n)\n\nval_ds = RSNADataset(\n    val_ids,\n    labels_df,\n    IMAGE_DIR\n)\n\ntrain_loader = DataLoader(\n    train_ds,\n    batch_size=2,\n    shuffle=True,\n    collate_fn=collate_fn\n)\n\nval_loader = DataLoader(\n    val_ds,\n    batch_size=2,\n    shuffle=False,\n    collate_fn=collate_fn\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:09:36.871545Z","iopub.execute_input":"2026-06-28T18:09:36.872181Z","iopub.status.idle":"2026-06-28T18:09:36.877125Z","shell.execute_reply.started":"2026-06-28T18:09:36.872151Z","shell.execute_reply":"2026-06-28T18:09:36.876199Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_a = fasterrcnn_resnet50_fpn(\n    weights=\"DEFAULT\"\n)\n\nin_features = (\n    model_a.roi_heads\n    .box_predictor\n    .cls_score\n    .in_features\n)\n\nmodel_a.roi_heads.box_predictor = (\n    FastRCNNPredictor(\n        in_features,\n        2\n    )\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:09:49.008686Z","iopub.execute_input":"2026-06-28T18:09:49.009423Z","iopub.status.idle":"2026-06-28T18:09:50.677827Z","shell.execute_reply.started":"2026-06-28T18:09:49.009391Z","shell.execute_reply":"2026-06-28T18:09:50.676992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_b = fasterrcnn_resnet50_fpn_v2(\n    weights=\"DEFAULT\"\n)\n\nin_features = (\n    model_b.roi_heads\n    .box_predictor\n    .cls_score\n    .in_features\n)\n\nmodel_b.roi_heads.box_predictor = (\n    FastRCNNPredictor(\n        in_features,\n        2\n    )\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:09:58.382402Z","iopub.execute_input":"2026-06-28T18:09:58.382797Z","iopub.status.idle":"2026-06-28T18:10:00.023264Z","shell.execute_reply.started":"2026-06-28T18:09:58.382769Z","shell.execute_reply":"2026-06-28T18:10:00.022594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\n    \"cuda\"\n    if torch.cuda.is_available()\n    else \"cpu\"\n)\n\nmodel_a.to(device)\nmodel_b.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:10:01.684184Z","iopub.execute_input":"2026-06-28T18:10:01.684802Z","iopub.status.idle":"2026-06-28T18:10:02.386826Z","shell.execute_reply.started":"2026-06-28T18:10:01.684774Z","shell.execute_reply":"2026-06-28T18:10:02.385989Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm.auto import tqdm\n\ndef train_one_epoch(\n    model,\n    loader,\n    optimizer\n):\n\n    model.train()\n\n    total_loss = 0\n\n    pbar = tqdm(\n        loader,\n        desc=\"Training\",\n        leave=False\n    )\n\n    for images, targets in pbar:\n\n        images = [\n            img.to(device)\n            for img in images\n        ]\n\n        targets = [\n            {\n                k: v.to(device)\n                for k, v in t.items()\n            }\n            for t in targets\n        ]\n\n        loss_dict = model(\n            images,\n            targets\n        )\n\n        losses = sum(\n            loss\n            for loss in loss_dict.values()\n        )\n\n        optimizer.zero_grad()\n\n        losses.backward()\n\n        optimizer.step()\n\n        total_loss += losses.item()\n\n        pbar.set_postfix(\n            loss=f\"{losses.item():.4f}\"\n        )\n\n    avg_loss = total_loss / len(loader)\n\n    return avg_loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:10:10.800997Z","iopub.execute_input":"2026-06-28T18:10:10.801413Z","iopub.status.idle":"2026-06-28T18:10:10.874675Z","shell.execute_reply.started":"2026-06-28T18:10:10.801385Z","shell.execute_reply":"2026-06-28T18:10:10.874032Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer_a = torch.optim.SGD(\n    model_a.parameters(),\n    lr=0.005,\n    momentum=0.9,\n    weight_decay=0.0005\n)\n\nfor epoch in range(5):\n\n    loss = train_one_epoch(\n        model_a,\n        train_loader,\n        optimizer_a\n    )\n\n    print(\n        f\"Epoch {epoch+1} Loss: {loss:.4f}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:10:14.264559Z","iopub.execute_input":"2026-06-28T18:10:14.265178Z","iopub.status.idle":"2026-06-28T18:48:17.158207Z","shell.execute_reply.started":"2026-06-28T18:10:14.265149Z","shell.execute_reply":"2026-06-28T18:48:17.157548Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer_b = torch.optim.SGD(\n    model_b.parameters(),\n    lr=0.005,\n    momentum=0.9,\n    weight_decay=0.0005\n)\n\nfor epoch in range(5):\n\n    loss = train_one_epoch(\n        model_b,\n        train_loader,\n        optimizer_b\n    )\n\n    print(\n        f\"Epoch {epoch+1} Loss: {loss:.4f}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T18:48:17.159667Z","iopub.execute_input":"2026-06-28T18:48:17.160005Z","iopub.status.idle":"2026-06-28T19:42:45.164819Z","shell.execute_reply.started":"2026-06-28T18:48:17.159942Z","shell.execute_reply":"2026-06-28T19:42:45.163743Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compute_iou(box1, box2):\n\n    xA = max(box1[0], box2[0])\n    yA = max(box1[1], box2[1])\n\n    xB = min(box1[2], box2[2])\n    yB = min(box1[3], box2[3])\n\n    inter_w = max(0, xB - xA)\n    inter_h = max(0, yB - yA)\n\n    inter_area = inter_w * inter_h\n\n    area1 = (\n        (box1[2] - box1[0]) *\n        (box1[3] - box1[1])\n    )\n\n    area2 = (\n        (box2[2] - box2[0]) *\n        (box2[3] - box2[1])\n    )\n\n    union = area1 + area2 - inter_area\n\n    if union == 0:\n        return 0\n\n    return inter_area / union","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:42:45.165932Z","iopub.execute_input":"2026-06-28T19:42:45.166301Z","iopub.status.idle":"2026-06-28T19:42:45.172094Z","shell.execute_reply.started":"2026-06-28T19:42:45.166276Z","shell.execute_reply":"2026-06-28T19:42:45.171358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def precision_at_threshold(gt_boxes, pred_boxes, threshold):\n    if len(gt_boxes) == 0 and len(pred_boxes) == 0:\n        return 1.0\n    if len(gt_boxes) == 0 or len(pred_boxes) == 0:\n        return 0.0\n\n    ious = np.zeros((len(gt_boxes), len(pred_boxes)))\n    for i, gt in enumerate(gt_boxes):\n        for j, pred in enumerate(pred_boxes):\n            ious[i, j] = compute_iou(gt, pred)\n\n    matched_gt = set()\n    matched_pred = set()\n\n    # Sort by IoU to match the best pairs first\n    indices = np.argsort(ious.ravel())[::-1]\n    for idx in indices:\n        i, j = divmod(idx, len(pred_boxes))\n        if ious[i, j] >= threshold and i not in matched_gt and j not in matched_pred:\n            matched_gt.add(i)\n            matched_pred.add(j)\n\n    tp = len(matched_gt)\n    fp = len(pred_boxes) - tp\n    fn = len(gt_boxes) - tp\n\n    return tp / (tp + fp + fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:42:45.173744Z","iopub.execute_input":"2026-06-28T19:42:45.174119Z","iopub.status.idle":"2026-06-28T19:42:45.193456Z","shell.execute_reply.started":"2026-06-28T19:42:45.174073Z","shell.execute_reply":"2026-06-28T19:42:45.192845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def image_score(\n    gt_boxes,\n    pred_boxes\n):\n\n    thresholds = [\n        0.4,\n        0.45,\n        0.5,\n        0.55,\n        0.6,\n        0.65,\n        0.7,\n        0.75\n    ]\n\n    scores = []\n\n    for t in thresholds:\n\n        score = precision_at_threshold(\n            gt_boxes,\n            pred_boxes,\n            t\n        )\n\n        scores.append(score)\n\n    return np.mean(scores)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:42:45.194219Z","iopub.execute_input":"2026-06-28T19:42:45.194530Z","iopub.status.idle":"2026-06-28T19:42:45.207762Z","shell.execute_reply.started":"2026-06-28T19:42:45.194481Z","shell.execute_reply":"2026-06-28T19:42:45.206768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef evaluate_rsna(\n    model,\n    loader,\n    score_thresh=0.5\n):\n\n    model.eval()\n\n    image_scores = []\n\n    for images, targets in loader:\n\n        images_gpu = [\n            img.to(device)\n            for img in images\n        ]\n\n        outputs = model(images_gpu)\n\n        for output, target in zip(\n            outputs,\n            targets\n        ):\n\n            gt_boxes = (\n                target[\"boxes\"]\n                .cpu()\n                .numpy()\n                .tolist()\n            )\n\n            keep = (\n                output[\"scores\"]\n                .cpu()\n                .numpy()\n                >= score_thresh\n            )\n\n            pred_boxes = (\n                output[\"boxes\"]\n                .cpu()\n                .numpy()[keep]\n                .tolist()\n            )\n\n            score = image_score(\n                gt_boxes,\n                pred_boxes\n            )\n\n            image_scores.append(score)\n\n    return np.mean(image_scores)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:42:45.208724Z","iopub.execute_input":"2026-06-28T19:42:45.209367Z","iopub.status.idle":"2026-06-28T19:42:45.222518Z","shell.execute_reply.started":"2026-06-28T19:42:45.209335Z","shell.execute_reply":"2026-06-28T19:42:45.221799Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"score_a = evaluate_rsna(\n    model_a,\n    val_loader\n)\n\nprint(\n    f\"Model A RSNA Score: {score_a:.4f}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:42:45.223376Z","iopub.execute_input":"2026-06-28T19:42:45.223649Z","iopub.status.idle":"2026-06-28T19:43:39.624064Z","shell.execute_reply.started":"2026-06-28T19:42:45.223619Z","shell.execute_reply":"2026-06-28T19:43:39.623184Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"score_b = evaluate_rsna(\n    model_b,\n    val_loader\n)\n\nprint(\n    f\"Model B RSNA Score: {score_b:.4f}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:43:39.625193Z","iopub.execute_input":"2026-06-28T19:43:39.625536Z","iopub.status.idle":"2026-06-28T19:44:55.664447Z","shell.execute_reply.started":"2026-06-28T19:43:39.625512Z","shell.execute_reply":"2026-06-28T19:44:55.663615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results = pd.DataFrame({\n    \"Model\": [\n        \"FasterRCNN-ResNet50-FPN\",\n        \"FasterRCNN-ResNet50-FPN-V2\"\n    ],\n    \"RSNA Score\": [\n        score_a,\n        score_b\n    ]\n})\n\nresults","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:44:55.665285Z","iopub.execute_input":"2026-06-28T19:44:55.665585Z","iopub.status.idle":"2026-06-28T19:44:55.674226Z","shell.execute_reply.started":"2026-06-28T19:44:55.665562Z","shell.execute_reply":"2026-06-28T19:44:55.673556Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\n\ndef visualize_positive_predictions(\n    model,\n    dataset,\n    device,\n    threshold=0.5,\n    num_images=3\n):\n\n    model.eval()\n\n    shown = 0\n\n    for idx in range(len(dataset)):\n\n        img, target = dataset[idx]\n\n        if len(target[\"boxes\"]) == 0:\n            continue\n\n        with torch.no_grad():\n            pred = model([img.to(device)])[0]\n\n        pred = {\n            k: v.cpu()\n            for k, v in pred.items()\n        }\n\n        fig, axes = plt.subplots(1, 2, figsize=(12, 6))\n\n        image_np = img.permute(1, 2, 0).numpy()\n\n        # Ground Truth\n        axes[0].imshow(image_np, cmap=\"gray\")\n\n        for box in target[\"boxes\"]:\n\n            x1, y1, x2, y2 = box.numpy()\n\n            rect = patches.Rectangle(\n                (x1, y1),\n                x2 - x1,\n                y2 - y1,\n                linewidth=2,\n                edgecolor=\"green\",\n                facecolor=\"none\"\n            )\n\n            axes[0].add_patch(rect)\n\n        axes[0].set_title(\"Ground Truth\")\n        axes[0].axis(\"off\")\n\n        # Predictions\n        axes[1].imshow(image_np, cmap=\"gray\")\n\n        for box, score in zip(\n            pred[\"boxes\"],\n            pred[\"scores\"]\n        ):\n\n            if score < threshold:\n                continue\n\n            x1, y1, x2, y2 = box.numpy()\n\n            rect = patches.Rectangle(\n                (x1, y1),\n                x2 - x1,\n                y2 - y1,\n                linewidth=2,\n                edgecolor=\"red\",\n                facecolor=\"none\"\n            )\n\n            axes[1].add_patch(rect)\n\n            axes[1].text(\n                x1,\n                y1 - 5,\n                f\"{score:.2f}\",\n                color=\"red\"\n            )\n\n        axes[1].set_title(\"Prediction\")\n        axes[1].axis(\"off\")\n\n        plt.show()\n\n        shown += 1\n\n        if shown >= num_images:\n            break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:44:55.676507Z","iopub.execute_input":"2026-06-28T19:44:55.676719Z","iopub.status.idle":"2026-06-28T19:44:55.688749Z","shell.execute_reply.started":"2026-06-28T19:44:55.676698Z","shell.execute_reply":"2026-06-28T19:44:55.688156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_positive_predictions(\n    model_a,\n    val_ds,\n    device,\n    threshold=0.5,\n    num_images=3\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:44:55.689732Z","iopub.execute_input":"2026-06-28T19:44:55.690110Z","iopub.status.idle":"2026-06-28T19:44:56.909766Z","shell.execute_reply.started":"2026-06-28T19:44:55.690088Z","shell.execute_reply":"2026-06-28T19:44:56.909185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_positive_predictions(\n    model_a,\n    val_ds,\n    device,\n    threshold=0.7,\n    num_images=3\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:44:56.910570Z","iopub.execute_input":"2026-06-28T19:44:56.910870Z","iopub.status.idle":"2026-06-28T19:44:57.869160Z","shell.execute_reply.started":"2026-06-28T19:44:56.910836Z","shell.execute_reply":"2026-06-28T19:44:57.868299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_positive_predictions(\n    model_b,\n    val_ds,\n    device,\n    threshold=0.5,\n    num_images=3\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-28T19:44:57.870321Z","iopub.execute_input":"2026-06-28T19:44:57.870736Z","iopub.status.idle":"2026-06-28T19:44:58.985308Z","shell.execute_reply.started":"2026-06-28T19:44:57.870711Z","shell.execute_reply":"2026-06-28T19:44:58.984419Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# RSNA Pneumonia Detection Challenge – Model Comparison\n\n## Objective\n\nThe goal was to detect pneumonia regions in chest X-ray images using object detection models. Unlike a standard classification task, the model must both:\n\n1. Detect whether pneumonia is present.\n2. Predict bounding boxes around the affected lung regions.\n\nThe RSNA dataset provides DICOM chest X-ray images together with pneumonia bounding box annotations.\n\n---\n\n## Dataset Preparation\n\nThe dataset contains:\n\n- Chest X-ray images in DICOM format.\n- Bounding box annotations for positive pneumonia cases.\n- Negative cases without bounding boxes.\n\nDuring preprocessing:\n\n- DICOM images were loaded using `pydicom`.\n- Images were normalized to the range `[0,1]`.\n- Since Faster R-CNN expects 3-channel input, grayscale images were duplicated across three channels.\n- Images were resized from approximately **1024×1024** to **512×512** to reduce memory usage and training time.\n- Bounding box coordinates were scaled accordingly after resizing.\n\n---\n\n## Models\n\nTwo pretrained object detection models from Torchvision were used:\n\n### Model A\n**Faster R-CNN with ResNet50-FPN**\n\n```python\nfasterrcnn_resnet50_fpn()\n```\n\n### Model B\n**Faster R-CNN with ResNet50-FPN-V2**\n\n```python\nfasterrcnn_resnet50_fpn_v2()\n```\n\nBoth models were initialized with COCO-pretrained weights and their classification heads were replaced with a custom predictor for:\n\n- Background\n- Pneumonia\n\n\nThis allows the models to perform binary object detection on the RSNA dataset instead of predicting the original COCO classes.","metadata":{}},{"cell_type":"markdown","source":"### Discussion\n\nAlthough the original objective referenced Fast R-CNN and Faster R-CNN, this implementation compares two modern Faster R-CNN variants available in Torchvision: **FasterRCNN-ResNet50-FPN** and **FasterRCNN-ResNet50-FPN-V2**. Both models are two-stage detectors that use a Region Proposal Network (RPN) to generate candidate object regions, eliminating the need for external proposal methods such as Selective Search that were required by the original Fast R-CNN pipeline. The V2 model retains the same overall architecture but benefits from improved pretrained weights and a more advanced training recipe, resulting in better localization performance.\n\nThe experimental results show that **FasterRCNN-ResNet50-FPN-V2 achieved a higher RSNA score (0.586) than FasterRCNN-ResNet50-FPN (0.545)**, indicating improved detection and localization of pneumonia regions. However, these scores were obtained using only a subset of the available training data and a limited number of training epochs. Additional improvements could likely be achieved through longer training, data augmentation, hyperparameter tuning, and training on the full dataset. Furthermore, images were resized from 1024×1024 to 512×512 to reduce computational cost, which may have reduced some fine image details.\n\nThe visualization results also demonstrate the effect of confidence thresholding. For example, when the confidence threshold is set to 0.5, the model may display multiple overlapping detections with different confidence scores. Increasing the threshold to 0.7 or higher removes lower-confidence detections and produces cleaner visualizations. However, threshold selection represents a trade-off between precision and recall, and the optimal value should be determined through validation rather than visual inspection alone. Both models internally apply Non-Maximum Suppression (NMS), which removes highly overlapping duplicate detections before producing the final predictions.\n","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}