{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":19989,"databundleVersionId":1160143},{"sourceType":"datasetVersion","sourceId":15413616,"datasetId":9857885,"databundleVersionId":16330214}],"dockerImageVersionId":31329,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Global Wheat Detection - Ultralytics Yolov8\n\nThis notebook is showing a workflow from data preprocessing to training and generating a Kaggle submission with a YOLO-based object detection.\n\nThe **Global Wheat Detection** challenge (2020) requires detecting wheat heads in aerial images of wheat fields. The dataset contains:\n\n- Thousands of high-resolution images of wheat fields\n- Bounding box annotations for each wheat head\n- A separate test set for which predictions must be submitted\n\n**This Notebook:**\n\n- Preprocess the dataset into **YOLO format**\n- Train a YOLOv8 model on the wheat dataset\n- Visualize predictions on validation images\n- Generate a submission file ready for Kaggle\n\nThis notebook use **Ultralytics YOLOv8** for object detection. The library is installed with an off line installation to be compatible with the competition (No Internet access).\n\n**If you think this notebook deserves an upvote, consider doing so. ;-)**\n","metadata":{}},{"cell_type":"markdown","source":"## Ultralytics install without internet\n\nUse of https://www.kaggle.com/datasets/thierryneusius/yolov8-ultralytics/\n\nWith Internet use directly:\n`!pip -q install ultralytics`","metadata":{}},{"cell_type":"code","source":"import sys\nimport os\n\npkg_path = \"/kaggle/input/datasets/thierryneusius/yolov8-ultralytics/ultralytics/ultralytics_pkg\"\n\nsys.path.append(pkg_path)\n!pip -q install --no-index --find-links={pkg_path} ultralytics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-29T09:14:46.484109Z","iopub.execute_input":"2026-03-29T09:14:46.484387Z","iopub.status.idle":"2026-03-29T09:14:50.545534Z","shell.execute_reply.started":"2026-03-29T09:14:46.484365Z","shell.execute_reply":"2026-03-29T09:14:50.544654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"Using device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-29T09:14:50.548492Z","iopub.execute_input":"2026-03-29T09:14:50.548935Z","iopub.status.idle":"2026-03-29T09:14:54.857837Z","shell.execute_reply.started":"2026-03-29T09:14:50.548896Z","shell.execute_reply":"2026-03-29T09:14:54.857165Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set to True for quick debugging (faster runs, lower accuracy, CPU used)\nDEBUG = False\n\nif DEBUG:\n    CONFIG = {\n        \"epochs\": 2,\n        \"batch\": 2,\n        \"imgsz\": 128,\n    }\nelse:\n    CONFIG = {\n        \"epochs\": 50,\n        \"batch\": 16,\n        \"imgsz\": 640,\n    }\n\n# Display configuration\nprint(\"Running in DEBUG mode\" if DEBUG else \"Running in FULL mode\")\nprint(CONFIG)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-29T09:14:54.858824Z","iopub.execute_input":"2026-03-29T09:14:54.859781Z","iopub.status.idle":"2026-03-29T09:14:54.864739Z","shell.execute_reply.started":"2026-03-29T09:14:54.859750Z","shell.execute_reply":"2026-03-29T09:14:54.864012Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Yolo format conversion and split of the dataset\n\nConvert COCO annotations to YOLO format + Train/Valid split\n\nYOLOv8 expects a txt file per image. \n\nEach line = one box → `class x_center y_center width height`\n\nClass 0 = “wheat head”, since there is only one class.","metadata":{}},{"cell_type":"markdown","source":"### Conversion and split","metadata":{}},{"cell_type":"code","source":"# Paths\nDATA_DIR = \"/kaggle/input/competitions/global-wheat-detection\"\nYOLO_IMAGES_DIR = \"./data/yolo/images\"\nYOLO_LABELS_DIR = \"./data/yolo/labels\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-29T09:14:54.866887Z","iopub.execute_input":"2026-03-29T09:14:54.867262Z","iopub.status.idle":"2026-03-29T09:14:54.905523Z","shell.execute_reply.started":"2026-03-29T09:14:54.867238Z","shell.execute_reply":"2026-03-29T09:14:54.904911Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport os\nimport ast\nimport shutil\nimport random\n\n# Load annotations\ndf = pd.read_csv(os.path.join(DATA_DIR, \"train.csv\"))\n\n# Create directories\nfor split in [\"train\", \"valid\"]:\n    os.makedirs(os.path.join(YOLO_IMAGES_DIR, split), exist_ok=True)\n    os.makedirs(os.path.join(YOLO_LABELS_DIR, split), exist_ok=True)\n\n# Get unique image IDs\nimage_ids = df[\"image_id\"].unique()\n\n# Reproducible split\nrandom.seed(42)\nrandom.shuffle(image_ids)\n\nsplit_idx = int(0.8 * len(image_ids))\ntrain_ids = set(image_ids[:split_idx])\nvalid_ids = set(image_ids[split_idx:])\n\nprint(f\"Train images: {len(train_ids)}\")\nprint(f\"Valid images: {len(valid_ids)}\")\n\n# Process each image\nfor img_id, group in df.groupby(\"image_id\"):\n\n    split = \"train\" if img_id in train_ids else \"valid\"\n    label_lines = []\n\n    for _, row in group.iterrows():\n        h, w = row.height, row.width\n\n        # Parse bbox: [x, y, width, height]\n        bbox = ast.literal_eval(row[\"bbox\"])\n        xmin, ymin, bw, bh = bbox\n\n        # Convert to YOLO format (normalized)\n        x_center = (xmin + bw / 2) / w\n        y_center = (ymin + bh / 2) / h\n        width = bw / w\n        height = bh / h\n\n        label_lines.append(\n            f\"0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\"\n        )\n\n    # Save label file\n    label_path = os.path.join(YOLO_LABELS_DIR, split, f\"{img_id}.txt\")\n    with open(label_path, \"w\") as f:\n        f.write(\"\\n\".join(label_lines))\n\n    # Copy image\n    src = os.path.join(DATA_DIR, \"train\", f\"{img_id}.jpg\")\n    dst = os.path.join(YOLO_IMAGES_DIR, split, f\"{img_id}.jpg\")\n\n    if os.path.exists(src):\n        shutil.copyfile(src, dst)\n    else:\n        print(f\"issing image: {img_id}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-29T09:14:54.906588Z","iopub.execute_input":"2026-03-29T09:14:54.907335Z","iopub.status.idle":"2026-03-29T09:15:10.523654Z","shell.execute_reply.started":"2026-03-29T09:14:54.907282Z","shell.execute_reply":"2026-03-29T09:15:10.522762Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Check on one image","metadata":{}},{"cell_type":"code","source":"import cv2\nimport matplotlib.pyplot as plt\n\nsample_id = list(train_ids)[0]\nimg = cv2.imread(os.path.join(YOLO_IMAGES_DIR, \"train\", f\"{sample_id}.jpg\"))\n\nh, w, _ = img.shape\n\nwith open(os.path.join(YOLO_LABELS_DIR, \"train\", f\"{sample_id}.txt\")) as f:\n    for line in f:\n        _, xc, yc, bw, bh = map(float, line.split())\n\n        x1 = int((xc - bw/2) * w)\n        y1 = int((yc - bh/2) * h)\n        x2 = int((xc + bw/2) * w)\n        y2 = int((yc + bh/2) * h)\n\n        cv2.rectangle(img, (x1, y1), (x2, y2), (0,255,0), 2)\n\nplt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\nplt.axis(\"off\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-29T09:15:10.524714Z","iopub.execute_input":"2026-03-29T09:15:10.525329Z","iopub.status.idle":"2026-03-29T09:15:10.713236Z","shell.execute_reply.started":"2026-03-29T09:15:10.525293Z","shell.execute_reply":"2026-03-29T09:15:10.712621Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## YOLO Dataset Configuration\n\nWe create a YAML file that defines:\n- training and validation image paths\n- number of classes\n- class names\n\nThis file is required by Ultralytics YOLO for training.","metadata":{}},{"cell_type":"code","source":"%%writefile ./data/yolo/wheat.yaml\ntrain: /kaggle/working/data/yolo/images/train\nval: /kaggle/working/data/yolo/images/valid\nnc: 1\nnames: ['wheat']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-29T09:15:10.714239Z","iopub.execute_input":"2026-03-29T09:15:10.715011Z","iopub.status.idle":"2026-03-29T09:15:10.719827Z","shell.execute_reply.started":"2026-03-29T09:15:10.714977Z","shell.execute_reply":"2026-03-29T09:15:10.719015Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Just to check the yaml file","metadata":{}},{"cell_type":"code","source":"with open(\"./data/yolo/wheat.yaml\", \"r\") as f:\n    print(f.read())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-29T09:15:10.720990Z","iopub.execute_input":"2026-03-29T09:15:10.721288Z","iopub.status.idle":"2026-03-29T09:15:10.732140Z","shell.execute_reply.started":"2026-03-29T09:15:10.721266Z","shell.execute_reply":"2026-03-29T09:15:10.731359Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train YOLOv8 Model\n\nWe train a YOLOv8 model using the prepared dataset.\n\nKey parameters:\n- **epochs**: number of training iterations  \n- **imgsz**: input image size  \n- **batch**: batch size  \n\nThe model is initialized from pretrained weights (transfer learning).","metadata":{}},{"cell_type":"code","source":"from ultralytics import YOLO\n\nmodel = YOLO(\"/kaggle/input/datasets/thierryneusius/yolov8-ultralytics/yolov8n.pt\")\n\nresults = model.train(\n    name=\"wheat_yolov8\",     # fixed name (no train2, train3...)\n    data=\"./data/yolo/wheat.yaml\",\n    epochs=CONFIG[\"epochs\"],\n    imgsz=CONFIG[\"imgsz\"],\n    batch=CONFIG[\"batch\"],\n    device=device,\n)\n\nprint(f\"Training completed. Results saved in: {results.save_dir}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-29T09:15:10.733033Z","iopub.execute_input":"2026-03-29T09:15:10.733676Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training Results Analysis\n\nThe `results.csv` file contains metrics recorded during training:\n- training and validation losses\n- precision, recall\n- mAP scores\n\nWe visualize these metrics to understand model performance.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport os\n\nresults_df = pd.read_csv(os.path.join(results.save_dir,\"results.csv\"))\nresults_df","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Plot losses\nplt.figure()\nplt.plot(results_df[\"epoch\"], results_df[\"train/box_loss\"], label=\"train box loss\")\nplt.plot(results_df[\"epoch\"], results_df[\"val/box_loss\"], label=\"val box loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.title(\"Box Loss\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure()\nplt.plot(results_df[\"epoch\"], results_df[\"metrics/mAP50(B)\"], label=\"mAP@0.5\")\nplt.plot(results_df[\"epoch\"], results_df[\"metrics/mAP50-95(B)\"], label=\"mAP@0.5:0.95\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"mAP\")\nplt.legend()\nplt.title(\"Model Performance\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Visualize validation predictions","metadata":{}},{"cell_type":"code","source":"from PIL import Image\nimport matplotlib.pyplot as plt\nimport os\n\n# List all generated images\nfiles = os.listdir(results.save_dir)\n\n# Filter validation batch images\nval_images = [f for f in files if \"val_batch\" in f and f.endswith(\".jpg\")]\n\nprint(\"Validation images found:\", val_images[:3])\n\n# Display one example\nif val_images:\n    img_path = os.path.join(results.save_dir, val_images[0])\n    \n    img = Image.open(img_path)\n    \n    plt.figure()\n    plt.imshow(img)\n    plt.axis(\"off\")\n    plt.title(\"Validation Predictions (GT vs Predicted)\")\n    plt.show()\nelse:\n    print(\"No validation images found\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nimport matplotlib.pyplot as plt\n\nimg = Image.open(os.path.join(results.save_dir,'confusion_matrix.png'))\nplt.imshow(img)\nplt.axis(\"off\");","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Test Inference and Submission\n\nWe run inference on the test dataset using a trained YOLOv8 model.\n\nKey steps:\n- Batch prediction for efficiency\n- Convert YOLO outputs to Kaggle format\n- Filter low-quality detections\n- Generate submission.csv","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\n\nbatch_size = 8  \n\ntest_image_dir = '/kaggle/input/competitions/global-wheat-detection/test'\n\n# Sort for reproducibility\nimage_files = sorted(os.listdir(test_image_dir))\nimage_paths = [os.path.join(test_image_dir, f) for f in image_files]\n\nresults_list = []\nfor i in range(0, len(image_paths), batch_size):\n    batch_paths = image_paths[i:i+batch_size]\n\n    # Predict\n    batch_results = model.predict(\n        batch_paths,\n        conf=0.25,\n        iou=0.5,\n        verbose=False\n    )\n\n    for img_path, result in zip(batch_paths, batch_results):\n        image_id = os.path.basename(img_path).replace(\".jpg\", \"\")\n\n        boxes = []\n\n        for box in result.boxes:\n            x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()\n            score = float(box.conf[0])\n\n            # Clamp to image bounds\n            x1 = max(0, int(x1))\n            y1 = max(0, int(y1))\n            x2 = max(0, int(x2))\n            y2 = max(0, int(y2))\n\n            w = max(0, x2 - x1)\n            h = max(0, y2 - y1)\n\n            # Filter very small boxes\n            if w * h < 10:\n                continue\n\n            boxes.append([score, x1, y1, w, h])\n\n        # Format submission string\n        prediction_string = \" \".join(\n            [f\"{conf:.4f} {x} {y} {w} {h}\" for conf, x, y, w, h in boxes]\n        ) if boxes else \"\"\n\n        results_list.append({\n            \"image_id\": image_id,\n            \"PredictionString\": prediction_string\n        })\n\n# Save submission\ndf = pd.DataFrame(results_list)\ndf.to_csv(\"submission.csv\", index=False)\n\nprint(\"Submission.csv created\")\ndf.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Display boxes on the last predicted image\nimport cv2\nimport matplotlib.pyplot as plt\n\nimg = cv2.imread(img_path)\n\nh, w, _ = img.shape\nfor box in boxes:\n    _, x1, y1, w, h = box\n    cv2.rectangle(img, (x1, y1), (x2, y2), (255,0,0), 2)\n\nplt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\nplt.axis(\"off\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}