{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Vinbigdata Chest X-ray Abnormalities Detection\n### Team 'scumed': 3rd Place Solution (Aortic Specialist Component; Inference)\n\nRequired input datasets:\n- `aortic_specialist_weights` -- our trained Yolov5 weights for the specialized Aortic Enlargement class detector\n- `vinbigdata-precomputed-image_dims` -- csv containing original image dimensions for each image ID\n- `vinbigdata-test-png-512-1024` -- PNG images of official competition test data, resized to 512 and 1024 respectively\n- `yolov5-vbd` -- Snapshot of Yolov5 commit that we used (original: https://github.com/ultralytics/yolov5/)\n\nOutput:\n- `aortic_final_fixed.csv` -- predictions for aortic enlargement class, used in our 3rd Place solution\n","metadata":{}},{"cell_type":"code","source":"import time\nfrom pathlib import Path\nfrom types import SimpleNamespace\nimport os\nimport sys\nimport pandas as pd\nimport numpy as np\nimport shutil\n\nimport cv2\nimport torch\nimport torch.backends.cudnn as cudnn\nfrom numpy import random\nimport multiprocess as mp\nfrom tqdm import tqdm\n\n# Yolov5\nsys.path.append('../input/yolov5-vbd/yolov5/')\nfrom models.experimental import attempt_load\nfrom utils.datasets import LoadStreams, LoadImages\nfrom utils.general import check_img_size, non_max_suppression, apply_classifier, scale_coords, xyxy2xywh, \\\n    strip_optimizer, set_logging, increment_path\nfrom utils.plots import plot_one_box\nfrom utils.torch_utils import select_device, load_classifier, time_synchronized\n\n# Ensemble-boxes library\n!pip install -qq ensemble-boxes\nfrom ensemble_boxes import *","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!mkdir /kaggle/temp/","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# From https://github.com/ultralytics/yolov5\ndef detect(opt, save_img=False):\n    source, weights, view_img, save_txt, imgsz = opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size\n    webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(\n        ('rtsp://', 'rtmp://', 'http://'))\n\n    # Directories\n    save_dir = Path(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok))  # increment run\n    (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir\n\n    # Initialize\n    set_logging()\n    device = select_device(opt.device)\n    half = device.type != 'cpu'  # half precision only supported on CUDA\n\n    # Load model\n    model = attempt_load(weights, map_location=device)  # load FP32 model\n    imgsz = check_img_size(imgsz, s=model.stride.max())  # check img_size\n    if half:\n        model.half()  # to FP16\n\n    # Second-stage classifier\n    classify = False\n#     if classify:\n#         modelc = load_classifier(name='resnet101', n=2)  # initialize\n#         modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()\n\n    # Set Dataloader\n    vid_path, vid_writer = None, None\n    if webcam:\n        view_img = True\n        cudnn.benchmark = True  # set True to speed up constant image size inference\n        dataset = LoadStreams(source, img_size=imgsz)\n    else:\n        save_img = True\n        dataset = LoadImages(source, img_size=imgsz)\n\n    # Get names and colors\n    names = model.module.names if hasattr(model, 'module') else model.names\n    colors = [[random.randint(0, 255) for _ in range(3)] for _ in names]\n\n    # Run inference\n    t0 = time.time()\n    img = torch.zeros((1, 3, imgsz, imgsz), device=device)  # init img\n    _ = model(img.half() if half else img) if device.type != 'cpu' else None  # run once\n    for path, img, im0s, vid_cap in dataset:\n        img = torch.from_numpy(img).to(device)\n        img = img.half() if half else img.float()  # uint8 to fp16/32\n        img /= 255.0  # 0 - 255 to 0.0 - 1.0\n        if img.ndimension() == 3:\n            img = img.unsqueeze(0)\n\n        # Inference\n        t1 = time_synchronized()\n        pred = model(img, augment=opt.augment)[0]\n\n        # Apply NMS\n        pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, classes=opt.classes, agnostic=opt.agnostic_nms)\n        t2 = time_synchronized()\n\n        # Apply Classifier\n        if classify:\n            pred = apply_classifier(pred, modelc, img, im0s)\n\n        # Process detections\n        for i, det in enumerate(pred):  # detections per image\n            if webcam:  # batch_size >= 1\n                p, s, im0 = Path(path[i]), '%g: ' % i, im0s[i].copy()\n            else:\n                p, s, im0 = Path(path), '', im0s\n\n            save_path = str(save_dir / p.name)\n            txt_path = str(save_dir / 'labels' / p.stem) + ('_%g' % dataset.frame if dataset.mode == 'video' else '')\n            s += '%gx%g ' % img.shape[2:]  # print string\n            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh\n            if len(det):\n                # Rescale boxes from img_size to im0 size\n                det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()\n\n                # Print results\n                for c in det[:, -1].unique():\n                    n = (det[:, -1] == c).sum()  # detections per class\n                    s += '%g %ss, ' % (n, names[int(c)])  # add to string\n\n                # Write results\n                for *xyxy, conf, cls in reversed(det):\n                    if save_txt:  # Write to file\n                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh\n                        line = (cls, *xywh, conf) if opt.save_conf else (cls, *xywh)  # label format\n                        with open(txt_path + '.txt', 'a') as f:\n                            f.write(('%g ' * len(line)).rstrip() % line + '\\n')\n\n                    if save_img or view_img:  # Add bbox to image\n                        label = '%s %.2f' % (names[int(cls)], conf)\n                        plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3)\n\n            # Print time (inference + NMS)\n            print('%sDone. (%.3fs)' % (s, t2 - t1))\n\n            # Stream results\n            if view_img:\n                cv2.imshow(str(p), im0)\n                if cv2.waitKey(1) == ord('q'):  # q to quit\n                    raise StopIteration\n\n            # Save results (image with detections)\n            if save_img:\n                if dataset.mode == 'images':\n                    cv2.imwrite(save_path, im0)\n                else:\n                    if vid_path != save_path:  # new video\n                        vid_path = save_path\n                        if isinstance(vid_writer, cv2.VideoWriter):\n                            vid_writer.release()  # release previous video writer\n\n                        fourcc = 'mp4v'  # output video codec\n                        fps = vid_cap.get(cv2.CAP_PROP_FPS)\n                        w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n                        h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n                        vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*fourcc), fps, (w, h))\n                    vid_writer.write(im0)\n\n    if save_txt or save_img:\n        s = f\"\\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}\" if save_txt else ''\n        print(f\"Results saved to {save_dir}{s}\")\n\n    print('Done. (%.3fs)' % (time.time() - t0))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Some utility functions\ndef get_yolo_inference_df(yolo_pred_path, meta):\n\n    output = os.listdir(f'{yolo_pred_path}/labels/')\n    output = [x for x in output if x.split('.')[-1] == 'txt']\n\n    yolo_results = []\n\n    for i in output:\n        yolo_dat = pd.read_csv(f'{yolo_pred_path}/labels/{i}', sep = \" \", header = None)\n        yolo_dat.columns = ['yolo_class', 'x_center_norm', 'y_center_norm', 'width_norm', 'height_norm', 'confidence']\n        yolo_dat['image_id'] = i.split('.')[0]\n\n        yolo_results.append(yolo_dat)\n\n    yolo_dat = pd.concat(yolo_results)\n\n    yolo_dat = yolo_dat.merge(meta, how = 'left', on = 'image_id')\n\n    yolo_dat['x_min_norm'] = yolo_dat['x_center_norm'] - yolo_dat['width_norm'] / 2\n    yolo_dat['x_max_norm'] = yolo_dat['x_center_norm'] + yolo_dat['width_norm'] / 2\n    yolo_dat['y_min_norm'] = yolo_dat['y_center_norm'] - yolo_dat['height_norm'] / 2\n    yolo_dat['y_max_norm'] = yolo_dat['y_center_norm'] + yolo_dat['height_norm'] / 2\n\n    yolo_dat['x_min_norm'] = yolo_dat['x_min_norm'].apply(lambda x: max(x, 0))\n    yolo_dat['y_min_norm'] = yolo_dat['y_min_norm'].apply(lambda x: max(x, 0))\n    yolo_dat['x_max_norm'] = yolo_dat['x_max_norm'].apply(lambda x: min(x, 1))\n    yolo_dat['y_max_norm'] = yolo_dat['y_max_norm'].apply(lambda x: min(x, 1))\n\n    yolo_dat['x_min'] = (yolo_dat['x_min_norm'] * yolo_dat['width']).astype(int)\n    yolo_dat['x_max'] = (yolo_dat['x_max_norm'] * yolo_dat['width']).astype(int)\n    yolo_dat['y_min'] = (yolo_dat['y_min_norm'] * yolo_dat['height']).astype(int)\n    yolo_dat['y_max'] = (yolo_dat['y_max_norm'] * yolo_dat['height']).astype(int)\n\n    return yolo_dat\n\n\ndef nms_ensemble(image_id, rad1, rad2, rad3, rad4, rad5, iou_thr = 0.5, skip_box_thr = 0.0001, sigma = 0.1):\n\n    rad1 = rad1.loc[rad1['image_id'] == image_id].reset_index(drop = True)\n    rad2 = rad2.loc[rad2['image_id'] == image_id].reset_index(drop = True)\n    rad3 = rad3.loc[rad3['image_id'] == image_id].reset_index(drop = True)\n    rad4 = rad4.loc[rad4['image_id'] == image_id].reset_index(drop = True)\n    rad5 = rad5.loc[rad5['image_id'] == image_id].reset_index(drop = True)\n\n\n    rad1_boxes = np.array(rad1[['x_min_norm', 'y_min_norm', 'x_max_norm', 'y_max_norm']]).tolist()\n    rad2_boxes = np.array(rad2[['x_min_norm', 'y_min_norm', 'x_max_norm', 'y_max_norm']]).tolist()\n    rad3_boxes = np.array(rad3[['x_min_norm', 'y_min_norm', 'x_max_norm', 'y_max_norm']]).tolist()\n    rad4_boxes = np.array(rad4[['x_min_norm', 'y_min_norm', 'x_max_norm', 'y_max_norm']]).tolist()\n    rad5_boxes = np.array(rad5[['x_min_norm', 'y_min_norm', 'x_max_norm', 'y_max_norm']]).tolist()\n\n    rad1_conf = np.array(rad1['confidence']).tolist()\n    rad2_conf = np.array(rad2['confidence']).tolist()\n    rad3_conf = np.array(rad3['confidence']).tolist()\n    rad4_conf = np.array(rad4['confidence']).tolist()\n    rad5_conf = np.array(rad5['confidence']).tolist()\n\n    rad1_lab = np.array(rad1['yolo_class']).tolist()\n    rad2_lab = np.array(rad2['yolo_class']).tolist()\n    rad3_lab = np.array(rad3['yolo_class']).tolist()\n    rad4_lab = np.array(rad4['yolo_class']).tolist()\n    rad5_lab = np.array(rad5['yolo_class']).tolist()\n\n    boxes_list = [rad1_boxes, rad2_boxes, rad3_boxes, rad4_boxes, rad5_boxes]\n    scores_list = [rad1_conf, rad2_conf, rad3_conf, rad4_conf, rad5_conf]\n    labels_list = [rad1_lab, rad2_lab, rad3_lab, rad4_lab, rad5_lab]\n\n    weights = [1, 1, 1, 1, 1]\n\n    boxes, scores, labels = nms(boxes_list, scores_list, labels_list, weights = weights, iou_thr = iou_thr)\n\n    ret_df = pd.DataFrame.from_records(boxes, columns = ['x_min_norm', 'y_min_norm', 'x_max_norm', 'y_max_norm'])\n    ret_df['image_id'] = image_id\n    ret_df['yolo_class'] = np.array(labels).astype(int)\n    ret_df['confidence'] = scores\n\n    return ret_df\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def yolo_aortic_inference(FOLD):\n\n    print(f'Yolov5 Inference for Fold {FOLD}...')\n    if os.path.isdir(f'/kaggle/temp/aortic_fold{FOLD}'):\n        shutil.rmtree(os.path.join(f'/kaggle/temp/aortic_fold{FOLD}'))\n\n    args = {\n        'weights' : f'../input/aortic-specialist-weights/weights/best_aortic_fold{FOLD}.pt',\n        'source': '../input/vinbigdata-test-png-512-1024/extracted_images/512/test/',\n        'img_size': 640,\n        'conf_thres': 0.00001,\n        'iou_thres': 0.5,\n        'device': '',\n        'classes': None,\n        'view_img': False,\n        'save_txt': True,\n        'save_conf': True,\n        'agnostic_nms': False,\n        'augment': True,\n        'update': False,\n        'project': '/kaggle/temp/aortic/',\n        'name': f'aortic_fold{FOLD}',\n        'exist_ok': True\n    }    \n\n    opt = SimpleNamespace(**args)\n    detect(opt)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Obtain Aortic (+Calcification) bboxes across 5 folds\nfor FOLD in range(0, 5):\n    yolo_aortic_inference(FOLD)","metadata":{"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load inference results from the 5 folds\nmetadata = pd.read_csv('../input/vinbigdata-precomputed-image-dims/test_meta.csv')\nyolo_dat_f0 = get_yolo_inference_df('/kaggle/temp/aortic/aortic_fold0', metadata)\nyolo_dat_f1 = get_yolo_inference_df('/kaggle/temp/aortic/aortic_fold1', metadata)\nyolo_dat_f2 = get_yolo_inference_df('/kaggle/temp/aortic/aortic_fold2', metadata)\nyolo_dat_f3 = get_yolo_inference_df('/kaggle/temp/aortic/aortic_fold3', metadata)\nyolo_dat_f4 = get_yolo_inference_df('/kaggle/temp/aortic/aortic_fold4', metadata)\n\n\nall_imageids = np.unique(np.concatenate([yolo_dat_f0.image_id,\n                                         yolo_dat_f1.image_id,\n                                         yolo_dat_f2.image_id,\n                                         yolo_dat_f3.image_id,\n                                         yolo_dat_f4.image_id]))\n\n# Combine predictions using NMS\npool = mp.Pool(min(mp.cpu_count(), 2))\n\ndef nms_ensemble_wrap(i):\n\n    return nms_ensemble(all_imageids[i],\n                        yolo_dat_f0,\n                        yolo_dat_f1,\n                        yolo_dat_f2,\n                        yolo_dat_f3,\n                        yolo_dat_f4,\n                        iou_thr=0.5)\nwith pool as p:\n    res = list(tqdm(p.imap(nms_ensemble_wrap,\n                           range(len(all_imageids))),\n                           total = len(all_imageids)))\n\npool.terminate()\npool.join()\n\n# Generate final prediction file\nyolo_dat = pd.concat(res).reset_index(drop = True)\nyolo_dat['yolo_class'] = yolo_dat['yolo_class'].astype(int)\n\nyolo_dat = yolo_dat.merge(metadata, how = 'left', on = 'image_id')\n\nyolo_dat['x_min'] = (yolo_dat['x_min_norm'] * yolo_dat['width']).astype(int)\nyolo_dat['x_max'] = (yolo_dat['x_max_norm'] * yolo_dat['width']).astype(int)\nyolo_dat['y_min'] = (yolo_dat['y_min_norm'] * yolo_dat['height']).astype(int)\nyolo_dat['y_max'] = (yolo_dat['y_max_norm'] * yolo_dat['height']).astype(int)\n\n# Only keep Aortic predictions\nyolo_dat = yolo_dat.loc[yolo_dat['yolo_class'] == 0].reset_index(drop = True)\nyolo_dat.to_csv('aortic_final_fixed.csv', index = False)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}