123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- # Copyright (c) OpenMMLab. All rights reserved.
- 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()
- # plt.show()
- 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: # for percent values
- 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), # 3 points vertical offset
- 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)) # the areaNames locations
- width = 0.60 # the width of the bars
- 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],
- ))
- # Add some text for labels, title and custom x-axis tick labels, etc.
- ax.set_ylabel('Mean Average Precision (mAP)')
- ax.set_title(figure_title)
- ax.set_xticks(x)
- ax.set_xticklabels(areaNames)
- ax.legend()
- # Add score texts over bars
- for rects in rects_list:
- autolabel(ax, rects)
- # Save plot
- 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)
- # Init figure
- fig, ax = plt.subplots()
- x = np.arange(len(areaRngLbl)) # the areaNames locations
- width = 0.60 # the width of the bars
- figure_title = 'number of annotations per area group'
- rects = ax.bar(x, areaRngLbl2Number.values(), width)
- # Add some text for labels, title and custom x-axis tick labels, etc.
- ax.set_ylabel('Number of annotations')
- ax.set_title(figure_title)
- ax.set_xticks(x)
- ax.set_xticklabels(areaRngLbl)
- # Add score texts over bars
- autolabel(ax, rects)
- # Save plot
- 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()]
- # init figure
- figure_title = 'gt annotation areas histogram plot'
- fig, ax = plt.subplots()
- # Set the number of bins
- ax.hist(np.sqrt(areas), bins=n_bins)
- # Add some text for labels, title and custom x-axis tick labels, etc.
- ax.set_xlabel('Squareroot Area')
- ax.set_ylabel('Number of annotations')
- ax.set_title(figure_title)
- # Save plot
- 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()
- # compute precision but ignore superclass confusion
- 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
- # compute precision but ignore any class confusion
- 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']
- # compute precision but ignore superclass confusion
- ps[3, :, k, :, :] = ps_supercategory
- # compute precision but ignore any class confusion
- ps[4, :, k, :, :] = ps_allcategory
- # fill in background and false negative errors and plot
- 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()
|