{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Out of fold evaluation for EfficientDet\n\nHi everyone!\n\nMy name is Alex Shonenkov, I am DL/NLP/CV/TS research engineer. Especially I am in Love with NLP & DL.\n\nRecently I have created kernels for this competition:\n- [WBF approach for ensemble](https://www.kaggle.com/shonenkov/wbf-approach-for-ensemble)\n- [[Training] EfficientDet](https://www.kaggle.com/shonenkov/training-efficientdet)\n- [[Inference] EfficientDet](https://www.kaggle.com/shonenkov/inference-efficientdet)","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Main Idea\n\nPeople from ODS slack asked me about competition metrics on my validation split for effdet. So I have decided to publish notebook with calculation oof-score for my models and selection best threshold :) \n\nFor this aims I have trained 5-folds using published [training kernel](https://www.kaggle.com/shonenkov/training-efficientdet). \n\nIt is very simple notebook without any really good idea, but I hope It can help you evaluate effdet models in the future during research!\nSo, lets start! \n\n[Update]:\n\nI would like to share with you mixup approach with 3 different implementation, also here do evaluation for new models.\n\nMixup is very simple approach, you can see this picture and understand main idea:\n\n![](https://cdn-images-1.medium.com/max/1000/0*yLCQYAtNAh28LQks.png)\n\nLets do this simple approach for wheat detection!","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"_kg_hide-output":true,"_kg_hide-input":true},"cell_type":"code","source":"!pip install --no-deps '../input/timm-package/timm-0.1.26-py3-none-any.whl' > /dev/null\n!pip install --no-deps '../input/pycocotools/pycocotools-2.0-cp37-cp37m-linux_x86_64.whl' > /dev/null","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"import sys\nsys.path.insert(0, \"../input/timm-efficientdet-pytorch\")\nsys.path.insert(0, \"../input/omegaconf\")\nsys.path.insert(0, \"../input/weightedboxesfusion\")\n\nfrom ensemble_boxes import *\nimport torch\nimport random\nimport numpy as np\nimport pandas as pd\nfrom glob import glob\nfrom torch.utils.data import Dataset,DataLoader\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\nimport cv2\nimport gc\nfrom tqdm import tqdm\nfrom matplotlib import pyplot as plt\nfrom effdet import get_efficientdet_config, EfficientDet, DetBenchEval\nfrom effdet.efficientdet import HeadNet\nfrom sklearn.model_selection import StratifiedKFold","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Prepare folds","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"marking = pd.read_csv('../input/global-wheat-detection/train.csv')\n\nbboxs = np.stack(marking['bbox'].apply(lambda x: np.fromstring(x[1:-1], sep=',')))\nfor i, column in enumerate(['x', 'y', 'w', 'h']):\n    marking[column] = bboxs[:,i]\nmarking.drop(columns=['bbox'], inplace=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n\ndf_folds = marking[['image_id']].copy()\ndf_folds.loc[:, 'bbox_count'] = 1\ndf_folds = df_folds.groupby('image_id').count()\ndf_folds.loc[:, 'source'] = marking[['image_id', 'source']].groupby('image_id').min()['source']\ndf_folds.loc[:, 'stratify_group'] = np.char.add(\n    df_folds['source'].values.astype(str),\n    df_folds['bbox_count'].apply(lambda x: f'_{x // 15}').values.astype(str)\n)\ndf_folds.loc[:, 'fold'] = 0\n\nfor fold_number, (train_index, val_index) in enumerate(skf.split(X=df_folds.index, y=df_folds['stratify_group'])):\n    df_folds.loc[df_folds.iloc[val_index].index, 'fold'] = fold_number","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_valid_transforms():\n    return A.Compose(\n        [\n            A.Resize(height=512, width=512, p=1.0),\n            ToTensorV2(p=1.0),\n        ], \n        p=1.0, \n        bbox_params=A.BboxParams(\n            format='pascal_voc',\n            min_area=0, \n            min_visibility=0,\n            label_fields=['labels']\n        )\n    )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"TRAIN_ROOT_PATH = '../input/global-wheat-detection/train'\n\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\n\nclass DatasetRetriever(Dataset):\n\n    def __init__(self, marking, image_ids, transforms=None, test=False):\n        super().__init__()\n\n        self.image_ids = image_ids\n        self.marking = marking\n        self.transforms = transforms\n        self.test = test\n\n    def __getitem__(self, index: int):\n        image_id = self.image_ids[index]\n\n        image, boxes = self.load_image_and_boxes(index)\n\n        # there is only one class\n        labels = torch.ones((boxes.shape[0],), dtype=torch.int64)\n        \n        target = {}\n        target['boxes'] = boxes\n        target['labels'] = labels\n        target['image_id'] = torch.tensor([index])\n\n        if self.transforms:\n            for i in range(10):\n                sample = self.transforms(**{\n                    'image': image,\n                    'bboxes': target['boxes'],\n                    'labels': labels\n                })\n                if len(sample['bboxes']) > 0:\n                    image = sample['image']\n                    target['boxes'] = torch.stack(tuple(map(torch.tensor, zip(*sample['bboxes'])))).permute(1, 0)\n#                     target['boxes'][:,[0,1,2,3]] = target['boxes'][:,[1,0,3,2]]  #yxyx: be warning\n                    break\n\n        return image, target, image_id\n\n    def __len__(self) -> int:\n        return self.image_ids.shape[0]\n\n    def load_image_and_boxes(self, index):\n        image_id = self.image_ids[index]\n        image = cv2.imread(f'{TRAIN_ROOT_PATH}/{image_id}.jpg', cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        image /= 255.0\n        records = self.marking[self.marking['image_id'] == image_id]\n        boxes = records[['x', 'y', 'w', 'h']].values\n        boxes[:, 2] = boxes[:, 0] + boxes[:, 2]\n        boxes[:, 3] = boxes[:, 1] + boxes[:, 3]\n        return image, boxes","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"def draw_image_and_boxes(list_images, list_boxes):\n    fig, ax = plt.subplots(4, 2, figsize=(16, 32))\n    for i, (image, boxes) in enumerate(zip(list_images, list_boxes)):\n        for box in boxes:\n            cv2.rectangle(image,(box[0], box[1]),(box[2],  box[3]),(0, 1, 0), 2)\n        ax.set_axis_off()\n        ax.imshow(image);","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"dataset = DatasetRetriever(\n    image_ids=df_folds[df_folds['fold'] == 0].index.values,\n    marking=marking,\n    transforms=get_valid_transforms(),\n    test=True,\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Mixup #1\n\nfull images 1:1","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"count = 4\n\nfig, ax = plt.subplots(count, 3, figsize=(16, 6*count))\n\nfor i in range(count):\n    image, boxes = dataset.load_image_and_boxes(random.randint(0, dataset.image_ids.shape[0] - 1))\n    r_image, r_boxes = dataset.load_image_and_boxes(random.randint(0, dataset.image_ids.shape[0] - 1))\n    mixup_image = (image+r_image)/2\n\n    for box in boxes.astype(int):\n        cv2.rectangle(image,(box[0], box[1]),(box[2],  box[3]),(0, 0, 1), 3)\n        cv2.rectangle(mixup_image,(box[0], box[1]),(box[2],  box[3]),(0, 0, 1), 3)\n        \n    for box in r_boxes.astype(int):\n        cv2.rectangle(r_image,(box[0], box[1]),(box[2],  box[3]),(1, 0, 0), 3)\n        cv2.rectangle(mixup_image,(box[0], box[1]),(box[2],  box[3]),(1, 0, 0), 3)\n        \n    ax[i][0].imshow(image)\n    ax[i][1].imshow(r_image)\n    ax[i][2].imshow(mixup_image)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Mixup #2\n\nrandom selection","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"count = 4\n\nfig, ax = plt.subplots(count, 3, figsize=(16, 6*count))\n\nfor i in range(count):\n    image, boxes = dataset.load_image_and_boxes(random.randint(0, dataset.image_ids.shape[0] - 1))\n    r_image, r_boxes = dataset.load_image_and_boxes(random.randint(0, dataset.image_ids.shape[0] - 1))\n    \n    for box in r_boxes.astype(int):\n        cv2.rectangle(r_image,(box[0], box[1]),(box[2],  box[3]),(1, 0, 0), 3)\n    \n    mixup_image = image.copy()\n\n    imsize = image.shape[0]\n    x1, y1 = [int(random.uniform(imsize * 0.0, imsize * 0.45)) for _ in range(2)]\n    x2, y2 = [int(random.uniform(imsize * 0.55, imsize * 1.0)) for _ in range(2)]\n    \n    mixup_boxes = r_boxes.copy()\n    mixup_boxes[:, [0, 2]] = mixup_boxes[:, [0, 2]].clip(min=x1, max=x2)\n    mixup_boxes[:, [1, 3]] = mixup_boxes[:, [1, 3]].clip(min=y1, max=y2)\n    \n    mixup_boxes = mixup_boxes.astype(np.int32)\n    mixup_boxes = mixup_boxes[np.where((mixup_boxes[:,2]-mixup_boxes[:,0])*(mixup_boxes[:,3]-mixup_boxes[:,1]) > 0)]\n    \n    cv2.rectangle(r_image,(x1, y1),(x2,  y2),(0, 1, 1), 5)\n    \n    mixup_image[y1:y2, x1:x2] = (mixup_image[y1:y2, x1:x2] + r_image[y1:y2, x1:x2])/2\n    \n    cv2.rectangle(mixup_image,(x1, y1),(x2,  y2),(0, 1, 1), 5)\n    \n    for box in boxes.astype(int):\n        cv2.rectangle(image,(box[0], box[1]),(box[2],  box[3]),(0, 0, 1), 3)\n        cv2.rectangle(mixup_image,(box[0], box[1]),(box[2],  box[3]),(0, 0, 1), 3)\n        \n    for box in mixup_boxes.astype(int):\n        cv2.rectangle(mixup_image,(box[0], box[1]),(box[2],  box[3]),(1, 0, 0), 3)\n        \n    ax[i][0].imshow(image)\n    ax[i][1].imshow(r_image)\n    ax[i][2].imshow(mixup_image)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Mixup #3\n\nusing only for one part of [mosaic](https://www.kaggle.com/nvnnghia/awesome-augmentation), getting fix size 2 random boxes from image1 and image2, then mixing","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"count = 4\n\nfig, ax = plt.subplots(count, 3, figsize=(16, 6*count))\n\nfor i in range(count):\n    image, boxes = dataset.load_image_and_boxes(random.randint(0, dataset.image_ids.shape[0] - 1))\n    r_image, r_boxes = dataset.load_image_and_boxes(random.randint(0, dataset.image_ids.shape[0] - 1))\n    \n    for box in r_boxes.astype(int):\n        cv2.rectangle(r_image,(box[0], box[1]),(box[2],  box[3]),(1, 0, 0), 3)\n\n    imsize = image.shape[0]\n    w,h = imsize, imsize\n    s = imsize // 2\n\n    xc, yc = [int(random.uniform(imsize * 0.4, imsize * 0.6)) for _ in range(2)]\n    direct = random.randint(0, 3)\n\n    result_image = image.copy()\n    result_boxes = []\n\n    if direct == 0:\n        x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc  # xmin, ymin, xmax, ymax (large image)\n        x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h  # xmin, ymin, xmax, ymax (small image)\n    elif direct == 1:  # top right\n        x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc\n        x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h\n    elif direct == 2:  # bottom left\n        x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)\n        x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, max(xc, w), min(y2a - y1a, h)\n    elif direct == 3:  # bottom right\n        x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)\n        x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)\n\n    padw = x1a - x1b\n    padh = y1a - y1b\n\n    r_boxes[:, 0] += padw\n    r_boxes[:, 1] += padh\n    r_boxes[:, 2] += padw\n    r_boxes[:, 3] += padh\n\n    result_boxes.append(r_boxes)\n\n    result_image[y1a:y2a, x1a:x2a] = (result_image[y1a:y2a, x1a:x2a] + r_image[y1b:y2b, x1b:x2b]) / 2 \n    \n    cv2.rectangle(image,(x1a, y1a),(x2a,  y2a),(0, 1, 1), 5)\n    cv2.rectangle(r_image,(x1b, y1b),(x2b,  y2b),(0, 1, 1), 5)\n    cv2.rectangle(result_image,(x1a, y1a),(x2a,  y2a),(0, 1, 1), 5)\n    \n    result_boxes = np.concatenate(result_boxes, 0)\n    np.clip(result_boxes[:, 0:], 0, 2 * s, out=result_boxes[:, 0:])\n    result_boxes = result_boxes.astype(np.int32)\n    result_boxes = result_boxes[np.where((result_boxes[:,2]-result_boxes[:,0])*(result_boxes[:,3]-result_boxes[:,1]) > 0)]\n    \n    for box in boxes.astype(int):\n        cv2.rectangle(image,(box[0], box[1]),(box[2],  box[3]),(0, 0, 1), 3)\n        cv2.rectangle(result_image,(box[0], box[1]),(box[2],  box[3]),(0, 0, 1), 3)\n        \n    for box in result_boxes.astype(int):\n        cv2.rectangle(result_image,(box[0], box[1]),(box[2],  box[3]),(1, 0, 0), 3)\n        \n    ax[i][0].imshow(image)\n    ax[i][1].imshow(r_image)\n    ax[i][2].imshow(result_image)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"At the moment I have used only Mixup\\#1, things that I have changed in [baseline](https://www.kaggle.com/shonenkov/training-efficientdet) for getting new score:\n\n- add mixup\\#1\n- equal proba mixup/cutmix\n- epoch 40 --> 100\n- patience 1 --> 2\n\nAfter this I have got new best score:\n\n- OOF-score 0.6859 --> 0.6906\n- LB score 0.7294 --> 0.7318\n\nLets do oof-evaluation.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Load Models","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_net(checkpoint_path):\n    config = get_efficientdet_config('tf_efficientdet_d5')\n    net = EfficientDet(config, pretrained_backbone=False)\n\n    config.num_classes = 1\n    config.image_size = 512\n    net.class_net = HeadNet(config, num_outputs=config.num_classes, norm_kwargs=dict(eps=.001, momentum=.01))\n\n    checkpoint = torch.load(checkpoint_path)\n    net.load_state_dict(checkpoint)\n\n    del checkpoint\n    gc.collect()\n\n    net = DetBenchEval(net, config)\n    net.eval();\n    return net.cuda()\n\nmodels = [\n    load_net('../input/effdet5-folds-mixup/fold0-best-v2.bin'),\n    load_net('../input/effdet5-folds-mixup/fold1-best-v2.bin'),\n    load_net('../input/effdet5-folds-mixup/fold2-best-v2.bin'),\n    load_net('../input/effdet5-folds-mixup/fold3-best-v2.bin'),\n    load_net('../input/effdet5-folds-mixup/fold4-best-v2.bin'),\n]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Out of fold prediction:","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\n\nall_predictions = []\nfor fold_number in range(5):\n    validation_dataset = DatasetRetriever(\n        image_ids=df_folds[df_folds['fold'] == fold_number].index.values,\n        marking=marking,\n        transforms=get_valid_transforms(),\n        test=True,\n    )\n\n    validation_loader = DataLoader(\n        validation_dataset,\n        batch_size=4,\n        shuffle=False,\n        num_workers=2,\n        drop_last=False,\n        collate_fn=collate_fn\n    )\n\n    for images, targets, image_ids in tqdm(validation_loader, total=len(validation_loader)):\n        with torch.no_grad():\n            images = torch.stack(images)\n            images = images.cuda().float()\n            det = models[fold_number](images, torch.tensor([1]*images.shape[0]).float().cuda())\n\n            for i in range(images.shape[0]):\n                boxes = det[i].detach().cpu().numpy()[:,:4]    \n                scores = det[i].detach().cpu().numpy()[:,4]\n                boxes[:, 2] = boxes[:, 2] + boxes[:, 0]\n                boxes[:, 3] = boxes[:, 3] + boxes[:, 1]\n                all_predictions.append({\n                    'pred_boxes': (boxes*2).clip(min=0, max=1023).astype(int),\n                    'scores': scores,\n                    'gt_boxes': (targets[i]['boxes'].cpu().numpy()*2).clip(min=0, max=1023).astype(int),\n                    'image_id': image_ids[i],\n                })","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Evaluation\n\nHere I have used [really good evaluation scripts](https://www.kaggle.com/pestipeti/competition-metric-details-script) by [Peter](https://www.kaggle.com/pestipeti), I recommend to use it! ","execution_count":null},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport numba\nimport re\nimport cv2\nimport ast\nimport matplotlib.pyplot as plt\n\nfrom numba import jit\nfrom typing import List, Union, Tuple\n\n\n@jit(nopython=True)\ndef calculate_iou(gt, pr, form='pascal_voc') -> float:\n    \"\"\"Calculates the Intersection over Union.\n\n    Args:\n        gt: (np.ndarray[Union[int, float]]) coordinates of the ground-truth box\n        pr: (np.ndarray[Union[int, float]]) coordinates of the prdected box\n        form: (str) gt/pred coordinates format\n            - pascal_voc: [xmin, ymin, xmax, ymax]\n            - coco: [xmin, ymin, w, h]\n    Returns:\n        (float) Intersection over union (0.0 <= iou <= 1.0)\n    \"\"\"\n    if form == 'coco':\n        gt = gt.copy()\n        pr = pr.copy()\n\n        gt[2] = gt[0] + gt[2]\n        gt[3] = gt[1] + gt[3]\n        pr[2] = pr[0] + pr[2]\n        pr[3] = pr[1] + pr[3]\n\n    # Calculate overlap area\n    dx = min(gt[2], pr[2]) - max(gt[0], pr[0]) + 1\n    \n    if dx < 0:\n        return 0.0\n    \n    dy = min(gt[3], pr[3]) - max(gt[1], pr[1]) + 1\n\n    if dy < 0:\n        return 0.0\n\n    overlap_area = dx * dy\n\n    # Calculate union area\n    union_area = (\n            (gt[2] - gt[0] + 1) * (gt[3] - gt[1] + 1) +\n            (pr[2] - pr[0] + 1) * (pr[3] - pr[1] + 1) -\n            overlap_area\n    )\n\n    return overlap_area / union_area\n\n\n@jit(nopython=True)\ndef find_best_match(gts, pred, pred_idx, threshold = 0.5, form = 'pascal_voc', ious=None) -> int:\n    \"\"\"Returns the index of the 'best match' between the\n    ground-truth boxes and the prediction. The 'best match'\n    is the highest IoU. (0.0 IoUs are ignored).\n\n    Args:\n        gts: (List[List[Union[int, float]]]) Coordinates of the available ground-truth boxes\n        pred: (List[Union[int, float]]) Coordinates of the predicted box\n        pred_idx: (int) Index of the current predicted box\n        threshold: (float) Threshold\n        form: (str) Format of the coordinates\n        ious: (np.ndarray) len(gts) x len(preds) matrix for storing calculated ious.\n\n    Return:\n        (int) Index of the best match GT box (-1 if no match above threshold)\n    \"\"\"\n    best_match_iou = -np.inf\n    best_match_idx = -1\n\n    for gt_idx in range(len(gts)):\n        \n        if gts[gt_idx][0] < 0:\n            # Already matched GT-box\n            continue\n        \n        iou = -1 if ious is None else ious[gt_idx][pred_idx]\n\n        if iou < 0:\n            iou = calculate_iou(gts[gt_idx], pred, form=form)\n            \n            if ious is not None:\n                ious[gt_idx][pred_idx] = iou\n\n        if iou < threshold:\n            continue\n\n        if iou > best_match_iou:\n            best_match_iou = iou\n            best_match_idx = gt_idx\n\n    return best_match_idx\n\n@jit(nopython=True)\ndef calculate_precision(gts, preds, threshold = 0.5, form = 'coco', ious=None) -> float:\n    \"\"\"Calculates precision for GT - prediction pairs at one threshold.\n\n    Args:\n        gts: (List[List[Union[int, float]]]) Coordinates of the available ground-truth boxes\n        preds: (List[List[Union[int, float]]]) Coordinates of the predicted boxes,\n               sorted by confidence value (descending)\n        threshold: (float) Threshold\n        form: (str) Format of the coordinates\n        ious: (np.ndarray) len(gts) x len(preds) matrix for storing calculated ious.\n\n    Return:\n        (float) Precision\n    \"\"\"\n    n = len(preds)\n    tp = 0\n    fp = 0\n    \n    # for pred_idx, pred in enumerate(preds_sorted):\n    for pred_idx in range(n):\n\n        best_match_gt_idx = find_best_match(gts, preds[pred_idx], pred_idx,\n                                            threshold=threshold, form=form, ious=ious)\n\n        if best_match_gt_idx >= 0:\n            # True positive: The predicted box matches a gt box with an IoU above the threshold.\n            tp += 1\n            # Remove the matched GT box\n            gts[best_match_gt_idx] = -1\n\n        else:\n            # No match\n            # False positive: indicates a predicted box had no associated gt box.\n            fp += 1\n\n    # False negative: indicates a gt box had no associated predicted box.\n    fn = (gts.sum(axis=1) > 0).sum()\n\n    return tp / (tp + fp + fn)\n\n\n@jit(nopython=True)\ndef calculate_image_precision(gts, preds, thresholds = (0.5, ), form = 'coco') -> float:\n    \"\"\"Calculates image precision.\n\n    Args:\n        gts: (List[List[Union[int, float]]]) Coordinates of the available ground-truth boxes\n        preds: (List[List[Union[int, float]]]) Coordinates of the predicted boxes,\n               sorted by confidence value (descending)\n        thresholds: (float) Different thresholds\n        form: (str) Format of the coordinates\n\n    Return:\n        (float) Precision\n    \"\"\"\n    n_threshold = len(thresholds)\n    image_precision = 0.0\n    \n    ious = np.ones((len(gts), len(preds))) * -1\n    # ious = None\n\n    for threshold in thresholds:\n        precision_at_threshold = calculate_precision(gts.copy(), preds, threshold=threshold,\n                                                     form=form, ious=ious)\n        image_precision += precision_at_threshold / n_threshold\n\n    return image_precision\n\ndef show_result(sample_id, preds, gt_boxes):\n    sample = cv2.imread(f'{TRAIN_ROOT_PATH}/{sample_id}.jpg', cv2.IMREAD_COLOR)\n    sample = cv2.cvtColor(sample, cv2.COLOR_BGR2RGB)\n\n    fig, ax = plt.subplots(1, 1, figsize=(16, 8))\n\n    for pred_box in preds:\n        cv2.rectangle(\n            sample,\n            (pred_box[0], pred_box[1]),\n            (pred_box[2], pred_box[3]),\n            (220, 0, 0), 2\n        )\n\n    for gt_box in gt_boxes:    \n        cv2.rectangle(\n            sample,\n            (gt_box[0], gt_box[1]),\n            (gt_box[2], gt_box[3]),\n            (0, 0, 220), 2\n        )\n\n    ax.set_axis_off()\n    ax.imshow(sample)\n    ax.set_title(\"RED: Predicted | BLUE - Ground-truth\")\n    \n# Numba typed list!\niou_thresholds = numba.typed.List()\n\nfor x in [0.5, 0.55, 0.6, 0.65, 0.7, 0.75]:\n    iou_thresholds.append(x)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def calculate_final_score(all_predictions, score_threshold):\n    final_scores = []\n    for i in range(len(all_predictions)):\n        gt_boxes = all_predictions[i]['gt_boxes'].copy()\n        pred_boxes = all_predictions[i]['pred_boxes'].copy()\n        scores = all_predictions[i]['scores'].copy()\n        image_id = all_predictions[i]['image_id']\n\n        indexes = np.where(scores>score_threshold)\n        pred_boxes = pred_boxes[indexes]\n        scores = scores[indexes]\n\n        image_precision = calculate_image_precision(gt_boxes, pred_boxes,thresholds=iou_thresholds,form='pascal_voc')\n        final_scores.append(image_precision)\n\n    return np.mean(final_scores)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Search best threshold for best score:","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\n\nbest_final_score, best_score_threshold = 0, 0\nfor score_threshold in tqdm(np.arange(0, 1, 0.01), total=np.arange(0, 1, 0.01).shape[0]):\n    final_score = calculate_final_score(all_predictions, score_threshold)\n    if final_score > best_final_score:\n        best_final_score = final_score\n        best_score_threshold = score_threshold","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print('-'*30)\nprint(f'[Best Score Threshold]: {best_score_threshold}')\nprint(f'[OOF Score]: {best_final_score:.4f}')\nprint('-'*30)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"i = 0\n\ngt_boxes = all_predictions[i]['gt_boxes'].copy()\npred_boxes = all_predictions[i]['pred_boxes'].copy()\nscores = all_predictions[i]['scores'].copy()\nimage_id = all_predictions[i]['image_id']\n\nindexes = np.where(scores>best_score_threshold)\npred_boxes = pred_boxes[indexes]\nscores = scores[indexes]\n\nshow_result(image_id, pred_boxes, gt_boxes)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"I understand that you need time for running your own models, so I don't share dataset of models with folds. But if you need their for your own experiments - write it in comments!","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Inference","execution_count":null},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"DATA_ROOT_PATH = '../input/global-wheat-detection/test'\n\nclass TestDatasetRetriever(Dataset):\n\n    def __init__(self, image_ids, transforms=None):\n        super().__init__()\n        self.image_ids = image_ids\n        self.transforms = transforms\n\n    def __getitem__(self, index: int):\n        image_id = self.image_ids[index]\n        image = cv2.imread(f'{DATA_ROOT_PATH}/{image_id}.jpg', cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        image /= 255.0\n        if self.transforms:\n            sample = {'image': image}\n            sample = self.transforms(**sample)\n            image = sample['image']\n        return image, image_id\n\n    def __len__(self) -> int:\n        return self.image_ids.shape[0]\n\ndef get_valid_transforms():\n    return A.Compose(\n        [\n            A.Resize(height=512, width=512, p=1.0),\n            ToTensorV2(p=1.0),\n        ], \n        p=1.0, \n    )\n\ndataset = TestDatasetRetriever(\n    image_ids=np.array([path.split('/')[-1][:-4] for path in glob(f'{DATA_ROOT_PATH}/*.jpg')]),\n    transforms=get_valid_transforms()\n)\n\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\ndata_loader = DataLoader(\n    dataset,\n    batch_size=2,\n    shuffle=False,\n    num_workers=4,\n    drop_last=False,\n    collate_fn=collate_fn\n)\n\ndef make_predictions(images, score_threshold=0.1):\n    images = torch.stack(images).cuda().float()\n    predictions = []\n    for net in models:\n        with torch.no_grad():\n            det = net(images, torch.tensor([1]*images.shape[0]).float().cuda())\n            result = []\n            for i in range(images.shape[0]):\n                boxes = det[i].detach().cpu().numpy()[:,:4]    \n                scores = det[i].detach().cpu().numpy()[:,4]\n                indexes = np.where(scores > score_threshold)[0]\n                boxes = boxes[indexes]\n                boxes[:, 2] = boxes[:, 2] + boxes[:, 0]\n                boxes[:, 3] = boxes[:, 3] + boxes[:, 1]\n                result.append({\n                    'boxes': boxes[indexes],\n                    'scores': scores[indexes],\n                })\n            predictions.append(result)\n    return predictions\n\n\ndef run_wbf(predictions, image_index, image_size=512, iou_thr=0.55, skip_box_thr=0.45, weights=None):\n    boxes = [(prediction[image_index]['boxes']/(image_size-1)).tolist()  for prediction in predictions]\n    scores = [prediction[image_index]['scores'].tolist()  for prediction in predictions]\n    labels = [np.ones(prediction[image_index]['scores'].shape[0]).tolist() for prediction in predictions]\n    boxes, scores, labels = weighted_boxes_fusion(boxes, scores, labels, weights=None, iou_thr=iou_thr, skip_box_thr=skip_box_thr)\n    boxes = boxes*(image_size-1)\n    return boxes, scores, labels\n\n\ndef format_prediction_string(boxes, scores):\n    pred_strings = []\n    for j in zip(scores, boxes):\n        pred_strings.append(\"{0:.4f} {1} {2} {3} {4}\".format(j[0], j[1][0], j[1][1], j[1][2], j[1][3]))\n    return \" \".join(pred_strings)\n\n\nresults = []\n\nfor images, image_ids in data_loader:\n    predictions = make_predictions(images)\n    for i, image in enumerate(images):\n        boxes, scores, labels = run_wbf(predictions, image_index=i)\n        boxes = (boxes*2).astype(np.int32).clip(min=0, max=1023)\n        image_id = image_ids[i]\n        \n        boxes[:, 2] = boxes[:, 2] - boxes[:, 0]\n        boxes[:, 3] = boxes[:, 3] - boxes[:, 1]\n\n        result = {\n            'image_id': image_id,\n            'PredictionString': format_prediction_string(boxes, scores)\n        }\n        results.append(result)\n        \n        \ntest_df = pd.DataFrame(results, columns=['image_id', 'PredictionString'])\ntest_df.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Thank you for reading my kernel!","execution_count":null}],"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":4,"nbformat_minor":4}