{"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":1185810,"sourceType":"datasetVersion","datasetId":674157}],"dockerImageVersionId":30528,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# UAS ADTT KELOMPOK 11 PART 1\n# Kelompok 11\n\n1. Jasmine Husna Sanditya (2006571034)\n2. Muhammad Jauhar Hakim (2006463982)\n3. Yudistira Dwi Cahya (2006530492)","metadata":{"execution":{"iopub.status.busy":"2023-08-18T17:09:53.013747Z","iopub.execute_input":"2023-08-18T17:09:53.014161Z","iopub.status.idle":"2023-08-18T17:09:53.222182Z","shell.execute_reply.started":"2023-08-18T17:09:53.01413Z","shell.execute_reply":"2023-08-18T17:09:53.221213Z"},"editable":false}},{"cell_type":"markdown","source":"# Code and Data Reference\n* https://www.kaggle.com/code/anmolys/animal-detection-using-yolo-v8/notebook\n* https://www.kaggle.com/competitions/iwildcam-2020-fgvc7\n* https://github.com/microsoft/CameraTraps/blob/main/megadetector.md","metadata":{"editable":false}},{"cell_type":"markdown","source":"# Part 1 :\nAnimal Detection in African Wildlife (Buffalo, Elephant, Rhino, and Zebra)\n\nhttps://www.kaggle.com/code/hakim571/uas-adtt-kelompok-12-part-1\n\n# Part 2 :\nAnimal Detection in Wildlife Across the World\n\nhttps://www.kaggle.com/code/hakim571/uas-adtt-kelompok-12-part-2","metadata":{"editable":false}},{"cell_type":"markdown","source":"# Multi-class object detection ","metadata":{"editable":false}},{"cell_type":"markdown","source":"### This is a multi-class object detection problem. Let's solve it using state-of-the-art YOLOv8","metadata":{"editable":false}},{"cell_type":"code","source":"# Importing necessary libraries\nimport os\nimport shutil\nimport cv2\nimport random\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport copy\nimport wandb","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Installing ultralytics\n!pip install ultralytics\n!pip install -U ray[tune]","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from ultralytics import YOLO","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"You will need a unique API key to log in to Weights & Biases. \n\n1. If you don't have a Weights & Biases account, you can go to https://wandb.ai/site and create a FREE account.\n2. Access your API key: https://wandb.ai/authorize.\n\nThere are two ways you can login using a Kaggle kernel:\n\n1. Run a cell with `wandb.login()`. It will ask for the API key, which you can copy + paste in.\n2. You can also use Kaggle secrets to store your API key and use the code snippet below to login. Check out this [discussion post](https://www.kaggle.com/product-feedback/114053) to learn more about Kaggle secrets. \n\n","metadata":{"editable":false}},{"cell_type":"code","source":"from kaggle_secrets import UserSecretsClient\nuser_secrets = UserSecretsClient()\nsecret_value_0 = user_secrets.get_secret(\"wandb_api\")","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"wandb.login(key=secret_value_0)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Defining data path and listing its contents\nip_datapath='/kaggle/input/african-wildlife'\nos.listdir(ip_datapath)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Defining standard image size for YOLO v8\nimage_size=640","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Creating working directories","metadata":{"editable":false}},{"cell_type":"code","source":"# Creating paths for separate images and labels\ncurr_path=os.getcwd()\nimgtrainpath = os.path.join(curr_path,'images','train')\nimgvalpath=os.path.join(curr_path,'images','validation')\nimgtestpath=os.path.join(curr_path,'images','test')\n\nlabeltrainpath=os.path.join(curr_path,'labels','train')\nlabelvalpath=os.path.join(curr_path,'labels','validation')\nlabeltestpath=os.path.join(curr_path,'labels','test')\n\n# Creating directories for all paths defined\nos.makedirs(imgtrainpath)\nos.makedirs(imgvalpath)\nos.makedirs(imgtestpath)\nos.makedirs(labeltrainpath)\nos.makedirs(labelvalpath)\nos.makedirs(labeltestpath)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":" Let's take images and labels of all the classes to working space and also rename them according to their class which can be buffalo, elephant, rhino or zebra.","metadata":{"editable":false}},{"cell_type":"code","source":"for dirname in os.listdir(ip_datapath):\n    dirpath=os.path.join(ip_datapath, dirname)\n    for file in os.listdir(dirpath):\n        filepath=os.path.join(dirpath, file)\n        newname=dirname+'_'+file\n        if file.endswith((\".txt\")): # if label file, take it to label train path\n            shutil.copy(filepath, labeltrainpath)\n            path=os.path.join(labeltrainpath, file)\n            newpath=os.path.join(labeltrainpath, newname)\n        elif file.endswith((\".jpg\", \".JPG\")): # if image file, resize and take it to image train path\n            img_resized=cv2.resize(cv2.imread(filepath), (image_size, image_size))\n            path=os.path.join(imgtrainpath, file)\n            cv2.imwrite(path, img_resized)\n            newpath=os.path.join(imgtrainpath, newname)\n        os.rename(path, newpath) # Rename the file (label or image)\n                ","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Checking the number of image and label files for all classes\nlen(os.listdir(imgtrainpath)), len(os.listdir(labeltrainpath))","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Extracting and visualising test images","metadata":{"editable":false}},{"cell_type":"markdown","source":"Let's create a difficult test set to see how good our model can be.\n\n1. Of all the images, we will include those images in test set which have objects of different classes. So all test images will have at least 2 objects of different classes. For example, a buffalo and a zebra in a single image is qualified to be part of the test data set. There can be multiple objects too. So 2 elephants and 1 buffalo in a single image is also perfectly valid.\n\n2. For the training and validation set, we will have images of only one object class. For example, any image in training and validation set can have one type of animal only, say rhino. Although there can be multiple rhinos in the same image.\n\n","metadata":{"editable":false}},{"cell_type":"code","source":"# function to check if all elements in the list are actually the same\ndef are_all_elements_same(lst):\n    if not lst:\n        return True  # An empty list is considered to have all elements the same.\n\n    first_element = lst[0]\n    for element in lst[1:]:\n        if element != first_element:\n            return False\n\n    return True","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's extract all the images with different types of animals present in the same image","metadata":{"editable":false}},{"cell_type":"code","source":"for file in os.listdir(labeltrainpath):\n    classes_list=[]\n    with open(os.path.join(labeltrainpath, file), \"r\") as f:\n        for line in f:\n            class_id,_,_,_,_=line.strip().split(\" \")\n            classes_list.append(class_id) # creating list of all unique animal types in given image\n            \n    # Checking if different types of animals are present in image\n    if not are_all_elements_same(classes_list): \n        filepath=os.path.join(labeltrainpath, file)\n        newpath=os.path.join(labeltestpath, file)\n        shutil.move(filepath, newpath) # moving label file to test path\n        basename=os.path.splitext(file)[0]\n        print(basename) # printing the image name\n        imgfilename=basename+'.jpg'\n        oldimgfilepath=os.path.join(imgtrainpath, imgfilename)\n        newimgfilepath=os.path.join(imgtestpath, imgfilename) \n        shutil.move(oldimgfilepath, newimgfilepath) # moving image to test path\n\n    ","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The above printed images are all those which have more than one type of animal in it, although the name suggests that there is just one animal. Let's check few of these images","metadata":{"editable":false}},{"cell_type":"code","source":"plt.figure(figsize=(30,30))\nfor i in range(6):\n    test_image=os.path.join(imgtestpath, os.listdir(imgtestpath)[i])\n    ax=plt.subplot(3,2,i+1)\n    \n    # Display actual image\n    plt.imshow(cv2.imread(test_image)) \n    plt.xticks([])\n    plt.yticks([])","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can see how the above images have different types of animals in the same image","metadata":{"editable":false}},{"cell_type":"code","source":"# Checking the size of test dataset\nlen(os.listdir(imgtestpath)), len(os.listdir(labeltestpath))","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Checking the size of training(+validation) dataset\nlen(os.listdir(imgtrainpath)), len(os.listdir(labeltrainpath))","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Separating training and validation dataset","metadata":{"editable":false}},{"cell_type":"markdown","source":"Let's now partition the training dataset into training and validation dataset","metadata":{"editable":false}},{"cell_type":"code","source":"# moving 20% of data to validation\n\nfactor=0.2 \n\nfor file in random.sample(os.listdir(imgtrainpath), int(len(os.listdir(imgtrainpath))*factor)):\n    basename=os.path.splitext(file)[0]\n    textfilename=basename+'.txt'\n    labelfilepath=os.path.join(labeltrainpath, textfilename)\n    labeldestpath=os.path.join(labelvalpath, textfilename)\n    imgfilepath=os.path.join(imgtrainpath, file)\n    imgdestpath=os.path.join(imgvalpath, file)\n    shutil.move(imgfilepath, imgdestpath)\n    shutil.move(labelfilepath, labeldestpath)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Checking the size of training dataset\nlen(os.listdir(imgtrainpath)), len(os.listdir(labeltrainpath))","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Checking the size of validation dataset\nlen(os.listdir(imgvalpath)), len(os.listdir(labelvalpath))","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Image visualisation with bounding box","metadata":{"editable":false}},{"cell_type":"markdown","source":"It's time to write a function to obtain bounding box coordinates of the images from the label files The label files contain information about image type, image centre coordinates and image size.","metadata":{"editable":false}},{"cell_type":"code","source":"# function to obtain bounding box coordinates from text label files\ndef get_bbox_from_label(text_file_path):\n    bbox_list=[]\n    with open(text_file_path, \"r\") as file:\n        for line in file:\n            class_id,x_centre,y_centre,width,height=line.strip().split(\" \")\n            x1=(float(x_centre)+(float(width)/2))*image_size\n            x0=(float(x_centre)-(float(width)/2))*image_size\n            y1=(float(y_centre)+(float(height)/2))*image_size\n            y0=(float(y_centre)-(float(height)/2))*image_size\n            \n            vertices=np.array([[int(x0), int(y0)], [int(x1), int(y0)], \n                               [int(x1),int(y1)], [int(x0),int(y1)]])\n            bbox_list.append(vertices)      \n    return tuple(bbox_list)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# defining red color in RGB to draw bounding box\nred=(255,0,0) ","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Drawing bounding box for random images in training data\nplt.figure(figsize=(30,30))\nfor i in range(1,8,2):\n    k=random.randint(0, len(os.listdir(imgtrainpath))-1)\n    img_path=os.path.join(imgtrainpath, sorted(os.listdir(imgtrainpath))[k])\n    label_path=os.path.join(labeltrainpath, sorted(os.listdir(labeltrainpath))[k])\n    bbox=get_bbox_from_label(label_path) # extracting bounding box coordinates\n    image=cv2.imread(img_path)\n    image_copy=copy.deepcopy(image)\n    ax=plt.subplot(4, 2, i)\n    plt.imshow(image) # displaying image\n    plt.xticks([])\n    plt.yticks([])\n    cv2.drawContours(image_copy, bbox, -1, red, 2) # drawing bounding box on copy of image\n    ax=plt.subplot(4, 2, i+1)\n    plt.imshow(image_copy) # displaying image with bounding box\n    plt.xticks([])\n    plt.yticks([])","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Creating config file","metadata":{"editable":false}},{"cell_type":"code","source":"# defining newline variable for config file\nnewline='\\n'","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's declare the contents of configuration file","metadata":{"editable":false}},{"cell_type":"code","source":"# Starting with a comment in config file\nln_1='# Train/val/test sets'+newline\n\n# train, val and test path declaration\nln_2='train: ' +\"'\"+imgtrainpath+\"'\"+newline\nln_3='val: ' +\"'\" + imgvalpath+\"'\"+newline\nln_4='test: ' +\"'\" + imgtestpath+\"'\"+newline\nln_5=newline\nln_6='# Classes'+newline\n\n# names of the classes declaration\nln_7='names:'+newline\nln_8='  0: buffalo'+newline\nln_9='  1: elephant'+newline\nln_10='  2: rhino'+newline\nln_11='  3: zebra'\n\nconfig_lines=[ln_1, ln_2, ln_3, ln_4, ln_5, ln_6, ln_7, ln_8, ln_9, ln_10, ln_11]","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Creating path for config file\nconfig_path=os.path.join(curr_path, 'config.yaml')\nconfig_path","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Writing config file\nwith open(config_path, 'w') as f:\n    f.writelines(config_lines)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model training","metadata":{"editable":false}},{"cell_type":"code","source":"# Using YOLO's pretrained model architecture and weights for training\nmodel=YOLO('yolov8m.yaml').load('yolov8m.pt')","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# pip install -U ipywidgets\n# A NumPy version >=1.16.5 and <1.23.0","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Training the model\n# results=model.train(data=config_path, epochs=100, iou=0.5, conf=0.01)\nresults=model.train(data=config_path, epochs=10, iou=0.5, conf=0.01)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Results can be converted to a zip file using the following command which is commented right now. This zip file can be downloaded later if results are to be analysed locally","metadata":{"editable":false}},{"cell_type":"code","source":"# !zip -r results.zip /kaggle/working/runs/detect/train","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"mAP50 is the average precision value obtained by model at 50% IoU. This is the default metric used by YOLOv8 for object detection tasks.","metadata":{"editable":false}},{"cell_type":"markdown","source":"Let's see how the training progressed with epochs by visualizing the plots","metadata":{"editable":false}},{"cell_type":"code","source":"import cv2\nplt.figure(figsize=(30,30))\ntrainingresult_path=os.path.join(curr_path, 'runs', 'detect', 'train')\nresults_png=cv2.imread(os.path.join(trainingresult_path,'results.png'))\nplt.imshow(results_png)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### All losses- Box loss, class loss, dfl loss are decreasing with epochs.\n#### All metrics- Precision, Recall, mAP50 and mAP50-95 are increasing with epochs","metadata":{"editable":false}},{"cell_type":"markdown","source":"# Model Performance ","metadata":{"editable":false}},{"cell_type":"markdown","source":" Let's write functions for evaluating model metrics and displaying plots","metadata":{"editable":false}},{"cell_type":"code","source":"# function for evaluating model metrics map50\ndef evaluate_map50(trainedmodel, data_path, dataset='val'):\n    metrics=trainedmodel.val(data=data_path, split=dataset)\n    map50=round(metrics.box.map50, 3)\n    print(\"The mAP of model for all images on {0} dataset is {1}\".format(dataset,map50))\n    return metrics, map50","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# function for displaying plots created by YOLO\ndef display_curves(root_path):\n    plt.figure(figsize=(50,50))\n    \n    #displaying p curve\n    p_curve=cv2.imread(os.path.join(root_path,'P_curve.png'))\n    ax=plt.subplot(5,1,1)\n    plt.imshow(p_curve)\n    \n    #displaying r curve\n    r_curve=cv2.imread(os.path.join(root_path,'R_curve.png'))\n    ax=plt.subplot(5,1,2)\n    plt.imshow(r_curve)\n    \n    #displaying pr curve\n    pr_curve=cv2.imread(os.path.join(root_path,'PR_curve.png'))\n    ax=plt.subplot(5,1,3)\n    plt.imshow(pr_curve)\n    \n    #displaying f1 curve\n    f1_curve=cv2.imread(os.path.join(root_path,'F1_curve.png'))\n    ax=plt.subplot(5,1,4)\n    plt.imshow(f1_curve)\n    \n    #displaying confusion matrix\n    confusion_matrix=cv2.imread(os.path.join(root_path,'confusion_matrix.png'))\n    ax=plt.subplot(5,1,5)\n    plt.imshow(confusion_matrix)\n    ","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Evaluating train metrics\ntrain_metrics, train_map50=evaluate_map50(model, config_path, dataset='train')","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Here we see class 'all' in addition to the 4 classes. 'all' here aggregates the true positives, false positives and false negatives across all classses to calculate a single precision and recall value for the entire dataset\n\nLet me explain how P and R are defined for 'all':\n\nLet's say I have the following counts for each class:\n\n1. True Positives for Buffalo: TP_b\n\n2. False Positives for Buffalo: FP_b\n\n3. False Negatives for Buffalo: FN_b\n\nFollowing the same for other classes as well: elephant, rhino, and zebra.\n\nNow, I calculate the total true positives, total false positives, and total false negatives across all classes:\n\n- Total True Positives (TP_t): TP_b + TP_z + TP_r + TP_e\n\n- Total False Positives (FP_t): FP_b + FP_z + FP_r + FP_e\n\n- Total False Negatives (FN_t): FN_b + FN_z + FN_r + FN_e\n\nNow,\n\n* P_all= TP_t/(TP_t + FP_t)\n* R_all= TP_t/(TP_t + FN_t","metadata":{"editable":false}},{"cell_type":"code","source":"# Path storing model's performance on training dataset\ntrain_path=os.path.join(curr_path, 'runs', 'detect', 'train2') #val is a misnomer, it is actually measuring validation on training dataset","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Display plots on training data\ndisplay_curves(train_path)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Evaluating val metrics\nval_metrics, val_map50=evaluate_map50(model, config_path, dataset='val')","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Path storing model's performance on validation dataset\nval_path=os.path.join(curr_path, 'runs', 'detect', 'train3') ","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Display plots on validation data\ndisplay_curves(val_path)","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Evaluating test metrics\ntest_metrics, test_map50=evaluate_map50(model, config_path, dataset='test')","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Obtained quite good mAP on a challenging test dataset","metadata":{"editable":false}},{"cell_type":"code","source":"# Path storing model's performance on test dataset\ntest_path=os.path.join(curr_path, 'runs', 'detect', 'train4') #val3 is a misnomer, it is actually measuring validation on test dataset","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Display plots on test data\ndisplay_curves(test_path) ","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The above plots are not smooth because we have very less data (only 18 images) in test dataset","metadata":{"editable":false}},{"cell_type":"markdown","source":"# Visualizing model's performance on random test images","metadata":{"editable":false}},{"cell_type":"code","source":"plt.figure(figsize=(30,30))\nm=random.randint(0, 17) # Selecting random image number from 18 images in test dataset\nfor i in range(1,6,2):\n    test_image=os.path.join(imgtestpath, os.listdir(imgtestpath)[m])\n    ax=plt.subplot(3,2,i)\n    \n    # Display actual image\n    plt.imshow(cv2.imread(test_image)) \n    plt.xticks([])\n    plt.yticks([])\n    plt.title(\"Actual image\", fontsize = 40)\n    \n    # Predict \n    res = model(test_image)\n    res_plotted = res[0].plot()\n    ax=plt.subplot(3,2,i+1)\n    \n    # Display image with predictions\n    plt.imshow(res_plotted)\n    plt.title(\"Image with predictions\", fontsize = 40)\n    plt.xticks([])\n    plt.yticks([])\n    m=m+1","metadata":{"editable":false,"trusted":true},"execution_count":null,"outputs":[]}]}