{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":24800,"databundleVersionId":1831594,"sourceType":"competition"},{"sourceId":1799615,"sourceType":"datasetVersion","datasetId":1069544}],"dockerImageVersionId":30761,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"![](https://www.futuretimeline.net/blog/images/1466-chest-xray-ai-technology.jpg)\n\n<p style='text-align: center;'><span style=\"color: #0D0D0D; font-family: Segoe UI; font-size: 2.6em; font-weight: 300;\">VINBIGDATA - FUSING BBOXES + BUILDING YOLO DATASET</span></p>\n\n\n","metadata":{}},{"cell_type":"markdown","source":"<span style=\"color: #ae1400; Segoe UI; font-size: 2.0em; font-weight: 300;\">Overview</span>\n\n<p style='text-align: justify;'><span style=\"font-family: Trebuchet MS; font-size: 1.2em;\"> This notebook explores the different ways to select or fuse multiple bboxes of the same chest abnormality, annotated by several radiologists</span></p>\n\n<p style='text-align: justify;'><span style=\"font-family: Trebuchet MS; font-size: 1.2em;\"> It also covers the conversion of custom bbox annotations to the YoLo format for frameworks such as YOLO etc</span></p>\n\n\n**A few key intricacies in this competetion includes the way the training data is provided. To state a few:** \n\n- **The abnormalities are labelled by multiple radiologists and there seems to be multiple bounding boxes for some abnormalities.**\n\n- **Another issue being that some dense abnormality/lesion area may contain multiple labels. The radiologists creates boxes and then they may assign many labels to a single bounding box. This was stated by one of the competetion hosts.**\n\nSo the challenge of this competetion includes handling these issues before or after model training.Some ways to handle this would be to use suppression, selection or fusion techniques below which are covered in this notebook:\n\n- **Non-maximum Suppression (NMS)**\n- **Soft-NMS**\n- **Non-maximum Weighted (NMW)**\n- **Weighted Bboxes Fusion (WBF)**\n\n\n\nThese are generally used after model scoring to get to a consensus of a bounding box where the abnormality is based on the confidence scores, weightage of different models (if ensembling is used) etc.\n\nHere, the challenge lies in the fact that we don't have confidence scores or metrics to assign weightage to the multiple annotations by radiologists. So all radiologists are treated equally and the suppression or fusion of bounding boxes has to be done with these factors out of the picture. This seems to show a behaviour by which the bounding boxes which appear alone, seems to get suppressed. This issue is handled in this notebook by separating out single bounding boxes before any technique is applied.\n\n**Finally, please feel free to suggest any other novel methods to address these issues. Hope everyone finds this useful!**\n\n\n\n<br />\n<br />\n\n\n<p style='text-align: justify;'><span style=\"color: #ae1400; Segoe UI; font-size: 1.2em; font-weight: 300;\">Check out the training notebook which uses the Yolo dataset generated here. It explains the Installation, Data preparation, Training and Inference using the Yolov8 model l. It can be adapted to various other models with just 1-2 lines of code change.</span></p>\n\n\n<p style='text-align: justify;'><span style=\"font-family: Trebuchet MS; font-size: 1.1em;\">vincxr-yolov8l⚡📈</span></p>\n\n\nTRAINING NOTEBOOK - https://www.kaggle.com/code/buithanhxuan/vincxr-yolov8l\n\n<br />\n<br />\n\n<p style='text-align: justify;'><span style=\"color: #ae1400; Segoe UI; font-size: 1.2em; font-weight: 300;\">The Yolo dataset with Fused Boxes generated as a part of this notebook is public. Please do check it out.</span></p>\n\n\n<p style='text-align: justify;'><span style=\"font-family: Trebuchet MS; font-size: 1.1em;\">VinBigData - Yolo Dataset with WBF 3x Downscaled</span></p>\n\n\nDATASET LINK - https://www.kaggle.com/datasets/buithanhxuan/vinbigdata-yolo-dataset-with-wbf-3x-downscaled\n\n**Please note that All images with Chest Abnormalities are present in this dataset.**\n\n<br />\n<br />\n\n**Class Mapping in the Annotations File:**\n\n    0 - Aortic enlargement\n    1 - Atelectasis\n    2 - Calcification\n    3 - Cardiomegaly\n    4 - Consolidation\n    5 - ILD\n    6 - Infiltration\n    7 - Lung Opacity\n    8 - Nodule/Mass\n    9 - Other lesion\n    10 - Pleural effusion\n    11 - Pleural thickening\n    12 - Pneumothorax\n    13 - Pulmonary fibrosis\n    14 - No finding\n \n \n### Citations:\n\n**Thanks to [raddar](http://https://www.kaggle.com/raddar) for creating the 3x Downsampled Images which is used here:\nhttps://www.kaggle.com/raddar/vinbigdata-competition-jpg-data-3x-downsampled**\n\n**[Weighted Boxes Fusion: ensembling boxes for object detection models paper](https://arxiv.org/abs/1910.13302)**\n\n\n<br />\n<br />\n\n[![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-something-1abc9c.svg?style=flat-square&logo=kaggle)](https://www.kaggle.com/buithanhxuan)\n<br />\n\n![Upvote!](https://img.shields.io/badge/Upvote-If%20you%20like%20my%20work-07b3c8?style=for-the-badge&logo=kaggle)","metadata":{}},{"cell_type":"code","source":"!pip install ensemble-boxes","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-09-15T17:43:26.336685Z","iopub.execute_input":"2024-09-15T17:43:26.337304Z","iopub.status.idle":"2024-09-15T17:43:45.256916Z","shell.execute_reply.started":"2024-09-15T17:43:26.337246Z","shell.execute_reply":"2024-09-15T17:43:45.255408Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%matplotlib inline\n\nimport os\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom matplotlib import rcParams\nsns.set(rc={\"font.size\":9,\"axes.titlesize\":15,\"axes.labelsize\":9,\n            \"axes.titlepad\":11, \"axes.labelpad\":9, \"legend.fontsize\":7,\n            \"legend.title_fontsize\":7, 'axes.grid' : False})\nimport cv2\nimport json\nimport pandas as pd\nimport glob\nimport os.path as osp\nfrom path import Path\nimport datetime\nimport numpy as np\nfrom tqdm.auto import tqdm\nimport random\nimport shutil\nfrom sklearn.model_selection import train_test_split\n\nfrom ensemble_boxes import *\nimport warnings\nfrom collections import Counter","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:43:45.259757Z","iopub.execute_input":"2024-09-15T17:43:45.260165Z","iopub.status.idle":"2024-09-15T17:43:48.899196Z","shell.execute_reply.started":"2024-09-15T17:43:45.260122Z","shell.execute_reply":"2024-09-15T17:43:48.897711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading the Annotation CSV","metadata":{}},{"cell_type":"code","source":"train_annotations = pd.read_csv(\"../input/vinbigdata-competition-jpg-data-3x-downsampled/train_downsampled.csv\")\ntrain_annotations.head(5)","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:43:48.900995Z","iopub.execute_input":"2024-09-15T17:43:48.901824Z","iopub.status.idle":"2024-09-15T17:43:49.157235Z","shell.execute_reply.started":"2024-09-15T17:43:48.901757Z","shell.execute_reply":"2024-09-15T17:43:49.155875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Selecting Images with Abnormalities","metadata":{}},{"cell_type":"code","source":"train_annotations = train_annotations[train_annotations.class_id!=14]\ntrain_annotations['image_path'] = train_annotations['image_id'].map(lambda x:os.path.join('../input/vinbigdata-competition-jpg-data-3x-downsampled/train/train', str(x)+'.jpg'))\ntrain_annotations.head(5)","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:43:49.161116Z","iopub.execute_input":"2024-09-15T17:43:49.161716Z","iopub.status.idle":"2024-09-15T17:43:49.306362Z","shell.execute_reply.started":"2024-09-15T17:43:49.161661Z","shell.execute_reply":"2024-09-15T17:43:49.305161Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"imagepaths = train_annotations['image_path'].unique()\nprint(\"Number of Images with abnormalities:\",len(imagepaths))\nanno_count = train_annotations.shape[0]\nprint(\"Number of Annotations with abnormalities:\", anno_count)","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:43:49.308156Z","iopub.execute_input":"2024-09-15T17:43:49.308629Z","iopub.status.idle":"2024-09-15T17:43:49.332913Z","shell.execute_reply.started":"2024-09-15T17:43:49.308581Z","shell.execute_reply":"2024-09-15T17:43:49.331655Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Helper Functions","metadata":{}},{"cell_type":"code","source":"def plot_img(img, size=(18, 18), is_rgb=True, title=\"\", cmap='gray'):\n    plt.figure(figsize=size)\n    plt.imshow(img, cmap=cmap)\n    plt.suptitle(title)\n    plt.show()\n\ndef plot_imgs(imgs, cols=2, size=10, is_rgb=True, title=\"\", cmap='gray', img_size=None):\n    rows = len(imgs)//cols + 1\n    fig = plt.figure(figsize=(cols*size, rows*size))\n    for i, img in enumerate(imgs):\n        if img_size is not None:\n            img = cv2.resize(img, img_size)\n        fig.add_subplot(rows, cols, i+1)\n        plt.imshow(img, cmap=cmap)\n    plt.suptitle(title)\n    \ndef draw_bbox(image, box, label, color):   \n    alpha = 0.1\n    alpha_box = 0.4\n    overlay_bbox = image.copy()\n    overlay_text = image.copy()\n    output = image.copy()\n\n    text_width, text_height = cv2.getTextSize(label.upper(), cv2.FONT_HERSHEY_SIMPLEX, 0.6, 1)[0]\n    cv2.rectangle(overlay_bbox, (box[0], box[1]), (box[2], box[3]),\n                color, -1)\n    cv2.addWeighted(overlay_bbox, alpha, output, 1 - alpha, 0, output)\n    cv2.rectangle(overlay_text, (box[0], box[1]-7-text_height), (box[0]+text_width+2, box[1]),\n                (0, 0, 0), -1)\n    cv2.addWeighted(overlay_text, alpha_box, output, 1 - alpha_box, 0, output)\n    cv2.rectangle(output, (box[0], box[1]), (box[2], box[3]),\n                    color, thickness)\n    cv2.putText(output, label.upper(), (box[0], box[1]-5),\n            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)\n    return output","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:43:49.334698Z","iopub.execute_input":"2024-09-15T17:43:49.335344Z","iopub.status.idle":"2024-09-15T17:43:49.354649Z","shell.execute_reply.started":"2024-09-15T17:43:49.335277Z","shell.execute_reply":"2024-09-15T17:43:49.353328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define Classes","metadata":{}},{"cell_type":"code","source":"labels =  [\n            \"__ignore__\",\n            \"Aortic_enlargement\",\n            \"Atelectasis\",\n            \"Calcification\",\n            \"Cardiomegaly\",\n            \"Consolidation\",\n            \"ILD\",\n            \"Infiltration\",\n            \"Lung_Opacity\",\n            \"Nodule/Mass\",\n            \"Other_lesion\",\n            \"Pleural_effusion\",\n            \"Pleural_thickening\",\n            \"Pneumothorax\",\n            \"Pulmonary_fibrosis\"\n            ]\nviz_labels = labels[1:]\n\nlabel2color = [[59, 238, 119], [222, 21, 229], [94, 49, 164], [206, 221, 133], [117, 75, 3],\n                 [210, 224, 119], [211, 176, 166], [63, 7, 197], [102, 65, 77], [194, 134, 175],\n                 [209, 219, 50], [255, 44, 47], [89, 125, 149], [110, 27, 100]]","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:43:49.356378Z","iopub.execute_input":"2024-09-15T17:43:49.357283Z","iopub.status.idle":"2024-09-15T17:43:49.371022Z","shell.execute_reply.started":"2024-09-15T17:43:49.357212Z","shell.execute_reply":"2024-09-15T17:43:49.369671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Visualize Original Bboxes","metadata":{}},{"cell_type":"code","source":"# map label_id to specify color\n#label2color = [[random.randint(0,255) for i in range(3)] for class_id in viz_labels]\nlabel2color = [[59, 238, 119], [222, 21, 229], [94, 49, 164], [206, 221, 133], [117, 75, 3],\n                 [210, 224, 119], [211, 176, 166], [63, 7, 197], [102, 65, 77], [194, 134, 175],\n                 [209, 219, 50], [255, 44, 47], [89, 125, 149], [110, 27, 100]]\n\nthickness = 3\nimgs = []\n\nfor img_id, path in zip(train_annotations['image_id'][:6], train_annotations['image_path'][:6]):\n\n    boxes = train_annotations.loc[train_annotations['image_id'] == img_id,\n                                  ['x_min', 'y_min', 'x_max', 'y_max']].values\n    img_labels = train_annotations.loc[train_annotations['image_id'] == img_id, ['class_id']].values.squeeze()\n    \n    img = cv2.imread(path)\n    \n    for label_id, box in zip(img_labels, boxes):\n        color = label2color[label_id]\n        img = draw_bbox(img, list(np.int_(box)), viz_labels[label_id], color)\n    imgs.append(img)\n\nplot_imgs(imgs, size=9, cmap=None)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:43:49.372946Z","iopub.execute_input":"2024-09-15T17:43:49.373547Z","iopub.status.idle":"2024-09-15T17:43:53.474817Z","shell.execute_reply.started":"2024-09-15T17:43:49.373456Z","shell.execute_reply":"2024-09-15T17:43:53.473159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Exploring Techniques to Combine Bboxes\n## Non-maximum Suppression (NMS)\n### Non-maximum Suppression (NMS): Loại bỏ các bbox chồng chéo dựa trên ngưỡng Intersection over Union (IoU) và điểm tin cậy.","metadata":{}},{"cell_type":"code","source":"iou_thr = 0.5\nskip_box_thr = 0.0001\nviz_images = []\n\nfor i, path in tqdm(enumerate(imagepaths[5:8])):\n    img_array  = cv2.imread(path)\n    image_basename = Path(path).stem\n    print(f\"(\\'{image_basename}\\', \\'{path}\\')\")\n    img_annotations = train_annotations[train_annotations.image_id==image_basename]\n\n    boxes_viz = img_annotations[['x_min', 'y_min', 'x_max', 'y_max']].to_numpy().tolist()\n    labels_viz = img_annotations['class_id'].to_numpy().tolist()\n    \n    print(\"Bboxes before nms:\\n\", boxes_viz)\n    print(\"Labels before nms:\\n\", labels_viz)\n    \n    ## Visualize Original Bboxes\n    img_before = img_array.copy()\n    for box, label in zip(boxes_viz, labels_viz):\n        x_min, y_min, x_max, y_max = (box[0], box[1], box[2], box[3])\n        color = label2color[int(label)]\n        img_before = draw_bbox(img_before, list(np.int_(box)), viz_labels[label], color)\n    viz_images.append(img_before)\n    \n    boxes_list = []\n    scores_list = []\n    labels_list = []\n    weights = []\n    \n    boxes_single = []\n    labels_single = []\n    \n    cls_ids = img_annotations['class_id'].unique().tolist()\n    count_dict = Counter(img_annotations['class_id'].tolist())\n    print(count_dict)\n\n    for cid in cls_ids:       \n        ## Performing Fusing operation only for multiple bboxes with the same label\n        if count_dict[cid]==1:\n            labels_single.append(cid)\n            boxes_single.append(img_annotations[img_annotations.class_id==cid][['x_min', 'y_min', 'x_max', 'y_max']].to_numpy().squeeze().tolist())\n\n        else:\n            cls_list =img_annotations[img_annotations.class_id==cid]['class_id'].tolist()\n            labels_list.append(cls_list)\n            bbox = img_annotations[img_annotations.class_id==cid][['x_min', 'y_min', 'x_max', 'y_max']].to_numpy()\n            ## Normalizing Bbox by Image Width and Height\n            bbox = bbox/(img_array.shape[1], img_array.shape[0], img_array.shape[1], img_array.shape[0])\n            bbox = np.clip(bbox, 0, 1)\n            boxes_list.append(bbox.tolist())\n            scores_list.append(np.ones(len(cls_list)).tolist())\n\n            weights.append(1)\n            \n    # Perform NMS\n    boxes, scores, box_labels = nms(boxes_list, scores_list, labels_list, weights=weights,\n                                    iou_thr=iou_thr)\n    \n    boxes = boxes*(img_array.shape[1], img_array.shape[0], img_array.shape[1], img_array.shape[0])\n    boxes = boxes.round(1).tolist()\n    box_labels = box_labels.astype(int).tolist()\n\n    boxes.extend(boxes_single)\n    box_labels.extend(labels_single)\n    \n    print(\"Bboxes after nms:\\n\", boxes)\n    print(\"Labels after nms:\\n\", box_labels)\n    \n    ## Visualize Bboxes after operation\n    img_after = img_array.copy()\n    for box, label in zip(boxes, box_labels):\n        color = label2color[int(label)]\n        img_after = draw_bbox(img_after, list(np.int_(box)), viz_labels[label], color)\n    viz_images.append(img_after)\n    print()\n        \nplot_imgs(viz_images, cmap=None)\nplt.figtext(0.3, 0.9,\"Original Bboxes\", va=\"top\", ha=\"center\", size=25)\nplt.figtext(0.73, 0.9,\"Non-max Suppression\", va=\"top\", ha=\"center\", size=25)\nplt.savefig('nms.png', bbox_inches='tight')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:43:53.477115Z","iopub.execute_input":"2024-09-15T17:43:53.477673Z","iopub.status.idle":"2024-09-15T17:44:06.979877Z","shell.execute_reply.started":"2024-09-15T17:43:53.477616Z","shell.execute_reply":"2024-09-15T17:44:06.978145Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Soft-NMS\n### Soft-NMS: Tương tự như NMS nhưng giảm dần điểm tin cậy của các bbox chồng chéo thay vì loại bỏ chúng hoàn toàn.","metadata":{}},{"cell_type":"code","source":"iou_thr = 0.5\nskip_box_thr = 0.0001\nviz_images = []\nsigma = 0.1\n\nfor i, path in tqdm(enumerate(imagepaths[5:8])):\n    img_array  = cv2.imread(path)\n    image_basename = Path(path).stem\n    print(f\"(\\'{image_basename}\\', \\'{path}\\')\")\n    img_annotations = train_annotations[train_annotations.image_id==image_basename]\n    \n    boxes_viz = img_annotations[['x_min', 'y_min', 'x_max', 'y_max']].to_numpy().tolist()\n    labels_viz = img_annotations['class_id'].to_numpy().tolist()\n    \n    print(\"Bboxes before soft_nms:\\n\", boxes_viz)\n    print(\"Labels before soft_nms:\\n\", labels_viz)\n    \n    ## Visualize Original Bboxes\n    img_before = img_array.copy()\n    for box, label in zip(boxes_viz, labels_viz):\n        x_min, y_min, x_max, y_max = (box[0], box[1], box[2], box[3])\n        color = label2color[int(label)]\n        img_before = draw_bbox(img_before, list(np.int_(box)), viz_labels[label], color)\n    viz_images.append(img_before)\n    \n    boxes_list = []\n    scores_list = []\n    labels_list = []\n    weights = []\n    \n    boxes_single = []\n    labels_single = []\n    \n    cls_ids = img_annotations['class_id'].unique().tolist()\n    count_dict = Counter(img_annotations['class_id'].tolist())\n    print(count_dict)\n\n    for cid in cls_ids:       \n        ## Performing Fusing operation only for multiple bboxes with the same label\n        if count_dict[cid]==1:\n            labels_single.append(cid)\n            boxes_single.append(img_annotations[img_annotations.class_id==cid][['x_min', 'y_min', 'x_max', 'y_max']].to_numpy().squeeze().tolist())\n\n        else:\n            cls_list =img_annotations[img_annotations.class_id==cid]['class_id'].tolist()\n            labels_list.append(cls_list)\n            bbox = img_annotations[img_annotations.class_id==cid][['x_min', 'y_min', 'x_max', 'y_max']].to_numpy()\n            ## Normalizing Bbox by Image Width and Height\n            bbox = bbox/(img_array.shape[1], img_array.shape[0], img_array.shape[1], img_array.shape[0])\n            bbox = np.clip(bbox, 0, 1)\n            boxes_list.append(bbox.tolist())\n            scores_list.append(np.ones(len(cls_list)).tolist())\n\n            weights.append(1)\n            \n        \n    # Perform Soft-NMS\n    boxes, scores, box_labels = soft_nms(boxes_list, scores_list, labels_list, weights=weights,\n                                         iou_thr=iou_thr, sigma=sigma, thresh=skip_box_thr)\n    \n    \n    boxes = boxes*(img_array.shape[1], img_array.shape[0], img_array.shape[1], img_array.shape[0])\n    boxes = boxes.round(1).tolist()\n    box_labels = box_labels.astype(int).tolist()\n    \n    boxes.extend(boxes_single)\n    box_labels.extend(labels_single)\n    \n    print(\"Bboxes after soft_nms:\\n\", boxes)\n    print(\"Labels after soft_nms:\\n\", box_labels)\n    \n    ## Visualize Bboxes after operation\n    img_after = img_array.copy()\n    for box, label in zip(boxes, box_labels):\n        color = label2color[int(label)]\n        img_after = draw_bbox(img_after, list(np.int_(box)), viz_labels[label], color)\n    viz_images.append(img_after)\n    print()\n        \nplot_imgs(viz_images, cmap=None)\nplt.figtext(0.3, 0.9,\"Original Bboxes\", va=\"top\", ha=\"center\", size=25)\nplt.figtext(0.73, 0.9,\"Soft NMS\", va=\"top\", ha=\"center\", size=25)\nplt.savefig('snms.png', bbox_inches='tight')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:44:06.983994Z","iopub.execute_input":"2024-09-15T17:44:06.98471Z","iopub.status.idle":"2024-09-15T17:44:14.642728Z","shell.execute_reply.started":"2024-09-15T17:44:06.984655Z","shell.execute_reply":"2024-09-15T17:44:14.64116Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Non-maximum Weighted\n### Non-maximum Weighted (NMW): Tương tự như NMS nhưng sử dụng trọng số để ưu tiên các bbox từ các mô hình hoặc nguồn khác nhau.","metadata":{}},{"cell_type":"code","source":"iou_thr = 0.5\nskip_box_thr = 0.0001\nviz_images = []\n\nfor i, path in tqdm(enumerate(imagepaths[5:8])):\n    img_array  = cv2.imread(path)\n    image_basename = Path(path).stem\n    print(f\"(\\'{image_basename}\\', \\'{path}\\')\")\n    img_annotations = train_annotations[train_annotations.image_id==image_basename]\n\n    boxes_viz = img_annotations[['x_min', 'y_min', 'x_max', 'y_max']].to_numpy().tolist()\n    labels_viz = img_annotations['class_id'].to_numpy().tolist()\n    \n    print(\"Bboxes before non_maximum_weighted:\\n\", boxes_viz)\n    print(\"Labels before non_maximum_weighted:\\n\", labels_viz)\n    \n    ## Visualize Original Bboxes\n    img_before = img_array.copy()\n    for box, label in zip(boxes_viz, labels_viz):\n        x_min, y_min, x_max, y_max = (box[0], box[1], box[2], box[3])\n        color = label2color[int(label)]\n        img_before = draw_bbox(img_before, list(np.int_(box)), viz_labels[label], color)\n    viz_images.append(img_before)\n    \n    boxes_list = []\n    scores_list = []\n    labels_list = []\n    weights = []\n    \n    boxes_single = []\n    labels_single = []\n    \n    cls_ids = img_annotations['class_id'].unique().tolist()\n    count_dict = Counter(img_annotations['class_id'].tolist())\n    print(count_dict)\n\n    for cid in cls_ids:       \n        ## Performing Fusing operation only for multiple bboxes with the same label\n        if count_dict[cid]==1:\n            labels_single.append(cid)\n            boxes_single.append(img_annotations[img_annotations.class_id==cid][['x_min', 'y_min', 'x_max', 'y_max']].to_numpy().squeeze().tolist())\n\n        else:\n            cls_list =img_annotations[img_annotations.class_id==cid]['class_id'].tolist()\n            labels_list.append(cls_list)\n            bbox = img_annotations[img_annotations.class_id==cid][['x_min', 'y_min', 'x_max', 'y_max']].to_numpy()\n            ## Normalizing Bbox by Image Width and Height\n            bbox = bbox/(img_array.shape[1], img_array.shape[0], img_array.shape[1], img_array.shape[0])\n            bbox = np.clip(bbox, 0, 1)\n            boxes_list.append(bbox.tolist())\n            scores_list.append(np.ones(len(cls_list)).tolist())\n\n            weights.append(1)\n            \n\n    # Perform Non-maximum Weighted\n    boxes, scores, box_labels = non_maximum_weighted(boxes_list, scores_list, labels_list,\n                                                     weights=weights, iou_thr=iou_thr,skip_box_thr=skip_box_thr)\n    \n    boxes = boxes*(img_array.shape[1], img_array.shape[0], img_array.shape[1], img_array.shape[0])\n    boxes = boxes.round(1).tolist()\n    box_labels = box_labels.astype(int).tolist()\n\n    boxes.extend(boxes_single)\n    box_labels.extend(labels_single)\n    \n    print(\"Bboxes after non_maximum_weighted:\\n\", boxes)\n    print(\"Labels after non_maximum_weighted:\\n\", box_labels)\n    \n    ## Visualize Bboxes after operation\n    img_after = img_array.copy()\n    for box, label in zip(boxes, box_labels):\n        color = label2color[int(label)]\n        img_after = draw_bbox(img_after, list(np.int_(box)), viz_labels[label], color)\n    viz_images.append(img_after)\n    print()\n        \nplot_imgs(viz_images, cmap=None)\nplt.figtext(0.3, 0.9,\"Original Bboxes\", va=\"top\", ha=\"center\", size=25)\nplt.figtext(0.73, 0.9,\"Non-maximum Weighted\", va=\"top\", ha=\"center\", size=25)\nplt.savefig('nmw.png', bbox_inches='tight')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:44:14.644684Z","iopub.execute_input":"2024-09-15T17:44:14.645215Z","iopub.status.idle":"2024-09-15T17:44:22.464361Z","shell.execute_reply.started":"2024-09-15T17:44:14.645141Z","shell.execute_reply":"2024-09-15T17:44:22.46264Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Weighted boxes fusion (WBF)\n### Weighted Boxes Fusion (WBF) là một kỹ thuật hiệu quả để kết hợp các dự đoán từ nhiều mô hình phát hiện đối tượng. Không giống như Non-Maximum Suppression (NMS) loại bỏ các hộp chồng chéo dựa trên điểm tin cậy, WBF sử dụng tất cả các hộp dự đoán để tính toán các hộp trung bình có trọng số.","metadata":{}},{"cell_type":"code","source":"iou_thr = 0.5\nskip_box_thr = 0.0001\nviz_images = []\nsigma = 0.1\n\nfor i, path in tqdm(enumerate(imagepaths[5:8])):\n    img_array  = cv2.imread(path)\n    image_basename = Path(path).stem\n    print(f\"(\\'{image_basename}\\', \\'{path}\\')\")\n    img_annotations = train_annotations[train_annotations.image_id==image_basename]\n\n    boxes_viz = img_annotations[['x_min', 'y_min', 'x_max', 'y_max']].to_numpy().tolist()\n    labels_viz = img_annotations['class_id'].to_numpy().tolist()\n    \n    print(\"Bboxes before WBF:\\n\", boxes_viz)\n    print(\"Labels before WBF:\\n\", labels_viz)\n    \n    ## Visualize Original Bboxes\n    img_before = img_array.copy()\n    for box, label in zip(boxes_viz, labels_viz):\n        x_min, y_min, x_max, y_max = (box[0], box[1], box[2], box[3])\n        color = label2color[int(label)]\n        img_before = draw_bbox(img_before, list(np.int_(box)), viz_labels[label], color)\n    viz_images.append(img_before)\n    \n    boxes_list = []\n    scores_list = []\n    labels_list = []\n    weights = []\n    \n    boxes_single = []\n    labels_single = []\n    \n    cls_ids = img_annotations['class_id'].unique().tolist()\n    count_dict = Counter(img_annotations['class_id'].tolist())\n    print(count_dict)\n\n    for cid in cls_ids:       \n        ## Performing Fusing operation only for multiple bboxes with the same label\n        if count_dict[cid]==1:\n            labels_single.append(cid)\n            boxes_single.append(img_annotations[img_annotations.class_id==cid][['x_min', 'y_min', 'x_max', 'y_max']].to_numpy().squeeze().tolist())\n\n        else:\n            cls_list =img_annotations[img_annotations.class_id==cid]['class_id'].tolist()\n            labels_list.append(cls_list)\n            bbox = img_annotations[img_annotations.class_id==cid][['x_min', 'y_min', 'x_max', 'y_max']].to_numpy()\n            ## Normalizing Bbox by Image Width and Height\n            bbox = bbox/(img_array.shape[1], img_array.shape[0], img_array.shape[1], img_array.shape[0])\n            bbox = np.clip(bbox, 0, 1)\n            boxes_list.append(bbox.tolist())\n            scores_list.append(np.ones(len(cls_list)).tolist())\n\n            weights.append(1)\n            \n\n    # Perform WBF\n    boxes, scores, box_labels= weighted_boxes_fusion(boxes_list, scores_list, labels_list, weights=weights,\n                                                     iou_thr=iou_thr, skip_box_thr=skip_box_thr)\n    \n    \n    boxes = boxes*(img_array.shape[1], img_array.shape[0], img_array.shape[1], img_array.shape[0])\n    boxes = boxes.round(1).tolist()\n    box_labels = box_labels.astype(int).tolist()\n\n    boxes.extend(boxes_single)\n    box_labels.extend(labels_single)\n    \n    print(\"Bboxes after WBF:\\n\", boxes)\n    print(\"Labels after WBF:\\n\", box_labels)\n    \n    ## Visualize Bboxes after operation\n    img_after = img_array.copy()\n    for box, label in zip(boxes, box_labels):\n        color = label2color[int(label)]\n        img_after = draw_bbox(img_after, list(np.int_(box)), viz_labels[label], color)\n    viz_images.append(img_after)\n    print()\n        \nplot_imgs(viz_images, cmap=None)\nplt.figtext(0.3, 0.9,\"Original Bboxes\", va=\"top\", ha=\"center\", size=25)\nplt.figtext(0.73, 0.9,\"WBF\", va=\"top\", ha=\"center\", size=25)\nplt.savefig('wbf.png', bbox_inches='tight')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:44:22.466693Z","iopub.execute_input":"2024-09-15T17:44:22.467216Z","iopub.status.idle":"2024-09-15T17:44:29.73082Z","shell.execute_reply.started":"2024-09-15T17:44:22.467141Z","shell.execute_reply":"2024-09-15T17:44:29.72939Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**### Weighted Boxes Fusion seems to give  gives better results comparing to others in this situation considering that we don't have the confidence/weights of the annotations done by different radiologists","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Building YOLO DATASET\n## Train, Validation & Test Split","metadata":{}},{"cell_type":"markdown","source":"Cú pháp cơ bản của định dạng YOLO\nMỗi file nhãn (label file) của YOLO sẽ có cùng tên với file ảnh tương ứng nhưng có phần mở rộng .txt. File này chứa một hoặc nhiều dòng, mỗi dòng mô tả một bounding box cho một đối tượng trong ảnh. Dưới đây là cú pháp cơ bản cho mỗi dòng trong file nhãn:\n\ncsharp\nCopy code\n<object-class> <x_center> <y_center> <width> <height>\nTrong đó:\n\n<object-class>: Chỉ số của lớp đối tượng (bắt đầu từ 0). Ví dụ: nếu bạn có 3 lớp: \"cat\", \"dog\", và \"person\", thì \"cat\" có thể có giá trị 0, \"dog\" có giá trị 1, và \"person\" có giá trị 2.\n<x_center>: Tọa độ x của tâm bounding box, được chuẩn hóa bằng chiều rộng của ảnh (nằm trong khoảng từ 0 đến 1).\n<y_center>: Tọa độ y của tâm bounding box, được chuẩn hóa bằng chiều cao của ảnh (nằm trong khoảng từ 0 đến 1).\n<width>: Chiều rộng của bounding box, được chuẩn hóa bằng chiều rộng của ảnh (nằm trong khoảng từ 0 đến 1).\n<height>: Chiều cao của bounding box, được chuẩn hóa bằng chiều cao của ảnh (nằm trong khoảng từ 0 đến 1).\nVí dụ về một file YOLO label\nGiả sử bạn có một ảnh image1.jpg với kích thước 1024x1024 pixels và file nhãn tương ứng image1.txt có nội dung sau:\n\nCopy code\n0 0.5 0.5 0.2 0.3\n1 0.7 0.8 0.1 0.1\nGiải thích:\n\nDòng 1: 0 0.5 0.5 0.2 0.3\n\n0: Đây là lớp đối tượng đầu tiên (ví dụ: \"cat\").\n0.5: Tọa độ x của tâm bounding box (50% từ cạnh trái của ảnh).\n0.5: Tọa độ y của tâm bounding box (50% từ cạnh trên của ảnh).\n0.2: Chiều rộng của bounding box (20% của chiều rộng ảnh).\n0.3: Chiều cao của bounding box (30% của chiều cao ảnh).\nDòng 2: 1 0.7 0.8 0.1 0.1\n\n1: Đây là lớp đối tượng thứ hai (ví dụ: \"dog\").\n0.7: Tọa độ x của tâm bounding box (70% từ cạnh trái của ảnh).\n0.8: Tọa độ y của tâm bounding box (80% từ cạnh trên của ảnh).\n0.1: Chiều rộng của bounding box (10% của chiều rộng ảnh).\n0.1: Chiều cao của bounding box (10% của chiều cao ảnh).","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Preprocessing\nIn the preprocessing, I used wbf and merged the boxes with iou=0.4.\nThis makes a single box created by multiple annotators.\nOne thing to keep in mind is that how many of the three annotators judged the disease.\nThere is a difference in how much confidence one can have in a disease box if only one person or two people or three people judges it to be a disease.\nI thought it is necessary to create a model that could account for the difference.\n\nFirst, I created three different training labels using the following methods.\n\nlabels-1: Leave boxes that are considered a disease by one or more annotators.\n\nlabels-2: Leave boxes that are considered a disease by two or more annotators.\n\nlabels-3: Leave boxes that are considered a disease by all three annotators.","metadata":{}},{"cell_type":"markdown","source":"### Tiền xử lý dữ liệu:\n### Sử dụng Weighted Boxes Fusion (WBF) để gộp các bounding box có IoU > 0.4\n### Tạo 3 bộ nhãn huấn luyện khác nhau:\n### a) labels-1: Giữ các box được ít nhất 1 người chú thích là bệnh\n### b) labels-2: Giữ các box được ít nhất 2 người chú thích là bệnh\n### c) labels-3: Giữ các box được cả 3 người chú thích là bệnh","metadata":{}},{"cell_type":"code","source":"# from collections import defaultdict\n# from tqdm import tqdm\n# import numpy as np\n# import shutil\n# import cv2\n# import os\n# import pandas as pd\n# from pathlib import Path\n# from ensemble_boxes import weighted_boxes_fusion\n# import matplotlib.pyplot as plt\n# import csv\n# import seaborn as sns\n\n\n# # Function to create necessary directories for YOLO dataset\n# def create_directories(base_dir, labels_sets, splits):\n#     \"\"\"\n#     Create directories for the YOLO dataset.\n#     \"\"\"\n#     for label_set in labels_sets:\n#         for split in splits:\n#             (base_dir / label_set / split / 'images').mkdir(parents=True, exist_ok=True)\n#             (base_dir / label_set / split / 'labels').mkdir(parents=True, exist_ok=True)\n\n# # Function to split data into train, validation, and test sets\n# def split_data(image_ids, train_ratio=0.75, val_ratio=0.20, seed=42):\n#     \"\"\"\n#     Split data into train, validation, and test sets.\n#     \"\"\"\n#     np.random.seed(seed)\n#     np.random.shuffle(image_ids)\n    \n#     train_size = int(train_ratio * len(image_ids))\n#     val_size = int(val_ratio * len(image_ids))\n    \n#     train_ids = image_ids[:train_size]\n#     val_ids = image_ids[train_size:train_size + val_size]\n#     test_ids = image_ids[train_size + val_size:]\n    \n#     return train_ids, val_ids, test_ids\n\n# # Function to convert bounding boxes to YOLO format\n# def convert_to_yolo_format(box, img_width, img_height):\n#     \"\"\"\n#     Convert bounding box coordinates to YOLO format.\n#     \"\"\"\n#     x_min, y_min, x_max, y_max = box\n#     x_center = (x_min + x_max) / 2\n#     y_center = (y_min + y_max) / 2\n#     width = x_max - x_min\n#     height = y_max - y_min\n#     return [x_center / img_width, y_center / img_height, width / img_width, height / img_height]\n\n# # Function to handle \"No findings\" case (for all label sets except labels_1_disease_only)\n# def no_findings_annotation(base_dir, label_sets, split, img_id):\n#     \"\"\"\n#     Handle cases where no disease is found.\n#     \"\"\"\n#     for label_set in label_sets:\n#         with open(base_dir / label_set / split / 'labels' / f\"{img_id}.txt\", 'w') as f:\n#             f.write(\"14 0.5 0.5 1 1\\n\")\n\n# # Function to save label information to a CSV file\n# def save_labels_to_csv(labels, filename, annotator_count):\n#     \"\"\"\n#     Save label information to a CSV file.\n\n#     Parameters:\n#     - labels: List of tuples containing bounding boxes and labels.\n#     - filename: The name of the CSV file to save.\n#     - annotator_count: A list of annotator counts corresponding to each label.\n#     \"\"\"\n#     with open(filename, mode='w', newline='') as file:\n#         writer = csv.writer(file)\n#         writer.writerow([\"Label\", \"Box_x_min\", \"Box_y_min\", \"Box_x_max\", \"Box_y_max\", \"Annotator_Count\"])\n        \n#         for (box, label), count in zip(labels, annotator_count):\n#             x_min, y_min, x_max, y_max = box\n#             writer.writerow([label, x_min, y_min, x_max, y_max, count])\n\n# # Function to compute IoU between two boxes\n# def compute_iou(box1, box2):\n#     \"\"\"\n#     Compute IoU between two boxes.\n#     Boxes are in the format [x_min, y_min, x_max, y_max] with normalized coordinates (0 to 1).\n#     \"\"\"\n#     x_min1, y_min1, x_max1, y_max1 = box1\n#     x_min2, y_min2, x_max2, y_max2 = box2\n    \n#     # Compute intersection\n#     x_min_inter = max(x_min1, x_min2)\n#     y_min_inter = max(y_min1, y_min2)\n#     x_max_inter = min(x_max1, x_max2)\n#     y_max_inter = min(y_max1, y_max2)\n    \n#     inter_area = max(0, x_max_inter - x_min_inter) * max(0, y_max_inter - y_min_inter)\n    \n#     # Compute union\n#     area1 = (x_max1 - x_min1) * (y_max1 - y_min1)\n#     area2 = (x_max2 - x_min2) * (y_max2 - y_min2)\n#     union_area = area1 + area2 - inter_area\n    \n#     if union_area == 0:\n#         return 0.0\n#     else:\n#         return inter_area / union_area\n\n# # Function to process annotations with WBF\n# def process_annotations_with_wbf(base_dir, label_sets, split, img_id, boxes_list, scores_list, labels_list, img_width, img_height, viz_images, img_before):\n#     \"\"\"\n#     Process annotations with WBF and save labels based on the number of annotators.\n#     \"\"\"\n#     # Apply WBF to combine boxes from multiple annotators\n#     boxes, scores, labels = weighted_boxes_fusion(boxes_list, scores_list, labels_list, weights=None, iou_thr=0.4, skip_box_thr=0.0001)\n\n#     # Initialize a list to store the set of annotators for each fused box\n#     num_fused_boxes = len(boxes)\n#     box_annotators = [set() for _ in range(num_fused_boxes)]\n\n#     # For each fused box, determine which annotators contributed to it\n#     for annotator_idx, (annotator_boxes, annotator_labels) in enumerate(zip(boxes_list, labels_list)):\n#         for fused_box_idx, fused_box in enumerate(boxes):\n#             for box, label in zip(annotator_boxes, annotator_labels):\n#                 # Compute IoU between the fused box and the annotator's box\n#                 iou = compute_iou(fused_box, box)\n#                 if iou >= 0.4:\n#                     box_annotators[fused_box_idx].add(annotator_idx)\n\n#     # Calculate the number of annotators for each fused box\n#     annotator_counts = [len(annotators) for annotators in box_annotators]\n\n#     # Create label sets based on the number of annotators\n#     labels_1 = [(box, label) for box, label, count in zip(boxes, labels, annotator_counts) if count >= 1]\n#     counts_1 = [count for count in annotator_counts if count >= 1]\n#     labels_2 = [(box, label) for box, label, count in zip(boxes, labels, annotator_counts) if count >= 2]\n#     counts_2 = [count for count in annotator_counts if count >= 2]\n#     labels_3 = [(box, label) for box, label, count in zip(boxes, labels, annotator_counts) if count == 3]\n#     counts_3 = [count for count in annotator_counts if count == 3]\n\n#     # Save each set of labels to a CSV file\n#     save_labels_to_csv(labels_1, f\"{base_dir}/{split}_labels_1.csv\", counts_1)\n#     save_labels_to_csv(labels_2, f\"{base_dir}/{split}_labels_2.csv\", counts_2)\n#     save_labels_to_csv(labels_3, f\"{base_dir}/{split}_labels_3.csv\", counts_3)\n\n#     # Save labels in YOLO format for each label set\n#     for label_set, labels_data in zip(label_sets[:3], [labels_1, labels_2, labels_3]):  # Exclude labels_1_disease_only for now\n#         if len(labels_data) > 0:  # Ensure there are labels to write\n#             with open(base_dir / label_set / split / 'labels' / f\"{img_id}.txt\", 'w') as f:\n#                 for box, label in labels_data:\n#                     yolo_box = convert_to_yolo_format(box, img_width, img_height)\n#                     f.write(f\"{int(label)} {' '.join(map(str, yolo_box))}\\n\")\n\n#     # Visualization of images before and after WBF\n#     img_after = img_before.copy()\n\n#     # Draw boxes on the original image (img_before) before WBF\n#     for i, boxes_list_per_annotator in enumerate(boxes_list):\n#         for j, box in enumerate(boxes_list_per_annotator):\n#             box_abs = np.array(box) * [img_width, img_height, img_width, img_height]\n#             label = labels_list[i][j]  # Get the corresponding label for each box\n#             color = label2color[int(label)]  # Use the same color for each label as in img_after\n#             img_before = draw_bbox(img_before, list(map(int, box_abs)), viz_labels[int(label)], color)\n\n#     # Draw boxes on the image after WBF (img_after)\n#     for box, label in zip(boxes, labels):\n#         color = label2color[int(label)]\n#         img_after = draw_bbox(img_after, list(np.int_(box * [img_width, img_height, img_width, img_height])), viz_labels[int(label)], color)\n\n#     # Add img_before and img_after to the visualization\n#     viz_images[split].append((img_id, img_before, img_after))\n\n#     return annotator_counts\n\n\n# # Function to prepare dataset for each label set and split\n# def prepare_dataset(split, ids, annotations, base_dir, label_sets, viz_images):\n#     \"\"\"\n#     Prepare datasets by processing each image and saving to the appropriate directories.\n#     \"\"\"\n#     for img_id in tqdm(ids, desc=f\"Processing {split} set\"):\n#         img_annotations = annotations[annotations.image_id == img_id]\n#         img_path = img_annotations.iloc[0]['image_path']\n#         img = cv2.imread(img_path)\n#         img_height, img_width = img.shape[:2]\n\n#         # Initialize flags to check if image should be added to label sets\n#         add_to_labels_1 = False\n#         add_to_labels_2 = False\n#         add_to_labels_3 = False\n#         add_to_labels_1_disease_only = False  # New flag\n\n#         if 14 in img_annotations['class_id'].values:\n#             # Handle \"No findings\" case\n#             no_findings_annotation(base_dir, label_sets[:3], split, img_id)  # Exclude labels_1_disease_only\n#             add_to_labels_1 = add_to_labels_2 = add_to_labels_3 = True  # No findings should be in all label sets\n#             # Do not add to labels_1_disease_only since it's only for images with disease\n#         else:\n#             boxes_list, scores_list, labels_list = [], [], []\n#             cls_ids = img_annotations['class_id'].unique().tolist()\n\n#             # Group by rad_id to get annotations from each annotator\n#             rad_grouped = img_annotations.groupby('rad_id')\n\n#             for rad_id, group in rad_grouped:\n#                 annotator_boxes = []\n#                 annotator_labels = []\n#                 for _, row in group.iterrows():\n#                     cid = row['class_id']\n#                     if not np.isnan(row['x_min']):\n#                         x_min = row['x_min'] / img_width\n#                         y_min = row['y_min'] / img_height\n#                         x_max = row['x_max'] / img_width\n#                         y_max = row['y_max'] / img_height\n#                         box = [x_min, y_min, x_max, y_max]\n#                         box = np.clip(box, 0, 1)\n#                         annotator_boxes.append(box)\n#                         annotator_labels.append(cid)\n#                 if annotator_boxes:\n#                     boxes_list.append(annotator_boxes)\n#                     scores_list.append([1.0] * len(annotator_boxes))  # Assign score 1.0 to all boxes\n#                     labels_list.append(annotator_labels)\n\n#             if boxes_list:\n#                 # Process the annotations and get annotator counts\n#                 annotator_counts = process_annotations_with_wbf(\n#                     base_dir, label_sets, split, img_id, boxes_list, scores_list, labels_list, img_width, img_height, viz_images, img.copy()\n#                 )\n\n#                 # Determine if image meets criteria for each label set based on annotator counts\n#                 if any(count >= 1 for count in annotator_counts):\n#                     add_to_labels_1 = True\n#                     add_to_labels_1_disease_only = True  # Image with disease added to labels_1_disease_only\n#                 if any(count >= 2 for count in annotator_counts):\n#                     add_to_labels_2 = True\n#                 if any(count == 3 for count in annotator_counts):\n#                     add_to_labels_3 = True\n\n#                 # For labels_1_disease_only, copy labels from labels_1\n#                 if add_to_labels_1_disease_only:\n#                     label_src = base_dir / 'labels_1' / split / 'labels' / f\"{img_id}.txt\"\n#                     label_dst = base_dir / 'labels_1_disease_only' / split / 'labels' / f\"{img_id}.txt\"\n#                     shutil.copy(label_src, label_dst)\n\n#         # Only copy the image if it meets the criteria for the label set\n#         if add_to_labels_1:\n#             shutil.copy(img_path, base_dir / 'labels_1' / split / 'images' / f\"{img_id}.jpg\")\n#         if add_to_labels_2:\n#             shutil.copy(img_path, base_dir / 'labels_2' / split / 'images' / f\"{img_id}.jpg\")\n#         if add_to_labels_3:\n#             shutil.copy(img_path, base_dir / 'labels_3' / split / 'images' / f\"{img_id}.jpg\")\n#         if add_to_labels_1_disease_only:\n#             shutil.copy(img_path, base_dir / 'labels_1_disease_only' / split / 'images' / f\"{img_id}.jpg\")\n#             # Labels already copied above\n\n# # Function to generate dataset split files (train.txt, val.txt, test.txt)\n# def generate_split_files(base_dir, label_sets):\n#     \"\"\"\n#     Generate train, validation, and test split files for YOLO training.\n#     \"\"\"\n#     for label_set in label_sets:\n#         for split in ['train', 'val', 'test']:\n#             with open(base_dir / label_set / f\"{split}.txt\", 'w') as f:\n#                 for img_path in (base_dir / label_set / split / 'images').glob('*.jpg'):\n#                     modified_img_path = str(img_path.absolute()).replace(\"working\", \"input/vinbigdata-yolo-dataset-with-wbf-3labels\")\n#                     f.write(f\"{modified_img_path}\\n\")\n\n# # Function to generate data.yaml files for each label set\n# def generate_data_yaml(base_dir, data_input_dir, label_sets, viz_labels):\n#     \"\"\"\n#     Generate data.yaml configuration files for YOLO training.\n#     \"\"\"\n#     for label_set in label_sets:\n#         # Determine if 'No findings' class is included in this label set\n#         if label_set == 'labels_1_disease_only':\n#             # Exclude 'No findings' class\n#             nc = len(viz_labels)\n#             names = viz_labels\n#         else:\n#             # Include 'No findings' class\n#             nc = len(viz_labels) + 1 # +1 for the \"no finding\" class\n#             names = viz_labels + ['No finding'] \n#         data_yaml = f\"\"\"\n# train: {(data_input_dir / label_set / 'train.txt')}\n# val: {(data_input_dir / label_set / 'val.txt')}\n# test: {(data_input_dir / label_set / 'test.txt')}\n\n# nc: {nc}\n# names: {names}\n# \"\"\"\n#         with open(base_dir / label_set / 'data.yaml', 'w') as f:\n#             f.write(data_yaml)\n\n# # Main execution\n# yolo_dir = Path('./vinbigdata-yolo-dataset-with-wbf-3labels')\n# yolo_dir.mkdir(exist_ok=True)\n\n# # Load annotations\n# all_annotations = pd.read_csv(\"../input/vinbigdata-competition-jpg-data-3x-downsampled/train_downsampled.csv\")\n# all_annotations['image_path'] = all_annotations['image_id'].map(lambda x: os.path.join('../input/vinbigdata-competition-jpg-data-3x-downsampled/train/train', str(x) + '.jpg'))\n\n# # Data splitting\n# image_ids = all_annotations['image_id'].unique()\n# train_ids, val_ids, test_ids = split_data(image_ids)\n\n# # Define label sets including labels_1_disease_only\n# label_sets = ['labels_1', 'labels_2', 'labels_3', 'labels_1_disease_only']\n\n# # Create directories for YOLO dataset\n# create_directories(yolo_dir, label_sets, ['train', 'val', 'test'])\n\n# # Initialize visualization storage\n# viz_images = defaultdict(list)\n\n# # Prepare datasets\n# prepare_dataset('train', train_ids, all_annotations, yolo_dir, label_sets, viz_images)\n# prepare_dataset('val', val_ids, all_annotations, yolo_dir, label_sets, viz_images)\n# prepare_dataset('test', test_ids, all_annotations, yolo_dir, label_sets, viz_images)\n\n# # Generate split files\n# generate_split_files(yolo_dir, label_sets)\n\n# # Generate data.yaml files\n# data_input_dir = Path(\"/kaggle/input/vinbigdata-yolo-dataset-with-wbf-3labels/vinbigdata-yolo-dataset-with-wbf-3labels\")\n# generate_data_yaml(yolo_dir, data_input_dir, label_sets, viz_labels)\n\n# print(\"Dataset preparation complete. Split files and data.yaml files created for all label sets.\")\n\n# # Count and print the number of images in each directory\n# for label_set in label_sets:\n#     for split in ['train', 'val', 'test']:\n#         image_dir = yolo_dir / label_set / split / 'images'\n#         num_images = len(list(image_dir.glob('*.jpg')))\n#         print(f\"Number of images in {label_set}/{split}: {num_images}\")\n\n# # Count and print the number of labels in each directory\n# for label_set in label_sets:\n#     for split in ['train', 'val', 'test']:\n#         image_dir = yolo_dir / label_set / split / 'labels'\n#         num_images = len(list(image_dir.glob('*.txt')))\n#         print(f\"Number of labels in {label_set}/{split}: {num_images}\")\n\n# # Visualize and save images before and after WBF\n# max_images_to_display = 5  # Maximum number of images to display per split\n\n# for split, images in viz_images.items():\n#     for idx, (img_id, img_before, img_after) in enumerate(images):\n#         if idx >= max_images_to_display:  # Stop after displaying max_images_to_display images\n#             break\n\n#         plt.figure(figsize=(20, 10))\n\n#         # Plot image before WBF with initial boxes\n#         plt.subplot(1, 2, 1)\n#         plt.imshow(cv2.cvtColor(img_before, cv2.COLOR_BGR2RGB))\n#         plt.title(f\"{split.capitalize()} Image {idx+1} ({img_id}) Before WBF\")\n#         plt.axis('off')\n\n#         # Plot image after WBF\n#         plt.subplot(1, 2, 2)\n#         plt.imshow(cv2.cvtColor(img_after, cv2.COLOR_BGR2RGB))\n#         plt.title(f\"{split.capitalize()} Image {idx+1} ({img_id}) After WBF\")\n#         plt.axis('off')\n\n#         plt.savefig(f'{split}_image_{img_id}_comparison.png', bbox_inches='tight')\n#         plt.show()\n\n# print(\"Visualization complete.\")","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:44:29.732986Z","iopub.execute_input":"2024-09-15T17:44:29.73348Z","iopub.status.idle":"2024-09-15T17:44:29.757841Z","shell.execute_reply.started":"2024-09-15T17:44:29.733432Z","shell.execute_reply":"2024-09-15T17:44:29.756629Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from collections import defaultdict\nfrom tqdm import tqdm\nimport numpy as np\nimport shutil\nimport cv2\nimport os\nimport pandas as pd\nfrom pathlib import Path\nfrom ensemble_boxes import weighted_boxes_fusion\nimport matplotlib.pyplot as plt\nimport csv\nimport seaborn as sns\n\n# Định nghĩa tên các lớp và màu sắc cho trực quan hóa\nviz_labels = {\n    0: \"Aortic_enlargement\",\n    1: \"Atelectasis\",\n    2: \"Calcification\",\n    3: \"Cardiomegaly\",\n    4: \"Consolidation\",\n    5: \"ILD\",\n    6: \"Infiltration\",\n    7: \"Lung_Opacity\",\n    8: \"Nodule/Mass\",\n    9: \"Other_lesion\",\n    10: \"Pleural_effusion\",\n    11: \"Pleural_thickening\",\n    12: \"Pneumothorax\",\n    13: \"Pulmonary_fibrosis\",\n    14: \"No findings\"\n}\n\nlabel2color = {\n    0: (255, 0, 0),        # Red\n    1: (0, 255, 0),        # Green\n    2: (0, 0, 255),        # Blue\n    3: (255, 255, 0),      # Cyan\n    4: (255, 0, 255),      # Magenta\n    5: (0, 255, 255),      # Yellow\n    6: (128, 0, 0),        # Maroon\n    7: (0, 128, 0),        # Dark Green\n    8: (0, 0, 128),        # Navy\n    9: (128, 128, 0),      # Olive\n    10: (128, 0, 128),     # Purple\n    11: (0, 128, 128),     # Teal\n    12: (128, 128, 128),   # Gray\n    13: (0, 0, 0),         # Black\n    14: (255, 255, 255)    # White for \"No findings\"\n}\n\n# Hàm vẽ bounding box trên hình ảnh\ndef draw_bbox(image, box, label, color, thickness=2):\n    \"\"\"\n    Vẽ bounding box trên hình ảnh.\n    \"\"\"\n    x_min, y_min, x_max, y_max = box\n    cv2.rectangle(image, (x_min, y_min), (x_max, y_max), color, thickness)\n    cv2.putText(image, label, (x_min, y_min - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)\n    return image\n\n# Hàm chia dữ liệu thành train, validation và test\ndef split_data(image_ids, train_ratio=0.75, val_ratio=0.20, seed=42):\n    \"\"\"\n    Chia dữ liệu thành train, validation và test.\n    \"\"\"\n    np.random.seed(seed)\n    np.random.shuffle(image_ids)\n    \n    train_size = int(train_ratio * len(image_ids))\n    val_size = int(val_ratio * len(image_ids))\n    \n    train_ids = image_ids[:train_size]\n    val_ids = image_ids[train_size:train_size + val_size]\n    test_ids = image_ids[train_size + val_size:]\n    \n    return train_ids, val_ids, test_ids\n\n# Hàm tạo các thư mục cần thiết cho bộ dữ liệu YOLO\ndef create_directories(base_dir, labels_sets, splits):\n    \"\"\"\n    Tạo các thư mục cho bộ dữ liệu YOLO.\n    \"\"\"\n    for label_set in labels_sets:\n        for split in splits:\n            (base_dir / label_set / split / 'images').mkdir(parents=True, exist_ok=True)\n            (base_dir / label_set / split / 'labels').mkdir(parents=True, exist_ok=True)\n\n# Hàm chuyển đổi bounding box sang định dạng YOLO\ndef convert_to_yolo_format(box, img_width, img_height):\n    \"\"\"\n    Chuyển đổi tọa độ bounding box sang định dạng YOLO.\n    \"\"\"\n    x_min, y_min, x_max, y_max = box\n    x_center = (x_min + x_max) / 2 / img_width\n    y_center = (y_min + y_max) / 2 / img_height\n    width = (x_max - x_min) / img_width\n    height = (y_max - y_min) / img_height\n    return [x_center, y_center, width, height]\n\n# Hàm xử lý trường hợp \"No findings\" bằng cách để tệp nhãn trống hoặc thêm một bounding box nhỏ\ndef no_findings_annotation(base_dir, label_sets, split, img_id):\n    \"\"\"\n    Xử lý trường hợp không tìm thấy bệnh bằng cách thêm nhãn trống hoặc một bounding box.\n    \"\"\"\n    for label_set in label_sets:\n        with open(base_dir / label_set / split / 'labels' / f\"{img_id}.txt\", 'w') as f:\n            f.write(\"14 0.5 0.5 1 1\\n\")  # Hoặc tạo bounding box nhỏ, hoặc bỏ trống tùy theo yêu cầu\n\n# Hàm lưu thông tin nhãn vào tệp CSV\ndef save_labels_to_csv(labels, filename, annotator_count):\n    \"\"\"\n    Lưu thông tin nhãn vào tệp CSV.\n\n    Parameters:\n    - labels: Danh sách các tuple chứa bounding boxes và labels.\n    - filename: Tên tệp CSV để lưu.\n    - annotator_count: Danh sách số lượng annotator tương ứng với mỗi label.\n    \"\"\"\n    with open(filename, mode='w', newline='') as file:\n        writer = csv.writer(file)\n        writer.writerow([\"Label\", \"Box_x_min\", \"Box_y_min\", \"Box_x_max\", \"Box_y_max\", \"Annotator_Count\"])\n        \n        for (box, label), count in zip(labels, annotator_count):\n            x_min, y_min, x_max, y_max = box\n            writer.writerow([label, x_min, y_min, x_max, y_max, count])\n\n# Hàm tính IoU giữa hai bounding boxes\ndef compute_iou(box1, box2):\n    \"\"\"\n    Tính IoU giữa hai bounding boxes.\n    Boxes ở định dạng [x_min, y_min, x_max, y_max] với tọa độ chuẩn hóa (0 đến 1).\n    \"\"\"\n    x_min1, y_min1, x_max1, y_max1 = box1\n    x_min2, y_min2, x_max2, y_max2 = box2\n    \n    # Tính diện tích giao nhau\n    x_min_inter = max(x_min1, x_min2)\n    y_min_inter = max(y_min1, y_min2)\n    x_max_inter = min(x_max1, x_max2)\n    y_max_inter = min(y_max1, y_max2)\n    \n    inter_area = max(0, x_max_inter - x_min_inter) * max(0, y_max_inter - y_min_inter)\n    \n    # Tính diện tích hợp nhất\n    area1 = (x_max1 - x_min1) * (y_max1 - y_min1)\n    area2 = (x_max2 - x_min2) * (y_max2 - y_min2)\n    union_area = area1 + area2 - inter_area\n    \n    return inter_area / union_area if union_area != 0 else 0\n\n# Hàm xử lý annotations với WBF\ndef process_annotations_with_wbf(base_dir, label_sets, split, img_id, boxes_list, scores_list, labels_list, img_width, img_height, viz_images, img_before):\n    \"\"\"\n    Xử lý annotations với WBF và lưu nhãn dựa trên số lượng annotator.\n    \"\"\"\n    # Lưu ảnh gốc để sử dụng cho việc vẽ bounding box sau WBF\n    img_original = img_before.copy()\n\n    # Áp dụng WBF để kết hợp các bounding boxes từ nhiều annotator\n    boxes, scores, labels = weighted_boxes_fusion(boxes_list, scores_list, labels_list, weights=None, iou_thr=0.4, skip_box_thr=0.0001)\n\n    # Khởi tạo danh sách lưu trữ annotator cho mỗi box đã hợp nhất\n    num_fused_boxes = len(boxes)\n    box_annotators = [set() for _ in range(num_fused_boxes)]\n\n    # Vẽ bounding boxes trước WBF (img_before) từ các annotators\n    for annotator_boxes, annotator_labels in zip(boxes_list, labels_list):\n        for box, label in zip(annotator_boxes, annotator_labels):\n            color = label2color[int(label)]  # Màu tương ứng với class\n            box_abs = np.array(box) * [img_width, img_height, img_width, img_height]  # Chuyển đổi tọa độ về giá trị gốc\n            img_before = draw_bbox(\n                img_before,\n                list(map(int, box_abs)),  # Chuyển đổi về tọa độ nguyên\n                viz_labels[int(label)],   # Label của class\n                color\n            )\n\n    # Đối với mỗi box đã hợp nhất, xác định các annotator đã đóng góp\n    for annotator_idx, (annotator_boxes, annotator_labels) in enumerate(zip(boxes_list, labels_list)):\n        for fused_box_idx, fused_box in enumerate(boxes):\n            for box, label in zip(annotator_boxes, annotator_labels):\n                # Tính IoU giữa box đã hợp nhất và box của annotator\n                iou = compute_iou(fused_box, box)\n                if iou >= 0.4 and label == labels[fused_box_idx]:\n                    box_annotators[fused_box_idx].add(annotator_idx)\n\n    # Tính số lượng annotator cho mỗi box đã hợp nhất\n    annotator_counts = [len(annotators) for annotators in box_annotators]\n\n    # Tạo các set nhãn dựa trên số lượng annotator\n    labels_1 = [(box, label) for box, label, count in zip(boxes, labels, annotator_counts) if count >= 1]\n    counts_1 = [count for count in annotator_counts if count >= 1]\n    labels_2 = [(box, label) for box, label, count in zip(boxes, labels, annotator_counts) if count >= 2]\n    counts_2 = [count for count in annotator_counts if count >= 2]\n    labels_3 = [(box, label) for box, label, count in zip(boxes, labels, annotator_counts) if count == 3]\n    counts_3 = [count for count in annotator_counts if count == 3]\n\n    # Lưu mỗi set nhãn vào tệp CSV\n    save_labels_to_csv(labels_1, f\"{base_dir}/{split}_labels_1.csv\", counts_1)\n    save_labels_to_csv(labels_2, f\"{base_dir}/{split}_labels_2.csv\", counts_2)\n    save_labels_to_csv(labels_3, f\"{base_dir}/{split}_labels_3.csv\", counts_3)\n\n    # Lưu nhãn ở định dạng YOLO cho mỗi label set\n    for label_set, labels_data in zip(label_sets[:3], [labels_1, labels_2, labels_3]):\n        if len(labels_data) > 0:  # Đảm bảo có nhãn để ghi\n            with open(base_dir / label_set / split / 'labels' / f\"{img_id}.txt\", 'a') as f:\n                for box, label in labels_data:\n                    yolo_box = convert_to_yolo_format(box, 1, 1)\n                    f.write(f\"{int(label)} {' '.join(map(str, yolo_box))}\\n\")\n\n    # Trực quan hóa hình ảnh sau khi áp dụng WBF\n    img_after = img_original.copy()\n\n    # Vẽ bounding boxes sau khi áp dụng WBF (img_after)\n    for box, label in zip(boxes, labels):\n        color = label2color[int(label)]\n        img_after = draw_bbox(\n            img_after,\n            [\n                int(box[0] * img_width),\n                int(box[1] * img_height),\n                int(box[2] * img_width),\n                int(box[3] * img_height)\n            ],\n            viz_labels[int(label)],\n            color\n        )\n\n    # Thêm img_before và img_after vào bộ lưu trữ trực quan hóa\n    viz_images[split].append((img_id, img_before, img_after))\n\n    return annotator_counts\n\n# Hàm chuẩn bị bộ dữ liệu cho mỗi label set và split\ndef prepare_dataset(split, ids, annotations, base_dir, label_sets, viz_images):\n    \"\"\"\n    Chuẩn bị bộ dữ liệu bằng cách xử lý mỗi hình ảnh và lưu vào các thư mục phù hợp.\n    \"\"\"\n    for img_id in tqdm(ids, desc=f\"Processing {split} set\"):\n        img_annotations = annotations[annotations.image_id == img_id]\n        img_path = img_annotations.iloc[0]['image_path']\n        img = cv2.imread(img_path)\n        if img is None:\n            print(f\"Warning: Image {img_path} could not be read.\")\n            continue\n        img_height, img_width = img.shape[:2]\n\n        # Khởi tạo các flag để kiểm tra xem hình ảnh có nên được thêm vào các label set không\n        add_to_labels_1 = False\n        add_to_labels_2 = False\n        add_to_labels_3 = False\n        add_to_labels_1_disease_only = False  # Flag mới\n\n        if 14 in img_annotations['class_id'].values:\n            # Xử lý trường hợp \"No findings\" bằng cách để tệp nhãn trống\n            no_findings_annotation(base_dir, label_sets[:3], split, img_id)  # Loại trừ labels_1_disease_only\n            add_to_labels_1 = add_to_labels_2 = add_to_labels_3 = True\n        else:\n            boxes_list, scores_list, labels_list = [], [], []\n            cls_ids = img_annotations['class_id'].unique().tolist()\n\n            # Nhóm theo rad_id để lấy annotations từ mỗi annotator\n            rad_grouped = img_annotations.groupby('rad_id')\n\n            for rad_id, group in rad_grouped:\n                annotator_boxes = []\n                annotator_labels = []\n                for _, row in group.iterrows():\n                    cid = row['class_id']\n                    if not np.isnan(row['x_min']):\n                        x_min = row['x_min'] / img_width\n                        y_min = row['y_min'] / img_height\n                        x_max = row['x_max'] / img_width\n                        y_max = row['y_max'] / img_height\n                        box = [x_min, y_min, x_max, y_max]\n                        box = np.clip(box, 0, 1)\n                        annotator_boxes.append(box)\n                        annotator_labels.append(cid)\n                if annotator_boxes:\n                    boxes_list.append(annotator_boxes)\n                    scores_list.append([1.0] * len(annotator_boxes))  # Gán score 1.0 cho tất cả các box\n                    labels_list.append(annotator_labels)\n\n            if boxes_list:\n                # Xử lý annotations và lấy số lượng annotator\n                annotator_counts = process_annotations_with_wbf(\n                    base_dir, label_sets, split, img_id, boxes_list, scores_list, labels_list, img_width, img_height, viz_images, img.copy()\n                )\n\n                # Xác định xem hình ảnh có đáp ứng yêu cầu cho từng label set dựa trên số lượng annotator\n                if any(count >= 1 for count in annotator_counts):\n                    add_to_labels_1 = True\n                    add_to_labels_1_disease_only = True\n                if any(count >= 2 for count in annotator_counts):\n                    add_to_labels_2 = True\n                if any(count == 3 for count in annotator_counts):\n                    add_to_labels_3 = True\n\n                # Đối với labels_1_disease_only, sao chép labels từ labels_1\n                if add_to_labels_1_disease_only:\n                    label_src = base_dir / 'labels_1' / split / 'labels' / f\"{img_id}.txt\"\n                    label_dst = base_dir / 'labels_1_disease_only' / split / 'labels' / f\"{img_id}.txt\"\n                    shutil.copy(label_src, label_dst)\n\n        # Chỉ sao chép hình ảnh nếu nó đáp ứng yêu cầu cho các label set\n        if add_to_labels_1:\n            shutil.copy(img_path, base_dir / 'labels_1' / split / 'images' / f\"{img_id}.jpg\")\n        if add_to_labels_2:\n            shutil.copy(img_path, base_dir / 'labels_2' / split / 'images' / f\"{img_id}.jpg\")\n        if add_to_labels_3:\n            shutil.copy(img_path, base_dir / 'labels_3' / split / 'images' / f\"{img_id}.jpg\")\n        if add_to_labels_1_disease_only:\n            shutil.copy(img_path, base_dir / 'labels_1_disease_only' / split / 'images' / f\"{img_id}.jpg\")\n\n# Hàm tạo các tệp split (train.txt, val.txt, test.txt)\ndef generate_split_files(base_dir, label_sets):\n    \"\"\"\n    Tạo các tệp split train, validation và test cho huấn luyện YOLO.\n    \"\"\"\n    kaggle_input_path = \"/kaggle/input/vinbigdata-yolo-dataset-with-wbf-3labels/vinbigdata-yolo-dataset-with-wbf-3labels/\"\n    for label_set in label_sets:\n        for split in ['train', 'val', 'test']:\n            with open(base_dir / label_set / f\"{split}.txt\", 'w') as f:\n                for img_path in (base_dir / label_set / split / 'images').glob('*.jpg'):\n                    modified_img_path = kaggle_input_path + str(img_path.relative_to(base_dir))\n                    f.write(f\"{modified_img_path}\\n\")\n\n# Hàm tạo các tệp data.yaml cho từng label set\ndef generate_data_yaml(base_dir, label_sets, viz_labels):\n    \"\"\"\n    Tạo các tệp data.yaml cấu hình cho huấn luyện YOLO.\n    \"\"\"\n    kaggle_input_path = \"/kaggle/input/vinbigdata-yolo-dataset-with-wbf-3labels/vinbigdata-yolo-dataset-with-wbf-3labels/\"\n    for label_set in label_sets:\n        if label_set == 'labels_1_disease_only':\n            nc = len(viz_labels) - 1  # Loại bỏ \"No findings\"\n            names = list(viz_labels.values())[:-1]\n        else:\n            nc = len(viz_labels)\n            names = list(viz_labels.values())\n\n        data_yaml_content = f\"\"\"\ntrain: {kaggle_input_path}{label_set}/train.txt\nval: {kaggle_input_path}{label_set}/val.txt\ntest: {kaggle_input_path}{label_set}/test.txt\n\nnc: {nc}\nnames: {names}\n\"\"\"\n        with open(base_dir / label_set / 'data.yaml', 'w') as f:\n            f.write(data_yaml_content)\n\n# Hàm đọc và hiển thị hình ảnh với bounding box từ các thư mục riêng lẻ\ndef display_images_with_bboxes(base_dir, label_set, split, num_images_to_display=3):\n    \"\"\"\n    Đọc và hiển thị hình ảnh với bounding box từ các thư mục riêng lẻ.\n    \"\"\"\n    image_dir = base_dir / label_set / split / 'images'\n    label_dir = base_dir / label_set / split / 'labels'\n\n    # Lấy danh sách hình ảnh\n    image_files = list(image_dir.glob('*.jpg'))\n\n    # Hiển thị số lượng hình ảnh mong muốn\n    for img_file in image_files[:num_images_to_display]:\n        img = cv2.imread(str(img_file))\n        if img is None:\n            print(f\"Warning: Could not read image {img_file}\")\n            continue\n        \n        img_height, img_width = img.shape[:2]\n        \n        # Đọc file nhãn\n        label_file = label_dir / (img_file.stem + '.txt')\n        if not label_file.exists():\n            print(f\"Warning: Label file {label_file} does not exist\")\n            continue\n        \n        with open(label_file, 'r') as f:\n            for line in f:\n                label_info = line.strip().split()\n                class_id = int(label_info[0])\n                x_center, y_center, width, height = map(float, label_info[1:])\n                \n                # Chuyển đổi lại tọa độ từ YOLO format sang (x_min, y_min, x_max, y_max)\n                x_min = int((x_center - width / 2) * img_width)\n                y_min = int((y_center - height / 2) * img_height)\n                x_max = int((x_center + width / 2) * img_width)\n                y_max = int((y_center + height / 2) * img_height)\n                \n                # Vẽ bounding box lên ảnh\n                color = label2color[class_id] if class_id in label2color else (255, 255, 255)  # White color for unknown class\n                label = viz_labels[class_id] if class_id in viz_labels else \"Unknown\"\n                img = draw_bbox(img, [x_min, y_min, x_max, y_max], label, color)\n        \n        # Hiển thị hình ảnh với bounding box\n        plt.figure(figsize=(10, 10))\n        plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n        plt.title(f\"Image: {img_file.name}\")\n        plt.axis('off')\n        plt.show()\n\n# Thêm hàm vào main để hiển thị 3 hình ảnh với bounding box sau khi lưu\ndef main():\n    yolo_dir = Path('./vinbigdata-yolo-dataset-with-wbf-3labels')\n    yolo_dir.mkdir(exist_ok=True)\n\n    # Tải annotations\n    all_annotations = pd.read_csv(\"../input/vinbigdata-competition-jpg-data-3x-downsampled/train_downsampled.csv\")\n    all_annotations['image_path'] = all_annotations['image_id'].map(lambda x: os.path.join('../input/vinbigdata-competition-jpg-data-3x-downsampled/train/train', str(x) + '.jpg'))\n\n    # Chia dữ liệu\n    image_ids = all_annotations['image_id'].unique()\n    train_ids, val_ids, test_ids = split_data(image_ids)\n\n    # Định nghĩa các label set bao gồm labels_1_disease_only\n    label_sets = ['labels_1', 'labels_2', 'labels_3', 'labels_1_disease_only']\n\n    # Tạo các thư mục cho bộ dữ liệu YOLO\n    create_directories(yolo_dir, label_sets, ['train', 'val', 'test'])\n\n    # Khởi tạo bộ lưu trữ trực quan hóa\n    viz_images = defaultdict(list)\n\n    # Chuẩn bị các bộ dữ liệu\n    prepare_dataset('train', train_ids, all_annotations, yolo_dir, label_sets, viz_images)\n    prepare_dataset('val', val_ids, all_annotations, yolo_dir, label_sets, viz_images)\n    prepare_dataset('test', test_ids, all_annotations, yolo_dir, label_sets, viz_images)\n\n    # Tạo các tệp split\n    generate_split_files(yolo_dir, label_sets)\n\n    # Tạo các tệp data.yaml\n    generate_data_yaml(yolo_dir, label_sets, viz_labels)\n\n    print(\"Dataset preparation complete. Split files and data.yaml files created for all label sets.\")\n\n    # Đếm và in số lượng hình ảnh trong mỗi thư mục\n    for label_set in label_sets:\n        for split in ['train', 'val', 'test']:\n            image_dir = yolo_dir / label_set / split / 'images'\n            num_images = len(list(image_dir.glob('*.jpg')))\n            print(f\"Number of images in {label_set}/{split}: {num_images}\")\n\n    # Đếm và in số lượng nhãn trong mỗi thư mục\n    for label_set in label_sets:\n        for split in ['train', 'val', 'test']:\n            label_dir = yolo_dir / label_set / split / 'labels'\n            num_labels = len(list(label_dir.glob('*.txt')))\n            print(f\"Number of labels in {label_set}/{split}: {num_labels}\")\n\n    # Trực quan hóa và lưu hình ảnh trước và sau khi áp dụng WBF\n    max_images_to_display = 5  # Số lượng hình ảnh tối đa để hiển thị mỗi split\n\n    for split, images in viz_images.items():\n        for idx, (img_id, img_before, img_after) in enumerate(images):\n            if idx >= max_images_to_display:\n                break\n\n            plt.figure(figsize=(20, 10))\n\n            # Hiển thị hình ảnh trước khi áp dụng WBF\n            plt.subplot(1, 2, 1)\n            plt.imshow(cv2.cvtColor(img_before, cv2.COLOR_BGR2RGB))\n            plt.title(f\"{split.capitalize()} Image {idx+1} ({img_id}) Before WBF\")\n            plt.axis('off')\n\n            # Hiển thị hình ảnh sau khi áp dụng WBF\n            plt.subplot(1, 2, 2)\n            plt.imshow(cv2.cvtColor(img_after, cv2.COLOR_BGR2RGB))\n            plt.title(f\"{split.capitalize()} Image {idx+1} ({img_id}) After WBF\")\n            plt.axis('off')\n\n            plt.savefig(f'{split}_image_{img_id}_comparison.png', bbox_inches='tight')\n            plt.show()\n\n    print(\"Visualization complete.\")\n\n    # Kiểm tra sự đồng bộ giữa hình ảnh và tệp nhãn\n    for label_set in label_sets:\n        for split in ['train', 'val', 'test']:\n            image_dir = yolo_dir / label_set / split / 'images'\n            label_dir = yolo_dir / label_set / split / 'labels'\n            image_files = set([p.stem for p in image_dir.glob('*.jpg')])\n            label_files = set([p.stem for p in label_dir.glob('*.txt')])\n            assert image_files == label_files, f\"Mismatch between images and labels in {label_set}/{split}\"\n            print(f\"Image and label synchronization verified for {label_set}/{split}.\")\n\n    # Hiển thị 3 hình ảnh với bounding box từ labels_1, split \"train\"\n    display_images_with_bboxes(yolo_dir, 'labels_1_disease_only', 'train', num_images_to_display=3)\n\nif __name__ == \"__main__\":\n    main()","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:44:29.759992Z","iopub.execute_input":"2024-09-15T17:44:29.760645Z","iopub.status.idle":"2024-09-15T17:56:38.841715Z","shell.execute_reply.started":"2024-09-15T17:44:29.760571Z","shell.execute_reply":"2024-09-15T17:56:38.840198Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"yolo_dir = Path('./vinbigdata-yolo-dataset-with-wbf-3labels')\n    \n# label_sets = ['labels_1', 'labels_2', 'labels_3', 'labels_1_disease_only']\n\ndisplay_images_with_bboxes(yolo_dir, 'labels_1', 'train', num_images_to_display=10)","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:56:38.844344Z","iopub.execute_input":"2024-09-15T17:56:38.844854Z","iopub.status.idle":"2024-09-15T17:56:45.184154Z","shell.execute_reply.started":"2024-09-15T17:56:38.8448Z","shell.execute_reply":"2024-09-15T17:56:45.182936Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Generalized-Mean (GM)-WBF\n### Tổng quan: Một phần mở rộng của WBF thay thế trung bình trọng số bằng hàm trung bình tổng quát. Nó nhằm cung cấp một cơ chế hợp nhất mạnh mẽ hơn.\n### Ưu điểm: Có thể điều chỉnh để nhấn mạnh các phát hiện có độ tin cậy cao hoặc thấp, cung cấp sự linh hoạt hơn.\n### Thuật toán:\n### Tính toán trung bình tổng quát của các bounding box thay vì trung bình trọng số đơn giản.\n### Sử dụng một tham số để điều chỉnh sự nhấn mạnh lên các bounding box khác nhau.\n### Cải tiến so với WBF: Cung cấp chiến lược kết hợp linh hoạt hơn có thể điều chỉnh theo nhu cầu của ứng dụng cụ thể.","metadata":{}},{"cell_type":"markdown","source":"## Thuật toán mới, chạy được nhưng chưa chính xác lắm\n\nfrom collections import defaultdict\nfrom tqdm import tqdm\nimport numpy as np\nimport shutil\nimport cv2\nimport os\nimport pandas as pd\nfrom pathlib import Path\nimport matplotlib.pyplot as plt\nimport csv\n\ndef generalized_mean(weights, boxes, p=1.0):\n    \"\"\"\n    Calculate the generalized mean of the bounding boxes coordinates.\n    \n    Parameters:\n    - weights: List of weights corresponding to the confidence scores of boxes.\n    - boxes: List of bounding boxes [x_min, y_min, x_max, y_max].\n    - p: The power parameter for generalized mean. \n        If p=1, it is equivalent to arithmetic mean (WBF). \n        If p<1, it emphasizes smaller values, useful for down-weighting uncertain boxes.\n    \n    Returns:\n    - A list representing the averaged bounding box [x_min, y_min, x_max, y_max].\n    \"\"\"\n    weights = np.array(weights)\n    boxes = np.array(boxes)\n\n    # Calculate generalized mean for each coordinate\n    gm_x_min = np.power(np.sum(weights * np.power(boxes[:, 0], p)), 1/p) / np.sum(weights)\n    gm_y_min = np.power(np.sum(weights * np.power(boxes[:, 1], p)), 1/p) / np.sum(weights)\n    gm_x_max = np.power(np.sum(weights * np.power(boxes[:, 2], p)), 1/p) / np.sum(weights)\n    gm_y_max = np.power(np.sum(weights * np.power(boxes[:, 3], p)), 1/p) / np.sum(weights)\n\n    return [gm_x_min, gm_y_min, gm_x_max, gm_y_max]\n\ndef gm_weighted_boxes_fusion(boxes_list, scores_list, labels_list, weights=None, iou_thr=0.4, skip_box_thr=0.0001, p=1.0):\n    \"\"\"\n    Generalized-Mean Weighted Boxes Fusion (GM-WBF) to merge bounding boxes from different models.\n    \n    Parameters:\n    - boxes_list: List of bounding boxes for each model.\n    - scores_list: List of scores for each bounding box.\n    - labels_list: List of class labels for each bounding box.\n    - weights: List of weights for each model's boxes.\n    - iou_thr: IoU threshold for considering boxes as overlapping.\n    - skip_box_thr: Threshold for skipping low-scoring boxes.\n    - p: The power parameter for generalized mean. \n\n    Returns:\n    - Merged boxes, scores, and labels.\n    \"\"\"\n    if weights is None:\n        weights = [1] * len(boxes_list)\n\n    # Flatten all boxes, scores, and labels\n    all_boxes = []\n    all_scores = []\n    all_labels = []\n\n    for i in range(len(boxes_list)):\n        for j in range(len(boxes_list[i])):\n            if scores_list[i][j] >= skip_box_thr:\n                all_boxes.append(boxes_list[i][j])\n                all_scores.append(scores_list[i][j] * weights[i])\n                all_labels.append(labels_list[i][j])\n\n    all_boxes = np.array(all_boxes)\n    all_scores = np.array(all_scores)\n    all_labels = np.array(all_labels)\n\n    # Perform the fusion\n    new_boxes = []\n    new_scores = []\n    new_labels = []\n\n    for label in np.unique(all_labels):\n        label_mask = all_labels == label\n        boxes_of_label = all_boxes[label_mask]\n        scores_of_label = all_scores[label_mask]\n\n        while len(scores_of_label) > 0:\n            max_score_idx = np.argmax(scores_of_label)\n            best_box = boxes_of_label[max_score_idx]\n            best_score = scores_of_label[max_score_idx]\n\n            iou = compute_iou(best_box, boxes_of_label)\n            overlapping = iou >= iou_thr\n\n            if len(overlapping) > 0:\n                boxes_to_merge = boxes_of_label[overlapping]\n                scores_to_merge = scores_of_label[overlapping]\n\n                fused_box = generalized_mean(scores_to_merge, boxes_to_merge, p)\n                fused_score = np.mean(scores_to_merge)\n                \n                new_boxes.append(fused_box)\n                new_scores.append(fused_score)\n                new_labels.append(label)\n\n                # Remove merged boxes\n                boxes_of_label = boxes_of_label[~overlapping]\n                scores_of_label = scores_of_label[~overlapping]\n\n            else:\n                break\n\n    return np.array(new_boxes), np.array(new_scores), np.array(new_labels)\n\ndef compute_iou(box, boxes):\n    \"\"\"\n    Compute the IoU between a box and an array of boxes.\n    \n    Parameters:\n    - box: The reference bounding box [x_min, y_min, x_max, y_max].\n    - boxes: The array of boxes to compare against [N, 4].\n    \n    Returns:\n    - Array of IoU values.\n    \"\"\"\n    x_min = np.maximum(box[0], boxes[:, 0])\n    y_min = np.maximum(box[1], boxes[:, 1])\n    x_max = np.minimum(box[2], boxes[:, 2])\n    y_max = np.minimum(box[3], boxes[:, 3])\n\n    intersection = np.maximum(x_max - x_min, 0) * np.maximum(y_max - y_min, 0)\n    area_box = (box[2] - box[0]) * (box[3] - box[1])\n    area_boxes = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])\n    \n    union = area_box + area_boxes - intersection\n    iou = intersection / union\n    return iou\n\n# Function to create necessary directories for YOLO dataset\ndef create_directories(base_dir, labels_sets, splits):\n    for label_set in labels_sets:\n        for split in splits:\n            (base_dir / label_set / split / 'images').mkdir(parents=True, exist_ok=True)\n            (base_dir / label_set / split / 'labels').mkdir(parents=True, exist_ok=True)\n\n# Function to split data into train, validation, and test sets\ndef split_data(image_ids, train_ratio=0.75, val_ratio=0.20, seed=42):\n    np.random.seed(seed)\n    np.random.shuffle(image_ids)\n    \n    train_size = int(train_ratio * len(image_ids))\n    val_size = int(val_ratio * len(image_ids))\n    \n    train_ids = image_ids[:train_size]\n    val_ids = image_ids[train_size:train_size + val_size]\n    test_ids = image_ids[train_size + val_size:]\n    \n    return train_ids, val_ids, test_ids\n\n# Function to convert bounding boxes to YOLO format\ndef convert_to_yolo_format(box, img_width, img_height):\n    x_min, y_min, x_max, y_max = box\n    x_center = (x_min + x_max) / 2\n    y_center = (y_min + y_max) / 2\n    width = x_max - x_min\n    height = y_max - y_min\n    return [x_center / img_width, y_center / img_height, width / img_width, height / img_height]\n\n# Function to handle \"No findings\" case\ndef no_findings_annotation(base_dir, label_sets, split, img_id):\n    for label_set in label_sets:\n        with open(base_dir / label_set / split / 'labels' / f\"{img_id}.txt\", 'w') as f:\n            f.write(\"14 0.5 0.5 1 1\\n\")\n\n# Function to save labels to CSV\ndef save_labels_to_csv(labels, filename, annotator_count):\n    \"\"\"\n    Save label information to a CSV file.\n\n    Parameters:\n    - labels: List of tuples containing bounding boxes and labels.\n    - filename: The name of the CSV file to save.\n    - annotator_count: A dictionary mapping each label to the count of annotators who marked it.\n    \"\"\"\n    with open(filename, mode='w', newline='') as file:\n        writer = csv.writer(file)\n        # Write header\n        writer.writerow([\"Label\", \"Box_x_min\", \"Box_y_min\", \"Box_x_max\", \"Box_y_max\", \"Annotator_Count\"])\n        \n        for box, label in labels:\n            x_min, y_min, x_max, y_max = box\n            count = annotator_count[label] if label in annotator_count else 0\n            \n            writer.writerow([label, x_min, y_min, x_max, y_max, count])\n\n# Function to process annotations with Generalized-Mean Weighted Boxes Fusion (GM-WBF)\ndef process_annotations_with_gm_wbf(base_dir, label_sets, split, img_id, boxes_list, scores_list, labels_list, img_width, img_height, viz_images, img_before):\n    # Apply GM-WBF to combine boxes from multiple annotators\n    boxes, scores, labels = gm_weighted_boxes_fusion(boxes_list, scores_list, labels_list, weights=None, iou_thr=0.4, skip_box_thr=0.0001)\n\n    # Calculate number of annotators for each label\n    annotator_count = defaultdict(int)\n    unique_annotators = defaultdict(set)\n    \n    for i, rad_labels in enumerate(labels_list):\n        for label in rad_labels:\n            unique_annotators[label].add(i)\n\n    for label, annotators in unique_annotators.items():\n        annotator_count[label] = len(annotators)\n\n#     # Debug: Print annotator counts for each label\n#     print(\"Annotator count per label:\")\n#     for label, count in annotator_count.items():\n#         print(f\"Label {label}: {count} annotators\")\n\n    # Create label sets based on the number of annotators\n    labels_1 = [(box, label) for box, label in zip(boxes, labels)]\n    labels_2 = [(box, label) for box, label in zip(boxes, labels) if annotator_count[label] >= 2]\n    labels_3 = [(box, label) for box, label in zip(boxes, labels) if annotator_count[label] == 3]\n\n#     # Debug: Print the number of labels in each set\n#     print(f\"Number of labels in labels_1: {len(labels_1)}\")\n#     print(f\"Number of labels in labels_2: {len(labels_2)}\")\n#     print(f\"Number of labels in labels_3: {len(labels_3)}\")\n\n    # Save each set of labels to a CSV file\n    save_labels_to_csv(labels_1, f\"{base_dir}/{split}_labels_1.csv\", annotator_count)\n    save_labels_to_csv(labels_2, f\"{base_dir}/{split}_labels_2.csv\", annotator_count)\n    save_labels_to_csv(labels_3, f\"{base_dir}/{split}_labels_3.csv\", annotator_count)\n\n    # Save labels in YOLO format for each label set\n    for label_set, labels_data in zip(label_sets, [labels_1, labels_2, labels_3]):\n        if len(labels_data) > 0:  # Ensure there are labels to write\n            with open(base_dir / label_set / split / 'labels' / f\"{img_id}.txt\", 'w') as f:\n                for box, label in labels_data:\n                    yolo_box = convert_to_yolo_format(box, img_width, img_height)\n                    f.write(f\"{int(label)} {' '.join(map(str, yolo_box))}\\n\")\n\n    # Visualization of images before and after GM-WBF\n    img_after = img_before.copy()\n    \n    # Draw boxes on the original image (img_before) before GM-WBF\n    for i, boxes_list_per_annotator in enumerate(boxes_list):\n        for j, box in enumerate(boxes_list_per_annotator):\n            box = np.array(box) * [img_width, img_height, img_width, img_height]\n            label = labels_list[i][j]  # Get the corresponding label for each box\n            color = label2color[int(label)]  # Use the same color for each label as in img_after\n            img_before = draw_bbox(img_before, list(map(int, box)), viz_labels[int(label)], color)\n\n    # Draw boxes on the image after GM-WBF (img_after)\n    for box, label in zip(boxes, labels):\n        color = label2color[int(label)]\n        img_after = draw_bbox(img_after, list(np.int_(box * [img_width, img_height, img_width, img_height])), viz_labels[int(label)], color)\n\n    # Add img_before and img_after to the visualization list for later plotting\n    viz_images[split].append((img_id, img_before, img_after))\n\n# Function to prepare dataset for each label set and split\ndef prepare_dataset(split, ids, annotations, base_dir, label_sets, viz_images):\n    for img_id in tqdm(ids, desc=f\"Processing {split} set\"):\n        img_annotations = annotations[annotations.image_id == img_id]\n        img_path = img_annotations.iloc[0]['image_path']\n        img = cv2.imread(img_path)\n        img_height, img_width = img.shape[:2]\n\n        # Initialize flags to check if image should be added to label sets\n        add_to_labels_1 = False\n        add_to_labels_2 = False\n        add_to_labels_3 = False\n        \n        if 14 in img_annotations['class_id'].values:\n            # Handle \"No findings\" case\n            no_findings_annotation(base_dir, label_sets, split, img_id)\n            add_to_labels_1 = add_to_labels_2 = add_to_labels_3 = True  # No findings should be in all label sets\n        else:\n            boxes_list, scores_list, labels_list = [], [], []\n            cls_ids = img_annotations['class_id'].unique().tolist()\n            \n            # Group by rad_id to count the number of annotators\n            rad_grouped = img_annotations.groupby('rad_id')\n            annotator_boxes = defaultdict(list)\n            annotator_labels = defaultdict(list)\n            \n            for rad_id, group in rad_grouped:\n                for cid in cls_ids:\n                    cls_annotations = group[group.class_id == cid]\n                    cls_boxes = cls_annotations[['x_min', 'y_min', 'x_max', 'y_max']].dropna().values\n                    cls_boxes = cls_boxes / [img_width, img_height, img_width, img_height]\n                    cls_boxes = np.clip(cls_boxes, 0, 1)\n                    \n                    if len(cls_boxes) > 0:\n                        annotator_boxes[rad_id].append(cls_boxes.tolist())\n                        annotator_labels[rad_id].append([cid] * len(cls_boxes))\n            \n            # Aggregate boxes and labels from all annotators\n            for rad_id in annotator_boxes:\n                boxes_list.extend(annotator_boxes[rad_id])\n                scores_list.extend([np.ones(len(b)) for b in annotator_boxes[rad_id]])\n                labels_list.extend(annotator_labels[rad_id])\n            \n            if boxes_list:\n                # Perform GM-WBF and check conditions to add images to each label set\n                boxes, scores, labels = gm_weighted_boxes_fusion(\n                    boxes_list, scores_list, labels_list, weights=None, iou_thr=0.4, skip_box_thr=0.0001)\n\n                annotator_count = defaultdict(int)\n                unique_annotators = defaultdict(set)\n\n                for i, rad_labels in enumerate(labels_list):\n                    for label in rad_labels:\n                        unique_annotators[label].add(i)\n\n                for label, annotators in unique_annotators.items():\n                    annotator_count[label] = len(annotators)\n                \n                # Determine if image meets criteria for each label set\n                for label in labels:\n                    if annotator_count[label] >= 1:\n                        add_to_labels_1 = True\n                    if annotator_count[label] >= 2:\n                        add_to_labels_2 = True\n                    if annotator_count[label] == 3:\n                        add_to_labels_3 = True\n\n                # Process the annotations for visualization and storage\n                process_annotations_with_gm_wbf(base_dir, label_sets, split, img_id, \n                                                boxes_list, scores_list, labels_list, \n                                                img_width, img_height, viz_images, img.copy())\n        \n        # Only copy the image if it meets the criteria for the label set\n        if add_to_labels_1:\n            shutil.copy(img_path, base_dir / 'labels_1' / split / 'images' / f\"{img_id}.jpg\")\n        if add_to_labels_2:\n            shutil.copy(img_path, base_dir / 'labels_2' / split / 'images' / f\"{img_id}.jpg\")\n        if add_to_labels_3:\n            shutil.copy(img_path, base_dir / 'labels_3' / split / 'images' / f\"{img_id}.jpg\")\n\n# Function to generate dataset split files (train.txt, val.txt, test.txt)\ndef generate_split_files(base_dir, label_sets):\n    for label_set in label_sets:\n        for split in ['train', 'val', 'test']:\n            with open(base_dir / label_set / f\"{split}.txt\", 'w') as f:\n                for img_path in (base_dir / label_set / split / 'images').glob('*.jpg'):\n                    modified_img_path = str(img_path.absolute()).replace(\"working\", \"input/vinbigdata-yolo-dataset-with-wbf-3x-downscaled\")\n                    f.write(f\"{modified_img_path}\\n\")\n\n# Function to generate data.yaml files for each label set\ndef generate_data_yaml(base_dir, data_input_dir, label_sets, viz_labels):\n    for label_set in label_sets:\n        data_yaml = f\"\"\"\n        train: {(data_input_dir / label_set / 'train.txt')}\n        val: {(data_input_dir / label_set / 'val.txt')}\n        test: {(data_input_dir / label_set / 'test.txt')}\n    \n        nc: {len(viz_labels) + 1}  # +1 for the \"no finding\" class\n        names: {viz_labels + ['No finding']}\n        \"\"\"\n        with open(base_dir / label_set / 'data.yaml', 'w') as f:\n            f.write(data_yaml)\n\n# Main execution\nyolo_dir = Path('./vinbigdata-yolo-dataset-with-gm-wbf-3x-downscaled')\nyolo_dir.mkdir(exist_ok=True)\n\n# Load annotations\nall_annotations = pd.read_csv(\"../input/vinbigdata-competition-jpg-data-3x-downsampled/train_downsampled.csv\")\nall_annotations['image_path'] = all_annotations['image_id'].map(lambda x: os.path.join('../input/vinbigdata-competition-jpg-data-3x-downsampled/train/train', str(x) + '.jpg'))\n\n# Data splitting\nimage_ids = all_annotations['image_id'].unique()\ntrain_ids, val_ids, test_ids = split_data(image_ids)\n\n# Create directories for YOLO dataset\ncreate_directories(yolo_dir, ['labels_1', 'labels_2', 'labels_3'], ['train', 'val', 'test'])\n\n# Initialize visualization storage\nviz_images = defaultdict(list)\n\n# Prepare datasets\nprepare_dataset('train', train_ids, all_annotations, yolo_dir, ['labels_1', 'labels_2', 'labels_3'], viz_images)\nprepare_dataset('val', val_ids, all_annotations, yolo_dir, ['labels_1', 'labels_2', 'labels_3'], viz_images)\nprepare_dataset('test', test_ids, all_annotations, yolo_dir, ['labels_1', 'labels_2', 'labels_3'], viz_images)\n\n# Generate split files\ngenerate_split_files(yolo_dir, ['labels_1', 'labels_2', 'labels_3'])\n\n# Generate data.yaml files\ndata_input_dir = Path(\"/kaggle/input/vinbigdata-yolo-dataset-with-gm-wbf-3x-downscaled/vinbigdata-yolo-dataset-with-gm-wbf-3x-downscaled\")\ngenerate_data_yaml(yolo_dir, data_input_dir, ['labels_1', 'labels_2', 'labels_3'], viz_labels)\n\nprint(\"Dataset preparation complete. Split files and data.yaml files created for all label sets.\")\n\n# Count and print the number of images in each directory\nfor label_set in ['labels_1', 'labels_2', 'labels_3']:\n    for split in ['train', 'val', 'test']:\n        image_dir = yolo_dir / label_set / split / 'images'\n        num_images = len(list(image_dir.glob('*.jpg')))\n        print(f\"Number of images in {label_set}/{split}: {num_images}\")\n\n# Visualize and save images before and after GM-WBF\nmax_images_to_display = 5  # Maximum number of images to display per split\n\nfor split, images in viz_images.items():\n    for idx, (img_id, img_before, img_after) in enumerate(images):\n        if idx >= max_images_to_display:  # Stop after displaying max_images_to_display images\n            break\n\n        plt.figure(figsize=(20, 10))\n\n        # Plot image before GM-WBF with initial boxes\n        plt.subplot(1, 2, 1)\n        plt.imshow(cv2.cvtColor(img_before, cv2.COLOR_BGR2RGB))\n        plt.title(f\"{split.capitalize()} Image {idx+1} ({img_id}) Before GM-WBF\")\n        plt.axis('off')\n\n        # Plot image after GM-WBF\n        plt.subplot(1, 2, 2)\n        plt.imshow(cv2.cvtColor(img_after, cv2.COLOR_BGR2RGB))\n        plt.title(f\"{split.capitalize()} Image {idx+1} ({img_id}) After GM-WBF\")\n        plt.axis('off')\n\n        plt.savefig(f'{split}_image_{img_id}_gm_wbf_comparison.png', bbox_inches='tight')\n        plt.show()\n\nprint(\"Visualization complete.\")","metadata":{"execution":{"iopub.status.busy":"2024-09-05T01:04:24.244503Z","iopub.execute_input":"2024-09-05T01:04:24.24502Z","iopub.status.idle":"2024-09-05T01:17:19.344359Z","shell.execute_reply.started":"2024-09-05T01:04:24.244949Z","shell.execute_reply":"2024-09-05T01:17:19.342883Z"}}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<object-class> <x_center> <y_center> <width> <height>\n","metadata":{}},{"cell_type":"code","source":"warnings.filterwarnings(\"ignore\", category=UserWarning)","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:56:45.18571Z","iopub.execute_input":"2024-09-15T17:56:45.186078Z","iopub.status.idle":"2024-09-15T17:56:45.191824Z","shell.execute_reply.started":"2024-09-15T17:56:45.186035Z","shell.execute_reply":"2024-09-15T17:56:45.190671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!find ./vinbigdata-yolo-dataset-with-wbf-3labels -type f | wc -l","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:56:45.19335Z","iopub.execute_input":"2024-09-15T17:56:45.193732Z","iopub.status.idle":"2024-09-15T17:56:47.14531Z","shell.execute_reply.started":"2024-09-15T17:56:45.193688Z","shell.execute_reply":"2024-09-15T17:56:47.143797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Packaging Dataset into Zip for Upload","metadata":{}},{"cell_type":"code","source":"%%bash\ncd ./vinbigdata-yolo-dataset-with-wbf-3labels\nzip -rq ../vinbigdata-yolo-dataset-with-wbf-3labels.zip ./*\necho \"Zipping completed successfully.\"","metadata":{"execution":{"iopub.status.busy":"2024-09-15T17:56:47.147243Z","iopub.execute_input":"2024-09-15T17:56:47.147702Z","iopub.status.idle":"2024-09-15T18:08:20.529628Z","shell.execute_reply.started":"2024-09-15T17:56:47.147637Z","shell.execute_reply":"2024-09-15T18:08:20.527827Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%bash\nrm -r ./vinbigdata-yolo-dataset-with-wbf-3labels\nls -ahl","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-09-15T18:08:20.532135Z","iopub.execute_input":"2024-09-15T18:08:20.53277Z","iopub.status.idle":"2024-09-15T18:08:24.497275Z","shell.execute_reply.started":"2024-09-15T18:08:20.532702Z","shell.execute_reply":"2024-09-15T18:08:24.495643Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<p style='text-align: center;'><span style=\"color: #0D0D0D; font-family: Segoe UI; font-size: 2.0em; font-weight: 300;\">THANK YOU! PLEASE UPVOTE</span></p>\n\n<p style='text-align: center;'><span style=\"color: #0D0D0D; font-family: Segoe UI; font-size: 2.5em; font-weight: 300;\">HOPE IT WAS USEFUL</span></p>\n\n<p style='text-align: center;'><span style=\"color: #009BAE; font-family: Segoe UI; font-size: 1.2em; font-weight: 300;\">Check out the Train Notebook below</span></p>\n\n\n\n<p style='text-align: center;'><span style=\"font-family: Trebuchet MS; font-size: 1.3em;\"><a href=\"https://www.kaggle.com/code/buithanhxuan/vincxr-yolov8l\" target=\"_top\">vincxr-yolov8l⚡📈</a></span></p>\n\n<p style='text-align: center;'></p>\n","metadata":{}}]}