#include #include #include #include #include #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 bbox; float score; // segmentation std::vector> segm_list; std::vector segm_size; std::vector segm_counts_list; std::string segm_counts_str; }; // dict type struct detection_struct { std::vector id; std::vector area; std::vector iscrowd; std::vector> bbox; std::vector score; std::vector> segm_size; std::vector ignore; std::vector segm_counts; }; // create index results std::vector imgids; std::vector catids; std::vector imgsgt; std::vector> gtbboxes; std::vector> gtsegm; std::vector gts; std::vector dts; // internal computeiou results std::vector> 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 > std::vector sort_indices(const std::vector& v, Comparator comparator = Comparator()) { std::vector 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 > std::vector stable_sort_indices(const std::vector& v, Comparator comparator = Comparator()) { std::vector 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>& gtignore, const std::vector>& dtignore, const std::vector>& dtmatches, const std::vector>& 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; idata; 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 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> gtIgnore_list; std::vector> dtIgnore_list; std::vector> dtMatches_list; std::vector> 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 gtm(T*G, 0.0); gtIgnore_list.push_back(std::vector(G)); dtIgnore_list.push_back(std::vector(T*D, 0.0)); dtMatches_list.push_back(std::vector(T*D, 0.0)); dtScores_list.push_back(std::vector(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]aRng1) ? 1 : 0; } // get sorting indices auto gtind = sort_indices(gtIg, std::less()); 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 = (valaRng1); 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 std::vector assemble_array(const std::vector>& list, size_t nrows, size_t maxDet, const std::vector& indices) { std::vector 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 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>& gtignore, const std::vector>& dtignore, const std::vector>& dtmatches, const std::vector>& 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::epsilon(); for (int m = 0; m < M; ++m) { // Concatenate first maxDet scores in each evalImg entry, -ve and sort w/indices std::vector 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(maxDets[m])); ++j) { dtScores.push_back(score[j]); } } // get sorted indices of scores auto indices = stable_sort_indices(dtScores); std::vector dtScoresSorted(dtScores.size()); for (size_t j = 0; j < indices.size(); ++j) { dtScoresSorted[j] = dtScores[indices[j]]; } auto dtm = assemble_array(dtmatches, T, maxDets[m], indices); auto dtIg = assemble_array(dtignore, T, maxDets[m], indices); size_t nrows = indices.size() ? dtm.size()/indices.size() : 0; size_t nd = indices.size(); std::vector tp_sum(nd * nrows); std::vector fp_sum(nd * nrows); std::vector rc(nd); std::vector 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; gh=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; jcnts[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(cnts[m-2]); } cnts[m++] = static_cast(x); } rleInit(R, h, w, m, cnts); free(cnts); } unsigned int umin( unsigned int a, unsigned int b ) { return (ab) ? a : b; } void rleArea( const RLE *R, unsigned long n, unsigned int *a ) { for(unsigned long i=0; i(R[i].h); w = static_cast(R[i].w); m = (static_cast(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; j0) { 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(i)/static_cast(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(maxDet)); const int n = G; if(m==0 || n==0) { continue; } ious_map[c*imgids.size() + i] = std::vector(m*n); auto inds = sort_indices(dtsm.score); if (iouType == "bbox") { std::vector 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(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 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(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; icnts[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 cnts, std::vector 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(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(scale*xy[j*2+0]+.5); x[k] = x[0]; for(j=0; j(scale*xy[j*2+1]+.5); y[k] = y[0]; for(j=0; j=dy && xs>xe) || (dxye); if(flip) { t=xs; xs=xe; xe=t; t=ys; ys=ye; ye=t; } s = dx>=dy ? static_cast(ye-ys)/dx : static_cast(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(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(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(u[j]w-1 ) continue; yd=static_cast(v[j]h) yd=h; yd=ceil(yd); x[m]=static_cast(xd); y[m]=static_cast(yd); m++; } /* compute rle encoding given y-boundary points */ k=m; a=(unsigned int*)malloc(sizeof(unsigned int)*(k+1)); for( j=0; j(x[j]*static_cast(h)+y[j]); a[k++]=static_cast(h*w); free(u); free(v); free(x); free(y); qsort(a,k,sizeof(unsigned int),uintCompare); unsigned int p=0; for( j=0; j0) b[m++]=a[j++]; else { j++; if(j0 ) { c=umin(ca,cb); cc+=c; ct=0; ca-=c; if(!ca && a> 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(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& size, std::string& counts) { // _frString RLE *Rs; rlesInit(&Rs,1); char *str = const_cast(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 toBbox(std::vector& size, std::string& counts) { // _frString RLE *Rs; rlesInit(&Rs,1); char *str = const_cast(counts.c_str()); rleFrString(&Rs[0],str,size[0],size[1]); str = NULL; std::vector bb(4*1); rleToBbox(Rs, bb.data(), 1); std::vector bbf(bb.size()); for (size_t i = 0; i < bb.size(); i++) { bbf[i] = static_cast(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> &size, std::vector &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 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(image[kHeight].get_int64()); img.w = static_cast(image[kWidth].get_int64()); imgsgt.push_back(img); imgids.push_back(img.id); } simdjson::dom::array cats = dataset[kCats]; std::vector 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(catids_tmp.begin()+begin, catids_tmp.begin()+end); // (2) distribute categories across processes round robin //for (int64_t i=0; i cat_num_anns(catids_tmp_size, 0); //std::vector 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()); //int argmin; //for (int64_t i=0; i(annotation[kArea].get_double()); iscrowd = static_cast(annotation[kIsCrowd].get_int64()); std::vector 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 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(count)); for (int64_t size : ann_segm[kSize]) ann.segm_size.push_back(static_cast(size)); } else { for (int64_t size : ann_segm[kSize]) ann.segm_size.push_back(static_cast(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 = >s[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(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(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(anns_data[i*dim2]); int64_t category_id = static_cast(anns_data[i*dim2+1]); if (find(catids.begin(), catids.end(), category_id) == catids.end()) continue; std::vector bbox; for (int d=0; d<4; d++) bbox.push_back(static_cast(anns_data[i*dim2+d+2])); float score = static_cast(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 bbox; for (int d=0; d<4; d++) bbox.push_back(static_cast(ann_bbox.at(d).get_double())); score = static_cast(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(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(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); }