{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.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"}],"dockerImageVersionId":30648,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# download pycocotools\n!pip install git+https://github.com/alperiox/cocoapi.git#subdirectory=PythonAPI","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-02-06T12:57:27.567340Z","iopub.execute_input":"2024-02-06T12:57:27.567683Z","iopub.status.idle":"2024-02-06T12:57:51.865824Z","shell.execute_reply.started":"2024-02-06T12:57:27.567656Z","shell.execute_reply":"2024-02-06T12:57:51.864705Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:51.867866Z","iopub.execute_input":"2024-02-06T12:57:51.868174Z","iopub.status.idle":"2024-02-06T12:57:51.872572Z","shell.execute_reply.started":"2024-02-06T12:57:51.868147Z","shell.execute_reply":"2024-02-06T12:57:51.871574Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# get the scripts for training and COCO-related utility functions\n\nos.system(\"wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/engine.py\")\nos.system(\"wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/utils.py\")\nos.system(\"wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/coco_utils.py\")\nos.system(\"wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/coco_eval.py\")\nos.system(\"wget https://raw.githubusercontent.com/pytorch/vision/main/references/detection/transforms.py\")","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:51.873819Z","iopub.execute_input":"2024-02-06T12:57:51.874163Z","iopub.status.idle":"2024-02-06T12:57:52.799679Z","shell.execute_reply.started":"2024-02-06T12:57:51.874129Z","shell.execute_reply":"2024-02-06T12:57:52.798490Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls /kaggle/input/global-wheat-detection","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:52.802343Z","iopub.execute_input":"2024-02-06T12:57:52.803271Z","iopub.status.idle":"2024-02-06T12:57:53.759630Z","shell.execute_reply.started":"2024-02-06T12:57:52.803235Z","shell.execute_reply":"2024-02-06T12:57:53.758627Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:53.760922Z","iopub.execute_input":"2024-02-06T12:57:53.761194Z","iopub.status.idle":"2024-02-06T12:57:54.131079Z","shell.execute_reply.started":"2024-02-06T12:57:53.761169Z","shell.execute_reply":"2024-02-06T12:57:54.130011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset_path = \"/kaggle/input/global-wheat-detection\"\n\ntrain_csv = pd.read_csv(os.path.join(dataset_path, \"train.csv\"))\n\ntrain_csv.head()","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:54.132385Z","iopub.execute_input":"2024-02-06T12:57:54.132856Z","iopub.status.idle":"2024-02-06T12:57:54.493201Z","shell.execute_reply.started":"2024-02-06T12:57:54.132824Z","shell.execute_reply":"2024-02-06T12:57:54.492232Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:54.494991Z","iopub.execute_input":"2024-02-06T12:57:54.495276Z","iopub.status.idle":"2024-02-06T12:57:54.499414Z","shell.execute_reply.started":"2024-02-06T12:57:54.495251Z","shell.execute_reply":"2024-02-06T12:57:54.498498Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from IPython.display import display\n\nids = train_csv['image_id'].unique()\nids = np.random.permutation(ids)\nvalid_ratio = 0.2\nN_valid = int(len(ids)*valid_ratio)\nvalid_idx = ids[:N_valid]\ntrain_idx = ids[N_valid:]\n\ntraining_csv = train_csv[train_csv[\"image_id\"].isin(train_idx)]\nvalidation_csv = train_csv[train_csv[\"image_id\"].isin(valid_idx)]\n\ndisplay(training_csv.head())\ndisplay(validation_csv.head())\n\nprint(\"Number of samples in training_csv: \", len(train_idx))\nprint(\"Number of samples in validation_csv: \", len(valid_idx))","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:54.500687Z","iopub.execute_input":"2024-02-06T12:57:54.500985Z","iopub.status.idle":"2024-02-06T12:57:54.573361Z","shell.execute_reply.started":"2024-02-06T12:57:54.500955Z","shell.execute_reply":"2024-02-06T12:57:54.572490Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"os.listdir(os.path.join(dataset_path, \"train\"))[:5]","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:54.574498Z","iopub.execute_input":"2024-02-06T12:57:54.574762Z","iopub.status.idle":"2024-02-06T12:57:54.861632Z","shell.execute_reply.started":"2024-02-06T12:57:54.574739Z","shell.execute_reply":"2024-02-06T12:57:54.860664Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_match(image_id, df) -> np.array:\n    bboxes = np.vstack(df[df[\"image_id\"] == image_id].bbox.apply(eval).values)\n    # [xmin, ymin, width, height] -> [xmin, ymin, xmax, ymax]\n    bboxes[:, 2] = bboxes[:, 0] + bboxes[:, 2]\n    bboxes[:, 3] = bboxes[:, 1] + bboxes[:, 3]\n    \n    return bboxes\n\nbboxes = get_match(\"944c60a15\", train_csv)\nbboxes[:5]","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:54.864880Z","iopub.execute_input":"2024-02-06T12:57:54.865217Z","iopub.status.idle":"2024-02-06T12:57:54.902276Z","shell.execute_reply.started":"2024-02-06T12:57:54.865192Z","shell.execute_reply":"2024-02-06T12:57:54.901305Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\n\nfrom torchvision.io import read_image\nfrom torchvision import tv_tensors\nfrom torchvision.utils import draw_bounding_boxes\nfrom torchvision.transforms.v2 import functional as F\n\nfrom torchvision.ops import box_area\n\nclass Dataset(torch.utils.data.Dataset):\n    def __init__(self, dataframe: pd.DataFrame, path: str, transforms = None):\n        self.df = dataframe\n        self.image_ids = self.df.image_id.unique()\n        self.path = path\n        self.transforms = transforms\n        self.get_match = lambda image_id: get_match(image_id, self.df)\n    \n    def __getitem__(self, idx):\n        sample_id = self.image_ids[idx]\n        \n        image_path = os.path.join(self.path, f\"{sample_id}.jpg\")\n        \n        bboxes_tensor = torch.tensor(self.get_match(sample_id), dtype=torch.float32)\n        \n        # read the image\n        image = tv_tensors.Image(read_image(image_path))\n        labels = torch.ones((bboxes_tensor.size(0), ), dtype=int)\n        iscrowd = torch.zeros((bboxes_tensor.size(0), ), dtype=int)\n        \n        \n        target = {\n            \"boxes\": tv_tensors.BoundingBoxes(bboxes_tensor, format=\"XYXY\", canvas_size=F.get_size(image)),\n            \"labels\": labels,\n            \"iscrowd\": iscrowd,\n            \"area\": box_area(bboxes_tensor),\n            \"image_id\": idx\n        }\n        \n        if self.transforms is not None:\n            image, target = self.transforms(image, target)\n        \n        return image, target\n\n    def __len__(self): return self.image_ids.shape[0]        ","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:57:54.903391Z","iopub.execute_input":"2024-02-06T12:57:54.903656Z","iopub.status.idle":"2024-02-06T12:58:01.066330Z","shell.execute_reply.started":"2024-02-06T12:57:54.903613Z","shell.execute_reply":"2024-02-06T12:58:01.065363Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"TRAINING_PATH = os.path.join(dataset_path, \"train\")\n\ntraining_set = Dataset(training_csv, TRAINING_PATH)\nvalidation_set = Dataset(validation_csv, TRAINING_PATH)","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:58:01.067649Z","iopub.execute_input":"2024-02-06T12:58:01.068537Z","iopub.status.idle":"2024-02-06T12:58:01.086307Z","shell.execute_reply.started":"2024-02-06T12:58:01.068503Z","shell.execute_reply":"2024-02-06T12:58:01.085374Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# pick a random sample and draw it\nfrom torchvision.utils import make_grid\nfrom PIL import Image\n\ndef draw_sample(image, bboxes):\n    bbox_image = draw_bounding_boxes(image, bboxes, width=3, colors=[\"red\"]*bboxes.size(0))\n    \n    grid = make_grid(torch.stack([image, bbox_image]), padding=10, pad_value=255)\n    \n    return Image.fromarray(grid.permute(1,2,0).numpy())\n    \n    \nrandom_idx = np.random.randint(0, len(training_set))\nsample = training_set[random_idx]\n\ndraw_sample(sample[0], sample[1]['boxes'])","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:58:01.087785Z","iopub.execute_input":"2024-02-06T12:58:01.088666Z","iopub.status.idle":"2024-02-06T12:58:01.985405Z","shell.execute_reply.started":"2024-02-06T12:58:01.088638Z","shell.execute_reply":"2024-02-06T12:58:01.984255Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nimport torchvision\n\ndef get_fasterrcnn():\n    model = torchvision.models.detection.fasterrcnn_resnet50_fpn_v2(weights=\"DEFAULT\")\n    \n    in_features = model.roi_heads.box_predictor.cls_score.in_features\n    \n    model.roi_heads.box_predictor = FastRCNNPredictor(in_features, 2)\n    \n    return model","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:58:01.986820Z","iopub.execute_input":"2024-02-06T12:58:01.987108Z","iopub.status.idle":"2024-02-06T12:58:01.994120Z","shell.execute_reply.started":"2024-02-06T12:58:01.987082Z","shell.execute_reply":"2024-02-06T12:58:01.992459Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torchvision.transforms import v2 as T\n\ndef get_transforms(train=False):\n    transforms = []\n    if train:\n        transforms.append(T.RandomHorizontalFlip(.5))\n    transforms.append(T.ToDtype(torch.float, scale=True))\n    transforms.append(T.ToPureTensor())\n    return T.Compose(transforms)","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:58:01.995381Z","iopub.execute_input":"2024-02-06T12:58:01.995688Z","iopub.status.idle":"2024-02-06T12:58:02.003177Z","shell.execute_reply.started":"2024-02-06T12:58:01.995654Z","shell.execute_reply":"2024-02-06T12:58:02.002369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import utils\n\nmodel = get_fasterrcnn()\ndataset = Dataset(training_csv, TRAINING_PATH, get_transforms())\ndataloader = torch.utils.data.DataLoader(\n    dataset,\n    batch_size=2,\n    shuffle=True,\n    num_workers=4,\n    collate_fn=utils.collate_fn\n)\n\n# check forward pass for training\nimages, targets = next(iter(dataloader))\nimages = list(image for image in images)\ntargets = [{k: v for k,v in target.items()} for target in targets]\noutput = model(images, targets)\nprint(output)\n\n# check inference\nmodel.eval()\nx = [torch.rand(3, 1024, 1024), torch.rand(3, 512, 512)]\np = model(x)\nprint(p)","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:58:02.004191Z","iopub.execute_input":"2024-02-06T12:58:02.004559Z","iopub.status.idle":"2024-02-06T12:58:21.818470Z","shell.execute_reply.started":"2024-02-06T12:58:02.004524Z","shell.execute_reply":"2024-02-06T12:58:21.817272Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from engine import train_one_epoch, evaluate\n\ndevice = torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\")\n\n# get the datasets\ntrain_dataset = Dataset(training_csv, TRAINING_PATH, get_transforms(train=True))\nvalid_dataset = Dataset(validation_csv, TRAINING_PATH, get_transforms(train=False))\n\n# define the dataloaders \ntrain_loader = torch.utils.data.DataLoader(\n    train_dataset,\n    batch_size=8,\n    shuffle=True,\n    num_workers=4,\n    collate_fn=utils.collate_fn\n)\n\nvalid_loader = torch.utils.data.DataLoader(\n    valid_dataset,\n    batch_size=1,\n    shuffle=False,\n    num_workers=4,\n    collate_fn=utils.collate_fn\n)\n\n# get the model\nmodel = get_fasterrcnn()\n# move the model to the device\nmodel.to(device)\n\n# optimization parameters\nparams = [p for p in model.parameters() if p.requires_grad]\noptimizer = torch.optim.SGD(\n    params,\n    lr=0.01, # the initial one will be 0.001 since the scheduler is gonna scale it \n    momentum=0.99,\n    weight_decay=0.0005\n)\n\n# the scheduler\nlr_scheduler = torch.optim.lr_scheduler.StepLR(\n    optimizer,\n    step_size=1,\n    gamma=0.1\n)\n\nnum_epochs = 4\n\nfor epoch in range(num_epochs):\n    train_one_epoch(model, optimizer, train_loader, device, epoch, print_freq=50)\n    lr_scheduler.step()\n    evaluate(model, valid_loader, device=device)","metadata":{"execution":{"iopub.status.busy":"2024-02-06T12:58:21.820153Z","iopub.execute_input":"2024-02-06T12:58:21.820989Z","iopub.status.idle":"2024-02-06T13:35:58.248068Z","shell.execute_reply.started":"2024-02-06T12:58:21.820950Z","shell.execute_reply":"2024-02-06T13:35:58.247076Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def predict(image):\n    eval_transform = get_transforms(train=False)\n\n    model.eval()\n    with torch.no_grad():\n        x = eval_transform(image).to(device)\n        predictions = model([x, ])\n        pred = predictions[0]\n    model.train()\n\n    # filter the predictions with the threshold of 0.4\n    pred_mask = (pred['scores'] >= .4).cpu()\n    # filter more by using non-maximum suppression with .85 as the threshold\n    nms_idx = nms(pred['boxes'], pred['scores'], .85).cpu()\n    nms_mask = torch.zeros_like(pred_mask)\n    nms_mask[nms_idx] = 1\n\n    mask = pred_mask * nms_mask # apply \"AND\" gate by multiplying the two\n\n    indices = torch.arange(0, mask.size(0), 1)[mask]\n\n    final_preds = {\n        \"boxes\": pred['boxes'][indices, :],\n        \"labels\": pred['labels'][indices],\n        \"scores\": pred['scores'][indices]\n    }\n    \n    return final_preds","metadata":{"execution":{"iopub.status.busy":"2024-02-06T14:00:50.554747Z","iopub.execute_input":"2024-02-06T14:00:50.555449Z","iopub.status.idle":"2024-02-06T14:00:50.563007Z","shell.execute_reply.started":"2024-02-06T14:00:50.555415Z","shell.execute_reply":"2024-02-06T14:00:50.562095Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from PIL import Image\nimport random\nfrom torchvision.ops import nms\n\n# pick a random sample\nrandom_idx = random.randint(0, len(validation_set))\nsample = validation_set[random_idx]\nground_truth = sample[1]\ngt_boxes = ground_truth['boxes']\ngt_labels = [str(label.item()) for label in ground_truth['labels']]\n\nimage = sample[0]\n\nfinal_preds = predict(image)\n\n\nimage = (255.0 * (image - image.min()) / (image.max() - image.min())).to(torch.uint8)\npred_labels = [f\"label {label}: {score:.3f}\" for label, score in zip(final_preds[\"labels\"], final_preds[\"scores\"])]\npred_boxes = final_preds[\"boxes\"].long()\ncolors = [\"white\"]*len(pred_labels)\n\nprint(image.shape, pred_boxes.shape)\n\nimage_with_boxes = draw_bounding_boxes(image=image, boxes=pred_boxes, labels=pred_labels, colors=colors)\nimage_with_boxes = draw_bounding_boxes(image=image_with_boxes, boxes=gt_boxes, labels=gt_labels, colors=['red']*len(gt_labels));\n\n\nImage.fromarray(image_with_boxes.permute(1,2,0).numpy())","metadata":{"execution":{"iopub.status.busy":"2024-02-06T14:00:52.584292Z","iopub.execute_input":"2024-02-06T14:00:52.584677Z","iopub.status.idle":"2024-02-06T14:00:53.239686Z","shell.execute_reply.started":"2024-02-06T14:00:52.584650Z","shell.execute_reply":"2024-02-06T14:00:53.238780Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# export the torchvision model\n\ntorch.save(model.state_dict(), \"model_weights.pth\")\n\n# test the save\nnew_model = get_fasterrcnn()\nnew_model.load_state_dict(torch.load('model_weights.pth'))\nnew_model.eval();","metadata":{"execution":{"iopub.status.busy":"2024-02-06T14:07:15.096667Z","iopub.execute_input":"2024-02-06T14:07:15.097040Z","iopub.status.idle":"2024-02-06T14:07:16.544173Z","shell.execute_reply.started":"2024-02-06T14:07:15.097014Z","shell.execute_reply":"2024-02-06T14:07:16.543270Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pd.read_csv(os.path.join(dataset_path, 'sample_submission.csv'))","metadata":{"execution":{"iopub.status.busy":"2024-02-06T13:49:53.370946Z","iopub.execute_input":"2024-02-06T13:49:53.371347Z","iopub.status.idle":"2024-02-06T13:49:53.396026Z","shell.execute_reply.started":"2024-02-06T13:49:53.371297Z","shell.execute_reply":"2024-02-06T13:49:53.395035Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def format_prediction_string(boxes, scores):\n    pred_strings = []\n    for j in zip(scores, boxes):\n        pred_strings.append(\"{0:.4f} {1} {2} {3} {4}\".format(j[0], j[1][0], j[1][1], j[1][2], j[1][3]))\n\n    return \" \".join(pred_strings)","metadata":{"execution":{"iopub.status.busy":"2024-02-06T13:57:06.793983Z","iopub.execute_input":"2024-02-06T13:57:06.794526Z","iopub.status.idle":"2024-02-06T13:57:06.800988Z","shell.execute_reply.started":"2024-02-06T13:57:06.794488Z","shell.execute_reply":"2024-02-06T13:57:06.799795Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# get the test set\ntest_images = os.listdir(os.path.join(dataset_path, \"test\"))\npredictions = []\n\nfor img_name in test_images:\n    img_path = os.path.join(dataset_path, \"test\", img_name)\n    img_id = img_name.split(\".\")[0]\n    \n    image = tv_tensors.Image(read_image(img_path))\n    preds = predict(image)\n    pred_str = format_prediction_string(preds['boxes'], preds['scores'])\n    \n    predictions.append((img_id, pred_str))\n    \nsubmission = pd.DataFrame(predictions, columns=['image_id', 'PredictionString'])\n\nsubmission","metadata":{"execution":{"iopub.status.busy":"2024-02-06T14:04:17.383051Z","iopub.execute_input":"2024-02-06T14:04:17.383554Z","iopub.status.idle":"2024-02-06T14:04:18.771024Z","shell.execute_reply.started":"2024-02-06T14:04:17.383521Z","shell.execute_reply":"2024-02-06T14:04:18.770005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.to_csv(\"submission.csv\", index=None);","metadata":{"execution":{"iopub.status.busy":"2024-02-06T14:04:57.171700Z","iopub.execute_input":"2024-02-06T14:04:57.172290Z","iopub.status.idle":"2024-02-06T14:04:57.181023Z","shell.execute_reply.started":"2024-02-06T14:04:57.172262Z","shell.execute_reply":"2024-02-06T14:04:57.180109Z"},"trusted":true},"execution_count":null,"outputs":[]}]}