halpecocotools 0.0.0__cp312-cp312-macosx_14_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,818 @@
1
+ __author__ = 'tsungyi, Haoyi Zhu'
2
+
3
+ import numpy as np
4
+ import datetime
5
+ import time
6
+ from collections import defaultdict
7
+ from . import mask as maskUtils
8
+ import copy
9
+
10
+ class COCOeval:
11
+ # Interface for evaluating detection on the Microsoft COCO dataset.
12
+ #
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/.
21
+ #
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.
34
+ #
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
44
+ #
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.
53
+ #
54
+ # See also coco, mask, pycocoDemo, pycocoEvalDemo
55
+ #
56
+ # The original Microsoft COCO Toolbox is written
57
+ # by Piotr Dollar and Tsung-Yi Lin, 2015.
58
+ # Licensed under the Simplified BSD License [see coco/license.txt]
59
+ #
60
+ # Updated and renamed to Halpe COCO Toolbox (cocohalpetools) \
61
+ # by Haoyi Zhu in 2021. The Halpe COCO Toolbox is
62
+ # developed to support wholebody human pose datasets,
63
+ # including Halpe Full-body and COCO WholeBody.
64
+
65
+ def __init__(self, cocoGt=None, cocoDt=None, iouType='segm'):
66
+ '''
67
+ Initialize CocoEval using coco APIs for gt and dt
68
+ :param cocoGt: coco object with ground truth annotations
69
+ :param cocoDt: coco object with detection results
70
+ :return: None
71
+ '''
72
+ if not iouType:
73
+ print('iouType not specified. use default iouType segm')
74
+ self.cocoGt = cocoGt # ground truth COCO API
75
+ self.cocoDt = cocoDt # detections COCO API
76
+ self.params = {} # evaluation parameters
77
+ self.evalImgs = defaultdict(list) # per-image per-category evaluation results [KxAxI] elements
78
+ self.eval = {} # accumulated evaluation results
79
+ self._gts = defaultdict(list) # gt for evaluation
80
+ self._dts = defaultdict(list) # dt for evaluation
81
+ self.params = Params(iouType=iouType) # parameters
82
+ self._paramsEval = {} # parameters for evaluation
83
+ self.stats = [] # result summarization
84
+ self.ious = {} # ious between all gts and dts
85
+ if not cocoGt is None:
86
+ self.params.imgIds = sorted(cocoGt.getImgIds())
87
+ self.params.catIds = sorted(cocoGt.getCatIds())
88
+
89
+
90
+ def _prepare(self):
91
+ '''
92
+ Prepare ._gts and ._dts for evaluation based on params
93
+ :return: None
94
+ '''
95
+ def _toMask(anns, coco):
96
+ # modify ann['segmentation'] by reference
97
+ for ann in anns:
98
+ rle = coco.annToRLE(ann)
99
+ ann['segmentation'] = rle
100
+ p = self.params
101
+ if p.useCats:
102
+ gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds))
103
+ dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds))
104
+ else:
105
+ gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds))
106
+ dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds))
107
+
108
+ # convert ground truth to mask if iouType == 'segm'
109
+ if p.iouType == 'segm':
110
+ _toMask(gts, self.cocoGt)
111
+ _toMask(dts, self.cocoDt)
112
+ # set ignore flag
113
+ for gt in gts:
114
+ gt['ignore'] = gt['ignore'] if 'ignore' in gt else 0
115
+ # gt['ignore'] = 'iscrowd' in gt and gt['iscrowd']
116
+ if p.iouType == 'keypoints':
117
+ if len(gt['keypoints']) % 133 == 0 or len(gt['keypoints']) % 136 == 0:
118
+ _ignore = gt['ignore']
119
+ gt['ignore'] = dict()
120
+ body_kp = np.array(gt['keypoints'][:-116*3])
121
+ foot_kp = np.array(gt['keypoints'][-116*3:-110*3])
122
+ face_kp = np.array(gt['keypoints'][-110*3:-42*3])
123
+ hand_kp = np.array(gt['keypoints'][-42*3:])
124
+ fullbody_kp = np.array(gt['keypoints'])
125
+
126
+ vg_body = body_kp[2::3]
127
+ vg_foot = foot_kp[2::3]
128
+ vg_face = face_kp[2::3]
129
+ vg_hand = hand_kp[2::3]
130
+ vg_fullbody = fullbody_kp[2::3]
131
+ k1_body = np.count_nonzero(vg_body > 0)
132
+ k1_foot = np.count_nonzero(vg_foot > 0)
133
+ k1_face = np.count_nonzero(vg_face > 0)
134
+ k1_hand = np.count_nonzero(vg_hand > 0)
135
+ k1_fullbody = np.count_nonzero(vg_fullbody > 0)
136
+
137
+ gt['ignore']['body'] = (k1_body == 0) or _ignore
138
+ gt['ignore']['foot'] = (k1_foot == 0) or _ignore
139
+ gt['ignore']['face'] = (k1_face == 0) or _ignore
140
+ gt['ignore']['hand'] = (k1_hand == 0) or _ignore
141
+ gt['ignore']['fullbody'] = (k1_fullbody == 0) or _ignore
142
+ else:
143
+ gt['ignore'] = (gt['num_keypoints'] == 0) or gt['ignore']
144
+ self._gts = defaultdict(list) # gt for evaluation
145
+ self._dts = defaultdict(list) # dt for evaluation
146
+ for gt in gts:
147
+ self._gts[gt['image_id'], gt['category_id']].append(gt)
148
+ for dt in dts:
149
+ vd = np.array(dt['keypoints'])[2::3]
150
+ if len(vd[vd>0]) == 0:
151
+ continue
152
+ self._dts[dt['image_id'], dt['category_id']].append(dt)
153
+ self.evalImgs = defaultdict(list) # per-image per-category evaluation results
154
+ self.eval = {} # accumulated evaluation results
155
+
156
+ def evaluate(self):
157
+ '''
158
+ Run per image evaluation on given images and store results (a list of dict) in self.evalImgs
159
+ :return: None
160
+ '''
161
+ tic = time.time()
162
+ print('Running per image evaluation...')
163
+ p = self.params
164
+ # add backward compatibility if useSegm is specified in params
165
+ if not p.useSegm is None:
166
+ p.iouType = 'segm' if p.useSegm == 1 else 'bbox'
167
+ print('useSegm (deprecated) is not None. Running {} evaluation'.format(p.iouType))
168
+ print('Evaluate annotation type *{}*'.format(p.iouType))
169
+ p.imgIds = list(np.unique(p.imgIds))
170
+ if p.useCats:
171
+ p.catIds = list(np.unique(p.catIds))
172
+ p.maxDets = sorted(p.maxDets)
173
+ self.params=p
174
+
175
+ self._prepare()
176
+ # loop through images, area range, max detection number
177
+ catIds = p.catIds if p.useCats else [-1]
178
+
179
+ if p.iouType == 'segm' or p.iouType == 'bbox':
180
+ computeIoU = self.computeIoU
181
+ elif p.iouType == 'keypoints':
182
+ computeIoU = self.computeOks
183
+ self.ious = {(imgId, catId): computeIoU(imgId, catId) \
184
+ for imgId in p.imgIds
185
+ for catId in catIds}
186
+
187
+ evaluateImg = self.evaluateImg
188
+ maxDet = p.maxDets[-1]
189
+ self.evalImgs = [evaluateImg(imgId, catId, areaRng, maxDet)
190
+ for catId in catIds
191
+ for areaRng in p.areaRng
192
+ for imgId in p.imgIds
193
+ ]
194
+ self._paramsEval = copy.deepcopy(self.params)
195
+ toc = time.time()
196
+ print('DONE (t={:0.2f}s).'.format(toc-tic))
197
+
198
+ def computeIoU(self, imgId, catId):
199
+ p = self.params
200
+ if p.useCats:
201
+ gt = self._gts[imgId,catId]
202
+ dt = self._dts[imgId,catId]
203
+ else:
204
+ gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]]
205
+ dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]]
206
+ if len(gt) == 0 and len(dt) ==0:
207
+ return []
208
+ inds = np.argsort([-d['score'] for d in dt], kind='mergesort')
209
+ dt = [dt[i] for i in inds]
210
+ if len(dt) > p.maxDets[-1]:
211
+ dt=dt[0:p.maxDets[-1]]
212
+
213
+ if p.iouType == 'segm':
214
+ g = [g['segmentation'] for g in gt]
215
+ d = [d['segmentation'] for d in dt]
216
+ elif p.iouType == 'bbox':
217
+ g = [g['bbox'] for g in gt]
218
+ d = [d['bbox'] for d in dt]
219
+ else:
220
+ raise Exception('unknown iouType for iou computation')
221
+
222
+ # compute iou between each dt and gt region
223
+ iscrowd = [int(o['iscrowd']) for o in gt]
224
+ ious = maskUtils.iou(d,g,iscrowd)
225
+ return ious
226
+
227
+ def computeOks(self, imgId, catId):
228
+ parts = ['body', 'foot', 'face', 'hand', 'fullbody']
229
+ _ious = {}
230
+ for part in parts:
231
+ p = self.params
232
+ # dimention here should be Nxm
233
+ gts = self._gts[imgId, catId]
234
+ dts = self._dts[imgId, catId]
235
+ inds = np.argsort([-d['score'] for d in dts], kind='mergesort')
236
+ dts = [dts[i] for i in inds]
237
+ if len(dts) > p.maxDets[-1]:
238
+ dts = dts[0:p.maxDets[-1]]
239
+ # if len(gts) == 0 and len(dts) == 0:
240
+ if len(gts) == 0 or len(dts) == 0:
241
+ _ious[part] = []
242
+ continue
243
+ ious = np.zeros((len(dts), len(gts)))
244
+ if len(gts[0]['keypoints']) == 136 * 3:
245
+ sigmas = np.array([.26, .25, .25, .35, .35, .79, .79, .72, .72, .62,.62, 1.07, 1.07, .87, .87, .89, .89, .8,.8,.8,.89, .89, .89, .89, .89, .89,
246
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15,
247
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15,
248
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15,
249
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15,
250
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15])/10.0
251
+ body_kpnum = 20
252
+ foot_kpnum = 6
253
+ face_kpnum = 68
254
+ hand_kpnum = 21 * 2
255
+
256
+ elif len(gts[0]['keypoints']) == 133 * 3: # surport for COCO WholeBody
257
+ sigmas = np.array([.26, .25, .25, .35, .35, .79, .79, .72, .72, .62,.62, 1.07, 1.07, .87, .87, .89, .89, .89, .89, .89, .89, .89, .89,
258
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15,
259
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15,
260
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15,
261
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15,
262
+ .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15, .15])/10.0
263
+ body_kpnum = 17
264
+ foot_kpnum = 6
265
+ face_kpnum = 68
266
+ hand_kpnum = 21 * 2
267
+
268
+ if part == 'body':
269
+ sigmas = sigmas[0 : body_kpnum]
270
+ elif part == 'foot':
271
+ sigmas = sigmas[body_kpnum : body_kpnum + foot_kpnum]
272
+ elif part == 'face':
273
+ sigmas = sigmas[body_kpnum + foot_kpnum : body_kpnum + foot_kpnum + face_kpnum]
274
+ elif part == 'hand':
275
+ sigmas = sigmas[body_kpnum + foot_kpnum + face_kpnum:]
276
+
277
+ vars = (sigmas * 2)**2
278
+ k = len(sigmas)
279
+ from tkinter import _flatten
280
+ # compute oks between each detection and ground truth object
281
+ for j, gt in enumerate(gts):
282
+ # create bounds for ignore regions(double the gt bbox)
283
+ if part == 'body':
284
+ g = np.array(gt['keypoints'][0 : body_kpnum*3],dtype=np.float32)
285
+ elif part == 'foot':
286
+ g = np.array(gt['keypoints'][body_kpnum*3 : (body_kpnum + foot_kpnum)*3],dtype=np.float32)
287
+ elif part == 'face':
288
+ g = np.array(gt['keypoints'][(body_kpnum + foot_kpnum)*3 : (body_kpnum + foot_kpnum + face_kpnum)*3],dtype=np.float32)
289
+ elif part == 'hand':
290
+ g = np.array(gt['keypoints'][(body_kpnum + foot_kpnum + face_kpnum)*3:],dtype=np.float32)
291
+ else:
292
+ g = np.array(gt['keypoints'],dtype=np.float32)
293
+
294
+ xg = g[0::3]; yg = g[1::3]; vg = g[2::3]
295
+ k1 = np.count_nonzero(vg > 0)
296
+ bb = gt['bbox']
297
+ x0 = bb[0] - bb[2]; x1 = bb[0] + bb[2] * 2
298
+ y0 = bb[1] - bb[3]; y1 = bb[1] + bb[3] * 2
299
+ for i, dt in enumerate(dts):
300
+ if part == 'body':
301
+ d = np.array(dt['keypoints'][0 : body_kpnum*3],dtype=np.float32)
302
+ elif part == 'foot':
303
+ d = np.array(dt['keypoints'][body_kpnum*3 : (body_kpnum + foot_kpnum)*3],dtype=np.float32)
304
+ elif part == 'face':
305
+ d = np.array(dt['keypoints'][(body_kpnum + foot_kpnum)*3 : (body_kpnum + foot_kpnum + face_kpnum)*3],dtype=np.float32)
306
+ elif part == 'hand':
307
+ d = np.array(dt['keypoints'][(body_kpnum + foot_kpnum + face_kpnum)*3:],dtype=np.float32)
308
+ else:
309
+ d = np.array(dt['keypoints'], dtype=np.float32)
310
+
311
+ xd = d[0::3]; yd = d[1::3]
312
+ if k1>0:
313
+ # measure the per-keypoint distance if keypoints visible
314
+ dx = xd - xg
315
+ dy = yd - yg
316
+ else:
317
+ # measure minimum distance to keypoints in (x0,y0) & (x1,y1)
318
+ z = np.zeros((k))
319
+ dx = np.max((z, x0-xd),axis=0)+np.max((z, xd-x1),axis=0)
320
+ dy = np.max((z, y0-yd),axis=0)+np.max((z, yd-y1),axis=0)
321
+ e = (dx**2 + dy**2) / vars / (gt['area']+np.spacing(1)) / 2
322
+ if k1 > 0:
323
+ e=e[vg > 0]
324
+ ious[i, j] = np.sum(np.exp(-e)) / e.shape[0]
325
+ _ious[part] = ious
326
+ return _ious
327
+
328
+ def evaluateImg(self, imgId, catId, aRng, maxDet):
329
+ '''
330
+ perform evaluation for single category and image
331
+ :return: dict (single image results)
332
+ '''
333
+ if self.params.iouType == 'keypoints':
334
+ parts = ['body', 'foot', 'face', 'hand', 'fullbody']
335
+ res = {}
336
+ for part in parts:
337
+ p = self.params
338
+ if p.useCats:
339
+ gt = self._gts[imgId,catId]
340
+ dt = self._dts[imgId,catId]
341
+ else:
342
+ gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]]
343
+ dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]]
344
+ if len(gt) == 0 and len(dt) ==0:
345
+ res[part] = None
346
+ continue
347
+
348
+ for g in gt:
349
+ if g['ignore'][part] or (g['area']<aRng[0] or g['area']>aRng[1]):
350
+ g['_ignore'] = 1
351
+ else:
352
+ g['_ignore'] = 0
353
+
354
+ # sort dt highest score first, sort gt ignore last
355
+ gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
356
+ gt = [gt[i] for i in gtind]
357
+ dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
358
+ dt = [dt[i] for i in dtind[0:maxDet]]
359
+ iscrowd = [int(o['iscrowd']) for o in gt]
360
+ # load computed ious
361
+ ious = self.ious[imgId, catId][part][:, gtind] if len(self.ious[imgId, catId][part]) > 0 else self.ious[imgId, catId][part]
362
+
363
+ T = len(p.iouThrs)
364
+ G = len(gt)
365
+ D = len(dt)
366
+ gtm = np.zeros((T,G))
367
+ dtm = np.zeros((T,D))
368
+ gtIg = np.array([g['_ignore'] for g in gt])
369
+ dtIg = np.zeros((T,D))
370
+ if not len(ious)==0:
371
+ for tind, t in enumerate(p.iouThrs):
372
+ for dind, d in enumerate(dt):
373
+ # information about best match so far (m=-1 -> unmatched)
374
+ iou = min([t,1-1e-10])
375
+ m = -1
376
+ for gind, g in enumerate(gt):
377
+ # if this gt already matched, and not a crowd, continue
378
+ if gtm[tind,gind]>0 and not iscrowd[gind]:
379
+ continue
380
+ # if dt matched to reg gt, and on ignore gt, stop
381
+ if m>-1 and gtIg[m]==0 and gtIg[gind]==1:
382
+ break
383
+ # continue to next gt unless better match made
384
+ if ious[dind,gind] < iou:
385
+ continue
386
+ # if match successful and best so far, store appropriately
387
+ iou=ious[dind,gind]
388
+ m=gind
389
+ # if match made store id of match for both dt and gt
390
+ if m ==-1:
391
+ continue
392
+ dtIg[tind,dind] = gtIg[m]
393
+ dtm[tind,dind] = gt[m]['id']
394
+ gtm[tind,m] = d['id']
395
+ # set unmatched detections outside of area range to ignore
396
+ a = np.array([d['area']<aRng[0] or d['area']>aRng[1] for d in dt]).reshape((1, len(dt)))
397
+ dtIg = np.logical_or(dtIg, np.logical_and(dtm==0, np.repeat(a,T,0)))
398
+ # store results for given image and category
399
+ res[part]={
400
+ 'image_id': imgId,
401
+ 'category_id': catId,
402
+ 'aRng': aRng,
403
+ 'maxDet': maxDet,
404
+ 'dtIds': [d['id'] for d in dt],
405
+ 'gtIds': [g['id'] for g in gt],
406
+ 'dtMatches': dtm,
407
+ 'gtMatches': gtm,
408
+ 'dtScores': [d['score'] for d in dt],
409
+ 'gtIgnore': gtIg,
410
+ 'dtIgnore': dtIg,
411
+ }
412
+ return res
413
+ else:
414
+ p = self.params
415
+ if p.useCats:
416
+ gt = self._gts[imgId,catId]
417
+ dt = self._dts[imgId,catId]
418
+ else:
419
+ gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]]
420
+ dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]]
421
+ if len(gt) == 0 and len(dt) ==0:
422
+ return None
423
+
424
+ for g in gt:
425
+ if g['ignore'] or (g['area']<aRng[0] or g['area']>aRng[1]):
426
+ g['_ignore'] = 1
427
+ else:
428
+ g['_ignore'] = 0
429
+
430
+ # sort dt highest score first, sort gt ignore last
431
+ gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
432
+ gt = [gt[i] for i in gtind]
433
+ dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
434
+ dt = [dt[i] for i in dtind[0:maxDet]]
435
+ iscrowd = [int(o['iscrowd']) for o in gt]
436
+ # load computed ious
437
+ ious = self.ious[imgId, catId][:, gtind] if len(self.ious[imgId, catId]) > 0 else self.ious[imgId, catId]
438
+
439
+ T = len(p.iouThrs)
440
+ G = len(gt)
441
+ D = len(dt)
442
+ gtm = np.zeros((T,G))
443
+ dtm = np.zeros((T,D))
444
+ gtIg = np.array([g['_ignore'] for g in gt])
445
+ dtIg = np.zeros((T,D))
446
+ if not len(ious)==0:
447
+ for tind, t in enumerate(p.iouThrs):
448
+ for dind, d in enumerate(dt):
449
+ # information about best match so far (m=-1 -> unmatched)
450
+ iou = min([t,1-1e-10])
451
+ m = -1
452
+ for gind, g in enumerate(gt):
453
+ # if this gt already matched, and not a crowd, continue
454
+ if gtm[tind,gind]>0 and not iscrowd[gind]:
455
+ continue
456
+ # if dt matched to reg gt, and on ignore gt, stop
457
+ if m>-1 and gtIg[m]==0 and gtIg[gind]==1:
458
+ break
459
+ # continue to next gt unless better match made
460
+ if ious[dind,gind] < iou:
461
+ continue
462
+ # if match successful and best so far, store appropriately
463
+ iou=ious[dind,gind]
464
+ m=gind
465
+ # if match made store id of match for both dt and gt
466
+ if m ==-1:
467
+ continue
468
+ dtIg[tind,dind] = gtIg[m]
469
+ dtm[tind,dind] = gt[m]['id']
470
+ gtm[tind,m] = d['id']
471
+ # set unmatched detections outside of area range to ignore
472
+ a = np.array([d['area']<aRng[0] or d['area']>aRng[1] for d in dt]).reshape((1, len(dt)))
473
+ dtIg = np.logical_or(dtIg, np.logical_and(dtm==0, np.repeat(a,T,0)))
474
+ # store results for given image and category
475
+ return {
476
+ 'image_id': imgId,
477
+ 'category_id': catId,
478
+ 'aRng': aRng,
479
+ 'maxDet': maxDet,
480
+ 'dtIds': [d['id'] for d in dt],
481
+ 'gtIds': [g['id'] for g in gt],
482
+ 'dtMatches': dtm,
483
+ 'gtMatches': gtm,
484
+ 'dtScores': [d['score'] for d in dt],
485
+ 'gtIgnore': gtIg,
486
+ 'dtIgnore': dtIg,
487
+ }
488
+ return res
489
+
490
+ def accumulate(self, p = None):
491
+ '''
492
+ Accumulate per image evaluation results and store the result in self.eval
493
+ :param p: input params for evaluation
494
+ :return: None
495
+ '''
496
+ print('Accumulating evaluation results...')
497
+ tic = time.time()
498
+ if not self.evalImgs:
499
+ print('Please run evaluate() first')
500
+ if self.params.iouType == 'keypoints':
501
+ parts = ['body', 'foot', 'face', 'hand', 'fullbody']
502
+ for part in parts:
503
+ # allows input customized parameters
504
+ if p is None:
505
+ p = self.params
506
+ p.catIds = p.catIds if p.useCats == 1 else [-1]
507
+ T = len(p.iouThrs)
508
+ R = len(p.recThrs)
509
+ K = len(p.catIds) if p.useCats else 1
510
+ A = len(p.areaRng)
511
+ M = len(p.maxDets)
512
+ precision = -np.ones((T,R,K,A,M)) # -1 for the precision of absent categories
513
+ recall = -np.ones((T,K,A,M))
514
+ scores = -np.ones((T,R,K,A,M))
515
+
516
+ # create dictionary for future indexing
517
+ _pe = self._paramsEval
518
+ catIds = _pe.catIds if _pe.useCats else [-1]
519
+ setK = set(catIds)
520
+ setA = set(map(tuple, _pe.areaRng))
521
+ setM = set(_pe.maxDets)
522
+ setI = set(_pe.imgIds)
523
+ # get inds to evaluate
524
+ k_list = [n for n, k in enumerate(p.catIds) if k in setK]
525
+ m_list = [m for n, m in enumerate(p.maxDets) if m in setM]
526
+ a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA]
527
+ i_list = [n for n, i in enumerate(p.imgIds) if i in setI]
528
+ I0 = len(_pe.imgIds)
529
+ A0 = len(_pe.areaRng)
530
+ # retrieve E at each category, area range, and max number of detections
531
+
532
+ for k, k0 in enumerate(k_list):
533
+ Nk = k0*A0*I0
534
+ for a, a0 in enumerate(a_list):
535
+ Na = a0*I0
536
+ for m, maxDet in enumerate(m_list):
537
+ E = [self.evalImgs[Nk + Na + i][part] for i in i_list]
538
+ E = [e for e in E if not e is None]
539
+ if len(E) == 0:
540
+ continue
541
+ dtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E])
542
+
543
+ # different sorting method generates slightly different results.
544
+ # mergesort is used to be consistent as Matlab implementation.
545
+ inds = np.argsort(-dtScores, kind='mergesort')
546
+ dtScoresSorted = dtScores[inds]
547
+
548
+ dtm = np.concatenate([e['dtMatches'][:,0:maxDet] for e in E], axis=1)[:,inds]
549
+ dtIg = np.concatenate([e['dtIgnore'][:,0:maxDet] for e in E], axis=1)[:,inds]
550
+ gtIg = np.concatenate([e['gtIgnore'] for e in E])
551
+ npig = np.count_nonzero(gtIg==0 )
552
+ if npig == 0:
553
+ continue
554
+ tps = np.logical_and( dtm, np.logical_not(dtIg) )
555
+ fps = np.logical_and(np.logical_not(dtm), np.logical_not(dtIg) )
556
+
557
+ tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float)
558
+ fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float)
559
+ for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):
560
+ tp = np.array(tp)
561
+ fp = np.array(fp)
562
+ nd = len(tp)
563
+ rc = tp / npig
564
+ pr = tp / (fp+tp+np.spacing(1))
565
+ q = np.zeros((R,))
566
+ ss = np.zeros((R,))
567
+
568
+ if nd:
569
+ recall[t,k,a,m] = rc[-1]
570
+ else:
571
+ recall[t,k,a,m] = 0
572
+
573
+ # numpy is slow without cython optimization for accessing elements
574
+ # use python array gets significant speed improvement
575
+ pr = pr.tolist(); q = q.tolist()
576
+
577
+ for i in range(nd-1, 0, -1):
578
+ if pr[i] > pr[i-1]:
579
+ pr[i-1] = pr[i]
580
+
581
+ inds = np.searchsorted(rc, p.recThrs, side='left')
582
+ try:
583
+ for ri, pi in enumerate(inds):
584
+ q[ri] = pr[pi]
585
+ ss[ri] = dtScoresSorted[pi]
586
+ except:
587
+ pass
588
+ precision[t,:,k,a,m] = np.array(q)
589
+ scores[t,:,k,a,m] = np.array(ss)
590
+ self.eval[part] = {
591
+ 'params': p,
592
+ 'counts': [T, R, K, A, M],
593
+ 'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
594
+ 'precision': precision,
595
+ 'recall': recall,
596
+ 'scores': scores,
597
+ }
598
+ else:
599
+ if p is None:
600
+ p = self.params
601
+ p.catIds = p.catIds if p.useCats == 1 else [-1]
602
+ T = len(p.iouThrs)
603
+ R = len(p.recThrs)
604
+ K = len(p.catIds) if p.useCats else 1
605
+ A = len(p.areaRng)
606
+ M = len(p.maxDets)
607
+ precision = -np.ones((T,R,K,A,M)) # -1 for the precision of absent categories
608
+ recall = -np.ones((T,K,A,M))
609
+ scores = -np.ones((T,R,K,A,M))
610
+
611
+ # create dictionary for future indexing
612
+ _pe = self._paramsEval
613
+ catIds = _pe.catIds if _pe.useCats else [-1]
614
+ setK = set(catIds)
615
+ setA = set(map(tuple, _pe.areaRng))
616
+ setM = set(_pe.maxDets)
617
+ setI = set(_pe.imgIds)
618
+ # get inds to evaluate
619
+ k_list = [n for n, k in enumerate(p.catIds) if k in setK]
620
+ m_list = [m for n, m in enumerate(p.maxDets) if m in setM]
621
+ a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA]
622
+ i_list = [n for n, i in enumerate(p.imgIds) if i in setI]
623
+ I0 = len(_pe.imgIds)
624
+ A0 = len(_pe.areaRng)
625
+ # retrieve E at each category, area range, and max number of detections
626
+
627
+ for k, k0 in enumerate(k_list):
628
+ Nk = k0*A0*I0
629
+ for a, a0 in enumerate(a_list):
630
+ Na = a0*I0
631
+ for m, maxDet in enumerate(m_list):
632
+ E = [self.evalImgs[Nk + Na + i] for i in i_list]
633
+ E = [e for e in E if not e is None]
634
+ if len(E) == 0:
635
+ continue
636
+ dtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E])
637
+
638
+ # different sorting method generates slightly different results.
639
+ # mergesort is used to be consistent as Matlab implementation.
640
+ inds = np.argsort(-dtScores, kind='mergesort')
641
+ dtScoresSorted = dtScores[inds]
642
+
643
+ dtm = np.concatenate([e['dtMatches'][:,0:maxDet] for e in E], axis=1)[:,inds]
644
+ dtIg = np.concatenate([e['dtIgnore'][:,0:maxDet] for e in E], axis=1)[:,inds]
645
+ gtIg = np.concatenate([e['gtIgnore'] for e in E])
646
+ npig = np.count_nonzero(gtIg==0 )
647
+ if npig == 0:
648
+ continue
649
+ tps = np.logical_and( dtm, np.logical_not(dtIg) )
650
+ fps = np.logical_and(np.logical_not(dtm), np.logical_not(dtIg) )
651
+
652
+ tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float)
653
+ fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float)
654
+ for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):
655
+ tp = np.array(tp)
656
+ fp = np.array(fp)
657
+ nd = len(tp)
658
+ rc = tp / npig
659
+ pr = tp / (fp+tp+np.spacing(1))
660
+ q = np.zeros((R,))
661
+ ss = np.zeros((R,))
662
+
663
+ if nd:
664
+ recall[t,k,a,m] = rc[-1]
665
+ else:
666
+ recall[t,k,a,m] = 0
667
+
668
+ # numpy is slow without cython optimization for accessing elements
669
+ # use python array gets significant speed improvement
670
+ pr = pr.tolist(); q = q.tolist()
671
+
672
+ for i in range(nd-1, 0, -1):
673
+ if pr[i] > pr[i-1]:
674
+ pr[i-1] = pr[i]
675
+
676
+ inds = np.searchsorted(rc, p.recThrs, side='left')
677
+ try:
678
+ for ri, pi in enumerate(inds):
679
+ q[ri] = pr[pi]
680
+ ss[ri] = dtScoresSorted[pi]
681
+ except:
682
+ pass
683
+ precision[t,:,k,a,m] = np.array(q)
684
+ scores[t,:,k,a,m] = np.array(ss)
685
+ self.eval = {
686
+ 'params': p,
687
+ 'counts': [T, R, K, A, M],
688
+ 'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
689
+ 'precision': precision,
690
+ 'recall': recall,
691
+ 'scores': scores,
692
+ }
693
+
694
+ toc = time.time()
695
+ print('DONE (t={:0.2f}s).'.format( toc-tic))
696
+
697
+ def summarize(self):
698
+ '''
699
+ Compute and display summary metrics for evaluation results.
700
+ Note this functin can *only* be applied on the default parameter setting
701
+ '''
702
+ def _summarize( ap=1, iouThr=None, areaRng='all', maxDets=100, part=None):
703
+ p = self.params
704
+ iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}'
705
+ titleStr = 'Average Precision' if ap == 1 else 'Average Recall'
706
+ typeStr = '(AP)' if ap==1 else '(AR)'
707
+ iouStr = '{:0.2f}:{:0.2f}'.format(p.iouThrs[0], p.iouThrs[-1]) \
708
+ if iouThr is None else '{:0.2f}'.format(iouThr)
709
+
710
+ aind = [i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng]
711
+ mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets]
712
+ if ap == 1:
713
+ # dimension of precision: [TxRxKxAxM]
714
+ if part:
715
+ s = self.eval[part]['precision']
716
+ else:
717
+ s = self.eval['precision']
718
+ # IoU
719
+ if iouThr is not None:
720
+ t = np.where(iouThr == p.iouThrs)[0]
721
+ s = s[t]
722
+ s = s[:,:,:,aind,mind]
723
+ else:
724
+ # dimension of recall: [TxKxAxM]
725
+ if part:
726
+ s = self.eval[part]['recall']
727
+ else:
728
+ s = self.eval['recall']
729
+ if iouThr is not None:
730
+ t = np.where(iouThr == p.iouThrs)[0]
731
+ s = s[t]
732
+ s = s[:,:,aind,mind]
733
+ if len(s[s>-1])==0:
734
+ mean_s = -1
735
+ else:
736
+ mean_s = np.mean(s[s>-1])
737
+ print(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s))
738
+ return mean_s
739
+ def _summarizeDets():
740
+ stats = np.zeros((12,))
741
+ stats[0] = _summarize(1)
742
+ stats[1] = _summarize(1, iouThr=.5, maxDets=self.params.maxDets[2])
743
+ stats[2] = _summarize(1, iouThr=.75, maxDets=self.params.maxDets[2])
744
+ stats[3] = _summarize(1, areaRng='small', maxDets=self.params.maxDets[2])
745
+ stats[4] = _summarize(1, areaRng='medium', maxDets=self.params.maxDets[2])
746
+ stats[5] = _summarize(1, areaRng='large', maxDets=self.params.maxDets[2])
747
+ stats[6] = _summarize(0, maxDets=self.params.maxDets[0])
748
+ stats[7] = _summarize(0, maxDets=self.params.maxDets[1])
749
+ stats[8] = _summarize(0, maxDets=self.params.maxDets[2])
750
+ stats[9] = _summarize(0, areaRng='small', maxDets=self.params.maxDets[2])
751
+ stats[10] = _summarize(0, areaRng='medium', maxDets=self.params.maxDets[2])
752
+ stats[11] = _summarize(0, areaRng='large', maxDets=self.params.maxDets[2])
753
+ return stats
754
+ def _summarizeKps(part='fullbody'):
755
+ stats = np.zeros((10,))
756
+ stats[0] = _summarize(1, maxDets=20, part=part)
757
+ stats[1] = _summarize(1, maxDets=20, iouThr=.5, part=part)
758
+ stats[2] = _summarize(1, maxDets=20, iouThr=.75, part=part)
759
+ stats[3] = _summarize(1, maxDets=20, areaRng='medium', part=part)
760
+ stats[4] = _summarize(1, maxDets=20, areaRng='large', part=part)
761
+ stats[5] = _summarize(0, maxDets=20, part=part)
762
+ stats[6] = _summarize(0, maxDets=20, iouThr=.5, part=part)
763
+ stats[7] = _summarize(0, maxDets=20, iouThr=.75, part=part)
764
+ stats[8] = _summarize(0, maxDets=20, areaRng='medium', part=part)
765
+ stats[9] = _summarize(0, maxDets=20, areaRng='large', part=part)
766
+ return {part: stats}
767
+ if not self.eval:
768
+ raise Exception('Please run accumulate() first')
769
+ iouType = self.params.iouType
770
+ if iouType == 'segm' or iouType == 'bbox':
771
+ summarize = _summarizeDets
772
+ self.stats = summarize()
773
+ elif iouType == 'keypoints':
774
+ summarize = _summarizeKps
775
+ parts = ['body', 'foot', 'face', 'hand', 'fullbody']
776
+ for part in parts:
777
+ print(part, ':')
778
+ self.stats.append(summarize(part))
779
+
780
+ def __str__(self):
781
+ self.summarize()
782
+
783
+ class Params:
784
+ '''
785
+ Params for coco evaluation api
786
+ '''
787
+ def setDetParams(self):
788
+ self.imgIds = []
789
+ self.catIds = []
790
+ # np.arange causes trouble. the data point on arange is slightly larger than the true value
791
+ self.iouThrs = np.linspace(.5, 0.95, int(np.round((0.95 - .5) / .05) + 1), endpoint=True)
792
+ self.recThrs = np.linspace(.0, 1.00, int(np.round((1.00 - .0) / .01) + 1), endpoint=True)
793
+ self.maxDets = [1, 10, 100]
794
+ self.areaRng = [[0 ** 2, 1e5 ** 2], [0 ** 2, 32 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]]
795
+ self.areaRngLbl = ['all', 'small', 'medium', 'large']
796
+ self.useCats = 1
797
+
798
+ def setKpParams(self):
799
+ self.imgIds = []
800
+ self.catIds = []
801
+ # np.arange causes trouble. the data point on arange is slightly larger than the true value
802
+ self.iouThrs = np.linspace(.5, 0.95, int(np.round((0.95 - .5) / .05) + 1), endpoint=True)
803
+ self.recThrs = np.linspace(.0, 1.00, int(np.round((1.00 - .0) / .01) + 1), endpoint=True)
804
+ self.maxDets = [20]
805
+ self.areaRng = [[0 ** 2, 1e5 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]]
806
+ self.areaRngLbl = ['all', 'medium', 'large']
807
+ self.useCats = 1
808
+
809
+ def __init__(self, iouType='segm'):
810
+ if iouType == 'segm' or iouType == 'bbox':
811
+ self.setDetParams()
812
+ elif iouType == 'keypoints':
813
+ self.setKpParams()
814
+ else:
815
+ raise Exception('iouType not supported')
816
+ self.iouType = iouType
817
+ # useSegm is deprecated
818
+ self.useSegm = None