6 from collections import defaultdict
7 from . import mask as maskUtils
11 # Interface for evaluating detection on the Microsoft COCO dataset.
13 # The usage for CocoEval is as follows:
14 # cocoGt=..., cocoDt=... # load dataset and results
15 # E = CocoEval(cocoGt,cocoDt); # initialize CocoEval object
16 # E.params.recThrs = ...; # set parameters as desired
17 # E.evaluate(); # run per image evaluation
18 # E.accumulate(); # accumulate per image results
19 # E.summarize(); # display summary metrics of results
20 # For example usage see evalDemo.m and http://mscoco.org/.
22 # The evaluation parameters are as follows (defaults in brackets):
23 # imgIds - [all] N img ids to use for evaluation
24 # catIds - [all] K cat ids to use for evaluation
25 # iouThrs - [.5:.05:.95] T=10 IoU thresholds for evaluation
26 # recThrs - [0:.01:1] R=101 recall thresholds for evaluation
27 # areaRng - [...] A=4 object area ranges for evaluation
28 # maxDets - [1 10 100] M=3 thresholds on max detections per image
29 # iouType - ['segm'] set iouType to 'segm', 'bbox' or 'keypoints'
30 # iouType replaced the now DEPRECATED useSegm parameter.
31 # useCats - [1] if true use category labels for evaluation
32 # Note: if useCats=0 category labels are ignored as in proposal scoring.
33 # Note: multiple areaRngs [Ax2] and maxDets [Mx1] can be specified.
35 # evaluate(): evaluates detections on every image and every category and
36 # concats the results into the "evalImgs" with fields:
37 # dtIds - [1xD] id for each of the D detections (dt)
38 # gtIds - [1xG] id for each of the G ground truths (gt)
39 # dtMatches - [TxD] matching gt id at each IoU or 0
40 # gtMatches - [TxG] matching dt id at each IoU or 0
41 # dtScores - [1xD] confidence of each dt
42 # gtIgnore - [1xG] ignore flag for each gt
43 # dtIgnore - [TxD] ignore flag for each dt at each IoU
45 # accumulate(): accumulates the per-image, per-category evaluation
46 # results in "evalImgs" into the dictionary "eval" with fields:
47 # params - parameters used for evaluation
48 # date - date evaluation was performed
49 # counts - [T,R,K,A,M] parameter dimensions (see above)
50 # precision - [TxRxKxAxM] precision for every evaluation setting
51 # recall - [TxKxAxM] max recall for every evaluation setting
52 # Note: precision and recall==-1 for settings with no gt objects.
54 # See also coco, mask, pycocoDemo, pycocoEvalDemo
56 # Microsoft COCO Toolbox. version 2.0
57 # Data, paper, and tutorials available at: http://mscoco.org/
58 # Code written by Piotr Dollar and Tsung-Yi Lin, 2015.
59 # Licensed under the Simplified BSD License [see coco/license.txt]
60 def __init__(self, cocoGt=None, cocoDt=None, iouType='segm'):
62 Initialize CocoEval using coco APIs for gt and dt
63 :param cocoGt: coco object with ground truth annotations
64 :param cocoDt: coco object with detection results
68 print('iouType not specified. use default iouType segm')
69 self.cocoGt = cocoGt # ground truth COCO API
70 self.cocoDt = cocoDt # detections COCO API
71 self.params = {} # evaluation parameters
72 self.evalImgs = defaultdict(list) # per-image per-category evaluation results [KxAxI] elements
73 self.eval = {} # accumulated evaluation results
74 self._gts = defaultdict(list) # gt for evaluation
75 self._dts = defaultdict(list) # dt for evaluation
76 self.params = Params(iouType=iouType) # parameters
77 self._paramsEval = {} # parameters for evaluation
78 self.stats = [] # result summarization
79 self.ious = {} # ious between all gts and dts
80 if not cocoGt is None:
81 self.params.imgIds = sorted(cocoGt.getImgIds())
82 self.params.catIds = sorted(cocoGt.getCatIds())
87 Prepare ._gts and ._dts for evaluation based on params
90 def _toMask(anns, coco):
91 # modify ann['segmentation'] by reference
93 rle = coco.annToRLE(ann)
94 ann['segmentation'] = rle
97 gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds))
98 dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds))
100 gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds))
101 dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds))
103 # convert ground truth to mask if iouType == 'segm'
104 if p.iouType == 'segm':
105 _toMask(gts, self.cocoGt)
106 _toMask(dts, self.cocoDt)
109 gt['ignore'] = gt['ignore'] if 'ignore' in gt else 0
110 gt['ignore'] = 'iscrowd' in gt and gt['iscrowd']
111 if p.iouType == 'keypoints':
112 gt['ignore'] = (gt['num_keypoints'] == 0) or gt['ignore']
113 self._gts = defaultdict(list) # gt for evaluation
114 self._dts = defaultdict(list) # dt for evaluation
116 self._gts[gt['image_id'], gt['category_id']].append(gt)
118 self._dts[dt['image_id'], dt['category_id']].append(dt)
119 self.evalImgs = defaultdict(list) # per-image per-category evaluation results
120 self.eval = {} # accumulated evaluation results
124 Run per image evaluation on given images and store results (a list of dict) in self.evalImgs
128 print('Running per image evaluation...')
130 # add backward compatibility if useSegm is specified in params
131 if not p.useSegm is None:
132 p.iouType = 'segm' if p.useSegm == 1 else 'bbox'
133 print('useSegm (deprecated) is not None. Running {} evaluation'.format(p.iouType))
134 print('Evaluate annotation type *{}*'.format(p.iouType))
135 p.imgIds = list(np.unique(p.imgIds))
137 p.catIds = list(np.unique(p.catIds))
138 p.maxDets = sorted(p.maxDets)
142 # loop through images, area range, max detection number
143 catIds = p.catIds if p.useCats else [-1]
145 if p.iouType == 'segm' or p.iouType == 'bbox':
146 computeIoU = self.computeIoU
147 elif p.iouType == 'keypoints':
148 computeIoU = self.computeOks
149 self.ious = {(imgId, catId): computeIoU(imgId, catId) \
150 for imgId in p.imgIds
153 evaluateImg = self.evaluateImg
154 maxDet = p.maxDets[-1]
155 self.evalImgs = [evaluateImg(imgId, catId, areaRng, maxDet)
157 for areaRng in p.areaRng
158 for imgId in p.imgIds
160 self._paramsEval = copy.deepcopy(self.params)
162 print('DONE (t={:0.2f}s).'.format(toc-tic))
164 def computeIoU(self, imgId, catId):
167 gt = self._gts[imgId,catId]
168 dt = self._dts[imgId,catId]
170 gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]]
171 dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]]
172 if len(gt) == 0 and len(dt) ==0:
174 inds = np.argsort([-d['score'] for d in dt], kind='mergesort')
175 dt = [dt[i] for i in inds]
176 if len(dt) > p.maxDets[-1]:
177 dt=dt[0:p.maxDets[-1]]
179 if p.iouType == 'segm':
180 g = [g['segmentation'] for g in gt]
181 d = [d['segmentation'] for d in dt]
182 elif p.iouType == 'bbox':
183 g = [g['bbox'] for g in gt]
184 d = [d['bbox'] for d in dt]
186 raise Exception('unknown iouType for iou computation')
188 # compute iou between each dt and gt region
189 iscrowd = [int(o['iscrowd']) for o in gt]
190 ious = maskUtils.iou(d,g,iscrowd)
193 def computeOks(self, imgId, catId):
195 # dimention here should be Nxm
196 gts = self._gts[imgId, catId]
197 dts = self._dts[imgId, catId]
198 inds = np.argsort([-d['score'] for d in dts], kind='mergesort')
199 dts = [dts[i] for i in inds]
200 if len(dts) > p.maxDets[-1]:
201 dts = dts[0:p.maxDets[-1]]
202 # if len(gts) == 0 and len(dts) == 0:
203 if len(gts) == 0 or len(dts) == 0:
205 ious = np.zeros((len(dts), len(gts)))
206 sigmas = np.array([.26, .25, .25, .35, .35, .79, .79, .72, .72, .62,.62, 1.07, 1.07, .87, .87, .89, .89])/10.0
207 vars = (sigmas * 2)**2
209 # compute oks between each detection and ground truth object
210 for j, gt in enumerate(gts):
211 # create bounds for ignore regions(double the gt bbox)
212 g = np.array(gt['keypoints'])
213 xg = g[0::3]; yg = g[1::3]; vg = g[2::3]
214 k1 = np.count_nonzero(vg > 0)
216 x0 = bb[0] - bb[2]; x1 = bb[0] + bb[2] * 2
217 y0 = bb[1] - bb[3]; y1 = bb[1] + bb[3] * 2
218 for i, dt in enumerate(dts):
219 d = np.array(dt['keypoints'])
220 xd = d[0::3]; yd = d[1::3]
222 # measure the per-keypoint distance if keypoints visible
226 # measure minimum distance to keypoints in (x0,y0) & (x1,y1)
228 dx = np.max((z, x0-xd),axis=0)+np.max((z, xd-x1),axis=0)
229 dy = np.max((z, y0-yd),axis=0)+np.max((z, yd-y1),axis=0)
230 e = (dx**2 + dy**2) / vars / (gt['area']+np.spacing(1)) / 2
233 ious[i, j] = np.sum(np.exp(-e)) / e.shape[0]
236 def evaluateImg(self, imgId, catId, aRng, maxDet):
238 perform evaluation for single category and image
239 :return: dict (single image results)
243 gt = self._gts[imgId,catId]
244 dt = self._dts[imgId,catId]
246 gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]]
247 dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]]
248 if len(gt) == 0 and len(dt) ==0:
252 if g['ignore'] or (g['area']<aRng[0] or g['area']>aRng[1]):
257 # sort dt highest score first, sort gt ignore last
258 gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
259 gt = [gt[i] for i in gtind]
260 dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
261 dt = [dt[i] for i in dtind[0:maxDet]]
262 iscrowd = [int(o['iscrowd']) for o in gt]
264 ious = self.ious[imgId, catId][:, gtind] if len(self.ious[imgId, catId]) > 0 else self.ious[imgId, catId]
269 gtm = np.zeros((T,G))
270 dtm = np.zeros((T,D))
271 gtIg = np.array([g['_ignore'] for g in gt])
272 dtIg = np.zeros((T,D))
274 for tind, t in enumerate(p.iouThrs):
275 for dind, d in enumerate(dt):
276 # information about best match so far (m=-1 -> unmatched)
277 iou = min([t,1-1e-10])
279 for gind, g in enumerate(gt):
280 # if this gt already matched, and not a crowd, continue
281 if gtm[tind,gind]>0 and not iscrowd[gind]:
283 # if dt matched to reg gt, and on ignore gt, stop
284 if m>-1 and gtIg[m]==0 and gtIg[gind]==1:
286 # continue to next gt unless better match made
287 if ious[dind,gind] < iou:
289 # if match successful and best so far, store appropriately
292 # if match made store id of match for both dt and gt
295 dtIg[tind,dind] = gtIg[m]
296 dtm[tind,dind] = gt[m]['id']
297 gtm[tind,m] = d['id']
298 # set unmatched detections outside of area range to ignore
299 a = np.array([d['area']<aRng[0] or d['area']>aRng[1] for d in dt]).reshape((1, len(dt)))
300 dtIg = np.logical_or(dtIg, np.logical_and(dtm==0, np.repeat(a,T,0)))
301 # store results for given image and category
304 'category_id': catId,
307 'dtIds': [d['id'] for d in dt],
308 'gtIds': [g['id'] for g in gt],
311 'dtScores': [d['score'] for d in dt],
316 def accumulate(self, p = None):
318 Accumulate per image evaluation results and store the result in self.eval
319 :param p: input params for evaluation
322 print('Accumulating evaluation results...')
324 if not self.evalImgs:
325 print('Please run evaluate() first')
326 # allows input customized parameters
329 p.catIds = p.catIds if p.useCats == 1 else [-1]
332 K = len(p.catIds) if p.useCats else 1
335 precision = -np.ones((T,R,K,A,M)) # -1 for the precision of absent categories
336 recall = -np.ones((T,K,A,M))
337 scores = -np.ones((T,R,K,A,M))
339 # create dictionary for future indexing
340 _pe = self._paramsEval
341 catIds = _pe.catIds if _pe.useCats else [-1]
343 setA = set(map(tuple, _pe.areaRng))
344 setM = set(_pe.maxDets)
345 setI = set(_pe.imgIds)
346 # get inds to evaluate
347 k_list = [n for n, k in enumerate(p.catIds) if k in setK]
348 m_list = [m for n, m in enumerate(p.maxDets) if m in setM]
349 a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA]
350 i_list = [n for n, i in enumerate(p.imgIds) if i in setI]
352 A0 = len(_pe.areaRng)
353 # retrieve E at each category, area range, and max number of detections
354 for k, k0 in enumerate(k_list):
356 for a, a0 in enumerate(a_list):
358 for m, maxDet in enumerate(m_list):
359 E = [self.evalImgs[Nk + Na + i] for i in i_list]
360 E = [e for e in E if not e is None]
363 dtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E])
365 # different sorting method generates slightly different results.
366 # mergesort is used to be consistent as Matlab implementation.
367 inds = np.argsort(-dtScores, kind='mergesort')
368 dtScoresSorted = dtScores[inds]
370 dtm = np.concatenate([e['dtMatches'][:,0:maxDet] for e in E], axis=1)[:,inds]
371 dtIg = np.concatenate([e['dtIgnore'][:,0:maxDet] for e in E], axis=1)[:,inds]
372 gtIg = np.concatenate([e['gtIgnore'] for e in E])
373 npig = np.count_nonzero(gtIg==0 )
376 tps = np.logical_and( dtm, np.logical_not(dtIg) )
377 fps = np.logical_and(np.logical_not(dtm), np.logical_not(dtIg) )
379 tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float)
380 fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float)
381 for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):
386 pr = tp / (fp+tp+np.spacing(1))
391 recall[t,k,a,m] = rc[-1]
395 # numpy is slow without cython optimization for accessing elements
396 # use python array gets significant speed improvement
397 pr = pr.tolist(); q = q.tolist()
399 for i in range(nd-1, 0, -1):
403 inds = np.searchsorted(rc, p.recThrs, side='left')
405 for ri, pi in enumerate(inds):
407 ss[ri] = dtScoresSorted[pi]
410 precision[t,:,k,a,m] = np.array(q)
411 scores[t,:,k,a,m] = np.array(ss)
414 'counts': [T, R, K, A, M],
415 'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
416 'precision': precision,
421 print('DONE (t={:0.2f}s).'.format( toc-tic))
425 Compute and display summary metrics for evaluation results.
426 Note this functin can *only* be applied on the default parameter setting
428 def _summarize( ap=1, iouThr=None, areaRng='all', maxDets=100 ):
430 iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}'
431 titleStr = 'Average Precision' if ap == 1 else 'Average Recall'
432 typeStr = '(AP)' if ap==1 else '(AR)'
433 iouStr = '{:0.2f}:{:0.2f}'.format(p.iouThrs[0], p.iouThrs[-1]) \
434 if iouThr is None else '{:0.2f}'.format(iouThr)
436 aind = [i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng]
437 mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets]
439 # dimension of precision: [TxRxKxAxM]
440 s = self.eval['precision']
442 if iouThr is not None:
443 t = np.where(iouThr == p.iouThrs)[0]
445 s = s[:,:,:,aind,mind]
447 # dimension of recall: [TxKxAxM]
448 s = self.eval['recall']
449 if iouThr is not None:
450 t = np.where(iouThr == p.iouThrs)[0]
456 mean_s = np.mean(s[s>-1])
457 print(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s))
459 def _summarizeDets():
460 stats = np.zeros((12,))
461 stats[0] = _summarize(1)
462 stats[1] = _summarize(1, iouThr=.5, maxDets=self.params.maxDets[2])
463 stats[2] = _summarize(1, iouThr=.75, maxDets=self.params.maxDets[2])
464 stats[3] = _summarize(1, areaRng='small', maxDets=self.params.maxDets[2])
465 stats[4] = _summarize(1, areaRng='medium', maxDets=self.params.maxDets[2])
466 stats[5] = _summarize(1, areaRng='large', maxDets=self.params.maxDets[2])
467 stats[6] = _summarize(0, maxDets=self.params.maxDets[0])
468 stats[7] = _summarize(0, maxDets=self.params.maxDets[1])
469 stats[8] = _summarize(0, maxDets=self.params.maxDets[2])
470 stats[9] = _summarize(0, areaRng='small', maxDets=self.params.maxDets[2])
471 stats[10] = _summarize(0, areaRng='medium', maxDets=self.params.maxDets[2])
472 stats[11] = _summarize(0, areaRng='large', maxDets=self.params.maxDets[2])
475 stats = np.zeros((10,))
476 stats[0] = _summarize(1, maxDets=20)
477 stats[1] = _summarize(1, maxDets=20, iouThr=.5)
478 stats[2] = _summarize(1, maxDets=20, iouThr=.75)
479 stats[3] = _summarize(1, maxDets=20, areaRng='medium')
480 stats[4] = _summarize(1, maxDets=20, areaRng='large')
481 stats[5] = _summarize(0, maxDets=20)
482 stats[6] = _summarize(0, maxDets=20, iouThr=.5)
483 stats[7] = _summarize(0, maxDets=20, iouThr=.75)
484 stats[8] = _summarize(0, maxDets=20, areaRng='medium')
485 stats[9] = _summarize(0, maxDets=20, areaRng='large')
488 raise Exception('Please run accumulate() first')
489 iouType = self.params.iouType
490 if iouType == 'segm' or iouType == 'bbox':
491 summarize = _summarizeDets
492 elif iouType == 'keypoints':
493 summarize = _summarizeKps
494 self.stats = summarize()
501 Params for coco evaluation api
503 def setDetParams(self):
506 # np.arange causes trouble. the data point on arange is slightly larger than the true value
507 self.iouThrs = np.linspace(.5, 0.95, np.round((0.95 - .5) / .05) + 1, endpoint=True)
508 self.recThrs = np.linspace(.0, 1.00, np.round((1.00 - .0) / .01) + 1, endpoint=True)
509 self.maxDets = [1, 10, 100]
510 self.areaRng = [[0 ** 2, 1e5 ** 2], [0 ** 2, 32 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]]
511 self.areaRngLbl = ['all', 'small', 'medium', 'large']
514 def setKpParams(self):
517 # np.arange causes trouble. the data point on arange is slightly larger than the true value
518 self.iouThrs = np.linspace(.5, 0.95, np.round((0.95 - .5) / .05) + 1, endpoint=True)
519 self.recThrs = np.linspace(.0, 1.00, np.round((1.00 - .0) / .01) + 1, endpoint=True)
521 self.areaRng = [[0 ** 2, 1e5 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]]
522 self.areaRngLbl = ['all', 'medium', 'large']
525 def __init__(self, iouType='segm'):
526 if iouType == 'segm' or iouType == 'bbox':
528 elif iouType == 'keypoints':
531 raise Exception('iouType not supported')
532 self.iouType = iouType
533 # useSegm is deprecated