def __call__(self, results): gt_bboxes = results['gt_bboxes'] if len(gt_bboxes) <= self.n: return results if 'gt_masks' in results: areas = results['gt_masks'].areas else: areas = bt.bbox_areas(gt_bboxes) index = np.argsort(areas)[:self.n] results['gt_bboxes'] = gt_bboxes[index] if 'gt_labels' in results: results['gt_labels'] = results['gt_labels'][index] if 'gt_masks' in results: results['gt_masks'] = results['gt_masks'][index] return results
def __call__(self, results): for k in ['gt_bboxes', 'gt_masks', 'gt_labels']: if k in results: num_objs = len(results[k]) break else: return results ignore = np.zeros((num_objs, ), dtype=np.bool) if self.ignore_diff: assert 'diffs' in results diffs = results['diffs'] ignore[diffs == 1] = True if self.ignore_truncated: assert 'trunc' in results trunc = results['trunc'] ignore[trunc == 1] = True if self.ignore_size: bboxes = results['gt_bboxes'] wh = bboxes[:, 2:] - bboxes[:, :2] ignore[np.min(wh, axis=1) < self.ignore_size] = True if self.ignore_real_scales: assert len(self.ignore_real_scales) == (len(results['split_sizes']) * len(results['split_rates'])) polys = mask2bbox(results['gt_masks'], 'poly') if 'scale_factor' in results: scale_factor = np.tile(results['scale_factor'], 2) polys = polys / scale_factor bbox_scales = np.sqrt(bt.bbox_areas(polys)) split_sizes=[] for rate in results['split_rates']: split_sizes += [int(size / rate) for size in results['split_sizes']] img_scale = results['img_info']['width'] scale_ratio = np.array(split_sizes) / img_scale inds = np.argmin(abs(np.log(scale_ratio))) min_scale, max_scale = self.ignore_real_scales[inds] if min_scale is None: min_scale = 0 if max_scale is None: max_scale = np.inf ignore[bbox_scales < min_scale] = True ignore[bbox_scales > max_scale] = True if 'gt_bboxes' in results: bboxes = results['gt_bboxes'] gt_bboxes = bboxes[~ignore] gt_bboxes_ignore = bboxes[ignore] results['gt_bboxes'] = gt_bboxes results['gt_bboxes_ignore'] = gt_bboxes_ignore if 'gt_bboxes_ignore' not in results['bbox_fields']: results['bbox_fields'].append('gt_bboxes_ignore') if 'gt_masks' in results: gt_inds = np.nonzero(~ignore)[0] ignore_inds = np.nonzero(ignore)[0] if isinstance(results['gt_masks'], PolygonMasks) \ and len(gt_inds) == 0: height = results['gt_masks'].height width = results['gt_masks'].width gt_masks = PolygonMasks([], height, width) else: gt_masks = results['gt_masks'][gt_inds] if isinstance(results['gt_masks'], PolygonMasks) \ and len(ignore_inds) == 0: height = results['gt_masks'].height width = results['gt_masks'].width gt_masks_ignore = PolygonMasks([], height, width) else: gt_masks_ignore = results['gt_masks'][ignore_inds] results['gt_masks'] = gt_masks results['gt_masks_ignore'] = gt_masks_ignore if 'gt_masks_ignore' not in results['mask_fields']: results['mask_fields'].append('gt_masks_ignore') if 'gt_labels' in results: results['gt_labels'] = results['gt_labels'][~ignore] for k in results.get('aligned_fields', []): results[k] = results[k][~ignore] return results
def eval_arb_map(det_results, annotations, scale_ranges=None, iou_thr=0.5, use_07_metric=True, dataset=None, logger=None, nproc=4): """Evaluate mAP of a dataset. Args: det_results (list[list]): [[cls1_det, cls2_det, ...], ...]. The outer list indicates images, and the inner list indicates per-class detected bboxes. annotations (list[dict]): Ground truth annotations where each item of the list indicates an image. Keys of annotations are: - `bboxes`: numpy array of shape (n, 4) - `labels`: numpy array of shape (n, ) - `bboxes_ignore` (optional): numpy array of shape (k, 4) - `labels_ignore` (optional): numpy array of shape (k, ) scale_ranges (list[tuple] | None): Range of scales to be evaluated, in the format [(min1, max1), (min2, max2), ...]. A range of (32, 64) means the area range between (32**2, 64**2). Default: None. iou_thr (float): IoU threshold to be considered as matched. Default: 0.5. dataset (list[str] | str | None): Dataset name or dataset classes, there are minor differences in metrics for different datsets, e.g. "voc07", "imagenet_det", etc. Default: None. logger (logging.Logger | str | None): The way to print the mAP summary. See `mmdet.utils.print_log()` for details. Default: None. nproc (int): Processes used for computing TP and FP. Default: 4. Returns: tuple: (mAP, [dict, dict, ...]) """ assert len(det_results) == len(annotations) num_imgs = len(det_results) num_scales = len(scale_ranges) if scale_ranges is not None else 1 num_classes = len(det_results[0]) # positive class num area_ranges = ([(rg[0]**2, rg[1]**2) for rg in scale_ranges] if scale_ranges is not None else None) pool = Pool(nproc) eval_results = [] for i in range(num_classes): # get gt and det bboxes of this class cls_dets, cls_gts, cls_gts_ignore = get_cls_results( det_results, annotations, i) # compute tp and fp for each image with multiple processes tpfp = pool.starmap( tpfp_default, zip(cls_dets, cls_gts, cls_gts_ignore, [iou_thr for _ in range(num_imgs)], [area_ranges for _ in range(num_imgs)])) tp, fp = tuple(zip(*tpfp)) # calculate gt number of each scale # ignored gts or gts beyond the specific scale are not counted num_gts = np.zeros(num_scales, dtype=int) for j, bbox in enumerate(cls_gts): if area_ranges is None: num_gts[0] += bbox.shape[0] else: gt_areas = bt.bbox_areas( bbox) #--------------------------------------- # gt_areas = (bbox[:, 2] - bbox[:, 0]) * ( # bbox[:, 3] - bbox[:, 1]) for k, (min_area, max_area) in enumerate(area_ranges): num_gts[k] += np.sum((gt_areas >= min_area) & (gt_areas < max_area)) # sort all det bboxes by score, also sort tp and fp cls_dets = np.vstack(cls_dets) num_dets = cls_dets.shape[0] sort_inds = np.argsort(-cls_dets[:, -1]) tp = np.hstack(tp)[:, sort_inds] fp = np.hstack(fp)[:, sort_inds] # calculate recall and precision with tp and fp tp = np.cumsum(tp, axis=1) fp = np.cumsum(fp, axis=1) eps = np.finfo(np.float32).eps recalls = tp / np.maximum(num_gts[:, np.newaxis], eps) precisions = tp / np.maximum((tp + fp), eps) # calculate AP if scale_ranges is None: recalls = recalls[0, :] precisions = precisions[0, :] num_gts = num_gts.item() mode = 'area' if not use_07_metric else '11points' ap = average_precision(recalls, precisions, mode) eval_results.append({ 'num_gts': num_gts, 'num_dets': num_dets, 'recall': recalls, 'precision': precisions, 'ap': ap }) pool.close() if scale_ranges is not None: # shape (num_classes, num_scales) all_ap = np.vstack([cls_result['ap'] for cls_result in eval_results]) all_num_gts = np.vstack( [cls_result['num_gts'] for cls_result in eval_results]) mean_ap = [] for i in range(num_scales): if np.any(all_num_gts[:, i] > 0): mean_ap.append(all_ap[all_num_gts[:, i] > 0, i].mean()) else: mean_ap.append(0.0) else: aps = [] for cls_result in eval_results: if cls_result['num_gts'] > 0: aps.append(cls_result['ap']) mean_ap = np.array(aps).mean().item() if aps else 0.0 print_map_summary(mean_ap, eval_results, dataset, area_ranges, logger=logger) return mean_ap, eval_results
def tpfp_default(det_bboxes, gt_bboxes, gt_bboxes_ignore=None, iou_thr=0.5, area_ranges=None): """Check if detected bboxes are true positive or false positive. Args: det_bbox (ndarray): Detected bboxes of this image, of shape (m, 5). gt_bboxes (ndarray): GT bboxes of this image, of shape (n, 4). gt_bboxes_ignore (ndarray): Ignored gt bboxes of this image, of shape (k, 4). Default: None iou_thr (float): IoU threshold to be considered as matched. Default: 0.5. area_ranges (list[tuple] | None): Range of bbox areas to be evaluated, in the format [(min1, max1), (min2, max2), ...]. Default: None. Returns: tuple[np.ndarray]: (tp, fp) whose elements are 0 and 1. The shape of each array is (num_scales, m). """ # an indicator of ignored gts gt_ignore_inds = np.concatenate((np.zeros(gt_bboxes.shape[0], dtype=np.bool), np.ones(gt_bboxes_ignore.shape[0], dtype=np.bool))) # stack gt_bboxes and gt_bboxes_ignore for convenience gt_bboxes = np.vstack((gt_bboxes, gt_bboxes_ignore)) num_dets = det_bboxes.shape[0] num_gts = gt_bboxes.shape[0] if area_ranges is None: area_ranges = [(None, None)] num_scales = len(area_ranges) # tp and fp are of shape (num_scales, num_gts), each row is tp or fp of # a certain scale tp = np.zeros((num_scales, num_dets), dtype=np.float32) fp = np.zeros((num_scales, num_dets), dtype=np.float32) # if there is no gt bboxes in this image, then all det bboxes # within area range are false positives if gt_bboxes.shape[0] == 0: if area_ranges == [(None, None)]: fp[...] = 1 else: det_areas = bt.bbox_areas( det_bboxes[:, :-1] ) #------------------------------------------------- for i, (min_area, max_area) in enumerate(area_ranges): fp[i, (det_areas >= min_area) & (det_areas < max_area)] = 1 return tp, fp ious = bt.bbox_overlaps(det_bboxes[:, :-1], gt_bboxes) #------------------------------ # for each det, the max iou with all gts ious_max = ious.max(axis=1) # for each det, which gt overlaps most with it ious_argmax = ious.argmax(axis=1) # sort all dets in descending order by scores sort_inds = np.argsort(-det_bboxes[:, -1]) for k, (min_area, max_area) in enumerate(area_ranges): gt_covered = np.zeros(num_gts, dtype=bool) # if no area range is specified, gt_area_ignore is all False if min_area is None: gt_area_ignore = np.zeros_like(gt_ignore_inds, dtype=bool) else: gt_areas = bt.bbox_areas( gt_bboxes) #------------------------------------ gt_area_ignore = (gt_areas < min_area) | (gt_areas >= max_area) for i in sort_inds: if ious_max[i] >= iou_thr: matched_gt = ious_argmax[i] if not (gt_ignore_inds[matched_gt] or gt_area_ignore[matched_gt]): if not gt_covered[matched_gt]: gt_covered[matched_gt] = True tp[k, i] = 1 else: fp[k, i] = 1 # otherwise ignore this detected bbox, tp = 0, fp = 0 elif min_area is None: fp[k, i] = 1 else: bbox = det_bboxes[i:i + 1, :-1] area = bt.bbox_areas( bbox) #-------------------------------------------- if area >= min_area and area < max_area: fp[k, i] = 1 return tp, fp
def format_results(self, results, with_merge=True, ign_scale_ranges=None, iou_thr=0.5, nproc=4, save_dir=None, **kwargs): nproc = min(nproc, os.cpu_count()) task = self.task if mmcv.is_list_of(results, tuple): dets, segments = results if task == 'Task1': dets = _list_mask_2_obb(dets, segments) else: dets = results if not with_merge: results = [(data_info['id'], result) for data_info, result in zip(self.data_infos, results)] if save_dir is not None: id_list, dets_list = zip(*results) bt.save_dota_submission(save_dir, id_list, dets_list, task, self.CLASSES) return results print('\nMerging patch bboxes into full image!!!') start_time = time.time() if ign_scale_ranges is not None: assert len(ign_scale_ranges) == (len(self.split_info['rates']) * len(self.split_info['sizes'])) split_sizes = [] for rate in self.split_info['rates']: split_sizes += [ int(size / rate) for size in self.split_info['sizes'] ] collector = defaultdict(list) for data_info, result in zip(self.data_infos, dets): if ign_scale_ranges is not None: img_scale = data_info['width'] scale_ratio = np.array(split_sizes) / img_scale inds = np.argmin(abs(np.log(scale_ratio))) min_scale, max_scale = ign_scale_ranges[inds] min_scale = 0 if min_scale is None else min_scale max_scale = np.inf if max_scale is None else max_scale x_start, y_start = data_info['x_start'], data_info['y_start'] new_result = [] for i, dets in enumerate(result): if ign_scale_ranges is not None: bbox_scales = np.sqrt(bt.bbox_areas(dets[:, :-1])) valid_inds = (bbox_scales > min_scale) & (bbox_scales < max_scale) dets = dets[valid_inds] bboxes, scores = dets[:, :-1], dets[:, [-1]] bboxes = bt.translate(bboxes, x_start, y_start) labels = np.zeros((bboxes.shape[0], 1)) + i new_result.append( np.concatenate([labels, bboxes, scores], axis=1)) new_result = np.concatenate(new_result, axis=0) collector[data_info['ori_id']].append(new_result) merge_func = partial(_merge_func, CLASSES=self.CLASSES, iou_thr=iou_thr, task=task) if nproc <= 1: print('Single processing') merged_results = mmcv.track_iter_progress( (map(merge_func, collector.items()), len(collector))) else: print('Multiple processing') merged_results = mmcv.track_parallel_progress( merge_func, list(collector.items()), nproc) if save_dir is not None: id_list, dets_list = zip(*merged_results) bt.save_dota_submission(save_dir, id_list, dets_list, task, self.CLASSES) stop_time = time.time() print('Used time: %.1f s' % (stop_time - start_time)) return merged_results