├── .gitignore ├── PythonAPI ├── Makefile ├── __init__.py ├── myAmodalDemo.ipynb ├── myPyAmodalEvalDemo.py ├── pycocotools │ ├── __init__.py │ ├── _mask.pyx │ ├── _mask.so │ ├── amodal.py │ ├── amodaleval.py │ ├── coco.py │ ├── cocoeval.py │ └── mask.py └── setup.py ├── README.md ├── batchEval.py ├── common ├── gason.cpp ├── gason.h ├── maskApi.c └── maskApi.h └── eval.sh /.gitignore: -------------------------------------------------------------------------------- 1 | images/ 2 | bsds_images/ 3 | annotations/ 4 | 5 | PythonAPI/.ipynb_checkpoints/ 6 | PythonAPI/*pyc 7 | PythonAPI/pycocotools/*pyc 8 | -------------------------------------------------------------------------------- /PythonAPI/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | # install pycocotools locally 3 | python setup.py build_ext --inplace 4 | rm -rf build 5 | 6 | install: 7 | # install pycocotools to the Python site-packages 8 | python setup.py build_ext install 9 | rm -rf build -------------------------------------------------------------------------------- /PythonAPI/__init__.py: -------------------------------------------------------------------------------- 1 | # empty 2 | -------------------------------------------------------------------------------- /PythonAPI/myPyAmodalEvalDemo.py: -------------------------------------------------------------------------------- 1 | from pycocotools.amodal import Amodal 2 | from pycocotools.amodaleval import AmodalEval 3 | import numpy as np 4 | import json 5 | import os,sys 6 | import glob 7 | import pdb 8 | dd = pdb.set_trace 9 | 10 | import argparse 11 | def parse_args(args): 12 | parser = argparse.ArgumentParser(description='') 13 | parser.add_argument('--useAmodalGT', type=int, default=1, help='1 use amodal GT; 0 use visible GT.') 14 | parser.add_argument('--useAmodalDT', type=int, default=0, help='1 use amodal DT; 0 use modal DT.') 15 | parser.add_argument('--onlyThings', type=int, default=1, help='2: stuff only; 1: things only; 0: both') 16 | parser.add_argument('--occRange', type=str, default='all', help='support slight, medium, severe and all') 17 | parser.add_argument('--maxProp', type=int, default=100, help='support slight, medium, severe and all') 18 | params = parser.parse_args(args) 19 | return params 20 | 21 | def createAmodalRegion(ann, id): 22 | region = {} 23 | region['id'] = id #used for gt/dt matching 24 | region['segmentation'] = ann['segmentation'] 25 | region['score'] = ann['score'] 26 | region['isStuff'] = 0 # default things 27 | if 'foreground_ness' in ann: 28 | region['foreground_ness'] = ann['foreground_ness'] 29 | if 'invisibleMask' in ann: 30 | region['invisible_mask'] = ann['invisibleMask'] 31 | if 'amodalMask' in ann: 32 | region['amodal_mask'] = ann['amodalMask'] 33 | return region 34 | 35 | def createAmodalAnn(image_id, ann_id): 36 | ann = {} 37 | ann['id'] = ann_id 38 | ann['category_id'] = 1 # fake label 39 | ann['image_id'] = image_id 40 | ann['regions'] =[] 41 | return ann 42 | 43 | # filter coco dt, according to amodal gt imgId 44 | # coco dt is formated as instance. This function transform data into image_id view 45 | def filterDtFile(resFiles, amodalGtImgIds): 46 | amodalDt = {} 47 | id = 0 48 | ann_id = 0 49 | for i, file in enumerate(resFiles): 50 | print "processing json %d in total %d" %(i+1, len(resFiles)) 51 | anns = json.load(open(file)) 52 | for ann in anns: 53 | image_id = ann['image_id'] 54 | if image_id in amodalGtImgIds: 55 | id = id + 1 56 | if image_id not in amodalDt: 57 | amodalDt[image_id] = createAmodalAnn(image_id, ann_id) 58 | ann_id = ann_id + 1 59 | region = createAmodalRegion(ann, id) 60 | amodalDt[image_id]['regions'].append(region) 61 | res = [] 62 | for image_id, ann in amodalDt.iteritems(): 63 | res.append(ann) 64 | return res 65 | 66 | def evalWrapper(amodalDt, amodalGt, useAmodalGT, useAmodalDT, onlyThings, occRange, maxProp): 67 | annType = 'segm' 68 | imgIds=sorted(amodalGt.getImgIds()) 69 | 70 | amodalEval = AmodalEval(amodalGt,amodalDt) 71 | amodalEval.params.imgIds = imgIds 72 | amodalEval.params.useSegm = (annType == 'segm') 73 | if maxProp == 1000: 74 | amodalEval.params.maxDets = [1,10,100,1000] 75 | amodalEval.params.useAmodalGT = useAmodalGT # 1: use amodal GT; 0 use visible GT 76 | amodalEval.params.useAmodalDT = useAmodalDT # 1: use amodal DT; 0 use modal DT 77 | amodalEval.params.onlyThings = onlyThings # 2: stuff only; 1: things only; 0: both, 3: only select stuff vs things in constraints 78 | 79 | if occRange == 'all': 80 | amodalEval.params.occRng = [0, 1] 81 | elif occRange == 'none': 82 | amodalEval.params.occRng = [0, 0.00001] # only non occlusion regions 83 | elif occRange == 'partial': 84 | amodalEval.params.occRng = [0.00001, 0.25] # exclude non-occluding regions 85 | elif occRange == 'heavy': 86 | amodalEval.params.occRng = [0.25, 1] 87 | else: 88 | raise Exception('invalid occRange') 89 | 90 | amodalEval.evaluate() 91 | #annotatedGT = amodalEval.exportDtFile(matchedDtFile) 92 | amodalEval.accumulate() 93 | stats = amodalEval.summarize() 94 | return amodalEval.stats 95 | 96 | if __name__ == "__main__": 97 | args=parse_args(sys.argv[1:]) 98 | stats = evalWrapper(**vars(args)) 99 | -------------------------------------------------------------------------------- /PythonAPI/pycocotools/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'tylin' 2 | -------------------------------------------------------------------------------- /PythonAPI/pycocotools/_mask.pyx: -------------------------------------------------------------------------------- 1 | # distutils: language = c 2 | # distutils: sources = ../common/maskApi.c 3 | 4 | #************************************************************************** 5 | # Microsoft COCO Toolbox. version 2.0 6 | # Data, paper, and tutorials available at: http://mscoco.org/ 7 | # Code written by Piotr Dollar and Tsung-Yi Lin, 2015. 8 | # Licensed under the Simplified BSD License [see coco/license.txt] 9 | #************************************************************************** 10 | 11 | __author__ = 'tsungyi' 12 | 13 | # import both Python-level and C-level symbols of Numpy 14 | # the API uses Numpy to interface C and Python 15 | import numpy as np 16 | cimport numpy as np 17 | from libc.stdlib cimport malloc, free 18 | # intialized Numpy. must do. 19 | np.import_array() 20 | 21 | # import numpy C function 22 | # we use PyArray_ENABLEFLAGS to make Numpy ndarray responsible to memoery management 23 | cdef extern from "numpy/arrayobject.h": 24 | void PyArray_ENABLEFLAGS(np.ndarray arr, int flags) 25 | 26 | # Declare the prototype of the C functions in MaskApi.h 27 | cdef extern from "maskApi.h": 28 | ctypedef unsigned int uint 29 | ctypedef unsigned long siz 30 | ctypedef unsigned char byte 31 | ctypedef double* BB 32 | ctypedef struct RLE: 33 | siz h, 34 | siz w, 35 | siz m, 36 | uint* cnts, 37 | void rlesInit( RLE **R, siz n ) 38 | void rleEncode( RLE *R, const byte *M, siz h, siz w, siz n ) 39 | void rleDecode( const RLE *R, byte *mask, siz n ) 40 | void rleMerge( const RLE *R, RLE *M, siz n, bint intersect ) 41 | void rleMinus( const RLE *R, RLE *M, siz n) 42 | void rleArea( const RLE *R, siz n, uint *a ) 43 | void rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o ) 44 | void bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o ) 45 | void rleToBbox( const RLE *R, BB bb, siz n ) 46 | void rleFrBbox( RLE *R, const BB bb, siz h, siz w, siz n ) 47 | void rleFrPoly( RLE *R, const double *xy, siz k, siz h, siz w ) 48 | char* rleToString( const RLE *R ) 49 | void rleFrString( RLE *R, char *s, siz h, siz w ) 50 | 51 | # python class to wrap RLE array in C 52 | # the class handles the memory allocation and deallocation 53 | cdef class RLEs: 54 | cdef RLE *_R 55 | cdef siz _n 56 | 57 | def __cinit__(self, siz n =0): 58 | rlesInit(&self._R, n) 59 | self._n = n 60 | 61 | # free the RLE array here 62 | def __dealloc__(self): 63 | if self._R is not NULL: 64 | for i in range(self._n): 65 | free(self._R[i].cnts) 66 | free(self._R) 67 | def __getattr__(self, key): 68 | if key == 'n': 69 | return self._n 70 | raise AttributeError(key) 71 | 72 | # python class to wrap Mask array in C 73 | # the class handles the memory allocation and deallocation 74 | cdef class Masks: 75 | cdef byte *_mask 76 | cdef siz _h 77 | cdef siz _w 78 | cdef siz _n 79 | 80 | def __cinit__(self, h, w, n): 81 | self._mask = malloc(h*w*n* sizeof(byte)) 82 | self._h = h 83 | self._w = w 84 | self._n = n 85 | # def __dealloc__(self): 86 | # the memory management of _mask has been passed to np.ndarray 87 | # it doesn't need to be freed here 88 | 89 | # called when passing into np.array() and return an np.ndarray in column-major order 90 | def __array__(self): 91 | cdef np.npy_intp shape[1] 92 | shape[0] = self._h*self._w*self._n 93 | # Create a 1D array, and reshape it to fortran/Matlab column-major array 94 | ndarray = np.PyArray_SimpleNewFromData(1, shape, np.NPY_UINT8, self._mask).reshape((self._h, self._w, self._n), order='F') 95 | # The _mask allocated by Masks is now handled by ndarray 96 | PyArray_ENABLEFLAGS(ndarray, np.NPY_OWNDATA) 97 | return ndarray 98 | 99 | # internal conversion from Python RLEs object to compressed RLE format 100 | def _toString(RLEs Rs): 101 | cdef siz n = Rs.n 102 | cdef bytes py_string 103 | cdef char* c_string 104 | objs = [] 105 | for i in range(n): 106 | c_string = rleToString( &Rs._R[i] ) 107 | py_string = c_string 108 | objs.append({ 109 | 'size': [Rs._R[i].h, Rs._R[i].w], 110 | 'counts': py_string 111 | }) 112 | free(c_string) 113 | return objs 114 | 115 | # internal conversion from compressed RLE format to Python RLEs object 116 | def _frString(rleObjs): 117 | cdef siz n = len(rleObjs) 118 | Rs = RLEs(n) 119 | cdef bytes py_string 120 | cdef char* c_string 121 | for i, obj in enumerate(rleObjs): 122 | py_string = str(obj['counts']) 123 | c_string = py_string 124 | rleFrString( &Rs._R[i], c_string, obj['size'][0], obj['size'][1] ) 125 | return Rs 126 | 127 | # encode mask to RLEs objects 128 | # list of RLE string can be generated by RLEs member function 129 | def encode(np.ndarray[np.uint8_t, ndim=3, mode='fortran'] mask): 130 | h, w, n = mask.shape[0], mask.shape[1], mask.shape[2] 131 | cdef RLEs Rs = RLEs(n) 132 | rleEncode(Rs._R,mask.data,h,w,n) 133 | objs = _toString(Rs) 134 | return objs 135 | 136 | # decode mask from compressed list of RLE string or RLEs object 137 | def decode(rleObjs): 138 | cdef RLEs Rs = _frString(rleObjs) 139 | h, w, n = Rs._R[0].h, Rs._R[0].w, Rs._n 140 | masks = Masks(h, w, n) 141 | rleDecode( Rs._R, masks._mask, n ); 142 | return np.array(masks) 143 | 144 | def minus(rleObjs): 145 | cdef RLEs Rs = _frString(rleObjs) 146 | cdef RLEs R = RLEs(1) 147 | rleMinus(Rs._R, R._R, Rs._n) 148 | obj = _toString(R)[0] 149 | return obj 150 | 151 | def merge(rleObjs, bint intersect=0): 152 | cdef RLEs Rs = _frString(rleObjs) 153 | cdef RLEs R = RLEs(1) 154 | rleMerge(Rs._R, R._R, Rs._n, intersect) 155 | obj = _toString(R)[0] 156 | return obj 157 | 158 | def area(rleObjs): 159 | cdef RLEs Rs = _frString(rleObjs) 160 | cdef uint* _a = malloc(Rs._n* sizeof(uint)) 161 | rleArea(Rs._R, Rs._n, _a) 162 | cdef np.npy_intp shape[1] 163 | shape[0] = Rs._n 164 | a = np.array((Rs._n, ), dtype=np.uint8) 165 | a = np.PyArray_SimpleNewFromData(1, shape, np.NPY_UINT32, _a) 166 | PyArray_ENABLEFLAGS(a, np.NPY_OWNDATA) 167 | return a 168 | 169 | # iou computation. support function overload (RLEs-RLEs and bbox-bbox). 170 | def iou( dt, gt, pyiscrowd ): 171 | def _preproc(objs): 172 | if len(objs) == 0: 173 | return objs 174 | if type(objs) == np.ndarray: 175 | if len(objs.shape) == 1: 176 | objs = objs.reshape((objs[0], 1)) 177 | # check if it's Nx4 bbox 178 | if not len(objs.shape) == 2 or not objs.shape[1] == 4: 179 | raise Exception('numpy ndarray input is only for *bounding boxes* and should have Nx4 dimension') 180 | objs = objs.astype(np.double) 181 | elif type(objs) == list: 182 | # check if list is in box format and convert it to np.ndarray 183 | isbox = np.all(np.array([(len(obj)==4) and ((type(obj)==list) or (type(obj)==np.ndarray)) for obj in objs])) 184 | isrle = np.all(np.array([type(obj) == dict for obj in objs])) 185 | if isbox: 186 | objs = np.array(objs, dtype=np.double) 187 | if len(objs.shape) == 1: 188 | objs = objs.reshape((1,objs.shape[0])) 189 | elif isrle: 190 | objs = _frString(objs) 191 | else: 192 | raise Exception('list input can be bounding box (Nx4) or RLEs ([RLE])') 193 | else: 194 | raise Exception('unrecognized type. The following type: RLEs (rle), np.ndarray (box), and list (box) are supported.') 195 | return objs 196 | def _rleIou(RLEs dt, RLEs gt, np.ndarray[np.uint8_t, ndim=1] iscrowd, siz m, siz n, np.ndarray[np.double_t, ndim=1] _iou): 197 | rleIou( dt._R, gt._R, m, n, iscrowd.data, _iou.data ) 198 | def _bbIou(np.ndarray[np.double_t, ndim=2] dt, np.ndarray[np.double_t, ndim=2] gt, np.ndarray[np.uint8_t, ndim=1] iscrowd, siz m, siz n, np.ndarray[np.double_t, ndim=1] _iou): 199 | bbIou( dt.data, gt.data, m, n, iscrowd.data, _iou.data ) 200 | def _len(obj): 201 | cdef siz N = 0 202 | if type(obj) == RLEs: 203 | N = obj.n 204 | elif len(obj)==0: 205 | pass 206 | elif type(obj) == np.ndarray: 207 | N = obj.shape[0] 208 | return N 209 | # convert iscrowd to numpy array 210 | cdef np.ndarray[np.uint8_t, ndim=1] iscrowd = np.array(pyiscrowd, dtype=np.uint8) 211 | # simple type checking 212 | cdef siz m, n 213 | dt = _preproc(dt) 214 | gt = _preproc(gt) 215 | m = _len(dt) 216 | n = _len(gt) 217 | if m == 0 or n == 0: 218 | return [] 219 | if not type(dt) == type(gt): 220 | raise Exception('The dt and gt should have the same data type, either RLEs, list or np.ndarray') 221 | 222 | # define local variables 223 | cdef double* _iou = 0 224 | cdef np.npy_intp shape[1] 225 | # check type and assign iou function 226 | if type(dt) == RLEs: 227 | _iouFun = _rleIou 228 | elif type(dt) == np.ndarray: 229 | _iouFun = _bbIou 230 | else: 231 | raise Exception('input data type not allowed.') 232 | _iou = malloc(m*n* sizeof(double)) 233 | iou = np.zeros((m*n, ), dtype=np.double) 234 | shape[0] = m*n 235 | iou = np.PyArray_SimpleNewFromData(1, shape, np.NPY_DOUBLE, _iou) 236 | PyArray_ENABLEFLAGS(iou, np.NPY_OWNDATA) 237 | _iouFun(dt, gt, iscrowd, m, n, iou) 238 | return iou.reshape((m,n), order='F') 239 | 240 | def toBbox( rleObjs ): 241 | cdef RLEs Rs = _frString(rleObjs) 242 | cdef siz n = Rs.n 243 | cdef BB _bb = malloc(4*n* sizeof(double)) 244 | rleToBbox( Rs._R, _bb, n ) 245 | cdef np.npy_intp shape[1] 246 | shape[0] = 4*n 247 | bb = np.array((1,4*n), dtype=np.double) 248 | bb = np.PyArray_SimpleNewFromData(1, shape, np.NPY_DOUBLE, _bb).reshape((n, 4)) 249 | PyArray_ENABLEFLAGS(bb, np.NPY_OWNDATA) 250 | return bb 251 | 252 | def frBbox(np.ndarray[np.double_t, ndim=2] bb, siz h, siz w ): 253 | cdef siz n = bb.shape[0] 254 | Rs = RLEs(n) 255 | rleFrBbox( Rs._R, bb.data, h, w, n ) 256 | objs = _toString(Rs) 257 | return objs 258 | 259 | def frPoly( poly, siz h, siz w ): 260 | cdef np.ndarray[np.double_t, ndim=1] np_poly 261 | n = len(poly) 262 | Rs = RLEs(n) 263 | for i, p in enumerate(poly): 264 | np_poly = np.array(p, dtype=np.double, order='F') 265 | rleFrPoly( &Rs._R[i], np_poly.data, len(np_poly)/2, h, w ) 266 | objs = _toString(Rs) 267 | return objs 268 | 269 | def frUncompressedRLE(ucRles, siz h, siz w): 270 | cdef np.ndarray[np.uint32_t, ndim=1] cnts 271 | cdef RLE R 272 | cdef uint *data 273 | n = len(ucRles) 274 | objs = [] 275 | for i in range(n): 276 | Rs = RLEs(1) 277 | cnts = np.array(ucRles[i]['counts'], dtype=np.uint32) 278 | # time for malloc can be saved here but it's fine 279 | data = malloc(len(cnts)* sizeof(uint)) 280 | for j in range(len(cnts)): 281 | data[j] = cnts[j] 282 | R = RLE(ucRles[i]['size'][0], ucRles[i]['size'][1], len(cnts), data) 283 | Rs._R[0] = R 284 | objs.append(_toString(Rs)[0]) 285 | return objs 286 | 287 | def frPyObjects(pyobj, siz h, w): 288 | if type(pyobj) == np.ndarray: 289 | objs = frBbox(pyobj, h, w ) 290 | elif type(pyobj) == list and len(pyobj[0]) == 4: 291 | objs = frBbox(pyobj, h, w ) 292 | elif type(pyobj) == list and len(pyobj[0]) > 4: 293 | objs = frPoly(pyobj, h, w ) 294 | elif type(pyobj) == list and type(pyobj[0]) == dict: 295 | objs = frUncompressedRLE(pyobj, h, w) 296 | else: 297 | raise Exception('input type is not supported.') 298 | return objs 299 | -------------------------------------------------------------------------------- /PythonAPI/pycocotools/_mask.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wakeupbuddy/amodalAPI/694095b475371081345aff91507b98b2eeab71c5/PythonAPI/pycocotools/_mask.so -------------------------------------------------------------------------------- /PythonAPI/pycocotools/amodal.py: -------------------------------------------------------------------------------- 1 | __author__ = 'yzhu' 2 | __version__ = '0.1' 3 | 4 | import json 5 | import datetime 6 | import time 7 | import matplotlib.pyplot as plt 8 | from matplotlib.collections import PatchCollection 9 | from matplotlib.patches import Polygon 10 | import numpy as np 11 | from skimage.draw import polygon 12 | import urllib 13 | import copy 14 | import itertools 15 | import mask 16 | import os 17 | from pycocotools.coco import COCO 18 | 19 | class Amodal(COCO): 20 | def __init__(self, annotation_file=None, verbose=True): 21 | """ 22 | Constructor of Microsoft COCO helper class for reading and visualizing annotations. 23 | :param annotation_file (str): location of annotation file 24 | :param image_folder (str): location to the folder that hosts images. 25 | :return: 26 | """ 27 | COCO.__init__(self, annotation_file) 28 | self.verbose = verbose 29 | 30 | def createIndex(self): 31 | # create index 32 | print 'creating index...' 33 | anns = {} 34 | imgToAnns = {} 35 | imgs = {} 36 | regions = [] 37 | if 'annotations' in self.dataset: 38 | imgToAnns = {ann['image_id']: [] for ann in self.dataset['annotations']} 39 | anns = {ann['id']: [] for ann in self.dataset['annotations']} 40 | for ann in self.dataset['annotations']: 41 | imgToAnns[ann['image_id']] += [ann] 42 | anns[ann['id']] = ann 43 | for region in ann['regions']: 44 | region['image_id'] = ann['image_id'] 45 | regions.append(region) 46 | 47 | if 'images' in self.dataset: 48 | imgs = {im['id']: {} for im in self.dataset['images']} 49 | for img in self.dataset['images']: 50 | imgs[img['id']] = img 51 | 52 | print 'index created!' 53 | 54 | # create class members 55 | self.anns = anns 56 | self.imgToAnns = imgToAnns 57 | self.imgs = imgs 58 | self.regions = regions 59 | 60 | def getAmodalAnnIds(self, imgIds=[]): 61 | """ 62 | Get amodal ann id that satisfy given fiter conditions. 63 | :param imgIds (int array): get anns for given imgs 64 | :return: ids (int array) : integer array of ann ids 65 | """ 66 | imgIds = imgIds if type(imgIds) == list else [imgIds] 67 | 68 | if len(imgIds) == 0: 69 | anns = self.dataset['annotations'] 70 | else: 71 | lists = [self.imgToAnns[imgId] for imgId in imgIds if imgId in self.imgToAnns] 72 | anns = list(itertools.chain.from_iterable(lists)) 73 | ids = [ann['id'] for ann in anns] 74 | 75 | return ids 76 | 77 | def getImgIds(self, imgIds=[], catIds=[]): 78 | ''' 79 | Get img ids that satisfy given filter conditions. 80 | :param imgIds (int array) : get imgs for given ids 81 | :param catIds (int array) : get imgs with all given cats 82 | :return: ids (int array) : integer array of img ids 83 | ''' 84 | imgIds = imgIds if type(imgIds) == list else [imgIds] 85 | catIds = catIds if type(catIds) == list else [catIds] 86 | 87 | if len(imgIds) == len(catIds) == 0: 88 | ids = self.imgs.keys() 89 | else: 90 | ids = set(imgIds) 91 | for i, catId in enumerate(catIds): 92 | if i == 0 and len(ids) == 0: 93 | ids = set(self.catToImgs[catId]) 94 | else: 95 | ids &= set(self.catToImgs[catId]) 96 | return list(ids) 97 | 98 | def showAmodalAnns(self, anns): 99 | """ 100 | Display a set of amodal Ann object. 101 | :param anns: a dict object 102 | return: None 103 | """ 104 | if type(anns) == list: 105 | print("anns cannot be a list! Should be a dict.") 106 | return 0 107 | ax = plt.gca() 108 | polygons = [] 109 | lines = [] 110 | color = [] 111 | for ann in reversed(anns['regions']): 112 | c = np.random.random((1, 3)).tolist()[0] 113 | if type(ann['segmentation']) == list: 114 | # polygon 115 | seg = ann['segmentation'] 116 | poly = np.array(seg).reshape((len(seg)/2, 2)) 117 | polygons.append(Polygon(poly, True, alpha=0.2)) 118 | color.append(c) 119 | else: 120 | print("todo") 121 | raise NotImplementedError 122 | 123 | p = PatchCollection(polygons, facecolors=color, edgecolors=(0,0,0,1), linewidths=3, alpha=0.8) 124 | ax.add_collection(p) 125 | 126 | def showEdgeMap(self, anns): 127 | """ 128 | Show edge map for an annontation 129 | :param anns: a dict object 130 | return: None 131 | """ 132 | if type(anns) == list: 133 | print("anns cannot be a list! Should be a dict") 134 | return 0 135 | ax = plt.gca() 136 | polygons = [] 137 | lines = [] 138 | color = [] 139 | for ann in reversed(anns['regions']): 140 | c = np.zeros([1, 3]).tolist()[0] 141 | if type(ann['segmentation']) == list: 142 | # polygon 143 | seg = ann['segmentation'] 144 | poly = np.array(seg).reshape((len(seg)/2, 2)) 145 | polygons.append(Polygon(poly, True, alpha=0.2)) 146 | color.append(c) 147 | else: 148 | print("todo") 149 | raise NotImplementedError 150 | 151 | p = PatchCollection(polygons, facecolors=color, edgecolors=(1,1,1,1), linewidths=1, alpha=1) 152 | ax.add_collection(p) 153 | 154 | def showMask(self, M, ax, c = [0, 1, 0]): 155 | m = mask.decode([M]) 156 | img = np.ones( (m.shape[0], m.shape[1], 3) ) 157 | 158 | # get boundary quickly 159 | B = np.zeros( (m.shape[0], m.shape[1]) ) 160 | for aa in range(m.shape[0]-1): 161 | for bb in range(m.shape[1]-1): 162 | #kk = aa*m.shape[1]+bb 163 | if m[aa,bb] != m[aa,bb+1]: 164 | B[aa,bb], B[aa,bb+1] = 1,1 165 | if m[aa,bb] != m[aa+1,bb]: 166 | B[aa,bb], B[aa+1,bb] = 1,1 167 | if m[aa,bb] != m[aa+1,bb+1]: 168 | B[aa,bb], B[aa+1,bb+1] = 1,1 169 | 170 | for i in range(3): 171 | img[:, :, i] = c[i] 172 | ax.imshow(np.dstack( (img, B*1) )) 173 | ax.imshow(np.dstack( (img, m*0.3) )) 174 | 175 | def showAmodalInstance(self, anns, k=-1): 176 | """ 177 | Display k-th instance only: print segmentation first, then print invisible_mask 178 | anns: a single annotation 179 | k: the depth order of anns, 1-index. If k = -1, just visulize input 180 | """ 181 | ax = plt.gca() 182 | c = np.random.random((1,3)).tolist()[0] 183 | c = [0.0, 1.0, 0.0] # green 184 | 185 | if k < 0: 186 | self.showMask(anns['segmentation'], ax) 187 | return 188 | 189 | if type(anns) == list: 190 | print("ann cannot be a list! Should be a dict") 191 | return 0 192 | ann = anns['regions'][k-1] 193 | polygons = [] 194 | color = [] 195 | # draw whole mask 196 | if type(ann['segmentation']) == list: 197 | # polygon 198 | seg = ann['segmentation'] 199 | poly = np.array(seg).reshape((len(seg)/2, 2)) 200 | polygons.append(Polygon(poly, True, alpha=0.2)) 201 | color.append(c) 202 | p = PatchCollection(polygons, facecolors=color, edgecolors=(1,1,1,1), linewidths=3, alpha=0.2) 203 | ax.add_collection(p) 204 | else: 205 | self.showMask(ann['segmentation'], ax) 206 | 207 | # draw invisible_mask 208 | if 'invisible_mask' in ann: 209 | self.showMask(ann['invisible_mask'], ax, [1, 0, 0]) 210 | 211 | def showModalInstance(self, anns, k): 212 | """ 213 | Display k-th instance: print its visible mask 214 | anns: a single annotation 215 | k: the depth order of anns, 1-index 216 | """ 217 | if type(anns) == list: 218 | print("ann cannot be a list! Should be a dict") 219 | return 0 220 | ax = plt.gca() 221 | c = np.random.random((1,3)).tolist()[0] 222 | c = [0.0, 1.0, 0.0] # green 223 | ann = anns['regions'][k-1] 224 | polygons = [] 225 | color = [] 226 | # draw whole mask 227 | if 'visible_mask' in ann: 228 | mm = mask.decode([ann['visible_mask']]) 229 | img = np.ones( (mm.shape[0], mm.shape[1], 3) ) 230 | color_mask = c 231 | for i in range(3): 232 | img[:, :, i] = color_mask[i] 233 | ax.imshow(np.dstack( (img, mm*0.6) )) 234 | else: 235 | if type(ann['segmentation']) == list: 236 | # polygon 237 | seg = ann['segmentation'] 238 | poly = np.array(seg).reshape((len(seg)/2, 2)) 239 | polygons.append(Polygon(poly, True, alpha=0.2)) 240 | color.append(c) 241 | else: 242 | #mask 243 | mm = mask.decode([ann['segmentation']]) 244 | img = np.ones( (mm.shape[0], mm.shape[1], 3) ) 245 | color_mask = c 246 | for i in range(3): 247 | img[:, :, i] = color_mask[i] 248 | ax.imshow(np.dstack( (img, mm*0.6) )) 249 | 250 | p = PatchCollection(polygons, facecolors=color, edgecolors=(0,0,0,1), linewidths=3, alpha=0.4) 251 | ax.add_collection(p) 252 | 253 | def loadRes(self, resFile): 254 | """ 255 | Load result file and return a result api object. 256 | :param resFile (str) : file name of result file 257 | :return: res (obj) : result api object 258 | """ 259 | res = Amodal() 260 | res.dataset['images'] = [img for img in self.dataset['images']] 261 | if self.verbose: 262 | print 'Loading and preparing results...' 263 | tic = time.time() 264 | anns = json.load(open(resFile)) 265 | assert type(anns) == list, 'results in not an array of objects' 266 | annsImgIds = [ann['image_id'] for ann in anns] 267 | 268 | assert set(annsImgIds) == (set(annsImgIds) & set(self.getImgIds())), \ 269 | 'Results do not correspond to current coco set' 270 | if self.verbose: 271 | print 'DONE (t=%0.2fs)'%(time.time()- tic) 272 | res.dataset['annotations'] = anns 273 | res.createIndex() 274 | return res 275 | -------------------------------------------------------------------------------- /PythonAPI/pycocotools/amodaleval.py: -------------------------------------------------------------------------------- 1 | __author__ = 'yzhu' 2 | 3 | import numpy as np 4 | import datetime 5 | import time 6 | from collections import defaultdict 7 | import json 8 | import mask 9 | import copy 10 | import torchfile 11 | import operator 12 | import os.path 13 | class AmodalEval: 14 | def __init__(self, amodalGt=None, amodalDt=None): 15 | ''' 16 | Initialize CocoEval using coco APIs for gt and dt 17 | :param amodalGt: amodal object with ground truth annotations 18 | :param amodalDt: amodal object with detection results(no category) 19 | :return: None 20 | ''' 21 | self.amodalGt = amodalGt # ground truth amodal API 22 | self.amodalDt = amodalDt # detections amodal API 23 | self.params = {} # evaluation parameters 24 | self.evalImgs = defaultdict(list) # per-image per-category evaluation results [KxAxI] elements 25 | self.eval = {} # accumulated evaluation results 26 | self._gts = defaultdict(list) # gt for evaluation 27 | self._dts = defaultdict(list) # dt for evaluation 28 | self.params = Params() # parameters 29 | self._paramsEval = {} # parameters for evaluation 30 | self.stats = [] # result summarization 31 | self.ious = {} # ious between all gts and dts 32 | if not amodalGt is None: 33 | self.params.imgIds = sorted(amodalGt.getImgIds()) 34 | self.params.catIds = [1] 35 | 36 | def _prepare(self): 37 | ''' 38 | Prepare ._gts and ._dts for evaluation based on params 39 | :return: None 40 | ''' 41 | # 42 | def _toMask(objs, coco): 43 | # modify segmentation by reference 44 | for obj in objs: 45 | t = coco.imgs[obj['image_id']] 46 | for region in obj['regions']: 47 | if type(region['segmentation']) == list: 48 | # format is xyxy, convert to RLE 49 | region['segmentation'] = mask.frPyObjects([region['segmentation']], t['height'], t['width']) 50 | if len(region['segmentation']) == 1: 51 | region['segmentation'] = region['segmentation'][0] 52 | else: 53 | # an object can have multiple polygon regions 54 | # merge them into one RLE mask 55 | region['segmentation'] = mask.merge(obj['segmentation']) 56 | if 'area' not in region: 57 | region['area'] = mask.area([region['segmentation']])[0] 58 | elif type(region['segmentation']) == dict and type(region['segmentation']['counts']) == list: 59 | region['segmentation'] = mask.frPyObjects([region['segmentation']],t['height'],t['width'])[0] 60 | elif type(region['segmentation']) == dict and \ 61 | type(region['segmentation']['counts'] == unicode or type(region['segmentation']['counts']) == str): 62 | # format is already RLE, do nothing 63 | if 'area' not in region: 64 | region['area'] = mask.area([region['segmentation']])[0] 65 | else: 66 | raise Exception('segmentation format not supported.') 67 | p = self.params 68 | gts=self.amodalGt.loadAnns(self.amodalGt.getAnnIds(imgIds=p.imgIds)) 69 | dts=self.amodalDt.loadAnns(self.amodalDt.getAnnIds(imgIds=p.imgIds)) 70 | 71 | if p.useSegm: 72 | _toMask(dts, self.amodalDt) 73 | _toMask(gts, self.amodalGt) 74 | self._gts = defaultdict(list) # gt for evaluation 75 | self._dts = defaultdict(list) # dt for evaluation 76 | for gt in gts: 77 | self._gts[gt['image_id'], 1].append(gt) 78 | for dt in dts: 79 | if 'category_id' not in dt: 80 | dt['category_id'] = 1 81 | self._dts[dt['image_id'], dt['category_id']].append(dt) 82 | self.evalImgs = [] # per-image per-category evaluation results 83 | self.queries = [] 84 | self.eval = {} # accumulated evaluation results 85 | 86 | def evaluate(self): 87 | ''' 88 | Run per image evaluation on given images and store results (a list of dict) in self.evalImgs 89 | :return: None 90 | ''' 91 | tic = time.time() 92 | #print 'Running per image evaluation...' 93 | p = self.params 94 | p.imgIds = list(np.unique(p.imgIds)) 95 | if p.useCats: 96 | p.catIds = list(np.unique(p.catIds)) 97 | p.maxDets = sorted(p.maxDets) 98 | self.params=p 99 | self._prepare() 100 | # loop through images, area range, max detection number 101 | catIds = p.catIds if p.useCats else [-1] 102 | computeIoU = self.computeIoU 103 | self.ious = {(imgId, catId): computeIoU(imgId, catId) \ 104 | for imgId in p.imgIds 105 | for catId in catIds} 106 | evaluateImg = self.evaluateImg 107 | maxDet = p.maxDets[-1] 108 | for catId in catIds: 109 | for areaRng in p.areaRng: 110 | for imgId in p.imgIds: 111 | evalRes = evaluateImg(imgId, catId, areaRng, maxDet, p.occRng) 112 | self.evalImgs.append(evalRes) 113 | 114 | self._paramsEval = copy.deepcopy(self.params) 115 | toc = time.time() 116 | print 'DONE (t=%0.2fs).'%(toc-tic) 117 | 118 | def computeIoU(self, imgId, catId): 119 | p = self.params 120 | if p.useCats: 121 | gt = self._gts[imgId,catId] 122 | dt = self._dts[imgId,catId] 123 | else: 124 | gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]] 125 | dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]] 126 | # for multiple gt/annotator case, use gt[0] for now. 127 | dt = dt[0]['regions']; gt = gt[0]['regions'] 128 | dt = sorted(dt, key=lambda x: -x['score']) 129 | if len(dt) > p.maxDets[-1]: 130 | dt=dt[0:p.maxDets[-1]] 131 | 132 | if p.useSegm: 133 | if p.useAmodalGT: 134 | g = [g['segmentation'] for g in gt] 135 | else: 136 | g = [g['visible_mask'] if 'visible_mask' in g else g['segmentation'] for g in gt] 137 | 138 | if p.useAmodalDT: 139 | d = [d['amodal_mask'] if 'amodal_mask' in d else d['segmentation'] for d in dt] 140 | else: 141 | d = [d['segmentation'] for d in dt] 142 | else: 143 | g = [g['bbox'] for g in gt] 144 | d = [d['bbox'] for d in dt] 145 | # compute iou between each dt and gt region 146 | iscrowd = [0 for o in gt] 147 | ious = mask.iou(d,g,iscrowd) 148 | return ious 149 | 150 | def exportDtFile(self, fname): 151 | # save the matched dt, as a field of gt's regions. Then export the file again. 152 | if not self.evalImgs: 153 | print 'Please run evaluate() first' 154 | res = [] 155 | for key, item in self._gts.iteritems(): 156 | gt = item 157 | while type(gt) == list: 158 | gt = gt[0] 159 | res.append(gt) 160 | with open(fname, 'wb') as output: 161 | json.dump(res, output) 162 | return res 163 | 164 | def evaluateImg(self, imgId, catId, aRng, maxDet, oRng): 165 | ''' 166 | perform evaluation for single category and image 167 | :return: dict (single image results) 168 | ''' 169 | # 170 | p = self.params 171 | if p.useCats: 172 | gt = self._gts[imgId,catId] 173 | dt = self._dts[imgId,catId] 174 | else: 175 | gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]] 176 | dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]] 177 | if len(gt) == 0 and len(dt) ==0: 178 | return None 179 | dt = dt[0]['regions']; gt = gt[0]['regions'] 180 | for g in gt: 181 | if 'ignore' not in g: 182 | g['ignore'] = 0 183 | g['_ignore'] = 0 # default 184 | if p.onlyThings == 1 and g['isStuff'] == 1: 185 | g['_ignore'] = 1 186 | if p.onlyThings == 2 and g['isStuff'] == 0: 187 | g['_ignore'] = 1 188 | if g['occlude_rate'] < oRng[0] or g['occlude_rate'] > oRng[1]: 189 | g['_ignore'] = 1 190 | 191 | # sort dt highest score first, sort gt ignore last 192 | gtind = [ind for (ind, g) in sorted(enumerate(gt), key=lambda (ind, g): g['_ignore']) ] 193 | def inv(perm): 194 | inverse = [0] * len(perm) 195 | for i, p in enumerate(perm): 196 | inverse[p] = i 197 | return inverse 198 | inv_gtind = inv(gtind) 199 | 200 | gt = [gt[ind] for ind in gtind] 201 | dt = sorted(dt, key=lambda x: -x['score'])[0:maxDet] 202 | iscrowd = [0 for o in gt] 203 | # load computed ious 204 | N_iou = len(self.ious[imgId, catId]) 205 | ious = self.ious[imgId, catId][0:maxDet, np.array(gtind)] if N_iou >0 else self.ious[imgId, catId] 206 | T = len(p.iouThrs) 207 | G = len(gt) 208 | D = len(dt) 209 | gtm = np.zeros((T,G)) 210 | dtm = np.zeros((T,D)) 211 | gtIg = np.array([g['_ignore'] for g in gt]) 212 | dtIg = np.zeros((T,D)) 213 | if not len(ious)==0: 214 | for tind, t in enumerate(p.iouThrs): 215 | for dind, d in enumerate(dt): 216 | iou = min([t,1-1e-10]) 217 | m = -1 218 | for gind, g in enumerate(gt): 219 | if gtm[tind,gind]>0 and not iscrowd[gind]: 220 | continue 221 | if m>-1 and gtIg[m]==0 and gtIg[gind]==1: 222 | break 223 | if ious[dind,gind] < iou: 224 | continue 225 | iou=ious[dind,gind] 226 | m=gind 227 | if m ==-1: 228 | continue 229 | dtIg[tind,dind] = gtIg[m] 230 | dtm[tind,dind] = gt[m]['order'] 231 | gtm[tind,m] = d['id'] 232 | 233 | gtm = gtm[:,np.array(inv_gtind)] 234 | # set unmatched detections outside of area range to ignore 235 | a = np.array([d['area']aRng[1] for d in dt]).reshape((1, len(dt))) 236 | dtIg = np.logical_or(dtIg, np.logical_and(dtm==0, np.repeat(a,T,0))) 237 | # store results for given image and category 238 | # save matching ids into self._gts 239 | self._gts[imgId, catId][0]['gtm'] = gtm.tolist() 240 | return { 241 | 'image_id': imgId, 242 | 'category_id': catId, 243 | 'aRng': aRng, 244 | 'maxDet': maxDet, 245 | 'dtIds': [d['id'] for d in dt], 246 | 'gtIds': [g['order'] for g in gt], 247 | 'dtMatches': dtm, 248 | 'gtMatches': gtm, 249 | 'dtScores': [d['score'] for d in dt], 250 | 'gtIgnore': gtIg, 251 | 'dtIgnore': dtIg, 252 | } 253 | 254 | def accumulate(self, p = None): 255 | ''' 256 | Accumulate per image evaluation results and store the result in self.eval 257 | :param p: input params for evaluation 258 | :return: None 259 | ''' 260 | #print 'Accumulating evaluation results... ' 261 | tic = time.time() 262 | if not self.evalImgs: 263 | print 'Please run evaluate() first' 264 | # allows input customized parameters 265 | if p is None: 266 | p = self.params 267 | p.catIds = p.catIds if p.useCats == 1 else [-1] 268 | T = len(p.iouThrs) 269 | R = len(p.recThrs) 270 | K = len(p.catIds) if p.useCats else 1 271 | A = len(p.areaRng) 272 | M = len(p.maxDets) 273 | precision = -np.ones((T,R,K,A,M)) # -1 for the precision of absent categories 274 | recall = -np.ones((T,K,A,M)) 275 | # create dictionary for future indexing 276 | _pe = self._paramsEval 277 | catIds = _pe.catIds if _pe.useCats else [-1] 278 | setK = set(catIds) 279 | setA = set(map(tuple, _pe.areaRng)) 280 | setM = set(_pe.maxDets) 281 | setI = set(_pe.imgIds) 282 | # get inds to evaluate 283 | k_list = [n for n, k in enumerate(p.catIds) if k in setK] 284 | m_list = [m for n, m in enumerate(p.maxDets) if m in setM] 285 | a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA] 286 | i_list = [n for n, i in enumerate(p.imgIds) if i in setI] 287 | # K0 = len(_pe.catIds) 288 | I0 = len(_pe.imgIds) 289 | A0 = len(_pe.areaRng) 290 | # retrieve E at each category, area range, and max number of detections 291 | for k, k0 in enumerate(k_list): # length(1) 292 | Nk = k0*A0*I0 293 | for a, a0 in enumerate(a_list): # length(1) 294 | Na = a0*I0 295 | for m, maxDet in enumerate(m_list): # length(4) 296 | E = [self.evalImgs[Nk+Na+i] for i in i_list] 297 | E = filter(None, E) 298 | if len(E) == 0: 299 | continue 300 | dtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E]) 301 | # different sorting method generates slightly different results. 302 | # mergesort is used to be consistent as Matlab implementation. 303 | inds = np.argsort(-dtScores, kind='mergesort') 304 | dtm = np.concatenate([e['dtMatches'][:,0:maxDet] for e in E], axis=1)[:,inds] 305 | dtIg = np.concatenate([e['dtIgnore'][:,0:maxDet] for e in E], axis=1)[:,inds] 306 | gtIg = np.concatenate([e['gtIgnore'] for e in E]) 307 | npig = len([ig for ig in gtIg if ig == 0]) 308 | if npig == 0: 309 | continue 310 | tps = np.logical_and( dtm, np.logical_not(dtIg) ) 311 | fps = np.logical_and(np.logical_not(dtm), np.logical_not(dtIg) ) 312 | tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float) 313 | fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float) 314 | 315 | for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)): 316 | tp = np.array(tp) 317 | fp = np.array(fp) 318 | nd = len(tp) 319 | rc = tp / npig 320 | pr = tp / (fp+tp+np.spacing(1)) 321 | q = np.zeros((R,)) 322 | 323 | if nd: 324 | recall[t,k,a,m] = rc[-1] 325 | else: 326 | recall[t,k,a,m] = 0 327 | 328 | # numpy is slow without cython optimization for accessing elements 329 | # use python array gets significant speed improvement 330 | pr = pr.tolist(); q = q.tolist() 331 | 332 | for i in range(nd-1, 0, -1): 333 | if pr[i] > pr[i-1]: 334 | pr[i-1] = pr[i] 335 | 336 | inds = np.searchsorted(rc, p.recThrs) 337 | try: 338 | for ri, pi in enumerate(inds): 339 | q[ri] = pr[pi] 340 | except: 341 | pass 342 | precision[t,:,k,a,m] = np.array(q) 343 | 344 | self.eval = { 345 | 'params': p, 346 | 'counts': [T, R, K, A, M], 347 | 'date': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 348 | 'precision': precision, 349 | 'recall': recall, 350 | } 351 | toc = time.time() 352 | #print 'DONE (t=%0.2fs).'%( toc-tic ) 353 | 354 | def summarize(self): 355 | ''' 356 | Compute and display summary metrics for evaluation results. 357 | Note this functin can *only* be applied on the default parameter setting 358 | ''' 359 | def _summarize( ap=1, iouThr=None, areaRng='all', maxDets=100, verbose=False): 360 | p = self.params 361 | iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6} | maxDets={:>3} ] = {}' 362 | #titleStr = 'Average Precision' if ap == 1 else 'Average Recall' 363 | if ap == 1: 364 | typeStr = '(AP)' 365 | titleStr = 'Average Precision' 366 | elif ap == 2: 367 | typeStr = '(AR)' 368 | titleStr = 'Average Recall' 369 | elif ap == 3: 370 | typeStr = '(Order AR -- ' + p.sortKey + ')' 371 | titleStr = 'Order Average Recall' 372 | 373 | iouStr = '%0.2f:%0.2f'%(p.iouThrs[0], p.iouThrs[-1]) if iouThr is None else '%0.2f'%(iouThr) 374 | areaStr = areaRng 375 | maxDetsStr = '%d'%(maxDets) 376 | 377 | aind = [i for i, aRng in enumerate(['all', 'small', 'medium', 'large']) if aRng == areaRng] 378 | mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets] 379 | 380 | if ap == 1: 381 | # dimension of precision: [TxRxKxAxM] 382 | s = self.eval['precision'] 383 | # IoU 384 | if iouThr is not None: 385 | t = np.where(iouThr == p.iouThrs)[0] 386 | s = s[t] 387 | # areaRng 388 | s = s[:,:,:,aind,mind] 389 | elif ap == 2: 390 | # dimension of recall: [TxKxAxM] 391 | s = self.eval['recall'] 392 | s = s[:,:,aind,mind] 393 | 394 | if len(s[s>-1])==0: 395 | mean_s = -1 396 | else: 397 | mean_s = np.mean(s[s>-1]) 398 | if verbose: 399 | print iStr.format(titleStr, typeStr, iouStr, areaStr, maxDetsStr, '%.3f'%(float(mean_s))) 400 | return mean_s 401 | 402 | if not self.eval: 403 | raise Exception('Please run accumulate() first') 404 | maxProp = self.params.maxDets[-1] 405 | 406 | self.stats = np.zeros((12,)) 407 | self.stats[0] = _summarize(1, maxDets = maxProp) 408 | self.stats[1] = _summarize(1,iouThr=.5, maxDets = maxProp) 409 | self.stats[2] = _summarize(1,iouThr=.75, maxDets = maxProp) 410 | self.stats[3] = _summarize(2,maxDets=1) 411 | self.stats[4] = _summarize(2,maxDets=10) 412 | self.stats[5] = _summarize(2,maxDets=100) 413 | if maxProp == 1000: 414 | self.stats[6] = _summarize(2,maxDets=1000) 415 | 416 | def __str__(self): 417 | self.summarize() 418 | 419 | class Params: 420 | ''' 421 | Params for coco evaluation api 422 | ''' 423 | def __init__(self): 424 | self.imgIds = [] 425 | self.catIds = [] 426 | # np.arange causes trouble. the data point on arange is slightly larger than the true value 427 | self.iouThrs = np.linspace(.5, 0.95, np.round((0.95-.5)/.05)+1, endpoint=True) 428 | self.recThrs = np.linspace(.0, 1.00, np.round((1.00-.0)/.01)+1, endpoint=True) 429 | self.maxDets = [1,10,100] 430 | #self.maxDets = [1,10,100,1000] 431 | self.areaRng = [ [0**2,1e5**2] ] 432 | self.useSegm = 1 433 | self.useAmodalGT = 1 434 | self.onlyThings = 1 # 1: things only; 0: both 435 | self.useCats = 1 436 | self.occRng = [0, 1] # occluding ratio filter. not yet support multi filter for now. 437 | -------------------------------------------------------------------------------- /PythonAPI/pycocotools/coco.py: -------------------------------------------------------------------------------- 1 | __author__ = 'tylin' 2 | __version__ = '1.0.1' 3 | # Interface for accessing the Microsoft COCO dataset. 4 | 5 | # Microsoft COCO is a large image dataset designed for object detection, 6 | # segmentation, and caption generation. pycocotools is a Python API that 7 | # assists in loading, parsing and visualizing the annotations in COCO. 8 | # Please visit http://mscoco.org/ for more information on COCO, including 9 | # for the data, paper, and tutorials. The exact format of the annotations 10 | # is also described on the COCO website. For example usage of the pycocotools 11 | # please see pycocotools_demo.ipynb. In addition to this API, please download both 12 | # the COCO images and annotations in order to run the demo. 13 | 14 | # An alternative to using the API is to load the annotations directly 15 | # into Python dictionary 16 | # Using the API provides additional utility functions. Note that this API 17 | # supports both *instance* and *caption* annotations. In the case of 18 | # captions not all functions are defined (e.g. categories are undefined). 19 | 20 | # The following API functions are defined: 21 | # COCO - COCO api class that loads COCO annotation file and prepare data structures. 22 | # decodeMask - Decode binary mask M encoded via run-length encoding. 23 | # encodeMask - Encode binary mask M using run-length encoding. 24 | # getAnnIds - Get ann ids that satisfy given filter conditions. 25 | # getCatIds - Get cat ids that satisfy given filter conditions. 26 | # getImgIds - Get img ids that satisfy given filter conditions. 27 | # loadAnns - Load anns with the specified ids. 28 | # loadCats - Load cats with the specified ids. 29 | # loadImgs - Load imgs with the specified ids. 30 | # segToMask - Convert polygon segmentation to binary mask. 31 | # showAnns - Display the specified annotations. 32 | # loadRes - Load algorithm results and create API for accessing them. 33 | # download - Download COCO images from mscoco.org server. 34 | # Throughout the API "ann"=annotation, "cat"=category, and "img"=image. 35 | # Help on each functions can be accessed by: "help COCO>function". 36 | 37 | # See also COCO>decodeMask, 38 | # COCO>encodeMask, COCO>getAnnIds, COCO>getCatIds, 39 | # COCO>getImgIds, COCO>loadAnns, COCO>loadCats, 40 | # COCO>loadImgs, COCO>segToMask, COCO>showAnns 41 | 42 | # Microsoft COCO Toolbox. version 2.0 43 | # Data, paper, and tutorials available at: http://mscoco.org/ 44 | # Code written by Piotr Dollar and Tsung-Yi Lin, 2014. 45 | # Licensed under the Simplified BSD License [see bsd.txt] 46 | 47 | import json 48 | import datetime 49 | import time 50 | import matplotlib.pyplot as plt 51 | from matplotlib.collections import PatchCollection 52 | from matplotlib.patches import Polygon 53 | import numpy as np 54 | from skimage.draw import polygon 55 | import urllib 56 | import copy 57 | import itertools 58 | import mask 59 | import os 60 | import pdb 61 | dd = pdb.set_trace 62 | class COCO: 63 | def __init__(self, annotation_file=None): 64 | """ 65 | Constructor of Microsoft COCO helper class for reading and visualizing annotations. 66 | :param annotation_file (str): location of annotation file 67 | :param image_folder (str): location to the folder that hosts images. 68 | :return: 69 | """ 70 | # load dataset 71 | self.dataset = {} 72 | self.anns = [] 73 | self.imgToAnns = {} 74 | self.catToImgs = {} 75 | self.imgs = {} 76 | self.cats = {} 77 | if not annotation_file == None: 78 | print 'loading annotations into memory...' 79 | tic = time.time() 80 | dataset = json.load(open(annotation_file, 'r')) 81 | print 'Done (t=%0.2fs)'%(time.time()- tic) 82 | self.dataset = dataset 83 | self.createIndex() 84 | 85 | def createIndex(self): 86 | # create index 87 | print 'creating index...' 88 | anns = {} 89 | imgToAnns = {} 90 | catToImgs = {} 91 | cats = {} 92 | imgs = {} 93 | filenameToImgs = {} 94 | if 'annotations' in self.dataset: 95 | imgToAnns = {ann['image_id']: [] for ann in self.dataset['annotations']} 96 | anns = {ann['id']: [] for ann in self.dataset['annotations']} 97 | for ann in self.dataset['annotations']: 98 | imgToAnns[ann['image_id']] += [ann] 99 | anns[ann['id']] = ann 100 | 101 | if 'images' in self.dataset: 102 | imgs = {im['id']: {} for im in self.dataset['images']} 103 | for img in self.dataset['images']: 104 | imgs[img['id']] = img 105 | #filenameToImgs[img['file_name']] = (img['id'], img['height'], img['width']) 106 | filenameToImgs[img['file_name']] = img 107 | 108 | if 'categories' in self.dataset: 109 | cats = {cat['id']: [] for cat in self.dataset['categories']} 110 | for cat in self.dataset['categories']: 111 | cats[cat['id']] = cat 112 | catToImgs = {cat['id']: [] for cat in self.dataset['categories']} 113 | for ann in self.dataset['annotations']: 114 | catToImgs[ann['category_id']] += [ann['image_id']] 115 | 116 | print 'index created!' 117 | 118 | # create class members 119 | self.anns = anns 120 | self.imgToAnns = imgToAnns 121 | self.catToImgs = catToImgs 122 | self.filenameToImgs = filenameToImgs 123 | self.imgs = imgs 124 | self.cats = cats 125 | 126 | def info(self): 127 | """ 128 | Print information about the annotation file. 129 | :return: 130 | """ 131 | for key, value in self.dataset['info'].items(): 132 | print '%s: %s'%(key, value) 133 | 134 | def getAnnIds(self, imgIds=[], catIds=[], areaRng=[], iscrowd=None): 135 | """ 136 | Get ann ids that satisfy given filter conditions. default skips that filter 137 | :param imgIds (int array) : get anns for given imgs 138 | catIds (int array) : get anns for given cats 139 | areaRng (float array) : get anns for given area range (e.g. [0 inf]) 140 | iscrowd (boolean) : get anns for given crowd label (False or True) 141 | :return: ids (int array) : integer array of ann ids 142 | """ 143 | imgIds = imgIds if type(imgIds) == list else [imgIds] 144 | catIds = catIds if type(catIds) == list else [catIds] 145 | 146 | if len(imgIds) == len(catIds) == len(areaRng) == 0: 147 | anns = self.dataset['annotations'] 148 | else: 149 | if not len(imgIds) == 0: 150 | # this can be changed by defaultdict 151 | lists = [self.imgToAnns[imgId] for imgId in imgIds if imgId in self.imgToAnns] 152 | anns = list(itertools.chain.from_iterable(lists)) 153 | else: 154 | anns = self.dataset['annotations'] 155 | anns = anns if len(catIds) == 0 else [ann for ann in anns if ann['category_id'] in catIds] 156 | anns = anns if len(areaRng) == 0 else [ann for ann in anns if ann['area'] > areaRng[0] and ann['area'] < areaRng[1]] 157 | if not iscrowd == None: 158 | ids = [ann['id'] for ann in anns if ann['iscrowd'] == iscrowd] 159 | else: 160 | ids = [ann['id'] for ann in anns] 161 | return ids 162 | 163 | def getCatIds(self, catNms=[], supNms=[], catIds=[]): 164 | """ 165 | filtering parameters. default skips that filter. 166 | :param catNms (str array) : get cats for given cat names 167 | :param supNms (str array) : get cats for given supercategory names 168 | :param catIds (int array) : get cats for given cat ids 169 | :return: ids (int array) : integer array of cat ids 170 | """ 171 | catNms = catNms if type(catNms) == list else [catNms] 172 | supNms = supNms if type(supNms) == list else [supNms] 173 | catIds = catIds if type(catIds) == list else [catIds] 174 | 175 | if len(catNms) == len(supNms) == len(catIds) == 0: 176 | cats = self.dataset['categories'] 177 | else: 178 | cats = self.dataset['categories'] 179 | cats = cats if len(catNms) == 0 else [cat for cat in cats if cat['name'] in catNms] 180 | cats = cats if len(supNms) == 0 else [cat for cat in cats if cat['supercategory'] in supNms] 181 | cats = cats if len(catIds) == 0 else [cat for cat in cats if cat['id'] in catIds] 182 | ids = [cat['id'] for cat in cats] 183 | return ids 184 | 185 | def getImgIds(self, imgIds=[], catIds=[]): 186 | ''' 187 | Get img ids that satisfy given filter conditions. 188 | :param imgIds (int array) : get imgs for given ids 189 | :param catIds (int array) : get imgs with all given cats 190 | :return: ids (int array) : integer array of img ids 191 | ''' 192 | imgIds = imgIds if type(imgIds) == list else [imgIds] 193 | catIds = catIds if type(catIds) == list else [catIds] 194 | 195 | if len(imgIds) == len(catIds) == 0: 196 | ids = self.imgs.keys() 197 | else: 198 | ids = set(imgIds) 199 | for i, catId in enumerate(catIds): 200 | if i == 0 and len(ids) == 0: 201 | ids = set(self.catToImgs[catId]) 202 | else: 203 | ids &= set(self.catToImgs[catId]) 204 | return list(ids) 205 | 206 | def loadAnns(self, ids=[]): 207 | """ 208 | Load anns with the specified ids. 209 | :param ids (int array) : integer ids specifying anns 210 | :return: anns (object array) : loaded ann objects 211 | """ 212 | if type(ids) == list: 213 | return [self.anns[id] for id in ids] 214 | elif type(ids) == int: 215 | return [self.anns[ids]] 216 | 217 | def loadCats(self, ids=[]): 218 | """ 219 | Load cats with the specified ids. 220 | :param ids (int array) : integer ids specifying cats 221 | :return: cats (object array) : loaded cat objects 222 | """ 223 | if type(ids) == list: 224 | return [self.cats[id] for id in ids] 225 | elif type(ids) == int: 226 | return [self.cats[ids]] 227 | 228 | def loadImgs(self, ids=[]): 229 | """ 230 | Load anns with the specified ids. 231 | :param ids (int array) : integer ids specifying img 232 | :return: imgs (object array) : loaded img objects 233 | """ 234 | if type(ids) == list: 235 | return [self.imgs[id] for id in ids] 236 | elif type(ids) == int: 237 | return [self.imgs[ids]] 238 | 239 | def showAnns(self, anns): 240 | """ 241 | Display the specified annotations. 242 | :param anns (array of object): annotations to display 243 | :return: None 244 | """ 245 | if len(anns) == 0: 246 | return 0 247 | if 'segmentation' in anns[0]: 248 | datasetType = 'instances' 249 | elif 'caption' in anns[0]: 250 | datasetType = 'captions' 251 | if datasetType == 'instances': 252 | ax = plt.gca() 253 | polygons = [] 254 | color = [] 255 | for ann in anns: 256 | c = np.random.random((1, 3)).tolist()[0] 257 | if type(ann['segmentation']) == list: 258 | # polygon 259 | for seg in ann['segmentation']: 260 | poly = np.array(seg).reshape((len(seg)/2, 2)) 261 | polygons.append(Polygon(poly, True,alpha=0.4)) 262 | color.append(c) 263 | else: 264 | # mask 265 | t = self.imgs[ann['image_id']] 266 | if type(ann['segmentation']['counts']) == list: 267 | rle = mask.frPyObjects([ann['segmentation']], t['height'], t['width']) 268 | else: 269 | rle = [ann['segmentation']] 270 | m = mask.decode(rle) 271 | img = np.ones( (m.shape[0], m.shape[1], 3) ) 272 | if ann['iscrowd'] == 1: 273 | color_mask = np.array([2.0,166.0,101.0])/255 274 | if ann['iscrowd'] == 0: 275 | color_mask = np.random.random((1, 3)).tolist()[0] 276 | for i in range(3): 277 | img[:,:,i] = color_mask[i] 278 | ax.imshow(np.dstack( (img, m*0.5) )) 279 | p = PatchCollection(polygons, facecolors=color, edgecolors=(0,0,0,1), linewidths=3, alpha=0.4) 280 | ax.add_collection(p) 281 | elif datasetType == 'captions': 282 | for ann in anns: 283 | print ann['caption'] 284 | 285 | def loadRes(self, resFile): 286 | """ 287 | Load result file and return a result api object. 288 | :param resFile (str) : file name of result file 289 | :return: res (obj) : result api object 290 | """ 291 | res = COCO() 292 | res.dataset['images'] = [img for img in self.dataset['images']] 293 | # res.dataset['info'] = copy.deepcopy(self.dataset['info']) 294 | # res.dataset['licenses'] = copy.deepcopy(self.dataset['licenses']) 295 | 296 | print 'Loading and preparing results... ' 297 | tic = time.time() 298 | anns = json.load(open(resFile)) 299 | assert type(anns) == list, 'results in not an array of objects' 300 | annsImgIds = [ann['image_id'] for ann in anns] 301 | assert set(annsImgIds) == (set(annsImgIds) & set(self.getImgIds())), \ 302 | 'Results do not correspond to current coco set' 303 | if 'caption' in anns[0]: 304 | imgIds = set([img['id'] for img in res.dataset['images']]) & set([ann['image_id'] for ann in anns]) 305 | res.dataset['images'] = [img for img in res.dataset['images'] if img['id'] in imgIds] 306 | for id, ann in enumerate(anns): 307 | ann['id'] = id+1 308 | elif 'bbox' in anns[0] and not anns[0]['bbox'] == []: 309 | res.dataset['categories'] = copy.deepcopy(self.dataset['categories']) 310 | for id, ann in enumerate(anns): 311 | bb = ann['bbox'] 312 | x1, x2, y1, y2 = [bb[0], bb[0]+bb[2], bb[1], bb[1]+bb[3]] 313 | if not 'segmentation' in ann: 314 | ann['segmentation'] = [[x1, y1, x1, y2, x2, y2, x2, y1]] 315 | ann['area'] = bb[2]*bb[3] 316 | ann['id'] = id+1 317 | ann['iscrowd'] = 0 318 | elif 'segmentation' in anns[0]: 319 | res.dataset['categories'] = copy.deepcopy(self.dataset['categories']) 320 | for id, ann in enumerate(anns): 321 | # now only support compressed RLE format as segmentation results 322 | ann['area'] = mask.area([ann['segmentation']])[0] 323 | if not 'bbox' in ann: 324 | ann['bbox'] = mask.toBbox([ann['segmentation']])[0] 325 | ann['id'] = id+1 326 | ann['iscrowd'] = 0 327 | print 'DONE (t=%0.2fs)'%(time.time()- tic) 328 | 329 | res.dataset['annotations'] = anns 330 | res.createIndex() 331 | return res 332 | 333 | def download( self, tarDir = None, imgIds = [] ): 334 | ''' 335 | Download COCO images from mscoco.org server. 336 | :param tarDir (str): COCO results directory name 337 | imgIds (list): images to be downloaded 338 | :return: 339 | ''' 340 | if tarDir is None: 341 | print 'Please specify target directory' 342 | return -1 343 | if len(imgIds) == 0: 344 | imgs = self.imgs.values() 345 | else: 346 | imgs = self.loadImgs(imgIds) 347 | N = len(imgs) 348 | if not os.path.exists(tarDir): 349 | os.makedirs(tarDir) 350 | for i, img in enumerate(imgs): 351 | tic = time.time() 352 | fname = os.path.join(tarDir, img['file_name']) 353 | if not os.path.exists(fname): 354 | urllib.urlretrieve(img['coco_url'], fname) 355 | print 'downloaded %d/%d images (t=%.1fs)'%(i, N, time.time()- tic) 356 | -------------------------------------------------------------------------------- /PythonAPI/pycocotools/cocoeval.py: -------------------------------------------------------------------------------- 1 | __author__ = 'tsungyi' 2 | 3 | import numpy as np 4 | import datetime 5 | import time 6 | from collections import defaultdict 7 | import mask 8 | import copy 9 | import pdb 10 | dd = pdb.set_trace 11 | 12 | class COCOeval: 13 | # Interface for evaluating detection on the Microsoft COCO dataset. 14 | # 15 | # The usage for CocoEval is as follows: 16 | # cocoGt=..., cocoDt=... # load dataset and results 17 | # E = CocoEval(cocoGt,cocoDt); # initialize CocoEval object 18 | # E.params.recThrs = ...; # set parameters as desired 19 | # E.evaluate(); # run per image evaluation 20 | # E.accumulate(); # accumulate per image results 21 | # E.summarize(); # display summary metrics of results 22 | # For example usage see evalDemo.m and http://mscoco.org/. 23 | # 24 | # The evaluation parameters are as follows (defaults in brackets): 25 | # imgIds - [all] N img ids to use for evaluation 26 | # catIds - [all] K cat ids to use for evaluation 27 | # iouThrs - [.5:.05:.95] T=10 IoU thresholds for evaluation 28 | # recThrs - [0:.01:1] R=101 recall thresholds for evaluation 29 | # areaRng - [...] A=4 object area ranges for evaluation 30 | # maxDets - [1 10 100] M=3 thresholds on max detections per image 31 | # useSegm - [1] if true evaluate against ground-truth segments 32 | # useCats - [1] if true use category labels for evaluation # Note: if useSegm=0 the evaluation is run on bounding boxes. 33 | # Note: if useCats=0 category labels are ignored as in proposal scoring. 34 | # Note: multiple areaRngs [Ax2] and maxDets [Mx1] can be specified. 35 | # 36 | # evaluate(): evaluates detections on every image and every category and 37 | # concats the results into the "evalImgs" with fields: 38 | # dtIds - [1xD] id for each of the D detections (dt) 39 | # gtIds - [1xG] id for each of the G ground truths (gt) 40 | # dtMatches - [TxD] matching gt id at each IoU or 0 41 | # gtMatches - [TxG] matching dt id at each IoU or 0 42 | # dtScores - [1xD] confidence of each dt 43 | # gtIgnore - [1xG] ignore flag for each gt 44 | # dtIgnore - [TxD] ignore flag for each dt at each IoU 45 | # 46 | # accumulate(): accumulates the per-image, per-category evaluation 47 | # results in "evalImgs" into the dictionary "eval" with fields: 48 | # params - parameters used for evaluation 49 | # date - date evaluation was performed 50 | # counts - [T,R,K,A,M] parameter dimensions (see above) 51 | # precision - [TxRxKxAxM] precision for every evaluation setting 52 | # recall - [TxKxAxM] max recall for every evaluation setting 53 | # Note: precision and recall==-1 for settings with no gt objects. 54 | # 55 | # See also coco, mask, pycocoDemo, pycocoEvalDemo 56 | # 57 | # Microsoft COCO Toolbox. version 2.0 58 | # Data, paper, and tutorials available at: http://mscoco.org/ 59 | # Code written by Piotr Dollar and Tsung-Yi Lin, 2015. 60 | # Licensed under the Simplified BSD License [see coco/license.txt] 61 | def __init__(self, cocoGt=None, cocoDt=None): 62 | ''' 63 | Initialize CocoEval using coco APIs for gt and dt 64 | :param cocoGt: coco object with ground truth annotations 65 | :param cocoDt: coco object with detection results 66 | :return: None 67 | ''' 68 | self.cocoGt = cocoGt # ground truth COCO API 69 | self.cocoDt = cocoDt # detections COCO API 70 | self.params = {} # evaluation parameters 71 | self.evalImgs = defaultdict(list) # per-image per-category evaluation results [KxAxI] elements 72 | self.eval = {} # accumulated evaluation results 73 | self._gts = defaultdict(list) # gt for evaluation 74 | self._dts = defaultdict(list) # dt for evaluation 75 | self.params = Params() # parameters 76 | self._paramsEval = {} # parameters for evaluation 77 | self.stats = [] # result summarization 78 | self.ious = {} # ious between all gts and dts 79 | if not cocoGt is None: 80 | self.params.imgIds = sorted(cocoGt.getImgIds()) 81 | self.params.catIds = sorted(cocoGt.getCatIds()) 82 | 83 | 84 | def _prepare(self): 85 | ''' 86 | Prepare ._gts and ._dts for evaluation based on params 87 | :return: None 88 | ''' 89 | # 90 | def _toMask(objs, coco): 91 | # modify segmentation by reference 92 | for obj in objs: 93 | t = coco.imgs[obj['image_id']] 94 | if type(obj['segmentation']) == list: 95 | if type(obj['segmentation'][0]) == dict: 96 | print 'debug' 97 | obj['segmentation'] = mask.frPyObjects(obj['segmentation'],t['height'],t['width']) 98 | if len(obj['segmentation']) == 1: 99 | obj['segmentation'] = obj['segmentation'][0] 100 | else: 101 | # an object can have multiple polygon regions 102 | # merge them into one RLE mask 103 | obj['segmentation'] = mask.merge(obj['segmentation']) 104 | elif type(obj['segmentation']) == dict and type(obj['segmentation']['counts']) == list: 105 | obj['segmentation'] = mask.frPyObjects([obj['segmentation']],t['height'],t['width'])[0] 106 | elif type(obj['segmentation']) == dict and \ 107 | type(obj['segmentation']['counts'] == unicode or type(obj['segmentation']['counts']) == str): 108 | pass 109 | else: 110 | raise Exception('segmentation format not supported.') 111 | p = self.params 112 | if p.useCats: 113 | gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds)) 114 | dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds)) 115 | else: 116 | gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds)) 117 | dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds)) 118 | if p.useSegm: 119 | _toMask(gts, self.cocoGt) 120 | _toMask(dts, self.cocoDt) 121 | self._gts = defaultdict(list) # gt for evaluation 122 | self._dts = defaultdict(list) # dt for evaluation 123 | for gt in gts: 124 | self._gts[gt['image_id'], gt['category_id']].append(gt) 125 | for dt in dts: 126 | self._dts[dt['image_id'], dt['category_id']].append(dt) 127 | self.evalImgs = defaultdict(list) # per-image per-category evaluation results 128 | self.eval = {} # accumulated evaluation results 129 | 130 | def evaluate(self): 131 | ''' 132 | Run per image evaluation on given images and store results (a list of dict) in self.evalImgs 133 | :return: None 134 | ''' 135 | tic = time.time() 136 | print 'Running per image evaluation... ' 137 | p = self.params 138 | p.imgIds = list(np.unique(p.imgIds)) 139 | if p.useCats: 140 | p.catIds = list(np.unique(p.catIds)) 141 | p.maxDets = sorted(p.maxDets) 142 | self.params=p 143 | 144 | self._prepare() 145 | # loop through images, area range, max detection number 146 | catIds = p.catIds if p.useCats else [-1] 147 | 148 | computeIoU = self.computeIoU 149 | self.ious = {(imgId, catId): computeIoU(imgId, catId) \ 150 | for imgId in p.imgIds 151 | for catId in catIds} 152 | # self.ious is a dict, where key is a tuple(imgId, catId). 153 | # so the ious are measured by per img and per cat 154 | evaluateImg = self.evaluateImg 155 | maxDet = p.maxDets[-1] 156 | self.evalImgs = [evaluateImg(imgId, catId, areaRng, maxDet) 157 | for catId in catIds 158 | for areaRng in p.areaRng 159 | for imgId in p.imgIds 160 | ] 161 | self._paramsEval = copy.deepcopy(self.params) 162 | toc = time.time() 163 | print 'DONE (t=%0.2fs).'%(toc-tic) 164 | 165 | def computeIoU(self, imgId, catId): 166 | p = self.params 167 | if p.useCats: 168 | gt = self._gts[imgId,catId] 169 | dt = self._dts[imgId,catId] 170 | else: 171 | gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]] 172 | dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]] 173 | if len(gt) == 0 and len(dt) ==0: 174 | return [] 175 | dt = sorted(dt, key=lambda x: -x['score']) 176 | if len(dt) > p.maxDets[-1]: 177 | dt=dt[0:p.maxDets[-1]] 178 | 179 | if p.useSegm: 180 | g = [g['segmentation'] for g in gt] 181 | d = [d['segmentation'] for d in dt] 182 | else: 183 | g = [g['bbox'] for g in gt] 184 | d = [d['bbox'] for d in dt] 185 | # compute iou between each dt and gt region 186 | iscrowd = [int(o['iscrowd']) for o in gt] 187 | ious = mask.iou(d,g,iscrowd) 188 | return ious 189 | 190 | def evaluateImg(self, imgId, catId, aRng, maxDet): 191 | ''' 192 | perform evaluation for single category and image 193 | :return: dict (single image results) 194 | ''' 195 | # 196 | p = self.params 197 | if p.useCats: 198 | gt = self._gts[imgId,catId] 199 | dt = self._dts[imgId,catId] 200 | else: 201 | gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]] 202 | dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]] 203 | if len(gt) == 0 and len(dt) ==0: 204 | return None 205 | 206 | for g in gt: 207 | if 'ignore' not in g: 208 | g['ignore'] = 0 209 | if g['iscrowd'] == 1 or g['ignore'] or (g['area']aRng[1]): 210 | g['_ignore'] = 1 211 | else: 212 | g['_ignore'] = 0 213 | # 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 | gt = [gt[ind] for ind in gtind] 217 | dt = sorted(dt, key=lambda x: -x['score'])[0:maxDet] 218 | iscrowd = [int(o['iscrowd']) for o in gt] 219 | # load computed ious 220 | N_iou = len(self.ious[imgId, catId]) 221 | ious = self.ious[imgId, catId][0:maxDet, np.array(gtind)] if N_iou >0 else self.ious[imgId, catId] 222 | 223 | T = len(p.iouThrs) 224 | G = len(gt) 225 | D = len(dt) 226 | gtm = np.zeros((T,G)) 227 | dtm = np.zeros((T,D)) 228 | gtIg = np.array([g['_ignore'] for g in gt]) 229 | dtIg = np.zeros((T,D)) 230 | if not len(ious)==0: 231 | for tind, t in enumerate(p.iouThrs): 232 | for dind, d in enumerate(dt): 233 | # information about best match so far (m=-1 -> unmatched) 234 | iou = min([t,1-1e-10]) 235 | m = -1 236 | for gind, g in enumerate(gt): 237 | # if this gt already matched, and not a crowd, continue 238 | if gtm[tind,gind]>0 and not iscrowd[gind]: 239 | continue 240 | # if dt matched to reg gt, and on ignore gt, stop 241 | if m>-1 and gtIg[m]==0 and gtIg[gind]==1: 242 | break 243 | # continue to next gt unless better match made 244 | if ious[dind,gind] < iou: 245 | continue 246 | # match successful and best so far, store appropriately 247 | iou=ious[dind,gind] 248 | m=gind 249 | # if match made store id of match for both dt and gt 250 | if m ==-1: 251 | continue 252 | dtIg[tind,dind] = gtIg[m] 253 | dtm[tind,dind] = gt[m]['id'] 254 | gtm[tind,m] = d['id'] 255 | # set unmatched detections outside of area range to ignore 256 | a = np.array([d['area']aRng[1] for d in dt]).reshape((1, len(dt))) 257 | dtIg = np.logical_or(dtIg, np.logical_and(dtm==0, np.repeat(a,T,0))) 258 | # store results for given image and category 259 | return { 260 | 'image_id': imgId, 261 | 'category_id': catId, 262 | 'aRng': aRng, 263 | 'maxDet': maxDet, 264 | 'dtIds': [d['id'] for d in dt], 265 | 'gtIds': [g['id'] for g in gt], 266 | 'dtMatches': dtm, 267 | 'gtMatches': gtm, 268 | 'dtScores': [d['score'] for d in dt], 269 | 'gtIgnore': gtIg, 270 | 'dtIgnore': dtIg, 271 | } 272 | 273 | def accumulate(self, p = None): 274 | ''' 275 | Accumulate per image evaluation results and store the result in self.eval 276 | :param p: input params for evaluation 277 | :return: None 278 | ''' 279 | print 'Accumulating evaluation results... ' 280 | tic = time.time() 281 | if not self.evalImgs: 282 | print 'Please run evaluate() first' 283 | # allows input customized parameters 284 | if p is None: 285 | p = self.params 286 | p.catIds = p.catIds if p.useCats == 1 else [-1] 287 | T = len(p.iouThrs) 288 | R = len(p.recThrs) 289 | K = len(p.catIds) if p.useCats else 1 290 | A = len(p.areaRng) 291 | M = len(p.maxDets) 292 | precision = -np.ones((T,R,K,A,M)) # -1 for the precision of absent categories 293 | recall = -np.ones((T,K,A,M)) 294 | 295 | # create dictionary for future indexing 296 | _pe = self._paramsEval 297 | catIds = _pe.catIds if _pe.useCats else [-1] 298 | setK = set(catIds) 299 | setA = set(map(tuple, _pe.areaRng)) 300 | setM = set(_pe.maxDets) 301 | setI = set(_pe.imgIds) 302 | # get inds to evaluate 303 | k_list = [n for n, k in enumerate(p.catIds) if k in setK] 304 | m_list = [m for n, m in enumerate(p.maxDets) if m in setM] 305 | a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA] 306 | i_list = [n for n, i in enumerate(p.imgIds) if i in setI] 307 | # K0 = len(_pe.catIds) 308 | I0 = len(_pe.imgIds) 309 | A0 = len(_pe.areaRng) 310 | # retrieve E at each category, area range, and max number of detections 311 | for k, k0 in enumerate(k_list): 312 | Nk = k0*A0*I0 313 | for a, a0 in enumerate(a_list): 314 | Na = a0*I0 315 | for m, maxDet in enumerate(m_list): 316 | E = [self.evalImgs[Nk+Na+i] for i in i_list] 317 | E = filter(None, E) 318 | if len(E) == 0: 319 | continue 320 | dtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E]) 321 | # different sorting method generates slightly different results. 322 | # mergesort is used to be consistent as Matlab implementation. 323 | inds = np.argsort(-dtScores, kind='mergesort') 324 | dtm = np.concatenate([e['dtMatches'][:,0:maxDet] for e in E], axis=1)[:,inds] 325 | dtIg = np.concatenate([e['dtIgnore'][:,0:maxDet] for e in E], axis=1)[:,inds] 326 | gtIg = np.concatenate([e['gtIgnore'] for e in E]) 327 | npig = len([ig for ig in gtIg if ig == 0]) 328 | if npig == 0: 329 | continue 330 | tps = np.logical_and( dtm, np.logical_not(dtIg) ) 331 | fps = np.logical_and(np.logical_not(dtm), np.logical_not(dtIg) ) 332 | 333 | tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float) 334 | fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float) 335 | for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)): 336 | tp = np.array(tp) 337 | fp = np.array(fp) 338 | nd = len(tp) 339 | rc = tp / npig 340 | pr = tp / (fp+tp+np.spacing(1)) 341 | q = np.zeros((R,)) 342 | 343 | if nd: 344 | recall[t,k,a,m] = rc[-1] 345 | else: 346 | recall[t,k,a,m] = 0 347 | 348 | # numpy is slow without cython optimization for accessing elements 349 | # use python array gets significant speed improvement 350 | pr = pr.tolist(); q = q.tolist() 351 | 352 | for i in range(nd-1, 0, -1): 353 | if pr[i] > pr[i-1]: 354 | pr[i-1] = pr[i] 355 | 356 | inds = np.searchsorted(rc, p.recThrs) 357 | try: 358 | for ri, pi in enumerate(inds): 359 | q[ri] = pr[pi] 360 | except: 361 | pass 362 | precision[t,:,k,a,m] = np.array(q) 363 | self.eval = { 364 | 'params': p, 365 | 'counts': [T, R, K, A, M], 366 | 'date': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 367 | 'precision': precision, 368 | 'recall': recall, 369 | } 370 | toc = time.time() 371 | print 'DONE (t=%0.2fs).'%( toc-tic ) 372 | 373 | def summarize(self): 374 | ''' 375 | Compute and display summary metrics for evaluation results. 376 | Note this functin can *only* be applied on the default parameter setting 377 | ''' 378 | def _summarize( ap=1, iouThr=None, areaRng='all', maxDets=100 ): 379 | p = self.params 380 | iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6} | maxDets={:>3} ] = {}' 381 | titleStr = 'Average Precision' if ap == 1 else 'Average Recall' 382 | typeStr = '(AP)' if ap==1 else '(AR)' 383 | iouStr = '%0.2f:%0.2f'%(p.iouThrs[0], p.iouThrs[-1]) if iouThr is None else '%0.2f'%(iouThr) 384 | areaStr = areaRng 385 | maxDetsStr = '%d'%(maxDets) 386 | 387 | aind = [i for i, aRng in enumerate(['all', 'small', 'medium', 'large']) if aRng == areaRng] 388 | mind = [i for i, mDet in enumerate([1, 10, 100]) if mDet == maxDets] 389 | if ap == 1: 390 | # dimension of precision: [TxRxKxAxM] 391 | s = self.eval['precision'] 392 | # IoU 393 | if iouThr is not None: 394 | t = np.where(iouThr == p.iouThrs)[0] 395 | s = s[t] 396 | # areaRng 397 | s = s[:,:,:,aind,mind] 398 | else: 399 | # dimension of recall: [TxKxAxM] 400 | s = self.eval['recall'] 401 | s = s[:,:,aind,mind] 402 | if len(s[s>-1])==0: 403 | mean_s = -1 404 | else: 405 | mean_s = np.mean(s[s>-1]) 406 | print iStr.format(titleStr, typeStr, iouStr, areaStr, maxDetsStr, '%.3f'%(float(mean_s))) 407 | return mean_s 408 | 409 | if not self.eval: 410 | raise Exception('Please run accumulate() first') 411 | self.stats = np.zeros((12,)) 412 | self.stats[0] = _summarize(1) 413 | self.stats[1] = _summarize(1,iouThr=.5) 414 | self.stats[2] = _summarize(1,iouThr=.75) 415 | self.stats[3] = _summarize(1,areaRng='small') 416 | self.stats[4] = _summarize(1,areaRng='medium') 417 | self.stats[5] = _summarize(1,areaRng='large') 418 | self.stats[6] = _summarize(0,maxDets=1) 419 | self.stats[7] = _summarize(0,maxDets=10) 420 | self.stats[8] = _summarize(0,maxDets=100) 421 | self.stats[9] = _summarize(0,areaRng='small') 422 | self.stats[10] = _summarize(0,areaRng='medium') 423 | self.stats[11] = _summarize(0,areaRng='large') 424 | 425 | def __str__(self): 426 | self.summarize() 427 | 428 | class Params: 429 | ''' 430 | Params for coco evaluation api 431 | ''' 432 | def __init__(self): 433 | self.imgIds = [] 434 | self.catIds = [] 435 | # np.arange causes trouble. the data point on arange is slightly larger than the true value 436 | self.iouThrs = np.linspace(.5, 0.95, np.round((0.95-.5)/.05)+1, endpoint=True) 437 | self.recThrs = np.linspace(.0, 1.00, np.round((1.00-.0)/.01)+1, endpoint=True) 438 | self.maxDets = [1,10,100] 439 | self.areaRng = [ [0**2,1e5**2], [0**2, 32**2], [32**2, 96**2], [96**2, 1e5**2] ] 440 | self.useSegm = 0 441 | self.useCats = 1 442 | -------------------------------------------------------------------------------- /PythonAPI/pycocotools/mask.py: -------------------------------------------------------------------------------- 1 | __author__ = 'tsungyi' 2 | 3 | import pycocotools._mask as _mask 4 | 5 | # Interface for manipulating masks stored in RLE format. 6 | # 7 | # RLE is a simple yet efficient format for storing binary masks. RLE 8 | # first divides a vector (or vectorized image) into a series of piecewise 9 | # constant regions and then for each piece simply stores the length of 10 | # that piece. For example, given M=[0 0 1 1 1 0 1] the RLE counts would 11 | # be [2 3 1 1], or for M=[1 1 1 1 1 1 0] the counts would be [0 6 1] 12 | # (note that the odd counts are always the numbers of zeros). Instead of 13 | # storing the counts directly, additional compression is achieved with a 14 | # variable bitrate representation based on a common scheme called LEB128. 15 | # 16 | # Compression is greatest given large piecewise constant regions. 17 | # Specifically, the size of the RLE is proportional to the number of 18 | # *boundaries* in M (or for an image the number of boundaries in the y 19 | # direction). Assuming fairly simple shapes, the RLE representation is 20 | # O(sqrt(n)) where n is number of pixels in the object. Hence space usage 21 | # is substantially lower, especially for large simple objects (large n). 22 | # 23 | # Many common operations on masks can be computed directly using the RLE 24 | # (without need for decoding). This includes computations such as area, 25 | # union, intersection, etc. All of these operations are linear in the 26 | # size of the RLE, in other words they are O(sqrt(n)) where n is the area 27 | # of the object. Computing these operations on the original mask is O(n). 28 | # Thus, using the RLE can result in substantial computational savings. 29 | # 30 | # The following API functions are defined: 31 | # encode - Encode binary masks using RLE. 32 | # decode - Decode binary masks encoded via RLE. 33 | # merge - Compute union or intersection of encoded masks. 34 | # minus - Compute A - B 35 | # iou - Compute intersection over union between masks. 36 | # area - Compute area of encoded masks. 37 | # toBbox - Get bounding boxes surrounding encoded masks. 38 | # frPyObjects - Convert polygon, bbox, and uncompressed RLE to encoded RLE mask. 39 | # 40 | # Usage: 41 | # Rs = encode( masks ) 42 | # masks = decode( Rs ) 43 | # R = merge( Rs, intersect=false ) 44 | # o = iou( dt, gt, iscrowd ) 45 | # a = area( Rs ) 46 | # bbs = toBbox( Rs ) 47 | # Rs = frPyObjects( [pyObjects], h, w ) 48 | # 49 | # In the API the following formats are used: 50 | # Rs - [dict] Run-length encoding of binary masks 51 | # R - dict Run-length encoding of binary mask 52 | # masks - [hxwxn] Binary mask(s) (must have type np.ndarray(dtype=uint8) in column-major order) 53 | # iscrowd - [nx1] list of np.ndarray. 1 indicates corresponding gt image has crowd region to ignore 54 | # bbs - [nx4] Bounding box(es) stored as [x y w h] 55 | # poly - Polygon stored as [[x1 y1 x2 y2...],[x1 y1 ...],...] (2D list) 56 | # dt,gt - May be either bounding boxes or encoded masks 57 | # Both poly and bbs are 0-indexed (bbox=[0 0 1 1] encloses first pixel). 58 | # 59 | # Finally, a note about the intersection over union (iou) computation. 60 | # The standard iou of a ground truth (gt) and detected (dt) object is 61 | # iou(gt,dt) = area(intersect(gt,dt)) / area(union(gt,dt)) 62 | # For "crowd" regions, we use a modified criteria. If a gt object is 63 | # marked as "iscrowd", we allow a dt to match any subregion of the gt. 64 | # Choosing gt' in the crowd gt that best matches the dt can be done using 65 | # gt'=intersect(dt,gt). Since by definition union(gt',dt)=dt, computing 66 | # iou(gt,dt,iscrowd) = iou(gt',dt) = area(intersect(gt,dt)) / area(dt) 67 | # For crowd gt regions we use this modified criteria above for the iou. 68 | # 69 | # To compile run "python setup.py build_ext --inplace" 70 | # Please do not contact us for help with compiling. 71 | # 72 | # Microsoft COCO Toolbox. version 2.0 73 | # Data, paper, and tutorials available at: http://mscoco.org/ 74 | # Code written by Piotr Dollar and Tsung-Yi Lin, 2015. 75 | # Licensed under the Simplified BSD License [see coco/license.txt] 76 | 77 | encode = _mask.encode 78 | decode = _mask.decode 79 | iou = _mask.iou 80 | merge = _mask.merge 81 | minus = _mask.minus 82 | area = _mask.area 83 | toBbox = _mask.toBbox 84 | frPyObjects = _mask.frPyObjects 85 | -------------------------------------------------------------------------------- /PythonAPI/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from Cython.Build import cythonize 3 | from distutils.extension import Extension 4 | import numpy as np 5 | 6 | # To compile and install locally run "python setup.py build_ext --inplace" 7 | # To install library to Python site-packages run "python setup.py build_ext install" 8 | 9 | ext_modules = [ 10 | Extension( 11 | 'pycocotools._mask', 12 | sources=['../common/maskApi.c', 'pycocotools/_mask.pyx'], 13 | include_dirs = [np.get_include(), '../common'], 14 | extra_compile_args=['-Wno-cpp', '-Wno-unused-function', '-std=c99'], 15 | ) 16 | ] 17 | 18 | setup(name='pycocotools', 19 | packages=['pycocotools'], 20 | package_dir = {'pycocotools': 'pycocotools'}, 21 | version='2.0', 22 | ext_modules= 23 | cythonize(ext_modules) 24 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Semantic Amodal Segmentation dataset and API 2 | 3 | This is the Python API code for the amodal segmentation dataset proposed in [Semantic Amodal Segmentation](https://arxiv.org/abs/1509.01329) (CVPR 2017). This API code is built on [COCO API](https://github.com/pdollar/coco). 4 | 5 | ## setup 6 | 7 | 1. git clone and compile: 8 | * ```git clone https://github.com/wakeupbuddy/amodalAPI``` 9 | * ```cd PythonAPI; python setup.py build_ext install; cd ..``` 10 | 11 | 2. create soft link for coco/bsds images: 12 | * ```ln -s /your/coco/images ./images``` 13 | * ```ln -s /your/bsds/images ./bsds_images``` 14 | 3. dowload [annotation files](https://drive.google.com/open?id=0B8e3LNo7STslZURoTzhhMFpCelE) and untar. 15 | 16 | ## notebook demo 17 | 18 | 1. To see the annotation and some useful APIs, please run the [ipython notebook demo](https://github.com/Wakeupbuddy/amodalAPI/blob/master/PythonAPI/myAmodalDemo.ipynb). 19 | 20 | ## evaluate 21 | 22 | 1. dowload the [baseline amodalMask output](https://drive.google.com/open?id=0B8e3LNo7STslUGRFUVlQSnZRUVE) on coco val set and untar: 23 | 24 | 2. run the segmentation evaluation. 25 | * ```bash eval.sh``` 26 | 27 | It measures amodal segment proposal quality using average recall. Please see details in table 3a and section 5.1 from [the paper](https://arxiv.org/abs/1509.01329). 28 | 29 | ## annotation tool 30 | 31 | We also release the web tool we used for annotation in another repo [here](https://github.com/Wakeupbuddy/amodal-ui). It's modified based on [OpenSurface](https://github.com/seanbell/opensurfaces). 32 | 33 | ## citation 34 | 35 | If you find this dataset useful to your research, please consider citing: 36 | ``` 37 | @inproceedings{zhu2017semantic, 38 | Author = {Zhu, Yan and Tian, Yuandong and Mexatas, Dimitris and Doll{\'a}r, Piotr}, 39 | Title = {Semantic Amodal Segmentation}, 40 | Booktitle = {Conference on Computer Vision and Pattern Recognition ({CVPR})}, 41 | Year = {2017} 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /batchEval.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os,sys,glob,json 3 | import string 4 | import random 5 | import argparse 6 | from pycocotools.amodal import Amodal 7 | from PythonAPI.myPyAmodalEvalDemo import evalWrapper, filterDtFile 8 | 9 | mystr = "{:10.4f}".format 10 | 11 | class Metric(object): 12 | def __init__(self, name, maxProp): 13 | self.name = name 14 | self.ar1 = 0 15 | self.ar10 = 0 16 | self.ar100 = 0 17 | self.ar1000 = 0 18 | self.ap = 0 19 | self.ap_05 = 0 20 | self.ap_075 = 0 21 | self.ap_none = 0 22 | self.ap_partial = 0 23 | self.ap_heavy = 0 24 | self.ar_none = 0 25 | self.ar_partial = 0 26 | self.ar_heavy = 0 27 | self.maxProp = maxProp 28 | 29 | def summarize(self, outputFile = ''): 30 | if outputFile != '': 31 | fout = open(outputFile, 'a') 32 | myprint = lambda x: fout.write(x + '\n') 33 | else: 34 | myprint = lambda x: print(x) 35 | 36 | myprint("") 37 | myprint("== " + self.name) 38 | myprint("AR1: " + mystr(self.ar1)) 39 | myprint("AR10: " + mystr(self.ar10)) 40 | myprint("AR100: " + mystr(self.ar100)) 41 | if self.maxProp == 1000: 42 | myprint("AR1000: " + mystr(self.ar1000)) 43 | 44 | myprint("AR_none: " + mystr(self.ar_none)) 45 | myprint("AR_partial: " + mystr(self.ar_partial)) 46 | myprint("AR_heavy: " + mystr(self.ar_heavy)) 47 | 48 | if outputFile != '': 49 | fout.close() 50 | 51 | def singleEval(amodalDt, amodalGt, useAmodalGT=1, onlyThings=0, maxProp=1000): 52 | # eval on different occlusion levels 53 | name = "" 54 | if useAmodalGT == 1: 55 | name = name + "Amodal mask" 56 | elif useAmodalGT == 2: 57 | name = name + "visible mask" 58 | else: 59 | raise NotImplementedError 60 | 61 | if onlyThings == 1: 62 | name = name + ", things only" 63 | elif onlyThings == 2: 64 | name = name + ", stuff only" 65 | elif onlyThings == 0: 66 | name = name + ", both stuff and things" 67 | else: 68 | raise NotImplementedError 69 | 70 | metric = Metric(name, maxProp) 71 | 72 | useAmodalDT = 1 73 | occRange = 'all' 74 | stats = evalWrapper(amodalDt, amodalGt, useAmodalGT, useAmodalDT, onlyThings, occRange, maxProp) 75 | metric.ap = stats[0] 76 | metric.ap_05 = stats[1] 77 | metric.ap_075 = stats[2] 78 | metric.ar1 = stats[3] 79 | metric.ar10 = stats[4] 80 | metric.ar100 = stats[5] 81 | metric.ar1000 = stats[6] 82 | 83 | occRange = 'none' 84 | stats = evalWrapper(amodalDt, amodalGt,useAmodalGT, useAmodalDT, onlyThings, occRange, maxProp) 85 | metric.ap_none = stats[0] 86 | if maxProp == 100: 87 | metric.ar_none = stats[5] 88 | elif maxProp == 1000: 89 | metric.ar_none = stats[6] 90 | del stats 91 | 92 | occRange = 'partial' 93 | stats = evalWrapper(amodalDt, amodalGt,useAmodalGT, useAmodalDT, onlyThings, occRange, maxProp) 94 | metric.ap_partial = stats[0] 95 | if maxProp == 100: 96 | metric.ar_partial = stats[5] 97 | elif maxProp == 1000: 98 | metric.ar_partial = stats[6] 99 | del stats 100 | 101 | occRange = 'heavy' 102 | stats = evalWrapper(amodalDt, amodalGt, useAmodalGT, useAmodalDT, onlyThings, occRange, maxProp) 103 | metric.ap_heavy = stats[0] 104 | if maxProp == 100: 105 | metric.ar_heavy = stats[5] 106 | elif maxProp == 1000: 107 | metric.ar_heavy = stats[6] 108 | del stats 109 | 110 | return metric 111 | 112 | def main(args): 113 | annFile = '%s/annotations/COCO_amodal_%s.json'%(args.dataDir,args.dataType) 114 | amodalGt=Amodal(annFile) 115 | imgIds=sorted(amodalGt.getImgIds()) 116 | amodalDtFile = '/tmp/%s_amodalDt.json' %(''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))) 117 | 118 | resFiles = [] 119 | for filename in glob.glob(args.resFileFolder + '*.json'): 120 | resFiles.append(filename) 121 | if len(resFiles) == 0: 122 | print("wrong") 123 | assert len(resFiles) > 0, " wrong resFileFolder." 124 | 125 | amodalDt = filterDtFile(resFiles, imgIds) 126 | with open(amodalDtFile, 'wb') as output: 127 | json.dump(amodalDt, output) 128 | amodalDt=amodalGt.loadRes(amodalDtFile) 129 | # both things and stuff 130 | metric10 = singleEval(amodalDt, amodalGt, onlyThings=0, maxProp=args.maxProp) 131 | # things only 132 | metric11 = singleEval(amodalDt, amodalGt, onlyThings=1, maxProp=args.maxProp) 133 | # amodalGT, stuff only 134 | metric12 = singleEval(amodalDt, amodalGt, onlyThings=2, maxProp=args.maxProp) 135 | 136 | metric10.summarize(args.outputFile) 137 | metric11.summarize(args.outputFile) 138 | metric12.summarize(args.outputFile) 139 | 140 | metrics = {} 141 | metrics['both'] = metric10 142 | metrics['things'] = metric11 143 | metrics['stuff'] = metric12 144 | 145 | os.system("rm -f " + amodalDtFile) 146 | print("done! intermediate file cleaned!") 147 | 148 | return metrics 149 | 150 | if __name__ == "__main__": 151 | parser = argparse.ArgumentParser() 152 | parser.add_argument('-r', '--resFileFolder', required=True) 153 | parser.add_argument('--dataType', default='val2014') 154 | parser.add_argument('--dataDir', default='./') 155 | parser.add_argument('--maxProp', default=1000, type=int) 156 | parser.add_argument('--outputFile', default='', type=str) 157 | args = parser.parse_args() 158 | 159 | metrics = main(args) 160 | -------------------------------------------------------------------------------- /common/gason.cpp: -------------------------------------------------------------------------------- 1 | // https://github.com/vivkin/gason - pulled January 10, 2016 2 | #include "gason.h" 3 | #include 4 | 5 | #define JSON_ZONE_SIZE 4096 6 | #define JSON_STACK_SIZE 32 7 | 8 | const char *jsonStrError(int err) { 9 | switch (err) { 10 | #define XX(no, str) \ 11 | case JSON_##no: \ 12 | return str; 13 | JSON_ERRNO_MAP(XX) 14 | #undef XX 15 | default: 16 | return "unknown"; 17 | } 18 | } 19 | 20 | void *JsonAllocator::allocate(size_t size) { 21 | size = (size + 7) & ~7; 22 | 23 | if (head && head->used + size <= JSON_ZONE_SIZE) { 24 | char *p = (char *)head + head->used; 25 | head->used += size; 26 | return p; 27 | } 28 | 29 | size_t allocSize = sizeof(Zone) + size; 30 | Zone *zone = (Zone *)malloc(allocSize <= JSON_ZONE_SIZE ? JSON_ZONE_SIZE : allocSize); 31 | if (zone == nullptr) 32 | return nullptr; 33 | zone->used = allocSize; 34 | if (allocSize <= JSON_ZONE_SIZE || head == nullptr) { 35 | zone->next = head; 36 | head = zone; 37 | } else { 38 | zone->next = head->next; 39 | head->next = zone; 40 | } 41 | return (char *)zone + sizeof(Zone); 42 | } 43 | 44 | void JsonAllocator::deallocate() { 45 | while (head) { 46 | Zone *next = head->next; 47 | free(head); 48 | head = next; 49 | } 50 | } 51 | 52 | static inline bool isspace(char c) { 53 | return c == ' ' || (c >= '\t' && c <= '\r'); 54 | } 55 | 56 | static inline bool isdelim(char c) { 57 | return c == ',' || c == ':' || c == ']' || c == '}' || isspace(c) || !c; 58 | } 59 | 60 | static inline bool isdigit(char c) { 61 | return c >= '0' && c <= '9'; 62 | } 63 | 64 | static inline bool isxdigit(char c) { 65 | return (c >= '0' && c <= '9') || ((c & ~' ') >= 'A' && (c & ~' ') <= 'F'); 66 | } 67 | 68 | static inline int char2int(char c) { 69 | if (c <= '9') 70 | return c - '0'; 71 | return (c & ~' ') - 'A' + 10; 72 | } 73 | 74 | static double string2double(char *s, char **endptr) { 75 | char ch = *s; 76 | if (ch == '-') 77 | ++s; 78 | 79 | double result = 0; 80 | while (isdigit(*s)) 81 | result = (result * 10) + (*s++ - '0'); 82 | 83 | if (*s == '.') { 84 | ++s; 85 | 86 | double fraction = 1; 87 | while (isdigit(*s)) { 88 | fraction *= 0.1; 89 | result += (*s++ - '0') * fraction; 90 | } 91 | } 92 | 93 | if (*s == 'e' || *s == 'E') { 94 | ++s; 95 | 96 | double base = 10; 97 | if (*s == '+') 98 | ++s; 99 | else if (*s == '-') { 100 | ++s; 101 | base = 0.1; 102 | } 103 | 104 | unsigned int exponent = 0; 105 | while (isdigit(*s)) 106 | exponent = (exponent * 10) + (*s++ - '0'); 107 | 108 | double power = 1; 109 | for (; exponent; exponent >>= 1, base *= base) 110 | if (exponent & 1) 111 | power *= base; 112 | 113 | result *= power; 114 | } 115 | 116 | *endptr = s; 117 | return ch == '-' ? -result : result; 118 | } 119 | 120 | static inline JsonNode *insertAfter(JsonNode *tail, JsonNode *node) { 121 | if (!tail) 122 | return node->next = node; 123 | node->next = tail->next; 124 | tail->next = node; 125 | return node; 126 | } 127 | 128 | static inline JsonValue listToValue(JsonTag tag, JsonNode *tail) { 129 | if (tail) { 130 | auto head = tail->next; 131 | tail->next = nullptr; 132 | return JsonValue(tag, head); 133 | } 134 | return JsonValue(tag, nullptr); 135 | } 136 | 137 | int jsonParse(char *s, char **endptr, JsonValue *value, JsonAllocator &allocator) { 138 | JsonNode *tails[JSON_STACK_SIZE]; 139 | JsonTag tags[JSON_STACK_SIZE]; 140 | char *keys[JSON_STACK_SIZE]; 141 | JsonValue o; 142 | int pos = -1; 143 | bool separator = true; 144 | JsonNode *node; 145 | *endptr = s; 146 | 147 | while (*s) { 148 | while (isspace(*s)) { 149 | ++s; 150 | if (!*s) break; 151 | } 152 | *endptr = s++; 153 | switch (**endptr) { 154 | case '-': 155 | if (!isdigit(*s) && *s != '.') { 156 | *endptr = s; 157 | return JSON_BAD_NUMBER; 158 | } 159 | case '0': 160 | case '1': 161 | case '2': 162 | case '3': 163 | case '4': 164 | case '5': 165 | case '6': 166 | case '7': 167 | case '8': 168 | case '9': 169 | o = JsonValue(string2double(*endptr, &s)); 170 | if (!isdelim(*s)) { 171 | *endptr = s; 172 | return JSON_BAD_NUMBER; 173 | } 174 | break; 175 | case '"': 176 | o = JsonValue(JSON_STRING, s); 177 | for (char *it = s; *s; ++it, ++s) { 178 | int c = *it = *s; 179 | if (c == '\\') { 180 | c = *++s; 181 | switch (c) { 182 | case '\\': 183 | case '"': 184 | case '/': 185 | *it = c; 186 | break; 187 | case 'b': 188 | *it = '\b'; 189 | break; 190 | case 'f': 191 | *it = '\f'; 192 | break; 193 | case 'n': 194 | *it = '\n'; 195 | break; 196 | case 'r': 197 | *it = '\r'; 198 | break; 199 | case 't': 200 | *it = '\t'; 201 | break; 202 | case 'u': 203 | c = 0; 204 | for (int i = 0; i < 4; ++i) { 205 | if (isxdigit(*++s)) { 206 | c = c * 16 + char2int(*s); 207 | } else { 208 | *endptr = s; 209 | return JSON_BAD_STRING; 210 | } 211 | } 212 | if (c < 0x80) { 213 | *it = c; 214 | } else if (c < 0x800) { 215 | *it++ = 0xC0 | (c >> 6); 216 | *it = 0x80 | (c & 0x3F); 217 | } else { 218 | *it++ = 0xE0 | (c >> 12); 219 | *it++ = 0x80 | ((c >> 6) & 0x3F); 220 | *it = 0x80 | (c & 0x3F); 221 | } 222 | break; 223 | default: 224 | *endptr = s; 225 | return JSON_BAD_STRING; 226 | } 227 | } else if ((unsigned int)c < ' ' || c == '\x7F') { 228 | *endptr = s; 229 | return JSON_BAD_STRING; 230 | } else if (c == '"') { 231 | *it = 0; 232 | ++s; 233 | break; 234 | } 235 | } 236 | if (!isdelim(*s)) { 237 | *endptr = s; 238 | return JSON_BAD_STRING; 239 | } 240 | break; 241 | case 't': 242 | if (!(s[0] == 'r' && s[1] == 'u' && s[2] == 'e' && isdelim(s[3]))) 243 | return JSON_BAD_IDENTIFIER; 244 | o = JsonValue(JSON_TRUE); 245 | s += 3; 246 | break; 247 | case 'f': 248 | if (!(s[0] == 'a' && s[1] == 'l' && s[2] == 's' && s[3] == 'e' && isdelim(s[4]))) 249 | return JSON_BAD_IDENTIFIER; 250 | o = JsonValue(JSON_FALSE); 251 | s += 4; 252 | break; 253 | case 'n': 254 | if (!(s[0] == 'u' && s[1] == 'l' && s[2] == 'l' && isdelim(s[3]))) 255 | return JSON_BAD_IDENTIFIER; 256 | o = JsonValue(JSON_NULL); 257 | s += 3; 258 | break; 259 | case ']': 260 | if (pos == -1) 261 | return JSON_STACK_UNDERFLOW; 262 | if (tags[pos] != JSON_ARRAY) 263 | return JSON_MISMATCH_BRACKET; 264 | o = listToValue(JSON_ARRAY, tails[pos--]); 265 | break; 266 | case '}': 267 | if (pos == -1) 268 | return JSON_STACK_UNDERFLOW; 269 | if (tags[pos] != JSON_OBJECT) 270 | return JSON_MISMATCH_BRACKET; 271 | if (keys[pos] != nullptr) 272 | return JSON_UNEXPECTED_CHARACTER; 273 | o = listToValue(JSON_OBJECT, tails[pos--]); 274 | break; 275 | case '[': 276 | if (++pos == JSON_STACK_SIZE) 277 | return JSON_STACK_OVERFLOW; 278 | tails[pos] = nullptr; 279 | tags[pos] = JSON_ARRAY; 280 | keys[pos] = nullptr; 281 | separator = true; 282 | continue; 283 | case '{': 284 | if (++pos == JSON_STACK_SIZE) 285 | return JSON_STACK_OVERFLOW; 286 | tails[pos] = nullptr; 287 | tags[pos] = JSON_OBJECT; 288 | keys[pos] = nullptr; 289 | separator = true; 290 | continue; 291 | case ':': 292 | if (separator || keys[pos] == nullptr) 293 | return JSON_UNEXPECTED_CHARACTER; 294 | separator = true; 295 | continue; 296 | case ',': 297 | if (separator || keys[pos] != nullptr) 298 | return JSON_UNEXPECTED_CHARACTER; 299 | separator = true; 300 | continue; 301 | case '\0': 302 | continue; 303 | default: 304 | return JSON_UNEXPECTED_CHARACTER; 305 | } 306 | 307 | separator = false; 308 | 309 | if (pos == -1) { 310 | *endptr = s; 311 | *value = o; 312 | return JSON_OK; 313 | } 314 | 315 | if (tags[pos] == JSON_OBJECT) { 316 | if (!keys[pos]) { 317 | if (o.getTag() != JSON_STRING) 318 | return JSON_UNQUOTED_KEY; 319 | keys[pos] = o.toString(); 320 | continue; 321 | } 322 | if ((node = (JsonNode *) allocator.allocate(sizeof(JsonNode))) == nullptr) 323 | return JSON_ALLOCATION_FAILURE; 324 | tails[pos] = insertAfter(tails[pos], node); 325 | tails[pos]->key = keys[pos]; 326 | keys[pos] = nullptr; 327 | } else { 328 | if ((node = (JsonNode *) allocator.allocate(sizeof(JsonNode) - sizeof(char *))) == nullptr) 329 | return JSON_ALLOCATION_FAILURE; 330 | tails[pos] = insertAfter(tails[pos], node); 331 | } 332 | tails[pos]->value = o; 333 | } 334 | return JSON_BREAKING_BAD; 335 | } 336 | -------------------------------------------------------------------------------- /common/gason.h: -------------------------------------------------------------------------------- 1 | // https://github.com/vivkin/gason - pulled January 10, 2016 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum JsonTag { 9 | JSON_NUMBER = 0, 10 | JSON_STRING, 11 | JSON_ARRAY, 12 | JSON_OBJECT, 13 | JSON_TRUE, 14 | JSON_FALSE, 15 | JSON_NULL = 0xF 16 | }; 17 | 18 | struct JsonNode; 19 | 20 | #define JSON_VALUE_PAYLOAD_MASK 0x00007FFFFFFFFFFFULL 21 | #define JSON_VALUE_NAN_MASK 0x7FF8000000000000ULL 22 | #define JSON_VALUE_TAG_MASK 0xF 23 | #define JSON_VALUE_TAG_SHIFT 47 24 | 25 | union JsonValue { 26 | uint64_t ival; 27 | double fval; 28 | 29 | JsonValue(double x) 30 | : fval(x) { 31 | } 32 | JsonValue(JsonTag tag = JSON_NULL, void *payload = nullptr) { 33 | assert((uintptr_t)payload <= JSON_VALUE_PAYLOAD_MASK); 34 | ival = JSON_VALUE_NAN_MASK | ((uint64_t)tag << JSON_VALUE_TAG_SHIFT) | (uintptr_t)payload; 35 | } 36 | bool isDouble() const { 37 | return (int64_t)ival <= (int64_t)JSON_VALUE_NAN_MASK; 38 | } 39 | JsonTag getTag() const { 40 | return isDouble() ? JSON_NUMBER : JsonTag((ival >> JSON_VALUE_TAG_SHIFT) & JSON_VALUE_TAG_MASK); 41 | } 42 | uint64_t getPayload() const { 43 | assert(!isDouble()); 44 | return ival & JSON_VALUE_PAYLOAD_MASK; 45 | } 46 | double toNumber() const { 47 | assert(getTag() == JSON_NUMBER); 48 | return fval; 49 | } 50 | char *toString() const { 51 | assert(getTag() == JSON_STRING); 52 | return (char *)getPayload(); 53 | } 54 | JsonNode *toNode() const { 55 | assert(getTag() == JSON_ARRAY || getTag() == JSON_OBJECT); 56 | return (JsonNode *)getPayload(); 57 | } 58 | }; 59 | 60 | struct JsonNode { 61 | JsonValue value; 62 | JsonNode *next; 63 | char *key; 64 | }; 65 | 66 | struct JsonIterator { 67 | JsonNode *p; 68 | 69 | void operator++() { 70 | p = p->next; 71 | } 72 | bool operator!=(const JsonIterator &x) const { 73 | return p != x.p; 74 | } 75 | JsonNode *operator*() const { 76 | return p; 77 | } 78 | JsonNode *operator->() const { 79 | return p; 80 | } 81 | }; 82 | 83 | inline JsonIterator begin(JsonValue o) { 84 | return JsonIterator{o.toNode()}; 85 | } 86 | inline JsonIterator end(JsonValue) { 87 | return JsonIterator{nullptr}; 88 | } 89 | 90 | #define JSON_ERRNO_MAP(XX) \ 91 | XX(OK, "ok") \ 92 | XX(BAD_NUMBER, "bad number") \ 93 | XX(BAD_STRING, "bad string") \ 94 | XX(BAD_IDENTIFIER, "bad identifier") \ 95 | XX(STACK_OVERFLOW, "stack overflow") \ 96 | XX(STACK_UNDERFLOW, "stack underflow") \ 97 | XX(MISMATCH_BRACKET, "mismatch bracket") \ 98 | XX(UNEXPECTED_CHARACTER, "unexpected character") \ 99 | XX(UNQUOTED_KEY, "unquoted key") \ 100 | XX(BREAKING_BAD, "breaking bad") \ 101 | XX(ALLOCATION_FAILURE, "allocation failure") 102 | 103 | enum JsonErrno { 104 | #define XX(no, str) JSON_##no, 105 | JSON_ERRNO_MAP(XX) 106 | #undef XX 107 | }; 108 | 109 | const char *jsonStrError(int err); 110 | 111 | class JsonAllocator { 112 | struct Zone { 113 | Zone *next; 114 | size_t used; 115 | } *head = nullptr; 116 | 117 | public: 118 | JsonAllocator() = default; 119 | JsonAllocator(const JsonAllocator &) = delete; 120 | JsonAllocator &operator=(const JsonAllocator &) = delete; 121 | JsonAllocator(JsonAllocator &&x) : head(x.head) { 122 | x.head = nullptr; 123 | } 124 | JsonAllocator &operator=(JsonAllocator &&x) { 125 | head = x.head; 126 | x.head = nullptr; 127 | return *this; 128 | } 129 | ~JsonAllocator() { 130 | deallocate(); 131 | } 132 | void *allocate(size_t size); 133 | void deallocate(); 134 | }; 135 | 136 | int jsonParse(char *str, char **endptr, JsonValue *value, JsonAllocator &allocator); 137 | -------------------------------------------------------------------------------- /common/maskApi.c: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Microsoft COCO Toolbox. version 2.0 3 | * Data, paper, and tutorials available at: http://mscoco.org/ 4 | * Code written by Piotr Dollar and Tsung-Yi Lin, 2015. 5 | * Licensed under the Simplified BSD License [see coco/license.txt] 6 | **************************************************************************/ 7 | #include "maskApi.h" 8 | #include 9 | #include 10 | 11 | uint umin( uint a, uint b ) { return (ab) ? a : b; } 13 | 14 | void rleInit( RLE *R, siz h, siz w, siz m, uint *cnts ) { 15 | R->h=h; R->w=w; R->m=m; R->cnts=(m==0)?0:malloc(sizeof(uint)*m); 16 | siz j; if(cnts) for(j=0; jcnts[j]=cnts[j]; 17 | } 18 | 19 | void rleFree( RLE *R ) { 20 | free(R->cnts); R->cnts=0; 21 | } 22 | 23 | void rlesInit( RLE **R, siz n ) { 24 | siz i; *R = (RLE*) malloc(sizeof(RLE)*n); 25 | for(i=0; i0 ) { 61 | c=umin(ca,cb); cc+=c; ct=0; 62 | ca-=c; if(!ca && a0 ) { 83 | c=umin(ca,cb); cc+=c; ct=0; 84 | ca-=c; if(!ca && a0) { 105 | crowd=iscrowd!=NULL && iscrowd[g]; 106 | if(dt[d].h!=gt[g].h || dt[d].w!=gt[g].w) { o[g*m+d]=-1; continue; } 107 | siz ka, kb, a, b; uint c, ca, cb, ct, i, u; int va, vb; 108 | ca=dt[d].cnts[0]; ka=dt[d].m; va=vb=0; 109 | cb=gt[g].cnts[0]; kb=gt[g].m; a=b=1; i=u=0; ct=1; 110 | while( ct>0 ) { 111 | c=umin(ca,cb); if(va||vb) { u+=c; if(va&&vb) i+=c; } ct=0; 112 | ca-=c; if(!ca && ad?1:c=dy && xs>xe) || (dxye); 173 | if(flip) { t=xs; xs=xe; xe=t; t=ys; ys=ye; ye=t; } 174 | s = dx>=dy ? (double)(ye-ys)/dx : (double)(xe-xs)/dy; 175 | if(dx>=dy) for( d=0; d<=dx; d++ ) { 176 | t=flip?dx-d:d; u[m]=t+xs; v[m]=(int)(ys+s*t+.5); m++; 177 | } else for( d=0; d<=dy; d++ ) { 178 | t=flip?dy-d:d; v[m]=t+ys; u[m]=(int)(xs+s*t+.5); m++; 179 | } 180 | } 181 | /* get points along y-boundary and downsample */ 182 | free(x); free(y); k=m; m=0; double xd, yd; 183 | x=malloc(sizeof(int)*k); y=malloc(sizeof(int)*k); 184 | for( j=1; jw-1 ) continue; 187 | yd=(double)(v[j]h) yd=h; yd=ceil(yd); 189 | x[m]=(int) xd; y[m]=(int) yd; m++; 190 | } 191 | /* compute rle encoding given y-boundary points */ 192 | k=m; a=malloc(sizeof(uint)*(k+1)); 193 | for( j=0; j0) b[m++]=a[j++]; else { 199 | j++; if(jm, p=0; long x; int more; 206 | char *s=malloc(sizeof(char)*m*6); 207 | for( i=0; icnts[i]; if(i>2) x-=(long) R->cnts[i-2]; more=1; 209 | while( more ) { 210 | char c=x & 0x1f; x >>= 5; more=(c & 0x10) ? x!=-1 : x!=0; 211 | if(more) c |= 0x20; c+=48; s[p++]=c; 212 | } 213 | } 214 | s[p]=0; return s; 215 | } 216 | 217 | void rleFrString( RLE *R, char *s, siz h, siz w ) { 218 | siz m=0, p=0, k; long x; int more; uint *cnts; 219 | while( s[m] ) m++; cnts=malloc(sizeof(uint)*m); m=0; 220 | while( s[p] ) { 221 | x=0; k=0; more=1; 222 | while( more ) { 223 | char c=s[p]-48; x |= (c & 0x1f) << 5*k; 224 | more = c & 0x20; p++; k++; 225 | if(!more && (c & 0x10)) x |= -1 << 5*k; 226 | } 227 | if(m>2) x+=(long) cnts[m-2]; cnts[m++]=(uint) x; 228 | } 229 | rleInit(R,h,w,m,cnts); free(cnts); 230 | } 231 | -------------------------------------------------------------------------------- /common/maskApi.h: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Microsoft COCO Toolbox. version 2.0 3 | * Data, paper, and tutorials available at: http://mscoco.org/ 4 | * Code written by Piotr Dollar and Tsung-Yi Lin, 2015. 5 | * Licensed under the Simplified BSD License [see coco/license.txt] 6 | **************************************************************************/ 7 | #pragma once 8 | 9 | typedef unsigned int uint; 10 | typedef unsigned long siz; 11 | typedef unsigned char byte; 12 | typedef double* BB; 13 | typedef struct { siz h, w, m; uint *cnts; } RLE; 14 | 15 | /* Initialize/destroy RLE. */ 16 | void rleInit( RLE *R, siz h, siz w, siz m, uint *cnts ); 17 | void rleFree( RLE *R ); 18 | 19 | /* Initialize/destroy RLE array. */ 20 | void rlesInit( RLE **R, siz n ); 21 | void rlesFree( RLE **R, siz n ); 22 | 23 | /* Encode binary masks using RLE. */ 24 | void rleEncode( RLE *R, const byte *mask, siz h, siz w, siz n ); 25 | 26 | /* Decode binary masks encoded via RLE. */ 27 | void rleDecode( const RLE *R, byte *mask, siz n ); 28 | 29 | /* Compute union or intersection of encoded masks. */ 30 | void rleMerge( const RLE *R, RLE *M, siz n, int intersect ); 31 | 32 | /* Compute A - B of encoded masks A and B. */ 33 | void rleMinus( const RLE *R, RLE *M, siz n); 34 | 35 | /* Compute area of encoded masks. */ 36 | void rleArea( const RLE *R, siz n, uint *a ); 37 | 38 | /* Compute intersection over union between masks. */ 39 | void rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o ); 40 | 41 | /* Compute intersection over union between bounding boxes. */ 42 | void bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o ); 43 | 44 | /* Get bounding boxes surrounding encoded masks. */ 45 | void rleToBbox( const RLE *R, BB bb, siz n ); 46 | 47 | /* Convert bounding boxes to encoded masks. */ 48 | void rleFrBbox( RLE *R, const BB bb, siz h, siz w, siz n ); 49 | 50 | /* Convert polygon to encoded mask. */ 51 | void rleFrPoly( RLE *R, const double *xy, siz k, siz h, siz w ); 52 | 53 | /* Get compressed string representation of encoded mask. */ 54 | char* rleToString( const RLE *R ); 55 | 56 | /* Convert from compressed string representation of encoded mask. */ 57 | void rleFrString( RLE *R, char *s, siz h, siz w ); 58 | -------------------------------------------------------------------------------- /eval.sh: -------------------------------------------------------------------------------- 1 | # evaluate the amodal segments from JSONDIR 2 | JSONDIR='./dt_amodalMask/' 3 | python batchEval.py -r $JSONDIR 4 | --------------------------------------------------------------------------------