{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":12537987,"sourceType":"datasetVersion","datasetId":7915408},{"sourceId":12578044,"sourceType":"datasetVersion","datasetId":7943689},{"sourceId":250085529,"sourceType":"kernelVersion"},{"sourceId":487612,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":388879,"modelId":407801},{"sourceId":490575,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":390128,"modelId":408939}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Falcon Multiclass Object Detection (v2)\n\nThis notebook upgrades our original Falcon Cheerios-Soup dataset by adding **6 synthetic scenarios** (Scenario1–6) and retrains a YOLOv11X model on the combined set.  \nWe then perform a multi‐scale ensemble and export a Kaggle submission.\n\n**Contents**  \n1. Setup & Imports  \n2. Directory & Data Inspection  \n3. Build `data_v2.yaml`  \n4. Visualize Samples  \n5. Train YOLOv11X on Combined Dataset  \n6. Run Inference & Weighted Boxes Fusion  \n7. Export Submission  \n","metadata":{}},{"cell_type":"markdown","source":"## 1. Setup & Dependencies\n\nFirst, install the required packages and import all the libraries we’ll need. We’ll also fix our random seeds for reproducibility.\n\n","metadata":{}},{"cell_type":"code","source":"# Install YOLO (Ultralytics) and ensemble-boxes for WBF\n!pip install -q ultralytics ensemble-boxes","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T10:52:56.815212Z","iopub.execute_input":"2025-07-27T10:52:56.816002Z","iopub.status.idle":"2025-07-27T10:54:20.655091Z","shell.execute_reply.started":"2025-07-27T10:52:56.815967Z","shell.execute_reply":"2025-07-27T10:54:20.654289Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Standard libs\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom PIL import Image\n\n# PyTorch & YOLO\nimport torch\nfrom ultralytics import YOLO\n\n# Ensembling\nfrom ensemble_boxes import weighted_boxes_fusion\n\n# Fix random seeds for reproducibility\nseed = 42\nrandom.seed(seed)\nnp.random.seed(seed)\ntorch.manual_seed(seed)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(seed)\n\nprint(\"🔧 Dependencies loaded and seeds fixed.\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-27T10:54:20.656727Z","iopub.execute_input":"2025-07-27T10:54:20.657287Z","iopub.status.idle":"2025-07-27T10:54:29.881018Z","shell.execute_reply.started":"2025-07-27T10:54:20.657259Z","shell.execute_reply":"2025-07-27T10:54:29.880173Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Directory Structure & Quick Inspection\n\nWe have:\n\n- **Original Falcon** under `/kaggle/input/falcon-multiclass-cheerios-soup/...`  \n- **Six new scenarios** under `/kaggle/input/falcon-multiclass-cheerios-soupV2/.../Scenario{1..6}`  \n- **Competition VAL/TEST** under `/kaggle/input/multi-class-object-detection-challenge/...`\n","metadata":{}},{"cell_type":"code","source":"base1 = Path('/kaggle/input/falcon-multiclass-cheerios-soup/falcon-multiclass-cheerios-soup')\nbase2 = Path('/kaggle/input/falcon-multiclass-cheerios-soupv2/falcon-multiclass-cheerios-soupV2')\n\nprint(\"Original Falcon:\")\nfor split in ['train','val']:\n    imgs = list((base1/split/'images').glob('*.*'))\n    labs = list((base1/split/'labels').glob('*.txt'))\n    print(f\"  {split}: {len(imgs)} imgs, {len(labs)} labels\")\n\nfor i in range(1,7):\n    d = base2/f\"Scenario{i}\"\n    t_imgs = len(list((d/'train'/'images').glob('*.*')))\n    v_imgs = len(list((d/'val'  /'images').glob('*.*')))\n    print(f\"Scenario{i}: train={t_imgs}, val={v_imgs}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T10:54:29.881973Z","iopub.execute_input":"2025-07-27T10:54:29.882340Z","iopub.status.idle":"2025-07-27T10:54:30.062777Z","shell.execute_reply.started":"2025-07-27T10:54:29.882320Z","shell.execute_reply":"2025-07-27T10:54:30.061972Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3. Build `data.yaml` Configuration\n\nHere we’ll use **our own Falcon** dataset for training (both its `train` and `val` folders),  \nand the **Multi-class Object Detection Challenge** for validation and test.\n","metadata":{}},{"cell_type":"code","source":"import yaml\nfrom pathlib import Path\n\n# 1. Mount points\nfalcon_mount = Path('/kaggle/input/falcon-multiclass-cheerios-soup/falcon-multiclass-cheerios-soup')\nv2_mount     = Path('/kaggle/input/falcon-multiclass-cheerios-soupv2/falcon-multiclass-cheerios-soupV2')\nchal_mount   = Path('/kaggle/input/multi-class-object-detection-challenge/Starter_Dataset')\n\n# 2. Auto-discover original Falcon root\ntrain_img_dirs = list(falcon_mount.glob('**/train/images'))\nif not train_img_dirs:\n    raise FileNotFoundError(f\"No nested train/images under {falcon_mount}\")\nfalcon_root = train_img_dirs[0].parents[1]  # go up two levels\n\n# 3. Auto-discover V2 root (assumes scenarios at v2_mount/ScenarioX/train/images)\nv2_img_dirs = list(v2_mount.glob('**/Scenario1/train/images'))\nif not v2_img_dirs:\n    raise FileNotFoundError(f\"No Scenario1/train/images under {v2_mount}\")\nv2_root = v2_img_dirs[0].parents[2]\n\n# 4. Build train list\ntrain_dirs = [\n    str(falcon_root/'train'/'images'),\n    str(falcon_root/'val'  /'images')  # if you also want to oversample the original val\n]\nfor i in range(1, 7):\n    train_dirs += [\n        str(v2_root/f\"Scenario{i}\" / 'train' / 'images'),\n        str(v2_root/f\"Scenario{i}\" / 'val'   / 'images')\n    ]\n\n# 5. Use **only** the competition val folder for validation\nval_dirs = [\n    str(chal_mount/'val'/'images')\n]\n\n# 6. Test set\ntest_dir = str(chal_mount/'testImages'/'images')\n\n# 7. Assemble and write YAML\ndata = {\n    'train': train_dirs,\n    'val':   val_dirs,\n    'test':  test_dir,\n    'nc':    2,\n    'names': ['cheerios', 'soup']\n}\n\nwith open('data.yaml', 'w') as f:\n    yaml.safe_dump(data, f, sort_keys=False, default_flow_style=False)\n\nprint(\"✅ data.yaml:\")\nprint(open('data.yaml').read())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T10:54:30.064498Z","iopub.execute_input":"2025-07-27T10:54:30.064709Z","iopub.status.idle":"2025-07-27T10:54:35.981973Z","shell.execute_reply.started":"2025-07-27T10:54:30.064692Z","shell.execute_reply":"2025-07-27T10:54:35.981090Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 4. Visualize Annotated Samples\n\nLet’s plot a few random training images with their bounding boxes and class labels.  \nThis helps us verify that our labels align correctly with the images before training.\n","metadata":{}},{"cell_type":"code","source":"# 3. Plot random samples from ALL merged train folders (original + scenarios)\nfrom pathlib import Path\nimport random\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom PIL import Image\n\n# Define roots\norig_root = Path('/kaggle/input/falcon-multiclass-cheerios-soup/falcon-multiclass-cheerios-soup')\nv2_root   = Path('/kaggle/input/falcon-multiclass-cheerios-soupv2/falcon-multiclass-cheerios-soupV2')\n\n# 1. Build list of (images, labels) for original + each Scenario\ntrain_pairs = [\n    (orig_root / 'train' / 'images', orig_root / 'train' / 'labels'),\n]\nfor i in range(1, 7):\n    scen = v2_root / f'Scenario{i}'\n    train_pairs += [\n        (scen / 'train' / 'images', scen / 'train' / 'labels'),\n        (scen / 'val'   / 'images', scen / 'val'   / 'labels'),\n    ]\n\n# 2. Gather all (img_path, lbl_path) tuples\nall_samples = []\nfor img_dir, lbl_dir in train_pairs:\n    if img_dir.exists() and lbl_dir.exists():\n        for img_path in img_dir.glob('*.*'):\n            lbl_path = lbl_dir / f\"{img_path.stem}.txt\"\n            all_samples.append((img_path, lbl_path))\n\n# 3. Sample a few examples\nnum_to_show = 6\nsampled = random.sample(all_samples, k=min(len(all_samples), num_to_show))\n\nclasses = ['cheerios', 'soup']\n\n# 4. Plot\nfor img_path, lbl_path in sampled:\n    img = Image.open(img_path)\n    fig, ax = plt.subplots(figsize=(6, 6))\n    ax.imshow(img)\n    if lbl_path.exists():\n        with open(lbl_path) as f:\n            for line in f:\n                cls_id, x_c, y_c, w, h = map(float, line.split())\n                img_w, img_h = img.size\n                x1 = (x_c - w/2) * img_w\n                y1 = (y_c - h/2) * img_h\n                rect = patches.Rectangle((x1, y1), w*img_w, h*img_h,\n                                         linewidth=2, edgecolor='red', facecolor='none')\n                ax.add_patch(rect)\n                ax.text(x1, y1, classes[int(cls_id)],\n                        color='white', fontsize=10,\n                        bbox=dict(facecolor='red', alpha=0.5, pad=0.5))\n    ax.axis('off')\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T10:54:35.982729Z","iopub.execute_input":"2025-07-27T10:54:35.983027Z","iopub.status.idle":"2025-07-27T10:54:38.566669Z","shell.execute_reply.started":"2025-07-27T10:54:35.983002Z","shell.execute_reply":"2025-07-27T10:54:38.565810Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### 5. Train the YOLOv11X Model\n\nWe’ll train for 60 epochs, using SGD, Cosine LR schedule, and the same augmentations and hyperparameters from the baseline notebook.\n","metadata":{}},{"cell_type":"code","source":"from ultralytics import YOLO\n\n# Toggle training on/off\nTRAIN = True\n\nif TRAIN:\n    model = YOLO(\"yolo11x.pt\")  # veya kendi checkpoint’in\n\n    model.train(\n        data=\"data.yaml\",\n        epochs=77,\n        batch=16,\n        imgsz=512,\n        optimizer=\"SGD\",\n        lr0=0.002,\n        lrf=0.0001,\n        weight_decay=0.0001,\n        dropout=0.3,\n        dfl=1.00,            # 1.5 -> 0.75 (DFL’i azaltıyoruz)\n        cos_lr=True,\n        patience=100,\n        save_period=10,\n        project=\"runs2/train\",  # yeni proje klasörü\n        exist_ok=True,\n        plots=True,\n        augment=True,        # default augment’lar\n        mosaic=1.0,          # tam mosaic\n        mixup=0.25,           # mixup arttı\n        cutmix=0.25,          # cutmix arttı\n        copy_paste=0.05,      # copy-paste arttı\n        close_mosaic=10,\n        hsv_h=0.05, hsv_s=1.0, hsv_v=0.75,\n        flipud=0.1, fliplr=0.6,\n        translate=0.1, scale=0.6, shear=0.02,\n        warmup_epochs=5, warmup_momentum=1,\n        workers=4,\n        conf=0.25, iou=0.5\n    )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-27T10:54:38.567567Z","iopub.execute_input":"2025-07-27T10:54:38.567787Z","execution_failed":"2025-07-27T11:30:15.746Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 6. Plot Validation Losses & Confusion Matrix\n\nHere we plot the epoch-by-epoch validation losses (box, classification, DFL)  \nand then display the confusion matrix that was saved by `model.val()`.\n","metadata":{}},{"cell_type":"code","source":"# 8.1 Load metrics and plot validation losses & DFL loss\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nfrom PIL import Image\n\n# Point to your training run directory\nexp_dir   = Path(\"/kaggle/working/runs2/train/train\")  # adjust if different\nresults_csv = exp_dir / \"results.csv\"\n\n# Load the per-epoch metrics\nresults = pd.read_csv(results_csv)\n\n# Plot validation losses over epochs\nplt.figure(figsize=(10, 5))\nplt.plot(results['epoch'], results['val/box_loss'], label='val box loss')\nplt.plot(results['epoch'], results['val/cls_loss'], label='val cls loss')\nplt.plot(results['epoch'], results['val/dfl_loss'], label='val dfl loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('YOLO Validation Losses')\nplt.legend()\nplt.grid(True)\nplt.show()\n\n# 8.2 Display the confusion matrix image(s)\ncm_paths = [\n    exp_dir / \"results.png\",\n    exp_dir / \"confusion_matrix.png\"\n]\nfor p in cm_paths:\n    if p.exists():\n        img = Image.open(p)\n        plt.figure(figsize=(8, 8))\n        plt.imshow(img)\n        plt.axis('off')\n        plt.show()\n    else:\n        print(f\"No file at {p}\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-27T11:30:15.748Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 7. Inference Helper Functions\n\nDefine two utilities:\n\n1. **`filter_invalid_boxes`** drops any zero‐area boxes.  \n2. **`run_inference`** runs batched, multi‐scale inference over a directory of test images.\n","metadata":{}},{"cell_type":"code","source":"import csv\nimport pandas as pd\nfrom pathlib import Path\nfrom PIL import Image\nfrom ultralytics import YOLO\nfrom ensemble_boxes import weighted_boxes_fusion\n\ndef filter_invalid_boxes(boxes, scores, labels):\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 and abs(b[3] - b[1]) > 1e-6:\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, image_sizes, test_images_path, conf=0.25, iou_thr=0.5):\n    test_dir = Path(test_images_path)\n    image_paths = [p for p in test_dir.glob('*') if p.suffix.lower() in ('.jpg','.jpeg','.png')]\n    predictions = {}\n\n    for mi, model in enumerate(models):\n        model.eval()\n        predictions[mi] = {}\n        for size in image_sizes:\n            predictions[mi][size] = {}\n            for img_path in image_paths:\n                image_id = img_path.stem\n                img = Image.open(img_path)\n                w, h = img.size\n\n                # run predict\n                results = model.predict(source=str(img_path),\n                                        conf=conf, iou=iou_thr,\n                                        imgsz=size, augment=True,\n                                        verbose=False)\n                # collect all boxes for this image/size\n                boxes, scores, labels = [], [], []\n                for r in results:\n                    if r.boxes is None: \n                        continue\n                    boxes = r.boxes.xyxy.cpu().numpy().tolist()\n                    scores = r.boxes.conf.cpu().numpy().tolist()\n                    labels = r.boxes.cls.cpu().numpy().tolist()\n\n                # normalize & filter\n                norm_boxes = [[x1/w, y1/h, x2/w, y2/h] for x1,y1,x2,y2 in boxes]\n                norm_boxes, scores, labels = filter_invalid_boxes(norm_boxes, scores, labels)\n\n                predictions[mi][size][image_id] = {\"boxes\": norm_boxes,\n                                                  \"scores\": scores,\n                                                  \"labels\": labels}\n    return predictions\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-27T11:30:15.748Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 8. Apply WBF & Save Submission\n\nLoad your model checkpoints, run `run_inference()`, then fuse all predictions per image  \nwith **Weighted Boxes Fusion** and write out the final `submission.csv`.\n","metadata":{}},{"cell_type":"code","source":"# 1. Specify your checkpoints and test folder\nmodel_paths = [\n    '/kaggle/input/3lc-yolo-baseline-submission/Duality-3LC-Kaggle/run-1/weights/best.pt',\n    '/kaggle/working/runs2/train/train/weights/best.pt',\n    '/kaggle/input/best3/pytorch/default/1/best-3.pt',\n    '/kaggle/input/best-2.pt/pytorch/bestpt/1/best-2.pt',]\ntest_images_path = '/kaggle/input/multi-class-object-detection-challenge/testImages/images'\n\n# 2. Load models\nmodels = [YOLO(p) for p in model_paths]\n\n# 3. Run batched, multi‐scale inference\nimage_sizes = [640, 800, 864]\npredictions = run_inference(models, image_sizes, test_images_path,\n                            conf=0.05, iou_thr=0.35)\n\n# 4. Collect all image_ids\nfirst_model = predictions[next(iter(predictions))]\nfirst_scale = first_model[next(iter(first_model))]\nimage_ids = list(first_scale.keys())\n\n# 5. Fuse & build submission rows\nrows = []\nfor img_id in image_ids:\n    all_boxes, all_scores, all_labels = [], [], []\n    for model_preds in predictions.values():\n        for size_preds in model_preds.values():\n            if img_id in size_preds:\n                p = size_preds[img_id]\n                if p['boxes']:\n                    all_boxes.append(p['boxes'])\n                    all_scores.append(p['scores'])\n                    all_labels.append(p['labels'])\n    if all_boxes:\n        fb, fs, fl = weighted_boxes_fusion(all_boxes, all_scores, all_labels,\n                                           iou_thr=0.35, skip_box_thr=0.01)\n        pred_str = \" \".join(\n            f\"{int(l)} {s:.6f} {(b[0]+b[2])/2:.6f} {(b[1]+b[3])/2:.6f} {(b[2]-b[0]):.6f} {(b[3]-b[1]):.6f}\"\n            for b, s, l in zip(fb, fs, fl)\n        )\n    else:\n        pred_str = \"no boxes\"\n    rows.append({'image_id': img_id, 'prediction_string': pred_str})\n\n# 6. Save to CSV\nsubmission_df = pd.DataFrame(rows)\nsubmission_df.to_csv('submission.csv', index=False, quoting=csv.QUOTE_MINIMAL)\nprint(f\"✅ submission.csv created with {len(submission_df)} entries.\")\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-27T11:30:15.749Z"}},"outputs":[],"execution_count":null}]}