| |
| import copy |
| import os |
| from argparse import ArgumentParser |
| from multiprocessing import Pool |
|
|
| import matplotlib.pyplot as plt |
| import numpy as np |
| from pycocotools.coco import COCO |
| from pycocotools.cocoeval import COCOeval |
|
|
|
|
| def makeplot(rs, ps, outDir, class_name, iou_type): |
| cs = np.vstack([ |
| np.ones((2, 3)), |
| np.array([0.31, 0.51, 0.74]), |
| np.array([0.75, 0.31, 0.30]), |
| np.array([0.36, 0.90, 0.38]), |
| np.array([0.50, 0.39, 0.64]), |
| np.array([1, 0.6, 0]), |
| ]) |
| areaNames = ['allarea', 'small', 'medium', 'large'] |
| types = ['C75', 'C50', 'Loc', 'Sim', 'Oth', 'BG', 'FN'] |
| for i in range(len(areaNames)): |
| area_ps = ps[..., i, 0] |
| figure_title = iou_type + '-' + class_name + '-' + areaNames[i] |
| aps = [ps_.mean() for ps_ in area_ps] |
| ps_curve = [ |
| ps_.mean(axis=1) if ps_.ndim > 1 else ps_ for ps_ in area_ps |
| ] |
| ps_curve.insert(0, np.zeros(ps_curve[0].shape)) |
| fig = plt.figure() |
| ax = plt.subplot(111) |
| for k in range(len(types)): |
| ax.plot(rs, ps_curve[k + 1], color=[0, 0, 0], linewidth=0.5) |
| ax.fill_between( |
| rs, |
| ps_curve[k], |
| ps_curve[k + 1], |
| color=cs[k], |
| label=str(f'[{aps[k]:.3f}]' + types[k]), |
| ) |
| plt.xlabel('recall') |
| plt.ylabel('precision') |
| plt.xlim(0, 1.0) |
| plt.ylim(0, 1.0) |
| plt.title(figure_title) |
| plt.legend() |
| |
| fig.savefig(outDir + f'/{figure_title}.png') |
| plt.close(fig) |
|
|
|
|
| def autolabel(ax, rects): |
| """Attach a text label above each bar in *rects*, displaying its height.""" |
| for rect in rects: |
| height = rect.get_height() |
| if height > 0 and height <= 1: |
| text_label = '{:2.0f}'.format(height * 100) |
| else: |
| text_label = '{:2.0f}'.format(height) |
| ax.annotate( |
| text_label, |
| xy=(rect.get_x() + rect.get_width() / 2, height), |
| xytext=(0, 3), |
| textcoords='offset points', |
| ha='center', |
| va='bottom', |
| fontsize='x-small', |
| ) |
|
|
|
|
| def makebarplot(rs, ps, outDir, class_name, iou_type): |
| areaNames = ['allarea', 'small', 'medium', 'large'] |
| types = ['C75', 'C50', 'Loc', 'Sim', 'Oth', 'BG', 'FN'] |
| fig, ax = plt.subplots() |
| x = np.arange(len(areaNames)) |
| width = 0.60 |
| rects_list = [] |
| figure_title = iou_type + '-' + class_name + '-' + 'ap bar plot' |
| for i in range(len(types) - 1): |
| type_ps = ps[i, ..., 0] |
| aps = [ps_.mean() for ps_ in type_ps.T] |
| rects_list.append( |
| ax.bar( |
| x - width / 2 + (i + 1) * width / len(types), |
| aps, |
| width / len(types), |
| label=types[i], |
| )) |
|
|
| |
| ax.set_ylabel('Mean Average Precision (mAP)') |
| ax.set_title(figure_title) |
| ax.set_xticks(x) |
| ax.set_xticklabels(areaNames) |
| ax.legend() |
|
|
| |
| for rects in rects_list: |
| autolabel(ax, rects) |
|
|
| |
| fig.savefig(outDir + f'/{figure_title}.png') |
| plt.close(fig) |
|
|
|
|
| def get_gt_area_group_numbers(cocoEval): |
| areaRng = cocoEval.params.areaRng |
| areaRngStr = [str(aRng) for aRng in areaRng] |
| areaRngLbl = cocoEval.params.areaRngLbl |
| areaRngStr2areaRngLbl = dict(zip(areaRngStr, areaRngLbl)) |
| areaRngLbl2Number = dict.fromkeys(areaRngLbl, 0) |
| for evalImg in cocoEval.evalImgs: |
| if evalImg: |
| for gtIgnore in evalImg['gtIgnore']: |
| if not gtIgnore: |
| aRngLbl = areaRngStr2areaRngLbl[str(evalImg['aRng'])] |
| areaRngLbl2Number[aRngLbl] += 1 |
| return areaRngLbl2Number |
|
|
|
|
| def make_gt_area_group_numbers_plot(cocoEval, outDir, verbose=True): |
| areaRngLbl2Number = get_gt_area_group_numbers(cocoEval) |
| areaRngLbl = areaRngLbl2Number.keys() |
| if verbose: |
| print('number of annotations per area group:', areaRngLbl2Number) |
|
|
| |
| fig, ax = plt.subplots() |
| x = np.arange(len(areaRngLbl)) |
| width = 0.60 |
| figure_title = 'number of annotations per area group' |
|
|
| rects = ax.bar(x, areaRngLbl2Number.values(), width) |
|
|
| |
| ax.set_ylabel('Number of annotations') |
| ax.set_title(figure_title) |
| ax.set_xticks(x) |
| ax.set_xticklabels(areaRngLbl) |
|
|
| |
| autolabel(ax, rects) |
|
|
| |
| fig.tight_layout() |
| fig.savefig(outDir + f'/{figure_title}.png') |
| plt.close(fig) |
|
|
|
|
| def make_gt_area_histogram_plot(cocoEval, outDir): |
| n_bins = 100 |
| areas = [ann['area'] for ann in cocoEval.cocoGt.anns.values()] |
|
|
| |
| figure_title = 'gt annotation areas histogram plot' |
| fig, ax = plt.subplots() |
|
|
| |
| ax.hist(np.sqrt(areas), bins=n_bins) |
|
|
| |
| ax.set_xlabel('Squareroot Area') |
| ax.set_ylabel('Number of annotations') |
| ax.set_title(figure_title) |
|
|
| |
| fig.tight_layout() |
| fig.savefig(outDir + f'/{figure_title}.png') |
| plt.close(fig) |
|
|
|
|
| def analyze_individual_category(k, |
| cocoDt, |
| cocoGt, |
| catId, |
| iou_type, |
| areas=None): |
| nm = cocoGt.loadCats(catId)[0] |
| print(f'--------------analyzing {k + 1}-{nm["name"]}---------------') |
| ps_ = {} |
| dt = copy.deepcopy(cocoDt) |
| nm = cocoGt.loadCats(catId)[0] |
| imgIds = cocoGt.getImgIds() |
| dt_anns = dt.dataset['annotations'] |
| select_dt_anns = [] |
| for ann in dt_anns: |
| if ann['category_id'] == catId: |
| select_dt_anns.append(ann) |
| dt.dataset['annotations'] = select_dt_anns |
| dt.createIndex() |
| |
| gt = copy.deepcopy(cocoGt) |
| child_catIds = gt.getCatIds(supNms=[nm['supercategory']]) |
| for idx, ann in enumerate(gt.dataset['annotations']): |
| if ann['category_id'] in child_catIds and ann['category_id'] != catId: |
| gt.dataset['annotations'][idx]['ignore'] = 1 |
| gt.dataset['annotations'][idx]['iscrowd'] = 1 |
| gt.dataset['annotations'][idx]['category_id'] = catId |
| cocoEval = COCOeval(gt, copy.deepcopy(dt), iou_type) |
| cocoEval.params.imgIds = imgIds |
| cocoEval.params.maxDets = [100] |
| cocoEval.params.iouThrs = [0.1] |
| cocoEval.params.useCats = 1 |
| if areas: |
| cocoEval.params.areaRng = [[0**2, areas[2]], [0**2, areas[0]], |
| [areas[0], areas[1]], [areas[1], areas[2]]] |
| cocoEval.evaluate() |
| cocoEval.accumulate() |
| ps_supercategory = cocoEval.eval['precision'][0, :, k, :, :] |
| ps_['ps_supercategory'] = ps_supercategory |
| |
| gt = copy.deepcopy(cocoGt) |
| for idx, ann in enumerate(gt.dataset['annotations']): |
| if ann['category_id'] != catId: |
| gt.dataset['annotations'][idx]['ignore'] = 1 |
| gt.dataset['annotations'][idx]['iscrowd'] = 1 |
| gt.dataset['annotations'][idx]['category_id'] = catId |
| cocoEval = COCOeval(gt, copy.deepcopy(dt), iou_type) |
| cocoEval.params.imgIds = imgIds |
| cocoEval.params.maxDets = [100] |
| cocoEval.params.iouThrs = [0.1] |
| cocoEval.params.useCats = 1 |
| if areas: |
| cocoEval.params.areaRng = [[0**2, areas[2]], [0**2, areas[0]], |
| [areas[0], areas[1]], [areas[1], areas[2]]] |
| cocoEval.evaluate() |
| cocoEval.accumulate() |
| ps_allcategory = cocoEval.eval['precision'][0, :, k, :, :] |
| ps_['ps_allcategory'] = ps_allcategory |
| return k, ps_ |
|
|
|
|
| def analyze_results(res_file, |
| ann_file, |
| res_types, |
| out_dir, |
| extraplots=None, |
| areas=None): |
| for res_type in res_types: |
| assert res_type in ['bbox', 'segm'] |
| if areas: |
| assert len(areas) == 3, '3 integers should be specified as areas, \ |
| representing 3 area regions' |
|
|
| directory = os.path.dirname(out_dir + '/') |
| if not os.path.exists(directory): |
| print(f'-------------create {out_dir}-----------------') |
| os.makedirs(directory) |
|
|
| cocoGt = COCO(ann_file) |
| cocoDt = cocoGt.loadRes(res_file) |
| imgIds = cocoGt.getImgIds() |
| for res_type in res_types: |
| res_out_dir = out_dir + '/' + res_type + '/' |
| res_directory = os.path.dirname(res_out_dir) |
| if not os.path.exists(res_directory): |
| print(f'-------------create {res_out_dir}-----------------') |
| os.makedirs(res_directory) |
| iou_type = res_type |
| cocoEval = COCOeval( |
| copy.deepcopy(cocoGt), copy.deepcopy(cocoDt), iou_type) |
| cocoEval.params.imgIds = imgIds |
| cocoEval.params.iouThrs = [0.75, 0.5, 0.1] |
| cocoEval.params.maxDets = [100] |
| if areas: |
| cocoEval.params.areaRng = [[0**2, areas[2]], [0**2, areas[0]], |
| [areas[0], areas[1]], |
| [areas[1], areas[2]]] |
| cocoEval.evaluate() |
| cocoEval.accumulate() |
| ps = cocoEval.eval['precision'] |
| ps = np.vstack([ps, np.zeros((4, *ps.shape[1:]))]) |
| catIds = cocoGt.getCatIds() |
| recThrs = cocoEval.params.recThrs |
| with Pool(processes=48) as pool: |
| args = [(k, cocoDt, cocoGt, catId, iou_type, areas) |
| for k, catId in enumerate(catIds)] |
| analyze_results = pool.starmap(analyze_individual_category, args) |
| for k, catId in enumerate(catIds): |
| nm = cocoGt.loadCats(catId)[0] |
| print(f'--------------saving {k + 1}-{nm["name"]}---------------') |
| analyze_result = analyze_results[k] |
| assert k == analyze_result[0] |
| ps_supercategory = analyze_result[1]['ps_supercategory'] |
| ps_allcategory = analyze_result[1]['ps_allcategory'] |
| |
| ps[3, :, k, :, :] = ps_supercategory |
| |
| ps[4, :, k, :, :] = ps_allcategory |
| |
| ps[ps == -1] = 0 |
| ps[5, :, k, :, :] = ps[4, :, k, :, :] > 0 |
| ps[6, :, k, :, :] = 1.0 |
| makeplot(recThrs, ps[:, :, k], res_out_dir, nm['name'], iou_type) |
| if extraplots: |
| makebarplot(recThrs, ps[:, :, k], res_out_dir, nm['name'], |
| iou_type) |
| makeplot(recThrs, ps, res_out_dir, 'allclass', iou_type) |
| if extraplots: |
| makebarplot(recThrs, ps, res_out_dir, 'allclass', iou_type) |
| make_gt_area_group_numbers_plot( |
| cocoEval=cocoEval, outDir=res_out_dir, verbose=True) |
| make_gt_area_histogram_plot(cocoEval=cocoEval, outDir=res_out_dir) |
|
|
|
|
| def main(): |
| parser = ArgumentParser(description='COCO Error Analysis Tool') |
| parser.add_argument('result', help='result file (json format) path') |
| parser.add_argument('out_dir', help='dir to save analyze result images') |
| parser.add_argument( |
| '--ann', |
| default='data/coco/annotations/instances_val2017.json', |
| help='annotation file path') |
| parser.add_argument( |
| '--types', type=str, nargs='+', default=['bbox'], help='result types') |
| parser.add_argument( |
| '--extraplots', |
| action='store_true', |
| help='export extra bar/stat plots') |
| parser.add_argument( |
| '--areas', |
| type=int, |
| nargs='+', |
| default=[1024, 9216, 10000000000], |
| help='area regions') |
| args = parser.parse_args() |
| analyze_results( |
| args.result, |
| args.ann, |
| args.types, |
| out_dir=args.out_dir, |
| extraplots=args.extraplots, |
| areas=args.areas) |
|
|
|
|
| if __name__ == '__main__': |
| main() |
|
|