{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.10","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":26680,"databundleVersionId":2283525,"sourceType":"competition"},{"sourceId":2246635,"sourceType":"datasetVersion","datasetId":1347696}],"dockerImageVersionId":30097,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"This is a starter kernel to train a YOLOv5 model on [SIIM-FISABIO-RSNA COVID-19 Detection](https://www.kaggle.com/c/siim-covid19-detection/overview) dataset. Given an input image the task is to find the region of opacity in the chest  using bounding box coordinates. Check out [Visualize Bounding Boxes Interactively](https://www.kaggle.com/ayuraj/visualize-bounding-boxes-interactively) for interactive bounding box EDA. \n\n## 🖼️ What is YOLOv5?\n\nYOLO an acronym for 'You only look once', is an object detection algorithm that divides images into a grid system. Each cell in the grid is responsible for detecting objects within itself.\n\n[Ultralytics' YOLOv5](https://ultralytics.com/yolov5) (\"You Only Look Once\") model family enables real-time object detection with convolutional neural networks. \n\n## 🦄 What is Weights and Biases?\n\nWeights & Biases (W&B) is a set of machine learning tools that helps you build better models faster. Check out [Experiment Tracking with Weights and Biases](https://www.kaggle.com/ayuraj/experiment-tracking-with-weights-and-biases) to learn more.  \nWeights & Biases is directly integrated into YOLOv5, providing experiment metric tracking, model and dataset versioning, rich model prediction visualization, and more.\n\n\nIt's a work in progress:\n\n✔️ Required folder structure. <br>\n✔️ Bounding box format required for YOLOv5. <br>\n✔️ **Train** a small YOLOv5 model. <br>\n✔️ Experiment tracking with W&B. <br>\n✔️ Proper documentation <br>\n✔️ Inference <br>\n\n❌ Model prediction visualization. \n\n## Results \n\n### [Check out W&B Run Page $\\rightarrow$](https://wandb.ai/ayush-thakur/kaggle-siim-covid/runs/1bk93e3j)\n\n![img](https://i.imgur.com/quOYtNN.gif)","metadata":{}},{"cell_type":"markdown","source":"# ☀️ Imports and Setup\n\nAccording to the official [Train Custom Data](https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data) guide, YOLOv5 requires a certain directory structure. \n\n```\n/parent_folder\n    /dataset\n         /images\n         /labels\n    /yolov5\n```\n\n* We thus will create a `/tmp` directory. <br>\n* Download YOLOv5 repository and pip install the required dependencies. <br>\n* Install the latest version of W&B and login with your wandb account. You can create your free W&B account [here](https://wandb.ai/site).","metadata":{}},{"cell_type":"code","source":"%cd ../\n!mkdir tmp\n%cd tmp","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:01:43.79575Z","iopub.execute_input":"2025-11-17T07:01:43.796097Z","iopub.status.idle":"2025-11-17T07:01:44.824446Z","shell.execute_reply.started":"2025-11-17T07:01:43.796007Z","shell.execute_reply":"2025-11-17T07:01:44.823433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Download YOLOv5\n!git clone https://github.com/ultralytics/yolov5  # clone repo\n%cd yolov5\n# Install dependencies\n%pip install -qr requirements.txt  # install dependencies\n\n%cd ../\nimport torch\nprint(f\"Setup complete. Using torch {torch.__version__} ({torch.cuda.get_device_properties(0).name if torch.cuda.is_available() else 'CPU'})\")","metadata":{"execution":{"iopub.status.busy":"2025-11-17T07:01:51.690958Z","iopub.execute_input":"2025-11-17T07:01:51.691309Z","iopub.status.idle":"2025-11-17T07:01:59.27363Z","shell.execute_reply.started":"2025-11-17T07:01:51.691278Z","shell.execute_reply":"2025-11-17T07:01:59.272654Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Install W&B \n!pip install -q --upgrade wandb\n# Login \nimport wandb\nwandb.login()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T07:03:00.103034Z","iopub.execute_input":"2025-11-17T07:03:00.103371Z","iopub.status.idle":"2025-11-17T07:03:02.236376Z","shell.execute_reply.started":"2025-11-17T07:03:00.103344Z","shell.execute_reply":"2025-11-17T07:03:02.234731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Necessary/extra dependencies. \nimport os\nimport gc\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\nfrom shutil import copyfile\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\n\n#customize iPython writefile so we can write variables\nfrom IPython.core.magic import register_line_cell_magic\n\n@register_line_cell_magic\ndef writetemplate(line, cell):\n    with open(line, 'w') as f:\n        f.write(cell.format(**globals()))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2025-11-14T12:02:20.077252Z","iopub.execute_input":"2025-11-14T12:02:20.077564Z","iopub.status.idle":"2025-11-14T12:02:20.895749Z","shell.execute_reply.started":"2025-11-14T12:02:20.077538Z","shell.execute_reply":"2025-11-14T12:02:20.895072Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🦆 Hyperparameters","metadata":{}},{"cell_type":"code","source":"TRAIN_PATH = 'input/siim-covid19-resized-to-256px-jpg/train/'\nIMG_SIZE = 256\nBATCH_SIZE = 16\nEPOCHS = 10","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔨 Prepare Dataset\n\nThis is the most important section when it comes to training an object detector with YOLOv5. The directory structure, bounding box format, etc must be in the correct order. This section builds every piece needed to train a YOLOv5 model.\n\nI am using [xhlulu's](https://www.kaggle.com/xhlulu) resized dataset. The uploaded 256x256 Kaggle dataset is [here](https://www.kaggle.com/xhlulu/siim-covid19-resized-to-256px-jpg). Find other image resolutions [here](https://www.kaggle.com/c/siim-covid19-detection/discussion/239918).\n\n* Create train-validation split. <br>\n* Create required `/dataset` folder structure and more the images to that folder. <br>\n* Create `data.yaml` file needed to train the model. <br>\n* Create bounding box coordinates in the required YOLO format. ","metadata":{}},{"cell_type":"code","source":"# Everything is done from /kaggle directory.\n%cd ../\n\n# Load image level csv file\ndf = pd.read_csv('input/siim-covid19-detection/train_image_level.csv')\n\n# Modify values in the id column\ndf['id'] = df.apply(lambda row: row.id.split('_')[0], axis=1)\n# Add absolute path\ndf['path'] = df.apply(lambda row: TRAIN_PATH+row.id+'.jpg', axis=1)\n# Get image level labels\ndf['image_level'] = df.apply(lambda row: row.label.split(' ')[0], axis=1)\n\ndf.head(5)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load meta.csv file\n# Original dimensions are required to scale the bounding box coordinates appropriately.\nmeta_df = pd.read_csv('input/siim-covid19-resized-to-256px-jpg/meta.csv')\ntrain_meta_df = meta_df.loc[meta_df.split == 'train']\ntrain_meta_df = train_meta_df.drop('split', axis=1)\ntrain_meta_df.columns = ['id', 'dim0', 'dim1']\n\ntrain_meta_df.head(2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Merge both the dataframes\ndf = df.merge(train_meta_df, on='id',how=\"left\")\ndf.head(2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🍘 Train-validation split","metadata":{}},{"cell_type":"code","source":"# Create train and validation split.\ntrain_df, valid_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df.image_level.values)\n\ntrain_df.loc[:, 'split'] = 'train'\nvalid_df.loc[:, 'split'] = 'valid'\n\ndf = pd.concat([train_df, valid_df]).reset_index(drop=True)","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f'Size of dataset: {len(df)}, training images: {len(train_df)}. validation images: {len(valid_df)}')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🍚 Prepare Required Folder Structure\n\nThe required folder structure for the dataset directory is: \n\n```\n/parent_folder\n    /dataset\n         /images\n             /train\n             /val\n         /labels\n             /train\n             /val\n    /yolov5\n```\n\nNote that I have named the directory `covid`.","metadata":{}},{"cell_type":"code","source":"os.makedirs('tmp/covid/images/train', exist_ok=True)\nos.makedirs('tmp/covid/images/valid', exist_ok=True)\n\nos.makedirs('tmp/covid/labels/train', exist_ok=True)\nos.makedirs('tmp/covid/labels/valid', exist_ok=True)\n\n! ls tmp/covid/images","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Move the images to relevant split folder.\nfor i in tqdm(range(len(df))):\n    row = df.loc[i]\n    if row.split == 'train':\n        copyfile(row.path, f'tmp/covid/images/train/{row.id}.jpg')\n    else:\n        copyfile(row.path, f'tmp/covid/images/valid/{row.id}.jpg')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🍜 Create `.YAML` file\n\nThe `data.yaml`, is the dataset configuration file that defines \n\n1. an \"optional\" download command/URL for auto-downloading, \n2. a path to a directory of training images (or path to a *.txt file with a list of training images), \n3. a path to a directory of validation images (or path to a *.txt file with a list of validation images), \n4. the number of classes, \n5. a list of class names.\n\n> 📍 Important: In this competition, each image can either belong to `opacity` or `none` image-level labels. That's why I have  used the number of classes, `nc` to be 2. YOLOv5 automatically handles the images without any bounding box coordinates. \n\n> 📍 Note: The `data.yaml` is created in the `yolov5/data` directory as required. ","metadata":{}},{"cell_type":"code","source":"# Create .yaml file \nimport yaml\n\ndata_yaml = dict(\n    train = '../covid/images/train',\n    val = '../covid/images/valid',\n    nc = 2,\n    names = ['none', 'opacity']\n)\n\n# Note that I am creating the file in the yolov5/data/ directory.\nwith open('tmp/yolov5/data/data.yaml', 'w') as outfile:\n    yaml.dump(data_yaml, outfile, default_flow_style=True)\n    \n%cat tmp/yolov5/data/data.yaml","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🍮 Prepare Bounding Box Coordinated for YOLOv5\n\nFor every image with **bounding box(es)** a `.txt` file with the same name as the image will be created in the format shown below:\n\n* One row per object. <br>\n* Each row is class `x_center y_center width height format`. <br>\n* Box coordinates must be in normalized xywh format (from 0 - 1). We can normalize by the boxes in pixels by dividing `x_center` and `width` by image width, and `y_center` and `height` by image height. <br>\n* Class numbers are zero-indexed (start from 0). <br>\n\n> 📍 Note: We don't have to remove the images without bounding boxes from the training or validation sets. ","metadata":{}},{"cell_type":"code","source":"# Get the raw bounding box by parsing the row value of the label column.\n# Ref: https://www.kaggle.com/yujiariyasu/plot-3positive-classes\ndef get_bbox(row):\n    bboxes = []\n    bbox = []\n    for i, l in enumerate(row.label.split(' ')):\n        if (i % 6 == 0) | (i % 6 == 1):\n            continue\n        bbox.append(float(l))\n        if i % 6 == 5:\n            bboxes.append(bbox)\n            bbox = []  \n            \n    return bboxes\n\n# Scale the bounding boxes according to the size of the resized image. \ndef scale_bbox(row, bboxes):\n    # Get scaling factor\n    scale_x = IMG_SIZE/row.dim1\n    scale_y = IMG_SIZE/row.dim0\n    \n    scaled_bboxes = []\n    for bbox in bboxes:\n        x = int(np.round(bbox[0]*scale_x, 4))\n        y = int(np.round(bbox[1]*scale_y, 4))\n        x1 = int(np.round(bbox[2]*(scale_x), 4))\n        y1= int(np.round(bbox[3]*scale_y, 4))\n\n        scaled_bboxes.append([x, y, x1, y1]) # xmin, ymin, xmax, ymax\n        \n    return scaled_bboxes\n\n# Convert the bounding boxes in YOLO format.\ndef get_yolo_format_bbox(img_w, img_h, bboxes):\n    yolo_boxes = []\n    for bbox in bboxes:\n        w = bbox[2] - bbox[0] # xmax - xmin\n        h = bbox[3] - bbox[1] # ymax - ymin\n        xc = bbox[0] + int(np.round(w/2)) # xmin + width/2\n        yc = bbox[1] + int(np.round(h/2)) # ymin + height/2\n        \n        yolo_boxes.append([xc/img_w, yc/img_h, w/img_w, h/img_h]) # x_center y_center width height\n    \n    return yolo_boxes","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Prepare the txt files for bounding box\nfor i in tqdm(range(len(df))):\n    row = df.loc[i]\n    # Get image id\n    img_id = row.id\n    # Get split\n    split = row.split\n    # Get image-level label\n    label = row.image_level\n    \n    if row.split=='train':\n        file_name = f'tmp/covid/labels/train/{row.id}.txt'\n    else:\n        file_name = f'tmp/covid/labels/valid/{row.id}.txt'\n        \n    \n    if label=='opacity':\n        # Get bboxes\n        bboxes = get_bbox(row)\n        # Scale bounding boxes\n        scale_bboxes = scale_bbox(row, bboxes)\n        # Format for YOLOv5\n        yolo_bboxes = get_yolo_format_bbox(IMG_SIZE, IMG_SIZE, scale_bboxes)\n        \n        with open(file_name, 'w') as f:\n            for bbox in yolo_bboxes:\n                bbox = [1]+bbox\n                bbox = [str(i) for i in bbox]\n                bbox = ' '.join(bbox)\n                f.write(bbox)\n                f.write('\\n')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🚅 Train with W&B\n\n","metadata":{}},{"cell_type":"code","source":"%cd tmp/yolov5/","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"```\n--img {IMG_SIZE} \\ # Input image size.\n--batch {BATCH_SIZE} \\ # Batch size\n--epochs {EPOCHS} \\ # Number of epochs\n--data data.yaml \\ # Configuration file\n--weights yolov5s.pt \\ # Model name\n--save_period 1\\ # Save model after interval\n--project kaggle-siim-covid # W&B project name\n```","metadata":{}},{"cell_type":"code","source":"!python train.py --img {IMG_SIZE} \\\n                 --batch {BATCH_SIZE} \\\n                 --epochs {EPOCHS} \\\n                 --data data.yaml \\\n                 --weights yolov5s.pt \\\n                 --save_period 1\\\n                 --project kaggle-siim-covid","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Saved Automatically as Artifact\n\nSince it's a kernel based competition, you can easily download the best model from the W&B Artifacts UI and upload as a Kaggle dataset that you can load in your inference kernel (internel disabled).\n\n### [Path to saved model $\\rightarrow$](https://wandb.ai/ayush-thakur/kaggle-siim-covid/artifacts/model/run_jbt74n7q_model/4c3ca5752dba99bd227e)\n\n![img](https://i.imgur.com/KhRLQvR.png)\n\n> 📍 Download the model with the `best` alias tagged to it. ","metadata":{}},{"cell_type":"markdown","source":"# Inference\n\nYou will probably use a `Submission.ipynb` kernel to run all the predictions. After training a YOLOv5 based object detector -> head to the artifacts page and download the best model -> upload the model as a Kaggle dataset -> Use it with the submission folder. \n\n> 📍 Note that you might have to clone the YOLOv5 repository in a Kaggle dataset as well. \n\nIn this section, I will show you how you can do the inference and modify the predicted bounding box coordinates.","metadata":{}},{"cell_type":"code","source":"TEST_PATH = '/kaggle/input/siim-covid19-resized-to-256px-jpg/test/' # absolute path","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Since I am training the model in this kernel itself, I will not be using the method that I have described above. The best model is saved in the directory `project_name/exp*/weights/best.pt`. In `exp*`, * can be 1, 2, etc. ","metadata":{}},{"cell_type":"code","source":"MODEL_PATH = 'kaggle-siim-covid/exp/weights/best.pt'","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"```\n--weights {MODEL_PATH} \\ # path to the best model.\n--source {TEST_PATH} \\ # absolute path to the test images.\n--img {IMG_SIZE} \\ # Size of image\n--conf 0.281 \\ # Confidence threshold (default is 0.25)\n--iou-thres 0.5 \\ # IOU threshold (default is 0.45)\n--max-det 3 \\ # Number of detections per image (default is 1000) \n--save-txt \\ # Save predicted bounding box coordinates as txt files\n--save-conf # Save the confidence of prediction for each bounding box\n```","metadata":{}},{"cell_type":"code","source":"!python detect.py --weights {MODEL_PATH} \\\n                  --source {TEST_PATH} \\\n                  --img {IMG_SIZE} \\\n                  --conf 0.281 \\\n                  --iou-thres 0.5 \\\n                  --max-det 3 \\\n                  --save-txt \\\n                  --save-conf","metadata":{"scrolled":true,"_kg_hide-output":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### How to find the confidence score?\n\n1. First first the [W&B run page](https://wandb.ai/ayush-thakur/kaggle-siim-covid/runs/jbt74n7q) generated by training the YOLOv5 model. \n\n2. Go to the media panel -> click on the F1_curve.png file to get a rough estimate of the threshold -> go to the Bounding Box Debugger panel and interactively adjust the confidence threshold. \n\n![img](https://i.imgur.com/cCUnTBw.gif)","metadata":{}},{"cell_type":"markdown","source":"> 📍 The bounding box coordinates are saved as text file per image name. It is saved in this directory `runs/detect/exp3/labels`. ","metadata":{}},{"cell_type":"code","source":"PRED_PATH = 'runs/detect/exp3/labels'\n!ls {PRED_PATH}","metadata":{"scrolled":true,"_kg_hide-output":true,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize predicted coordinates.\n%cat runs/detect/exp3/labels/ba91d37ee459.txt","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> 📍 Note: 1 is class id (opacity), the first four float numbers are `x_center`, `y_center`, `width` and `height`. The final float value is `confidence`.","metadata":{}},{"cell_type":"code","source":"prediction_files = os.listdir(PRED_PATH)\nprint('Number of test images predicted as opaque: ', len(prediction_files))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> 📍 Out of 1263 test images, 583 were predicted with `opacity` label and thus we have that many prediction txt files.","metadata":{}},{"cell_type":"markdown","source":"# Submission\n\nIn this section, I will show how you can use YOLOv5 as object detector and prepare `submission.csv` file.","metadata":{}},{"cell_type":"code","source":"# The submisison requires xmin, ymin, xmax, ymax format. \n# YOLOv5 returns x_center, y_center, width, height\ndef correct_bbox_format(bboxes):\n    correct_bboxes = []\n    for b in bboxes:\n        xc, yc = int(np.round(b[0]*IMG_SIZE)), int(np.round(b[1]*IMG_SIZE))\n        w, h = int(np.round(b[2]*IMG_SIZE)), int(np.round(b[3]*IMG_SIZE))\n\n        xmin = xc - int(np.round(w/2))\n        xmax = xc + int(np.round(w/2))\n        ymin = yc - int(np.round(h/2))\n        ymax = yc + int(np.round(h/2))\n        \n        correct_bboxes.append([xmin, xmax, ymin, ymax])\n        \n    return correct_bboxes\n\n# Read the txt file generated by YOLOv5 during inference and extract \n# confidence and bounding box coordinates.\ndef get_conf_bboxes(file_path):\n    confidence = []\n    bboxes = []\n    with open(file_path, 'r') as file:\n        for line in file:\n            preds = line.strip('\\n').split(' ')\n            preds = list(map(float, preds))\n            confidence.append(preds[-1])\n            bboxes.append(preds[1:-1])\n    return confidence, bboxes","metadata":{"_kg_hide-input":false,"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Read the submisison file\nsub_df = pd.read_csv('/kaggle/input/siim-covid19-detection/sample_submission.csv')\nsub_df.tail()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Prediction loop for submission\npredictions = []\n\nfor i in tqdm(range(len(sub_df))):\n    row = sub_df.loc[i]\n    id_name = row.id.split('_')[0]\n    id_level = row.id.split('_')[-1]\n    \n    if id_level == 'study':\n        # do study-level classification\n        predictions.append(\"Negative 1 0 0 1 1\") # dummy prediction\n        \n    elif id_level == 'image':\n        # we can do image-level classification here.\n        # also we can rely on the object detector's classification head.\n        # for this example submisison we will use YOLO's classification head. \n        # since we already ran the inference we know which test images belong to opacity.\n        if f'{id_name}.txt' in prediction_files:\n            # opacity label\n            confidence, bboxes = get_conf_bboxes(f'{PRED_PATH}/{id_name}.txt')\n            bboxes = correct_bbox_format(bboxes)\n            pred_string = ''\n            for j, conf in enumerate(confidence):\n                pred_string += f'opacity {conf} ' + ' '.join(map(str, bboxes[j])) + ' '\n            predictions.append(pred_string[:-1]) \n        else:\n            predictions.append(\"None 1 0 0 1 1\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sub_df['PredictionString'] = predictions\nsub_df.to_csv('submission.csv', index=False)\nsub_df.tail()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# WORK IN PROGRESS\n\nFinal component is model prediction visualization which is an optional debugging tool I would like to share. :)\n\nConsider upvoting if you find the work useful. ","metadata":{}}]}