{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":10338,"databundleVersionId":862042,"sourceType":"competition"},{"sourceId":7604865,"sourceType":"datasetVersion","datasetId":4427334}],"dockerImageVersionId":30646,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install ultralytics\n# !pip install -e git+https://github.com/troobadure/ultralytics@shibarinu\\#egg=ultralytics\n# !pip install -e git+https://github.com/ultralytics/ultralytics\\#egg=ultralytics --exists-action s","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-02-11T22:37:15.270014Z","iopub.execute_input":"2024-02-11T22:37:15.270482Z","iopub.status.idle":"2024-02-11T22:37:30.504779Z","shell.execute_reply.started":"2024-02-11T22:37:15.270448Z","shell.execute_reply":"2024-02-11T22:37:30.503394Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport random\nimport shutil\n\nimport cv2\nimport pydicom\nfrom PIL import Image\n\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom tqdm import tqdm\n\nfrom ultralytics import YOLO\nimport wandb\n\nnp.random.seed(1337)","metadata":{"execution":{"iopub.status.busy":"2024-02-11T22:37:30.507274Z","iopub.execute_input":"2024-02-11T22:37:30.507713Z","iopub.status.idle":"2024-02-11T22:37:38.683264Z","shell.execute_reply.started":"2024-02-11T22:37:30.507674Z","shell.execute_reply":"2024-02-11T22:37:38.681901Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"try:\n    shutil.rmtree('/kaggle/working/')\nexcept:\n    pass","metadata":{"execution":{"iopub.status.busy":"2024-02-11T22:37:38.684808Z","iopub.execute_input":"2024-02-11T22:37:38.685191Z","iopub.status.idle":"2024-02-11T22:37:40.792100Z","shell.execute_reply.started":"2024-02-11T22:37:38.685158Z","shell.execute_reply":"2024-02-11T22:37:40.790254Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"CSV_FILE = '../input/rsna-pneumonia-detection-challenge/stage_2_train_labels.csv'\nTRAIN_SRC_DIR = '../input/rsna-pneumonia-detection-challenge/stage_2_train_images/'\nTEST_SRC_DIR = '../input/rsna-pneumonia-detection-challenge/stage_2_test_images/'\nDATASET_DIR = './dataset/'\nTEST_IMG_DIR = 'test_images/'\n\nos.mkdir(DATASET_DIR)\nos.mkdir(DATASET_DIR + 'images/')\nos.mkdir(DATASET_DIR + 'images/train/')\nos.mkdir(DATASET_DIR + 'images/val/')\nos.mkdir(DATASET_DIR + 'images/test/')\nos.mkdir(DATASET_DIR + 'labels/')\nos.mkdir(DATASET_DIR + 'labels/train/')\nos.mkdir(DATASET_DIR + 'labels/val/')\nos.mkdir(DATASET_DIR + 'labels/test/')\nos.mkdir(TEST_IMG_DIR)","metadata":{"execution":{"iopub.status.busy":"2024-02-11T22:37:40.797833Z","iopub.execute_input":"2024-02-11T22:37:40.798285Z","iopub.status.idle":"2024-02-11T22:37:40.810560Z","shell.execute_reply.started":"2024-02-11T22:37:40.798249Z","shell.execute_reply":"2024-02-11T22:37:40.808334Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prepare images and labels for training","metadata":{}},{"cell_type":"code","source":"annotations = pd.read_csv(CSV_FILE)\nprint(annotations.info())\nannotations.head()","metadata":{"execution":{"iopub.status.busy":"2024-02-11T09:06:52.328638Z","iopub.execute_input":"2024-02-11T09:06:52.328904Z","iopub.status.idle":"2024-02-11T09:06:52.422701Z","shell.execute_reply.started":"2024-02-11T09:06:52.328880Z","shell.execute_reply":"2024-02-11T09:06:52.421805Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"positive_annotations = annotations[annotations.Target == 1]\nnegative_annotations = annotations[annotations.Target == 0]\n\nprint(positive_annotations['patientId'].drop_duplicates().shape[0])\nprint(negative_annotations['patientId'].drop_duplicates().shape[0])\nprint(negative_annotations['patientId'].shape[0])\n\nnegative_sample = negative_annotations.sample(600)\nnegative_sample['patientId'].shape[0]\n\nannotations = pd.concat([positive_annotations, negative_sample])\nprint(annotations.shape)\nannotations.head()","metadata":{"execution":{"iopub.status.busy":"2024-02-11T09:06:52.423846Z","iopub.execute_input":"2024-02-11T09:06:52.424135Z","iopub.status.idle":"2024-02-11T09:06:52.452212Z","shell.execute_reply.started":"2024-02-11T09:06:52.424111Z","shell.execute_reply":"2024-02-11T09:06:52.451317Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"patient_id_series = annotations.patientId.drop_duplicates()\nprint('Number of images:', patient_id_series.size)\n\ntrain_series, val_series = train_test_split(patient_id_series, test_size=0.1, random_state=42)\nprint('Train set number:', len(train_series))\nprint('Validation set number:', len(val_series))","metadata":{"execution":{"iopub.status.busy":"2024-02-11T09:06:52.453241Z","iopub.execute_input":"2024-02-11T09:06:52.453520Z","iopub.status.idle":"2024-02-11T09:06:52.462324Z","shell.execute_reply.started":"2024-02-11T09:06:52.453495Z","shell.execute_reply":"2024-02-11T09:06:52.461336Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for patient_id in tqdm(train_series):\n    src_path = TRAIN_SRC_DIR + patient_id + '.dcm'\n    dcm_data = pydicom.dcmread(src_path)\n    image_array = dcm_data.pixel_array\n    image = Image.fromarray(image_array)\n    image.save(DATASET_DIR + 'images/train/' + patient_id + '.jpg')\nprint('Images moved to train folder:', len(os.listdir(DATASET_DIR + 'images/train/')))\n    \nfor patient_id in tqdm(val_series):\n    src_path = TRAIN_SRC_DIR + patient_id + '.dcm'\n    dcm_data = pydicom.dcmread(src_path)\n    image_array = dcm_data.pixel_array\n    image = Image.fromarray(image_array)\n    image.save(DATASET_DIR + 'images/val/' + patient_id + '.jpg')\nprint('Images moved to val folder:', len(os.listdir(DATASET_DIR + 'images/val/')))","metadata":{"execution":{"iopub.status.busy":"2024-02-11T09:06:52.463448Z","iopub.execute_input":"2024-02-11T09:06:52.463745Z","iopub.status.idle":"2024-02-11T09:08:20.616400Z","shell.execute_reply.started":"2024-02-11T09:06:52.463713Z","shell.execute_reply":"2024-02-11T09:08:20.615526Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def translate_bbox(bbox):\n    img_size = 1024 # rsna defualt image size\n    \n    top_left_x = bbox[0]\n    top_left_y = bbox[1]\n    absolute_w = bbox[2]\n    absolute_h = bbox[3]\n\n    relative_w = absolute_w / img_size\n    relative_h = absolute_h / img_size\n    \n    relative_x = top_left_x / img_size + relative_w / 2\n    relative_y = top_left_y / img_size + relative_h / 2\n    \n    return relative_x, relative_y, relative_w, relative_h\n    \ndef revert_bbox(rx, ry, rw, rh):\n    img_size = 1024 # rsna defualt image size\n    \n    x = (rx-rw/2)*img_size\n    y = (ry-rh/2)*img_size\n    w = rw*img_size\n    h = rh*img_size\n    \n    return x, y, w, h\n    \n    \ndef save_label(label_dir, patient_id, bbox):\n    label_fp = os.path.join(label_dir, patient_id + '.txt')\n    \n    f = open(label_fp, \"a\")\n    if (bbox == 'nan').all():\n        f.close()\n        return\n    \n    x, y, w, h = translate_bbox(bbox)\n    \n    line = f\"0 {x} {y} {w} {h}\\n\"\n    \n    f.write(line)\n    f.close()","metadata":{"execution":{"iopub.status.busy":"2024-02-11T09:08:20.617723Z","iopub.execute_input":"2024-02-11T09:08:20.618069Z","iopub.status.idle":"2024-02-11T09:08:20.627807Z","shell.execute_reply.started":"2024-02-11T09:08:20.618036Z","shell.execute_reply":"2024-02-11T09:08:20.626721Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"LABELS_DIR = \"./labels_temp/\"\nos.mkdir(LABELS_DIR)\n\nfor row in annotations.values:\n    if pd.notna(row[1:5]).all():\n        save_label(LABELS_DIR, row[0], row[1:5])\n    \nfor patient_id in train_series:\n    if os.path.isfile(LABELS_DIR + patient_id + '.txt'):\n        shutil.copy(LABELS_DIR + patient_id + '.txt', DATASET_DIR + 'labels/train/')\n    \nfor patient_id in val_series:\n    if os.path.isfile(LABELS_DIR + patient_id + '.txt'):\n        shutil.copy(LABELS_DIR + patient_id + '.txt', DATASET_DIR + 'labels/val/')\n    \nshutil.rmtree(LABELS_DIR)","metadata":{"execution":{"iopub.status.busy":"2024-02-11T09:08:20.630368Z","iopub.execute_input":"2024-02-11T09:08:20.630697Z","iopub.status.idle":"2024-02-11T09:08:22.238058Z","shell.execute_reply.started":"2024-02-11T09:08:20.630662Z","shell.execute_reply":"2024-02-11T09:08:22.237217Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Display example from dataset","metadata":{}},{"cell_type":"code","source":"demo_patient_id = val_series.values[8]\ndemo_img_path = DATASET_DIR + 'images/val/' + demo_patient_id + '.jpg'\ndemo_label_path = DATASET_DIR + 'labels/val/' + demo_patient_id + '.txt'\n\nplt.imshow(cv2.imread(demo_img_path))\n\nwith open(demo_label_path, \"r\") as f:\n    for line in f:\n        print(line)\n        class_id, rx, ry, rw, rh = list(map(float, line.strip().split()))\n        \n        x, y, w, h = revert_bbox(rx, ry, rw, rh)\n        plt.plot([x, x, x+w, x+w, x], [y, y+h, y+h, y, y])","metadata":{"execution":{"iopub.status.busy":"2024-02-11T09:08:22.239147Z","iopub.execute_input":"2024-02-11T09:08:22.239428Z","iopub.status.idle":"2024-02-11T09:08:22.664858Z","shell.execute_reply.started":"2024-02-11T09:08:22.239404Z","shell.execute_reply":"2024-02-11T09:08:22.663954Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prepare configuration and run training","metadata":{}},{"cell_type":"code","source":"%%writefile config.yaml\n\npath: '/kaggle/working/dataset' # dataset root dir\ntrain: images/train  # train images (relative to 'path')\nval: images/val  # val images (relative to 'path')\n\n# Classes\nnames:\n  0: pneumonia","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = YOLO('yolov8l.pt') # yaml","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"wandb.login(key='da27b138f0f392e3f931cf71acdab08543ac649c')\nresults = model.train(data='config.yaml', epochs=200)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inspect model predictions","metadata":{}},{"cell_type":"code","source":"def plot_val_pred(demo_patient_id, verbose=True, split='val'):\n    demo_img_path = DATASET_DIR + f'images/{split}/' + demo_patient_id + '.jpg'\n    demo_label_path = DATASET_DIR + f'labels/{split}/' + demo_patient_id + '.txt'\n\n    res = model(demo_img_path, verbose=verbose)\n    if verbose:\n        print(res[0].probs)\n        print(res[0].boxes.xywh)\n\n    plt.imshow(cv2.imread(demo_img_path))\n\n    img_size = 1014\n    if os.path.isfile(demo_label_path):\n        with open(demo_label_path, \"r\") as f:\n            for line in f:\n                if verbose:\n                    print(line)\n                class_id, rx, ry, rw, rh = list(map(float, line.strip().split()))\n\n                x, y, w, h = revert_bbox(rx, ry, rw, rh)\n                plt.plot([x, x, x+w, x+w, x], [y, y+h, y+h, y, y], c='blue')\n\n                \n    for box in res[0].boxes.xywh.cpu():\n        px, py, pw, ph = box\n        plt.plot([px-pw/2, px-pw/2, px+pw/2, px+pw/2, px-pw/2], [py-ph/2, py+ph/2, py+ph/2, py-ph/2, py-ph/2], c='orange')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def random_value(series):\n    return series.iloc[random.randrange(0, len(series))]\n\ndef plot_examples(series, rows = 5, cols = 2, split='val'):\n    plt.suptitle(split)\n    plt.figure(figsize=(10*cols,10*rows))\n    for h in range(rows):\n        for w in range(cols):\n            plt.subplot(rows, cols, h*2+w+1)\n            plot_val_pred(random_value(series), verbose=False, split=split)\n\nplot_examples(train_series, 2, 2, 'train')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_examples(val_series, 2, 2, 'val')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prepare test dataset","metadata":{}},{"cell_type":"code","source":"for file in tqdm(os.listdir(TEST_SRC_DIR)):\n    src_path = TEST_SRC_DIR + file\n    dcm_data = pydicom.dcmread(src_path)\n    image_array = dcm_data.pixel_array\n    image = Image.fromarray(image_array)\n    image.save(TEST_IMG_DIR + os.path.splitext(file)[0] + '.jpg')","metadata":{"execution":{"iopub.status.busy":"2024-02-11T22:37:40.822555Z","iopub.execute_input":"2024-02-11T22:37:40.823104Z","iopub.status.idle":"2024-02-11T22:38:42.153629Z","shell.execute_reply.started":"2024-02-11T22:37:40.823074Z","shell.execute_reply":"2024-02-11T22:38:42.151412Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Generate submission.csv","metadata":{}},{"cell_type":"code","source":"model = YOLO('../input/yolov8n-trained-on-rsna-pneumonia-detection/best.pt')","metadata":{"execution":{"iopub.status.busy":"2024-02-11T22:38:42.157155Z","iopub.execute_input":"2024-02-11T22:38:42.158339Z","iopub.status.idle":"2024-02-11T22:38:43.692992Z","shell.execute_reply.started":"2024-02-11T22:38:42.158293Z","shell.execute_reply":"2024-02-11T22:38:43.691403Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"results = model(TEST_IMG_DIR, verbose=False, conf=0.26)","metadata":{"execution":{"iopub.status.busy":"2024-02-11T22:38:43.695346Z","iopub.execute_input":"2024-02-11T22:38:43.695789Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_id_from_path(path):\n    return os.path.splitext(os.path.basename(path))[0]\n\nprint(get_id_from_path(results[1].path))\nprint(results[1].boxes.xywh)\nprint(results[1].boxes.conf)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with open('submission.csv', 'w') as file:\n    file.write(\"patientId,PredictionString\\n\")\n\n    for result in tqdm(results):\n        line = get_id_from_path(result.path) + ','\n        \n        for conf, xywh in zip(result.boxes.conf, result.boxes.xywh):\n            x, y, w, h = xywh\n            line += f\"{conf:.2f} {x-w/2:.2f} {y-h/2:.2f} {w:.2f} {h:.2f} \"\n            \n        line = line.strip()\n        file.write(line+\"\\n\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Collapse overlapping boxes","metadata":{}},{"cell_type":"code","source":"from pathlib import Path\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom PIL import Image","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path = Path('submission.csv')\ndf = pd.read_csv(path)\nprint(path.name)\n\ndf['pred_count'] = df['PredictionString'].apply(lambda x: 0 if pd.isna(x) else int(len(x.split()) / 5))\ndf['pred_count'].value_counts().sort_index()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def str_to_boxes(s: str) -> list:  # return c,x,y,w,h\n    if pd.isna(s) or len(s) == 0:\n        return []\n\n    boxes = []\n    n = len(s.split()) // 5\n    for i in range(n):\n        box = s.split()[i * 5:i * 5 + 5]\n        boxes.append(list(map(float, box)))\n\n    return boxes\n\ndef remove_empty_boxes(s: str) -> str:\n    if pd.isna(s) or len(s) == 0:\n        return s\n\n    n = len(s.split()) // 5\n    data = s.split()\n    for i in range(n):\n        box = data[i * 5:i * 5 + 5]\n        if float(box[2]) * float(box[3]) == 0:\n            s = s.replace(' '.join(map(str, box)), '').strip()\n\n    return s","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['PredictionString'] = df['PredictionString'].apply(remove_empty_boxes)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def box_areas_utils(box1, box2):  # corner coords\n    _, left_x1, left_y1, w1, h1 = box1\n    _, left_x2, left_y2, w2, h2 = box2\n\n    assert w1 * h1 * w2 * h2 > 0, 'w or h is 0'\n\n    right_x1, right_x2 = left_x1 + w1, left_x2 + w2\n    top_y1, top_y2 = left_y1 + h1, left_y2 + h2\n\n    area1, area2 = w1 * h1, w2 * h2\n    right_xi = min(right_x1, right_x2)\n    left_xi = max(left_x1, left_x2)\n    top_yi = min(top_y1, top_y2)\n    bottom_yi = max(left_y1, left_y2)\n\n    if right_xi <= left_xi or top_yi <= bottom_yi:\n        intersection = 0\n    else:\n        intersection = (right_xi - left_xi) * (top_yi - bottom_yi)\n\n    union = area1 + area2 - intersection\n    return area1, area2, intersection, union\n\n\ndef iou(box1, box2):\n    area1, area2, intersection, union = box_areas_utils(box1, box2)\n    return intersection / union\n\n\ndef two_boxes_overlap(box1, box2) -> bool:\n    return iou(box1, box2) > 0.3\n\n\ndef one_box_inside_another(box1, box2) -> bool:\n    area1, area2, intersection, union = box_areas_utils(box1, box2)\n    return intersection / area1 > 0.7 or intersection / area2 > 0.7\n\n\ndef merge_boxes(box1, box2) -> (float, float, float, float, float):  # c,x,y,w,h - bottom left corner (0,0)\n    c1, x1, y1, w1, h1 = box1\n    c2, x2, y2, w2, h2 = box2\n    min_x, min_y = min([x1, x2]), min([y1, y2])\n    max_x, max_y = max([x1 + w1, x2 + w2]), max([y1 + h1, y2 + h2])\n\n    w, h = max_x - min_x, max_y - min_y\n    # reduce w, h by 10%\n    dw, dh = w * 0.05, h * 0.05\n    w, h = w * 0.9, h * 0.9\n\n    return (c1 + c2) / 2, (x1 + x2) / 2, (y1 + y2) / 2, (w1+w2) / 2, (h1+h2) / 2\n\n\ndef detect_overlapping(s: str, type='both') -> bool:\n    if pd.isna(s):\n        return False\n\n    n = len(s.split()) // 5\n    for i in range(n):\n        box1 = list(map(float, s.split()[i * 5:i * 5 + 5]))\n        for j in range(n):\n            if i == j:\n                continue\n            box2 = list(map(float, s.split()[j * 5:j * 5 + 5]))\n\n            if type == 'both':\n                if two_boxes_overlap(box1, box2) or one_box_inside_another(box1, box2):\n                    return True\n            elif type == 'overlap':\n                if two_boxes_overlap(box1, box2) and not one_box_inside_another(box1, box2):\n                    return True\n            elif type == 'inside':\n                if one_box_inside_another(box1, box2):\n                    return True\n\n    return False\n\n\ndef merge_overlapping(s: str) -> str:\n    if pd.isna(s):\n        return s\n\n    boxes = str_to_boxes(s)\n\n    for i in range(len(boxes)):\n        for j in range(i + 1, len(boxes)):\n            if boxes[i] is None or boxes[j] is None:\n                continue\n            if two_boxes_overlap(boxes[i], boxes[j]) or \\\n                    one_box_inside_another(boxes[i], boxes[j]):\n                boxes[i] = merge_boxes(boxes[i], boxes[j])\n                boxes[j] = None\n\n    return ' '.join([' '.join(map(str, c)) for c in boxes if c is not None]).strip()\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['overlaps'] = df['PredictionString'].apply(lambda s: detect_overlapping(s, type='overlap'));\ndf['inside_box'] = df['PredictionString'].apply(lambda s: detect_overlapping(s, type='inside'));\nprint(df.query('pred_count>0')['overlaps'].value_counts())\nprint(df.query('pred_count>0')['inside_box'].value_counts())","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df[df['overlaps']]['pred_count'].value_counts().sort_index()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['PredictionString_'] = df['PredictionString']\n\ns = df.query('inside_box').sample(1).iloc[0]\n\nimg = Image.open(f'test_images/{s[\"patientId\"]}.jpg')\nimg = img.convert('RGB')\nax = plt.gca()\nboxes = str_to_boxes(s['PredictionString_'])\nfor b in boxes:\n    rect = patches.Rectangle((b[1], b[2]), b[3], b[4], linewidth=1, edgecolor='r', facecolor='none')\n    ax.add_patch(rect)\n\nfixed_s = merge_overlapping(s['PredictionString_'])\nboxes_fixed = str_to_boxes(fixed_s)\nfor b in boxes_fixed:\n    rect = patches.Rectangle((b[1], b[2]), b[3], b[4], linewidth=1, edgecolor='b', facecolor='none', linestyle=':')\n    ax.add_patch(rect)\nplt.imshow(img);","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['PredictionString'] = df['PredictionString'].apply(merge_overlapping)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df[['patientId', 'PredictionString']].to_csv(path.name, index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}