{"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":19989,"databundleVersionId":1160143,"sourceType":"competition"},{"sourceId":13099015,"sourceType":"datasetVersion","datasetId":8297395}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, shutil\nimport numpy as np \nimport pandas as pd\nimport glob\nfrom tqdm import tqdm\n\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nplt.style.use('seaborn-v0_8-deep')   \nfrom PIL import Image \nimport cv2\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.cuda.amp import autocast, GradScaler\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.transforms import v2\nfrom torchvision.models.detection import fasterrcnn_resnet50_fpn_v2\nfrom torchvision.models.detection import fasterrcnn_resnet50_fpn # fasterrcnn_mobilenet_v3_large_fpn\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchmetrics.detection.mean_ap import MeanAveragePrecision","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:29.875556Z","iopub.execute_input":"2025-09-18T12:53:29.876045Z","iopub.status.idle":"2025-09-18T12:53:35.205432Z","shell.execute_reply.started":"2025-09-18T12:53:29.876018Z","shell.execute_reply":"2025-09-18T12:53:35.204637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for dirname, _, filenames in os.walk('/kaggle/input'):\n    print(f'current dir : {dirname}\\n')\n    if filenames :\n        for filename in filenames[:2]:\n            print(os.path.join(dirname, filename))\n    else:\n        print(\"no files found...\")\n    print(\"\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:35.206651Z","iopub.execute_input":"2025-09-18T12:53:35.207024Z","iopub.status.idle":"2025-09-18T12:53:36.087669Z","shell.execute_reply.started":"2025-09-18T12:53:35.207006Z","shell.execute_reply":"2025-09-18T12:53:36.087064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dir = '/kaggle/input/global-wheat-detection/train'\ntest_dir = '/kaggle/input/global-wheat-detection/test'\nprint(f\"{len(os.listdir(train_dir))} images in the train set\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:36.088365Z","iopub.execute_input":"2025-09-18T12:53:36.088563Z","iopub.status.idle":"2025-09-18T12:53:36.094176Z","shell.execute_reply.started":"2025-09-18T12:53:36.088539Z","shell.execute_reply":"2025-09-18T12:53:36.093625Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv('../input/global-wheat-detection/train.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:36.682259Z","iopub.execute_input":"2025-09-18T12:53:36.683026Z","iopub.status.idle":"2025-09-18T12:53:36.862404Z","shell.execute_reply.started":"2025-09-18T12:53:36.682993Z","shell.execute_reply":"2025-09-18T12:53:36.861828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:36.863883Z","iopub.execute_input":"2025-09-18T12:53:36.864196Z","iopub.status.idle":"2025-09-18T12:53:36.875824Z","shell.execute_reply.started":"2025-09-18T12:53:36.864177Z","shell.execute_reply":"2025-09-18T12:53:36.875141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(train_df.shape)\ntrain_df.isna().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:36.876464Z","iopub.execute_input":"2025-09-18T12:53:36.876642Z","iopub.status.idle":"2025-09-18T12:53:36.907648Z","shell.execute_reply.started":"2025-09-18T12:53:36.876628Z","shell.execute_reply":"2025-09-18T12:53:36.906975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_coords(row):\n    row = row[1:-1]\n    x_min, y_min, w, h = map(float ,row.split(','))\n    return [x_min, y_min, x_min+w, y_min+h]\n    \ntrain_df[['x1', 'y1', 'x2', 'y2']] = pd.DataFrame(train_df['bbox'].apply(get_coords).tolist(), index=train_df.index) ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:36.908253Z","iopub.execute_input":"2025-09-18T12:53:36.908438Z","iopub.status.idle":"2025-09-18T12:53:37.496042Z","shell.execute_reply.started":"2025-09-18T12:53:36.908423Z","shell.execute_reply":"2025-09-18T12:53:37.495249Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.drop(['bbox', 'source'], axis=1, inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:37.497444Z","iopub.execute_input":"2025-09-18T12:53:37.497675Z","iopub.status.idle":"2025-09-18T12:53:37.505213Z","shell.execute_reply.started":"2025-09-18T12:53:37.497657Z","shell.execute_reply":"2025-09-18T12:53:37.504603Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head(3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:37.505932Z","iopub.execute_input":"2025-09-18T12:53:37.506584Z","iopub.status.idle":"2025-09-18T12:53:37.522293Z","shell.execute_reply.started":"2025-09-18T12:53:37.506558Z","shell.execute_reply":"2025-09-18T12:53:37.521753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# create a sample from each category in the training data to visualize them\n\nidx = np.random.choice(len(os.listdir(train_dir)), 16)\nsample = np.array(os.listdir(train_dir))[idx]\nsample_imgs = []\nfor i in sample:\n    path = os.path.join(train_dir, i)\n    img = Image.open(path)\n    sample_imgs.append(img)\n\n# show the sample \nfig, ax = plt.subplots(4, 4, figsize=(18, 14))\nc = 0\nfor i in range(4):\n    for j in range(4):\n        ax[i][j].imshow(sample_imgs[c])\n        ax[i][j].axis(\"off\")\n        c += 1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:38.121093Z","iopub.execute_input":"2025-09-18T12:53:38.121359Z","iopub.status.idle":"2025-09-18T12:53:41.469627Z","shell.execute_reply.started":"2025-09-18T12:53:38.121339Z","shell.execute_reply":"2025-09-18T12:53:41.468598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"uniqe_imgs = list(map(lambda x: x + '.jpg', train_df['image_id'].unique()))\nuniqe_imgs[:3]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:41.471461Z","iopub.execute_input":"2025-09-18T12:53:41.473168Z","iopub.status.idle":"2025-09-18T12:53:41.491239Z","shell.execute_reply.started":"2025-09-18T12:53:41.473142Z","shell.execute_reply":"2025-09-18T12:53:41.490546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_indices, val_indices = train_test_split(np.arange(len(uniqe_imgs)), test_size=0.1, random_state=12)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:41.492297Z","iopub.execute_input":"2025-09-18T12:53:41.492558Z","iopub.status.idle":"2025-09-18T12:53:41.497871Z","shell.execute_reply.started":"2025-09-18T12:53:41.492535Z","shell.execute_reply":"2025-09-18T12:53:41.497164Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(uniqe_imgs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:41.498609Z","iopub.execute_input":"2025-09-18T12:53:41.498878Z","iopub.status.idle":"2025-09-18T12:53:41.521043Z","shell.execute_reply.started":"2025-09-18T12:53:41.498862Z","shell.execute_reply":"2025-09-18T12:53:41.520139Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# create a custom dataset class\nclass CustomDataset(Dataset):\n    def __init__(self, imgs, indices, df):\n        self.imgs = imgs\n        self.indices = indices\n        self.df = df\n\n    def __len__(self):\n        return len(self.indices)\n        \n    def __getitem__(self, idx):\n        img_name = self.imgs[self.indices[idx]]\n        img = Image.open(os.path.join('/kaggle/input/global-wheat-detection/train', img_name))\n        img = v2.ToTensor()(img)\n        \n        boxes = self.df[self.df['image_id'].str.contains(img_name[:-4])].values[:, 3:]\n        labels = torch.ones(boxes.shape[0], dtype=torch.int64)\n        \n        target = {\n            'boxes' : torch.tensor(boxes.tolist()),\n            'labels' : labels}\n\n        return img, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:41.522418Z","iopub.execute_input":"2025-09-18T12:53:41.522584Z","iopub.status.idle":"2025-09-18T12:53:41.537551Z","shell.execute_reply.started":"2025-09-18T12:53:41.522571Z","shell.execute_reply":"2025-09-18T12:53:41.536856Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:41.538708Z","iopub.execute_input":"2025-09-18T12:53:41.539084Z","iopub.status.idle":"2025-09-18T12:53:41.569593Z","shell.execute_reply.started":"2025-09-18T12:53:41.539067Z","shell.execute_reply":"2025-09-18T12:53:41.568845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# the collate_fn is the function that determines how individual samples from a dataset are combined into a batch.\n# create a custom collate function, as the length of the values inside each target dictionary differes between form one dict to another\ndef collate(data):\n    imgs ,targets = [], [] \n    for d in data:\n        imgs.append(d[0].to(device))\n        d[1]['boxes'] = d[1]['boxes'].to(device)\n        d[1]['labels'] = d[1]['labels'].to(device)\n        targets.append(d[1])\n    return imgs, targets\n\ntrain_set = CustomDataset(uniqe_imgs, train_indices, train_df)\ntrain_loader = DataLoader(train_set, batch_size=4, shuffle=True, collate_fn=collate)\n\nval_set = CustomDataset(uniqe_imgs, val_indices, train_df)\nval_loader = DataLoader(val_set, batch_size=8, shuffle=True, collate_fn=collate)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:53:41.570473Z","iopub.execute_input":"2025-09-18T12:53:41.571116Z","iopub.status.idle":"2025-09-18T12:53:41.583717Z","shell.execute_reply.started":"2025-09-18T12:53:41.571097Z","shell.execute_reply":"2025-09-18T12:53:41.582968Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# load the faster rcnn pre-trained model to finetune\n# model = fasterrcnn_resnet50_fpn(\n#     weights='DEFAULT', \n#     trainable_backbone_layers=5,  # train all resnet layers\n# )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T21:49:52.274536Z","iopub.execute_input":"2025-09-17T21:49:52.27507Z","iopub.status.idle":"2025-09-17T21:49:53.87979Z","shell.execute_reply.started":"2025-09-17T21:49:52.275044Z","shell.execute_reply":"2025-09-17T21:49:53.879009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# list(model.children())[-1] ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T21:49:53.88097Z","iopub.execute_input":"2025-09-17T21:49:53.88164Z","iopub.status.idle":"2025-09-17T21:49:53.886377Z","shell.execute_reply.started":"2025-09-17T21:49:53.881617Z","shell.execute_reply":"2025-09-17T21:49:53.885611Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# First Model - Faster RCNN","metadata":{}},{"cell_type":"code","source":"# change the number of classes at end of the last block\n# in_channels = model.roi_heads.box_predictor.cls_score.in_features # we can see they're 1024 from model structure above \n# model.roi_heads.box_predictor = FastRCNNPredictor(in_channels, 2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T21:49:55.656892Z","iopub.execute_input":"2025-09-17T21:49:55.657191Z","iopub.status.idle":"2025-09-17T21:49:55.662153Z","shell.execute_reply.started":"2025-09-17T21:49:55.65717Z","shell.execute_reply":"2025-09-17T21:49:55.661344Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# list(model.children())[-1].box_predictor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T21:49:55.846612Z","iopub.execute_input":"2025-09-17T21:49:55.846887Z","iopub.status.idle":"2025-09-17T21:49:55.852036Z","shell.execute_reply.started":"2025-09-17T21:49:55.846869Z","shell.execute_reply":"2025-09-17T21:49:55.851359Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Faster RCNN returns 4 losses :-\n\n>    - Two are internal -> RPN (Region Proposal Network) :\n        - loss_objectness -> Binary classification: is this anchor foreground (object) or background?\n        - loss_rpn_box_reg -> Bounding box regression for anchors that match objects.\n        \n>    - Two are the end of the -> network ROI Head losses :\n        - loss_classifier -> Multi-class classification: for each proposal, predict class (background vs wheat, in your case).\n        - loss_box_reg -> Bounding box regression for proposals that are matched to ground-truth objects.","metadata":{}},{"cell_type":"markdown","source":"#### example output of the model at evaluation on 1 image with 2 bounding boxes:\n     [{\n      'boxes': tensor([[ 48.1,  29.5, 202.3, 181.0],\n                       [ 32.2, 102.0, 118.5, 198.7]]),\n      'labels': tensor([1, 1]),\n      'scores': tensor([0.91, 0.76])\n    }]\n","metadata":{}},{"cell_type":"code","source":"# custom functions to compute the IOU and dice for 2 bounding boxes\ndef compute_iou(box1, box2):\n    x1 = max(box1[0], box2[0])\n    y1 = max(box1[1], box2[1])\n    x2 = min(box1[2], box2[2])\n    y2 = min(box1[3], box2[3])\n    inter_area = max(0, x2 - x1) * max(0, y2 - y1)\n\n    box1_area = (box1[2]-box1[0]) * (box1[3]-box1[1])\n    box2_area = (box2[2]-box2[0]) * (box2[3]-box2[1])\n\n    union_area = box1_area + box2_area - inter_area\n    return inter_area / union_area if union_area > 0 else 0.0\n\ndef compute_dice(box1, box2):\n    x1 = max(box1[0], box2[0])\n    y1 = max(box1[1], box2[1])\n    x2 = min(box1[2], box2[2])\n    y2 = min(box1[3], box2[3])\n    inter_area = max(0, x2 - x1) * max(0, y2 - y1)\n\n    box1_area = (box1[2]-box1[0]) * (box1[3]-box1[1])\n    box2_area = (box2[2]-box2[0]) * (box2[3]-box2[1])\n\n    return (2 * inter_area) / (box1_area + box2_area) if (box1_area + box2_area) > 0 else 0.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T22:01:46.743634Z","iopub.execute_input":"2025-09-17T22:01:46.744232Z","iopub.status.idle":"2025-09-17T22:01:46.7505Z","shell.execute_reply.started":"2025-09-17T22:01:46.744208Z","shell.execute_reply":"2025-09-17T22:01:46.749899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# I use the mean average precision metric and the custom IOU and Dice functions  \ndef evaluate(model, val_loader, iou_threshold=0.5):\n    model.eval()\n    map_metric = MeanAveragePrecision(iou_type=\"bbox\")\n    iou_scores, dice_scores = [0], [0]  # avoid nan if empty\n\n    with torch.no_grad():\n        for imgs, targets in val_loader:\n            preds = model(imgs)\n\n            # Move to CPU for torchmetrics\n            preds_cpu = [{k: v.cpu() for k, v in p.items()} for p in preds]\n            targets_cpu = [{k: v.cpu() for k, v in t.items()} for t in targets]\n            map_metric.update(preds_cpu, targets_cpu)\n\n            for gt, pred in zip(targets, preds):\n                gt_boxes, pred_boxes = gt['boxes'], pred['boxes']\n                \n                for pbox in pred_boxes:\n                    iou, dice = 0, 0\n                    for gtbox in gt_boxes:\n                        cur_iou = compute_iou(pbox, gtbox)\n                        cur_dice = compute_dice(pbox, gtbox)\n                        if cur_iou > iou:\n                            iou, dice = cur_iou, cur_dice\n                        \n                    if iou > iou_threshold:\n                        iou_scores.append(iou.detach().cpu().item())\n                        dice_scores.append(dice.detach().cpu().item())\n            \n    results = map_metric.compute()\n    mean_iou, mean_dice = np.mean(iou_scores), np.mean(dice_scores)   \n\n    return results, mean_iou, mean_dice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T22:01:47.528963Z","iopub.execute_input":"2025-09-17T22:01:47.529333Z","iopub.status.idle":"2025-09-17T22:01:47.536273Z","shell.execute_reply.started":"2025-09-17T22:01:47.529312Z","shell.execute_reply":"2025-09-17T22:01:47.53542Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def train(model, train_loader, val_loader, epochs, validate=True):\n#     model.to(device)\n\n#     optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4)\n    \n#     scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n#     optimizer,\n#     mode='min',      # because we monitor validation loss\n#     factor=0.5,      # multiply LR by 0.5\n#     patience=4,      # wait 4 epochs with no improvement before reducing\n#     min_lr=1e-4, \n#     verbose=True     # print when LR is reduced\n#     )\n    \n#     scaler = GradScaler()\n#     results = {\n#         'loss_on_epoch' : [],\n#         'map_at_half' : [],\n#         'mean_iou' : [],\n#         'mean_dice' : []\n#     }\n    \n#     for epoch in range(epochs):\n#         model.train()\n#         total_loss = 0\n    \n#         for imgs, targets in train_loader:\n#             with autocast():\n#                 loss_dict = model(imgs, targets) # average loss per image in the batch\n#                 loss = sum(loss_dict.values()) \n                \n\n#             optimizer.zero_grad()\n#             # loss.backward()\n#             scaler.scale(loss).backward()\n#             # optimizer.step()\n#             scaler.step(optimizer)\n#             scaler.update()\n\n#             total_loss += loss.item()\n\n#         results['loss_on_epoch'].append(total_loss / len(train_loader))\n\n#         print(f'epoch {epoch}\\n')\n#         # evaluate\n#         if validate:\n#             map_res, mean_iou, mean_dice = evaluate(model, val_loader)\n            \n#             print(f\"  average epoch loss : {results['loss_on_epoch'][-1]:.4f}\")\n#             print(f\"  mAP@0.5:     {map_res['map_50']:.4f}\")\n#             print(f\"  mAP@[.5:.95]: {map_res['map']:.4f}\")\n#             print(f\"  Mean IoU:    {mean_iou:.4f}\")\n#             print(f\"  Mean Dice:   {mean_dice:.4f}\\n\")\n    \n#             results['map_at_half'].append(map_res['map_50'])\n#             results['mean_iou'].append(mean_iou)\n#             results['mean_dice'].append(mean_dice)\n        \n#         else:\n#             print(f\"average epoch loss : {results['loss_on_epoch'][-1]:.4f}\\n\")\n                \n#         scheduler.step(results['loss_on_epoch'][-1])\n    \n#     return results","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T15:21:13.281962Z","iopub.execute_input":"2025-09-18T15:21:13.282268Z","iopub.status.idle":"2025-09-18T15:21:13.290139Z","shell.execute_reply.started":"2025-09-18T15:21:13.282248Z","shell.execute_reply":"2025-09-18T15:21:13.289375Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # first, freeze the backbone parameters\n# for param in model.backbone.parameters():\n#     param.requires_grad = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T23:45:17.841259Z","iopub.execute_input":"2025-09-16T23:45:17.841945Z","iopub.status.idle":"2025-09-16T23:45:17.84571Z","shell.execute_reply.started":"2025-09-16T23:45:17.841923Z","shell.execute_reply":"2025-09-16T23:45:17.845006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train the RPN + RoIHeads only.\n# results = train(model, train_loader, val_loader, 4, False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T23:45:28.246576Z","iopub.execute_input":"2025-09-16T23:45:28.24684Z","iopub.status.idle":"2025-09-17T00:04:37.701624Z","shell.execute_reply.started":"2025-09-16T23:45:28.246821Z","shell.execute_reply":"2025-09-17T00:04:37.700934Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # unfreeze the bacbone layers\n# for param in model.backbone.parameters():\n#     param.requires_grad = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T00:05:21.557483Z","iopub.execute_input":"2025-09-17T00:05:21.558064Z","iopub.status.idle":"2025-09-17T00:05:21.562069Z","shell.execute_reply.started":"2025-09-17T00:05:21.55804Z","shell.execute_reply":"2025-09-17T00:05:21.56132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# now, finetune the entire network\n# results = train(model, train_loader, val_loader, 11, True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T00:46:20.029316Z","iopub.execute_input":"2025-09-17T00:46:20.029623Z","iopub.status.idle":"2025-09-17T04:19:19.347798Z","shell.execute_reply.started":"2025-09-17T00:46:20.0296Z","shell.execute_reply":"2025-09-17T04:19:19.347123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# for k, v in results.items():\n#     print(f'{k}:-')\n#     print(f'{v}\\n')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T04:24:00.35192Z","iopub.execute_input":"2025-09-17T04:24:00.352553Z","iopub.status.idle":"2025-09-17T04:24:00.359406Z","shell.execute_reply.started":"2025-09-17T04:24:00.352529Z","shell.execute_reply":"2025-09-17T04:24:00.358704Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# plt.figure(figsize=(18, 12))\n\n# plt.subplot(2,2,1)\n# plt.plot(results['loss_on_epoch'])\n# plt.title('The Average Train Loss per Epoch')\n# plt.xlabel('Epoch')\n# plt.ylabel('Loss')\n# plt.grid()\n# plt.xticks(np.arange(1, len(results['loss_on_epoch'])+1))\n\n\n# plt.subplot(2,2,2)\n# plt.plot(results['map_at_half'])\n# plt.title('mean average precission at IoU=0.5')\n# plt.xlabel('Epoch')\n# plt.ylabel('MAP')\n# plt.xticks(np.arange(1, len(results['map_at_half'])+1))\n# plt.grid()\n\n# plt.subplot(2,2,3)\n# plt.plot(results['mean_iou'])\n# plt.title('The Average IoU per Image on Validation Data')\n# plt.xlabel('Epoch')\n# plt.ylabel('IoU')\n# plt.xticks(np.arange(1, len(results['mean_iou'])+1))\n# plt.grid()\n\n# plt.subplot(2,2,4)\n# plt.plot(results['mean_dice'])\n# plt.title('The Average Dice per Image on Validation Data')\n# plt.xlabel('Epoch')\n# plt.ylabel('Dice')\n# plt.xticks(np.arange(1, len(results['mean_dice'])+1))\n# plt.grid()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T04:24:42.813754Z","iopub.execute_input":"2025-09-17T04:24:42.814478Z","iopub.status.idle":"2025-09-17T04:24:43.536421Z","shell.execute_reply.started":"2025-09-17T04:24:42.814454Z","shell.execute_reply":"2025-09-17T04:24:43.535647Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save model after training finishes\n# torch.save(model.state_dict(), \"/kaggle/working/fasterrcnn_final.pth\")\n\n# print(\"Now go to 'Save Version / Commit' to keep it in Outputs.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T04:27:58.468431Z","iopub.execute_input":"2025-09-17T04:27:58.468699Z","iopub.status.idle":"2025-09-17T04:27:58.709226Z","shell.execute_reply.started":"2025-09-17T04:27:58.468677Z","shell.execute_reply":"2025-09-17T04:27:58.708628Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Now, I will train YOLO model to compare with Faster RCNN","metadata":{}},{"cell_type":"code","source":"# !pip install ultralytics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:48:40.486265Z","iopub.execute_input":"2025-09-18T12:48:40.486863Z","iopub.status.idle":"2025-09-18T12:49:51.742712Z","shell.execute_reply.started":"2025-09-18T12:48:40.486838Z","shell.execute_reply":"2025-09-18T12:49:51.741971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from ultralytics import YOLO","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:54:24.788862Z","iopub.execute_input":"2025-09-18T12:54:24.789116Z","iopub.status.idle":"2025-09-18T12:54:24.841975Z","shell.execute_reply.started":"2025-09-18T12:54:24.789099Z","shell.execute_reply":"2025-09-18T12:54:24.841361Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # create the labels with the format required by YOLO\n# train_imgs = [uniqe_imgs[i] for i in train_indices]\n\n# labels_path = '/kaggle/working/labels/train'\n# imgs_path = '/kaggle/working/images/train'\n# os.makedirs(labels_path, exist_ok=True)\n# os.makedirs(imgs_path, exist_ok=True)\n\n# for img in train_imgs:\n#     shutil.copy(os.path.join(train_dir, img), imgs_path)\n#     part = train_df[train_df['image_id'] == img[:-4]]\n#     with open(f'{os.path.join(labels_path, img[:-4])}.txt', 'w') as f:\n        \n#         for row in part.itertuples():\n#             x_cent = (row.x1 + row.x2) / 2.0 / row.width\n#             y_cent = (row.y1 + row.y2) / 2.0 / row.height\n#             w = (row.x2 - row.x1) / row.width\n#             h = (row.y2 - row.y1) / row.height\n#             f.write(f'0 {x_cent} {y_cent} {w} {h}\\n')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:50:43.401967Z","iopub.execute_input":"2025-09-18T12:50:43.402243Z","iopub.status.idle":"2025-09-18T12:51:45.459885Z","shell.execute_reply.started":"2025-09-18T12:50:43.402221Z","shell.execute_reply":"2025-09-18T12:51:45.459098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# val_imgs = [uniqe_imgs[i] for i in val_indices]\n# labels_path = '/kaggle/working/labels/val'\n# imgs_path = '/kaggle/working/images/val'\n# os.makedirs(labels_path, exist_ok=True)\n# os.makedirs(imgs_path, exist_ok=True)\n\n# for img in val_imgs:\n#     shutil.copy(os.path.join(train_dir, img), imgs_path)\n#     part = train_df[train_df['image_id'] == img[:-4]]\n#     with open(f'{os.path.join(labels_path, img[:-4])}.txt', 'w') as f:\n        \n#         for row in part.itertuples():\n#             x_cent = (row.x1 + row.x2) / 2.0 / row.width\n#             y_cent = (row.y1 + row.y2) / 2.0 / row.height\n#             w = (row.x2 - row.x1) / row.width\n#             h = (row.y2 - row.y1) / row.height\n#             f.write(f'0 {x_cent} {y_cent} {w} {h}\\n')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:51:45.460965Z","iopub.execute_input":"2025-09-18T12:51:45.461258Z","iopub.status.idle":"2025-09-18T12:51:52.516121Z","shell.execute_reply.started":"2025-09-18T12:51:45.46124Z","shell.execute_reply":"2025-09-18T12:51:52.515544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# with open('data.yaml', 'w') as f:\n#   f.write(f'path: /kaggle/working\\n\\n')\n#   f.write(f'train: images/train\\n')\n#   f.write(f'val: images/val\\n\\n')\n#   f.write(f'names:\\n')\n#   f.write(f'  0: wheat\\n')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:52:35.077258Z","iopub.execute_input":"2025-09-18T12:52:35.077545Z","iopub.status.idle":"2025-09-18T12:52:35.081872Z","shell.execute_reply.started":"2025-09-18T12:52:35.077524Z","shell.execute_reply":"2025-09-18T12:52:35.081123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# load the pretrained model\n# yolo_model = YOLO(\"yolo11s.pt\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:54:30.504503Z","iopub.execute_input":"2025-09-18T12:54:30.505106Z","iopub.status.idle":"2025-09-18T12:54:30.573273Z","shell.execute_reply.started":"2025-09-18T12:54:30.505082Z","shell.execute_reply":"2025-09-18T12:54:30.572493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train_result = yolo_model.train(\n#     data = '/kaggle/working/data.yaml' ,\n#     epochs = 10,\n#     imgsz = 800,\n#     optimizer= 'Adam',\n#     device = 'cuda',\n#     batch = 4\n# )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T12:54:37.714579Z","iopub.execute_input":"2025-09-18T12:54:37.715135Z","iopub.status.idle":"2025-09-18T13:18:07.702647Z","shell.execute_reply.started":"2025-09-18T12:54:37.715111Z","shell.execute_reply":"2025-09-18T13:18:07.701857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train_data = pd.read_csv('/kaggle/working/runs/detect/train2/results.csv')\n# train_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T13:20:44.063608Z","iopub.execute_input":"2025-09-18T13:20:44.064306Z","iopub.status.idle":"2025-09-18T13:20:44.085961Z","shell.execute_reply.started":"2025-09-18T13:20:44.064283Z","shell.execute_reply":"2025-09-18T13:20:44.08536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# plt.figure(figsize=(18,15))\n# plt.imshow(Image.open('/kaggle/working/runs/detect/train2/results.png'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T13:19:21.540036Z","iopub.execute_input":"2025-09-18T13:19:21.540635Z","iopub.status.idle":"2025-09-18T13:19:22.242777Z","shell.execute_reply.started":"2025-09-18T13:19:21.540613Z","shell.execute_reply":"2025-09-18T13:19:22.242045Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# precission & recall curves\n# curves = glob.glob(f'/kaggle/working/runs/detect/train2/*curve.png') \n# row, col = 1, 2\n# fig, ax = plt.subplots(row, col, figsize=(20, 14))\n# for i in range(2):\n#     ax[i].imshow(Image.open(curves[i]))\n#     ax[i].axis(\"off\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T13:19:37.477385Z","iopub.execute_input":"2025-09-18T13:19:37.478017Z","iopub.status.idle":"2025-09-18T13:19:38.484045Z","shell.execute_reply.started":"2025-09-18T13:19:37.477989Z","shell.execute_reply":"2025-09-18T13:19:38.483256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# row, col = 1, 2\n# fig, ax = plt.subplots(row, col, figsize=(20, 14))\n# for i in range(2, 4):\n#     ax[i-2].imshow(Image.open(curves[i]))\n#     ax[i-2].axis(\"off\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T13:19:47.993418Z","iopub.execute_input":"2025-09-18T13:19:47.994126Z","iopub.status.idle":"2025-09-18T13:19:49.010732Z","shell.execute_reply.started":"2025-09-18T13:19:47.994099Z","shell.execute_reply":"2025-09-18T13:19:49.010033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# confusion matrix\n# conf = glob.glob(f'/kaggle/working/runs/detect/train2/*confusion_matrix*png') \n\n# row, col = 1, 2\n# fig, ax = plt.subplots(row, col, figsize=(20, 14))\n# for i in range(2):\n#     ax[i].imshow(Image.open(conf[i]))\n#     ax[i].axis(\"off\")\n\n# plt.subplots_adjust(wspace=0.01)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T13:19:56.530639Z","iopub.execute_input":"2025-09-18T13:19:56.531253Z","iopub.status.idle":"2025-09-18T13:19:58.504287Z","shell.execute_reply.started":"2025-09-18T13:19:56.531226Z","shell.execute_reply":"2025-09-18T13:19:58.503665Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# photos = ['/kaggle/working/runs/detect/train2/val_batch1_labels.jpg', \n#          '/kaggle/working/runs/detect/train2/val_batch1_pred.jpg',]\n\n# row, col = 1, 2\n# fig, ax = plt.subplots(row, col, figsize=(20, 14))\n# for i in range(2):\n#     ax[i].imshow(Image.open(photos[i]))\n#     ax[i].set_title(f\"val batch 1 {'labels' if not i else 'predictions'} From epoch 0\", size=20)\n#     ax[i].axis(\"off\")\n\n# plt.subplots_adjust(wspace=0.1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T13:20:12.579526Z","iopub.execute_input":"2025-09-18T13:20:12.580209Z","iopub.status.idle":"2025-09-18T13:20:14.285885Z","shell.execute_reply.started":"2025-09-18T13:20:12.580187Z","shell.execute_reply":"2025-09-18T13:20:14.284878Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Try both models on the given test sample","metadata":{}},{"cell_type":"code","source":"# load the best found weights\n# yolo = YOLO('/kaggle/working/runs/detect/train2/weights/best.pt')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T13:22:34.846514Z","iopub.execute_input":"2025-09-18T13:22:34.847268Z","iopub.status.idle":"2025-09-18T13:22:34.901345Z","shell.execute_reply.started":"2025-09-18T13:22:34.847243Z","shell.execute_reply":"2025-09-18T13:22:34.900702Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# create the same Faster RCNN structure and load the trained weights on it\nfaster_model = fasterrcnn_resnet50_fpn(pretrained=False)\nin_features = faster_model.roi_heads.box_predictor.cls_score.in_features\nfaster_model.roi_heads.box_predictor = FastRCNNPredictor(in_features, 2)\n\nfaster_model.load_state_dict(torch.load(\"/kaggle/input/faster-rcnn-weights/fasterrcnn_final (1).pth\"\n                                        , map_location=device))\nfaster_model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T13:22:58.11602Z","iopub.execute_input":"2025-09-18T13:22:58.116784Z","iopub.status.idle":"2025-09-18T13:23:00.724772Z","shell.execute_reply.started":"2025-09-18T13:22:58.116748Z","shell.execute_reply":"2025-09-18T13:23:00.724Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# get YOLO predictions\n# yolo_test_pred = yolo.predict(test_dir, save=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T13:23:17.951623Z","iopub.execute_input":"2025-09-18T13:23:17.951955Z","iopub.status.idle":"2025-09-18T13:23:18.569038Z","shell.execute_reply.started":"2025-09-18T13:23:17.951932Z","shell.execute_reply":"2025-09-18T13:23:18.568294Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # get the number sof wheat heads in yolo detections\n# out_dir = '/kaggle/working/runs/detect/predict'\n# yolo_out = {\n# '2fd875eaa.jpg':  25,\n# '348a992bb.jpg':  38,\n# '51b3e36ab.jpg':  26,\n# '51f1be19e.jpg':  25,\n# '53f253011.jpg':  34,\n# '796707dd7.jpg':  17,\n# 'aac893a91.jpg':  12,\n# 'cb8d261a3.jpg':  23,\n# 'cc3532ff6.jpg':  25,\n# 'f5a1f0358.jpg': 28 ,\n#     }\n# yolo_heads = {}\n# for name in os.listdir(out_dir):\n#     yolo_heads[os.path.join(out_dir, name)] = yolo_out[name]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T14:44:02.727559Z","iopub.execute_input":"2025-09-18T14:44:02.728114Z","iopub.status.idle":"2025-09-18T14:44:02.73264Z","shell.execute_reply.started":"2025-09-18T14:44:02.728089Z","shell.execute_reply":"2025-09-18T14:44:02.732126Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # get Faster RCNN predictions and draw the output boxes \n# output_dir = '/kaggle/working/faster_predict'\n# os.makedirs(output_dir, exist_ok=True)\n\n# faster_model.eval()\n# transform = v2.ToTensor()\n# threshold = 0.5\n# heads = {} # to count the number of wheat heasd per image\n# all_output = {}\n\n# for img_name in os.listdir(test_dir):\n#     heads_num = 0\n#     img_path = os.path.join(test_dir, img_name)\n#     img = cv2.imread(img_path)\n\n#     img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n#     img_tensor = transform(img_rgb).unsqueeze(0).to(device)\n\n#     # Get predictions\n#     with torch.no_grad():\n#         outputs = faster_model(img_tensor)\n#     all_output[img_name] = outputs[0]\n\n#     pred_boxes = outputs[0]['boxes'].cpu().numpy()\n#     pred_scores = outputs[0]['scores'].cpu().numpy()\n#     pred_labels = outputs[0]['labels'].cpu().numpy()\n\n#     # Draw boxes\n#     for box, score, label in zip(pred_boxes, pred_scores, pred_labels):\n#         if score >= threshold:\n#             x1, y1, x2, y2 = map(int, box)\n#             cv2.rectangle(img_rgb, (x1, y1), (x2, y2), (0, 0, 255), 2)\n#             cv2.putText(img_rgb, f\"{score:.2f}\", (x1, y1 - 10),\n#                         cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 3)\n#             heads_num += 1\n\n#     # Save result\n#     save_path = os.path.join(output_dir, img_name)\n#     cv2.imwrite(save_path, cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))\n#     heads[save_path] = heads_num","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T16:53:45.2793Z","iopub.execute_input":"2025-09-18T16:53:45.279931Z","iopub.status.idle":"2025-09-18T16:53:46.002962Z","shell.execute_reply.started":"2025-09-18T16:53:45.279903Z","shell.execute_reply":"2025-09-18T16:53:46.002106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# yolo_pred = list(map(lambda x: os.path.join('/kaggle/working/runs/detect/predict', x), os.listdir('/kaggle/working/runs/detect/predict')))\n# rcnn_pred = list(map(lambda x: os.path.join(output_dir, x), os.listdir(output_dir)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T16:39:26.248754Z","iopub.execute_input":"2025-09-18T16:39:26.249295Z","iopub.status.idle":"2025-09-18T16:39:26.253307Z","shell.execute_reply.started":"2025-09-18T16:39:26.249275Z","shell.execute_reply":"2025-09-18T16:39:26.252635Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# fig, ax = plt.subplots(10, 2, figsize=(12, 70))\n# c = 0\n# flag = True\n# for i in range(10):\n#     for j in range(2):\n#         if flag: # yolo pred\n#             ax[i][j].imshow(Image.open(yolo_pred[c]))\n#             ax[i][j].axis('off')\n#             ax[i][j].set_title(f'Number of detected Wheat heads : {yolo_heads[yolo_pred[c]]}')\n#         else: # RCNN pred\n#             ax[i][j].imshow(Image.open(rcnn_pred[c]))\n#             ax[i][j].set_title(f'Number of detected Wheat heads : {heads[rcnn_pred[c]]}')\n#             ax[i][j].axis('off')\n#         flag = not flag\n#     c += 1\n# print('\\t\\t\\tYOLO' ,end=' ')\n# print('\\t'*7, end=' ')\n# print('Faster RCNN')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T16:39:28.521856Z","iopub.execute_input":"2025-09-18T16:39:28.522565Z","iopub.status.idle":"2025-09-18T16:39:34.418818Z","shell.execute_reply.started":"2025-09-18T16:39:28.522544Z","shell.execute_reply":"2025-09-18T16:39:34.41745Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### We can see theat the confidence levels of Faster RCNN is much better than YOLO. Although I'm limiting the drawn boxes form RCNN predictions of those with confidence score > 0.5 , it almost still gets all the wheat heads correctly\n### Also, sometimes YOLO predicts more than one box for the same wheat head","metadata":{}},{"cell_type":"markdown","source":"## Preparing for Submission","metadata":{}},{"cell_type":"code","source":"# sample_submission\nsub = pd.read_csv('/kaggle/input/global-wheat-detection/sample_submission.csv')\nsub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T16:21:55.318932Z","iopub.execute_input":"2025-09-18T16:21:55.319245Z","iopub.status.idle":"2025-09-18T16:21:55.33465Z","shell.execute_reply.started":"2025-09-18T16:21:55.319224Z","shell.execute_reply":"2025-09-18T16:21:55.334008Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# faster RCNN\nfaster_model.eval()\ntransform = v2.ToTensor()\nall_output = {}\ntest_dir = '/kaggle/input/global-wheat-detection/test'\n\nfor img_name in os.listdir(test_dir):\n    img_path = os.path.join(test_dir, img_name)\n    img = cv2.imread(img_path)\n\n    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n    img_tensor = transform(img_rgb).unsqueeze(0).to(device)\n\n    # Get predictions\n    with torch.no_grad():\n        outputs = faster_model(img_tensor)\n    all_output[img_name] = outputs[0]\n\n\nimgs_names, boxes = [], []\nfor name, out in all_output.items():\n    imgs_names.append(name[:-4])\n    temp = []\n    for score, box in zip(out['scores'], out['boxes']):\n        x_min, y_min, x_max, y_max = box\n        temp.append(f'{score:.2f} {x_min:.2f} {x_max:.2f} {(x_max - x_min):.2f} {(y_max - y_min):.2f}')\n    boxes.append(' '.join(temp))\n    \nsubmission = pd.DataFrame({'image_id' : imgs_names, 'PredictionString' : boxes})\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T17:20:14.332111Z","iopub.execute_input":"2025-09-18T17:20:14.332654Z","iopub.status.idle":"2025-09-18T17:20:15.02021Z","shell.execute_reply.started":"2025-09-18T17:20:14.332632Z","shell.execute_reply":"2025-09-18T17:20:15.019576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}