{"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":30822,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Object Detection Model \n1. Importing essential libraries","metadata":{}},{"cell_type":"markdown","source":"## Importing libraries","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport random\nimport tensorflow as tf\nimport albumentations as albu\n\nfrom tensorflow import keras\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\nfrom PIL import Image, ImageDraw, ImageEnhance","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:17.253819Z","iopub.execute_input":"2025-01-12T12:05:17.254123Z","iopub.status.idle":"2025-01-12T12:05:26.055276Z","shell.execute_reply.started":"2025-01-12T12:05:17.254099Z","shell.execute_reply":"2025-01-12T12:05:26.054463Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_PATH = \"/kaggle/input/global-wheat-detection\"\nDATA_PATH = Path(DATA_PATH)\n\ntrain_dir = DATA_PATH / \"train\"\ntest_dir = DATA_PATH / \"test\"\n\ntrain_dir, test_dir","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:32.917908Z","iopub.execute_input":"2025-01-12T12:05:32.918425Z","iopub.status.idle":"2025-01-12T12:05:32.924364Z","shell.execute_reply.started":"2025-01-12T12:05:32.918400Z","shell.execute_reply":"2025-01-12T12:05:32.923584Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Understanding the Data ","metadata":{}},{"cell_type":"code","source":"labels_path = \"/kaggle/input/global-wheat-detection/train.csv\"\nlabels = pd.read_csv(labels_path)\nlabels.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:33.301678Z","iopub.execute_input":"2025-01-12T12:05:33.301993Z","iopub.status.idle":"2025-01-12T12:05:33.593139Z","shell.execute_reply.started":"2025-01-12T12:05:33.301967Z","shell.execute_reply":"2025-01-12T12:05:33.592423Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Shape of the data about the training labels : \", labels.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:33.594119Z","iopub.execute_input":"2025-01-12T12:05:33.594345Z","iopub.status.idle":"2025-01-12T12:05:33.598744Z","shell.execute_reply.started":"2025-01-12T12:05:33.594325Z","shell.execute_reply":"2025-01-12T12:05:33.597946Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"No. of unique images : \", len(np.unique(labels[\"image_id\"])))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:33.600077Z","iopub.execute_input":"2025-01-12T12:05:33.600351Z","iopub.status.idle":"2025-01-12T12:05:33.692400Z","shell.execute_reply.started":"2025-01-12T12:05:33.600332Z","shell.execute_reply":"2025-01-12T12:05:33.691599Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Minimum bboxes in one trainig image : \", labels[\"image_id\"].value_counts().min())\nprint(\"Maximum bboxes in one trainig image : \", labels[\"image_id\"].value_counts().max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:33.693412Z","iopub.execute_input":"2025-01-12T12:05:33.693656Z","iopub.status.idle":"2025-01-12T12:05:33.725119Z","shell.execute_reply.started":"2025-01-12T12:05:33.693625Z","shell.execute_reply":"2025-01-12T12:05:33.724540Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:33.909343Z","iopub.execute_input":"2025-01-12T12:05:33.909665Z","iopub.status.idle":"2025-01-12T12:05:33.942055Z","shell.execute_reply.started":"2025-01-12T12:05:33.909639Z","shell.execute_reply":"2025-01-12T12:05:33.941189Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Preparation","metadata":{}},{"cell_type":"code","source":"# Converting the bounding boxes into float type from object/str type\n# Also bboxes are in the format of [x, y, w, h] where x,y is the leftpoint and w,h is the width and height \n\ndef str_to_float(group):\n    bboxes = group[\"bbox\"].str.split(\",\", expand=True)\n    bboxes[0] = bboxes[0].str.slice(start=1)\n    bboxes[3] = bboxes[3].str.slice(stop=-1)\n    return bboxes.values.astype(float)\n\nnew_labels = labels.groupby(\"image_id\").apply(str_to_float)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:35.431974Z","iopub.execute_input":"2025-01-12T12:05:35.432283Z","iopub.status.idle":"2025-01-12T12:05:39.178258Z","shell.execute_reply.started":"2025-01-12T12:05:35.432256Z","shell.execute_reply":"2025-01-12T12:05:39.177369Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lets see the first 10 bounding bboxes of an image \nimage_id = \"b6ab77fd7\"\nnew_labels[image_id][:10]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:39.179513Z","iopub.execute_input":"2025-01-12T12:05:39.179726Z","iopub.status.idle":"2025-01-12T12:05:39.185278Z","shell.execute_reply.started":"2025-01-12T12:05:39.179709Z","shell.execute_reply":"2025-01-12T12:05:39.184527Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_image_ids = new_labels.index\nprint(all_image_ids[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:39.186426Z","iopub.execute_input":"2025-01-12T12:05:39.186697Z","iopub.status.idle":"2025-01-12T12:05:39.217977Z","shell.execute_reply.started":"2025-01-12T12:05:39.186668Z","shell.execute_reply":"2025-01-12T12:05:39.217209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lets split the data into training data and validation data \n# The criteria is 80 % training adn 20% validation data \n\ntrain_idx = int(round(0.8 * len(all_image_ids), 0))\ntrain_data = new_labels[:train_idx]\nvalidation_data = new_labels[train_idx:]\n\nprint(\"Length of training data : \", train_data.shape)\nprint(\"Length of validation data : \", validation_data.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:39.219118Z","iopub.execute_input":"2025-01-12T12:05:39.219381Z","iopub.status.idle":"2025-01-12T12:05:39.234953Z","shell.execute_reply.started":"2025-01-12T12:05:39.219356Z","shell.execute_reply":"2025-01-12T12:05:39.234312Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lets prepare the image and load it as numpy arrays to merge images and its labels\ndef load_image_as_pixels(image_id):\n    image = Image.open(str(train_dir / image_id) + \".jpg\")\n    image = image.resize((256, 256))\n    return np.asarray(image)  # Normalizing the pixels intensities","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:39.235567Z","iopub.execute_input":"2025-01-12T12:05:39.235754Z","iopub.status.idle":"2025-01-12T12:05:39.248838Z","shell.execute_reply.started":"2025-01-12T12:05:39.235738Z","shell.execute_reply":"2025-01-12T12:05:39.248302Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# From the image_id now we can have both the image pixels (helpful in plotting image) \n# and bbox coordinates (helpful in plotting the bbox in the image)\n\ntrain_pixels = {}\ntrain_labels = {}\n                \nfor image_id in tqdm(train_data.index):\n    train_pixels[image_id] = load_image_as_pixels(image_id)\n    train_labels[image_id] = train_data[image_id].copy() / 4     # bacause we resized the image from 1024 to 256 so 1024 / 4 = 256     ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:05:39.249573Z","iopub.execute_input":"2025-01-12T12:05:39.249834Z","iopub.status.idle":"2025-01-12T12:06:40.911117Z","shell.execute_reply.started":"2025-01-12T12:05:39.249807Z","shell.execute_reply":"2025-01-12T12:06:40.910206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_pixels = {}\nval_labels = {}\n\nfor image_id in tqdm(validation_data.index):\n    val_pixels[image_id] = load_image_as_pixels(image_id)\n    val_labels[image_id] = validation_data[image_id].copy() / 4","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:06:40.912067Z","iopub.execute_input":"2025-01-12T12:06:40.912386Z","iopub.status.idle":"2025-01-12T12:06:56.465190Z","shell.execute_reply.started":"2025-01-12T12:06:40.912355Z","shell.execute_reply":"2025-01-12T12:06:56.464384Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Visualization","metadata":{}},{"cell_type":"code","source":"# Lets visulaize the images and their labels\n\ndef plot_bboxes(image_id, bboxes, data=\"train\"):\n    image = Image.open(str(DATA_PATH / data) + \"/\" + image_id + \".jpg\")\n    image = image.resize((256, 256)) \n    draw = ImageDraw.Draw(image)\n    for bbox in bboxes:\n        draw_bbox(draw, bbox)\n    return np.asarray(image) / 255\n\ndef draw_bbox(draw, bbox):\n    x, y, w, h = bbox\n    draw.rectangle([x, y, x+w, y+h], width=2, outline=\"cyan\")\n    draw.circle([(x+w/2) , (y+h/2) ], radius=1, width=2, outline=\"cyan\")\n    return\n\ndef plot_images_with_bboxes(image_ids, image_labels, data=\"train\"):\n    img_pixels = []\n    for img_id in np.unique(image_ids):\n        bboxes_of_img = image_labels[img_id]\n        img_pixels.append(plot_bboxes(img_id, bboxes_of_img, data))\n\n    n = len(image_ids)\n    if n == 1:\n        plt.imshow(img_pixels[0])\n        return\n        \n    fig, axes = plt.subplots(1, n, figsize=(20*n, 20*n))\n    for i, img_pixel in enumerate(img_pixels):\n        axes[i].imshow(img_pixel)\n    return","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:06:56.467331Z","iopub.execute_input":"2025-01-12T12:06:56.467564Z","iopub.status.idle":"2025-01-12T12:06:56.473952Z","shell.execute_reply.started":"2025-01-12T12:06:56.467546Z","shell.execute_reply":"2025-01-12T12:06:56.473145Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lets make a function to visualize some random images along with thier bboxes\n\ndef show_images(n):\n    all_image_ids = list(new_labels.index.values)\n    image_ids = random.sample(all_image_ids, k=n)\n    image_pixels = []\n    \n    for img_id in image_ids:\n        if img_id in train_labels:\n            bboxes_of_img = train_labels[img_id]\n        else:\n            bboxes_of_img = val_labels[img_id]\n        image_pixels.append(plot_bboxes(img_id, bboxes_of_img))\n    \n    fig, axes = plt.subplots(1, n, figsize=(20*n, 20*n))\n    for i, img_pixel in enumerate(image_pixels):\n        axes[i].imshow(img_pixel)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:06:56.475310Z","iopub.execute_input":"2025-01-12T12:06:56.475636Z","iopub.status.idle":"2025-01-12T12:06:56.491639Z","shell.execute_reply.started":"2025-01-12T12:06:56.475608Z","shell.execute_reply":"2025-01-12T12:06:56.490865Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot the examples from train_data\nplot_images_with_bboxes(train_data.index[:2], train_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:06:56.492323Z","iopub.execute_input":"2025-01-12T12:06:56.492608Z","iopub.status.idle":"2025-01-12T12:06:57.977140Z","shell.execute_reply.started":"2025-01-12T12:06:56.492576Z","shell.execute_reply":"2025-01-12T12:06:57.976250Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot the examples from validataion_data\nplot_images_with_bboxes(validation_data.index[:2], val_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:06:57.978176Z","iopub.execute_input":"2025-01-12T12:06:57.978559Z","iopub.status.idle":"2025-01-12T12:06:59.320567Z","shell.execute_reply.started":"2025-01-12T12:06:57.978519Z","shell.execute_reply":"2025-01-12T12:06:59.319645Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Show some random images\nshow_images(4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:06:59.321584Z","iopub.execute_input":"2025-01-12T12:06:59.321882Z","iopub.status.idle":"2025-01-12T12:07:02.015487Z","shell.execute_reply.started":"2025-01-12T12:06:59.321852Z","shell.execute_reply":"2025-01-12T12:07:02.014595Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- So here you can see some images have very tiny bounding boxes that are very hard to inference by any model and some are huge bounding boxes that are present along with the tiny ones, causing overlapping, in this case we need to apply non-max suppression to avoid the irrelevant tiny and huge bounding boxes.\n- One more technique we can do is to first clean some images with extremely tiny bounding boxes like having an area of 10 or less.\n- Also we cannot remove the huge bounding boxes because it may be possible that the image had only one bounding box or may be the image is a zoomed image. This doesn't mean that only tiny bounding boxes are irrelevant. Some examples of huge bboxes that are irrelevant are shown below.\n- We can only remove the huge bounding boxes with the **Non-Max Suppression Algorithm**. Because it works over a submetric called **Intersection Over Union or (*IOU*)**, which we will see it in later sections.","metadata":{}},{"cell_type":"code","source":"# Lets see the largest and smallest area bboxes in the entire dataset\nall_image_ids = new_labels.index\nmin_area = float(\"inf\")\nmax_area = float(\"-inf\")\n\nfor ids in tqdm(all_image_ids):\n    if ids in train_labels:\n        for label in train_labels[ids]:\n            min_area = min(min_area, label[2]*label[3])\n            max_area = max(max_area, label[2]*label[3])\nprint(\"Largest area of bounding box in dataset : \", max_area)\nprint(\"Smallest area of bounding box in dataset : \", min_area)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:02.016328Z","iopub.execute_input":"2025-01-12T12:07:02.016646Z","iopub.status.idle":"2025-01-12T12:07:02.176902Z","shell.execute_reply.started":"2025-01-12T12:07:02.016617Z","shell.execute_reply":"2025-01-12T12:07:02.176022Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lets get some huge bounding boxes image_ids and plot it to see if those are relevant.\n\n# In training data\nHUGE_AREA_THRESHOLD = 10000\nhuge_bbox = []\nfor i, img_id in enumerate(train_data.index):\n    for label in train_labels[img_id]:\n        if label[2] * label[3] >= HUGE_AREA_THRESHOLD:    # Area of rect = h * w\n            huge_bbox.append(img_id)\n\nprint(f\"There are {len(huge_bbox)} huge bounding boxes found with the given threshold.\")\nplot_images_with_bboxes(huge_bbox[:2], train_labels)\nhuge_bbox.clear()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:02.177769Z","iopub.execute_input":"2025-01-12T12:07:02.178081Z","iopub.status.idle":"2025-01-12T12:07:03.575748Z","shell.execute_reply.started":"2025-01-12T12:07:02.178050Z","shell.execute_reply":"2025-01-12T12:07:03.574881Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# In validation data\nfor i, img_id in enumerate(validation_data.index):\n    for label in val_labels[img_id]:\n        if label[2] * label[3] >= HUGE_AREA_THRESHOLD:    \n            huge_bbox.append(img_id)\nprint(f\"There are {len(huge_bbox)} huge bounding boxes found with the given threshold.\")\nplot_images_with_bboxes(huge_bbox[:2], val_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:03.576794Z","iopub.execute_input":"2025-01-12T12:07:03.577084Z","iopub.status.idle":"2025-01-12T12:07:05.006052Z","shell.execute_reply.started":"2025-01-12T12:07:03.577061Z","shell.execute_reply":"2025-01-12T12:07:05.005067Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lets get some tiny bounding boxes image_ids and plot it to see if those are relevant.\n\n# In training data \nTINY_AREA_THRESHOLD = 5\ntiny_bbox = []\nfor i, img_id in enumerate(train_data.index):\n    for label in train_labels[img_id]:\n        if label[2] * label[3] <= TINY_AREA_THRESHOLD:    \n            tiny_bbox.append(img_id)\nprint(f\"There are {len(tiny_bbox)} tiny bounding boxes found with the given threshold.\")\nplot_images_with_bboxes(tiny_bbox[:2], train_labels)\ntiny_bbox.clear()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:05.006931Z","iopub.execute_input":"2025-01-12T12:07:05.007149Z","iopub.status.idle":"2025-01-12T12:07:06.472008Z","shell.execute_reply.started":"2025-01-12T12:07:05.007131Z","shell.execute_reply":"2025-01-12T12:07:06.471181Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# In validation data \n\nfor i, img_id in enumerate(validation_data.index):\n    for label in val_labels[img_id]:\n        if label[2] * label[3] <= TINY_AREA_THRESHOLD:    \n            tiny_bbox.append(img_id)\nprint(f\"There are {len(tiny_bbox)} tiny bounding boxes found with the given threshold.\")\nplot_images_with_bboxes(tiny_bbox[:2], val_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:06.473043Z","iopub.execute_input":"2025-01-12T12:07:06.473351Z","iopub.status.idle":"2025-01-12T12:07:07.834690Z","shell.execute_reply.started":"2025-01-12T12:07:06.473321Z","shell.execute_reply":"2025-01-12T12:07:07.833771Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- So it is very clear that inorder to get good inference we need our model to focus only on the relevant bounding boxes that are neither too huge nor too tiny.\n- This can be done manually by removing the bboxes from the labels dictionary that we have created earlier.\n- Both for training and validation data, we will remove the tiny and huge bounding boxes.\n- Since pixels dictionary consists of only resized image pixels we will change the labels dictionary only.","metadata":{}},{"cell_type":"markdown","source":"## Data Cleaning ","metadata":{}},{"cell_type":"code","source":"def clean_labels(labels_dict, image_ids, T_threshold, H_threshold):\n    cleaned_labels = {}\n    for img_id in image_ids:\n        cleaned_labels[img_id] = []\n        for label in labels_dict[img_id]:\n            if label[2] * label[3] > 0 and label[2] * label[3] <= T_threshold:  # tiny bbox\n                continue\n            elif label[2] * label[3] >= H_threshold:                           # huge bbox\n                continue\n            else:\n                cleaned_labels[img_id].append(label.copy())\n    return cleaned_labels","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:07.835406Z","iopub.execute_input":"2025-01-12T12:07:07.835666Z","iopub.status.idle":"2025-01-12T12:07:07.840500Z","shell.execute_reply.started":"2025-01-12T12:07:07.835644Z","shell.execute_reply":"2025-01-12T12:07:07.839672Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Choose threshold according to your own convinience\ntrain_labels = clean_labels(train_labels, train_data.index, 5, 20000) \nval_labels = clean_labels(val_labels, validation_data.index, 5, 20000)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:07.841336Z","iopub.execute_input":"2025-01-12T12:07:07.841624Z","iopub.status.idle":"2025-01-12T12:07:08.081712Z","shell.execute_reply.started":"2025-01-12T12:07:07.841595Z","shell.execute_reply":"2025-01-12T12:07:08.080788Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Generator  \n- Although we have keras built in data-generator for Image data called as `ImageDataGenerator`, but the data we are working with needs to be preprocessed in different way than its usually done.\n    -  creating an image grid of the same size (32 X 32) for each image\n    -  reshaping the bounding boxes to label grid (we are using a grid of 32 X 32 for each image)\n    -  relocating the bounding boxes if there is a transformation\n    -  shuffle the dataset, making the batches of it.\n    - and many other operations.\n<br>\n<br>\n\n- Therefore it's very important to use a Custom-Data-Generator for this task.\n- Note : This is constructed using a class so we need the Sequence lib from keras to convert it to a datagenerator class in tensorflow, so that model constructed with keras or tensorflow can be able to use it.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.utils import Sequence    \nclass DataGenerator(Sequence):\n    def __init__(self, \n                 image_ids,\n                 image_pixels,\n                 image_labels=None,\n                 shuffle=False,\n                 batch_size=1,\n                 transformation=False):\n        \n        super().__init__()\n        self.image_ids = image_ids\n        self.image_pixels = image_pixels\n        self.labels = image_labels\n        self.shuffle = shuffle\n        self.batch_size = batch_size\n        self.transformation = transformation\n        self.on_epoch_end()\n        self.image_grid = self.create_image_grid()\n        self.train_augmentation = None\n        self.val_augmentation = None\n        \n    def __len__(self):\n        \"\"\"Returns the length of the data of each batch\"\"\"\n        return int(np.floor(len(self.image_ids) / self.batch_size))\n\n    def __getitem__(self, index):\n        \"\"\"To get a batch of dataset with the index provided\"\"\"\n        batch_start = index * self.batch_size\n        batch_end = (index + 1) * self.batch_size\n        indexes = self.indexes[batch_start : batch_end]\n        image_ids = [self.image_ids[i] for i in indexes]\n        X, Y = self.__data_generation(image_ids)\n        return X, Y\n\n    def __data_generation(self, image_ids):\n        X, Y = [], []\n        for image_id in image_ids:\n            pixels = self.image_pixels[image_id]\n            bboxes = self.labels[image_id]\n\n            if self.transformation is True:\n                pixels, bboxes = self.transform_images(pixels, bboxes)\n            else:\n                pixels = self.contrast_image(pixels) \n                bboxes = self.create_label_grid(bboxes)\n\n            X.append(pixels)\n            Y.append(bboxes)\n        return np.array(X), np.array(Y)\n\n    def transform_images(self, pixels, bboxes):\n        bbox_labels = np.ones(len(bboxes))\n        aug_result = self.train_augmentations(image=pixels, bboxes=bboxes, labels=bbox_labels)\n        bboxes = self.create_label_grid(aug_result['bboxes'])\n        pixels = np.array(aug_result[\"image\"]) / 255\n        return pixels, bboxes\n\n    def contrast_image(self, pixels):        \n        aug_result = self.val_augmentations(image=pixels)\n        return np.array(aug_result['image']) / 255\n    \n    def on_epoch_end(self):\n        self.indexes = np.arange(len(self.image_ids))\n        if self.shuffle is True:\n            np.random.shuffle(self.indexes)\n\n    def create_image_grid(self):\n        \"\"\"\n            Converts the image into grid of 32 X 32 for the YOLO Algorithm,\n            where each cell is used to determine if there is a bbox present in the grid cell\n            or not. \n        \"\"\"\n        image_grid = np.zeros((32, 32, 4))\n        # [x, y, w, h]\n        # since we are dividing the image into 32X32 cells each cell w and h will be 256/32\n        cell = [0, 0, 256/32, 256/32]         \n        for i in range(32):\n            cell[0] = 0                        # each new row x will be 0\n            for j in range(32):\n                image_grid[i][j] = cell\n                cell[0] = cell[0] + cell[2]    # x + w for each new col\n            cell[1] = cell[1] + cell[3]        # y + h for each new row\n        return image_grid\n\n    \n    def create_label_grid(self, bboxes):\n        \"\"\"\n            Converts the label format from left-upper point and height-width\n            to midpoint and height-width format by assigning bbox to each grid cell\n            of the image from the actual labels stored in a dictionary.\n            Each cell will contain an array of size 10 i.e of 2 anchor boxes coordinates \n            and thier probabilities.\n        \"\"\"    \n        label_grid = np.zeros((32, 32, 10))      \n        for i in range(32):\n            for j in range(32):\n                cell = self.image_grid[i][j]\n                label_grid[i][j] = self.create_anchor_boxes(cell, bboxes)\n        return label_grid\n\n    def create_anchor_boxes(self, cell, bboxes):\n        \"\"\"\n            Create two anchor boxes if two bbox are present in the same cell\n            and changes the format into a yolo format i.e mid-point and height-width format\n        \"\"\"\n        x, y, width, height = cell\n        x_max = x + width\n        y_max = y + height\n\n        anchor_1 = np.zeros((5))\n        anchor_2 = np.zeros((5))\n\n        for bbox in bboxes:\n            box_x, box_y, box_width, box_height = bbox\n            box_x_mid = box_x + (box_width / 2)\n            box_y_mid = box_y + (box_height / 2)\n\n            if x <= box_x_mid < x_max and y <= box_y_mid < y_max:\n                if anchor_1[0] == 0:\n                    anchor_1 = self.change_format_box(cell, bbox)\n                elif anchor_2[0] == 0:\n                    anchor_2 = self.change_format_box(cell, bbox)\n                else:\n                    break\n        \n        return np.concatenate((anchor_1, anchor_2), axis=None)        \n\n    def change_format_box(self, cell, box):\n        \"\"\"Helper function for changing the format of anchor boxes with relative to each cell\"\"\"\n        cell_x, cell_y, cell_width, cell_height = cell\n        box_x, box_y, box_width, box_height = box\n\n        # center the box_x and box_y first \n        box_x = box_x + (box_width / 2)\n        box_y = box_y + (box_height / 2)\n\n        # Make it relative to the cell\n        box_x = (box_x - cell_x) / cell_width\n        box_y = (box_y - cell_y) / cell_height\n\n        # adjust the height and width of box \n        box_height = box_height / 256\n        box_width = box_width / 256\n\n        return [1, box_x, box_y, box_width, box_height]\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:11.546036Z","iopub.execute_input":"2025-01-12T12:07:11.546329Z","iopub.status.idle":"2025-01-12T12:07:11.589330Z","shell.execute_reply.started":"2025-01-12T12:07:11.546306Z","shell.execute_reply":"2025-01-12T12:07:11.588714Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# These are the data augmentation techniques for both the training and validation data \nDataGenerator.train_augmentations = albu.Compose([\n        albu.RandomSizedCrop(\n            min_max_height=(200, 200), \n            height=256, \n            width=256, \n            p=0.8\n        ),\n        albu.OneOf([\n            albu.HueSaturationValue(),\n            albu.RandomBrightnessContrast()\n        ], p=1),\n        albu.OneOf([\n            albu.HorizontalFlip(),\n            albu.RandomRotate90(),\n        ], p=1),\n        albu.CLAHE(p=1),\n        albu.ToGray(p=1)\n    ], \n    bbox_params={'format': 'coco', 'label_fields': ['labels']})     # using format coco due to originally given\n\nDataGenerator.val_augmentations = albu.Compose([\n    albu.CLAHE(p=1),\n    albu.ToGray(p=1),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:15.193863Z","iopub.execute_input":"2025-01-12T12:07:15.194139Z","iopub.status.idle":"2025-01-12T12:07:15.201755Z","shell.execute_reply.started":"2025-01-12T12:07:15.194118Z","shell.execute_reply":"2025-01-12T12:07:15.200838Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_image_ids = train_data.index.values\nval_image_ids = validation_data.index.values\n\n\ntrain_generator = DataGenerator(\n    train_image_ids,\n    train_pixels,\n    train_labels, \n    batch_size=10, \n    shuffle=True,\n    transformation=True\n)\n\nval_generator = DataGenerator(\n    val_image_ids, \n    val_pixels,\n    val_labels, \n    batch_size=5,\n    shuffle=False,\n    transformation=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:16.425410Z","iopub.execute_input":"2025-01-12T12:07:16.425757Z","iopub.status.idle":"2025-01-12T12:07:16.431672Z","shell.execute_reply.started":"2025-01-12T12:07:16.425732Z","shell.execute_reply":"2025-01-12T12:07:16.430967Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X, Y = train_generator[0]\nprint(\"Shape of input image : \", X.shape, \" <- [batch-size, img-height, img-width, colo-channels]\")\nprint(\"Shape of bounding boxes : \", Y.shape, \" <- [batch-size, split-size, split-size, 2 anchor-boxes]\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:18.445614Z","iopub.execute_input":"2025-01-12T12:07:18.445911Z","iopub.status.idle":"2025-01-12T12:07:18.826150Z","shell.execute_reply.started":"2025-01-12T12:07:18.445888Z","shell.execute_reply":"2025-01-12T12:07:18.825232Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lets see how the bouding boxes look like: \nlabel_grid = Y[9]\nfor i in range(32):\n    for j in range(32):\n        if label_grid[i][j][0] == 1:\n            print(label_grid[i][j])\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:18.827425Z","iopub.execute_input":"2025-01-12T12:07:18.827767Z","iopub.status.idle":"2025-01-12T12:07:18.833544Z","shell.execute_reply.started":"2025-01-12T12:07:18.827736Z","shell.execute_reply":"2025-01-12T12:07:18.832652Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Building ","metadata":{}},{"cell_type":"code","source":"# This is one of the replica of YOLO v3 model found on internet, however it will be modified a little bit from the original paper\n\nx_input = tf.keras.Input(shape=(256,256,3))\n\nx = tf.keras.layers.Conv2D(32, (3, 3), strides=(1, 1), padding='same')(x_input)\nx = tf.keras.layers.BatchNormalization()(x)\nx = tf.keras.layers.LeakyReLU(0.1)(x)\n\n########## block 1 ##########\nx = tf.keras.layers.Conv2D(64, (3, 3), strides=(1, 1), padding='same')(x)\nx = tf.keras.layers.BatchNormalization()(x)\nx = tf.keras.layers.LeakyReLU(0.1)(x)\n\nx_shortcut = x\n\nfor i in range(2):\n    x = tf.keras.layers.Conv2D(32, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Conv2D(64, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Add()([x_shortcut, x])\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x_shortcut = x\n\n\n########## block 2 ##########\nx = tf.keras.layers.Conv2D(128, (3, 3), strides=(1, 1), padding='same')(x)\nx = tf.keras.layers.BatchNormalization()(x)\nx = tf.keras.layers.LeakyReLU(0.1)(x)\n\nx_shortcut = x\n\nfor i in range(2):\n    x = tf.keras.layers.Conv2D(64, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Conv2D(128, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Add()([x_shortcut, x])\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x_shortcut = x\n\n########## block 3 ##########\nx = tf.keras.layers.Conv2D(256, (3, 3), strides=(2, 2), padding='same')(x)\nx = tf.keras.layers.BatchNormalization()(x)\nx = tf.keras.layers.LeakyReLU(0.1)(x)\n\nx_shortcut = x\n\nfor i in range(8):\n    x = tf.keras.layers.Conv2D(128, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Conv2D(256, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Add()([x_shortcut, x])\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x_shortcut = x\n\n    \n########## block 4 ##########\nx = tf.keras.layers.Conv2D(512, (3, 3), strides=(2, 2), padding='same')(x)\nx = tf.keras.layers.BatchNormalization()(x)\nx = tf.keras.layers.LeakyReLU(0.1)(x)\n\nx_shortcut = x\n\nfor i in range(8):\n    x = tf.keras.layers.Conv2D(256, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Conv2D(512, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Add()([x_shortcut, x])\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x_shortcut = x\n\n########## block 5 ##########\nx = tf.keras.layers.Conv2D(1024, (3, 3), strides=(2, 2), padding='same')(x)\nx = tf.keras.layers.BatchNormalization()(x)\nx = tf.keras.layers.LeakyReLU(0.1)(x)\n\nx_shortcut = x\n\nfor i in range(4):\n    x = tf.keras.layers.Conv2D(512, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Conv2D(1024, (3, 3), strides=(1, 1), padding='same')(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x = tf.keras.layers.Add()([x_shortcut, x])\n    x = tf.keras.layers.LeakyReLU(0.1)(x)\n\n    x_shortcut = x\n\n########## output layers ##########\nx = tf.keras.layers.Conv2D(512, (3, 3), strides=(1, 1), padding='same')(x)\nx = tf.keras.layers.BatchNormalization()(x)\nx = tf.keras.layers.LeakyReLU(0.1)(x)\n\nx = tf.keras.layers.Conv2D(256, (3, 3), strides=(1, 1), padding='same')(x)\nx = tf.keras.layers.BatchNormalization()(x)\nx = tf.keras.layers.LeakyReLU(0.1)(x)\n\nx = tf.keras.layers.Conv2D(128, (3, 3), strides=(1, 1), padding='same')(x)\nx = tf.keras.layers.BatchNormalization()(x)\nx = tf.keras.layers.LeakyReLU(0.1)(x)\n\npredictions = tf.keras.layers.Conv2D(10, (1, 1), strides=(1, 1), activation='sigmoid')(x)\n\nmodel = tf.keras.Model(inputs=x_input, outputs=predictions)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:20.984037Z","iopub.execute_input":"2025-01-12T12:07:20.984320Z","iopub.status.idle":"2025-01-12T12:07:22.467498Z","shell.execute_reply.started":"2025-01-12T12:07:20.984298Z","shell.execute_reply":"2025-01-12T12:07:22.466618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:24.183566Z","iopub.execute_input":"2025-01-12T12:07:24.183898Z","iopub.status.idle":"2025-01-12T12:07:24.396860Z","shell.execute_reply.started":"2025-01-12T12:07:24.183873Z","shell.execute_reply":"2025-01-12T12:07:24.396049Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## YOLO loss function","metadata":{}},{"cell_type":"code","source":"def yolo_loss_function(y_true, y_pred):\n    binary_crossentropy = tf.keras.losses.BinaryCrossentropy(\n        reduction=tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE\n    )\n    \n    prob_loss = binary_crossentropy(\n        tf.concat([y_true[:,:,:,0], y_true[:,:,:,5]], axis=0), \n        tf.concat([y_pred[:,:,:,0], y_pred[:,:,:,5]], axis=0)\n    )\n    \n    xy_loss = tf.keras.losses.MSE(\n        tf.concat([y_true[:,:,:,1:3], y_true[:,:,:,6:8]], axis=0), \n        tf.concat([y_pred[:,:,:,1:3], y_pred[:,:,:,6:8]], axis=0)\n    )\n    \n    wh_loss = tf.keras.losses.MSE(\n        tf.concat([y_true[:,:,:,3:5], y_true[:,:,:,8:10]], axis=0), \n        tf.concat([y_pred[:,:,:,3:5], y_pred[:,:,:,8:10]], axis=0)\n    )\n    \n    bboxes_mask = get_mask(y_true)\n    \n    xy_loss = xy_loss * bboxes_mask\n    wh_loss = wh_loss * bboxes_mask\n    \n    return prob_loss + xy_loss + wh_loss\n\n\ndef get_mask(y_true):\n    anchor_one_mask = tf.where(\n        y_true[:,:,:,0] == 0, \n        0.5, \n        5.0\n    )\n    \n    anchor_two_mask = tf.where(\n        y_true[:,:,:,5] == 0, \n        0.5, \n        5.0\n    )\n    \n    bboxes_mask = tf.concat(\n        [anchor_one_mask,anchor_two_mask],\n        axis=0\n    )\n    \n    return bboxes_mask\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:25.505759Z","iopub.execute_input":"2025-01-12T12:07:25.506069Z","iopub.status.idle":"2025-01-12T12:07:25.512919Z","shell.execute_reply.started":"2025-01-12T12:07:25.506043Z","shell.execute_reply":"2025-01-12T12:07:25.512135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = tf.keras.optimizers.Adam(learning_rate=0.0001)\n\nmodel.compile(\n    optimizer=optimizer, \n    loss=yolo_loss_function\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:31.850868Z","iopub.execute_input":"2025-01-12T12:07:31.851174Z","iopub.status.idle":"2025-01-12T12:07:31.865304Z","shell.execute_reply.started":"2025-01-12T12:07:31.851152Z","shell.execute_reply":"2025-01-12T12:07:31.864436Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"callbacks = [\n    tf.keras.callbacks.ReduceLROnPlateau(monitor='loss', patience=2, verbose=1),\n    tf.keras.callbacks.EarlyStopping(monitor='loss', patience=5, verbose=1, restore_best_weights=True),\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:32.222162Z","iopub.execute_input":"2025-01-12T12:07:32.222444Z","iopub.status.idle":"2025-01-12T12:07:32.226566Z","shell.execute_reply.started":"2025-01-12T12:07:32.222421Z","shell.execute_reply":"2025-01-12T12:07:32.225721Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Training \n- Since we have set the all parameters such as train_generator, val_generator, callbacks, optimizer and the yolo loss function we can start the model for training.\n- I have already seen that the model is not overfitting as well as under fitting from training it for initial 10 epochs, so training the model for atleast 30 to 50 epochs again can give you better results  ","metadata":{}},{"cell_type":"code","source":"history = model.fit(\n    train_generator,\n    validation_data=val_generator,\n    epochs=30,\n    callbacks=callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T12:07:42.350040Z","iopub.execute_input":"2025-01-12T12:07:42.350334Z","iopub.status.idle":"2025-01-12T15:07:10.768875Z","shell.execute_reply.started":"2025-01-12T12:07:42.350311Z","shell.execute_reply":"2025-01-12T15:07:10.768018Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Prediction Preprocessing \n- Since we get the output of shape (32, 32, 10) for each image so first we need to reshape it into a format that can be used for plotting several bounding boxes. There are many things to do like:\n    \n    - reshaping the predictions from (32, 32, 10) to (N, 4) where N is the number of bounding boxes\n    - changing the format from yolo to coco since the original labels are coco format\n    - etc.\n      \n- Also we used two anchor boxes for each cell in image grid (32 X 32) so we need a way to find out which anchor box fits the best for the cell having a true label / target bounding box.\n- This can be achieved with a submetric called as **Intersection Over Union *(IoU)*** and the filtering of those predicted bounding boxes can be done by using an algorithm called **Non-Max Suppression Algorithm**, where we filter out those boxes having a less IoU then a threshold, and we do this for each cell and each class (here in our case n_classes=1) ","metadata":{}},{"cell_type":"code","source":"def predictions_to_bboxes(bboxes, image_grid):\n    \"\"\"\n        Since we converted the image into 32 X 32 grid.\n        we need to convert the predictions to bboxes for entire image for vizualization\n        Converting from [x, y, w, h] to [x_min, y_min, x_max, y_max] along with the entire image dimension. (0 to 255)\n    \"\"\"\n    bboxes = bboxes.copy()\n    \n    im_width = (image_grid[:,:,2] * 32)\n    im_height = (image_grid[:,:,3] * 32)\n    \n    # descale x,y\n    bboxes[:,:,1] = (bboxes[:,:,1] * image_grid[:,:,2]) + image_grid[:,:,0]\n    bboxes[:,:,2] = (bboxes[:,:,2] * image_grid[:,:,3]) + image_grid[:,:,1]\n    bboxes[:,:,6] = (bboxes[:,:,6] * image_grid[:,:,2]) + image_grid[:,:,0]\n    bboxes[:,:,7] = (bboxes[:,:,7] * image_grid[:,:,3]) + image_grid[:,:,1]\n    \n    # descale width,height\n    bboxes[:,:,3] = bboxes[:,:,3] * im_width \n    bboxes[:,:,4] = bboxes[:,:,4] * im_height\n    bboxes[:,:,8] = bboxes[:,:,8] * im_width \n    bboxes[:,:,9] = bboxes[:,:,9] * im_height\n    \n    # centre x,y to top left x,y\n    bboxes[:,:,1] = bboxes[:,:,1] - (bboxes[:,:,3] / 2)\n    bboxes[:,:,2] = bboxes[:,:,2] - (bboxes[:,:,4] / 2)\n    bboxes[:,:,6] = bboxes[:,:,6] - (bboxes[:,:,8] / 2)\n    bboxes[:,:,7] = bboxes[:,:,7] - (bboxes[:,:,9] / 2)\n    \n    # width,heigth to x_max,y_max\n    bboxes[:,:,3] = bboxes[:,:,1] + bboxes[:,:,3]\n    bboxes[:,:,4] = bboxes[:,:,2] + bboxes[:,:,4]\n    bboxes[:,:,8] = bboxes[:,:,6] + bboxes[:,:,8]\n    bboxes[:,:,9] = bboxes[:,:,7] + bboxes[:,:,9]\n    \n    return bboxes","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:07:10.770371Z","iopub.execute_input":"2025-01-12T15:07:10.770766Z","iopub.status.idle":"2025-01-12T15:07:10.780279Z","shell.execute_reply.started":"2025-01-12T15:07:10.770728Z","shell.execute_reply":"2025-01-12T15:07:10.779466Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Intersection Over Union","metadata":{}},{"cell_type":"code","source":"def intersection_over_union(prediction, target):\n    \"\"\"\n        Returns the intersection over union area that is used for non max suppression\n        It returns for one anchor box only\n    \"\"\"\n    # Extract coordinates\n    box1_x1, box1_y1, box1_x2, box1_y2 = prediction\n    box2_x1, box2_y1, box2_x2, box2_y2 = target\n\n    # Calculate intersection coordinates\n    x1 = max(box1_x1, box2_x1)\n    y1 = max(box1_y1, box2_y1)\n    x2 = min(box1_x2, box2_x2)\n    y2 = min(box1_y2, box2_y2)\n\n    # Compute intersection area\n    intersection = max(0, x2 - x1) * max(0, y2 - y1)\n\n    # Compute areas of the two boxes\n    box1_area = abs((box1_x2 - box1_x1) * (box1_y2 - box1_y1))\n    box2_area = abs((box2_x2 - box2_x1) * (box2_y2 - box2_y1))\n\n    # Compute union area and IoU\n    union = box1_area + box2_area - intersection\n    iou = intersection / (union + 1e-6)\n\n    return iou","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:07:10.782065Z","iopub.execute_input":"2025-01-12T15:07:10.782280Z","iopub.status.idle":"2025-01-12T15:07:10.798869Z","shell.execute_reply.started":"2025-01-12T15:07:10.782262Z","shell.execute_reply":"2025-01-12T15:07:10.798022Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Non-Max Suppression (NMS)","metadata":{}},{"cell_type":"code","source":"def non_max_suppression(bboxes, iou_threshold, prob_threshold):\n    # Reshape to (N, 10) and filter by prob_threshold\n    bboxes = bboxes.reshape(-1, 10)\n    filtered_bboxes = []\n\n    for box in bboxes:\n        if box[0] > prob_threshold:  # Anchor 1\n            filtered_bboxes.append([box[0], *box[1:5]])\n        if box[5] > prob_threshold:  # Anchor 2\n            filtered_bboxes.append([box[5], *box[6:10]])\n\n    if not filtered_bboxes:\n        return np.empty((0, 4))  # Return empty array if no boxes pass threshold\n\n    # Convert to numpy array and sort by probability\n    filtered_bboxes = np.array(filtered_bboxes)\n    filtered_bboxes = filtered_bboxes[np.argsort(-filtered_bboxes[:, 0])]\n\n    bboxes_after_nms = []\n\n    # Apply Non-Max Suppression\n    while len(filtered_bboxes) > 0:\n        chosen_box = filtered_bboxes[0]\n        bboxes_after_nms.append(chosen_box)\n        filtered_bboxes = filtered_bboxes[1:]\n        filtered_bboxes = np.array([\n            box for box in filtered_bboxes\n            if intersection_over_union(chosen_box[1:], box[1:]) < iou_threshold\n        ])\n\n    return np.array(bboxes_after_nms)[:, 1:]  # Return only [x, y, w, h]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:07:10.800107Z","iopub.execute_input":"2025-01-12T15:07:10.800341Z","iopub.status.idle":"2025-01-12T15:07:10.815502Z","shell.execute_reply.started":"2025-01-12T15:07:10.800322Z","shell.execute_reply":"2025-01-12T15:07:10.814695Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def process_predictions(predictions, image_ids, image_grid):\n    bboxes = {}\n    for i, image_id in tqdm(enumerate(image_ids)):\n        predictions[i] = predictions_to_bboxes(predictions[i], image_grid)\n        bboxes[image_id] = non_max_suppression(predictions[i], iou_threshold=0.2, prob_threshold=0.2)\n        # back to coco shape\n        bboxes[image_id][:,2:4] = bboxes[image_id][:,2:4] - bboxes[image_id][:,0:2]\n    \n    return bboxes","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:07:10.816266Z","iopub.execute_input":"2025-01-12T15:07:10.816573Z","iopub.status.idle":"2025-01-12T15:07:10.831571Z","shell.execute_reply.started":"2025-01-12T15:07:10.816552Z","shell.execute_reply":"2025-01-12T15:07:10.830863Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Prediction on validation data ","metadata":{}},{"cell_type":"code","source":"image_grid = val_generator.image_grid\nval_predictions = model.predict(val_generator)\nval_image_ids = validation_data.index","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:07:10.832203Z","iopub.execute_input":"2025-01-12T15:07:10.832385Z","iopub.status.idle":"2025-01-12T15:07:58.692261Z","shell.execute_reply.started":"2025-01-12T15:07:10.832369Z","shell.execute_reply":"2025-01-12T15:07:58.691556Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_predictions = process_predictions(val_predictions, val_image_ids, image_grid)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:07:58.693020Z","iopub.execute_input":"2025-01-12T15:07:58.693244Z","iopub.status.idle":"2025-01-12T15:08:12.838713Z","shell.execute_reply.started":"2025-01-12T15:07:58.693225Z","shell.execute_reply":"2025-01-12T15:08:12.837873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_predictions[\"cc2bbb768\"].shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:12.841072Z","iopub.execute_input":"2025-01-12T15:08:12.841282Z","iopub.status.idle":"2025-01-12T15:08:12.845821Z","shell.execute_reply.started":"2025-01-12T15:08:12.841264Z","shell.execute_reply":"2025-01-12T15:08:12.845177Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lets see the predictions of our model\nplot_images_with_bboxes(val_image_ids[0:4], val_predictions)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:12.847968Z","iopub.execute_input":"2025-01-12T15:08:12.848190Z","iopub.status.idle":"2025-01-12T15:08:15.065137Z","shell.execute_reply.started":"2025-01-12T15:08:12.848170Z","shell.execute_reply":"2025-01-12T15:08:15.064092Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_with_bboxes(val_image_ids[4:8], val_predictions)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:15.066338Z","iopub.execute_input":"2025-01-12T15:08:15.066677Z","iopub.status.idle":"2025-01-12T15:08:17.299541Z","shell.execute_reply.started":"2025-01-12T15:08:15.066649Z","shell.execute_reply":"2025-01-12T15:08:17.298304Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_with_bboxes(val_image_ids[8:12], val_predictions)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:17.300333Z","iopub.execute_input":"2025-01-12T15:08:17.300562Z","iopub.status.idle":"2025-01-12T15:08:19.590559Z","shell.execute_reply.started":"2025-01-12T15:08:17.300544Z","shell.execute_reply":"2025-01-12T15:08:19.589566Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Evaluation","metadata":{}},{"cell_type":"code","source":"print('Epochs: ' + str(len(history.history['loss'])))\nprint('Final training loss: ' + str(history.history['loss'][-1]))\nprint('Final validation loss: ' + str(history.history['val_loss'][-1]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:19.591619Z","iopub.execute_input":"2025-01-12T15:08:19.591921Z","iopub.status.idle":"2025-01-12T15:08:19.598373Z","shell.execute_reply.started":"2025-01-12T15:08:19.591895Z","shell.execute_reply":"2025-01-12T15:08:19.597448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, ax = plt.subplots(1, 2, figsize=(15, 5))\n\nax[0].set_title('Training Loss')\nax[0].plot(history.history['loss'])\n\nax[1].set_title('Validation Loss')\nax[1].plot(history.history['val_loss'])\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:19.599212Z","iopub.execute_input":"2025-01-12T15:08:19.599485Z","iopub.status.idle":"2025-01-12T15:08:19.875795Z","shell.execute_reply.started":"2025-01-12T15:08:19.599441Z","shell.execute_reply":"2025-01-12T15:08:19.873070Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.makedirs(\"models\", exist_ok=True)\nmodel.save(\"models/yolo_model.h5\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:19.876627Z","iopub.execute_input":"2025-01-12T15:08:19.876856Z","iopub.status.idle":"2025-01-12T15:08:21.618019Z","shell.execute_reply.started":"2025-01-12T15:08:19.876836Z","shell.execute_reply":"2025-01-12T15:08:21.617116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TEST_IMAGE_DIR = \"/kaggle/input/global-wheat-detection/test\"\nPRED_DIR = \"predictions\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:21.619201Z","iopub.execute_input":"2025-01-12T15:08:21.619545Z","iopub.status.idle":"2025-01-12T15:08:21.623997Z","shell.execute_reply.started":"2025-01-12T15:08:21.619514Z","shell.execute_reply":"2025-01-12T15:08:21.622863Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_predictions = []\ntest_image_ids = os.listdir(TEST_IMAGE_DIR)\ntest_image_ids = [image_id[:-4] for image_id in test_image_ids]\n\nfor i, image_id in tqdm(enumerate(test_image_ids)):\n    image = Image.open(TEST_IMAGE_DIR + \"/\" + image_id + \".jpg\")\n    image = image.resize((256, 256))\n    pixels = np.asarray(image)\n    \n    # same as val_augmentations\n    augmentations = albu.Compose([\n        albu.CLAHE(p=1),\n        albu.ToGray(p=1)\n    ])\n\n    aug_result = augmentations(image=pixels)\n    pixels = np.array(aug_result[\"image\"]) / 255\n    pixels = np.expand_dims(pixels, axis=0)\n\n    bboxes = model.predict(pixels)\n    test_predictions.append(bboxes)\n\ntest_predictions = np.concatenate(test_predictions)\ntest_predictions = process_predictions(test_predictions, test_image_ids, image_grid)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:21.624841Z","iopub.execute_input":"2025-01-12T15:08:21.625111Z","iopub.status.idle":"2025-01-12T15:08:26.724294Z","shell.execute_reply.started":"2025-01-12T15:08:21.625076Z","shell.execute_reply":"2025-01-12T15:08:26.723247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.makedirs(PRED_DIR, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:26.725039Z","iopub.execute_input":"2025-01-12T15:08:26.725296Z","iopub.status.idle":"2025-01-12T15:08:26.729041Z","shell.execute_reply.started":"2025-01-12T15:08:26.725274Z","shell.execute_reply":"2025-01-12T15:08:26.728323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i, image_id in tqdm(enumerate(test_image_ids)):\n    image = Image.open(TEST_IMAGE_DIR + \"/\" + image_id + \".jpg\")\n    image = image.resize((256, 256))\n    draw = ImageDraw.Draw(image)\n    bboxes = test_predictions[image_id]\n    for bbox in bboxes:\n        draw_bbox(draw, bbox)\n    image.save(PRED_DIR + \"/\" + image_id + \".jpg\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:26.729806Z","iopub.execute_input":"2025-01-12T15:08:26.730007Z","iopub.status.idle":"2025-01-12T15:08:26.922811Z","shell.execute_reply.started":"2025-01-12T15:08:26.729989Z","shell.execute_reply":"2025-01-12T15:08:26.921949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_with_bboxes(test_image_ids[:4], test_predictions, \"test\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:26.923699Z","iopub.execute_input":"2025-01-12T15:08:26.924002Z","iopub.status.idle":"2025-01-12T15:08:29.234073Z","shell.execute_reply.started":"2025-01-12T15:08:26.923973Z","shell.execute_reply":"2025-01-12T15:08:29.232929Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_with_bboxes(test_image_ids[4:8], test_predictions, \"test\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:29.235222Z","iopub.execute_input":"2025-01-12T15:08:29.235563Z","iopub.status.idle":"2025-01-12T15:08:31.630706Z","shell.execute_reply.started":"2025-01-12T15:08:29.235531Z","shell.execute_reply":"2025-01-12T15:08:31.629394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_images_with_bboxes(test_image_ids[8:], test_predictions, \"test\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-12T15:08:31.631433Z","iopub.execute_input":"2025-01-12T15:08:31.631747Z","iopub.status.idle":"2025-01-12T15:08:33.042151Z","shell.execute_reply.started":"2025-01-12T15:08:31.631719Z","shell.execute_reply":"2025-01-12T15:08:33.041168Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}