{"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":"gpu","dataSources":[{"sourceId":117876,"databundleVersionId":14198377,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install 3lc-ultralytics 3lc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T12:48:39.929802Z","iopub.execute_input":"2025-11-18T12:48:39.93043Z","iopub.status.idle":"2025-11-18T12:48:48.094272Z","shell.execute_reply.started":"2025-11-18T12:48:39.930396Z","shell.execute_reply":"2025-11-18T12:48:48.093503Z"},"_kg_hide-output":true,"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nfrom pathlib import Path\nimport matplotlib.pyplot as plt\nimport torch\nimport tlc\nfrom pathlib import Path\nimport pandas as pd\nfrom IPython.display import display\nimport tlc\nfrom tlc_ultralytics import YOLO, Settings\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T13:39:40.440296Z","iopub.execute_input":"2025-11-18T13:39:40.441225Z","iopub.status.idle":"2025-11-18T13:39:40.472361Z","shell.execute_reply.started":"2025-11-18T13:39:40.441187Z","shell.execute_reply":"2025-11-18T13:39:40.471391Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# Check environment\nprint(\"Environment Check:\")\nprint(\"=\" * 50)\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"3LC version: {tlc.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")\n\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n    print(\n        f\"GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB\"\n    )\nelse:\n    print(\"!!! No GPU detected - training will be slower on CPU\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T12:45:58.745135Z","iopub.execute_input":"2025-11-18T12:45:58.745434Z","iopub.status.idle":"2025-11-18T12:46:35.850823Z","shell.execute_reply.started":"2025-11-18T12:45:58.745411Z","shell.execute_reply":"2025-11-18T12:46:35.849893Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set up file paths\nWORK_DIR = Path(\"/kaggle/input/the-3lc-cotton-weed-detection-challenge/cotton_weed_competition_dataset\")  # Current directory\nDATASET_YAML = WORK_DIR / \"dataset.yaml\"\n\n# Verify paths exist\nprint(\"Verifying dataset structure...\")\nprint(\"=\" * 50)\n\nif not DATASET_YAML.exists():\n    print(f\"Could not find {DATASET_YAML}\")\n    print(f\"Current directory: {Path.cwd()}\")\n    print(\"Please make sure dataset.yaml is in the current directory\")\n    raise FileNotFoundError(f\"Dataset config not found: {DATASET_YAML}\")\n\nprint(f\"✅ Dataset config: {DATASET_YAML}\")\nprint(f\"✅ Working directory: {WORK_DIR.resolve()}\")\n\n# Display dataset configuration\nprint(\"\\n Dataset Configuration:\")\nprint(\"-\" * 50)\nwith open(DATASET_YAML, \"r\") as f:\n    config_content = f.read()\n    print(config_content)\n\n# Count dataset files\ntrain_images = list((WORK_DIR / \"train\" / \"images\").glob(\"*.jpg\"))\ntrain_labels = list((WORK_DIR / \"train\" / \"labels\").glob(\"*.txt\"))\nval_images = list((WORK_DIR / \"val\" / \"images\").glob(\"*.jpg\"))\nval_labels = list((WORK_DIR / \"val\" / \"labels\").glob(\"*.txt\"))\ntest_images = list((WORK_DIR / \"test\" / \"images\").glob(\"*.jpg\"))\n\nprint(\"\\n Dataset Statistics:\")\nprint(\"-\" * 50)\nprint(f\"✅ Training:   {len(train_images)} images, {len(train_labels)} labels\")\nprint(f\"✅ Validation: {len(val_images)} images, {len(val_labels)} labels\")\nprint(f\"✅ Test: {len(test_images)} images\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T12:46:40.27652Z","iopub.execute_input":"2025-11-18T12:46:40.277066Z","iopub.status.idle":"2025-11-18T12:46:40.392258Z","shell.execute_reply.started":"2025-11-18T12:46:40.27704Z","shell.execute_reply":"2025-11-18T12:46:40.391508Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Setup\nTRAIN_IMAGES = WORK_DIR / \"train\" / \"images\"\nTRAIN_LABELS = WORK_DIR / \"train\" / \"labels\"\nCLASS_NAMES = [\"Carpetweed\", \"Morning Glory\", \"Palmer Amaranth\"]\ncolors = [(0, 255, 0), (255, 0, 0), (0, 0, 255)]\n\n# Find examples\nexamples = {}\nfor label_file in TRAIN_LABELS.glob(\"*.txt\"):\n    if label_file.stat().st_size > 0:\n        with open(label_file, \"r\") as f:\n            class_id = int(f.readline().split()[0])\n            if class_id not in examples:\n                image_file = TRAIN_IMAGES / f\"{label_file.stem}.jpg\"\n                if image_file.exists():\n                    examples[class_id] = image_file\n                    print(f\"✓ {CLASS_NAMES[class_id]}: {image_file.name}\")\n\n# Display\nif examples:\n    fig, axes = plt.subplots(1, len(examples), figsize=(15, 5))\n    for idx, (class_id, img_path) in enumerate(examples.items()):\n        img = cv2.cvtColor(cv2.imread(str(img_path)), cv2.COLOR_BGR2RGB)\n        h, w = img.shape[:2]\n        \n        # Draw bounding boxes\n        with open(TRAIN_LABELS / f\"{img_path.stem}.txt\", \"r\") as f:\n            for line in f:\n                parts = line.strip().split()\n                if int(parts[0]) == class_id:\n                    xc, yc, bw, bh = map(float, parts[1:5])\n                    x1, y1 = int((xc - bw/2) * w), int((yc - bh/2) * h)\n                    x2, y2 = int((xc + bw/2) * w), int((yc + bh/2) * h)\n                    cv2.rectangle(img, (x1, y1), (x2, y2), colors[class_id], 3)\n                    cv2.putText(img, CLASS_NAMES[class_id], (x1, y1-10), \n                               cv2.FONT_HERSHEY_SIMPLEX, 0.8, colors[class_id], 2)\n        \n        axes[idx].imshow(img)\n        axes[idx].set_title(f\"{CLASS_NAMES[class_id]}\")\n        axes[idx].axis(\"off\")\n    \n    plt.tight_layout()\n    plt.show()\n    print(\"✅ Examples displayed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T12:46:42.672186Z","iopub.execute_input":"2025-11-18T12:46:42.672901Z","iopub.status.idle":"2025-11-18T12:46:55.558792Z","shell.execute_reply.started":"2025-11-18T12:46:42.672873Z","shell.execute_reply":"2025-11-18T12:46:55.557638Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tlc\nfrom pathlib import Path\n\n# Define constants for 3LC registration\nPROJECT_NAME = \"kaggle_cotton_weed_detection\"\nDATASET_NAME = \"cotton_weed_det3\"\nWORK_DIR = Path(\"/kaggle/input/the-3lc-cotton-weed-detection-challenge/cotton_weed_competition_dataset\")\nDATASET_YAML = WORK_DIR / \"dataset.yaml\"\n\ntry:\n    # Check if tables already exist\n    existing_train = tlc.Table.from_names(\n        project_name=PROJECT_NAME,\n        dataset_name=DATASET_NAME,\n        table_name=f\"{DATASET_NAME}-train1\",\n    )\n    existing_val = tlc.Table.from_names(\n        project_name=PROJECT_NAME,\n        dataset_name=DATASET_NAME,\n        table_name=f\"{DATASET_NAME}-val1\",\n    )\n\n    print(\"\\n⚠️  Tables already exist!\")\n    print(f\" Training: {len(existing_train)} samples\")\n    print(f\" Validation: {len(existing_val)} samples\")\n    print(\"\\n✅ Using existing tables (no duplicates created)\")\n    print(\" This cell is safe to run multiple times!\")\n\n    # Set variables for compatibility\n    train_table = existing_train\n    val_table = existing_val\n\nexcept Exception:\n    # Tables don't exist, create them\n    print(\"\\n✅ No existing tables - creating new ones...\")\n\n    # Create training table\n    print(\"\\n Creating training table...\")\n    train_table = tlc.Table.from_yolo(\n        dataset_yaml_file=str(DATASET_YAML),\n        split=\"train\",\n        task=\"detect\",\n        dataset_name=DATASET_NAME,\n        project_name=PROJECT_NAME,\n        table_name=f\"{DATASET_NAME}-train1\",\n    )\n\n    # Create validation table\n    print(\" Creating validation table...\")\n    val_table = tlc.Table.from_yolo(\n        dataset_yaml_file=str(DATASET_YAML),\n        split=\"val\",\n        task=\"detect\",\n        dataset_name=DATASET_NAME,\n        project_name=PROJECT_NAME,\n        table_name=f\"{DATASET_NAME}-val1\",\n    )\n\n# Display registration results\nprint(\"\\n✅ Tables created successfully!\")\nprint(\"=\" * 70)\nprint(\"\\n Training Table:\")\nprint(f\"   Samples: {len(train_table)}\")\nprint(f\"   URL: {train_table.url}\")\n\nprint(\"\\n Validation Table:\")\nprint(f\"   Samples: {len(val_table)}\")\nprint(f\"   URL: {val_table.url}\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T12:47:04.416806Z","iopub.execute_input":"2025-11-18T12:47:04.417132Z","iopub.status.idle":"2025-11-18T12:47:14.937214Z","shell.execute_reply.started":"2025-11-18T12:47:04.417109Z","shell.execute_reply":"2025-11-18T12:47:14.936479Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# STEP 1: Load Tables for Training\n# ============================================================================\n# Define 3LC project constants\nPROJECT_NAME = \"kaggle_cotton_weed_detection\"\nDATASET_NAME = \"cotton_weed_det3\"\n\nprint(\"=\" * 70)\nprint(\"LOADING TABLES FOR TRAINING\")\nprint(\"=\" * 70)\n\ntry:\n    # ========================================================================\n    # OPTION 1: Load by Name (Recommended - Automatic Latest Version)\n    # ========================================================================\n    # This automatically loads the latest table version (includes Dashboard edits)\n\n    train_table_latest = tlc.Table.from_names(\n        project_name=PROJECT_NAME,\n        dataset_name=DATASET_NAME,\n        table_name=f\"{DATASET_NAME}-train1\",\n    ).latest()\n\n    val_table_latest = tlc.Table.from_names(\n        project_name=PROJECT_NAME,\n        dataset_name=DATASET_NAME,\n        table_name=f\"{DATASET_NAME}-val1\",\n    ).latest()\n\n    print(\n        f\"\\n✅ Training table loaded: {len(train_table_latest)} samples (latest version)\"\n    )\n    print(\n        f\"✅ Validation table loaded: {len(val_table_latest)} samples (latest version)\"\n    )\n\n    # Prepare tables dictionary for training\n    tables = {\"train\": train_table_latest, \"val\": val_table_latest}\n\n\n    print(\"\\n\" + \"=\" * 70)\n    print(\"✅ Tables Ready!\")\n    print(\"=\" * 70)\n\nexcept Exception as e:\n    print(f\"\\n Error loading tables: {e}\")\n    print(\"\\n💡 Troubleshooting:\")\n    print(\"   1. Make sure you ran Data Registration Cell at least once\")\n    print(\"   2. Check that PROJECT_NAME and DATASET_NAME match your setup\")\n    print(\"   3. Verify tables exist in Dashboard: https://dashboard.3lc.ai\")\n    raise\n\n# ============================================================================\n# STEP 2: Training Configuration\n# ============================================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"YOLOV8N TRAINING WITH 3LC TRACKING\")\nprint(\"=\" * 70)\n\n# ============================================================================\n# TRAINING CONSTANTS - Change these for each iteration\n# ============================================================================\nRUN_NAME = \"yolov8n_baseline\"  # Change for each run (e.g., \"v2_fixed_labels\")\nRUN_DESCRIPTION = \"Baseline YOLOv8n with default hyperparameters\"\n\n# Hyperparameters (customize these!)\nEPOCHS = 10  # Number of training epochs\nBATCH_SIZE = 16  # Batch size (adjust based on GPU memory)\nIMAGE_SIZE = 640  # Input image size (FIXED by competition rules)\nDEVICE = 0  # GPU device (0 for first GPU, 'cpu' for CPU)\nWORKERS = 4  # Number of dataloader workers\n\n# Display configuration\nprint(\"\\n Training Configuration:\")\nprint(f\"   Run name: {RUN_NAME}\")\nprint(\"   Model: YOLOv8n (ONLY model allowed)\")\nprint(f\"   Epochs: {EPOCHS}\")\nprint(f\"   Batch size: {BATCH_SIZE}\")\nprint(f\"   Image size: {IMAGE_SIZE} (FIXED)\")\nprint(f\"   Device: GPU {DEVICE}\" if DEVICE != \"cpu\" else \"   Device: CPU\")\n\n# Display dataset info (already loaded in STEP 1 above)\nprint(\"\\n Dataset:\")\nprint(f\"   Training: {len(tables['train'])} samples\")\nprint(f\"   Validation: {len(tables['val'])} samples\")\n\n# Create 3LC Settings for run tracking\nsettings = Settings(\n    project_name=PROJECT_NAME,\n    run_name=RUN_NAME,\n    run_description=RUN_DESCRIPTION,\n    image_embeddings_dim=0,\n)\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"✅ CONFIGURATION COMPLETE!\")\nprint(\"=\" * 70)\n\nprint(\"\\n💡 Configuration Summary:\")\nprint(f\"   • Tables loaded: {len(tables['train'])} train, {len(tables['val'])} val\")\nprint(f\"   • Run name: {RUN_NAME}\")\nprint(f\"   • Training for: {EPOCHS} epochs\")\nprint(f\"   • Batch size: {BATCH_SIZE}\")\nprint(f\"   • Device: GPU {DEVICE}\" if DEVICE != \"cpu\" else \"   • Device: CPU\")\n\nprint(\"\\n Next: Run the cell below to start training!\")\nprint(\"   (Review the configuration above before proceeding)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T12:56:40.198723Z","iopub.execute_input":"2025-11-18T12:56:40.199249Z","iopub.status.idle":"2025-11-18T12:56:40.323324Z","shell.execute_reply.started":"2025-11-18T12:56:40.199203Z","shell.execute_reply":"2025-11-18T12:56:40.322501Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n# Load YOLOv8n pretrained model\nprint(\"\\nLoading YOLOv8n pretrained weights...\")\nmodel = YOLO(\"yolov8n.pt\")\nprint(\"✅ Model loaded (3M parameters, 6MB size)\")\n\nresults = model.train(\n    tables=tables,  # Use 3LC Tables\n    name=RUN_NAME,  # Name for saving results (creates runs/detect/{RUN_NAME}/)\n    epochs=EPOCHS,\n    imgsz=IMAGE_SIZE,\n    batch=BATCH_SIZE,\n    device=DEVICE,\n    workers=WORKERS,\n    settings=settings,  # 3LC tracking\n    val=True,  # Validate during training\n    # AUGMENTATION - Uncomment for better performance in later iterations:\n    # mosaic=1.0,              # Mosaic augmentation - helps with scale variation\n    # copy_paste=0.1,          # Copy-paste - helps with occlusion\n    # mixup=0.05,              # Mixup - improves generalization\n    # patience=20,             # Early stopping patience\n)\n\n\nprint(\"\\n📁 Model Weights Saved:\")\nprint(f\"   Best model: runs/detect/{RUN_NAME}/weights/best.pt\")\nprint(f\"   Last model: runs/detect/{RUN_NAME}/weights/last.pt\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T12:56:43.229206Z","iopub.execute_input":"2025-11-18T12:56:43.229821Z","iopub.status.idle":"2025-11-18T13:01:50.878829Z","shell.execute_reply.started":"2025-11-18T12:56:43.229792Z","shell.execute_reply":"2025-11-18T13:01:50.874168Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport shutil\nfrom datetime import datetime\n\n# Setup\nWORK_DIR = Path(\"/kaggle/input/the-3lc-cotton-weed-detection-challenge/cotton_weed_competition_dataset\")\nTEST_DIR = WORK_DIR / \"test\" / \"images\"\nPRED_DIR = Path(\"predictions\")\nIMAGE_SIZE = 640\n\n# Get test images\ntest_images = list(TEST_DIR.glob(\"*.jpg\"))\n\n# Handle existing predictions\nif PRED_DIR.exists():\n    timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n    backup_dir = Path(f\"predictions_backup_{timestamp}\")\n    print(f\"⚠️  Backing up existing predictions to: {backup_dir}\")\n    shutil.move(str(PRED_DIR), str(backup_dir))\n\nprint(f\"📸 Generating predictions for {len(test_images)} test images...\")\n\n# Run inference\ntest_results = model.predict(\n    source=str(TEST_DIR),\n    save=False,\n    save_txt=True,\n    save_conf=True,\n    conf=0,\n    imgsz=IMAGE_SIZE,\n    project=str(PRED_DIR.parent),\n    name=PRED_DIR.name,\n    exist_ok=False,\n)\n\nprint(\"✅ Predictions generated!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T13:15:01.653065Z","iopub.execute_input":"2025-11-18T13:15:01.654141Z","iopub.status.idle":"2025-11-18T13:17:04.51074Z","shell.execute_reply.started":"2025-11-18T13:15:01.65409Z","shell.execute_reply":"2025-11-18T13:17:04.510084Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\nCLASS_NAMES = [\"Carpetweed\", \"Morning Glory\", \"Palmer Amaranth\"]\nPRED_DIR = Path(\"predictions\")\nlabels_dir = PRED_DIR / \"labels\"\n\nif labels_dir.exists():\n    pred_files = list(labels_dir.glob(\"*.txt\"))\n    class_counts = {i: 0 for i in range(len(CLASS_NAMES))}\n    images_with_preds = 0\n    total_detections = 0\n\n    for pred_file in pred_files:\n        if pred_file.stat().st_size > 0:\n            images_with_preds += 1\n            with open(pred_file, \"r\") as f:\n                for line in f:\n                    if line.strip():\n                        class_id = int(line.strip().split()[0])\n                        class_counts[class_id] += 1\n                        total_detections += 1\n\n    # Print results\n    print(f\"📊 Prediction Analysis:\")\n    print(f\"   Test images: {len(test_images)}\")\n    print(f\"   With detections: {images_with_preds}\")\n    print(f\"   No detections: {len(test_images) - images_with_preds}\")\n    print(f\"   Total detections: {total_detections}\")\n    \n    print(\"\\n   By class:\")\n    for class_id, count in class_counts.items():\n        pct = (count / total_detections * 100) if total_detections else 0\n        print(f\"   {CLASS_NAMES[class_id]:15}: {count:3d} ({pct:4.1f}%)\")\n\nelse:\n    print(\"❌ No predictions found\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T13:17:37.464542Z","iopub.execute_input":"2025-11-18T13:17:37.46487Z","iopub.status.idle":"2025-11-18T13:17:37.525465Z","shell.execute_reply.started":"2025-11-18T13:17:37.46485Z","shell.execute_reply":"2025-11-18T13:17:37.524768Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport pandas as pd\n\n# Setup\nWORK_DIR = Path(\"/kaggle/input/the-3lc-cotton-weed-detection-challenge/cotton_weed_competition_dataset\")\nPRED_DIR = Path(\"predictions\")\nTEST_DIR = WORK_DIR / \"test\" / \"images\"\nlabels_dir = PRED_DIR / \"labels\"\noutput_csv = \"submission.csv\"\n\nprint(\"📊 Generating Kaggle Submission...\")\n\n# Get unique test images\ntest_images = {}\nfor ext in [\"*.jpg\", \"*.jpeg\", \"*.JPG\", \"*.JPEG\", \"*.png\", \"*.PNG\"]:\n    for img_path in TEST_DIR.glob(ext):\n        test_images[img_path.stem] = img_path\n\ntest_images = [test_images[img_id] for img_id in sorted(test_images.keys())]\nprint(f\"✓ Found {len(test_images)} test images\")\n\n# Create submission data\nsubmission_data = []\nimages_with_preds = total_boxes = 0\n\nfor img_path in test_images:\n    image_id = img_path.stem\n    pred_file = labels_dir / f\"{image_id}.txt\"\n    \n    if pred_file.exists() and pred_file.stat().st_size > 0:\n        prediction_boxes = []\n        with open(pred_file, \"r\") as f:\n            for line in f:\n                if line.strip():\n                    parts = line.strip().split()\n                    if len(parts) >= 6:\n                        # Reorder: class conf xc yc w h\n                        class_id, conf, xc, yc, w, h = parts[0], parts[5], parts[1], parts[2], parts[3], parts[4]\n                        prediction_boxes.append(f\"{class_id} {conf} {xc} {yc} {w} {h}\")\n                        total_boxes += 1\n        \n        prediction_string = \" \".join(prediction_boxes) if prediction_boxes else \"no box\"\n        images_with_preds += bool(prediction_boxes)\n    else:\n        prediction_string = \"no box\"\n    \n    submission_data.append({\"image_id\": image_id, \"prediction_string\": prediction_string})\n\n# Save submission\nsubmission_df = pd.DataFrame(submission_data)[[\"image_id\", \"prediction_string\"]]\nsubmission_df.to_csv(output_csv, index=False)\n\n# Print results\nprint(f\"\\n📈 Submission Statistics:\")\nprint(f\"   Total images: {len(submission_df)}\")\nprint(f\"   With predictions: {images_with_preds}\")\nprint(f\"   Without predictions: {len(submission_df) - images_with_preds}\")\nprint(f\"   Total boxes: {total_boxes}\")\nprint(f\"   Avg boxes per image: {total_boxes/len(submission_df):.2f}\")\n\n# Validation\nprint(f\"\\n✅ Submission ready: {output_csv}\")\nprint(f\"📤 Upload to Kaggle and check your leaderboard score!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-18T13:19:08.551655Z","iopub.execute_input":"2025-11-18T13:19:08.551978Z","iopub.status.idle":"2025-11-18T13:19:08.745171Z","shell.execute_reply.started":"2025-11-18T13:19:08.551956Z","shell.execute_reply":"2025-11-18T13:19:08.744559Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}