{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":107469,"databundleVersionId":13058354,"sourceType":"competition"},{"sourceId":12474415,"sourceType":"datasetVersion","datasetId":7870336},{"sourceId":12578044,"sourceType":"datasetVersion","datasetId":7943689},{"sourceId":12801346,"sourceType":"datasetVersion","datasetId":7907405}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install ultralytics\n!pip install ensemble-boxes","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:01:41.703969Z","iopub.execute_input":"2025-08-18T22:01:41.704157Z","iopub.status.idle":"2025-08-18T22:03:08.789664Z","shell.execute_reply.started":"2025-08-18T22:01:41.704140Z","shell.execute_reply":"2025-08-18T22:03:08.788859Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport shutil\n\n# Original val directories\nval_root        = Path(\"/kaggle/input/multi-class-object-detection-challenge/Starter_Dataset/val\")\nval_images_dir  = val_root / \"images\"\nval_labels_dir  = val_root / \"labels\"\n\n# Destination for real images only\nreal_val_root   = Path(\"/kaggle/working/val_real\")\nreal_images_dir = real_val_root / \"images\"\nreal_labels_dir = real_val_root / \"labels\"\n\n# Make sure destination dirs exist\nreal_images_dir.mkdir(parents=True, exist_ok=True)\nreal_labels_dir.mkdir(parents=True, exist_ok=True)\n\n# Copy only files starting with IMG and with jpg/png/jpeg extensions\nfor img_path in val_images_dir.iterdir():\n    if img_path.suffix.lower() in [\".png\", \".jpg\", \".jpeg\"] and img_path.name.startswith(\"IMG\"):\n        shutil.copy(img_path, real_images_dir / img_path.name)\n        # Corresponding label\n        label_path = val_labels_dir / f\"{img_path.stem}.txt\"\n        if label_path.exists():\n            shutil.copy(label_path, real_labels_dir / label_path.name)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:03:08.790706Z","iopub.execute_input":"2025-08-18T22:03:08.790966Z","iopub.status.idle":"2025-08-18T22:03:25.958634Z","shell.execute_reply.started":"2025-08-18T22:03:08.790925Z","shell.execute_reply":"2025-08-18T22:03:25.958088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import yaml\nfrom pathlib import Path\n\n# Paths\nbase_path       = \"/kaggle/input/multi-class-object-detection-challenge/Starter_Dataset\"\nsynthetic_base  = \"/kaggle/input/extra-synthetic-data\"\nsynthetic_base2 = '/kaggle/input/falcon-multiclass-cheerios-soupv2/falcon-multiclass-cheerios-soupV2'\nsynthetic_base3 = '/kaggle/working/synthetic_soup_extra'\n# Gather synthetic train/val image dirs\nsynthetic_train_dirs = [str(p) for p in Path(synthetic_base).rglob(\"train/images\")]\nsynthetic_val_dirs   = [str(p) for p in Path(synthetic_base).rglob(\"val/images\")]\n# Gather synthetic train/val image dirs\nsynthetic_train_dirs2 = [str(p) for p in Path(synthetic_base2).rglob(\"train/images\")]\nsynthetic_val_dirs2   = [str(p) for p in Path(synthetic_base2).rglob(\"val/images\")]\n# Gather synthetic train/val image dirs\nsynthetic_train_dirs3 = [str(p) for p in Path(synthetic_base3).rglob(\"train/images\")]\nsynthetic_val_dirs3   = [str(p) for p in Path(synthetic_base3).rglob(\"val/images\")]\n\n# Build YAML dict\ndata_yaml = {\n    \"train\": (\n        [\n            f\"{base_path}/train/images\",\n        '/kaggle/input/sample-synthetic-data-generated/home/ubuntu/Output/2025-07-15-06-26-28/train/images',\n        '/kaggle/input/sample-synthetic-data-generated/home/ubuntu/Output/2025-07-15-06-26-28/val/images',\n        ]\n        + synthetic_train_dirs\n        + synthetic_val_dirs\n        + synthetic_train_dirs2\n        + synthetic_val_dirs2\n        # + synthetic_train_dirs3\n        # + synthetic_val_dirs3\n    ),\n    \"val\":   '/kaggle/working/val_real/images',\n    \"test\": f\"{base_path}/TestImages\",\n    \"nc\":   2,\n    \"names\": [\"cheerios\", \"Soup\"]\n}\n\n# Save to data.yaml\nwith open(\"data.yaml\", \"w\") as f:\n    yaml.safe_dump(data_yaml, f, default_flow_style=False)\n\nprint(\"Created data.yaml with the following content:\")\nprint(yaml.safe_dump(data_yaml, default_flow_style=False))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:03:25.991180Z","iopub.execute_input":"2025-08-18T22:03:25.991741Z","iopub.status.idle":"2025-08-18T22:03:29.631891Z","shell.execute_reply.started":"2025-08-18T22:03:25.991714Z","shell.execute_reply":"2025-08-18T22:03:29.631270Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom ultralytics import YOLO\nfrom pathlib import Path\nimport csv\nimport os\nimport random\nimport torch\n# Set random seeds for reproducibility\nnp.random.seed(42)\nrandom.seed(42)\ntorch.manual_seed(42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:03:29.632673Z","iopub.execute_input":"2025-08-18T22:03:29.632884Z","iopub.status.idle":"2025-08-18T22:03:35.010701Z","shell.execute_reply.started":"2025-08-18T22:03:29.632865Z","shell.execute_reply":"2025-08-18T22:03:35.009886Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = YOLO(\"yolo11x.pt\")\ndata_yaml = '/kaggle/working/data.yaml'\n\nmodel.train(\n    data=data_yaml,\n    epochs=25,                \n    batch=4,                   \n    imgsz=672,\n    patience=500,               \n    optimizer='SGD',        \n    lr0=0.001,\n    lrf = 0.001,\n    weight_decay=0.0003,       \n    cos_lr=True,               \n    save_period=10,             \n    workers=4,\n    # Augmentations\n    close_mosaic=5,\n    hsv_h=0.0,\n    hsv_s=0.0,\n    hsv_v=0.0,\n    flipud=0,\n    fliplr=0.5,\n    translate=0.01,\n    scale=0.25,\n    # shear=0.05,\n    mixup = 0.05,\n    cutmix = 0.15,\n    warmup_epochs= 3,\n    warmup_momentum= 1,\n    \n    exist_ok = True,\n    project = 'runs1/train',\n    plots = True, \n    augment = True,\n    conf = 0.001,\n    iou = 0.3,\n    multi_scale = True,\n    freeze = 4\n    # agnostic_nms=True,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:08:12.621264Z","iopub.execute_input":"2025-08-18T22:08:12.621590Z","iopub.status.idle":"2025-08-18T22:09:03.000906Z","shell.execute_reply.started":"2025-08-18T22:08:12.621566Z","shell.execute_reply":"2025-08-18T22:09:02.999417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = YOLO(\"yolo12x.pt\")\n# data_yaml = \"/kaggle/input/multi-instance-object-detection-challenge/Starter_Dataset/yolo_params.yaml\"\ndata_yaml = '/kaggle/working/data.yaml'\n\nmodel.train(\n    data=data_yaml,\n    epochs=25,           \n    batch=16,                   \n    imgsz=512,\n    patience=500,               \n    optimizer='SGD', \n    lr0=0.001,\n    lrf = 0.001,  \n    weight_decay=0.0001,       \n    cos_lr=True,               \n    save_period=10,             \n    workers=8,\n    # Augmentations\n    close_mosaic=5,\n    hsv_h=0.015,\n    hsv_s=0.2,\n    hsv_v=0.25,\n    flipud=0.0,\n    fliplr=0.5,\n    translate=0.01,\n    scale=0.75,\n    shear=2,\n    perspective = 0.001,\n    mixup = 0.1,\n    cutmix = 0.25,\n    warmup_epochs= 5,\n    warmup_momentum= 1,\n    exist_ok = True,\n    project = 'runs2/train',\n    plots = True, \n    augment = True,\n    conf = 0.001,\n    iou = 0.3,\n    freeze = 6,\n    # multi_scale = True\n    \n)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:06:20.376828Z","iopub.status.idle":"2025-08-18T22:06:20.377185Z","shell.execute_reply.started":"2025-08-18T22:06:20.376999Z","shell.execute_reply":"2025-08-18T22:06:20.377014Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = YOLO(\"yolov8x.pt\")\n# data_yaml = \"/kaggle/input/multi-instance-object-detection-challenge/Starter_Dataset/yolo_params.yaml\"\ndata_yaml = '/kaggle/working/data.yaml'\n\nmodel.train(\n    data=data_yaml,\n    epochs=35,                \n    batch=32,                   \n    imgsz=640,\n    patience=500,               \n    optimizer='SGD',         \n    lr0=0.001,\n    lrf = 0.0001,\n    weight_decay=0.0002,       \n    cos_lr=True,               \n    save_period=10,             \n    workers=8,\n    # Augmentations\n    close_mosaic=10,\n    hsv_h=0.0,\n    hsv_s=0.0,\n    hsv_v=0.0,\n    flipud=0,\n    fliplr=0.5,\n    translate=0.05,\n    erasing = 0.5,\n    scale=0.75,\n    # shear=0.2,\n    mixup = 0.01,\n    cutmix = 0.10,\n    warmup_epochs= 3,\n    warmup_momentum= 1,\n    \n    # exist_ok = True,\n    project = 'runs3/train',\n    plots = True, \n    augment = True,\n    conf = 0.001,\n    iou = 0.35,\n    freeze = 8,\n    # multi_scale = True,\n    \n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:06:20.378364Z","iopub.status.idle":"2025-08-18T22:06:20.378706Z","shell.execute_reply.started":"2025-08-18T22:06:20.378540Z","shell.execute_reply":"2025-08-18T22:06:20.378555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport shutil\n\n# source and destination\nsrc = Path(\"/kaggle/working/runs1/train/train/weights/best.pt\")\ndst = Path(\"/kaggle/working/yolo11 best model.pt\")   \n\n# sanity checks\nif not src.exists():\n    raise FileNotFoundError(f\"Not found: {src}\")\n\ndst.parent.mkdir(parents=True, exist_ok=True)\n\n# copy (overwrite if exists)\nshutil.copy2(src, dst)\n\nprint(f\"Saved copy to: {dst.resolve()}  | size: {dst.stat().st_size/1e6:.2f} MB\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:06:20.379848Z","iopub.status.idle":"2025-08-18T22:06:20.380137Z","shell.execute_reply.started":"2025-08-18T22:06:20.380020Z","shell.execute_reply":"2025-08-18T22:06:20.380033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport shutil\n\n# source and destination\nsrc = Path(\"/kaggle/working/runs2/train/train/weights/best.pt\")\ndst = Path(\"/kaggle/working/yolo12 best model.pt\") \n\n# sanity checks\nif not src.exists():\n    raise FileNotFoundError(f\"Not found: {src}\")\n\ndst.parent.mkdir(parents=True, exist_ok=True)\n\n# copy (overwrite if exists)\nshutil.copy2(src, dst)\n\nprint(f\"Saved copy to: {dst.resolve()}  | size: {dst.stat().st_size/1e6:.2f} MB\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport shutil\n\n# source and destination\nsrc = Path(\"/kaggle/working/runs3/train/train/weights/best.pt\")\ndst = Path(\"/kaggle/working/yolov8 best model.pt\") \n\n# sanity checks\nif not src.exists():\n    raise FileNotFoundError(f\"Not found: {src}\")\n\ndst.parent.mkdir(parents=True, exist_ok=True)\n\n# copy (overwrite if exists)\nshutil.copy2(src, dst)\n\nprint(f\"Saved copy to: {dst.resolve()}  | size: {dst.stat().st_size/1e6:.2f} MB\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nfrom pathlib import Path\nfrom PIL import Image\nimport pandas as pd\nimport csv\nfrom ensemble_boxes import weighted_boxes_fusion\n\ndef filter_invalid_boxes(boxes, scores, labels, conf_thr):\n    # keep only non-degenerate boxes; ignore confidence here by passing conf_thr < 0\n    filtered_boxes, filtered_scores, filtered_labels = [], [], []\n    for b, s, l in zip(boxes, scores, labels):\n        if abs(b[2] - b[0]) <= 1e-6 or abs(b[3] - b[1]) <= 1e-6:\n            continue\n        if s < conf_thr:  # will be disabled by using conf_thr=-1.0\n            continue\n        filtered_boxes.append(b)\n        filtered_scores.append(s)\n        filtered_labels.append(l)\n    return filtered_boxes, filtered_scores, filtered_labels\n\ndef run_inference(models,\n                  image_sizes,\n                  test_images_path,\n                  conf=0.15,          # user-threshold to apply AFTER WBF\n                  iou_thr=0.45,\n                  max_det=50,\n                  pre_conf=1e-3):     # very low pre-threshold so we don't drop boxes before WBF\n    \"\"\"\n    Runs YOLO inference at multiple scales, stores raw preds (no confidence filtering),\n    saves per-model/size CSVs, and returns a nested dict for WBF.\n    \"\"\"\n    image_paths = [p for p in Path(test_images_path).glob(\"*\")\n                   if p.suffix.lower() in {\".jpg\", \".jpeg\", \".png\"}]\n    predictions = {}\n\n    for model_idx, model in enumerate(models):\n        model.eval()\n        predictions[model_idx] = {}\n\n        for size in image_sizes:\n            predictions[model_idx][size] = {}\n            rows = []\n\n            for img_path in image_paths:\n                image_id = img_path.stem\n                img = Image.open(img_path).convert(\"RGB\")\n                w, h = img.size\n\n                # run prediction with a very low conf to keep candidates\n                results = model.predict(\n                    source=str(img_path),\n                    conf=pre_conf,\n                    iou=iou_thr,\n                    max_det=max_det,\n                    augment=True,\n                    imgsz=size,\n                    verbose=False\n                )\n                res = results[0]\n\n                raw_boxes  = res.boxes.xyxy.cpu().numpy().tolist()\n                raw_scores = res.boxes.conf.cpu().numpy().tolist()\n                raw_labels = res.boxes.cls.cpu().numpy().tolist()\n\n                # normalize and keep geometry-valid boxes only (no conf filtering here)\n                rel_boxes = [[x1/w, y1/h, x2/w, y2/h] for x1, y1, x2, y2 in raw_boxes]\n                boxes, scores, labels = filter_invalid_boxes(rel_boxes, raw_scores, raw_labels, conf_thr=-1.0)\n\n                predictions[model_idx][size][image_id] = {\n                    \"boxes\":  boxes,\n                    \"scores\": scores,\n                    \"labels\": labels\n                }\n\n                # per-model CSV (raw, unfiltered by confidence)\n                if boxes:\n                    pred_str = \" \".join(\n                        f\"{int(lbl)} {score:.6f} \"\n                        f\"{(b[0]+b[2])/2:.6f} {(b[1]+b[3])/2:.6f} \"\n                        f\"{(b[2]-b[0]):.6f} {(b[3]-b[1]):.6f}\"\n                        for b, score, lbl in zip(boxes, scores, labels)\n                    )\n                else:\n                    pred_str = \"no boxes\"\n\n                rows.append({\"image_id\": image_id, \"prediction_string\": pred_str})\n\n            df = pd.DataFrame(rows)\n            csv_path = f\"submission_{model_idx}_{size}.csv\"\n            df.to_csv(csv_path, index=False, quoting=csv.QUOTE_MINIMAL)\n            print(f\"[saved] {csv_path}\")\n            print(df.head(5))\n\n    return predictions\n\ndef apply_wbf_and_save_final_submission(predictions,\n                                        image_ids,\n                                        output_path=\"submission.csv\",\n                                        iou_thr=0.4,\n                                        skip_box_thr=0.0,    # do NOT filter before fusion\n                                        conf_post=0.15,      # filter AFTER WBF using this threshold\n                                        conf_type=\"avg\"):    # or \"max\" if you prefer\n    \"\"\"\n    Applies WBF across all models & sizes, then filters fused boxes by conf_post,\n    and saves a single merged submission CSV.\n    \"\"\"\n    final_rows = []\n\n    for image_id in image_ids:\n        all_boxes, all_scores, all_labels = [], [], []\n\n        for model_preds in predictions.values():\n            for size_preds in model_preds.values():\n                pred = size_preds.get(image_id)\n                if not pred or not pred[\"boxes\"]:\n                    continue\n                all_boxes.append(pred[\"boxes\"])\n                all_scores.append(pred[\"scores\"])\n                all_labels.append(pred[\"labels\"])\n\n        if not all_boxes:\n            pred_str = \"no boxes\"\n        else:\n            fb, fs, fl = weighted_boxes_fusion(\n                all_boxes,\n                all_scores,\n                all_labels,\n                iou_thr=iou_thr,\n                skip_box_thr=skip_box_thr,\n                conf_type=conf_type\n            )\n\n            keep = [i for i, s in enumerate(fs) if s >= conf_post]\n            if not keep:\n                pred_str = \"no boxes\"\n            else:\n                pred_str = \" \".join(\n                    f\"{int(fl[i])} {fs[i]:.6f} \"\n                    f\"{(fb[i][0]+fb[i][2])/2:.6f} {(fb[i][1]+fb[i][3])/2:.6f} \"\n                    f\"{(fb[i][2]-fb[i][0]):.6f} {(fb[i][3]-fb[i][1]):.6f}\"\n                    for i in keep\n                )\n\n        final_rows.append({\"image_id\": image_id, \"prediction_string\": pred_str})\n\n    pd.DataFrame(final_rows).to_csv(output_path, index=False, quoting=csv.QUOTE_MINIMAL)\n    print(f\"[notice] ✅ WBF submission saved to {output_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:06:20.381697Z","iopub.status.idle":"2025-08-18T22:06:20.381954Z","shell.execute_reply.started":"2025-08-18T22:06:20.381836Z","shell.execute_reply":"2025-08-18T22:06:20.381847Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom pathlib import Path\nimport pandas as pd\nimport csv\nfrom ultralytics import YOLO\nfrom ensemble_boxes import weighted_boxes_fusion\nfrom PIL import Image\n\nmodel_paths = [\n    # '/kaggle/working/runs2/train/train/weights/last.pt',\n    # '/kaggle/working/runs1/train/train/weights/last.pt',\n    # '/kaggle/working/runs3/train/train/weights/last.pt',\n    # '/kaggle/working/runs2/train/train/weights/best.pt',\n    '/kaggle/working/yolo11 best model.pt',\n    '/kaggle/working/yolo12 best model.pt',\n    '/kaggle/working/yolov8 best model.pt',\n]\n\ntest_images_path = \"/kaggle/input/multi-class-object-detection-challenge/testImages/images\"\noutput_dir = \"/kaggle/working/predictions/labels\"\n\nconf = 0.00001\niou_thr = 0.35\nskip_box_thr = conf\n# image_sizes = [640,800,864,1024,1216,512,1344,2048]\n# image_sizes = [512,640,1024,864,1216,2048]\nimage_sizes = [800,1024,2048]\n\n\nmodels = [YOLO(path) for path in model_paths]\npredictions = run_inference(models, image_sizes, test_images_path,conf=conf,iou_thr=iou_thr)\n\nimage_ids = list(next(iter(next(iter(predictions.values())).values())).keys())\n\napply_wbf_and_save_final_submission(predictions, image_ids,iou_thr=0.4,skip_box_thr=0.001,conf_post=0.14)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:06:20.383044Z","iopub.status.idle":"2025-08-18T22:06:20.383295Z","shell.execute_reply.started":"2025-08-18T22:06:20.383178Z","shell.execute_reply":"2025-08-18T22:06:20.383191Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nfrom typing import Union, Dict\n\ndef keep_topk_per_class(df: pd.DataFrame, k: Union[int, Dict[int, int]]) -> pd.DataFrame:\n    \"\"\"\n    Keep only the top-k highest-confidence boxes *per class* for each image.\n    - df must have columns: ['image_id', 'prediction_string']\n    - prediction_string format: 'cls conf xc yc w h ...' (normalized coords)\n    - k can be an int (same K for all classes) or a dict {class_id: K_for_that_class}\n    - Rows with 'no boxes' remain unchanged.\n    \"\"\"\n    def _per_image_topk(pred_str: str) -> str:\n        if not isinstance(pred_str, str) or pred_str.strip().lower() == \"no boxes\":\n            return \"no boxes\"\n\n        toks = pred_str.strip().split()\n        n = len(toks) // 6\n        if n == 0:\n            return \"no boxes\"\n\n        # group by class -> list of (conf, cls, xc, yc, w, h)\n        groups = {}\n        for i in range(n):\n            seg = toks[i*6:(i+1)*6]\n            if len(seg) != 6:\n                continue\n            try:\n                cls  = int(float(seg[0]))\n                conf = float(seg[1])\n                xc   = float(seg[2]); yc = float(seg[3])\n                w    = float(seg[4]); h  = float(seg[5])\n            except Exception:\n                continue\n            groups.setdefault(cls, []).append((conf, cls, xc, yc, w, h))\n\n        kept = []\n        for cls, items in groups.items():\n            items.sort(key=lambda x: x[0], reverse=True)  # by confidence\n            k_cls = k.get(cls, len(items)) if isinstance(k, dict) else int(k)\n            kept.extend(items[:max(0, k_cls)])\n\n        if not kept:\n            return \"no boxes\"\n\n        # (optional) sort final by confidence desc for readability\n        kept.sort(key=lambda x: x[0], reverse=True)\n        return \" \".join(f\"{cls} {conf:.6f} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}\"\n                        for conf, cls, xc, yc, w, h in kept)\n\n    out = df.copy()\n    out[\"prediction_string\"] = out[\"prediction_string\"].apply(_per_image_topk)\n    return out\n\n# ---- usage ----\nsub = pd.read_csv('/kaggle/working/submission.csv')\n\n# Same K for every class:\nsub_topk = keep_topk_per_class(sub, k=2) # keep only top k=3 conf bboxes\n\n\nsub_topk.to_csv('/kaggle/working/submission.csv', index=False)\nprint(\"saved -> /kaggle/working/submission.csv\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nfrom PIL import Image\nfrom matplotlib.patches import Rectangle\n\ndef plot_submission_predictions(\n    submission_csv: str,\n    test_images_path: str,\n    K: int = 5,\n    min_conf: float = 0.0\n):\n    \"\"\"\n    Plot the first K images from submission.csv with their predicted boxes,\n    but only show boxes with confidence >= min_conf.\n    \n    submission_csv:   path to your final submission.csv\n    test_images_path: folder containing the test images\n    K:                number of images to visualize\n    min_conf:         minimum confidence threshold for drawing a box\n    \"\"\"\n    df = pd.read_csv(submission_csv)\n    img_folder = Path(test_images_path)\n    \n    for _, row in df.head(K).iterrows():\n        image_id = row[\"image_id\"]\n        pred_str = row[\"prediction_string\"]\n        \n        # locate the image file\n        matches = list(img_folder.glob(f\"{image_id}.*\"))\n        if not matches:\n            print(f\"⚠️  Could not find file for {image_id}\")\n            continue\n        img = Image.open(matches[0])\n        \n        fig, ax = plt.subplots(figsize=(8, 6))\n        ax.imshow(img)\n        ax.axis(\"off\")\n        \n        if pred_str.lower() != \"no boxes\":\n            toks = pred_str.split()\n            for i in range(0, len(toks), 6):\n                lbl   = int(toks[i])\n                score = float(toks[i+1])\n                if score < min_conf:\n                    continue\n                x_c   = float(toks[i+2])\n                y_c   = float(toks[i+3])\n                w     = float(toks[i+4])\n                h     = float(toks[i+5])\n                \n                # convert normalized center w,h to absolute top-left corner + size\n                x1    = (x_c - w/2) * img.width\n                y1    = (y_c - h/2) * img.height\n                abs_w = w * img.width\n                abs_h = h * img.height\n                \n                rect = Rectangle((x1, y1), abs_w, abs_h,\n                                 fill=False, edgecolor=\"red\", lw=2)\n                ax.add_patch(rect)\n                ax.text(\n                    x1, y1 - 3,\n                    f\"{lbl}:{score:.2f}\",\n                    color=\"yellow\", fontsize=10,\n                    backgroundcolor=\"black\", alpha=0.7\n                )\n        \n        plt.show()\nplot_submission_predictions(\n    submission_csv=\"/kaggle/working/submission.csv\",\n    test_images_path= \"/kaggle/input/multi-class-object-detection-challenge/testImages/images\",\n    K=280,\n    min_conf=0.005   # only show boxes with confidence ≥ min_conf\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T22:06:20.384489Z","iopub.status.idle":"2025-08-18T22:06:20.384729Z","shell.execute_reply.started":"2025-08-18T22:06:20.384612Z","shell.execute_reply":"2025-08-18T22:06:20.384625Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}