Commit 64b02fb6 authored by liangjing's avatar liangjing
Browse files

version 1

parents
Pipeline #176 failed with stages
in 0 seconds
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"from pycocotools.coco import COCO\n",
"from pycocotools.cocoeval import COCOeval\n",
"import numpy as np\n",
"import skimage.io as io\n",
"import pylab\n",
"pylab.rcParams['figure.figsize'] = (10.0, 8.0)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Running demo for *bbox* results.\n"
]
}
],
"source": [
"annType = ['segm','bbox','keypoints']\n",
"annType = annType[1] #specify type here\n",
"prefix = 'person_keypoints' if annType=='keypoints' else 'instances'\n",
"print 'Running demo for *%s* results.'%(annType)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"loading annotations into memory...\n",
"Done (t=8.01s)\n",
"creating index...\n",
"index created!\n"
]
}
],
"source": [
"#initialize COCO ground truth api\n",
"dataDir='../'\n",
"dataType='val2014'\n",
"annFile = '%s/annotations/%s_%s.json'%(dataDir,prefix,dataType)\n",
"cocoGt=COCO(annFile)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading and preparing results... \n",
"DONE (t=0.05s)\n",
"creating index...\n",
"index created!\n"
]
}
],
"source": [
"#initialize COCO detections api\n",
"resFile='%s/results/%s_%s_fake%s100_results.json'\n",
"resFile = resFile%(dataDir, prefix, dataType, annType)\n",
"cocoDt=cocoGt.loadRes(resFile)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"imgIds=sorted(cocoGt.getImgIds())\n",
"imgIds=imgIds[0:100]\n",
"imgId = imgIds[np.random.randint(100)]"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Running per image evaluation... \n",
"DONE (t=0.46s).\n",
"Accumulating evaluation results... \n",
"DONE (t=0.38s).\n",
" Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.505\n",
" Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.697\n",
" Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.573\n",
" Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.586\n",
" Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.519\n",
" Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.501\n",
" Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.387\n",
" Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.594\n",
" Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.595\n",
" Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.640\n",
" Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.566\n",
" Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.564\n"
]
}
],
"source": [
"# running evaluation\n",
"cocoEval = COCOeval(cocoGt,cocoDt,annType)\n",
"cocoEval.params.imgIds = imgIds\n",
"cocoEval.evaluate()\n",
"cocoEval.accumulate()\n",
"cocoEval.summarize()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Metadata-Version: 1.0
Name: pycocotools
Version: 2.0+nv0.7.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
setup.py
../common/maskApi.c
pycocotools/__init__.py
pycocotools/_mask.c
pycocotools/_mask.pyx
pycocotools/coco.py
pycocotools/cocoeval.py
pycocotools/ext.cpp
pycocotools/mask.py
pycocotools/simdjson.cpp
pycocotools.egg-info/PKG-INFO
pycocotools.egg-info/SOURCES.txt
pycocotools.egg-info/dependency_links.txt
pycocotools.egg-info/requires.txt
pycocotools.egg-info/top_level.txt
\ No newline at end of file
setuptools>=18.0
cython>=0.27.3
matplotlib>=2.1.0
# distutils: language = c
# distutils: sources = ../common/maskApi.c
#**************************************************************************
# Microsoft COCO Toolbox. version 2.0
# Data, paper, and tutorials available at: http://mscoco.org/
# Code written by Piotr Dollar and Tsung-Yi Lin, 2015.
# Licensed under the Simplified BSD License [see coco/license.txt]
#**************************************************************************
__author__ = 'tsungyi'
import sys
PYTHON_VERSION = sys.version_info[0]
# import both Python-level and C-level symbols of Numpy
# the API uses Numpy to interface C and Python
import numpy as np
cimport numpy as np
from libc.stdlib cimport malloc, free
# intialized Numpy. must do.
np.import_array()
# import numpy C function
# we use PyArray_ENABLEFLAGS to make Numpy ndarray responsible to memoery management
cdef extern from "numpy/arrayobject.h":
void PyArray_ENABLEFLAGS(np.ndarray arr, int flags)
# Declare the prototype of the C functions in MaskApi.h
cdef extern from "maskApi.h":
ctypedef unsigned int uint
ctypedef unsigned long siz
ctypedef unsigned char byte
ctypedef double* BB
ctypedef struct RLE:
siz h,
siz w,
siz m,
uint* cnts,
void rlesInit( RLE **R, siz n )
void rleEncode( RLE *R, const byte *M, siz h, siz w, siz n )
void rleEncodePaste( RLE *R, const byte *M, siz h, siz w, siz n, siz oy, siz ox, siz oh, siz ow )
void rleDecode( const RLE *R, byte *mask, siz n )
void rleMerge( const RLE *R, RLE *M, siz n, int intersect )
void rleArea( const RLE *R, siz n, uint *a )
void rleIou( RLE *dt, RLE *gt, siz m, siz n, byte *iscrowd, double *o )
void bbIou( BB dt, BB gt, siz m, siz n, byte *iscrowd, double *o )
void rleToBbox( const RLE *R, BB bb, siz n )
void rleFrBbox( RLE *R, const BB bb, siz h, siz w, siz n )
void rleFrPoly( RLE *R, const double *xy, siz k, siz h, siz w )
char* rleToString( const RLE *R )
void rleFrString( RLE *R, char *s, siz h, siz w )
# python class to wrap RLE array in C
# the class handles the memory allocation and deallocation
cdef class RLEs:
cdef RLE *_R
cdef siz _n
def __cinit__(self, siz n =0):
rlesInit(&self._R, n)
self._n = n
# free the RLE array here
def __dealloc__(self):
if self._R is not NULL:
for i in range(self._n):
free(self._R[i].cnts)
free(self._R)
def __getattr__(self, key):
if key == 'n':
return self._n
raise AttributeError(key)
# python class to wrap Mask array in C
# the class handles the memory allocation and deallocation
cdef class Masks:
cdef byte *_mask
cdef siz _h
cdef siz _w
cdef siz _n
def __cinit__(self, h, w, n):
self._mask = <byte*> malloc(h*w*n* sizeof(byte))
self._h = h
self._w = w
self._n = n
# def __dealloc__(self):
# the memory management of _mask has been passed to np.ndarray
# it doesn't need to be freed here
# called when passing into np.array() and return an np.ndarray in column-major order
def __array__(self):
cdef np.npy_intp shape[1]
shape[0] = <np.npy_intp> self._h*self._w*self._n
# Create a 1D array, and reshape it to fortran/Matlab column-major array
ndarray = np.PyArray_SimpleNewFromData(1, shape, np.NPY_UINT8, self._mask).reshape((self._h, self._w, self._n), order='F')
# The _mask allocated by Masks is now handled by ndarray
PyArray_ENABLEFLAGS(ndarray, np.NPY_OWNDATA)
return ndarray
# internal conversion from Python RLEs object to compressed RLE format
def _toString(RLEs Rs):
cdef siz n = Rs.n
cdef bytes py_string
cdef char* c_string
objs = []
for i in range(n):
c_string = rleToString( <RLE*> &Rs._R[i] )
py_string = c_string
objs.append({
'size': [Rs._R[i].h, Rs._R[i].w],
'counts': py_string
})
free(c_string)
return objs
# internal conversion from compressed RLE format to Python RLEs object
def _frString(rleObjs):
cdef siz n = len(rleObjs)
Rs = RLEs(n)
cdef bytes py_string
cdef char* c_string
for i, obj in enumerate(rleObjs):
if PYTHON_VERSION == 2:
py_string = str(obj['counts']).encode('utf8')
elif PYTHON_VERSION == 3:
py_string = str.encode(obj['counts']) if type(obj['counts']) == str else obj['counts']
else:
raise Exception('Python version must be 2 or 3')
c_string = py_string
rleFrString( <RLE*> &Rs._R[i], <char*> c_string, obj['size'][0], obj['size'][1] )
return Rs
# encode mask to RLEs objects
# list of RLE string can be generated by RLEs member function
def encode(np.ndarray[np.uint8_t, ndim=3, mode='fortran'] mask, oy=0, ox=0, oh=0, ow=0):
h, w, n = mask.shape[0], mask.shape[1], mask.shape[2]
cdef RLEs Rs = RLEs(n)
if oh > 0 and ow > 0:
rleEncodePaste(Rs._R,<byte*>mask.data,h,w,n,oy,ox,oh,ow)
else:
rleEncode(Rs._R,<byte*>mask.data,h,w,n)
objs = _toString(Rs)
return objs
# decode mask from compressed list of RLE string or RLEs object
def decode(rleObjs):
cdef RLEs Rs = _frString(rleObjs)
h, w, n = Rs._R[0].h, Rs._R[0].w, Rs._n
masks = Masks(h, w, n)
rleDecode(<RLE*>Rs._R, masks._mask, n);
return np.array(masks)
def merge(rleObjs, intersect=0):
cdef RLEs Rs = _frString(rleObjs)
cdef RLEs R = RLEs(1)
rleMerge(<RLE*>Rs._R, <RLE*> R._R, <siz> Rs._n, intersect)
obj = _toString(R)[0]
return obj
def area(rleObjs):
cdef RLEs Rs = _frString(rleObjs)
cdef uint* _a = <uint*> malloc(Rs._n* sizeof(uint))
rleArea(Rs._R, Rs._n, _a)
cdef np.npy_intp shape[1]
shape[0] = <np.npy_intp> Rs._n
a = np.array((Rs._n, ), dtype=np.uint8)
a = np.PyArray_SimpleNewFromData(1, shape, np.NPY_UINT32, _a)
PyArray_ENABLEFLAGS(a, np.NPY_OWNDATA)
return a
# iou computation. support function overload (RLEs-RLEs and bbox-bbox).
def iou( dt, gt, pyiscrowd ):
def _preproc(objs):
if len(objs) == 0:
return objs
if type(objs) == np.ndarray:
if len(objs.shape) == 1:
objs = objs.reshape((objs[0], 1))
# check if it's Nx4 bbox
if not len(objs.shape) == 2 or not objs.shape[1] == 4:
raise Exception('numpy ndarray input is only for *bounding boxes* and should have Nx4 dimension')
objs = objs.astype(np.double)
elif type(objs) == list:
# check if list is in box format and convert it to np.ndarray
isbox = np.all(np.array([(len(obj)==4) and ((type(obj)==list) or (type(obj)==np.ndarray)) for obj in objs]))
isrle = np.all(np.array([type(obj) == dict for obj in objs]))
if isbox:
objs = np.array(objs, dtype=np.double)
if len(objs.shape) == 1:
objs = objs.reshape((1,objs.shape[0]))
elif isrle:
objs = _frString(objs)
else:
raise Exception('list input can be bounding box (Nx4) or RLEs ([RLE])')
else:
raise Exception('unrecognized type. The following type: RLEs (rle), np.ndarray (box), and list (box) are supported.')
return objs
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):
rleIou( <RLE*> dt._R, <RLE*> gt._R, m, n, <byte*> iscrowd.data, <double*> _iou.data )
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):
bbIou( <BB> dt.data, <BB> gt.data, m, n, <byte*> iscrowd.data, <double*>_iou.data )
def _len(obj):
cdef siz N = 0
if type(obj) == RLEs:
N = obj.n
elif len(obj)==0:
pass
elif type(obj) == np.ndarray:
N = obj.shape[0]
return N
# convert iscrowd to numpy array
cdef np.ndarray[np.uint8_t, ndim=1] iscrowd = np.array(pyiscrowd, dtype=np.uint8)
# simple type checking
cdef siz m, n
dt = _preproc(dt)
gt = _preproc(gt)
m = _len(dt)
n = _len(gt)
if m == 0 or n == 0:
return []
if not type(dt) == type(gt):
raise Exception('The dt and gt should have the same data type, either RLEs, list or np.ndarray')
# define local variables
cdef double* _iou = <double*> 0
cdef np.npy_intp shape[1]
# check type and assign iou function
if type(dt) == RLEs:
_iouFun = _rleIou
elif type(dt) == np.ndarray:
_iouFun = _bbIou
else:
raise Exception('input data type not allowed.')
_iou = <double*> malloc(m*n* sizeof(double))
iou = np.zeros((m*n, ), dtype=np.double)
shape[0] = <np.npy_intp> m*n
iou = np.PyArray_SimpleNewFromData(1, shape, np.NPY_DOUBLE, _iou)
PyArray_ENABLEFLAGS(iou, np.NPY_OWNDATA)
_iouFun(dt, gt, iscrowd, m, n, iou)
return iou.reshape((m,n), order='F')
def toBbox( rleObjs ):
cdef RLEs Rs = _frString(rleObjs)
cdef siz n = Rs.n
cdef BB _bb = <BB> malloc(4*n* sizeof(double))
rleToBbox( <const RLE*> Rs._R, _bb, n )
cdef np.npy_intp shape[1]
shape[0] = <np.npy_intp> 4*n
bb = np.array((1,4*n), dtype=np.double)
bb = np.PyArray_SimpleNewFromData(1, shape, np.NPY_DOUBLE, _bb).reshape((n, 4))
PyArray_ENABLEFLAGS(bb, np.NPY_OWNDATA)
return bb
def frBbox(np.ndarray[np.double_t, ndim=2] bb, siz h, siz w ):
cdef siz n = bb.shape[0]
Rs = RLEs(n)
rleFrBbox( <RLE*> Rs._R, <const BB> bb.data, h, w, n )
objs = _toString(Rs)
return objs
def frPoly( poly, siz h, siz w ):
cdef np.ndarray[np.double_t, ndim=1] np_poly
n = len(poly)
Rs = RLEs(n)
for i, p in enumerate(poly):
np_poly = np.array(p, dtype=np.double, order='F')
rleFrPoly( <RLE*>&Rs._R[i], <const double*> np_poly.data, int(len(p)/2), h, w )
objs = _toString(Rs)
return objs
def frUncompressedRLE(ucRles, siz h, siz w):
cdef np.ndarray[np.uint32_t, ndim=1] cnts
cdef RLE R
cdef uint *data
n = len(ucRles)
objs = []
for i in range(n):
Rs = RLEs(1)
cnts = np.array(ucRles[i]['counts'], dtype=np.uint32)
# time for malloc can be saved here but it's fine
data = <uint*> malloc(len(cnts)* sizeof(uint))
for j in range(len(cnts)):
data[j] = <uint> cnts[j]
R = RLE(ucRles[i]['size'][0], ucRles[i]['size'][1], len(cnts), <uint*> data)
Rs._R[0] = R
objs.append(_toString(Rs)[0])
return objs
def frPyObjects(pyobj, h, w):
# encode rle from a list of python objects
if type(pyobj) == np.ndarray:
objs = frBbox(pyobj, h, w)
elif type(pyobj) == list and len(pyobj[0]) == 4:
objs = frBbox(pyobj, h, w)
elif type(pyobj) == list and len(pyobj[0]) > 4:
objs = frPoly(pyobj, h, w)
elif type(pyobj) == list and type(pyobj[0]) == dict \
and 'counts' in pyobj[0] and 'size' in pyobj[0]:
objs = frUncompressedRLE(pyobj, h, w)
# encode rle from single python object
elif type(pyobj) == list and len(pyobj) == 4:
objs = frBbox([pyobj], h, w)[0]
elif type(pyobj) == list and len(pyobj) > 4:
objs = frPoly([pyobj], h, w)[0]
elif type(pyobj) == dict and 'counts' in pyobj and 'size' in pyobj:
objs = frUncompressedRLE([pyobj], h, w)[0]
else:
raise Exception('input type is not supported.')
return objs
__author__ = 'tylin'
__version__ = '2.0'
# Interface for accessing the Microsoft COCO dataset.
# Microsoft COCO is a large image dataset designed for object detection,
# segmentation, and caption generation. pycocotools is a Python API that
# assists in loading, parsing and visualizing the annotations in COCO.
# Please visit http://mscoco.org/ for more information on COCO, including
# for the data, paper, and tutorials. The exact format of the annotations
# is also described on the COCO website. For example usage of the pycocotools
# please see pycocotools_demo.ipynb. In addition to this API, please download both
# the COCO images and annotations in order to run the demo.
# An alternative to using the API is to load the annotations directly
# into Python dictionary
# Using the API provides additional utility functions. Note that this API
# supports both *instance* and *caption* annotations. In the case of
# captions not all functions are defined (e.g. categories are undefined).
# The following API functions are defined:
# COCO - COCO api class that loads COCO annotation file and prepare data structures.
# decodeMask - Decode binary mask M encoded via run-length encoding.
# encodeMask - Encode binary mask M using run-length encoding.
# getAnnIds - Get ann ids that satisfy given filter conditions.
# getCatIds - Get cat ids that satisfy given filter conditions.
# getImgIds - Get img ids that satisfy given filter conditions.
# loadAnns - Load anns with the specified ids.
# loadCats - Load cats with the specified ids.
# loadImgs - Load imgs with the specified ids.
# annToMask - Convert segmentation in an annotation to binary mask.
# showAnns - Display the specified annotations.
# loadRes - Load algorithm results and create API for accessing them.
# download - Download COCO images from mscoco.org server.
# Throughout the API "ann"=annotation, "cat"=category, and "img"=image.
# Help on each functions can be accessed by: "help COCO>function".
# See also COCO>decodeMask,
# COCO>encodeMask, COCO>getAnnIds, COCO>getCatIds,
# COCO>getImgIds, COCO>loadAnns, COCO>loadCats,
# COCO>loadImgs, COCO>annToMask, COCO>showAnns
# Microsoft COCO Toolbox. version 2.0
# Data, paper, and tutorials available at: http://mscoco.org/
# Code written by Piotr Dollar and Tsung-Yi Lin, 2014.
# Licensed under the Simplified BSD License [see bsd.txt]
try:
import ujson as json
except:
import json
print("NOTE! Installing ujson may make loading annotations faster.")
import time
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Polygon
import numpy as np
import copy
import itertools
from . import mask as maskUtils
import os
from collections import defaultdict
import sys
PYTHON_VERSION = sys.version_info[0]
if PYTHON_VERSION == 2:
from urllib import urlretrieve
elif PYTHON_VERSION == 3:
from urllib.request import urlretrieve
import ext
import multiprocessing as mp
def mp_pool_task(func, args):
#os.environ["OMP_PROC_BIND"]="close"
#os.environ["OMP_PLACES"]="cores"
results = func(*args)
if func is ext.cpp_evaluate:
return results
def mp_pool_wrapper(proc_pid_map, func, args):
pid = os.getpid()
if proc_pid_map is None or len(proc_pid_map) == 0:
proc_pid_map = {pid:0}
proc_id = proc_pid_map[pid]
if func is ext.cpp_create_index:
newargs = list(args)
newargs.insert(2, proc_id)
mp_pool_task(func, tuple(newargs))
if func is ext.cpp_load_res_numpy:
newresFile = np.load(args[0], allow_pickle=True)
mp_pool_task(func, (newresFile, args[1]))
if func is ext.cpp_load_res_json:
mp_pool_task(func, args)
if func is ext.cpp_evaluate:
return mp_pool_task(func, args)
def _isArrayLike(obj):
return hasattr(obj, '__iter__') and hasattr(obj, '__len__')
class COCO:
def __init__(self, annotation_file=None, use_ext=False, multi_procs=(1,None), num_threads=1):
"""
Constructor of Microsoft COCO helper class for reading and visualizing annotations.
:param annotation_file (str): location of annotation file
:param image_folder (str): location to the folder that hosts images.
:return:
"""
# load dataset
self.dataset,self.anns,self.cats,self.imgs = dict(),dict(),dict(),dict()
self.imgToAnns, self.catToImgs = defaultdict(list), defaultdict(list)
self.annotation_file = annotation_file
self.use_ext = use_ext
self.num_procs, self.proc_pid_map = multi_procs
self.num_threads = num_threads
if not self.annotation_file == None:
print('Loading annotations into memory...')
tic = time.time()
dataset = json.load(open(annotation_file, 'r'))
assert type(dataset)==dict, 'annotation file format {} not supported'.format(type(dataset))
self.dataset = dataset
print('Done (t={:0.2f}s)'.format(time.time()- tic))
self.createIndex()
def createIndex(self, use_ext=False):
# create index
print('Creating index...')
if self.use_ext or use_ext:
tic = time.time()
input_iter = (self.proc_pid_map, ext.cpp_create_index,
(self.annotation_file, self.num_procs, self.num_threads))
mp_pool_wrapper(*input_iter)
print('Done (t={:0.2f}s)'.format(time.time() - tic))
return
anns, cats, imgs = {}, {}, {}
imgToAnns,catToImgs = defaultdict(list),defaultdict(list)
if 'annotations' in self.dataset:
for ann in self.dataset['annotations']:
imgToAnns[ann['image_id']].append(ann)
anns[ann['id']] = ann
if 'images' in self.dataset:
for img in self.dataset['images']:
imgs[img['id']] = img
if 'categories' in self.dataset:
for cat in self.dataset['categories']:
cats[cat['id']] = cat
if 'annotations' in self.dataset and 'categories' in self.dataset:
for ann in self.dataset['annotations']:
catToImgs[ann['category_id']].append(ann['image_id'])
print('index created!')
# create class members
self.anns = anns
self.imgToAnns = imgToAnns
self.catToImgs = catToImgs
self.imgs = imgs
self.cats = cats
def info(self):
"""
Print information about the annotation file.
:return:
"""
for key, value in self.dataset['info'].items():
print('{}: {}'.format(key, value))
def getimgToAnns(self):
return self.imgToAnns;
def getAnnIds(self, imgIds=[], catIds=[], areaRng=[], iscrowd=None):
"""
Get ann ids that satisfy given filter conditions. default skips that filter
:param imgIds (int array) : get anns for given imgs
catIds (int array) : get anns for given cats
areaRng (float array) : get anns for given area range (e.g. [0 inf])
iscrowd (boolean) : get anns for given crowd label (False or True)
:return: ids (int array) : integer array of ann ids
"""
imgIds = imgIds if _isArrayLike(imgIds) else [imgIds]
catIds = catIds if _isArrayLike(catIds) else [catIds]
if len(imgIds) == len(catIds) == len(areaRng) == 0:
anns = self.dataset['annotations']
else:
if not len(imgIds) == 0:
lists = [self.imgToAnns[imgId] for imgId in imgIds if imgId in self.imgToAnns]
anns = list(itertools.chain.from_iterable(lists))
else:
anns = self.dataset['annotations']
anns = anns if len(catIds) == 0 else [ann for ann in anns if ann['category_id'] in catIds]
anns = anns if len(areaRng) == 0 else [ann for ann in anns if ann['area'] > areaRng[0] and ann['area'] < areaRng[1]]
if not iscrowd == None:
ids = [ann['id'] for ann in anns if ann['iscrowd'] == iscrowd]
else:
ids = [ann['id'] for ann in anns]
return ids
def getCatIds(self, catNms=[], supNms=[], catIds=[]):
"""
filtering parameters. default skips that filter.
:param catNms (str array) : get cats for given cat names
:param supNms (str array) : get cats for given supercategory names
:param catIds (int array) : get cats for given cat ids
:return: ids (int array) : integer array of cat ids
"""
catNms = catNms if _isArrayLike(catNms) else [catNms]
supNms = supNms if _isArrayLike(supNms) else [supNms]
catIds = catIds if _isArrayLike(catIds) else [catIds]
if len(catNms) == len(supNms) == len(catIds) == 0:
cats = self.dataset['categories']
else:
cats = self.dataset['categories']
cats = cats if len(catNms) == 0 else [cat for cat in cats if cat['name'] in catNms]
cats = cats if len(supNms) == 0 else [cat for cat in cats if cat['supercategory'] in supNms]
cats = cats if len(catIds) == 0 else [cat for cat in cats if cat['id'] in catIds]
ids = [cat['id'] for cat in cats]
return ids
def getImgIds(self, imgIds=[], catIds=[]):
'''
Get img ids that satisfy given filter conditions.
:param imgIds (int array) : get imgs for given ids
:param catIds (int array) : get imgs with all given cats
:return: ids (int array) : integer array of img ids
'''
imgIds = imgIds if _isArrayLike(imgIds) else [imgIds]
catIds = catIds if _isArrayLike(catIds) else [catIds]
if len(imgIds) == len(catIds) == 0:
ids = self.imgs.keys()
else:
ids = set(imgIds)
for i, catId in enumerate(catIds):
if i == 0 and len(ids) == 0:
ids = set(self.catToImgs[catId])
else:
ids &= set(self.catToImgs[catId])
return list(ids)
def getAnns(self):
return self.anns;
def loadAnns(self, ids=[]):
"""
Load anns with the specified ids.
:param ids (int array) : integer ids specifying anns
:return: anns (object array) : loaded ann objects
"""
if _isArrayLike(ids):
return [self.anns[id] for id in ids]
elif type(ids) == int:
return [self.anns[ids]]
def loadCats(self, ids=[]):
"""
Load cats with the specified ids.
:param ids (int array) : integer ids specifying cats
:return: cats (object array) : loaded cat objects
"""
if _isArrayLike(ids):
return [self.cats[id] for id in ids]
elif type(ids) == int:
return [self.cats[ids]]
def loadImgs(self, ids=[]):
"""
Load anns with the specified ids.
:param ids (int array) : integer ids specifying img
:return: imgs (object array) : loaded img objects
"""
if _isArrayLike(ids):
return [self.imgs[id] for id in ids]
elif type(ids) == int:
return [self.imgs[ids]]
def showAnns(self, anns):
"""
Display the specified annotations.
:param anns (array of object): annotations to display
:return: None
"""
if len(anns) == 0:
return 0
if 'segmentation' in anns[0] or 'keypoints' in anns[0]:
datasetType = 'instances'
elif 'caption' in anns[0]:
datasetType = 'captions'
else:
raise Exception('datasetType not supported')
if datasetType == 'instances':
ax = plt.gca()
ax.set_autoscale_on(False)
polygons = []
color = []
for ann in anns:
c = (np.random.random((1, 3))*0.6+0.4).tolist()[0]
if 'segmentation' in ann:
if type(ann['segmentation']) == list:
# polygon
for seg in ann['segmentation']:
poly = np.array(seg).reshape((int(len(seg)/2), 2))
polygons.append(Polygon(poly))
color.append(c)
else:
# mask
t = self.imgs[ann['image_id']]
if type(ann['segmentation']['counts']) == list:
rle = maskUtils.frPyObjects([ann['segmentation']], t['height'], t['width'])
else:
rle = [ann['segmentation']]
m = maskUtils.decode(rle)
img = np.ones( (m.shape[0], m.shape[1], 3) )
if ann['iscrowd'] == 1:
color_mask = np.array([2.0,166.0,101.0])/255
if ann['iscrowd'] == 0:
color_mask = np.random.random((1, 3)).tolist()[0]
for i in range(3):
img[:,:,i] = color_mask[i]
ax.imshow(np.dstack( (img, m*0.5) ))
if 'keypoints' in ann and type(ann['keypoints']) == list:
# turn skeleton into zero-based index
sks = np.array(self.loadCats(ann['category_id'])[0]['skeleton'])-1
kp = np.array(ann['keypoints'])
x = kp[0::3]
y = kp[1::3]
v = kp[2::3]
for sk in sks:
if np.all(v[sk]>0):
plt.plot(x[sk],y[sk], linewidth=3, color=c)
plt.plot(x[v>0], y[v>0],'o',markersize=8, markerfacecolor=c, markeredgecolor='k',markeredgewidth=2)
plt.plot(x[v>1], y[v>1],'o',markersize=8, markerfacecolor=c, markeredgecolor=c, markeredgewidth=2)
p = PatchCollection(polygons, facecolor=color, linewidths=0, alpha=0.4)
ax.add_collection(p)
p = PatchCollection(polygons, facecolor='none', edgecolors=color, linewidths=2)
ax.add_collection(p)
elif datasetType == 'captions':
for ann in anns:
print(ann['caption'])
def loadRes(self, resFile, use_ext=False):
"""
Load result file and return a result api object.
:param resFile (str) : file name of result file
:return: res (obj) : result api object
"""
print('Loading and preparing results...')
tic = time.time()
res = COCO()
if self.use_ext or use_ext:
if type(resFile) == np.ndarray:
resfilename = ''.join(['/dev/shm/resfile_',str(os.getpid()),'.npy'])
np.save(resfilename, resFile)
input_iter = (self.proc_pid_map, ext.cpp_load_res_numpy,
(resfilename, self.num_threads))
elif type(resFile) == str:
input_iter = (self.proc_pid_map, ext.cpp_load_res_json,
(resFile, self.num_threads))
else:
print("loadRes only supports numpy array or json file name as its input")
mp_pool_wrapper(*input_iter)
print('DONE (t={:0.2f}s)'.format(time.time()- tic))
return res
res.dataset['images'] = [img for img in self.dataset['images']]
if type(resFile) == str: #or type(resFile) == unicode:
anns = json.load(open(resFile))
elif type(resFile) == np.ndarray:
anns = self.loadNumpyAnnotations(resFile)
else:
anns = resFile
assert type(anns) == list, 'results in not an array of objects'
annsImgIds = [ann['image_id'] for ann in anns]
assert set(annsImgIds) == (set(annsImgIds) & set(self.getImgIds())), \
'Results do not correspond to current coco set'
if 'caption' in anns[0]:
imgIds = set([img['id'] for img in res.dataset['images']]) & set([ann['image_id'] for ann in anns])
res.dataset['images'] = [img for img in res.dataset['images'] if img['id'] in imgIds]
for id, ann in enumerate(anns):
ann['id'] = id+1
elif 'bbox' in anns[0] and not anns[0]['bbox'] == []:
res.dataset['categories'] = copy.deepcopy(self.dataset['categories'])
for id, ann in enumerate(anns):
bb = ann['bbox']
x1, x2, y1, y2 = [bb[0], bb[0]+bb[2], bb[1], bb[1]+bb[3]]
if not 'segmentation' in ann:
ann['segmentation'] = [[x1, y1, x1, y2, x2, y2, x2, y1]]
ann['area'] = bb[2]*bb[3]
ann['id'] = id+1
ann['iscrowd'] = 0
elif 'segmentation' in anns[0]:
res.dataset['categories'] = copy.deepcopy(self.dataset['categories'])
for id, ann in enumerate(anns):
# now only support compressed RLE format as segmentation results
ann['area'] = maskUtils.area(ann['segmentation'])
if not 'bbox' in ann:
ann['bbox'] = maskUtils.toBbox(ann['segmentation'])
ann['id'] = id+1
ann['iscrowd'] = 0
elif 'keypoints' in anns[0]:
res.dataset['categories'] = copy.deepcopy(self.dataset['categories'])
for id, ann in enumerate(anns):
s = ann['keypoints']
x = s[0::3]
y = s[1::3]
x0,x1,y0,y1 = np.min(x), np.max(x), np.min(y), np.max(y)
ann['area'] = (x1-x0)*(y1-y0)
ann['id'] = id + 1
ann['bbox'] = [x0,y0,x1-x0,y1-y0]
print('DONE (t={:0.2f}s)'.format(time.time()- tic))
res.dataset['annotations'] = anns
res.createIndex(self.use_ext)
return res
def download(self, tarDir = None, imgIds = [] ):
'''
Download COCO images from mscoco.org server.
:param tarDir (str): COCO results directory name
imgIds (list): images to be downloaded
:return:
'''
if tarDir is None:
print('Please specify target directory')
return -1
if len(imgIds) == 0:
imgs = self.imgs.values()
else:
imgs = self.loadImgs(imgIds)
N = len(imgs)
if not os.path.exists(tarDir):
os.makedirs(tarDir)
for i, img in enumerate(imgs):
tic = time.time()
fname = os.path.join(tarDir, img['file_name'])
if not os.path.exists(fname):
urlretrieve(img['coco_url'], fname)
print('downloaded {}/{} images (t={:0.1f}s)'.format(i, N, time.time()- tic))
def loadNumpyAnnotations(self, data):
"""
Convert result data from a numpy array [Nx7] where each row contains {imageID,x1,y1,w,h,score,class}
:param data (numpy.ndarray)
:return: annotations (python nested list)
"""
print('Converting ndarray to lists...')
assert(type(data) == np.ndarray)
print(data.shape)
assert(data.shape[1] == 7)
N = data.shape[0]
ann = []
for i in range(N):
if i % 1000000 == 0:
print('{}/{}'.format(i,N))
ann += [{
'image_id' : int(data[i, 0]),
'bbox' : [ data[i, 1], data[i, 2], data[i, 3], data[i, 4] ],
'score' : data[i, 5],
'category_id': int(data[i, 6]),
}]
return ann
def getImgs(self):
return self.imgs
def annToRLE(self, ann):
"""
Convert annotation which can be polygons, uncompressed RLE to RLE.
:return: binary mask (numpy 2D array)
"""
t = self.imgs[ann['image_id']]
h, w = t['height'], t['width']
segm = ann['segmentation']
if type(segm) == list:
# polygon -- a single object might consist of multiple parts
# we merge all parts into one mask rle code
rles = maskUtils.frPyObjects(segm, h, w)
rle = maskUtils.merge(rles)
elif type(segm['counts']) == list:
# uncompressed RLE
rle = maskUtils.frPyObjects(segm, h, w)
else:
# rle
rle = ann['segmentation']
return rle
def annToMask(self, ann):
"""
Convert annotation which can be polygons, uncompressed RLE, or RLE to binary mask.
:return: binary mask (numpy 2D array)
"""
rle = self.annToRLE(ann)
m = maskUtils.decode(rle)
return m
__author__ = 'tsungyi'
import numpy as np
import datetime
import time
from collections import defaultdict
from . import mask as maskUtils
import copy
import ext
from .coco import mp_pool_wrapper, mp_pool_task
class COCOeval:
# Interface for evaluating detection on the Microsoft COCO dataset.
#
# The usage for CocoEval is as follows:
# cocoGt=..., cocoDt=... # load dataset and results
# E = CocoEval(cocoGt,cocoDt); # initialize CocoEval object
# E.params.recThrs = ...; # set parameters as desired
# E.evaluate(); # run per image evaluation
# E.accumulate(); # accumulate per image results
# E.summarize(); # display summary metrics of results
# For example usage see evalDemo.m and http://mscoco.org/.
#
# The evaluation parameters are as follows (defaults in brackets):
# imgIds - [all] N img ids to use for evaluation
# catIds - [all] K cat ids to use for evaluation
# iouThrs - [.5:.05:.95] T=10 IoU thresholds for evaluation
# recThrs - [0:.01:1] R=101 recall thresholds for evaluation
# areaRng - [...] A=4 object area ranges for evaluation
# maxDets - [1 10 100] M=3 thresholds on max detections per image
# iouType - ['segm'] set iouType to 'segm', 'bbox' or 'keypoints'
# iouType replaced the now DEPRECATED useSegm parameter.
# useCats - [1] if true use category labels for evaluation
# Note: if useCats=0 category labels are ignored as in proposal scoring.
# Note: multiple areaRngs [Ax2] and maxDets [Mx1] can be specified.
#
# evaluate(): evaluates detections on every image and every category and
# concats the results into the "evalImgs" with fields:
# dtIds - [1xD] id for each of the D detections (dt)
# gtIds - [1xG] id for each of the G ground truths (gt)
# dtMatches - [TxD] matching gt id at each IoU or 0
# gtMatches - [TxG] matching dt id at each IoU or 0
# dtScores - [1xD] confidence of each dt
# gtIgnore - [1xG] ignore flag for each gt
# dtIgnore - [TxD] ignore flag for each dt at each IoU
#
# accumulate(): accumulates the per-image, per-category evaluation
# results in "evalImgs" into the dictionary "eval" with fields:
# params - parameters used for evaluation
# date - date evaluation was performed
# counts - [T,R,K,A,M] parameter dimensions (see above)
# precision - [TxRxKxAxM] precision for every evaluation setting
# recall - [TxKxAxM] max recall for every evaluation setting
# Note: precision and recall==-1 for settings with no gt objects.
#
# See also coco, mask, pycocoDemo, pycocoEvalDemo
#
# Microsoft COCO Toolbox. version 2.0
# Data, paper, and tutorials available at: http://mscoco.org/
# Code written by Piotr Dollar and Tsung-Yi Lin, 2015.
# Licensed under the Simplified BSD License [see coco/license.txt]
def __init__(self, cocoGt=None, cocoDt=None, iouType='segm', use_ext=False, multi_procs=(1, None), num_threads=1):
'''
Initialize CocoEval using coco APIs for gt and dt
:param cocoGt: coco object with ground truth annotations
:param cocoDt: coco object with detection results
:return: None
'''
if not iouType:
print('iouType not specified. use default iouType segm')
self.cocoGt = cocoGt # ground truth COCO API
self.cocoDt = cocoDt # detections COCO API
self.params = {} # evaluation parameters
self.evalImgs = defaultdict(list) # per-image per-category evaluation results [KxAxI] elements
self.eval = {} # accumulated evaluation results
self._gts = defaultdict(list) # gt for evaluation
self._dts = defaultdict(list) # dt for evaluation
self.params = Params(iouType=iouType) # parameters
self._paramsEval = {} # parameters for evaluation
self.stats = [] # result summarization
self.ious = {} # ious between all gts and dts
self.use_ext = use_ext # use c++ extension
self.num_threads = num_threads # number of OpenMP threads
self.num_procs, self.proc_pid_map = multi_procs
if not self.use_ext:
if not cocoGt is None:
self.params.imgIds = sorted(cocoGt.getImgIds())
self.params.catIds = sorted(cocoGt.getCatIds())
def _prepare(self):
'''
Prepare ._gts and ._dts for evaluation based on params
:return: None
'''
def _toMask(anns, coco):
# modify ann['segmentation'] by reference
for ann in anns:
rle = coco.annToRLE(ann)
ann['segmentation'] = rle
p = self.params
if p.useCats:
gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds))
dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds))
else:
gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds))
dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds))
# convert ground truth to mask if iouType == 'segm'
if p.iouType == 'segm':
_toMask(gts, self.cocoGt)
_toMask(dts, self.cocoDt)
# set ignore flag
for gt in gts:
gt['ignore'] = gt['ignore'] if 'ignore' in gt else 0
gt['ignore'] = 'iscrowd' in gt and gt['iscrowd']
if p.iouType == 'keypoints':
gt['ignore'] = (gt['num_keypoints'] == 0) or gt['ignore']
self._gts = defaultdict(list) # gt for evaluation
self._dts = defaultdict(list) # dt for evaluation
for gt in gts:
self._gts[gt['image_id'], gt['category_id']].append(gt)
for dt in dts:
self._dts[dt['image_id'], dt['category_id']].append(dt)
self.evalImgs = defaultdict(list) # per-image per-category evaluation results
self.eval = {} # accumulated evaluation results
def evaluate(self):
'''
Run per image evaluation on given images and store results (a list of dict) in self.evalImgs
:return: None
'''
tic = time.time()
print('Running per image evaluation...')
p = self.params
# add backward compatibility if useSegm is specified in params
if not p.useSegm is None:
p.iouType = 'segm' if p.useSegm == 1 else 'bbox'
print('useSegm (deprecated) is not None. Running {} evaluation'.format(p.iouType))
print('Evaluate annotation type *{}*'.format(p.iouType))
if not self.use_ext:
p.imgIds = list(np.unique(p.imgIds))
if p.useCats:
p.catIds = list(np.unique(p.catIds))
p.maxDets = sorted(p.maxDets)
self.params=p
if self.use_ext:
p.areaRng=np.array(p.areaRng)
p.maxDets=np.array(p.maxDets,dtype=np.int32)
input_iter = (self.proc_pid_map, ext.cpp_evaluate,
(p.useCats,p.areaRng,p.iouThrs,p.maxDets,p.recThrs,p.iouType,self.num_threads))
outputs = mp_pool_wrapper(*input_iter)
# if self.num_procs >1, outputs are only results from one proc;
# please average coco_eval.stats after summarize() call
p.imgIds, p.catIds, self.eval = outputs
print('DONE (t={:0.2f}s).'.format(time.time()-tic))
return
self._prepare()
# loop through images, area range, max detection number
catIds = p.catIds if p.useCats else [-1]
if p.iouType == 'segm' or p.iouType == 'bbox':
computeIoU = self.computeIoU
elif p.iouType == 'keypoints':
computeIoU = self.computeOks
self.ious = {(imgId, catId): computeIoU(imgId, catId) \
for imgId in p.imgIds
for catId in catIds}
evaluateImg = self.evaluateImg
maxDet = p.maxDets[-1]
self.evalImgs = [evaluateImg(imgId, catId, areaRng, maxDet)
for catId in catIds
for areaRng in p.areaRng
for imgId in p.imgIds
]
self._paramsEval = copy.deepcopy(self.params)
toc = time.time()
print('DONE (t={:0.2f}s).'.format(toc-tic))
def computeIoU(self, imgId, catId):
p = self.params
if p.useCats:
gt = self._gts[imgId,catId]
dt = self._dts[imgId,catId]
else:
gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]]
dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]]
if len(gt) == 0 and len(dt) ==0:
return []
inds = np.argsort([-d['score'] for d in dt], kind='mergesort')
dt = [dt[i] for i in inds]
if len(dt) > p.maxDets[-1]:
dt=dt[0:p.maxDets[-1]]
if p.iouType == 'segm':
g = [g['segmentation'] for g in gt]
d = [d['segmentation'] for d in dt]
elif p.iouType == 'bbox':
g = [g['bbox'] for g in gt]
d = [d['bbox'] for d in dt]
else:
raise Exception('unknown iouType for iou computation')
# compute iou between each dt and gt region
iscrowd = [int(o['iscrowd']) for o in gt]
ious = maskUtils.iou(d,g,iscrowd)
return ious
def computeOks(self, imgId, catId):
p = self.params
# dimention here should be Nxm
gts = self._gts[imgId, catId]
dts = self._dts[imgId, catId]
inds = np.argsort([-d['score'] for d in dts], kind='mergesort')
dts = [dts[i] for i in inds]
if len(dts) > p.maxDets[-1]:
dts = dts[0:p.maxDets[-1]]
# if len(gts) == 0 and len(dts) == 0:
if len(gts) == 0 or len(dts) == 0:
return []
ious = np.zeros((len(dts), len(gts)))
sigmas = np.array([.26, .25, .25, .35, .35, .79, .79, .72, .72, .62,.62, 1.07, 1.07, .87, .87, .89, .89])/10.0
vars = (sigmas * 2)**2
k = len(sigmas)
# compute oks between each detection and ground truth object
for j, gt in enumerate(gts):
# create bounds for ignore regions(double the gt bbox)
g = np.array(gt['keypoints'])
xg = g[0::3]; yg = g[1::3]; vg = g[2::3]
k1 = np.count_nonzero(vg > 0)
bb = gt['bbox']
x0 = bb[0] - bb[2]; x1 = bb[0] + bb[2] * 2
y0 = bb[1] - bb[3]; y1 = bb[1] + bb[3] * 2
for i, dt in enumerate(dts):
d = np.array(dt['keypoints'])
xd = d[0::3]; yd = d[1::3]
if k1>0:
# measure the per-keypoint distance if keypoints visible
dx = xd - xg
dy = yd - yg
else:
# measure minimum distance to keypoints in (x0,y0) & (x1,y1)
z = np.zeros((k))
dx = np.max((z, x0-xd),axis=0)+np.max((z, xd-x1),axis=0)
dy = np.max((z, y0-yd),axis=0)+np.max((z, yd-y1),axis=0)
e = (dx**2 + dy**2) / vars / (gt['area']+np.spacing(1)) / 2
if k1 > 0:
e=e[vg > 0]
ious[i, j] = np.sum(np.exp(-e)) / e.shape[0]
return ious
def evaluateImg(self, imgId, catId, aRng, maxDet):
'''
perform evaluation for single category and image
:return: dict (single image results)
'''
p = self.params
if p.useCats:
gt = self._gts[imgId,catId]
dt = self._dts[imgId,catId]
else:
gt = [_ for cId in p.catIds for _ in self._gts[imgId,cId]]
dt = [_ for cId in p.catIds for _ in self._dts[imgId,cId]]
if len(gt) == 0 and len(dt) ==0:
return None
for g in gt:
if g['ignore'] or (g['area']<aRng[0] or g['area']>aRng[1]):
g['_ignore'] = 1
else:
g['_ignore'] = 0
# sort dt highest score first, sort gt ignore last
gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
gt = [gt[i] for i in gtind]
dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
dt = [dt[i] for i in dtind[0:maxDet]]
iscrowd = [int(o['iscrowd']) for o in gt]
# load computed ious
ious = self.ious[imgId, catId][:, gtind] if len(self.ious[imgId, catId]) > 0 else self.ious[imgId, catId]
T = len(p.iouThrs)
G = len(gt)
D = len(dt)
gtm = np.zeros((T,G))
dtm = np.zeros((T,D))
gtIg = np.array([g['_ignore'] for g in gt])
dtIg = np.zeros((T,D))
if not len(ious)==0:
for tind, t in enumerate(p.iouThrs):
for dind, d in enumerate(dt):
# information about best match so far (m=-1 -> unmatched)
iou = min([t,1-1e-10])
m = -1
for gind, g in enumerate(gt):
# if this gt already matched, and not a crowd, continue
if gtm[tind,gind]>0 and not iscrowd[gind]:
continue
# if dt matched to reg gt, and on ignore gt, stop
if m>-1 and gtIg[m]==0 and gtIg[gind]==1:
break
# continue to next gt unless better match made
if ious[dind,gind] < iou:
continue
# if match successful and best so far, store appropriately
iou=ious[dind,gind]
m=gind
# if match made store id of match for both dt and gt
if m ==-1:
continue
dtIg[tind,dind] = gtIg[m]
dtm[tind,dind] = gt[m]['id']
gtm[tind,m] = d['id']
# set unmatched detections outside of area range to ignore
a = np.array([d['area']<aRng[0] or d['area']>aRng[1] for d in dt]).reshape((1, len(dt)))
dtIg = np.logical_or(dtIg, np.logical_and(dtm==0, np.repeat(a,T,0)))
# store results for given image and category
return {
'image_id': imgId,
'category_id': catId,
'aRng': aRng,
'maxDet': maxDet,
'dtIds': [d['id'] for d in dt],
'gtIds': [g['id'] for g in gt],
'dtMatches': dtm,
'gtMatches': gtm,
'dtScores': [d['score'] for d in dt],
'gtIgnore': gtIg,
'dtIgnore': dtIg,
}
def accumulate(self, p = None):
'''
Accumulate per image evaluation results and store the result in self.eval
:param p: input params for evaluation
:return: None
'''
print('Accumulating evaluation results...')
tic = time.time()
#if not self.evalImgs:
# print('Please run evaluate() first')
# allows input customized parameters
if self.use_ext:
toc = time.time()
print('DONE (t={:0.2f}s).'.format( toc-tic))
return
if p is None:
p = self.params
p.catIds = p.catIds if p.useCats == 1 else [-1]
T = len(p.iouThrs)
R = len(p.recThrs)
K = len(p.catIds) if p.useCats else 1
A = len(p.areaRng)
M = len(p.maxDets)
precision = -np.ones((T,R,K,A,M)) # -1 for the precision of absent categories
recall = -np.ones((T,K,A,M))
scores = -np.ones((T,R,K,A,M))
# create dictionary for future indexing
_pe = self._paramsEval
catIds = _pe.catIds if _pe.useCats else [-1]
setK = set(catIds)
setA = set(map(tuple, _pe.areaRng))
setM = set(_pe.maxDets)
setI = set(_pe.imgIds)
# get inds to evaluate
k_list = [n for n, k in enumerate(p.catIds) if k in setK]
m_list = [m for n, m in enumerate(p.maxDets) if m in setM]
a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA]
i_list = [n for n, i in enumerate(p.imgIds) if i in setI]
I0 = len(_pe.imgIds)
A0 = len(_pe.areaRng)
# retrieve E at each category, area range, and max number of detections
for k, k0 in enumerate(k_list):
Nk = k0*A0*I0
for a, a0 in enumerate(a_list):
Na = a0*I0
for m, maxDet in enumerate(m_list):
E = [self.evalImgs[Nk + Na + i] for i in i_list]
E = [e for e in E if not e is None]
if len(E) == 0:
continue
dtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E])
# different sorting method generates slightly different results.
# mergesort is used to be consistent as Matlab implementation.
inds = np.argsort(-dtScores, kind='mergesort')
dtScoresSorted = dtScores[inds]
dtm = np.concatenate([e['dtMatches'][:,0:maxDet] for e in E], axis=1)[:,inds]
dtIg = np.concatenate([e['dtIgnore'][:,0:maxDet] for e in E], axis=1)[:,inds]
gtIg = np.concatenate([e['gtIgnore'] for e in E])
npig = np.count_nonzero(gtIg==0 )
if npig == 0:
continue
tps = np.logical_and( dtm, np.logical_not(dtIg) )
fps = np.logical_and(np.logical_not(dtm), np.logical_not(dtIg) )
tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float)
fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float)
for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):
tp = np.array(tp)
fp = np.array(fp)
nd = len(tp)
rc = tp / npig
pr = tp / (fp+tp+np.spacing(1))
q = np.zeros((R,))
ss = np.zeros((R,))
if nd:
recall[t,k,a,m] = rc[-1]
else:
recall[t,k,a,m] = 0
# numpy is slow without cython optimization for accessing elements
# use python array gets significant speed improvement
pr = pr.tolist(); q = q.tolist()
for i in range(nd-1, 0, -1):
if pr[i] > pr[i-1]:
pr[i-1] = pr[i]
inds = np.searchsorted(rc, p.recThrs, side='left')
try:
for ri, pi in enumerate(inds):
q[ri] = pr[pi]
ss[ri] = dtScoresSorted[pi]
except:
pass
precision[t,:,k,a,m] = np.array(q)
scores[t,:,k,a,m] = np.array(ss)
self.eval = {
'params': p,
'counts': [T, R, K, A, M],
'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'precision': precision,
'recall': recall,
'scores': scores,
}
toc = time.time()
print('DONE (t={:0.2f}s).'.format( toc-tic))
def summarize(self):
'''
Compute and display summary metrics for evaluation results.
Note this functin can *only* be applied on the default parameter setting
'''
def _summarize( ap=1, iouThr=None, areaRng='all', maxDets=100 ):
p = self.params
iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.5f}'
titleStr = 'Average Precision' if ap == 1 else 'Average Recall'
typeStr = '(AP)' if ap==1 else '(AR)'
iouStr = '{:0.2f}:{:0.2f}'.format(p.iouThrs[0], p.iouThrs[-1]) \
if iouThr is None else '{:0.2f}'.format(iouThr)
aind = [i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng]
mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets]
if ap == 1:
# dimension of precision: [TxRxKxAxM]
s = self.eval['precision']
# IoU
if iouThr is not None:
t = np.where(iouThr == p.iouThrs)[0]
s = s[:,:,:,t,:]
s = s[:,aind,mind,:,:]
else:
# dimension of recall: [TxKxAxM]
s = self.eval['recall']
if iouThr is not None:
t = np.where(iouThr == p.iouThrs)[0]
s = s[:,:,:,t]
s = s[:,aind,mind,:]
if len(s[s>-1])==0:
mean_s = -1
else:
mean_s = np.mean(s[s>-1])
print(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s))
return mean_s
def _summarizeDets():
stats = np.zeros((12,))
stats[0] = _summarize(1)
stats[1] = _summarize(1, iouThr=.5, maxDets=self.params.maxDets[2])
stats[2] = _summarize(1, iouThr=.75, maxDets=self.params.maxDets[2])
stats[3] = _summarize(1, areaRng='small', maxDets=self.params.maxDets[2])
stats[4] = _summarize(1, areaRng='medium', maxDets=self.params.maxDets[2])
stats[5] = _summarize(1, areaRng='large', maxDets=self.params.maxDets[2])
stats[6] = _summarize(0, maxDets=self.params.maxDets[0])
stats[7] = _summarize(0, maxDets=self.params.maxDets[1])
stats[8] = _summarize(0, maxDets=self.params.maxDets[2])
stats[9] = _summarize(0, areaRng='small', maxDets=self.params.maxDets[2])
stats[10] = _summarize(0, areaRng='medium', maxDets=self.params.maxDets[2])
stats[11] = _summarize(0, areaRng='large', maxDets=self.params.maxDets[2])
return stats
def _summarizeKps():
stats = np.zeros((10,))
stats[0] = _summarize(1, maxDets=20)
stats[1] = _summarize(1, maxDets=20, iouThr=.5)
stats[2] = _summarize(1, maxDets=20, iouThr=.75)
stats[3] = _summarize(1, maxDets=20, areaRng='medium')
stats[4] = _summarize(1, maxDets=20, areaRng='large')
stats[5] = _summarize(0, maxDets=20)
stats[6] = _summarize(0, maxDets=20, iouThr=.5)
stats[7] = _summarize(0, maxDets=20, iouThr=.75)
stats[8] = _summarize(0, maxDets=20, areaRng='medium')
stats[9] = _summarize(0, maxDets=20, areaRng='large')
return stats
if not self.eval:
raise Exception('Please run accumulate() first')
iouType = self.params.iouType
if iouType == 'segm' or iouType == 'bbox':
summarize = _summarizeDets
elif iouType == 'keypoints':
summarize = _summarizeKps
self.stats = summarize()
def __str__(self):
self.summarize()
class Params:
'''
Params for coco evaluation api
'''
def setDetParams(self):
self.imgIds = []
self.catIds = []
# np.arange causes trouble. the data point on arange is slightly larger than the true value
self.iouThrs = np.linspace(.5, 0.95, int(np.round((0.95 - .5) / .05) + 1), endpoint=True)
self.recThrs = np.linspace(.0, 1.00, int(np.round((1.00 - .0) / .01) + 1), endpoint=True)
self.maxDets = [1, 10, 100]
self.areaRng = [[0 ** 2, 1e5 ** 2], [0 ** 2, 32 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]]
self.areaRngLbl = ['all', 'small', 'medium', 'large']
self.useCats = 1
def setKpParams(self):
self.imgIds = []
self.catIds = []
# np.arange causes trouble. the data point on arange is slightly larger than the true value
self.iouThrs = np.linspace(.5, 0.95, int(np.round((0.95 - .5) / .05) + 1), endpoint=True)
self.recThrs = np.linspace(.0, 1.00, int(np.round((1.00 - .0) / .01) + 1), endpoint=True)
self.maxDets = [20]
self.areaRng = [[0 ** 2, 1e5 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]]
self.areaRngLbl = ['all', 'medium', 'large']
self.useCats = 1
def __init__(self, iouType='segm'):
if iouType == 'segm' or iouType == 'bbox':
self.setDetParams()
elif iouType == 'keypoints':
self.setKpParams()
else:
raise Exception('iouType not supported')
self.iouType = iouType
# useSegm is deprecated
self.useSegm = None
#include <omp.h>
#include <numeric>
#include <iostream>
#include <string>
#include <vector>
#include "numpy/arrayobject.h"
#include "simdjson.h"
constexpr char kId[] = "id";
constexpr char kImages[] = "images";
constexpr char kHeight[] = "height";
constexpr char kWidth[] = "width";
constexpr char kCats[] = "categories";
constexpr char kAnns[] = "annotations";
constexpr char kSegm[] = "segmentation";
constexpr char kImageId[] = "image_id";
constexpr char kCategoryId[] = "category_id";
constexpr char kArea[] = "area";
constexpr char kIsCrowd[] = "iscrowd";
constexpr char kBbox[] = "bbox";
constexpr char kCounts[] = "counts";
constexpr char kScore[] = "score";
constexpr char kSize[] = "size";
constexpr char kCaption[] = "caption";
struct image_struct {
int64_t id;
int h;
int w;
};
typedef struct {
unsigned long h;
unsigned long w;
unsigned long m;
unsigned int *cnts;
} RLE;
struct anns_struct {
int64_t image_id;
int64_t category_id;
int64_t id;
float area;
int iscrowd;
std::vector<float> bbox;
float score;
// segmentation
std::vector<std::vector<double>> segm_list;
std::vector<int> segm_size;
std::vector<int> segm_counts_list;
std::string segm_counts_str;
};
// dict type
struct detection_struct {
std::vector<int64_t> id;
std::vector<float> area;
std::vector<int> iscrowd;
std::vector<std::vector<float>> bbox;
std::vector<float> score;
std::vector<std::vector<int>> segm_size;
std::vector<int> ignore;
std::vector<std::string> segm_counts;
};
// create index results
std::vector<int64_t> imgids;
std::vector<int64_t> catids;
std::vector<image_struct> imgsgt;
std::vector<std::vector<double>> gtbboxes;
std::vector<std::vector<RLE>> gtsegm;
std::vector<detection_struct> gts;
std::vector<detection_struct> dts;
// internal computeiou results
std::vector<std::vector<double>> ious_map;
// global variables within each process
int num_procs = 0;
int proc_id = 0;
double *precision;
double *recall;
double *scores;
size_t len_precision = 0;
size_t len_recall = 0;
size_t len_scores = 0;
template <typename T, typename Comparator = std::greater<T> >
std::vector<size_t> sort_indices(const std::vector<T>& v, Comparator comparator = Comparator()) {
std::vector<size_t> indices(v.size());
std::iota(indices.begin(), indices.end(), 0);
std::sort(indices.begin(), indices.end(),
[&](size_t i1, size_t i2) { return comparator(v[i1], v[i2]); });
return indices;
}
template <typename T, typename Comparator = std::greater<T> >
std::vector<size_t> stable_sort_indices(const std::vector<T>& v, Comparator comparator = Comparator()) {
std::vector<size_t> indices(v.size());
std::iota(indices.begin(), indices.end(), 0);
std::stable_sort(indices.begin(), indices.end(),
[&](size_t i1, size_t i2) { return comparator(v[i1], v[i2]); });
return indices;
}
void accumulate(int T, int A, const int* maxDets, const double* recThrs,
double* precision, double* recall, double* scores,
const int K, const int I, const int R, const int M, const int k, const int a,
const std::vector<std::vector<int64_t>>& gtignore,
const std::vector<std::vector<double>>& dtignore,
const std::vector<std::vector<double>>& dtmatches,
const std::vector<std::vector<double>>& dtscores);
void compute_iou(std::string iouType, int maxDet, int useCats, int nthreads);
static PyObject* cpp_evaluate(PyObject* self, PyObject* args) {
// there must be at least 1 process to run this program
if (num_procs<1){
std::cout << "[cpp_evaluate] Error: num_procs must be >=1" << std::endl;
return NULL;
}
// read arguments
int useCats;
PyArrayObject *pareaRngs, *piouThrs_ptr, *pmaxDets, *precThrs;
const char* iouType_chars;
// evaluate() will use however many proesses create_index uses,
// and with the same (pid : data chuck id) mapping
int nthreads;
if (!PyArg_ParseTuple(args, "iO!O!O!O!si|", &useCats,
&PyArray_Type, &pareaRngs, &PyArray_Type, &piouThrs_ptr,
&PyArray_Type, &pmaxDets, &PyArray_Type, &precThrs,
&iouType_chars, &nthreads)) {
std::cout << "[cpp_evaluate] Error: can't parse arguments (must be int, numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray,str, int)" << std::endl;
return NULL;
}
std::string iouType(iouType_chars);
if (useCats<=0){
std::cout << "[cpp_evaluate] Error: useCats must be >0" << std::endl;
return NULL;
}
int areaRngs_dim1 = pareaRngs->dimensions[0];
int areaRngs_dim2 = pareaRngs->dimensions[1];
double **areaRngs = (double **)malloc((size_t) (areaRngs_dim1*sizeof(double)));
double *areaRngs_data = (double *)pareaRngs->data;
for (int i=0; i<areaRngs_dim1; i++)
areaRngs[i] = areaRngs_data + i * areaRngs_dim2;
double *iouThrs_ptr = (double *)piouThrs_ptr->data;
int *maxDets = (int *)pmaxDets->data;
double *recThrs = (double *)precThrs->data;
int T = piouThrs_ptr->dimensions[0];
int A = pareaRngs->dimensions[0];
int K = catids.size();
int R = precThrs->dimensions[0];
int M = pmaxDets->dimensions[0];
const npy_intp len1[] = {T*R*K*A*M};
const npy_intp len2[] = {T*K*A*M};
// if precision/recall/scores have been allocated, do not allocate again
// if they haven't, allocate on the heap and ensure returned PyObject retains value
if (len_precision != len1[0]) {
precision = (double *)malloc((size_t)(len1[0]*sizeof(double)));
len_precision = len1[0];
}
if (len_recall != len2[0]) {
recall = (double *)malloc((size_t)(len2[0]*sizeof(double)));
len_recall = len2[0];
}
if (len_scores != len1[0]) {
scores = (double *)malloc((size_t)(len1[0]*sizeof(double)));
len_scores = len1[0];
}
// initialize in first run or zero out in subsequent runs
for (npy_intp i=0; i<len1[0]; i++) {
precision[i]=0.0D;
scores[i]=0.0D;
}
for (npy_intp i=0; i<len2[0]; i++) {
recall[i]=0.0D;
}
// wrap data with PyObject wrapper
PyObject* pprecision = PyArray_SimpleNewFromData(1, len1, NPY_DOUBLE, precision);
PyObject* precall = PyArray_SimpleNewFromData(1, len2, NPY_DOUBLE, recall);
PyObject* pscores = PyArray_SimpleNewFromData(1, len1, NPY_DOUBLE, scores);
// if gts or dts doesn't exist, no need to evaluate, return zeros as precision/recall/scores
if (gts.size() > 0 && dts.size() > 0)
{
// compute ious
int maxDet = maxDets[M-1];
compute_iou(iouType, maxDet, useCats, nthreads);
// main loop
#pragma omp parallel for num_threads(nthreads) collapse(2) schedule(guided, 8)
for(npy_intp a = 0; a < pareaRngs->dimensions[0]; a++) {
for(size_t c = 0; c < catids.size(); c++) {
const double aRng0 = areaRngs[a][0];
const double aRng1 = areaRngs[a][1];
std::vector<std::vector<int64_t>> gtIgnore_list;
std::vector<std::vector<double>> dtIgnore_list;
std::vector<std::vector<double>> dtMatches_list;
std::vector<std::vector<double>> dtScores_list;
for(size_t i = 0; i < imgids.size(); i++) {
auto& gtsm = gts[c*imgids.size() + i];
auto& dtsm = dts[c*imgids.size() + i];
if((gtsm.id.size()==0) && (dtsm.id.size()==0))
continue;
// sizes
const int T = piouThrs_ptr->dimensions[0];
const int G = gtsm.id.size();
const int Do = dtsm.id.size();
const int D = std::min(Do, maxDet);
const int I = (G==0||D==0) ? 0 : D;
// arrays
std::vector<double> gtm(T*G, 0.0);
gtIgnore_list.push_back(std::vector<int64_t>(G));
dtIgnore_list.push_back(std::vector<double>(T*D, 0.0));
dtMatches_list.push_back(std::vector<double>(T*D, 0.0));
dtScores_list.push_back(std::vector<double>(D));
// pointers
auto& gtIg = gtIgnore_list.back();
auto& dtIg = dtIgnore_list.back();
auto& dtm = dtMatches_list.back();
auto& dtScores = dtScores_list.back();
auto ious = (ious_map[c*imgids.size() + i].size() == 0) ? nullptr : ious_map[c*imgids.size() + i].data();
// set ignores
for (int g = 0; g < G; g++) {
gtIg[g] = (gtsm.ignore[g] || gtsm.area[g]<aRng0 || gtsm.area[g]>aRng1) ? 1 : 0;
}
// get sorting indices
auto gtind = sort_indices(gtIg, std::less<double>());
auto dtind = sort_indices(dtsm.score);
if(I != 0) {
for (int t = 0; t < T; t++) {
double thresh = iouThrs_ptr[t];
for (int d = 0; d < D; d++) {
double iou = thresh < (1-1e-10) ? thresh : (1-1e-10);
int m = -1;
for (int g = 0; g < G; g++) {
// if this gt already matched, and not a crowd, continue
if((gtm[t * G + g]>0) && (gtsm.iscrowd[gtind[g]]==0))
continue;
// if dt matched to reg gt, and on ignore gt, stop
if((m>-1) && (gtIg[gtind[m]]==0) && (gtIg[gtind[g]]==1))
break;
// continue to next gt unless better match made
double val = ious[d + I * gtind[g]];
if(val < iou)
continue;
// if match successful and best so far, store appropriately
iou=val;
m=g;
}
// if match made store id of match for both dt and gt
if(m ==-1)
continue;
dtIg[t * D + d] = gtIg[gtind[m]];
dtm[t * D + d] = gtsm.id[gtind[m]];
gtm[t * G + m] = dtsm.id[dtind[d]];
}
}
}
// set unmatched detections outside of area range to ignore
for (int d = 0; d < D; d++) {
float val = dtsm.area[dtind[d]];
double x3 = (val<aRng0 || val>aRng1);
for (int t = 0; t < T; t++) {
double x1 = dtIg[t * D + d];
double x2 = dtm[t * D + d];
double res = x1 || ((x2==0) && x3);
dtIg[t * D + d] = res;
}
// store results for given image and category
dtScores[d] = dtsm.score[dtind[d]];
}
}
// accumulate
accumulate((int)(piouThrs_ptr->dimensions[0]), (int)(pareaRngs->dimensions[0]), maxDets, recThrs,
precision, recall, scores,
catids.size(), imgids.size(),
(int)(precThrs->dimensions[0]), (int)(pmaxDets->dimensions[0]), c, a,
gtIgnore_list, dtIgnore_list, dtMatches_list, dtScores_list);
}
}
// clear arrays
ious_map.clear();
//gts.clear();
dts.clear();
free(areaRngs);
}
PyObject* l = Py_BuildValue("[iiiii]", T, R, K, A, M);
npy_intp dims1[] = {K,A,M,T,R};
npy_intp dims2[] = {K,A,M,T};
PyArray_Dims pdims1 = {dims1, 5};
PyArray_Dims pdims2 = {dims2, 4};
PyArray_Resize((PyArrayObject*)pprecision, &pdims1, 0, NPY_CORDER);
PyArray_Resize((PyArrayObject*)precall, &pdims2, 0, NPY_CORDER);
PyArray_Resize((PyArrayObject*)pscores, &pdims1, 0, NPY_CORDER);
const npy_intp dims3[] = {imgids.size()};
const npy_intp dims4[] = {catids.size()};
PyObject* imgidsret = PyArray_SimpleNewFromData(1, dims3, NPY_INT64, imgids.data());
PyObject* catidsret = PyArray_SimpleNewFromData(1, dims4, NPY_INT64, catids.data());
PyObject* pReturn = Py_BuildValue("(O,O,{s:O,s:O,s:O,s:O})",
imgidsret,catidsret,
"counts",l,"precision",pprecision,"recall",precall,"scores",pscores);
return pReturn;
}
template <typename T>
std::vector<T> assemble_array(const std::vector<std::vector<T>>& list, size_t nrows, size_t maxDet, const std::vector<size_t>& indices) {
std::vector<T> q;
// Need to get N_rows from an entry in order to compute output size
// copy first maxDet entries from each entry -> array
for (size_t e = 0; e < list.size(); ++e) {
auto arr = list[e];
size_t cols = arr.size() / nrows;
size_t ncols = std::min(maxDet, cols);
for (size_t j = 0; j < ncols; ++j) {
for (size_t i = 0; i < nrows; ++i) {
q.push_back(arr[i * cols + j]);
}
}
}
// now we've done that, copy the relevant entries based on indices
std::vector<T> res(indices.size() * nrows);
for (size_t i = 0; i < nrows; ++i) {
for (size_t j = 0; j < indices.size(); ++j) {
res[i * indices.size() + j] = q[indices[j] * nrows + i];
}
}
return res;
}
void accumulate(int T, int A, const int* maxDets, const double* recThrs,
double* precision, double* recall, double* scores,
const int K, const int I, const int R, const int M, const int k, const int a,
const std::vector<std::vector<int64_t>>& gtignore,
const std::vector<std::vector<double>>& dtignore,
const std::vector<std::vector<double>>& dtmatches,
const std::vector<std::vector<double>>& dtscores)
{
if (dtscores.size() == 0) return;
int npig = 0;
for (auto& v: gtignore) {
npig += count(v.begin(), v.end(), 0);
}
if (npig == 0) return;
double eps = 2.220446049250313e-16; //numeric_limits<double>::epsilon();
for (int m = 0; m < M; ++m) {
// Concatenate first maxDet scores in each evalImg entry, -ve and sort w/indices
std::vector<double> dtScores;
for (size_t e = 0; e < dtscores.size(); ++e) {
auto score = dtscores[e];
for (size_t j = 0; j < std::min(score.size(), static_cast<size_t>(maxDets[m])); ++j) {
dtScores.push_back(score[j]);
}
}
// get sorted indices of scores
auto indices = stable_sort_indices(dtScores);
std::vector<double> dtScoresSorted(dtScores.size());
for (size_t j = 0; j < indices.size(); ++j) {
dtScoresSorted[j] = dtScores[indices[j]];
}
auto dtm = assemble_array<double>(dtmatches, T, maxDets[m], indices);
auto dtIg = assemble_array<double>(dtignore, T, maxDets[m], indices);
size_t nrows = indices.size() ? dtm.size()/indices.size() : 0;
size_t nd = indices.size();
std::vector<double> tp_sum(nd * nrows);
std::vector<double> fp_sum(nd * nrows);
std::vector<double> rc(nd);
std::vector<double> pr(nd);
for (size_t t = 0; t < nrows; ++t) {
size_t tsum = 0, fsum = 0;
for (size_t j = 0; j < nd; ++j) {
size_t index = t * nd + j;
tsum += (dtm[index]) && (!dtIg[index]);
fsum += (!dtm[index]) && (!dtIg[index]);
tp_sum[index] = tsum;
fp_sum[index] = fsum;
}
for (size_t j = 0; j < nd; ++j) {
size_t index = t * nd + j;
rc[j] = tp_sum[index] / npig;
pr[j] = tp_sum[index] / (fp_sum[index]+tp_sum[index]+eps);
}
recall[k*A*M*T + a*M*T + m*T + t] = nd ? rc[nd-1] : 0;
for (size_t i = nd-1; i > 0; --i) {
if (pr[i] > pr[i-1]) {
pr[i-1] = pr[i];
}
}
size_t inds;
for (int i = 0; i < R; i++) {
auto it = lower_bound(rc.begin(), rc.end(), recThrs[i]);
inds = it - rc.begin();
size_t index = k*A*M*T*R + a*M*T*R + m*T*R + t*R + i;
if (inds < nd) {
precision[index] = pr[inds];
scores[index] = dtScoresSorted[inds];
}
} // i loop
} // t loop
} // m loop
}
void bbIou(const double *dt, const double *gt, const int m, const int n, const int *iscrowd, double *o)
{
for(int g=0; g<n; g++ ) {
const double* G = gt+g*4;
const double ga = G[2]*G[3];
const double gw = G[2]+G[0];
const double gh = G[3]+G[1];
const double crowd = iscrowd!=NULL && iscrowd[g];
for(int d=0; d<m; d++ ) {
const double* D = dt+d*4;
const double da = D[2]*D[3];
double w = fmin(D[2]+D[0],gw)-fmax(D[0],G[0]);
double h = fmin(D[3]+D[1],gh)-fmax(D[1],G[1]);
h = h<=0 ? 0 : h;
w = w<=0 ? 0 : w;
double i = w*h;
double u = crowd ? da : da+ga-i;
o[g*m+d] = i/u;
}
}
}
void rleInit( RLE *R, unsigned long h, unsigned long w, unsigned long m, unsigned int *cnts ) {
R->h=h;
R->w=w;
R->m=m;
R->cnts = (m==0) ? 0: (unsigned int*)malloc(sizeof(unsigned int)*m);
if(cnts) {
for(unsigned long j=0; j<m; j++){
R->cnts[j]=cnts[j];
}
}
}
void rleFree( RLE *R ) {
free(R->cnts);
R->cnts=0;
}
void rleFrString(RLE *R, char *s, unsigned long h, unsigned long w ) {
unsigned long m=0, p=0, k;
long x;
int more;
unsigned int *cnts;
while( s[m] ){
m++;
}
cnts = (unsigned int*)malloc(sizeof(unsigned int)*m);
m = 0;
while( s[p] ) {
x=0; k=0; more=1;
while( more ) {
char c=s[p]-48; x |= (c & 0x1f) << 5*k;
more = c & 0x20; p++; k++;
if(!more && (c & 0x10)) x |= -1 << 5*k;
}
if(m>2) {
x += static_cast<long>(cnts[m-2]);
}
cnts[m++] = static_cast<unsigned int>(x);
}
rleInit(R, h, w, m, cnts);
free(cnts);
}
unsigned int umin( unsigned int a, unsigned int b ) { return (a<b) ? a : b; }
unsigned int umax( unsigned int a, unsigned int b ) { return (a>b) ? a : b; }
void rleArea( const RLE *R, unsigned long n, unsigned int *a ) {
for(unsigned long i=0; i<n; i++ ) {
a[i]=0;
for(unsigned long j=1; j<R[i].m; j+=2 ) {
a[i]+=R[i].cnts[j];
}
}
}
void rleToBbox( const RLE *R, double* bb, unsigned long n ) {
for(unsigned long i=0; i<n; i++ ) {
unsigned int h, w, xp=0, cc=0, t;
double x, y, xs, ys, xe=0.0D, ye=0.0D;
unsigned long j, m;
h = static_cast<unsigned int>(R[i].h);
w = static_cast<unsigned int>(R[i].w);
m = (static_cast<unsigned long>(R[i].m/2))*2;
xs=w;
ys=h;
if(m==0) {
bb[4*i+0]=bb[4*i+1]=bb[4*i+2]=bb[4*i+3]=0;
continue;
}
for( j=0; j<m; j++ ) {
cc += R[i].cnts[j];
t = cc-j%2;
y = t%h;
x = (t-y)/h;
if(j%2==0) {
xp = x;
} else if(xp<x) {
ys = 0;
ye = h-1;
}
xs = umin(xs, x);
xe = umax(xe, x);
ys = umin(ys, y);
ye = umax(ye, y);
}
bb[4*i+0] = xs;
bb[4*i+1] = ys;
bb[4*i+2] = xe-xs+1;
bb[4*i+3] = ye-ys+1;
}
}
void rleIou(const RLE *dt, const RLE *gt, const int m, const int n, const int *iscrowd, double *o )
{
double *db=(double*)malloc(sizeof(double)*m*4);
double *gb=(double*)malloc(sizeof(double)*n*4);
rleToBbox(dt, db, m);
rleToBbox(gt, gb, n);
bbIou(db, gb, m, n, iscrowd, o);
free(db);
free(gb);
for(int g=0; g<n; g++ ) {
for(int d=0; d<m; d++ ) {
if(o[g*m+d]>0) {
int crowd = iscrowd!=NULL && iscrowd[g];
if(dt[d].h!=gt[g].h || dt[d].w!=gt[g].w) {
o[g*m+d]=-1;
continue;
}
unsigned long ka, kb, a, b; uint c, ca, cb, ct, i, u; int va, vb;
ca=dt[d].cnts[0]; ka=dt[d].m; va=vb=0;
cb=gt[g].cnts[0]; kb=gt[g].m; a=b=1; i=u=0; ct=1;
while(ct > 0) {
c=umin(ca,cb);
if(va||vb) {
u+=c;
if(va&&vb) {
i+=c;
}
}
ct = 0;
ca -=c;
if(!ca && a<ka) {
ca=dt[d].cnts[a++]; va=!va;
}
ct+=ca;
cb -=c;
if(!cb && b<kb) {
cb=gt[g].cnts[b++];
vb=!vb;
}
ct += cb;
}
if(i==0) {
u=1;
} else if(crowd) {
rleArea(dt+d, 1, &u);
}
o[g*m+d] = static_cast<double>(i)/static_cast<double>(u);
}
}
}
}
void compute_iou(std::string iouType, int maxDet, int useCats, int nthreads) {
assert(iouType=="bbox"||iouType=="segm");
assert(useCats > 0);
if (ious_map.size()>0){
if (proc_id == 0) std::cout << "[compute_iou] IoUs already exist. Clearing vectors..." << std::endl;
ious_map.clear();
}
ious_map.resize(imgids.size() * catids.size());
// compute iou
#pragma omp parallel for num_threads(nthreads) schedule(guided, 8) collapse(2)
for(size_t c = 0; c < catids.size(); c++) {
for(size_t i = 0; i < imgids.size(); i++) {
const auto gtsm = gts[c*imgids.size() + i];
const auto dtsm = dts[c*imgids.size() + i];
const auto G = gtsm.id.size();
const auto D = dtsm.id.size();
const int m = std::min(D, static_cast<size_t>(maxDet));
const int n = G;
if(m==0 || n==0) {
continue;
}
ious_map[c*imgids.size() + i] = std::vector<double>(m*n);
auto inds = sort_indices(dtsm.score);
if (iouType == "bbox") {
std::vector<double> d;
for (auto i = 0; i < m; i++) {
auto arr = dtsm.bbox[inds[i]];
for (size_t j = 0; j < arr.size(); j++) {
d.push_back(static_cast<double>(arr[j]));
}
}
bbIou(d.data(), gtbboxes[c*imgids.size()+i].data(), m, n, gtsm.iscrowd.data(), ious_map[c*imgids.size() + i].data());
} else {
std::vector<RLE> d(m);
for (auto i = 0; i < m; i++) {
auto size = dtsm.segm_size[i];
auto str = dtsm.segm_counts[inds[i]];
char *val = const_cast<char*>(str.c_str());
rleFrString(&d[i],val,size[0],size[1]);
val = NULL;
}
rleIou(d.data(), gtsegm[c*imgids.size()+i].data(), m, n, gtsm.iscrowd.data(), ious_map[c*imgids.size() + i].data());
for (size_t i = 0; i < d.size(); i++) {free(d[i].cnts);}
}
}
}
}
std::string rleToString( const RLE *R ) {
/* Similar to LEB128 but using 6 bits/char and ascii chars 48-111. */
unsigned long i, m=R->m, p=0; long x; int more;
char *s=(char*)malloc(sizeof(char)*m*6);
for( i=0; i<m; i++ ) {
x=(long) R->cnts[i]; if(i>2) x-=(long) R->cnts[i-2]; more=1;
while( more ) {
char c=x & 0x1f; x >>= 5; more=(c & 0x10) ? x!=-1 : x!=0;
if (more)
c |= 0x20;
c+=48;
s[p++]=c;
}
}
s[p]=0;
std::string str = std::string(s);
free(s);
return str;
}
std::string frUncompressedRLE(std::vector<int> cnts, std::vector<int> size, int h, int w) {
unsigned int *data = (unsigned int*) malloc(cnts.size() * sizeof(unsigned int));
for(size_t i = 0; i < cnts.size(); i++) {
data[i] = static_cast<unsigned int>(cnts[i]);
}
RLE R;// = RLE(size[0],size[1],cnts.size(),data);
R.h = size[0];
R.w = size[1];
R.m = cnts.size();
R.cnts = data;
std::string str = rleToString(&R);
free(data);
return str;
}
int uintCompare(const void *a, const void *b) {
unsigned int c=*((unsigned int*)a), d=*((unsigned int*)b); return c>d?1:c<d?-1:0;
}
void rleFrPoly(RLE *R, const double *xy, int k, int h, int w ) {
/* upsample and get discrete points densely along entire boundary */
int j, m=0;
double scale=5;
unsigned int *a, *b;
int *x = (int*)malloc(sizeof(int)*(k+1));
int *y = (int*)malloc(sizeof(int)*(k+1));
for(j=0; j<k; j++)
x[j] = static_cast<int>(scale*xy[j*2+0]+.5);
x[k] = x[0];
for(j=0; j<k; j++)
y[j] = static_cast<int>(scale*xy[j*2+1]+.5);
y[k] = y[0];
for(j=0; j<k; j++)
m += umax(abs(x[j]-x[j+1]),abs(y[j]-y[j+1]))+1;
int *u=(int*)malloc(sizeof(int)*m);
int *v=(int*)malloc(sizeof(int)*m);
m = 0;
for( j=0; j<k; j++ ) {
int xs=x[j], xe=x[j+1], ys=y[j], ye=y[j+1], dx, dy, t, d;
int flip; double s; dx=abs(xe-xs); dy=abs(ys-ye);
flip = (dx>=dy && xs>xe) || (dx<dy && ys>ye);
if(flip) { t=xs; xs=xe; xe=t; t=ys; ys=ye; ye=t; }
s = dx>=dy ? static_cast<double>(ye-ys)/dx : static_cast<double>(xe-xs)/dy;
if(dx>=dy) for( d=0; d<=dx; d++ ) {
t=flip?dx-d:d; u[m]=t+xs; v[m]=static_cast<int>(ys+s*t+.5); m++;
} else for( d=0; d<=dy; d++ ) {
t=flip?dy-d:d; v[m]=t+ys; u[m]=static_cast<int>(xs+s*t+.5); m++;
}
}
/* get points along y-boundary and downsample */
free(x); free(y); k=m; m=0; double xd, yd;
x=(int*)malloc(sizeof(int)*k); y=(int*)malloc(sizeof(int)*k);
for( j=1; j<k; j++ ) if(u[j]!=u[j-1]) {
xd=static_cast<double>(u[j]<u[j-1]?u[j]:u[j]-1); xd=(xd+.5)/scale-.5;
if( floor(xd)!=xd || xd<0 || xd>w-1 ) continue;
yd=static_cast<double>(v[j]<v[j-1]?v[j]:v[j-1]); yd=(yd+.5)/scale-.5;
if(yd<0) yd=0; else if(yd>h) yd=h; yd=ceil(yd);
x[m]=static_cast<int>(xd); y[m]=static_cast<int>(yd); m++;
}
/* compute rle encoding given y-boundary points */
k=m; a=(unsigned int*)malloc(sizeof(unsigned int)*(k+1));
for( j=0; j<k; j++ ) a[j]=static_cast<unsigned int>(x[j]*static_cast<int>(h)+y[j]);
a[k++]=static_cast<unsigned int>(h*w); free(u); free(v); free(x); free(y);
qsort(a,k,sizeof(unsigned int),uintCompare); unsigned int p=0;
for( j=0; j<k; j++ ) { unsigned int t=a[j]; a[j]-=p; p=t; }
b=(unsigned int*)malloc(sizeof(unsigned int)*k); j=m=0; b[m++]=a[j++];
while(j<k) if(a[j]>0) b[m++]=a[j++]; else {
j++; if(j<k) b[m-1]+=a[j++]; }
rleInit(R,h,w,m,b); free(a); free(b);
}
void rleMerge( const RLE *R, RLE *M, unsigned long n, int intersect ) {
unsigned int *cnts, c, ca, cb, cc, ct; int v, va, vb, vp;
unsigned long i, a, b, h=R[0].h, w=R[0].w, m=R[0].m; RLE A, B;
if(n==0) { rleInit(M,0,0,0,0); return; }
if(n==1) { rleInit(M,h,w,m,R[0].cnts); return; }
cnts = (unsigned int*)malloc(sizeof(unsigned int)*(h*w+1));
for( a=0; a<m; a++ ) cnts[a]=R[0].cnts[a];
for( i=1; i<n; i++ ) {
B=R[i]; if(B.h!=h||B.w!=w) { h=w=m=0; break; }
rleInit(&A,h,w,m,cnts); ca=A.cnts[0]; cb=B.cnts[0];
v=va=vb=0; m=0; a=b=1; cc=0; ct=1;
while( ct>0 ) {
c=umin(ca,cb); cc+=c; ct=0;
ca-=c; if(!ca && a<A.m) { ca=A.cnts[a++]; va=!va; } ct+=ca;
cb-=c; if(!cb && b<B.m) { cb=B.cnts[b++]; vb=!vb; } ct+=cb;
vp=v; if(intersect) v=va&&vb; else v=va||vb;
if( v!=vp||ct==0 ) { cnts[m++]=cc; cc=0; }
}
rleFree(&A);
}
rleInit(M,h,w,m,cnts); free(cnts);
}
void rlesInit( RLE **R, unsigned long n ) {
unsigned long i; *R = (RLE*) malloc(sizeof(RLE)*n);
for(i=0; i<n; i++) rleInit((*R)+i,0,0,0,0);
}
std::string frPoly(std::vector<std::vector<double>> poly, int h, int w) {
size_t n = poly.size();
RLE *Rs;
rlesInit(&Rs,n);
for (size_t i = 0; i < n; i++) {
double* p = (double*)malloc(sizeof(double)*poly[i].size());
for (size_t j = 0; j < poly[i].size(); j++) {
p[j] = static_cast<double>(poly[i][j]);
}
rleFrPoly(&Rs[i],p,int(poly[i].size()/2),h,w);
free(p);
}
RLE R;
int intersect = 0;
rleMerge(Rs, &R, n, intersect);
std::string str = rleToString(&R);
for (size_t i = 0; i < n; i++) {free(Rs[i].cnts);}
free(Rs);
return str;
}
unsigned int area(std::vector<int>& size, std::string& counts) {
// _frString
RLE *Rs;
rlesInit(&Rs,1);
char *str = const_cast<char*>(counts.c_str());
rleFrString(&Rs[0],str,size[0],size[1]);
str = NULL;
unsigned int a;
rleArea(Rs, 1, &a);
for (size_t i = 0; i < 1; i++) {free(Rs[i].cnts);}
free(Rs);
return a;
}
std::vector<float> toBbox(std::vector<int>& size, std::string& counts) {
// _frString
RLE *Rs;
rlesInit(&Rs,1);
char *str = const_cast<char*>(counts.c_str());
rleFrString(&Rs[0],str,size[0],size[1]);
str = NULL;
std::vector<double> bb(4*1);
rleToBbox(Rs, bb.data(), 1);
std::vector<float> bbf(bb.size());
for (size_t i = 0; i < bb.size(); i++) {
bbf[i] = static_cast<float>(bb[i]);
}
for (size_t i = 0; i < 1; i++) {free(Rs[i].cnts);}
free(Rs);
return bbf;
}
void annToRLE(anns_struct& ann, std::vector<std::vector<int>> &size, std::vector<std::string> &counts, int h, int w) {
auto is_segm_list = ann.segm_list.size()>0;
auto is_cnts_list = is_segm_list ? 0 : ann.segm_counts_list.size()>0;
if (is_segm_list) {
std::vector<int> segm_size{h,w};
auto cnts = ann.segm_list;
auto segm_counts = frPoly(cnts, h, w);
size.push_back(segm_size);
counts.push_back(segm_counts);
} else if (is_cnts_list) {
auto segm_size = ann.segm_size;
auto cnts = ann.segm_counts_list;
auto segm_counts = frUncompressedRLE(cnts, segm_size, h, w);
size.push_back(segm_size);
counts.push_back(segm_counts);
} else {
auto segm_size = ann.segm_size;
auto segm_counts = ann.segm_counts_str;
size.push_back(segm_size);
counts.push_back(segm_counts);
}
}
static PyObject* cpp_create_index(PyObject* self, PyObject* args)
{
/* this function takes a JSON file and reads it into gts
* the JSON file should have keys, 'images', 'categories' and 'annotations'
* the 'images' field should have subfields, 'id', 'height', and 'width'
* the 'categories' field should have subfield, 'id'
* the 'annotations' field should have the following structure:
* [
* {'image_id': int/int64, 'category_id': int/int64,
* 'segmentation': xxx,
* 'bbox': [float, float, float, float], 'score': float/double},
* { ... }
* ]
* the 'segmentation' filed could take one of the following forms:
* 'segmentation': [[int, ...], ...]
* 'segmentation': {'size': [int, int], 'counts': [int, ...]}
* 'segmentation': {'size': [int, int], 'counts': std::string}
* */
const char *annotation_file;
int nthreads;
if (!PyArg_ParseTuple(args, "siii|", &annotation_file, &num_procs, &proc_id, &nthreads)) {
std::cout << "[cpp_create_index] Error: can't parse the argument (must be std::string)" << std::endl;
return NULL;
}
// there must be at least 1 process to run this program
if (num_procs<1){
std::cout << "[cpp_create_index] Error: num_procs must be >=1" << std::endl;
return NULL;
}
if (imgsgt.size()>0 || imgids.size()>0 || catids.size()>0) {
if (proc_id == 0) std::cout << "[cpp_create_index] Ground truth annotations already exist. Clearing vectors..." << std::endl;
imgsgt.clear();
imgids.clear();
catids.clear();
gtbboxes.clear();
gtsegm.clear();
gts.clear();
}
simdjson::dom::parser parser;
simdjson::dom::element dataset = parser.load(annotation_file);
simdjson::dom::array imgs = dataset[kImages];
for (simdjson::dom::object image: imgs) {
image_struct img;
img.id = image[kId];
img.h = static_cast<int>(image[kHeight].get_int64());
img.w = static_cast<int>(image[kWidth].get_int64());
imgsgt.push_back(img);
imgids.push_back(img.id);
}
simdjson::dom::array cats = dataset[kCats];
std::vector<int64_t> catids_tmp;
for (simdjson::dom::object cat: cats) {
catids_tmp.push_back(cat[kId]);
}
// only keep the categories that this process will work on
int catids_size = catids_tmp.size();
int chunk_size = (catids_size + num_procs -1)/num_procs;
// multi-process evaluation situation:
// (1) distribute categories across processes by chunk
int begin = proc_id * chunk_size;
int end = (proc_id +1) * chunk_size ;
if (end >= catids_size ) end = catids_size ;
catids = std::vector<int64_t>(catids_tmp.begin()+begin, catids_tmp.begin()+end);
// (2) distribute categories across processes round robin
//for (int64_t i=0; i<chunk_size; i++)
//{
// if ((catids_tmp.begin()+i*num_procs+proc_id) != catids_tmp.end())
// catids.push_back(*(catids_tmp.begin()+i*num_procs+proc_id));
// else
// {
// catids.push_back(*catids_tmp.end());
// break;
// }
//}
simdjson::dom::array anns = dataset[kAnns];
int64_t catid, category_id;
// (3) distribute categories across processes based on gt cat-ann distribution
// i.e. total anns per proc are balanced
// Note that dt cat-ann may have a different distribution,
// and this distribution may have inadvertent effect on the load balance of load_res/eval across processes, and their runtime
// Also note that when run in parallel, this distribution method requires a weighted average
// of the final coco_eval.stats from all the processes, with the weight being
// (num of categories on that process/total num of categories).
//int64_t catids_tmp_size = catids_tmp.size();
//std::vector<int64_t> cat_num_anns(catids_tmp_size, 0);
//std::vector<int64_t> sum_anns_per_proc(num_procs, 0);
//for (simdjson::dom::object annotation : anns) {
// category_id = annotation[kCategoryId].get_int64();
// catid = distance(catids_tmp.begin(),find(catids_tmp.begin(), catids_tmp.end(), category_id));
// cat_num_anns[catid]++;
//}
//auto cat_num_anns_inds = sort_indices(cat_num_anns, std::greater<int64_t>());
//int argmin;
//for (int64_t i=0; i<catids_tmp_size; i++){
// argmin = distance(sum_anns_per_proc.begin(), std::min_element(sum_anns_per_proc.begin(),sum_anns_per_proc.end()));
// if (argmin == proc_id) {
// catids.push_back(catids_tmp[cat_num_anns_inds[i]]);
// }
// sum_anns_per_proc[argmin] += cat_num_anns[cat_num_anns_inds[i]];
//}
gts.resize(catids.size()*imgids.size());
simdjson::dom::element test;
auto error = anns.at(0)[kSegm].get(test);
bool issegm=false;
if (!error)
issegm=true;
int64_t image_id, imgid;
int64_t id;
float area;
int iscrowd;
for (simdjson::dom::object annotation : anns) {
category_id = annotation[kCategoryId].get_int64();
if (find(catids.begin(), catids.end(), category_id) == catids.end())
continue;
image_id = annotation[kImageId].get_int64();
id = annotation[kId].get_int64();
area = static_cast<float>(annotation[kArea].get_double());
iscrowd = static_cast<int>(annotation[kIsCrowd].get_int64());
std::vector<float> bbox;
for (double bb : annotation[kBbox]) {
bbox.push_back(bb);
}
anns_struct ann;
if (issegm) {
simdjson::dom::element ann_segm = annotation[kSegm];
bool is_segm_list = ann_segm.is_array();
bool is_cnts_list = is_segm_list ? 0 : ann_segm[kCounts].is_array();
if (is_segm_list) {
for (simdjson::dom::array seg1 : ann_segm) {
std::vector<double> seg_item_l2;
for (double seg2 : seg1) {
seg_item_l2.push_back(seg2);
}
ann.segm_list.push_back(seg_item_l2);
}
} else if (is_cnts_list) {
for (int64_t count : ann_segm[kCounts])
ann.segm_counts_list.push_back(static_cast<int>(count));
for (int64_t size : ann_segm[kSize])
ann.segm_size.push_back(static_cast<int>(size));
} else {
for (int64_t size : ann_segm[kSize])
ann.segm_size.push_back(static_cast<int>(size));
ann.segm_counts_str = ann_segm[kCounts];
}
}
imgid = distance(imgids.begin(),find(imgids.begin(), imgids.end(), image_id));
catid = distance(catids.begin(),find(catids.begin(), catids.end(), category_id));
detection_struct* tmp = &gts[catid * imgids.size() + imgid ];
tmp->area.push_back(area);
tmp->iscrowd.push_back(iscrowd);
tmp->bbox.push_back(bbox);
tmp->ignore.push_back(iscrowd!=0);
tmp->id.push_back(id);
int h = imgsgt[imgid].h;
int w = imgsgt[imgid].w;
annToRLE(ann, tmp->segm_size, tmp->segm_counts, h, w);
}
auto num_cats = catids.size();
auto num_imgs = imgids.size();
gtbboxes.resize(num_cats * num_imgs);
gtsegm.resize(num_cats * num_imgs);
#pragma omp parallel for schedule(guided, 8) num_threads(nthreads) collapse(2)
for(size_t c = 0; c < num_cats; c++) {
for(size_t i = 0; i < num_imgs; i++) {
const auto gtsm = gts[c * imgids.size() + i];
const auto G = gtsm.id.size();
if(G==0)
continue;
gtsegm[c*num_imgs+i].resize(G);
for (size_t g = 0; g < G; g++) {
if (gtsm.segm_size[g].size()>0) {
auto size = gtsm.segm_size[g];
auto str = gtsm.segm_counts[g];
char *val = const_cast<char*>(str.c_str());
rleFrString(&(gtsegm[c*num_imgs+i][g]), val, size[0], size[1]);
val = NULL;
}
for (size_t j = 0; j < gtsm.bbox[g].size(); j++)
gtbboxes[c*num_imgs+i].push_back(static_cast<double>(gtsm.bbox[g][j]));
}
}
}
Py_RETURN_NONE;
}
static PyObject* cpp_load_res_numpy(PyObject* self, PyObject* args)
{
/* this function takes an numpy.ndarray of (rows x 7) and reads it into dts
* the 7 columns are [image_id, category_id, bbox[0], bbox[1], bbox[2], bbox[3], score]
* the elements need to be in dtype=numpy.float32
* */
// there must be at least 1 process to run this program
if (num_procs<1){
std::cout << "[cpp_load_res_numpy] Error: num_procs must be >=1" << std::endl;
return NULL;
}
PyArrayObject *anns;
// the function will use however many proesses create_index uses,
// and with the same (pid : data chuck id) mapping
int nthreads;
if (!PyArg_ParseTuple(args, "O!i|", &PyArray_Type, &anns, &nthreads)){
std::cout << "[cpp_load_res_numpy] Error: can't parse the argument (must be numpy.ndarray)" << std::endl;
return NULL;
}
int ndim = anns->nd;
int64_t dim1 = anns->dimensions[0];
int dim2 = anns->dimensions[1];
if (ndim != 2 || dim2 != 7){
std::cout << "[cpp_load_res_numpy] Error: Input array must be 2-d numpy array with 7 columns" << std::endl;
return NULL;
}
if (dts.size()>0) {
if (proc_id == 0) std::cout << "[cpp_load_res_numpy] Detection annotations already exist. Clearing vectors..." << std::endl;
dts.clear();
}
dts.resize(catids.size()*imgids.size());
float *anns_data = (float *)anns->data;
#pragma omp parallel for num_threads(nthreads)
for (int64_t i = 0; i < dim1; i++) {
int64_t image_id = static_cast<int64_t>(anns_data[i*dim2]);
int64_t category_id = static_cast<int64_t>(anns_data[i*dim2+1]);
if (find(catids.begin(), catids.end(), category_id) == catids.end())
continue;
std::vector<float> bbox;
for (int d=0; d<4; d++)
bbox.push_back(static_cast<float>(anns_data[i*dim2+d+2]));
float score = static_cast<float>(anns_data[i*dim2+6]);
float area = bbox[2]*bbox[3];
int64_t id = i+1;
int iscrowd = 0;
int64_t imgid = distance(imgids.begin(), find(imgids.begin(), imgids.end(), image_id));
int64_t catid = distance(catids.begin(), find(catids.begin(), catids.end(), category_id));
detection_struct* tmp = &dts[catid * imgids.size() + imgid];
tmp->area.push_back(area);
tmp->iscrowd.push_back(iscrowd);
tmp->bbox.push_back(bbox);
tmp->score.push_back(score);
tmp->id.push_back(id);
}
Py_RETURN_NONE;
}
static PyObject* cpp_load_res_json(PyObject* self, PyObject* args)
{
/* this function takes a JSON file and reads it into dts
* the JSON file should have the following structure:
* [
* {'image_id': int/int64, 'category_id': int/int64,
* 'segmentation': {'size': [int, int], 'counts': std::string},
* 'bbox': [float, float, float, float], 'score': float/double},
* { ... }
* ]
* the 'bbox' field is optional; the function will create it if not found
* */
// there must be at least 1 process to run this program
if (num_procs<1){
std::cout << "[cpp_load_res_json] Error: num_procs must be >=1" << std::endl;
return NULL;
}
const char *annotation_file;
// this function will use however many proesses create_index uses,
// and with the same (pid : data chuck id) mapping
int nthreads;
if (!PyArg_ParseTuple(args, "si|", &annotation_file, &nthreads)){
std::cout << "[cpp_load_res_json] Error: can't parse the argument (must be a .json file)" << std::endl;
return NULL;
}
if (dts.size()>0) {
if (proc_id == 0) std::cout << "[cpp_load_res_json] Detection annotations already exist. Clearing vectors..." << std::endl;
dts.clear();
}
dts.resize(catids.size()*imgids.size());
simdjson::dom::parser parser;
simdjson::dom::element anns= parser.load(annotation_file);
bool iscaption = true;
simdjson::dom::element test_caption;
auto error_caption = anns.at(0)[kCaption].get(test_caption);
if (error_caption) iscaption = false;
bool isbbox=true;
bool isbbox_exist=true;
simdjson::dom::array test_bbox;
auto error_bbox = anns.at(0)[kBbox].get(test_bbox);
if (error_bbox){
isbbox_exist=false;
isbbox=false;
}
else{
isbbox = isbbox_exist && (test_bbox.size() > 0);
}
bool issegm=true;
simdjson::dom::element test_segm;
auto error_segm = anns.at(0)[kSegm].get(test_segm);
if (error_segm) issegm=false;
assert(!iscaption && (isbbox||issegm));
int64_t image_id;
int64_t category_id;
int64_t id=0;
float ann_area;
int iscrowd=0;
float score;
if (isbbox){
for (simdjson::dom::object annotation : anns) {
image_id = annotation[kImageId].get_int64();
category_id = annotation[kCategoryId].get_int64();
if (find(catids.begin(), catids.end(), category_id) == catids.end())
continue;
simdjson::dom::array ann_bbox= annotation[kBbox];
std::vector<float> bbox;
for (int d=0; d<4; d++)
bbox.push_back(static_cast<float>(ann_bbox.at(d).get_double()));
score = static_cast<float>(annotation[kScore].get_double());
ann_area = bbox[2]*bbox[3];
id++;
int64_t imgid = distance(imgids.begin(), find(imgids.begin(), imgids.end(), image_id));
int64_t catid = distance(catids.begin(), find(catids.begin(), catids.end(), category_id));
detection_struct* tmp = &dts[catid * imgids.size() + imgid];
tmp->area.push_back(ann_area);
tmp->iscrowd.push_back(iscrowd);
tmp->bbox.push_back(bbox);
tmp->score.push_back(score);
tmp->id.push_back(id);
}
}
else{
for (simdjson::dom::object annotation : anns) {
image_id = annotation[kImageId].get_int64();
category_id = annotation[kCategoryId].get_int64();
if (find(catids.begin(), catids.end(), category_id) == catids.end())
continue;
anns_struct ann;
simdjson::dom::object ann_segm = annotation[kSegm];
for (int64_t size : ann_segm[kSize])
ann.segm_size.push_back(static_cast<int>(size));
ann.segm_counts_str = ann_segm[kCounts];
ann_area = area(ann.segm_size,ann.segm_counts_str);
// we never use bbox in segm type in cpp ext
//if (!isbbox_exist)
// ann.bbox = toBbox(ann.segm_size,ann.segm_counts_str);
id++;
score = static_cast<float>(annotation[kScore].get_double());
int64_t imgid = distance(imgids.begin(), find(imgids.begin(), imgids.end(), image_id));
int64_t catid = distance(catids.begin(), find(catids.begin(), catids.end(), category_id));
detection_struct* tmp = &dts[catid * imgids.size() + imgid];
tmp->area.push_back(ann_area);
tmp->iscrowd.push_back(iscrowd);
//tmp->bbox.push_back(ann.bbox);
tmp->score.push_back(score);
tmp->id.push_back(id);
auto h = imgsgt[imgid].h;
auto w = imgsgt[imgid].w;
annToRLE(ann,tmp->segm_size,tmp->segm_counts,h,w);
}
}
Py_RETURN_NONE;
}
static PyMethodDef ext_Methods[] = {
{"cpp_create_index", (PyCFunction)cpp_create_index, METH_VARARGS,
"cpp_create_index(annotation_file:str, num_procs:int, proc_id:int, nthreads:int)\n"
"Read .json file and create ground truth map.\n"
"Parameters: annotation_file: str \n"
" num_procs: int\n"
" proc_id: int\n"
" nthreads: int\n"
"Returns: None \n"},
{"cpp_load_res_numpy", (PyCFunction)cpp_load_res_numpy, METH_VARARGS,
"cpp_load_res_numpy(results:numpy.ndarray, nthreads:int)\n"
"Load results and create detection map.\n"
"Parameters: results: numpy.ndarray\n"
" results has (number of detections in all images) rows and 7 columns,\n"
" and the 7 columns are [image_id, category_id, bbox[4], score];\n"
" results has dtype=numpy.float32.\n"
" nthreads:int\n"
"Returns: None \n"},
{"cpp_load_res_json", (PyCFunction)cpp_load_res_json, METH_VARARGS,
"cpp_load_res_json(results:str, nthreads:int)\n"
"Load results and create detection map.\n"
"Parameters: results: str\n"
" results is a .json file with the following structure:\n"
" [\n"
" {'image_id': int/int64, 'category_id': int/int64,\n"
" 'segmentation': {'size': [int, int], 'counts': std::string},\n"
" 'bbox': [float, float, float, float], 'score': float/double},\n"
" { ... }\n"
" ]\n"
" either 'bbox' or 'segmentation' needs to exist \n"
" nthreads:int\n"
"Returns: None \n"},
{"cpp_evaluate", (PyCFunction)cpp_evaluate, METH_VARARGS,
"cpp_evaluate(useCats:int, areaRng:numpy.ndarray, iouThrs:numpy.ndarray, "
"maxDets:numpy.ndarray, recThrs:numpy.ndarray, iouType:str, nthreads:int)\n "
"Evaulate results (including the accumulation step).\n "
"Parameters: useCats: int\n"
" areaRng: 2-d numpy.ndarray (dtype=double)\n"
" iouThrs: 1-d numpy.ndarray (dtype=double)\n"
" maxDets: 1-d numpy.ndarray (dtype=int)\n"
" recThrs: 1-d numpy.ndarray (dtype=double)\n"
" iouType: str\n"
" nthreads: int\n"
"Returns: imgids: list\n"
" catids: list\n"
" eval: dict (keys=['counts','precision','recall','scores'])\n"},
{NULL, NULL, 0, NULL}
};
static char ext_doc[] = "COCO and COCOEval Extensions.";
static struct PyModuleDef ext_module = {
PyModuleDef_HEAD_INIT,
"ext",
ext_doc,
-1,
ext_Methods
};
PyMODINIT_FUNC
PyInit_ext(void)
{
import_array();
return PyModule_Create(&ext_module);
}
__author__ = 'tsungyi'
import pycocotools._mask as _mask
# Interface for manipulating masks stored in RLE format.
#
# RLE is a simple yet efficient format for storing binary masks. RLE
# first divides a vector (or vectorized image) into a series of piecewise
# constant regions and then for each piece simply stores the length of
# that piece. For example, given M=[0 0 1 1 1 0 1] the RLE counts would
# be [2 3 1 1], or for M=[1 1 1 1 1 1 0] the counts would be [0 6 1]
# (note that the odd counts are always the numbers of zeros). Instead of
# storing the counts directly, additional compression is achieved with a
# variable bitrate representation based on a common scheme called LEB128.
#
# Compression is greatest given large piecewise constant regions.
# Specifically, the size of the RLE is proportional to the number of
# *boundaries* in M (or for an image the number of boundaries in the y
# direction). Assuming fairly simple shapes, the RLE representation is
# O(sqrt(n)) where n is number of pixels in the object. Hence space usage
# is substantially lower, especially for large simple objects (large n).
#
# Many common operations on masks can be computed directly using the RLE
# (without need for decoding). This includes computations such as area,
# union, intersection, etc. All of these operations are linear in the
# size of the RLE, in other words they are O(sqrt(n)) where n is the area
# of the object. Computing these operations on the original mask is O(n).
# Thus, using the RLE can result in substantial computational savings.
#
# The following API functions are defined:
# encode - Encode binary masks using RLE.
# decode - Decode binary masks encoded via RLE.
# merge - Compute union or intersection of encoded masks.
# iou - Compute intersection over union between masks.
# area - Compute area of encoded masks.
# toBbox - Get bounding boxes surrounding encoded masks.
# frPyObjects - Convert polygon, bbox, and uncompressed RLE to encoded RLE mask.
#
# Usage:
# Rs = encode( masks )
# masks = decode( Rs )
# R = merge( Rs, intersect=false )
# o = iou( dt, gt, iscrowd )
# a = area( Rs )
# bbs = toBbox( Rs )
# Rs = frPyObjects( [pyObjects], h, w )
#
# In the API the following formats are used:
# Rs - [dict] Run-length encoding of binary masks
# R - dict Run-length encoding of binary mask
# masks - [hxwxn] Binary mask(s) (must have type np.ndarray(dtype=uint8) in column-major order)
# iscrowd - [nx1] list of np.ndarray. 1 indicates corresponding gt image has crowd region to ignore
# bbs - [nx4] Bounding box(es) stored as [x y w h]
# poly - Polygon stored as [[x1 y1 x2 y2...],[x1 y1 ...],...] (2D list)
# dt,gt - May be either bounding boxes or encoded masks
# Both poly and bbs are 0-indexed (bbox=[0 0 1 1] encloses first pixel).
#
# Finally, a note about the intersection over union (iou) computation.
# The standard iou of a ground truth (gt) and detected (dt) object is
# iou(gt,dt) = area(intersect(gt,dt)) / area(union(gt,dt))
# For "crowd" regions, we use a modified criteria. If a gt object is
# marked as "iscrowd", we allow a dt to match any subregion of the gt.
# Choosing gt' in the crowd gt that best matches the dt can be done using
# gt'=intersect(dt,gt). Since by definition union(gt',dt)=dt, computing
# iou(gt,dt,iscrowd) = iou(gt',dt) = area(intersect(gt,dt)) / area(dt)
# For crowd gt regions we use this modified criteria above for the iou.
#
# To compile run "python setup.py build_ext --inplace"
# Please do not contact us for help with compiling.
#
# Microsoft COCO Toolbox. version 2.0
# Data, paper, and tutorials available at: http://mscoco.org/
# Code written by Piotr Dollar and Tsung-Yi Lin, 2015.
# Licensed under the Simplified BSD License [see coco/license.txt]
iou = _mask.iou
merge = _mask.merge
frPyObjects = _mask.frPyObjects
def encode(bimask, paste_args={}):
if len(bimask.shape) == 3:
return _mask.encode(bimask, **paste_args)
elif len(bimask.shape) == 2:
h, w = bimask.shape
return _mask.encode(bimask.reshape((h, w, 1), order='F'), **paste_args)[0]
def decode(rleObjs):
if type(rleObjs) == list:
return _mask.decode(rleObjs)
else:
return _mask.decode([rleObjs])[:,:,0]
def area(rleObjs):
if type(rleObjs) == list:
return _mask.area(rleObjs)
else:
return _mask.area([rleObjs])[0]
def toBbox(rleObjs):
if type(rleObjs) == list:
return _mask.toBbox(rleObjs)
else:
return _mask.toBbox([rleObjs])[0]
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
from setuptools import setup, Extension
import numpy as np
# To compile and install locally run "python setup.py build_ext --inplace"
# To install library to Python site-packages run "python setup.py build_ext install"
ext_modules = [
Extension(
'pycocotools._mask',
sources=['../common/maskApi.c', 'pycocotools/_mask.pyx'],
include_dirs = [np.get_include(), '../common'],
extra_compile_args=['-Wno-cpp', '-Wno-unused-function', '-std=c99'],
),
Extension(
'ext',
sources=['pycocotools/ext.cpp', 'pycocotools/simdjson.cpp'],
extra_compile_args=['-O3', '-Wall', '-shared', '-fopenmp', '-std=c++17', '-fPIC', '-I/usr/local/lib/python3.7/site-packages/numpy/core/include', '-I/pycocotools/'],
extra_link_args=['-lgomp', '-L/usr/local/lib/python3.7/site-packages/numpy/core/lib', '-lnpymath'],
)
]
setup(
name='pycocotools',
packages=['pycocotools'],
package_dir = {'pycocotools': 'pycocotools'},
install_requires=[
'setuptools>=18.0',
'cython>=0.27.3',
'matplotlib>=2.1.0',
],
version='2.0+nv0.7.0',
ext_modules= ext_modules
)
COCO API - http://cocodataset.org/
COCO is a large image dataset designed for object detection, segmentation, person keypoints detection, stuff segmentation, and caption generation. This package provides Matlab, Python, and Lua APIs that assists in loading, parsing, and visualizing the annotations in COCO. Please visit http://cocodataset.org/ for more information on COCO, including for the data, paper, and tutorials. The exact format of the annotations is also described on the COCO website. The Matlab and Python APIs are complete, the Lua API provides only basic functionality.
In addition to this API, please download both the COCO images and annotations in order to run the demos and use the API. Both are available on the project website.
-Please download, unzip, and place the images in: coco/images/
-Please download and place the annotations in: coco/annotations/
For substantially more details on the API please see http://cocodataset.org/#download.
After downloading the images and annotations, run the Matlab, Python, or Lua demos for example usage.
To install:
-For Matlab, add coco/MatlabApi to the Matlab path (OSX/Linux binaries provided)
-For Python, run "make" under coco/PythonAPI
-For Lua, run “luarocks make LuaAPI/rocks/coco-scm-1.rockspec” under coco/
import torch
import pycocotools.mask as mask_util
import numpy as np
def create_image(width, height, left_fill, right_fill, top_fill, bottom_fill, interior_fill):
image = torch.zeros(size=[height, width], device='cpu', dtype=torch.uint8)
if left_fill:
image[:,0:1] = 1
if right_fill:
image[:,width-1:] = 1
if top_fill:
image[0:1,:] = 1
if bottom_fill:
image[height-1:,:] = 1
if interior_fill:
image[1:height-1,1:width-1] = 1
return image
def pad_image(image, left_pad, right_pad, top_pad, bottom_pad):
if left_pad == 0 and right_pad == 0 and top_pad == 0 and bottom_pad == 0:
return image
else:
height, width = image.shape
padded_image = torch.zeros(size=[height+top_pad+bottom_pad, width+left_pad+right_pad], device=image.device, dtype=image.dtype)
padded_image[top_pad:top_pad+height, left_pad:left_pad+width].copy_(image)
return padded_image
def bools(arg):
return "T" if arg else "F"
def check_image(n, width, height, left_fill, right_fill, top_fill, bottom_fill, interior_fill, left_pad, right_pad, top_pad, bottom_pad, silent_on_success=True, print_raw_rles=False):
image = create_image(width, height, left_fill, right_fill, top_fill, bottom_fill, interior_fill)
padded_image = pad_image(image, left_pad, right_pad, top_pad, bottom_pad)
padded_height, padded_width = padded_image.shape
c = np.array(image[ :, :, np.newaxis], order="F")
pad_c = np.array(padded_image[ :, :, np.newaxis], order="F")
rle = mask_util.encode(c, paste_args=dict(oy=top_pad, ox=left_pad, oh=padded_height, ow=padded_width))
rle_ref = mask_util.encode(pad_c)
success = True if rle == rle_ref else False
if not silent_on_success or not success:
if print_raw_rles:
print()
print(rle)
print(rle_ref)
print("%s :: Test %d :: w,h,lf,rf,tf,bf,if = %d,%d,%s,%s,%s,%s,%s ;; lp,rp,tp,bp = %d,%d,%d,%d" % (
"Success" if success else "FAIL", n,
width, height, bools(left_fill), bools(right_fill), bools(top_fill), bools(bottom_fill), bools(interior_fill),
left_pad, right_pad, top_pad, bottom_pad))
return success
def call_check_image(width, height, n, silent_on_success, print_raw_rles):
args = []
args.append( n )
args.append( width )
args.append( height )
args.append( True if n & 256 else False )
args.append( True if n & 128 else False )
args.append( True if n & 64 else False )
args.append( True if n & 32 else False )
args.append( True if n & 16 else False )
args.append( 1 if n & 8 else 0 )
args.append( 1 if n & 4 else 0 )
args.append( 1 if n & 2 else 0 )
args.append( 1 if n & 1 else 0 )
return check_image(*args, silent_on_success=silent_on_success, print_raw_rles=print_raw_rles)
def main():
num_success, num_total = 0,0
for n in range(512):
num_total += 1
if call_check_image(100, 100, n, True, False):
num_success += 1
print("%d / %d Tests succeeded" % (num_success,num_total))
#call_check_image(100, 100, 96, False, True)
if __name__ == "__main__":
main()
// https://github.com/vivkin/gason - pulled January 10, 2016
#include "gason.h"
#include <stdlib.h>
#define JSON_ZONE_SIZE 4096
#define JSON_STACK_SIZE 32
const char *jsonStrError(int err) {
switch (err) {
#define XX(no, str) \
case JSON_##no: \
return str;
JSON_ERRNO_MAP(XX)
#undef XX
default:
return "unknown";
}
}
void *JsonAllocator::allocate(size_t size) {
size = (size + 7) & ~7;
if (head && head->used + size <= JSON_ZONE_SIZE) {
char *p = (char *)head + head->used;
head->used += size;
return p;
}
size_t allocSize = sizeof(Zone) + size;
Zone *zone = (Zone *)malloc(allocSize <= JSON_ZONE_SIZE ? JSON_ZONE_SIZE : allocSize);
if (zone == nullptr)
return nullptr;
zone->used = allocSize;
if (allocSize <= JSON_ZONE_SIZE || head == nullptr) {
zone->next = head;
head = zone;
} else {
zone->next = head->next;
head->next = zone;
}
return (char *)zone + sizeof(Zone);
}
void JsonAllocator::deallocate() {
while (head) {
Zone *next = head->next;
free(head);
head = next;
}
}
static inline bool isspace(char c) {
return c == ' ' || (c >= '\t' && c <= '\r');
}
static inline bool isdelim(char c) {
return c == ',' || c == ':' || c == ']' || c == '}' || isspace(c) || !c;
}
static inline bool isdigit(char c) {
return c >= '0' && c <= '9';
}
static inline bool isxdigit(char c) {
return (c >= '0' && c <= '9') || ((c & ~' ') >= 'A' && (c & ~' ') <= 'F');
}
static inline int char2int(char c) {
if (c <= '9')
return c - '0';
return (c & ~' ') - 'A' + 10;
}
static double string2double(char *s, char **endptr) {
char ch = *s;
if (ch == '-')
++s;
double result = 0;
while (isdigit(*s))
result = (result * 10) + (*s++ - '0');
if (*s == '.') {
++s;
double fraction = 1;
while (isdigit(*s)) {
fraction *= 0.1;
result += (*s++ - '0') * fraction;
}
}
if (*s == 'e' || *s == 'E') {
++s;
double base = 10;
if (*s == '+')
++s;
else if (*s == '-') {
++s;
base = 0.1;
}
unsigned int exponent = 0;
while (isdigit(*s))
exponent = (exponent * 10) + (*s++ - '0');
double power = 1;
for (; exponent; exponent >>= 1, base *= base)
if (exponent & 1)
power *= base;
result *= power;
}
*endptr = s;
return ch == '-' ? -result : result;
}
static inline JsonNode *insertAfter(JsonNode *tail, JsonNode *node) {
if (!tail)
return node->next = node;
node->next = tail->next;
tail->next = node;
return node;
}
static inline JsonValue listToValue(JsonTag tag, JsonNode *tail) {
if (tail) {
auto head = tail->next;
tail->next = nullptr;
return JsonValue(tag, head);
}
return JsonValue(tag, nullptr);
}
int jsonParse(char *s, char **endptr, JsonValue *value, JsonAllocator &allocator) {
JsonNode *tails[JSON_STACK_SIZE];
JsonTag tags[JSON_STACK_SIZE];
char *keys[JSON_STACK_SIZE];
JsonValue o;
int pos = -1;
bool separator = true;
JsonNode *node;
*endptr = s;
while (*s) {
while (isspace(*s)) {
++s;
if (!*s) break;
}
*endptr = s++;
switch (**endptr) {
case '-':
if (!isdigit(*s) && *s != '.') {
*endptr = s;
return JSON_BAD_NUMBER;
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
o = JsonValue(string2double(*endptr, &s));
if (!isdelim(*s)) {
*endptr = s;
return JSON_BAD_NUMBER;
}
break;
case '"':
o = JsonValue(JSON_STRING, s);
for (char *it = s; *s; ++it, ++s) {
int c = *it = *s;
if (c == '\\') {
c = *++s;
switch (c) {
case '\\':
case '"':
case '/':
*it = c;
break;
case 'b':
*it = '\b';
break;
case 'f':
*it = '\f';
break;
case 'n':
*it = '\n';
break;
case 'r':
*it = '\r';
break;
case 't':
*it = '\t';
break;
case 'u':
c = 0;
for (int i = 0; i < 4; ++i) {
if (isxdigit(*++s)) {
c = c * 16 + char2int(*s);
} else {
*endptr = s;
return JSON_BAD_STRING;
}
}
if (c < 0x80) {
*it = c;
} else if (c < 0x800) {
*it++ = 0xC0 | (c >> 6);
*it = 0x80 | (c & 0x3F);
} else {
*it++ = 0xE0 | (c >> 12);
*it++ = 0x80 | ((c >> 6) & 0x3F);
*it = 0x80 | (c & 0x3F);
}
break;
default:
*endptr = s;
return JSON_BAD_STRING;
}
} else if ((unsigned int)c < ' ' || c == '\x7F') {
*endptr = s;
return JSON_BAD_STRING;
} else if (c == '"') {
*it = 0;
++s;
break;
}
}
if (!isdelim(*s)) {
*endptr = s;
return JSON_BAD_STRING;
}
break;
case 't':
if (!(s[0] == 'r' && s[1] == 'u' && s[2] == 'e' && isdelim(s[3])))
return JSON_BAD_IDENTIFIER;
o = JsonValue(JSON_TRUE);
s += 3;
break;
case 'f':
if (!(s[0] == 'a' && s[1] == 'l' && s[2] == 's' && s[3] == 'e' && isdelim(s[4])))
return JSON_BAD_IDENTIFIER;
o = JsonValue(JSON_FALSE);
s += 4;
break;
case 'n':
if (!(s[0] == 'u' && s[1] == 'l' && s[2] == 'l' && isdelim(s[3])))
return JSON_BAD_IDENTIFIER;
o = JsonValue(JSON_NULL);
s += 3;
break;
case ']':
if (pos == -1)
return JSON_STACK_UNDERFLOW;
if (tags[pos] != JSON_ARRAY)
return JSON_MISMATCH_BRACKET;
o = listToValue(JSON_ARRAY, tails[pos--]);
break;
case '}':
if (pos == -1)
return JSON_STACK_UNDERFLOW;
if (tags[pos] != JSON_OBJECT)
return JSON_MISMATCH_BRACKET;
if (keys[pos] != nullptr)
return JSON_UNEXPECTED_CHARACTER;
o = listToValue(JSON_OBJECT, tails[pos--]);
break;
case '[':
if (++pos == JSON_STACK_SIZE)
return JSON_STACK_OVERFLOW;
tails[pos] = nullptr;
tags[pos] = JSON_ARRAY;
keys[pos] = nullptr;
separator = true;
continue;
case '{':
if (++pos == JSON_STACK_SIZE)
return JSON_STACK_OVERFLOW;
tails[pos] = nullptr;
tags[pos] = JSON_OBJECT;
keys[pos] = nullptr;
separator = true;
continue;
case ':':
if (separator || keys[pos] == nullptr)
return JSON_UNEXPECTED_CHARACTER;
separator = true;
continue;
case ',':
if (separator || keys[pos] != nullptr)
return JSON_UNEXPECTED_CHARACTER;
separator = true;
continue;
case '\0':
continue;
default:
return JSON_UNEXPECTED_CHARACTER;
}
separator = false;
if (pos == -1) {
*endptr = s;
*value = o;
return JSON_OK;
}
if (tags[pos] == JSON_OBJECT) {
if (!keys[pos]) {
if (o.getTag() != JSON_STRING)
return JSON_UNQUOTED_KEY;
keys[pos] = o.toString();
continue;
}
if ((node = (JsonNode *) allocator.allocate(sizeof(JsonNode))) == nullptr)
return JSON_ALLOCATION_FAILURE;
tails[pos] = insertAfter(tails[pos], node);
tails[pos]->key = keys[pos];
keys[pos] = nullptr;
} else {
if ((node = (JsonNode *) allocator.allocate(sizeof(JsonNode) - sizeof(char *))) == nullptr)
return JSON_ALLOCATION_FAILURE;
tails[pos] = insertAfter(tails[pos], node);
}
tails[pos]->value = o;
}
return JSON_BREAKING_BAD;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment