@@ -26,8 +26,9 @@ class COCOeval:
26
26
# recThrs - [0:.01:1] R=101 recall thresholds for evaluation
27
27
# areaRng - [...] A=4 object area ranges for evaluation
28
28
# maxDets - [1 10 100] M=3 thresholds on max detections per image
29
- # useSegm - [1] if true evaluate against ground-truth segments
30
- # useCats - [1] if true use category labels for evaluation # Note: if useSegm=0 the evaluation is run on bounding boxes.
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
31
32
# Note: if useCats=0 category labels are ignored as in proposal scoring.
32
33
# Note: multiple areaRngs [Ax2] and maxDets [Mx1] can be specified.
33
34
#
@@ -56,21 +57,23 @@ class COCOeval:
56
57
# Data, paper, and tutorials available at: http://mscoco.org/
57
58
# Code written by Piotr Dollar and Tsung-Yi Lin, 2015.
58
59
# Licensed under the Simplified BSD License [see coco/license.txt]
59
- def __init__ (self , cocoGt = None , cocoDt = None ):
60
+ def __init__ (self , cocoGt = None , cocoDt = None , iouType = "segm" ):
60
61
'''
61
62
Initialize CocoEval using coco APIs for gt and dt
62
63
:param cocoGt: coco object with ground truth annotations
63
64
:param cocoDt: coco object with detection results
64
65
:return: None
65
66
'''
67
+ if not iouType :
68
+ print ("iouType not specified. use default iouType segm" )
66
69
self .cocoGt = cocoGt # ground truth COCO API
67
70
self .cocoDt = cocoDt # detections COCO API
68
71
self .params = {} # evaluation parameters
69
72
self .evalImgs = defaultdict (list ) # per-image per-category evaluation results [KxAxI] elements
70
73
self .eval = {} # accumulated evaluation results
71
74
self ._gts = defaultdict (list ) # gt for evaluation
72
75
self ._dts = defaultdict (list ) # dt for evaluation
73
- self .params = Params () # parameters
76
+ self .params = Params (iouType = iouType ) # parameters
74
77
self ._paramsEval = {} # parameters for evaluation
75
78
self .stats = [] # result summarization
76
79
self .ious = {} # ious between all gts and dts
@@ -84,7 +87,6 @@ def _prepare(self):
84
87
Prepare ._gts and ._dts for evaluation based on params
85
88
:return: None
86
89
'''
87
- #
88
90
def _toMask (objs , coco ):
89
91
# modify segmentation by reference
90
92
for obj in objs :
@@ -114,9 +116,16 @@ def _toMask(objs, coco):
114
116
gts = self .cocoGt .loadAnns (self .cocoGt .getAnnIds (imgIds = p .imgIds ))
115
117
dts = self .cocoDt .loadAnns (self .cocoDt .getAnnIds (imgIds = p .imgIds ))
116
118
117
- if p .useSegm :
119
+ # convert ground truth to mask if iouType == "segm"
120
+ if p .iouType == "segm" :
118
121
_toMask (gts , self .cocoGt )
119
122
_toMask (dts , self .cocoDt )
123
+ # set ignore flag
124
+ for gt in gts :
125
+ gt ["ignore" ] = gt ["ignore" ] if "ignore" in gt else 0
126
+ gt ["ignore" ] = "iscrowd" in gt and gt ["iscrowd" ]
127
+ if p .iouType == "keypoints" :
128
+ gt ["ignore" ] = (gt ["num_keypoints" ] == 0 ) or gt ["ignore" ]
120
129
self ._gts = defaultdict (list ) # gt for evaluation
121
130
self ._dts = defaultdict (list ) # dt for evaluation
122
131
for gt in gts :
@@ -134,6 +143,10 @@ def evaluate(self):
134
143
tic = time .time ()
135
144
print 'Running per image evaluation... '
136
145
p = self .params
146
+ # add backward compatibility if useSegm is specified in params
147
+ if not p .useSegm is None :
148
+ p .iouType = "segm" if p .useSegm == 1 else "bbox"
149
+ print ("useSegm (deprecated) is not None. Running %s evaluation" % (p .iouType ))
137
150
p .imgIds = list (np .unique (p .imgIds ))
138
151
if p .useCats :
139
152
p .catIds = list (np .unique (p .catIds ))
@@ -144,7 +157,10 @@ def evaluate(self):
144
157
# loop through images, area range, max detection number
145
158
catIds = p .catIds if p .useCats else [- 1 ]
146
159
147
- computeIoU = self .computeIoU
160
+ if p .iouType == "segm" or p .iouType == "bbox" :
161
+ computeIoU = self .computeIoU
162
+ elif p .iouType == "keypoints" :
163
+ computeIoU = self .computeOks
148
164
self .ious = {(imgId , catId ): computeIoU (imgId , catId ) \
149
165
for imgId in p .imgIds
150
166
for catId in catIds }
@@ -170,28 +186,73 @@ def computeIoU(self, imgId, catId):
170
186
dt = [_ for cId in p .catIds for _ in self ._dts [imgId ,cId ]]
171
187
if len (gt ) == 0 and len (dt ) == 0 :
172
188
return []
173
- dt = sorted (dt , key = lambda x : - x ['score' ])
189
+ inds = np .argsort (map (lambda x :- x ["score" ], dt ),kind = 'mergesort' )
190
+ dt = [dt [i ] for i in inds ]
174
191
if len (dt ) > p .maxDets [- 1 ]:
175
192
dt = dt [0 :p .maxDets [- 1 ]]
176
193
177
- if p .useSegm :
194
+ if p .iouType == "segm" :
178
195
g = [g ['segmentation' ] for g in gt ]
179
196
d = [d ['segmentation' ] for d in dt ]
180
- else :
197
+ elif p . iouType == "bbox" :
181
198
g = [g ['bbox' ] for g in gt ]
182
199
d = [d ['bbox' ] for d in dt ]
200
+ else :
201
+ raise Exception ("unknown iouType for iou computation" )
183
202
184
203
# compute iou between each dt and gt region
185
204
iscrowd = [int (o ['iscrowd' ]) for o in gt ]
186
205
ious = mask .iou (d ,g ,iscrowd )
187
206
return ious
188
207
208
+ def computeOks (self , imgId , catId ):
209
+ p = self .params
210
+ # dimention here should be Nxm
211
+ gts = self ._gts [imgId , catId ]
212
+ dts = self ._dts [imgId , catId ]
213
+ inds = np .argsort (map (lambda x : - x ["score" ], dts ), kind = 'mergesort' )
214
+ dts = [dts [i ] for i in inds ]
215
+ if len (dts ) > p .maxDets [- 1 ]:
216
+ dts = dts [0 :p .maxDets [- 1 ]]
217
+ # if len(gts) == 0 and len(dts) == 0:
218
+ if len (gts ) == 0 or len (dts ) == 0 :
219
+ return []
220
+ ious = np .zeros ((len (dts ), len (gts )))
221
+ sigmas = np .array ([.26 , .25 , .25 , .35 , .35 , .79 , .79 , .72 , .72 , .62 ,.62 , 1.07 , 1.07 , .87 , .87 , .89 , .89 ])/ 10.0
222
+ vars = (sigmas * 2 )** 2
223
+ k = len (sigmas )
224
+ # compute oks between each detection and ground truth object
225
+ for j , gt in enumerate (gts ):
226
+ # create bounds for ignore regions(double the gt bbox)
227
+ g = np .array (gt ["keypoints" ])
228
+ xg = g [0 ::3 ]; yg = g [1 ::3 ]; vg = g [2 ::3 ]
229
+ k1 = np .count_nonzero (vg > 0 )
230
+ bb = gt ["bbox" ]
231
+ x0 = bb [0 ] - bb [2 ]; x1 = bb [0 ] + bb [2 ] * 2
232
+ y0 = bb [1 ] - bb [3 ]; y1 = bb [1 ] + bb [3 ] * 2
233
+ for i , dt in enumerate (dts ):
234
+ d = np .array (dt ["keypoints" ])
235
+ xd = d [0 ::3 ]; yd = d [1 ::3 ]
236
+ if k1 > 0 :
237
+ # measure the per-keypoint distance if keypoints visible
238
+ dx = xd - xg
239
+ dy = yd - yg
240
+ else :
241
+ # measure minimum distance to keypoints in (x0,y0) & (x1,y1)
242
+ z = np .zeros ((k ))
243
+ dx = np .max ((z , x0 - xd ),axis = 0 )+ np .max ((z , xd - x1 ),axis = 0 )
244
+ dy = np .max ((z , y0 - yd ),axis = 0 )+ np .max ((z , yd - y1 ),axis = 0 )
245
+ e = (dx ** 2 + dy ** 2 ) / vars / (gt ["area" ]+ np .spacing (1 )) / 2
246
+ if k1 > 0 :
247
+ e = e [vg > 0 ]
248
+ ious [i , j ] = np .sum (np .exp (- e )) / e .shape [0 ]
249
+ return ious
250
+
189
251
def evaluateImg (self , imgId , catId , aRng , maxDet ):
190
252
'''
191
253
perform evaluation for single category and image
192
254
:return: dict (single image results)
193
255
'''
194
- #
195
256
p = self .params
196
257
if p .useCats :
197
258
gt = self ._gts [imgId ,catId ]
@@ -203,23 +264,19 @@ def evaluateImg(self, imgId, catId, aRng, maxDet):
203
264
return None
204
265
205
266
for g in gt :
206
- if 'ignore' not in g :
207
- g ['ignore' ] = 0
208
- if g ['iscrowd' ] == 1 or g ['ignore' ] or (g ['area' ]< aRng [0 ] or g ['area' ]> aRng [1 ]):
267
+ if g ['ignore' ] or (g ['area' ]< aRng [0 ] or g ['area' ]> aRng [1 ]):
209
268
g ['_ignore' ] = 1
210
269
else :
211
270
g ['_ignore' ] = 0
212
271
213
272
# sort dt highest score first, sort gt ignore last
214
- # gt = sorted(gt, key=lambda x: x['_ignore'])
215
- gtind = [ind for (ind , g ) in sorted (enumerate (gt ), key = lambda (ind , g ): g ['_ignore' ]) ]
216
-
217
- gt = [gt [ind ] for ind in gtind ]
218
- dt = sorted (dt , key = lambda x : - x ['score' ])[0 :maxDet ]
273
+ gtind = np .argsort ([g ['_ignore' ] for g in gt ], kind = "mergesort" )
274
+ gt = map (lambda i : gt [i ], gtind )
275
+ dtind = np .argsort ([- d ['score' ] for d in dt ], kind = "mergesort" )
276
+ dt = map (lambda i : dt [i ], dtind [0 :maxDet ])
219
277
iscrowd = [int (o ['iscrowd' ]) for o in gt ]
220
278
# load computed ious
221
- N_iou = len (self .ious [imgId , catId ])
222
- ious = self .ious [imgId , catId ][0 :maxDet , np .array (gtind )] if N_iou > 0 else self .ious [imgId , catId ]
279
+ ious = self .ious [imgId , catId ][:, gtind ] if len (self .ious [imgId , catId ]) > 0 else self .ious [imgId , catId ]
223
280
224
281
T = len (p .iouThrs )
225
282
G = len (gt )
@@ -244,7 +301,7 @@ def evaluateImg(self, imgId, catId, aRng, maxDet):
244
301
# continue to next gt unless better match made
245
302
if ious [dind ,gind ] < iou :
246
303
continue
247
- # match successful and best so far, store appropriately
304
+ # if match successful and best so far, store appropriately
248
305
iou = ious [dind ,gind ]
249
306
m = gind
250
307
# if match made store id of match for both dt and gt
@@ -305,7 +362,6 @@ def accumulate(self, p = None):
305
362
m_list = [m for n , m in enumerate (p .maxDets ) if m in setM ]
306
363
a_list = [n for n , a in enumerate (map (lambda x : tuple (x ), p .areaRng )) if a in setA ]
307
364
i_list = [n for n , i in enumerate (p .imgIds ) if i in setI ]
308
- # K0 = len(_pe.catIds)
309
365
I0 = len (_pe .imgIds )
310
366
A0 = len (_pe .areaRng )
311
367
# retrieve E at each category, area range, and max number of detections
@@ -326,8 +382,8 @@ def accumulate(self, p = None):
326
382
327
383
dtm = np .concatenate ([e ['dtMatches' ][:,0 :maxDet ] for e in E ], axis = 1 )[:,inds ]
328
384
dtIg = np .concatenate ([e ['dtIgnore' ][:,0 :maxDet ] for e in E ], axis = 1 )[:,inds ]
329
- gtIg = np .concatenate ([e ['gtIgnore' ] for e in E ])
330
- npig = len ([ ig for ig in gtIg if ig == 0 ] )
385
+ gtIg = np .concatenate ([e ['gtIgnore' ] for e in E ])
386
+ npig = np . count_nonzero ( gtIg == 0 )
331
387
if npig == 0 :
332
388
continue
333
389
tps = np .logical_and ( dtm , np .logical_not (dtIg ) )
@@ -356,7 +412,7 @@ def accumulate(self, p = None):
356
412
if pr [i ] > pr [i - 1 ]:
357
413
pr [i - 1 ] = pr [i ]
358
414
359
- inds = np .searchsorted (rc , p .recThrs )
415
+ inds = np .searchsorted (rc , p .recThrs , side = 'left' )
360
416
try :
361
417
for ri , pi in enumerate (inds ):
362
418
q [ri ] = pr [pi ]
@@ -387,43 +443,65 @@ def _summarize( ap=1, iouThr=None, areaRng='all', maxDets=100 ):
387
443
areaStr = areaRng
388
444
maxDetsStr = '%d' % (maxDets )
389
445
390
- aind = [i for i , aRng in enumerate ([ 'all' , 'small' , 'medium' , 'large' ] ) if aRng == areaRng ]
391
- mind = [i for i , mDet in enumerate ([ 1 , 10 , 100 ] ) if mDet == maxDets ]
446
+ aind = [i for i , aRng in enumerate (p . areaRngLbl ) if aRng == areaRng ]
447
+ mind = [i for i , mDet in enumerate (p . maxDets ) if mDet == maxDets ]
392
448
if ap == 1 :
393
449
# dimension of precision: [TxRxKxAxM]
394
450
s = self .eval ['precision' ]
395
451
# IoU
396
452
if iouThr is not None :
397
453
t = np .where (iouThr == p .iouThrs )[0 ]
398
454
s = s [t ]
399
- # areaRng
400
455
s = s [:,:,:,aind ,mind ]
401
456
else :
402
457
# dimension of recall: [TxKxAxM]
403
458
s = self .eval ['recall' ]
459
+ if iouThr is not None :
460
+ t = np .where (iouThr == p .iouThrs )[0 ]
461
+ s = s [t ]
404
462
s = s [:,:,aind ,mind ]
405
463
if len (s [s > - 1 ])== 0 :
406
464
mean_s = - 1
407
465
else :
408
466
mean_s = np .mean (s [s > - 1 ])
409
467
print iStr .format (titleStr , typeStr , iouStr , areaStr , maxDetsStr , '%.3f' % (float (mean_s )))
410
468
return mean_s
411
-
469
+ def _summarizeDets ():
470
+ stats = np .zeros ((12 ,))
471
+ stats [0 ] = _summarize (1 )
472
+ stats [1 ] = _summarize (1 , iouThr = .5 )
473
+ stats [2 ] = _summarize (1 , iouThr = .75 )
474
+ stats [3 ] = _summarize (1 , areaRng = 'small' )
475
+ stats [4 ] = _summarize (1 , areaRng = 'medium' )
476
+ stats [5 ] = _summarize (1 , areaRng = 'large' )
477
+ stats [6 ] = _summarize (0 , maxDets = 1 )
478
+ stats [7 ] = _summarize (0 , maxDets = 10 )
479
+ stats [8 ] = _summarize (0 , maxDets = 100 )
480
+ stats [9 ] = _summarize (0 , areaRng = 'small' )
481
+ stats [10 ] = _summarize (0 , areaRng = 'medium' )
482
+ stats [11 ] = _summarize (0 , areaRng = 'large' )
483
+ return stats
484
+ def _summarizeKps ():
485
+ stats = np .zeros ((10 ,))
486
+ stats [0 ] = _summarize (1 , maxDets = 20 )
487
+ stats [1 ] = _summarize (1 , maxDets = 20 , iouThr = .5 )
488
+ stats [2 ] = _summarize (1 , maxDets = 20 , iouThr = .75 )
489
+ stats [3 ] = _summarize (1 , maxDets = 20 , areaRng = 'medium' )
490
+ stats [4 ] = _summarize (1 , maxDets = 20 , areaRng = 'large' )
491
+ stats [5 ] = _summarize (0 , maxDets = 20 )
492
+ stats [6 ] = _summarize (0 , maxDets = 20 , iouThr = .5 )
493
+ stats [7 ] = _summarize (0 , maxDets = 20 , iouThr = .75 )
494
+ stats [8 ] = _summarize (0 , maxDets = 20 , areaRng = 'medium' )
495
+ stats [9 ] = _summarize (0 , maxDets = 20 , areaRng = 'large' )
496
+ return stats
412
497
if not self .eval :
413
498
raise Exception ('Please run accumulate() first' )
414
- self .stats = np .zeros ((12 ,))
415
- self .stats [0 ] = _summarize (1 )
416
- self .stats [1 ] = _summarize (1 ,iouThr = .5 )
417
- self .stats [2 ] = _summarize (1 ,iouThr = .75 )
418
- self .stats [3 ] = _summarize (1 ,areaRng = 'small' )
419
- self .stats [4 ] = _summarize (1 ,areaRng = 'medium' )
420
- self .stats [5 ] = _summarize (1 ,areaRng = 'large' )
421
- self .stats [6 ] = _summarize (0 ,maxDets = 1 )
422
- self .stats [7 ] = _summarize (0 ,maxDets = 10 )
423
- self .stats [8 ] = _summarize (0 ,maxDets = 100 )
424
- self .stats [9 ] = _summarize (0 ,areaRng = 'small' )
425
- self .stats [10 ] = _summarize (0 ,areaRng = 'medium' )
426
- self .stats [11 ] = _summarize (0 ,areaRng = 'large' )
499
+ iouType = self .params .iouType
500
+ if iouType == "segm" or iouType == "bbox" :
501
+ summarize = _summarizeDets
502
+ elif iouType == "keypoints" :
503
+ summarize = _summarizeKps
504
+ self .stats = summarize ()
427
505
428
506
def __str__ (self ):
429
507
self .summarize ()
@@ -432,13 +510,35 @@ class Params:
432
510
'''
433
511
Params for coco evaluation api
434
512
'''
435
- def __init__ (self ):
513
+ def setDetParams (self ):
436
514
self .imgIds = []
437
515
self .catIds = []
438
516
# np.arange causes trouble. the data point on arange is slightly larger than the true value
439
- self .iouThrs = np .linspace (.5 , 0.95 , np .round ((0.95 - .5 )/ .05 )+ 1 , endpoint = True )
440
- self .recThrs = np .linspace (.0 , 1.00 , np .round ((1.00 - .0 )/ .01 )+ 1 , endpoint = True )
441
- self .maxDets = [1 ,10 ,100 ]
442
- self .areaRng = [ [0 ** 2 ,1e5 ** 2 ], [0 ** 2 , 32 ** 2 ], [32 ** 2 , 96 ** 2 ], [96 ** 2 , 1e5 ** 2 ] ]
443
- self .useSegm = 0
444
- self .useCats = 1
517
+ self .iouThrs = np .linspace (.5 , 0.95 , np .round ((0.95 - .5 ) / .05 ) + 1 , endpoint = True )
518
+ self .recThrs = np .linspace (.0 , 1.00 , np .round ((1.00 - .0 ) / .01 ) + 1 , endpoint = True )
519
+ self .maxDets = [1 , 10 , 100 ]
520
+ self .areaRng = [[0 ** 2 , 1e5 ** 2 ], [0 ** 2 , 32 ** 2 ], [32 ** 2 , 96 ** 2 ], [96 ** 2 , 1e5 ** 2 ]]
521
+ self .areaRngLbl = ['all' , 'small' , 'medium' , 'large' ]
522
+ self .useCats = 1
523
+
524
+ def setKpParams (self ):
525
+ self .imgIds = []
526
+ self .catIds = []
527
+ # np.arange causes trouble. the data point on arange is slightly larger than the true value
528
+ self .iouThrs = np .linspace (.5 , 0.95 , np .round ((0.95 - .5 ) / .05 ) + 1 , endpoint = True )
529
+ self .recThrs = np .linspace (.0 , 1.00 , np .round ((1.00 - .0 ) / .01 ) + 1 , endpoint = True )
530
+ self .maxDets = [20 ]
531
+ self .areaRng = [[0 ** 2 , 1e5 ** 2 ], [32 ** 2 , 96 ** 2 ], [96 ** 2 , 1e5 ** 2 ]]
532
+ self .areaRngLbl = ['all' , 'medium' , 'large' ]
533
+ self .useCats = 1
534
+
535
+ def __init__ (self , iouType = "segm" ):
536
+ if iouType == "segm" or iouType == "bbox" :
537
+ self .setDetParams ()
538
+ elif iouType == "keypoints" :
539
+ self .setKpParams ()
540
+ else :
541
+ raise Exception ("iouType not supported" )
542
+ self .iouType = iouType
543
+ # useSegm is deprecated
544
+ self .useSegm = None
0 commit comments