| | |
| | |
| | |
| | __author__ = 'ychfan' |
| |
|
| | import copy |
| | import datetime |
| | import time |
| | from collections import defaultdict |
| |
|
| | import numpy as np |
| | from pycocotools import mask as maskUtils |
| |
|
| |
|
| | class YTVISeval: |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | def __init__(self, cocoGt=None, cocoDt=None, iouType='segm'): |
| | """Initialize CocoEval using coco APIs for gt and dt. |
| | |
| | :param cocoGt: coco object with ground truth annotations |
| | :param cocoDt: coco object with detection results |
| | :return: None |
| | """ |
| | if not iouType: |
| | print('iouType not specified. use default iouType segm') |
| | self.cocoGt = cocoGt |
| | self.cocoDt = cocoDt |
| | self.params = {} |
| | self.evalVids = defaultdict( |
| | list) |
| | self.eval = {} |
| | self._gts = defaultdict(list) |
| | self._dts = defaultdict(list) |
| | self.params = Params(iouType=iouType) |
| | self._paramsEval = {} |
| | self.stats = [] |
| | self.ious = {} |
| | if cocoGt is not None: |
| | self.params.vidIds = sorted(cocoGt.getVidIds()) |
| | self.params.catIds = sorted(cocoGt.getCatIds()) |
| |
|
| | def _prepare(self): |
| | ''' |
| | Prepare ._gts and ._dts for evaluation based on params |
| | :return: None |
| | ''' |
| |
|
| | def _toMask(anns, coco): |
| | |
| | for ann in anns: |
| | for i, a in enumerate(ann['segmentations']): |
| | if a: |
| | rle = coco.annToRLE(ann, i) |
| | ann['segmentations'][i] = rle |
| | l_ori = [a for a in ann['areas'] if a] |
| | if len(l_ori) == 0: |
| | ann['avg_area'] = 0 |
| | else: |
| | ann['avg_area'] = np.array(l_ori).mean() |
| |
|
| | p = self.params |
| | if p.useCats: |
| | gts = self.cocoGt.loadAnns( |
| | self.cocoGt.getAnnIds(vidIds=p.vidIds, catIds=p.catIds)) |
| | dts = self.cocoDt.loadAnns( |
| | self.cocoDt.getAnnIds(vidIds=p.vidIds, catIds=p.catIds)) |
| | else: |
| | gts = self.cocoGt.loadAnns(self.cocoGt.getAnnIds(vidIds=p.vidIds)) |
| | dts = self.cocoDt.loadAnns(self.cocoDt.getAnnIds(vidIds=p.vidIds)) |
| |
|
| | |
| | if p.iouType == 'segm': |
| | _toMask(gts, self.cocoGt) |
| | _toMask(dts, self.cocoDt) |
| | |
| | for gt in gts: |
| | gt['ignore'] = gt['ignore'] if 'ignore' in gt else 0 |
| | gt['ignore'] = 'iscrowd' in gt and gt['iscrowd'] |
| | if p.iouType == 'keypoints': |
| | gt['ignore'] = (gt['num_keypoints'] == 0) or gt['ignore'] |
| | self._gts = defaultdict(list) |
| | self._dts = defaultdict(list) |
| | for gt in gts: |
| | self._gts[gt['video_id'], gt['category_id']].append(gt) |
| | for dt in dts: |
| | self._dts[dt['video_id'], dt['category_id']].append(dt) |
| | self.evalVids = defaultdict( |
| | list) |
| | self.eval = {} |
| |
|
| | def evaluate(self): |
| | ''' |
| | Run per image evaluation on given images and store |
| | results (a list of dict) in self.evalVids |
| | :return: None |
| | ''' |
| | tic = time.time() |
| | print('Running per image evaluation...') |
| | p = self.params |
| | |
| | if p.useSegm is not None: |
| | p.iouType = 'segm' if p.useSegm == 1 else 'bbox' |
| | print('useSegm (deprecated) is not None. Running {} evaluation'. |
| | format(p.iouType)) |
| | print('Evaluate annotation type *{}*'.format(p.iouType)) |
| | p.vidIds = list(np.unique(p.vidIds)) |
| | if p.useCats: |
| | p.catIds = list(np.unique(p.catIds)) |
| | p.maxDets = sorted(p.maxDets) |
| | self.params = p |
| |
|
| | self._prepare() |
| | |
| | catIds = p.catIds if p.useCats else [-1] |
| |
|
| | if p.iouType == 'segm' or p.iouType == 'bbox': |
| | computeIoU = self.computeIoU |
| | elif p.iouType == 'keypoints': |
| | computeIoU = self.computeOks |
| | self.ious = {(vidId, catId): computeIoU(vidId, catId) |
| | for vidId in p.vidIds for catId in catIds} |
| |
|
| | evaluateVid = self.evaluateVid |
| | maxDet = p.maxDets[-1] |
| |
|
| | self.evalImgs = [ |
| | evaluateVid(vidId, catId, areaRng, maxDet) for catId in catIds |
| | for areaRng in p.areaRng for vidId in p.vidIds |
| | ] |
| | self._paramsEval = copy.deepcopy(self.params) |
| | toc = time.time() |
| | print('DONE (t={:0.2f}s).'.format(toc - tic)) |
| |
|
| | def computeIoU(self, vidId, catId): |
| | p = self.params |
| | if p.useCats: |
| | gt = self._gts[vidId, catId] |
| | dt = self._dts[vidId, catId] |
| | else: |
| | gt = [_ for cId in p.catIds for _ in self._gts[vidId, cId]] |
| | dt = [_ for cId in p.catIds for _ in self._dts[vidId, cId]] |
| | if len(gt) == 0 and len(dt) == 0: |
| | return [] |
| | inds = np.argsort([-d['score'] for d in dt], kind='mergesort') |
| | dt = [dt[i] for i in inds] |
| | if len(dt) > p.maxDets[-1]: |
| | dt = dt[0:p.maxDets[-1]] |
| |
|
| | if p.iouType == 'segm': |
| | g = [g['segmentations'] for g in gt] |
| | d = [d['segmentations'] for d in dt] |
| | elif p.iouType == 'bbox': |
| | g = [g['bboxes'] for g in gt] |
| | d = [d['bboxes'] for d in dt] |
| | else: |
| | raise Exception('unknown iouType for iou computation') |
| |
|
| | |
| |
|
| | def iou_seq(d_seq, g_seq): |
| | i = .0 |
| | u = .0 |
| | for d, g in zip(d_seq, g_seq): |
| | if d and g: |
| | i += maskUtils.area(maskUtils.merge([d, g], True)) |
| | u += maskUtils.area(maskUtils.merge([d, g], False)) |
| | elif not d and g: |
| | u += maskUtils.area(g) |
| | elif d and not g: |
| | u += maskUtils.area(d) |
| | if not u > .0: |
| | print('Mask sizes in video {} and category {} may not match!'. |
| | format(vidId, catId)) |
| | iou = i / u if u > .0 else .0 |
| | return iou |
| |
|
| | ious = np.zeros([len(d), len(g)]) |
| | for i, j in np.ndindex(ious.shape): |
| | ious[i, j] = iou_seq(d[i], g[j]) |
| |
|
| | return ious |
| |
|
| | def computeOks(self, imgId, catId): |
| | p = self.params |
| |
|
| | gts = self._gts[imgId, catId] |
| | dts = self._dts[imgId, catId] |
| | inds = np.argsort([-d['score'] for d in dts], kind='mergesort') |
| | dts = [dts[i] for i in inds] |
| | if len(dts) > p.maxDets[-1]: |
| | dts = dts[0:p.maxDets[-1]] |
| | |
| | if len(gts) == 0 or len(dts) == 0: |
| | return [] |
| | ious = np.zeros((len(dts), len(gts))) |
| | sigmas = np.array([ |
| | .26, .25, .25, .35, .35, .79, .79, .72, .72, .62, .62, 1.07, 1.07, |
| | .87, .87, .89, .89 |
| | ]) / 10.0 |
| | vars = (sigmas * 2)**2 |
| | k = len(sigmas) |
| | |
| | for j, gt in enumerate(gts): |
| | |
| | g = np.array(gt['keypoints']) |
| | xg = g[0::3] |
| | yg = g[1::3] |
| | vg = g[2::3] |
| | k1 = np.count_nonzero(vg > 0) |
| | bb = gt['bbox'] |
| | x0 = bb[0] - bb[2] |
| | x1 = bb[0] + bb[2] * 2 |
| | y0 = bb[1] - bb[3] |
| | y1 = bb[1] + bb[3] * 2 |
| | for i, dt in enumerate(dts): |
| | d = np.array(dt['keypoints']) |
| | xd = d[0::3] |
| | yd = d[1::3] |
| | if k1 > 0: |
| | |
| | dx = xd - xg |
| | dy = yd - yg |
| | else: |
| | |
| | z = np.zeros((k)) |
| | dx = np.max((z, x0 - xd), axis=0) + np.max( |
| | (z, xd - x1), axis=0) |
| | dy = np.max((z, y0 - yd), axis=0) + np.max( |
| | (z, yd - y1), axis=0) |
| | e = (dx**2 + dy**2) / vars / (gt['avg_area'] + |
| | np.spacing(1)) / 2 |
| | if k1 > 0: |
| | e = e[vg > 0] |
| | ious[i, j] = np.sum(np.exp(-e)) / e.shape[0] |
| | return ious |
| |
|
| | def evaluateVid(self, vidId, catId, aRng, maxDet): |
| | ''' |
| | perform evaluation for single category and image |
| | :return: dict (single image results) |
| | ''' |
| | p = self.params |
| | if p.useCats: |
| | gt = self._gts[vidId, catId] |
| | dt = self._dts[vidId, catId] |
| | else: |
| | gt = [_ for cId in p.catIds for _ in self._gts[vidId, cId]] |
| | dt = [_ for cId in p.catIds for _ in self._dts[vidId, cId]] |
| | if len(gt) == 0 and len(dt) == 0: |
| | return None |
| |
|
| | for g in gt: |
| | if g['ignore'] or (g['avg_area'] < aRng[0] |
| | or g['avg_area'] > aRng[1]): |
| | g['_ignore'] = 1 |
| | else: |
| | g['_ignore'] = 0 |
| |
|
| | |
| | gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort') |
| | gt = [gt[i] for i in gtind] |
| | dtind = np.argsort([-d['score'] for d in dt], kind='mergesort') |
| | dt = [dt[i] for i in dtind[0:maxDet]] |
| | iscrowd = [int(o['iscrowd']) for o in gt] |
| | |
| | ious = self.ious[vidId, catId][:, gtind] if len( |
| | self.ious[vidId, catId]) > 0 else self.ious[vidId, catId] |
| |
|
| | T = len(p.iouThrs) |
| | G = len(gt) |
| | D = len(dt) |
| | gtm = np.zeros((T, G)) |
| | dtm = np.zeros((T, D)) |
| | gtIg = np.array([g['_ignore'] for g in gt]) |
| | dtIg = np.zeros((T, D)) |
| | if not len(ious) == 0: |
| | for tind, t in enumerate(p.iouThrs): |
| | for dind, d in enumerate(dt): |
| | |
| | iou = min([t, 1 - 1e-10]) |
| | m = -1 |
| | for gind, g in enumerate(gt): |
| | |
| | if gtm[tind, gind] > 0 and not iscrowd[gind]: |
| | continue |
| | |
| | if m > -1 and gtIg[m] == 0 and gtIg[gind] == 1: |
| | break |
| | |
| | if ious[dind, gind] < iou: |
| | continue |
| | |
| | |
| | iou = ious[dind, gind] |
| | m = gind |
| | |
| | if m == -1: |
| | continue |
| | dtIg[tind, dind] = gtIg[m] |
| | dtm[tind, dind] = gt[m]['id'] |
| | gtm[tind, m] = d['id'] |
| | |
| | a = np.array([ |
| | d['avg_area'] < aRng[0] or d['avg_area'] > aRng[1] for d in dt |
| | ]).reshape((1, len(dt))) |
| | dtIg = np.logical_or(dtIg, np.logical_and(dtm == 0, np.repeat(a, T, |
| | 0))) |
| | |
| | return { |
| | 'video_id': vidId, |
| | 'category_id': catId, |
| | 'aRng': aRng, |
| | 'maxDet': maxDet, |
| | 'dtIds': [d['id'] for d in dt], |
| | 'gtIds': [g['id'] for g in gt], |
| | 'dtMatches': dtm, |
| | 'gtMatches': gtm, |
| | 'dtScores': [d['score'] for d in dt], |
| | 'gtIgnore': gtIg, |
| | 'dtIgnore': dtIg, |
| | } |
| |
|
| | def accumulate(self, p=None): |
| | """Accumulate per image evaluation results and store the result in |
| | self.eval. |
| | |
| | :param p: input params for evaluation |
| | :return: None |
| | """ |
| | print('Accumulating evaluation results...') |
| | tic = time.time() |
| | if not self.evalImgs: |
| | print('Please run evaluate() first') |
| | |
| | if p is None: |
| | p = self.params |
| | p.catIds = p.catIds if p.useCats == 1 else [-1] |
| | T = len(p.iouThrs) |
| | R = len(p.recThrs) |
| | K = len(p.catIds) if p.useCats else 1 |
| | A = len(p.areaRng) |
| | M = len(p.maxDets) |
| | precision = -np.ones( |
| | (T, R, K, A, M)) |
| | recall = -np.ones((T, K, A, M)) |
| | scores = -np.ones((T, R, K, A, M)) |
| |
|
| | |
| | _pe = self._paramsEval |
| | catIds = _pe.catIds if _pe.useCats else [-1] |
| | setK = set(catIds) |
| | setA = set(map(tuple, _pe.areaRng)) |
| | setM = set(_pe.maxDets) |
| | setI = set(_pe.vidIds) |
| | |
| | k_list = [n for n, k in enumerate(p.catIds) if k in setK] |
| | m_list = [m for n, m in enumerate(p.maxDets) if m in setM] |
| | a_list = [ |
| | n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) |
| | if a in setA |
| | ] |
| | i_list = [n for n, i in enumerate(p.vidIds) if i in setI] |
| | I0 = len(_pe.vidIds) |
| | A0 = len(_pe.areaRng) |
| | |
| | for k, k0 in enumerate(k_list): |
| | Nk = k0 * A0 * I0 |
| | for a, a0 in enumerate(a_list): |
| | Na = a0 * I0 |
| | for m, maxDet in enumerate(m_list): |
| | E = [self.evalImgs[Nk + Na + i] for i in i_list] |
| | E = [e for e in E if e is not None] |
| | if len(E) == 0: |
| | continue |
| | dtScores = np.concatenate( |
| | [e['dtScores'][0:maxDet] for e in E]) |
| |
|
| | inds = np.argsort(-dtScores, kind='mergesort') |
| | dtScoresSorted = dtScores[inds] |
| |
|
| | dtm = np.concatenate( |
| | [e['dtMatches'][:, 0:maxDet] for e in E], axis=1)[:, |
| | inds] |
| | dtIg = np.concatenate( |
| | [e['dtIgnore'][:, 0:maxDet] for e in E], axis=1)[:, |
| | inds] |
| | gtIg = np.concatenate([e['gtIgnore'] for e in E]) |
| | npig = np.count_nonzero(gtIg == 0) |
| | if npig == 0: |
| | continue |
| | tps = np.logical_and(dtm, np.logical_not(dtIg)) |
| | fps = np.logical_and( |
| | np.logical_not(dtm), np.logical_not(dtIg)) |
| |
|
| | tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float) |
| | fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float) |
| | for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)): |
| | tp = np.array(tp) |
| | fp = np.array(fp) |
| | nd_ori = len(tp) |
| | rc = tp / npig |
| | pr = tp / (fp + tp + np.spacing(1)) |
| | q = np.zeros((R, )) |
| | ss = np.zeros((R, )) |
| |
|
| | if nd_ori: |
| | recall[t, k, a, m] = rc[-1] |
| | else: |
| | recall[t, k, a, m] = 0 |
| |
|
| | |
| | pr = pr.tolist() |
| | q = q.tolist() |
| |
|
| | for i in range(nd_ori - 1, 0, -1): |
| | if pr[i] > pr[i - 1]: |
| | pr[i - 1] = pr[i] |
| |
|
| | inds = np.searchsorted(rc, p.recThrs, side='left') |
| | try: |
| | for ri, pi in enumerate(inds): |
| | q[ri] = pr[pi] |
| | ss[ri] = dtScoresSorted[pi] |
| | except Exception: |
| | pass |
| | precision[t, :, k, a, m] = np.array(q) |
| | scores[t, :, k, a, m] = np.array(ss) |
| | self.eval = { |
| | 'params': p, |
| | 'counts': [T, R, K, A, M], |
| | 'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), |
| | 'precision': precision, |
| | 'recall': recall, |
| | 'scores': scores, |
| | } |
| | toc = time.time() |
| | print('DONE (t={:0.2f}s).'.format(toc - tic)) |
| |
|
| | def summarize(self): |
| | """Compute and display summary metrics for evaluation results. |
| | |
| | Note this function can *only* be applied on the default parameter |
| | setting |
| | """ |
| |
|
| | def _summarize(ap=1, iouThr=None, areaRng='all', maxDets=100): |
| | p = self.params |
| | iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6s} | ' \ |
| | 'maxDets={:>3d} ] = {:0.3f}' |
| | titleStr = 'Average Precision' if ap == 1 else 'Average Recall' |
| | typeStr = '(AP)' if ap == 1 else '(AR)' |
| | iouStr = '{:0.2f}:{:0.2f}'.format(p.iouThrs[0], p.iouThrs[-1]) \ |
| | if iouThr is None else '{:0.2f}'.format(iouThr) |
| |
|
| | aind = [ |
| | i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng |
| | ] |
| | mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets] |
| | if ap == 1: |
| | |
| | s = self.eval['precision'] |
| | |
| | if iouThr is not None: |
| | t = np.where(iouThr == p.iouThrs)[0] |
| | s = s[t] |
| | s = s[:, :, :, aind, mind] |
| | else: |
| | |
| | s = self.eval['recall'] |
| | if iouThr is not None: |
| | t = np.where(iouThr == p.iouThrs)[0] |
| | s = s[t] |
| | s = s[:, :, aind, mind] |
| | if len(s[s > -1]) == 0: |
| | mean_s = -1 |
| | else: |
| | mean_s = np.mean(s[s > -1]) |
| | print( |
| | iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, |
| | mean_s)) |
| | return mean_s |
| |
|
| | def _summarizeDets(): |
| | stats = np.zeros((12, )) |
| | stats[0] = _summarize(1) |
| | stats[1] = _summarize(1, iouThr=.5, maxDets=self.params.maxDets[2]) |
| | stats[2] = _summarize( |
| | 1, iouThr=.75, maxDets=self.params.maxDets[2]) |
| | stats[3] = _summarize( |
| | 1, areaRng='small', maxDets=self.params.maxDets[2]) |
| | stats[4] = _summarize( |
| | 1, areaRng='medium', maxDets=self.params.maxDets[2]) |
| | stats[5] = _summarize( |
| | 1, areaRng='large', maxDets=self.params.maxDets[2]) |
| | stats[6] = _summarize(0, maxDets=self.params.maxDets[0]) |
| | stats[7] = _summarize(0, maxDets=self.params.maxDets[1]) |
| | stats[8] = _summarize(0, maxDets=self.params.maxDets[2]) |
| | stats[9] = _summarize( |
| | 0, areaRng='small', maxDets=self.params.maxDets[2]) |
| | stats[10] = _summarize( |
| | 0, areaRng='medium', maxDets=self.params.maxDets[2]) |
| | stats[11] = _summarize( |
| | 0, areaRng='large', maxDets=self.params.maxDets[2]) |
| | return stats |
| |
|
| | def _summarizeKps(): |
| | stats = np.zeros((10, )) |
| | stats[0] = _summarize(1, maxDets=20) |
| | stats[1] = _summarize(1, maxDets=20, iouThr=.5) |
| | stats[2] = _summarize(1, maxDets=20, iouThr=.75) |
| | stats[3] = _summarize(1, maxDets=20, areaRng='medium') |
| | stats[4] = _summarize(1, maxDets=20, areaRng='large') |
| | stats[5] = _summarize(0, maxDets=20) |
| | stats[6] = _summarize(0, maxDets=20, iouThr=.5) |
| | stats[7] = _summarize(0, maxDets=20, iouThr=.75) |
| | stats[8] = _summarize(0, maxDets=20, areaRng='medium') |
| | stats[9] = _summarize(0, maxDets=20, areaRng='large') |
| | return stats |
| |
|
| | if not self.eval: |
| | raise Exception('Please run accumulate() first') |
| | iouType = self.params.iouType |
| | if iouType == 'segm' or iouType == 'bbox': |
| | summarize = _summarizeDets |
| | elif iouType == 'keypoints': |
| | summarize = _summarizeKps |
| | self.stats = summarize() |
| |
|
| | def __str__(self): |
| | self.summarize() |
| |
|
| |
|
| | class Params: |
| | """Params for coco evaluation api.""" |
| |
|
| | def setDetParams(self): |
| | self.vidIds = [] |
| | self.catIds = [] |
| | |
| | |
| | self.iouThrs = np.linspace( |
| | .5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True) |
| | self.recThrs = np.linspace( |
| | .0, 1.00, int(np.round((1.00 - .0) / .01)) + 1, endpoint=True) |
| | self.maxDets = [1, 10, 100] |
| | self.areaRng = [[0**2, 1e5**2], [0**2, 128**2], [128**2, 256**2], |
| | [256**2, 1e5**2]] |
| | self.areaRngLbl = ['all', 'small', 'medium', 'large'] |
| | self.useCats = 1 |
| |
|
| | def setKpParams(self): |
| | self.vidIds = [] |
| | self.catIds = [] |
| | |
| | |
| | self.iouThrs = np.linspace( |
| | .5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True) |
| | self.recThrs = np.linspace( |
| | .0, 1.00, int(np.round((1.00 - .0) / .01)) + 1, endpoint=True) |
| | self.maxDets = [20] |
| | self.areaRng = [[0**2, 1e5**2], [32**2, 96**2], [96**2, 1e5**2]] |
| | self.areaRngLbl = ['all', 'medium', 'large'] |
| | self.useCats = 1 |
| |
|
| | def __init__(self, iouType='segm'): |
| | if iouType == 'segm' or iouType == 'bbox': |
| | self.setDetParams() |
| | elif iouType == 'keypoints': |
| | self.setKpParams() |
| | else: |
| | raise Exception('iouType not supported') |
| | self.iouType = iouType |
| | |
| | self.useSegm = None |
| |
|