{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":19989,"databundleVersionId":1160143,"sourceType":"competition"}],"dockerImageVersionId":30919,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport os, math, random\n\n\nDATA_DIR = \"/kaggle/input/global-wheat-detection\"  \nIMG_DIR = os.path.join(DATA_DIR, \"train\")          \nCSV_PATH = os.path.join(DATA_DIR, \"train.csv\")     \n\n# Load annotations\ndf = pd.read_csv(CSV_PATH)\ndf.head()\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-06T22:43:23.684866Z","iopub.execute_input":"2025-04-06T22:43:23.685176Z","iopub.status.idle":"2025-04-06T22:43:23.853192Z","shell.execute_reply.started":"2025-04-06T22:43:23.685152Z","shell.execute_reply":"2025-04-06T22:43:23.852390Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if 'bbox' in df.columns:\n    # Remove brackets and split into columns\n    bbox_coords = df['bbox'].str.strip('[]').str.split(',', expand=True).astype(float)\n    df[['x', 'y', 'w', 'h']] = bbox_coords\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T22:43:30.254233Z","iopub.execute_input":"2025-04-06T22:43:30.254600Z","iopub.status.idle":"2025-04-06T22:43:30.662663Z","shell.execute_reply.started":"2025-04-06T22:43:30.254574Z","shell.execute_reply":"2025-04-06T22:43:30.661712Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"YOLO_IMAGES_TRAIN = \"images/train\"\nYOLO_IMAGES_VAL = \"images/val\"\nYOLO_LABELS_TRAIN = \"labels/train\"\nYOLO_LABELS_VAL = \"labels/val\"\nos.makedirs(YOLO_IMAGES_TRAIN, exist_ok=True)\nos.makedirs(YOLO_IMAGES_VAL, exist_ok=True)\nos.makedirs(YOLO_LABELS_TRAIN, exist_ok=True)\nos.makedirs(YOLO_LABELS_VAL, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T22:43:59.880377Z","iopub.execute_input":"2025-04-06T22:43:59.880703Z","iopub.status.idle":"2025-04-06T22:43:59.885429Z","shell.execute_reply.started":"2025-04-06T22:43:59.880678Z","shell.execute_reply":"2025-04-06T22:43:59.884610Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_ids = df['image_id'].unique().tolist()\nrandom.shuffle(image_ids)\nval_frac = 0.2\nval_count = math.floor(len(image_ids) * val_frac)\nval_ids = set(image_ids[:val_count])\ntrain_ids = set(image_ids[val_count:])\n\n\ngroups = df.groupby('image_id')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T23:28:27.290041Z","iopub.execute_input":"2025-04-06T23:28:27.290429Z","iopub.status.idle":"2025-04-06T23:28:27.308992Z","shell.execute_reply.started":"2025-04-06T23:28:27.290400Z","shell.execute_reply":"2025-04-06T23:28:27.308034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom PIL import Image\nimport shutil\n\nfor img_id in image_ids:\n    # Determine split\n    img_split = \"val\" if img_id in val_ids else \"train\"\n\n    # Image file path (.jpg or .png)\n    img_path = os.path.join(IMG_DIR, f\"{img_id}.jpg\")\n    if not os.path.exists(img_path):\n        img_path = os.path.join(IMG_DIR, f\"{img_id}.png\")\n    \n    # Skip if image doesn't exist\n    if not os.path.exists(img_path):\n        print(f\"Image {img_id} not found in either .jpg or .png format.\")\n        continue\n\n    # Destination paths\n    if img_split == \"train\":\n        dest_img_path = os.path.join(YOLO_IMAGES_TRAIN, f\"{img_id}.jpg\")\n        dest_label_path = os.path.join(YOLO_LABELS_TRAIN, f\"{img_id}.txt\")\n    else:\n        dest_img_path = os.path.join(YOLO_IMAGES_VAL, f\"{img_id}.jpg\")\n        dest_label_path = os.path.join(YOLO_LABELS_VAL, f\"{img_id}.txt\")\n\n    # Ensure destination directory exists\n    os.makedirs(os.path.dirname(dest_img_path), exist_ok=True)\n    os.makedirs(os.path.dirname(dest_label_path), exist_ok=True)\n\n    # Copy image\n    shutil.copyfile(img_path, dest_img_path)\n\n    # Write YOLO label\n    with open(dest_label_path, 'w') as f:\n        if img_id in groups.groups:  # has annotations\n            for _, row in groups.get_group(img_id).iterrows():\n                x, y, w, h = row['x'], row['y'], row['w'], row['h']\n                img = Image.open(img_path)\n                W, H = img.size\n                x_center = (x + w / 2) / W\n                y_center = (y + h / 2) / H\n                bw = w / W\n                bh = h / H\n                # Clamp between 0 and 1\n                x_center = min(max(x_center, 0), 1)\n                y_center = min(max(y_center, 0), 1)\n                bw = min(max(bw, 0), 1)\n                bh = min(max(bh, 0), 1)\n                f.write(f\"0 {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T23:39:06.439065Z","iopub.execute_input":"2025-04-06T23:39:06.439429Z","iopub.status.idle":"2025-04-06T23:40:58.364862Z","shell.execute_reply.started":"2025-04-06T23:39:06.439403Z","shell.execute_reply":"2025-04-06T23:40:58.364077Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install albumentations==1.2.1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T23:03:58.100300Z","iopub.execute_input":"2025-04-06T23:03:58.100664Z","iopub.status.idle":"2025-04-06T23:04:03.099523Z","shell.execute_reply.started":"2025-04-06T23:03:58.100637Z","shell.execute_reply":"2025-04-06T23:04:03.098652Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Define augmentation pipeline\ntrain_transforms = A.Compose([\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.ShiftScaleRotate(shift_limit=0.15, scale_limit=0.2, rotate_limit=30, border_mode=0, p=0.5),\n    A.RandomBrightnessContrast(brightness_limit=0.3, contrast_limit=0.3, p=0.5),\n    A.HueSaturationValue(hue_shift_limit=20, sat_shift_limit=30, val_shift_limit=20, p=0.4),\n    # Note: Mosaic is not trivial to include in Compose directly; YOLO does it internally.\n    # We could implement a custom mosaic outside the pipeline if needed.\n    ToTensorV2()  # Convert image to PyTorch tensor\n], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T23:42:01.737646Z","iopub.execute_input":"2025-04-06T23:42:01.737935Z","iopub.status.idle":"2025-04-06T23:42:01.742969Z","shell.execute_reply.started":"2025-04-06T23:42:01.737914Z","shell.execute_reply":"2025-04-06T23:42:01.742160Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport matplotlib.pyplot as plt\n\n# Load a sample image and corresponding bboxes (assuming we have one from our dataset)\nsample_img_path = os.path.join(YOLO_IMAGES_TRAIN, os.listdir(YOLO_IMAGES_TRAIN)[0])\nsample_label_path = os.path.join(YOLO_LABELS_TRAIN, os.path.splitext(os.listdir(YOLO_IMAGES_TRAIN)[0])[0] + \".txt\")\nsample_img = cv2.imread(sample_img_path)\nsample_img = cv2.cvtColor(sample_img, cv2.COLOR_BGR2RGB)  # convert to RGB for plotting\n\n# Read sample bboxes in YOLO format\nsample_bboxes = []\nsample_class_labels = []\nwith open(sample_label_path, 'r') as f:\n    for line in f:\n        cls, x_center, y_center, w, h = line.strip().split()\n        x_center, y_center, w, h = map(float, (x_center, y_center, w, h))\n        # Albumentations expects bboxes as [x_min, y_min, x_max, y_max] for pascal_voc or [xc,yc,w,h] for yolo\n        # We already have normalized YOLO format and specified format='yolo', so we can use directly:\n        sample_bboxes.append([x_center, y_center, w, h])\n        sample_class_labels.append(int(cls))\n\n# Apply augmentation\naugmented = train_transforms(image=sample_img, bboxes=sample_bboxes, class_labels=sample_class_labels)\naug_img = augmented['image'].cpu().numpy().transpose(1,2,0)  # tensor to numpy image\naug_bboxes = augmented['bboxes']\n\n# Plot original and augmented for comparison\ndef plot_image_with_bboxes(img, bboxes, title=\"Image\"):\n    fig, ax = plt.subplots(1, 1, figsize=(6, 6))\n    ax.imshow(img)\n    H, W = img.shape[0], img.shape[1]\n    for (xc, yc, bw, bh) in bboxes:\n        # Convert normalized center format to top-left corner format for drawing\n        x_min = int((xc - bw/2) * W); x_max = int((xc + bw/2) * W)\n        y_min = int((yc - bh/2) * H); y_max = int((yc + bh/2) * H)\n        rect = plt.Rectangle((x_min, y_min), x_max-x_min, y_max-y_min,\n                              fill=False, color='red', linewidth=2)\n        ax.add_patch(rect)\n    ax.set_title(title)\n    ax.axis('off')\n    plt.show()\n\nplot_image_with_bboxes(sample_img, sample_bboxes, title=\"Original\")\nplot_image_with_bboxes(aug_img, aug_bboxes, title=\"Augmented\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T23:44:53.774326Z","iopub.execute_input":"2025-04-06T23:44:53.774632Z","iopub.status.idle":"2025-04-06T23:44:53.811983Z","shell.execute_reply.started":"2025-04-06T23:44:53.774611Z","shell.execute_reply":"2025-04-06T23:44:53.810842Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport matplotlib.pyplot as plt\n\n# Load a sample image and corresponding bboxes (assuming we have one from our dataset)\nsample_img_path = os.path.join(YOLO_IMAGES_TRAIN, os.listdir(YOLO_IMAGES_TRAIN)[0])\nsample_label_path = os.path.join(YOLO_LABELS_TRAIN, os.path.splitext(os.listdir(YOLO_IMAGES_TRAIN)[0])[0] + \".txt\")\nsample_img = cv2.imread(sample_img_path)\nsample_img = cv2.cvtColor(sample_img, cv2.COLOR_BGR2RGB)  # convert to RGB for plotting\n\n# Read sample bboxes in YOLO format\nsample_bboxes = []\nsample_class_labels = []\nwith open(sample_label_path, 'r') as f:\n    for line in f:\n        cls, x_center, y_center, w, h = line.strip().split()\n        x_center, y_center, w, h = map(float, (x_center, y_center, w, h))\n        # Albumentations expects bboxes as [x_min, y_min, x_max, y_max] for pascal_voc or [xc,yc,w,h] for yolo\n        # We already have normalized YOLO format and specified format='yolo', so we can use directly:\n        sample_bboxes.append([x_center, y_center, w, h])\n        sample_class_labels.append(int(cls))\n\n# Apply augmentation\naugmented = train_transforms(image=sample_img, bboxes=sample_bboxes, class_labels=sample_class_labels)\naug_img = augmented['image'].cpu().numpy().transpose(1,2,0)  # tensor to numpy image\naug_bboxes = augmented['bboxes']\n\n# Plot original and augmented for comparison\ndef plot_image_with_bboxes(img, bboxes, title=\"Image\"):\n    fig, ax = plt.subplots(1, 1, figsize=(6, 6))\n    ax.imshow(img)\n    H, W = img.shape[0], img.shape[1]\n    for (xc, yc, bw, bh) in bboxes:\n        # Convert normalized center format to top-left corner format for drawing\n        x_min = int((xc - bw/2) * W); x_max = int((xc + bw/2) * W)\n        y_min = int((yc - bh/2) * H); y_max = int((yc + bh/2) * H)\n        rect = plt.Rectangle((x_min, y_min), x_max-x_min, y_max-y_min,\n                              fill=False, color='red', linewidth=2)\n        ax.add_patch(rect)\n    ax.set_title(title)\n    ax.axis('off')\n    plt.show()\n\nplot_image_with_bboxes(sample_img, sample_bboxes, title=\"Original\")\nplot_image_with_bboxes(aug_img, aug_bboxes, title=\"Augmented\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"path:  ./   # base path for the dataset (we'll assume our images and labels directories are in working dir)\ntrain: images/train  \nval: images/val  \nnames: [ \"wheat_head\" ]  \nnc: 1  ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset_yaml = \"\"\"\npath: ./\ntrain: images/train\nval: images/val\nnames: ['wheat_head']\nnc: 1\n\"\"\"\nwith open(\"wheat.yaml\", \"w\") as f:\n    f.write(dataset_yaml)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q ultralytics>=8.0.20  # install Ultralytics YOLO\n\nfrom ultralytics import YOLO\n\n# Load a YOLO model (pretrained on COCO)\nmodel = YOLO('yolov8s.pt')  # using a small model; can also try 'yolov8m.pt' or others\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results = model.train(\n    data=\"wheat.yaml\",  # dataset config\n    epochs=50,\n    batch=8,\n    imgsz=640,\n    project=\"wheat-yolo-training\",  # directory to save runs\n    name=\"yolov8s-globalwheat\",\n    pretrained=True,   # start from COCO-pretrained weights (True by default when we loaded yolov8s.pt)\n    verbose=True\n)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nbest_model = YOLO(\"wheat-yolo-training/yolov8s-globalwheat/weights/best.pt\")\nmetrics = best_model.val(data=\"wheat.yaml\", split=\"val\") val_images = [os.path.join(YOLO_IMAGES_VAL, img) for img in os.listdir(YOLO_IMAGES_VAL)[:5]]\npredictions = best_model.predict(val_images, conf=0.25, imgsz=640)  \nfor i, result in enumerate(predictions):\n    boxes = result.boxes.xyxy.cpu().numpy()  # predicted bounding boxes in [x1,y1,x2,y2] format\n    scores = result.boxes.conf.cpu().numpy()  # confidence scores\n    img = result.orig_img  # original image array\n    # Plot the image with boxes\n    plt.figure(figsize=(6,4))\n    plt.imshow(img)\n    for (x1,y1,x2,y2), score in zip(boxes, scores):\n        # Draw rectangle\n        plt.gca().add_patch(plt.Rectangle((x1, y1), x2-x1, y2-y1, edgecolor='lime', facecolor='none', linewidth=2))\n        plt.gca().text(x1, y1-5, f\"{score:.2f}\", color='lime', fontsize=8)\n    plt.title(f\"Predictions on val image {i}\")\n    plt.axis('off')\n    plt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Precision: {metrics.box.precision:.4f}\")\nprint(f\"Recall: {metrics.box.recall:.4f}\")\nprint(f\"mAP@50: {metrics.box.map50:.4f}\")\nprint(f\"mAP@50:95: {metrics.box.map:.4f}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n# Get the underlying PyTorch model\nmodel_pt = best_model.model\n\n# Choose a layer to visualize (e.g., the last layer of the backbone or neck)\ntarget_layer = model_pt.model[ -2 ]  # just as an example, choose second to last module in model\nactivations = {}\n\n# Hook to capture the feature map from target layer\ndef hook_fn(module, input, output):\n    activations[\"feat\"] = output.detach()\n\nhook_handle = target_layer.register_forward_hook(hook_fn)\n\n# Pass an image through the model\nimg = cv2.imread(val_images[0])\nout = best_model.predict(img, verbose=False)  # run prediction (forward pass)\nfeature_map = activations[\"feat\"]\nprint(feature_map.shape)  # e.g., (1, C, H, W)\n\n# Remove hook\nhook_handle.remove()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfeat = feature_map.cpu().squeeze(0)  \n# Mean across channels\nheatmap = feat.mean(axis=0).numpy()\n# Normalize heatmap\nheatmap = (heatmap - heatmap.min()) / (heatmap.max() - heatmap.min() + 1e-6)\nheatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))\nplt.imshow(img)\nplt.imshow(heatmap, cmap='jet', alpha=0.4)  \nplt.title(\"Feature activation heatmap\")\nplt.axis('off')\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install pytorch-grad-cam","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install pytorch-grad-cam\nfrom pytorch_grad_cam import GradCAM\nfrom pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget\n\n# Suppose we want Grad-CAM for the first detection in the image\n# We identify the layer (target_layer as defined) and the target category (which for detection might be the objectness of class 0)\ncam = GradCAM(model=model_pt, target_layers=[target_layer], use_cuda=True)\n# We need to set up a target for the CAM - this is tricky for detection, but one way is to pick the output neuron corresponding to the highest confidence wheat head detection.\n# For simplicity, assume we target class 0 in the network (wheat_head class probability).\ntargets = [ClassifierOutputTarget(0)]\ngrayscale_cam = cam(input_tensor=torch.from_numpy(img).unsqueeze(0).permute(0,3,1,2).cuda().float()/255.0, targets=targets)\ngrayscale_cam = grayscale_cam[0]\nvisualization = show_cam_on_image(img/255.0, grayscale_cam, use_rgb=True)\nplt.imshow(visualization)\nplt.title(\"Grad-CAM for wheat head detection\")\nplt.axis('off')\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install ensemble-boxes\nfrom ensemble_boxes import weighted_boxes_fusion\n\n# Suppose preds1 and preds2 are results from original and flipped image\nboxes_list = [preds1_boxes_xy, preds2_boxes_xy]  # each list of [ [x1,y1,x2,y2], ... ] normalized 0-1\nscores_list = [preds1_scores, preds2_scores]\nlabels_list = [preds1_labels, preds2_labels]  # all zeros in our case (class 0)\n# Apply WBF\nrefined_boxes, refined_scores, refined_labels = weighted_boxes_fusion(\n    boxes_list, scores_list, labels_list, iou_thr=0.5, skip_box_thr=0.001\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}