{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"import torch\nimport numpy as np\nimport pandas as pd\nfrom torch import nn\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom tqdm.autonotebook import tqdm","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.read_csv(\"../input/global-wheat-detection/train.csv\")\ndf['bbox'] = df['bbox'].apply(eval)\ndf = df.groupby(\"image_id\")[\"bbox\"].apply(list).reset_index()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def xywh_to_xyxy(boxes):\n    if len(boxes):\n        boxes = torch.tensor(boxes, dtype = torch.float32)\n        boxes[..., [2,3]] = boxes[..., [2,3]] + boxes[..., [0,1]]\n    else:\n        boxes = torch.empty((0, 4))\n    return boxes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"image_id = df.loc[0, \"image_id\"]\nboxes = xywh_to_xyxy(df.loc[0, \"bbox\"])\nplt.figure(figsize = (10, 10))\nplt.imshow(Image.open(f\"../input/global-wheat-detection/train/{image_id}.jpg\"))\nfor bbox in boxes:\n    box = plt.Rectangle(bbox[:2], bbox[2]-bbox[0], bbox[3]-bbox[1], edgecolor = \"white\", fill = False, linewidth = 2)\n    plt.gca().add_patch(box)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Competetion metric\nAssumed that all boxes are sorted by scores. <br>\n<b style = \"color:green; font-size:20px\">Takes 42 secs for 3373 images</b>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class Metric(nn.Module):\n    def __init__(self, start = 0.5, end = 0.75, step = 0.05):\n        super().__init__()\n        self.thresholds = torch.arange(start = start, end = end, step = step)\n\n    def box_area(self, boxes):\n        return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])\n        \n    def box_iou(self, boxes1, boxes2):\n        area1 = self.box_area(boxes1)\n        area2 = self.box_area(boxes2)\n        lt = torch.max(boxes1[:, None, :2], boxes2[:, :2])  # [N,M,2]\n        rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:])  # [N,M,2]\n        wh = (rb - lt).clamp(min=0)  # [N,M,2]\n        inter = wh[:, :, 0] * wh[:, :, 1]  # [N,M]\n        union = area1[:, None] + area2 - inter\n        iou = inter / union\n        return iou\n\n    def find_best_match(self, iou, threshold):\n        return torch.tensor(-1).to(iou) if iou.max().lt(threshold) else iou.argmax()\n\n    def precision(self, pred_boxes, gt_boxes, threshold):\n        ious = self.box_iou(pred_boxes, gt_boxes)\n        fp = 0.0\n        for i in range(ious.size(0)):\n            idx = self.find_best_match(ious[i], threshold)\n            if idx.ge(0):\n                ious[:, idx] = -1\n            else:\n                fp += 1.0\n        tp = ious.min(dim = 0).values.eq(-1).sum()\n        fn = ious.max(dim = 0).values.gt(-1).sum()\n        return tp / (tp + fp + fn)\n\n    def avg_precision(self, pred_boxes, gt_boxes):\n        total_precision = torch.tensor(0.0).to(gt_boxes)\n        for threshold in self.thresholds:\n            total_precision += self.precision(pred_boxes, gt_boxes, threshold)\n        return total_precision / self.thresholds.numel()\n\n    def forward(self, outputs, targets):\n        total = 0.0\n        for output, target in tqdm(zip(outputs, targets), total = len(targets)):\n            if len(output)*len(target) > 0:\n                total += self.avg_precision(output, target).item()\n        return total / len(targets)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"metric = Metric(start = 0.5, end = 0.75, step = 0.05)\nboxes = [xywh_to_xyxy(boxes) for boxes in df.bbox.values]\nmetric(boxes, boxes)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Weighted box fusion\nIoU is not calculating over weighted box<br>\n<b style = \"color:green; font-size:20px\">Takes 2 mins for 3373 images over 5 folds</b>","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class WeightedBoxFusion(nn.Module):\n    def __init__(self, iou_threshold = 0.6, skip_threshold = 0.55):\n        super().__init__()\n        self.iou_threshold = iou_threshold\n        self.skip_threshold = skip_threshold\n\n    def box_area(self, boxes):\n        return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])\n\n    def box_iou(self, boxes1, boxes2):\n        area1 = self.box_area(boxes1)\n        area2 = self.box_area(boxes2)\n        lt = torch.max(boxes1[:, None, :2], boxes2[:, :2])  # [N,M,2]\n        rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:])  # [N,M,2]\n        wh = (rb - lt).clamp(min=0)  # [N,M,2]\n        inter = wh[:, :, 0] * wh[:, :, 1]  # [N,M]\n        union = area1[:, None] + area2 - inter\n        iou = inter / union\n        return iou\n\n    def cluster_boxes(self, boxes, iou_threshold):\n        ious = self.box_iou(boxes, boxes)\n        for i in range(ious.size(0)):\n            init = ious[i, i].item()\n            ious[i, i] = -1\n            args = ious[i].ge(iou_threshold).nonzero().view(-1)\n            ious[args, :] = -1\n            ious[:, args] = -1\n            ious[i, args] = 1\n            ious[i, i] = init\n        ious[ious.ne(1)] = 0\n        return ious.bool()\n\n    def weight_boxes(self, clustered_masks, boxes, scores, weights):\n        weighted_boxes = []\n        weighted_scores = []\n        for mask in clustered_masks:\n            if mask.sum() > 0:\n                weighted_box = (boxes[mask] * scores[mask][..., None]).sum(dim = 0) / scores[mask].sum()\n                weighted_score = scores[mask].sum() / max(weights.sum(), weights.mean() * mask.sum())\n                weighted_boxes.append(weighted_box)\n                weighted_scores.append(weighted_score)\n        if sum(map(len, weighted_boxes)):\n            weighted_boxes = torch.stack(weighted_boxes)\n            weighted_scores = torch.stack(weighted_scores)\n            indices = weighted_scores.argsort(descending = True)\n            weighted_boxes = weighted_boxes[indices]\n            weighted_scores = weighted_scores[indices]\n        else:\n            weighted_boxes = torch.empty((0, 4))\n            weighted_scores = torch.empty((0,))\n        return weighted_boxes, weighted_scores\n\n    def filter_boxes(self, boxes_list, scores_list, weights, skip_threshold):\n        assert len(boxes_list) == len(scores_list) == len(weights)\n        for i, weight in enumerate(weights):\n            mask = scores_list[i] > skip_threshold\n            boxes_list[i] = boxes_list[i][mask]\n            scores_list[i] = scores_list[i][mask]\n            scores_list[i] = scores_list[i] * weight\n        if sum(map(len, boxes_list)):\n            boxes = torch.cat(boxes_list).float()\n            scores = torch.cat(scores_list).float()\n            indices = scores.argsort(descending = True)\n            boxes = boxes[indices]\n            scores = scores[indices]\n        else:\n            boxes = torch.empty((0, 4))\n            scores = torch.empty((0,))\n        return boxes, scores\n\n    def forward(self, boxes_list, scores_list, weights = None):\n        if weights is None: weights = np.ones(len(scores_list))\n        weights = np.array(weights)\n        boxes, scores = self.filter_boxes(boxes_list, scores_list, weights, self.skip_threshold)\n        clustered_masks = self.cluster_boxes(boxes, self.iou_threshold)\n        weighted_boxes, weighted_scores = self.weight_boxes(clustered_masks, boxes, scores, weights)\n        return weighted_boxes, weighted_scores","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"wbf = WeightedBoxFusion(iou_threshold = 0.6, skip_threshold = 0.55)\nboxes = [xywh_to_xyxy(boxes) for boxes in df.bbox.values]\nscores = [torch.ones((len(boxes),)) for boxes in df.bbox.values]\n# WBF over folds (Copyied same boxes 5 times)\nfolds_boxes = [boxes for _ in range(5)]\nfolds_scores = [scores for _ in range(5)]\nwbf_boxes, wbf_scores = [], []\nfor boxes_list, scores_list in tqdm(zip(zip(*folds_boxes), zip(*folds_scores)), total = len(boxes)):\n    img_boxes, img_scores = wbf(list(boxes_list), list(scores_list))\n    wbf_boxes.append(img_boxes); wbf_scores.append(img_scores)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"image_id = df.loc[0, \"image_id\"]\nboxes = wbf_boxes[0]\nplt.figure(figsize = (10, 10))\nplt.imshow(Image.open(f\"../input/global-wheat-detection/train/{image_id}.jpg\"))\nfor bbox in boxes:\n    box = plt.Rectangle(bbox[:2], bbox[2]-bbox[0], bbox[3]-bbox[1], edgecolor = \"white\", fill = False, linewidth = 2)\n    plt.gca().add_patch(box)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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}