{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":107469,"databundleVersionId":13014309,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Union Ensemble Method\nThis code implements a Union Ensemble approach for combining two YOLO models. Here's how it works:\nEnsemble Strategy\n\nUnion approach: Combines ALL predictions from both models rather than filtering or averaging\nThree-step process:\n\nCommon predictions: Both models detect same object (IoU > 0.5, same class) → takes higher confidence\nUnique Model 1: Objects only Model 1 detected\nUnique Model 2: Objects only Model 2 detected\n\n\nFinal output: Common + Unique1 + Unique2 = Maximum detection coverage\n\nIoU-Based Matching\n\nUses Intersection over Union (IoU) to determine if predictions refer to same object\nDefault threshold: 0.5 (50% overlap)\nMatches predictions only if same class AND sufficient overlap\nPrevents duplicate detections of same object\n\nKey Algorithm\n\nRun both models on same image\nFind overlapping predictions using IoU calculation\nFor overlaps: select prediction with higher confidence\nCollect all non-overlapping predictions from both models\nCombine everything into final prediction set\n\nVisual Output\nColor-coded visualization:\n\n🔴 Red boxes: Common predictions (both models agree)\n🟢 Green boxes: Unique to Model 1 only\n🔵 Blue boxes: Unique to Model 2 only\nLabels: Show source (COMMON/M1/M2), class name, confidence score\n\nConsole Output\nFor each image, prints:\n📸 Processing: image_001.jpg\nModel 1 predictions: 15\nModel 2 predictions: 12\nCommon predictions: 8\nUnique to Model 1: 7\nUnique to Model 2: 4\nUnion ensemble predictions: 19\nAutomatic File Generation\n\nYOLO format labels: Individual .txt files for each image\nSubmission CSV: Competition-ready format with image_id and prediction_string\nAnnotated images: Visual results with color-coded bounding boxes\n\nAdvantages of Union Ensemble\n\nMaximum recall: Captures objects missed by individual models\nLeverages model diversity: Different models catch different objects\nConfidence-based selection: Uses best prediction when both models agree\nNo information loss: Preserves all valuable detections\n\nThis approach is particularly effective when models have complementary strengths - one might excel at small objects while another handles large objects better.\n\n**this is a custom made one for my problem. you can check out the next code to ensemble the 2 models using weighted_boxes_fusion**","metadata":{}},{"cell_type":"code","source":"from ultralytics import YOLO\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport csv\nfrom pathlib import Path\n\n# 1. Load both trained YOLO models\nmodel1 = YOLO(\"model1\")\nmodel2 = YOLO(\"model2\")\n\n# 2. Set test image directory and parameters\ntest_data_path = \"testimages/path\"\nconf_threshold = 0.05\nstart_idx = 0\nend_idx = 100\ndisplay_images = True #display images or not\n\n# 3. Create output directories\nos.makedirs(\"output_predictions\", exist_ok=True)\nos.makedirs(\"predictions/labels\", exist_ok=True)\n\n# 4. Get list of test images\nimage_files = sorted([\n    f for f in os.listdir(test_data_path) \n    if f.lower().endswith(('.jpg', '.jpeg', '.png'))\n])\n\nif end_idx is None or end_idx > len(image_files):\n    end_idx = len(image_files)\n\ndef get_all_predictions(results):\n    \"\"\"Get all predictions from YOLO results (no limit)\"\"\"\n    if len(results[0].boxes) == 0:\n        return []\n    \n    # Get boxes, confidences, and class IDs\n    boxes = results[0].boxes.xyxy.cpu().numpy()\n    confidences = results[0].boxes.conf.cpu().numpy()\n    class_ids = results[0].boxes.cls.cpu().numpy().astype(int)\n    class_names = results[0].names\n    \n    predictions = []\n    for idx in range(len(boxes)):\n        predictions.append({\n            'box': boxes[idx],\n            'confidence': confidences[idx],\n            'class_id': class_ids[idx],\n            'class_name': class_names[class_ids[idx]]\n        })\n    \n    return predictions\n\ndef find_common_predictions(pred1, pred2, iou_threshold=0.5):\n    \"\"\"Find common predictions between two models based on IoU and class\"\"\"\n    common_pairs = []\n    used_pred2_indices = set()\n    \n    for i, p1 in enumerate(pred1):\n        for j, p2 in enumerate(pred2):\n            if j in used_pred2_indices:\n                continue\n                \n            # Check if same class\n            if p1['class_id'] == p2['class_id']:\n                # Calculate IoU\n                iou = calculate_iou(p1['box'], p2['box'])\n                if iou > iou_threshold:\n                    common_pairs.append((i, j, p1, p2))\n                    used_pred2_indices.add(j)\n                    break\n    \n    return common_pairs\n\ndef calculate_iou(box1, box2):\n    \"\"\"Calculate Intersection over Union (IoU) of two bounding boxes\"\"\"\n    x1_min, y1_min, x1_max, y1_max = box1\n    x2_min, y2_min, x2_max, y2_max = box2\n    \n    # Calculate intersection\n    inter_x_min = max(x1_min, x2_min)\n    inter_y_min = max(y1_min, y2_min)\n    inter_x_max = min(x1_max, x2_max)\n    inter_y_max = min(y1_max, y2_max)\n    \n    if inter_x_max <= inter_x_min or inter_y_max <= inter_y_min:\n        return 0.0\n    \n    inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)\n    \n    # Calculate union\n    area1 = (x1_max - x1_min) * (y1_max - y1_min)\n    area2 = (x2_max - x2_min) * (y2_max - y2_min)\n    union_area = area1 + area2 - inter_area\n    \n    return inter_area / union_area if union_area > 0 else 0.0\n\ndef convert_to_yolo_format(predictions, img_width, img_height):\n    \"\"\"Convert predictions to YOLO format\"\"\"\n    yolo_lines = []\n    \n    for pred in predictions:\n        x1, y1, x2, y2 = pred['box']\n        conf = pred['confidence']\n        cls_id = pred['class_id']\n        \n        # Convert to YOLO format\n        x_center = ((x1 + x2) / 2) / img_width\n        y_center = ((y1 + y2) / 2) / img_height\n        width = (x2 - x1) / img_width\n        height = (y2 - y1) / img_height\n        \n        yolo_lines.append(f\"{int(cls_id)} {conf:.6f} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\")\n    \n    return yolo_lines\n\n# Store all predictions for submission\nall_predictions = []\n\n# 5. Run predictions and compare results\nfor idx in range(start_idx, end_idx):\n    image_path = os.path.join(test_data_path, image_files[idx])\n    \n    print(f\"\\n📸 Processing: {image_files[idx]}\")\n    \n    # Get predictions from both models\n    results1 = model1.predict(source=image_path, conf=conf_threshold, save=False, verbose=False)\n    results2 = model2.predict(source=image_path, conf=conf_threshold, save=False, verbose=False)\n    \n    # Get image dimensions\n    img_height, img_width = results1[0].orig_shape\n    \n    # Get ALL predictions from each model (no limit)\n    pred1 = get_all_predictions(results1)\n    pred2 = get_all_predictions(results2)\n    \n    print(f\"Model 1 predictions: {len(pred1)}\")\n    print(f\"Model 2 predictions: {len(pred2)}\")\n    \n    # Find common predictions\n    common_pairs = find_common_predictions(pred1, pred2)\n    \n    # Get common predictions (use higher confidence from both models)\n    common_predictions = []\n    for pair in common_pairs:\n        pred1_common = pair[2]\n        pred2_common = pair[3]\n        \n        # Use prediction with higher confidence\n        if pred1_common['confidence'] >= pred2_common['confidence']:\n            common_predictions.append(pred1_common)\n        else:\n            common_predictions.append(pred2_common)\n    \n    # Get unique predictions (not in common)\n    used_pred1_indices = {pair[0] for pair in common_pairs}\n    used_pred2_indices = {pair[1] for pair in common_pairs}\n    \n    unique_pred1 = [pred1[i] for i in range(len(pred1)) if i not in used_pred1_indices]\n    unique_pred2 = [pred2[i] for i in range(len(pred2)) if i not in used_pred2_indices]\n    \n    # Union ensemble: combine ALL predictions (common + unique from both models)\n    union_predictions = common_predictions + unique_pred1 + unique_pred2\n    \n    # Sort by confidence for better visualization\n    union_predictions.sort(key=lambda x: x['confidence'], reverse=True)\n    \n    print(f\"Common predictions: {len(common_predictions)}\")\n    print(f\"Unique to Model 1: {len(unique_pred1)}\")\n    print(f\"Unique to Model 2: {len(unique_pred2)}\")\n    print(f\"Union ensemble predictions: {len(union_predictions)}\")\n    \n    # Convert to YOLO format and save\n    base_name = os.path.splitext(image_files[idx])[0]\n    output_txt = os.path.join(\"predictions/labels\", f\"{base_name}.txt\")\n    \n    yolo_lines = convert_to_yolo_format(union_predictions, img_width, img_height)\n    \n    with open(output_txt, \"w\") as f:\n        for line in yolo_lines:\n            f.write(line + \"\\n\")\n    \n    # Store for submission CSV\n    pred_string = \" \".join(yolo_lines) if yolo_lines else \"no boxes\"\n    all_predictions.append({\n        \"image_id\": base_name,\n        \"prediction_string\": pred_string\n    })\n    \n    if display_images:\n        # Load original image\n        combined_image = cv2.imread(image_path)\n        combined_image_rgb = cv2.cvtColor(combined_image, cv2.COLOR_BGR2RGB)\n        \n        # Draw common predictions in RED\n        for pred in common_predictions:\n            x1, y1, x2, y2 = pred['box'].astype(int)\n            cv2.rectangle(combined_image_rgb, (x1, y1), (x2, y2), (255, 0, 0), 3)  # Red for common\n            label = f\"COMMON: {pred['class_name']} {pred['confidence']:.2f}\"\n            label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[0]\n            \n            # Background for text\n            cv2.rectangle(combined_image_rgb, (x1, y1 - label_size[1] - 10), \n                         (x1 + label_size[0], y1), (255, 0, 0), -1)\n            cv2.putText(combined_image_rgb, label, (x1, y1 - 5), \n                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)\n        \n        # Draw unique predictions from Model 1 in GREEN\n        for pred in unique_pred1:\n            x1, y1, x2, y2 = pred['box'].astype(int)\n            cv2.rectangle(combined_image_rgb, (x1, y1), (x2, y2), (0, 255, 0), 2)  # Green for Model 1 unique\n            label = f\"M1: {pred['class_name']} {pred['confidence']:.2f}\"\n            label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[0]\n            \n            # Background for text\n            cv2.rectangle(combined_image_rgb, (x1, y1 - label_size[1] - 10), \n                         (x1 + label_size[0], y1), (0, 255, 0), -1)\n            cv2.putText(combined_image_rgb, label, (x1, y1 - 5), \n                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)\n        \n        # Draw unique predictions from Model 2 in BLUE\n        for pred in unique_pred2:\n            x1, y1, x2, y2 = pred['box'].astype(int)\n            cv2.rectangle(combined_image_rgb, (x1, y1), (x2, y2), (0, 0, 255), 2)  # Blue for Model 2 unique\n            label = f\"M2: {pred['class_name']} {pred['confidence']:.2f}\"\n            label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[0]\n            \n            # Background for text\n            cv2.rectangle(combined_image_rgb, (x1, y1 - label_size[1] - 10), \n                         (x1 + label_size[0], y1), (0, 0, 255), -1)\n            cv2.putText(combined_image_rgb, label, (x1, y1 - 5), \n                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)\n        \n        # Display single combined image\n        plt.figure(figsize=(15, 10))\n        plt.imshow(combined_image_rgb)\n        plt.axis('off')\n        \n        # Create title with counts\n        title = f\"Union Ensemble: {image_files[idx]}\\n\"\n        title += f\"🔴 Common: {len(common_predictions)} | 🟢 Model1 Only: {len(unique_pred1)} | 🔵 Model2 Only: {len(unique_pred2)}\"\n        title += f\"\\n📄 Total Union Predictions: {len(union_predictions)}\"\n        plt.title(title, fontsize=14, pad=20)\n        plt.tight_layout()\n        plt.show()\n    \n    # Save single combined result\n    combined_save_image = cv2.imread(image_path)\n    combined_save_image_rgb = cv2.cvtColor(combined_save_image, cv2.COLOR_BGR2RGB)\n    \n    # Draw all predictions on save image\n    for pred in common_predictions:\n        x1, y1, x2, y2 = pred['box'].astype(int)\n        cv2.rectangle(combined_save_image_rgb, (x1, y1), (x2, y2), (255, 0, 0), 3)  # Red for common\n        label = f\"COMMON: {pred['class_name']} {pred['confidence']:.2f}\"\n        label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[0]\n        cv2.rectangle(combined_save_image_rgb, (x1, y1 - label_size[1] - 10), \n                     (x1 + label_size[0], y1), (255, 0, 0), -1)\n        cv2.putText(combined_save_image_rgb, label, (x1, y1 - 5), \n                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)\n    \n    for pred in unique_pred1:\n        x1, y1, x2, y2 = pred['box'].astype(int)\n        cv2.rectangle(combined_save_image_rgb, (x1, y1), (x2, y2), (0, 255, 0), 2)  # Green for Model 1\n        label = f\"M1: {pred['class_name']} {pred['confidence']:.2f}\"\n        label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[0]\n        cv2.rectangle(combined_save_image_rgb, (x1, y1 - label_size[1] - 10), \n                     (x1 + label_size[0], y1), (0, 255, 0), -1)\n        cv2.putText(combined_save_image_rgb, label, (x1, y1 - 5), \n                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)\n    \n    for pred in unique_pred2:\n        x1, y1, x2, y2 = pred['box'].astype(int)\n        cv2.rectangle(combined_save_image_rgb, (x1, y1), (x2, y2), (0, 0, 255), 2)  # Blue for Model 2\n        label = f\"M2: {pred['class_name']} {pred['confidence']:.2f}\"\n        label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[0]\n        cv2.rectangle(combined_save_image_rgb, (x1, y1 - label_size[1] - 10), \n                     (x1 + label_size[0], y1), (0, 0, 255), -1)\n        cv2.putText(combined_save_image_rgb, label, (x1, y1 - 5), \n                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)\n    \n    # Save combined image\n    combined_pil = Image.fromarray(combined_save_image_rgb)\n    combined_path = os.path.join(\"output_predictions\", f\"union_ensemble_{base_name}.jpg\")\n    combined_pil.save(combined_path)\n    print(f\"✅ Saved union ensemble: {combined_path}\")\n\nprint(f\"\\n[✅] All predictions saved in: predictions/labels\")\n\n# Create submission CSV\ndef create_submission_csv(\n    predictions_list,\n    output_csv: str = \"submission.csv\",\n    test_images_folder: str = None\n):\n    \"\"\"Create submission CSV from predictions list\"\"\"\n    \n    # Convert predictions list to DataFrame\n    submission_df = pd.DataFrame(predictions_list)\n    \n    # If test images folder is provided, check for missing images\n    if test_images_folder:\n        test_images_path = Path(test_images_folder)\n        allowed_extensions = (\".jpg\", \".png\", \".jpeg\")\n        test_images = {p.stem for p in test_images_path.glob(\"*\") if p.suffix.lower() in allowed_extensions}\n        \n        predicted_images = set(submission_df['image_id'].tolist())\n        missing_images = test_images - predicted_images\n        \n        # Add missing images with \"no boxes\"\n        for image_id in missing_images:\n            submission_df = pd.concat([\n                submission_df,\n                pd.DataFrame([{\"image_id\": image_id, \"prediction_string\": \"no boxes\"}])\n            ], ignore_index=True)\n    \n    # Sort by image_id for consistency\n    submission_df = submission_df.sort_values('image_id').reset_index(drop=True)\n    \n    # Save to CSV\n    submission_df.to_csv(output_csv, index=False, quoting=csv.QUOTE_MINIMAL)\n    print(f\"[notice] ✅ Submission saved to {output_csv}\")\n    print(f\"[notice] 📊 Total submissions: {len(submission_df)}\")\n    \n    return submission_df\n\n# Create submission file\nsubmission_df = create_submission_csv(\n    all_predictions,\n    output_csv=\"union_ensemble_submission.csv\",\n    test_images_folder=test_data_path\n)\n\nprint(\"\\n🎉 Union ensemble submission completed!\")\nprint(f\"📁 Label files: predictions/labels/\")\nprint(f\"📄 Submission CSV: union_ensemble_submission.csv\")\nprint(f\"🖼️ Visualization images: output_predictions/\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from ultralytics import YOLO\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport csv\nfrom pathlib import Path\nfrom ensemble_boxes import weighted_boxes_fusion\n\n# 1. Load both trained YOLO models\nmodel1 = YOLO(\"model1\")\nmodel2 = YOLO(\"model2\")\n\n# 2. Set test image directory and parameters\ntest_data_path = \"testimages/path\"\nconf_threshold = 0.05\nstart_idx = 0\nend_idx = 100\ndisplay_images = True #display images or not\n\n# WBF parameters\niou_thr = 0.5  # IoU threshold for WBF\nskip_box_thr = 0.0001  # Skip boxes with confidence lower than this\nconf_type = 'avg'  # How to calculate confidence in weighted boxes\n\n# 3. Create output directories\nos.makedirs(\"output_predictions\", exist_ok=True)\nos.makedirs(\"predictions/labels\", exist_ok=True)\n\n# 4. Get list of test images\nimage_files = sorted([\n    f for f in os.listdir(test_data_path) \n    if f.lower().endswith(('.jpg', '.jpeg', '.png'))\n])\n\nif end_idx is None or end_idx > len(image_files):\n    end_idx = len(image_files)\n\ndef get_predictions_for_wbf(results, img_width, img_height):\n    \"\"\"Get predictions from YOLO results in format suitable for WBF\"\"\"\n    if len(results[0].boxes) == 0:\n        return [], [], []\n    \n    # Get boxes, confidences, and class IDs\n    boxes = results[0].boxes.xyxy.cpu().numpy()\n    confidences = results[0].boxes.conf.cpu().numpy()\n    class_ids = results[0].boxes.cls.cpu().numpy().astype(int)\n    \n    # Convert to normalized coordinates for WBF (x1, y1, x2, y2 in range [0, 1])\n    normalized_boxes = []\n    for box in boxes:\n        x1, y1, x2, y2 = box\n        norm_box = [x1/img_width, y1/img_height, x2/img_width, y2/img_height]\n        normalized_boxes.append(norm_box)\n    \n    return normalized_boxes, confidences.tolist(), class_ids.tolist()\n\ndef wbf_predictions_to_display_format(boxes, scores, labels, class_names, img_width, img_height):\n    \"\"\"Convert WBF output back to display format\"\"\"\n    predictions = []\n    \n    for i in range(len(boxes)):\n        # Convert normalized coordinates back to pixel coordinates\n        x1, y1, x2, y2 = boxes[i]\n        pixel_box = [x1 * img_width, y1 * img_height, x2 * img_width, y2 * img_height]\n        \n        predictions.append({\n            'box': np.array(pixel_box),\n            'confidence': scores[i],\n            'class_id': int(labels[i]),\n            'class_name': class_names[int(labels[i])]\n        })\n    \n    return predictions\n\ndef convert_to_yolo_format(predictions, img_width, img_height):\n    \"\"\"Convert predictions to YOLO format\"\"\"\n    yolo_lines = []\n    \n    for pred in predictions:\n        x1, y1, x2, y2 = pred['box']\n        conf = pred['confidence']\n        cls_id = pred['class_id']\n        \n        # Convert to YOLO format\n        x_center = ((x1 + x2) / 2) / img_width\n        y_center = ((y1 + y2) / 2) / img_height\n        width = (x2 - x1) / img_width\n        height = (y2 - y1) / img_height\n        \n        yolo_lines.append(f\"{int(cls_id)} {conf:.6f} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\")\n    \n    return yolo_lines\n\n# Store all predictions for submission\nall_predictions = []\n\n# 5. Run predictions and apply WBF ensemble\nfor idx in range(start_idx, end_idx):\n    image_path = os.path.join(test_data_path, image_files[idx])\n    \n    print(f\"\\n📸 Processing: {image_files[idx]}\")\n    \n    # Get predictions from both models\n    results1 = model1.predict(source=image_path, conf=conf_threshold, save=False, verbose=False)\n    results2 = model2.predict(source=image_path, conf=conf_threshold, save=False, verbose=False)\n    \n    # Get image dimensions\n    img_height, img_width = results1[0].orig_shape\n    \n    # Get class names (assuming both models have same classes)\n    class_names = results1[0].names\n    \n    # Get predictions in WBF format\n    boxes1, scores1, labels1 = get_predictions_for_wbf(results1, img_width, img_height)\n    boxes2, scores2, labels2 = get_predictions_for_wbf(results2, img_width, img_height)\n    \n    print(f\"Model 1 predictions: {len(boxes1)}\")\n    print(f\"Model 2 predictions: {len(boxes2)}\")\n    \n    # Prepare data for WBF\n    boxes_list = [boxes1, boxes2]\n    scores_list = [scores1, scores2]\n    labels_list = [labels1, labels2]\n    weights = [1, 1]  # Equal weights for both models\n    \n    # Apply Weighted Boxes Fusion\n    if len(boxes1) > 0 or len(boxes2) > 0:\n        fused_boxes, fused_scores, fused_labels = weighted_boxes_fusion(\n            boxes_list, \n            scores_list, \n            labels_list, \n            weights=weights, \n            iou_thr=iou_thr, \n            skip_box_thr=skip_box_thr,\n            conf_type=conf_type\n        )\n        \n        # Convert back to display format\n        wbf_predictions = wbf_predictions_to_display_format(\n            fused_boxes, fused_scores, fused_labels, class_names, img_width, img_height\n        )\n    else:\n        wbf_predictions = []\n    \n    print(f\"WBF fused predictions: {len(wbf_predictions)}\")\n    \n    # Convert to YOLO format and save\n    base_name = os.path.splitext(image_files[idx])[0]\n    output_txt = os.path.join(\"predictions/labels\", f\"{base_name}.txt\")\n    \n    yolo_lines = convert_to_yolo_format(wbf_predictions, img_width, img_height)\n    \n    with open(output_txt, \"w\") as f:\n        for line in yolo_lines:\n            f.write(line + \"\\n\")\n    \n    # Store for submission CSV\n    pred_string = \" \".join(yolo_lines) if yolo_lines else \"no boxes\"\n    all_predictions.append({\n        \"image_id\": base_name,\n        \"prediction_string\": pred_string\n    })\n    \n    if display_images:\n        # Load original image\n        combined_image = cv2.imread(image_path)\n        combined_image_rgb = cv2.cvtColor(combined_image, cv2.COLOR_BGR2RGB)\n        \n        # Get individual model predictions for visualization\n        pred1 = wbf_predictions_to_display_format(boxes1, scores1, labels1, class_names, img_width, img_height)\n        pred2 = wbf_predictions_to_display_format(boxes2, scores2, labels2, class_names, img_width, img_height)\n        \n        # Draw Model 1 predictions in GREEN (thin lines)\n        for pred in pred1:\n            x1, y1, x2, y2 = pred['box'].astype(int)\n            cv2.rectangle(combined_image_rgb, (x1, y1), (x2, y2), (0, 255, 0), 1)  # Green thin\n            label = f\"M1: {pred['class_name']} {pred['confidence']:.2f}\"\n            cv2.putText(combined_image_rgb, label, (x1, y1 - 5), \n                       cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)\n        \n        # Draw Model 2 predictions in BLUE (thin lines)\n        for pred in pred2:\n            x1, y1, x2, y2 = pred['box'].astype(int)\n            cv2.rectangle(combined_image_rgb, (x1, y1), (x2, y2), (0, 0, 255), 1)  # Blue thin\n            label = f\"M2: {pred['class_name']} {pred['confidence']:.2f}\"\n            cv2.putText(combined_image_rgb, label, (x1, y2 + 15), \n                       cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), 1)\n        \n        # Draw WBF fused predictions in RED (thick lines)\n        for pred in wbf_predictions:\n            x1, y1, x2, y2 = pred['box'].astype(int)\n            cv2.rectangle(combined_image_rgb, (x1, y1), (x2, y2), (255, 0, 0), 3)  # Red thick\n            label = f\"WBF: {pred['class_name']} {pred['confidence']:.2f}\"\n            label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)[0]\n            \n            # Background for text\n            cv2.rectangle(combined_image_rgb, (x1, y1 - label_size[1] - 10), \n                         (x1 + label_size[0], y1), (255, 0, 0), -1)\n            cv2.putText(combined_image_rgb, label, (x1, y1 - 5), \n                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)\n        \n        # Display combined image\n        plt.figure(figsize=(15, 10))\n        plt.imshow(combined_image_rgb)\n        plt.axis('off')\n        \n        # Create title with counts\n        title = f\"WBF Ensemble: {image_files[idx]}\\n\"\n        title += f\"🟢 Model1: {len(pred1)} | 🔵 Model2: {len(pred2)} | 🔴 WBF Fused: {len(wbf_predictions)}\"\n        title += f\"\\nWBF Parameters: IoU={iou_thr}, conf_type={conf_type}\"\n        plt.title(title, fontsize=14, pad=20)\n        plt.tight_layout()\n        plt.show()\n    \n    # Save combined result\n    combined_save_image = cv2.imread(image_path)\n    combined_save_image_rgb = cv2.cvtColor(combined_save_image, cv2.COLOR_BGR2RGB)\n    \n    # Draw WBF predictions on save image\n    for pred in wbf_predictions:\n        x1, y1, x2, y2 = pred['box'].astype(int)\n        cv2.rectangle(combined_save_image_rgb, (x1, y1), (x2, y2), (255, 0, 0), 3)  # Red\n        label = f\"WBF: {pred['class_name']} {pred['confidence']:.2f}\"\n        label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)[0]\n        cv2.rectangle(combined_save_image_rgb, (x1, y1 - label_size[1] - 10), \n                     (x1 + label_size[0], y1), (255, 0, 0), -1)\n        cv2.putText(combined_save_image_rgb, label, (x1, y1 - 5), \n                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)\n    \n    # Save combined image\n    combined_pil = Image.fromarray(combined_save_image_rgb)\n    combined_path = os.path.join(\"output_predictions\", f\"wbf_ensemble_{base_name}.jpg\")\n    combined_pil.save(combined_path)\n    print(f\"✅ Saved WBF ensemble: {combined_path}\")\n\nprint(f\"\\n[✅] All predictions saved in: predictions/labels\")\n\n# Create submission CSV\ndef create_submission_csv(\n    predictions_list,\n    output_csv: str = \"submission.csv\",\n    test_images_folder: str = None\n):\n    \"\"\"Create submission CSV from predictions list\"\"\"\n    \n    # Convert predictions list to DataFrame\n    submission_df = pd.DataFrame(predictions_list)\n    \n    # If test images folder is provided, check for missing images\n    if test_images_folder:\n        test_images_path = Path(test_images_folder)\n        allowed_extensions = (\".jpg\", \".png\", \".jpeg\")\n        test_images = {p.stem for p in test_images_path.glob(\"*\") if p.suffix.lower() in allowed_extensions}\n        \n        predicted_images = set(submission_df['image_id'].tolist())\n        missing_images = test_images - predicted_images\n        \n        # Add missing images with \"no boxes\"\n        for image_id in missing_images:\n            submission_df = pd.concat([\n                submission_df,\n                pd.DataFrame([{\"image_id\": image_id, \"prediction_string\": \"no boxes\"}])\n            ], ignore_index=True)\n    \n    # Sort by image_id for consistency\n    submission_df = submission_df.sort_values('image_id').reset_index(drop=True)\n    \n    # Save to CSV\n    submission_df.to_csv(output_csv, index=False, quoting=csv.QUOTE_MINIMAL)\n    print(f\"[notice] ✅ Submission saved to {output_csv}\")\n    print(f\"[notice] 📊 Total submissions: {len(submission_df)}\")\n    \n    return submission_df\n\n# Create submission file\nsubmission_df = create_submission_csv(\n    all_predictions,\n    output_csv=\"wbf_ensemble_submission.csv\",\n    test_images_folder=test_data_path\n)\n\nprint(\"\\n🎉 WBF ensemble submission completed!\")\nprint(f\"📁 Label files: predictions/labels/\")\nprint(f\"📄 Submission CSV: wbf_ensemble_submission.csv\")\nprint(f\"🖼️ Visualization images: output_predictions/\")\nprint(f\"⚙️ WBF Parameters used: IoU threshold={iou_thr}, conf_type={conf_type}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"SAHI (Slicing Aided Hyper Inference) Implementation\nThis code implements SAHI - a technique that improves object detection by slicing large images into smaller patches for better small object detection.\n\n1. SAHI Method\n\n- Core concept: Divides large images into overlapping slices/patches\n- Problem solved: Standard YOLO struggles with small objects in high-resolution images\n- Solution: Process smaller patches where small objects appear larger relative to patch size\n- Post-processing: Combines predictions from all slices using NMS (Non-Maximum Suppression)\n\nthis is the documentation link-https://docs.ultralytics.com/guides/sahi-tiled-inference/","metadata":{}},{"cell_type":"markdown","source":"![](https://github.com/ultralytics/docs/releases/download/0/yolo11n-vs-sahi-yolo11n.avif)","metadata":{}},{"cell_type":"code","source":"import cv2\nfrom sahi import AutoDetectionModel\nfrom sahi.predict import get_sliced_prediction\nfrom sahi.utils.cv import visualize_object_predictions\n\n# 1. Define your paths\nmodel_path = \"/kaggle/input/igyh/pytorch/default/1/last (5).pt\"\nimage_path = \"/kaggle/input/multi-instance-object-detection-challenge/Starter_Dataset/TestImages/images/IMG_9617.jpg\"\noutput_path = \"/kaggle/working/result.jpg\"\n\n# 2. Load model with reduced computation\ndetection_model = AutoDetectionModel.from_pretrained(\n    model_type=\"ultralytics\",\n    model_path=model_path,\n    confidence_threshold=0.35,  # Slightly higher to reduce false positives\n    device=\"cpu\",  # Force CPU usage\n    load_at_init=True\n)\n\n# 3. Optimized SAHI prediction with fewer slices\nresult = get_sliced_prediction(\n    image_path,\n    detection_model,\n    slice_height=480,  # Larger slices = fewer total slices\n    slice_width=480,\n    overlap_height_ratio=0.15,  # Reduced overlap\n    overlap_width_ratio=0.15,\n    perform_standard_pred=True,  # First try standard prediction\n    postprocess_type=\"NMS\",\n    postprocess_match_threshold=0.4  # Slightly more aggressive NMS\n)\n\n# 4. Check if we got enough detections (50-60)\nif len(result.object_prediction_list) < 50:\n    # Fallback to slightly more slices if needed\n    result = get_sliced_prediction(\n        image_path,\n        detection_model,\n        slice_height=400,\n        slice_width=400,\n        overlap_height_ratio=0.2,\n        perform_standard_pred=False\n    )\n\n# 5. Filter to get top 50-60 most confident detections\nfinal_predictions = sorted(\n    result.object_prediction_list,\n    key=lambda x: x.score.value,\n    reverse=True\n)[:60]  # Take top 60\n\n# 6. Visualize results\nvisualization_result = visualize_object_predictions(\n    cv2.imread(image_path),\n    final_predictions,\n    output_dir=\"/kaggle/working/\",\n    file_name=\"optimized_result\",\n    export_format=\"jpg\"\n)\n\nprint(f\"Total detections: {len(final_predictions)}\")\nprint(f\"Visualization saved to: {output_path}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"🔍 What is Albumentations?\nA fast, flexible library for image augmentations, optimized for computer vision tasks like object detection. Key features:\n\n60+ transforms (geometric, color, etc.)\n\nGPU acceleration support\n\nPixel-perfect for object detection tasks\n\n**Integration with Ultralytics YOLO:**\n\nWhen the Albumentations package is installed, it integrates directly with Ultralytics YOLO training mode and automatically applies augmentations during training123.\nThe integration applies specific augmentations, including Blur, Median Blur, Grayscale conversion, and CLAHE (Contrast Limited Adaptive Histogram Equalization), each with a low default probability (p=0.01) to mimic real-world visual artifacts134.\nAugmentations are applied on-the-fly during training, so the dataset size does not increase, but the model sees varied versions of each image during each epoch5.","metadata":{}},{"cell_type":"code","source":"# Install the required packages\n!pip install albumentations ultralytics\nfrom ultralytics import YOLO\n\n# Load a pre-trained model\nmodel = YOLO(\"yolo11n.pt\")\n\n# Train the model\nresults = model.train(data=\"coco8.yaml\", epochs=100, imgsz=640)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- this is where the agumentation file is located-ultralytics/data/augment.py\n- you can check out this documentation for more information-https://docs.ultralytics.com/reference/data/augment/","metadata":{}},{"cell_type":"markdown","source":"**📌 Final Tips and Notes**\n- The first cell is my custom ensemble implementation. If it helps, feel free to use it.\n\n- The next cell demonstrates Weighted Boxes Fusion (WBF) for ensembling — a solid method to boost model performance.\n\n- I’ve also included a SAHI + YOLOv11 setup. Note: this works only with YOLOv11 models. If you're using YOLOv8, you’ll need to modify it accordingly.\n\n- Honestly, SAHI isn’t very helpful for this competition since it doesn’t allow targeting a single specific image. It’s better suited for real-world scenarios with many small objects with high resuluction images.\n\n- If you install the albumentations package, it will add light augmentations automatically (with low probability), which might be useful in some cases.","metadata":{}},{"cell_type":"markdown","source":"**All the best to everyone participating in this competition!\nLet’s learn together and grow together — this is a great place to sharpen your skills. 🚀**","metadata":{}}]}