#!/usr/bin/env python import sys from string import Template class argument: def __init__(self, fields): self.ty = fields[0] self.nm = fields[1] self.flags = "" self.init = None if len(fields) > 2: if fields[2][0] == '/': self.flags = fields[2][1:].split(",") else: self.init = fields[2] api = [] for l in open("%s/api" % sys.argv[1]): if l[0] == '#': continue l = l.rstrip() if (not l.startswith(' ')) and ('/' in l): (l, flags) = l.split('/') else: flags = "" f = l.split() if len(f) != 0: if l[0] != ' ': if len(f) > 1: ty = f[1] else: ty = None api.append((f[0], [], ty, flags)) else: api[-1][1].append(argument(f)) # Validation: check that any optional arguments are last had_error = False for (f, args, ty, flags) in api: has_init = [(a.init != None) for a in args if not 'O' in a.flags] if True in has_init and not all(has_init[has_init.index(True):]): print 'Error in definition for "%s", optional arguments must be last' % f had_error = True if had_error: sys.exit(1) def cname(n): if n.startswith("CV"): return '_' + n elif n[0].isdigit(): return '_' + n else: return n # RHS is how the aggregate gets expanded in the C call aggregate = { 'pts_npts_contours' : '!.pts,!.npts,!.contours', 'cvarr_count' : '!.cvarr,!.count', 'cvarr_plane_count' : '!.cvarr,!.count', 'floats' : '!.f', 'ints' : '!.i', 'ints0' : '!.i', 'CvPoints' : '!.p,!.count', 'CvPoint2D32fs' : '!.p,!.count', 'CvPoint3D32fs' : '!.p,!.count', 'cvarrseq' : '!.seq', 'CvArrs' : '!.ims', 'IplImages' : '!.ims', 'intpair' : '!.pairs,!.count', 'cvpoint2d32f_count' : '!.points,&!.count' } conversion_types = [ 'char', 'CvArr', 'CvArrSeq', 'CvBox2D', # '((ff)(ff)f)', 'CvBox2D*', 'CvCapture*', 'CvStereoBMState*', 'CvStereoGCState*', 'CvKalman*', 'CvVideoWriter*', 'CvContourTree*', 'CvFont', 'CvFont*', 'CvHaarClassifierCascade*', 'CvHistogram', 'CvMat', 'CvMatND', 'CvMemStorage', 'CvMoments', 'CvMoments*', 'CvNextEdgeType', 'CvPoint', 'CvPoint*', 'CvPoint2D32f', # '(ff)', 'CvPoint2D32f*', 'CvPoint3D32f*', 'CvPoint2D64f', 'CvPOSITObject*', 'CvRect', 'CvRect*', 'CvRNG*', 'CvScalar', 'CvSeq', 'CvSeqOfCvConvexityDefect', 'CvSize', 'CvSlice', 'CvStarDetectorParams', 'CvSubdiv2D*', 'CvSubdiv2DEdge', 'CvTermCriteria', 'generic', 'IplConvKernel*', 'IplImage', 'PyObject*', 'PyCallableObject*' ] def safename(s): return s.replace('*', 'PTR').replace('[', '_').replace(']', '_') def has_optional(al): """ return true if any argument is optional """ return any([a.init for a in al]) def gen(name, args, ty, flags): yield "" if has_optional(args): yield "static PyObject *pycv%s(PyObject *self, PyObject *args, PyObject *kw)" % cname(name) else: yield "static PyObject *pycv%s(PyObject *self, PyObject *args)" % cname(name) if 'doconly' in flags: yield ";" else: yield "{" destinations = [] for a in args: remap = { 'CvArr' : 'CvArr*', 'CvMat' : 'CvMat*', 'CvMatND' : 'CvMatND*', 'IplImage' : 'IplImage*', 'CvMemStorage' : 'CvMemStorage*', 'CvHistogram':'CvHistogram*', 'CvSeq':'CvSeq*', 'CvHaarClassifierCascade' : 'CvHaarClassifierCascade*' } ctype = remap.get(a.ty, a.ty) if a.init: init = " = %s" % a.init else: init = '' yield " %s %s%s;" % (ctype, a.nm, init) if 'O' in a.flags: continue if a.ty in (conversion_types + aggregate.keys()): yield ' PyObject *pyobj_%s = NULL;' % (a.nm) destinations.append('&pyobj_%s' % (a.nm)) elif a.ty in [ 'CvPoint2D32f' ]: destinations.append('&%s.x, &%s.y' % (a.nm, a.nm)) elif a.ty in [ 'CvTermCriteria' ]: destinations.append('&%s.type, &%s.max_iter, &%s.epsilon' % ((a.nm,)*3)) elif a.ty in [ 'CvSURFParams' ]: destinations.append('&%s.extended, &%s.hessianThreshold, &%s.nOctaves, &%s.nOctaveLayers' % ((a.nm,)*4)) elif a.nm in [ 'CvBox2D' ]: s = ", ".join([('&' + a.nm +'.' + fld) for fld in [ 'center.x', 'center.y', 'size.width', 'size.height', 'angle' ] ]) destinations.append(s) else: destinations.append('&%s' % a.nm) fmap = { 'CvSURFParams' : '(idii)', 'double' : 'd', 'float' : 'f', 'int' : 'i', 'int64' : 'L', 'char*' : 's', } for k in (conversion_types + aggregate.keys()): fmap[k] = 'O' in_args = [ a for a in args if not 'O' in a.flags ] fmt0 = "".join([ fmap[a.ty] for a in in_args if not a.init]) fmt1 = "".join([ fmap[a.ty] for a in in_args if a.init]) yield '' if len(fmt0 + fmt1) > 0: if len(fmt1) > 0: yield ' const char *keywords[] = { %s };' % (", ".join([ '"%s"' % arg.nm for arg in args if not 'O' in arg.flags ] + ['NULL'])) yield ' if (!PyArg_ParseTupleAndKeywords(args, kw, "%s|%s", %s))' % (fmt0, fmt1, ", ".join(['(char**)keywords'] + destinations)) if '(' in (fmt0 + fmt1): print "Tuple with kwargs is not allowed, function", name sys.exit(1) else: yield ' if (!PyArg_ParseTuple(args, "%s", %s))' % (fmt0, ", ".join(destinations)) yield ' return NULL;' # Do the conversions: for a in args: joinwith = [f[2:] for f in a.flags if f.startswith("J:")] if len(joinwith) > 0: yield 'preShareData(%s, &%s);' % (joinwith[0], a.nm) if 'O' in a.flags: continue if a.ty in (conversion_types + aggregate.keys()): if a.init: pred = '(pyobj_%s != NULL) && ' % a.nm else: pred = '' yield ' if (%s!convert_to_%s(pyobj_%s, &%s, "%s")) return NULL;' % (pred, safename(a.ty), a.nm, a.nm, a.nm) yield '#ifdef CVPY_VALIDATE_%s' % name yield 'CVPY_VALIDATE_%s();' % name yield '#endif' def invokename(a): if 'K' in a.flags: prefix = "(const CvArr **)" elif 'O' in a.flags and not 'A' in a.flags: prefix = "&" else: prefix = "" if a.ty in aggregate: return prefix + aggregate[a.ty].replace('!', a.nm) else: return prefix + a.nm def funcname(s): # The name by which the function is called, in C if s.startswith("CV"): return s else: return "cv" + s tocall = '%s(%s)' % (funcname(name), ", ".join(invokename(a) for a in args)) if 'stub' in flags: yield ' return stub%s(%s);' % (name, ", ".join(invokename(a) for a in args)) elif ty == None: yield ' ERRWRAP(%s);' % tocall yield ' Py_RETURN_NONE;' else: Rtypes = [ 'int', 'int64', 'double', 'CvCapture*', 'CvVideoWriter*', 'CvPOSITObject*', 'CvScalar', 'CvSize', 'CvRect', 'CvSeq*', 'CvBox2D', 'CvSeqOfCvAvgComp*', 'CvSeqOfCvConvexityDefect*', 'CvSeqOfCvStarKeypoint*', 'CvSeqOfCvSURFPoint*', 'CvSeqOfCvSURFDescriptor*', 'CvContourTree*', 'IplConvKernel*', 'IplImage*', 'CvMat*', 'constCvMat*', 'ROCvMat*', 'CvMatND*', 'CvPoint2D32f_4', 'CvRNG', 'CvSubdiv2D*', 'CvSubdiv2DPoint*', 'CvSubdiv2DEdge', 'ROIplImage*', 'CvStereoBMState*', 'CvStereoGCState*', 'CvKalman*', 'float', 'generic', 'unsigned' ] if ty in Rtypes: yield ' %s r;' % (ty) yield ' ERRWRAP(r = %s);' % (tocall) yield ' return FROM_%s(r);' % safename(ty) else: all_returns = ty.split(",") return_value_from_call = len(set(Rtypes) & set(all_returns)) != 0 if return_value_from_call: yield ' %s r;' % list(set(Rtypes) & set(all_returns))[0] yield ' ERRWRAP(r = %s);' % (tocall) else: yield ' ERRWRAP(%s);' % (tocall) typed = dict([ (a.nm,a.ty) for a in args]) for i in range(len(all_returns)): if all_returns[i] in Rtypes: typed['r'] = all_returns[i] all_returns[i] = "r" if len(all_returns) == 1: af = dict([ (a.nm,a.flags) for a in args]) joinwith = [f[2:] for f in af.get(all_returns[0], []) if f.startswith("J:")] if len(joinwith) > 0: yield ' return shareData(pyobj_%s, %s, %s);' % (joinwith[0], joinwith[0], all_returns[0]) else: yield ' return FROM_%s(%s);' % (safename(typed[all_returns[0]]), all_returns[0]) else: yield ' return Py_BuildValue("%s", %s);' % ("N" * len(all_returns), ", ".join(["FROM_%s(%s)" % (safename(typed[n]), n) for n in all_returns])) yield '}' gen_c = [ open("generated%d.i" % i, "w") for i in range(5) ] print "Generated %d functions" % len(api) for nm,args,ty,flags in sorted(api): # Figure out docstring into ds_* ds_args = [] mandatory = [a.nm for a in args if not ('O' in a.flags) and not a.init] optional = [a.nm for a in args if not ('O' in a.flags) and a.init] ds_args = ", ".join(mandatory) def o2s(o): if o == []: return "" else: return ' [, %s%s]' % (o[0], o2s(o[1:])) ds_args += o2s(optional) ds = "%s(%s) -> %s" % (nm, ds_args, str(ty)) #print ds if has_optional(args): entry = '{"%%s", (PyCFunction)pycv%s, METH_KEYWORDS, "%s"},' % (cname(nm), ds) else: entry = '{"%%s", pycv%s, METH_VARARGS, "%s"},' % (cname(nm), ds) print >>gen_c[1], entry % (nm) if nm.startswith('CV_'): print >>gen_c[1], entry % (nm[3:]) for l in gen(nm,args,ty,flags): print >>gen_c[0], l for l in open("%s/defs" % sys.argv[1]): print >>gen_c[2], "PUBLISH(%s);" % l.split()[1] ######################################################################## # Generated objects. ######################################################################## # gen_c[3] is the code, gen_c[4] initializers gensimple = Template(""" /* ${cvtype} is the OpenCV C struct ${ourname}_t is the Python object */ struct ${ourname}_t { PyObject_HEAD ${cvtype} v; }; static PyObject *${ourname}_repr(PyObject *self) { ${ourname}_t *p = (${ourname}_t*)self; char str[1000]; sprintf(str, "<${ourname} %p>", p); return PyString_FromString(str); } ${getset_funcs} static PyGetSetDef ${ourname}_getseters[] = { ${getset_inits} {NULL} /* Sentinel */ }; static PyTypeObject ${ourname}_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, /*size*/ MODULESTR".${ourname}", /*name*/ sizeof(${ourname}_t), /*basicsize*/ }; static void ${ourname}_specials(void) { ${ourname}_Type.tp_repr = ${ourname}_repr; ${ourname}_Type.tp_getset = ${ourname}_getseters; } static PyObject *FROM_${cvtype}(${cvtype} r) { ${ourname}_t *m = PyObject_NEW(${ourname}_t, &${ourname}_Type); m->v = r; return (PyObject*)m; } static int convert_to_${cvtype}PTR(PyObject *o, ${cvtype}** dst, const char *name = "no_name") { ${allownull} if (PyType_IsSubtype(o->ob_type, &${ourname}_Type)) { *dst = &(((${ourname}_t*)o)->v); return 1; } else { (*dst) = (${cvtype}*)NULL; return failmsg("Expected ${cvtype} for argument '%s'", name); } } """) genptr = Template(""" /* ${cvtype} is the OpenCV C struct ${ourname}_t is the Python object */ struct ${ourname}_t { PyObject_HEAD ${cvtype} *v; }; static void ${ourname}_dealloc(PyObject *self) { ${ourname}_t *p = (${ourname}_t*)self; cvRelease${ourname}(&p->v); PyObject_Del(self); } static PyObject *${ourname}_repr(PyObject *self) { ${ourname}_t *p = (${ourname}_t*)self; char str[1000]; sprintf(str, "<${ourname} %p>", p); return PyString_FromString(str); } ${getset_funcs} static PyGetSetDef ${ourname}_getseters[] = { ${getset_inits} {NULL} /* Sentinel */ }; static PyTypeObject ${ourname}_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, /*size*/ MODULESTR".${ourname}", /*name*/ sizeof(${ourname}_t), /*basicsize*/ }; static void ${ourname}_specials(void) { ${ourname}_Type.tp_dealloc = ${ourname}_dealloc; ${ourname}_Type.tp_repr = ${ourname}_repr; ${ourname}_Type.tp_getset = ${ourname}_getseters; } static PyObject *FROM_${cvtype}PTR(${cvtype} *r) { ${ourname}_t *m = PyObject_NEW(${ourname}_t, &${ourname}_Type); m->v = r; return (PyObject*)m; } static int convert_to_${cvtype}PTR(PyObject *o, ${cvtype}** dst, const char *name = "no_name") { ${allownull} if (PyType_IsSubtype(o->ob_type, &${ourname}_Type)) { *dst = ((${ourname}_t*)o)->v; return 1; } else { (*dst) = (${cvtype}*)NULL; return failmsg("Expected ${cvtype} for argument '%s'", name); } } """) getset_func_template = Template(""" static PyObject *${ourname}_get_${member}(${ourname}_t *p, void *closure) { return ${rconverter}(p->v${accessor}${member}); } static int ${ourname}_set_${member}(${ourname}_t *p, PyObject *value, void *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete the ${member} attribute"); return -1; } if (! ${checker}(value)) { PyErr_SetString(PyExc_TypeError, "The ${member} attribute value must be a ${typename}"); return -1; } p->v${accessor}${member} = ${converter}(value); return 0; } """) getset_init_template = Template(""" {(char*)"${member}", (getter)${ourname}_get_${member}, (setter)${ourname}_set_${member}, (char*)"${member}", NULL}, """) objects = [ ( 'IplConvKernel', ['allownull'], { "nCols" : 'i', "nRows" : 'i', "anchorX" : 'i', "anchorY" : 'i', }), ( 'CvCapture', [], {}), ( 'CvHaarClassifierCascade', [], {}), ( 'CvPOSITObject', [], {}), ( 'CvVideoWriter', [], {}), ( 'CvStereoBMState', [], { "preFilterType" : 'i', "preFilterSize" : 'i', "preFilterCap" : 'i', "SADWindowSize" : 'i', "minDisparity" : 'i', "numberOfDisparities" : 'i', "textureThreshold" : 'i', "uniquenessRatio" : 'i', "speckleWindowSize" : 'i', "speckleRange" : 'i', }), ( 'CvStereoGCState', [], { "Ithreshold" : 'i', "interactionRadius" : 'i', "K" : 'f', "lambda" : 'f', "lambda1" : 'f', "lambda2" : 'f', "occlusionCost" : 'i', "minDisparity" : 'i', "numberOfDisparities" : 'i', "maxIters" : 'i', }), ( 'CvKalman', [], { "MP" : 'i', "DP" : 'i', "CP" : 'i', "state_pre" : 'mr', "state_post" : 'mr', "transition_matrix" : 'mr', "control_matrix" : 'mr', "measurement_matrix" : 'mr', "control_matrix" : 'mr', "process_noise_cov" : 'mr', "measurement_noise_cov" : 'mr', "error_cov_pre" : 'mr', "gain" : 'mr', "error_cov_post" : 'mr', }), ( 'CvMoments', ['copy'], { "m00" : 'f', "m10" : 'f', "m01" : 'f', "m20" : 'f', "m11" : 'f', "m02" : 'f', "m30" : 'f', "m21" : 'f', "m12" : 'f', "m03" : 'f', "mu20" : 'f', "mu11" : 'f', "mu02" : 'f', "mu30" : 'f', "mu21" : 'f', "mu12" : 'f', "mu03" : 'f', "inv_sqrt_m00" : 'f', }), ] checkers = { 'i' : 'PyNumber_Check', 'f' : 'PyNumber_Check', 'm' : 'is_cvmat', 'mr' : 'is_cvmat' } # Python -> C converters = { 'i' : 'PyInt_AsLong', 'f' : 'PyFloat_AsDouble', 'm' : 'PyCvMat_AsCvMat', 'mr' : 'PyCvMat_AsCvMat' } # C -> Python rconverters = { 'i' : 'PyInt_FromLong', 'f' : 'PyFloat_FromDouble', 'm' : 'FROM_CvMat', 'mr' : 'FROM_ROCvMatPTR' } # Human-readable type names typenames = { 'i' : 'integer', 'f' : 'float', 'm' : 'list of CvMat', 'mr' : 'list of CvMat', } for (t, flags, members) in objects: map = {'cvtype' : t, 'ourname' : t.replace('Cv', '')} # gsf is all the generated code for the member accessors if 'copy' in flags: a = '.' else: a = '->' gsf = "".join([getset_func_template.substitute(map, accessor = a, member = m, checker = checkers[t], converter = converters[t], rconverter = rconverters[t], typename = typenames[t]) for (m, t) in members.items()]) # gsi is the generated code for the initializer for each accessor gsi = "".join([getset_init_template.substitute(map, member = m) for (m, t) in members.items()]) # s is the template that pulls everything together if 'allownull' in flags: nullcode = """if (o == Py_None) { *dst = (%s*)NULL; return 1; }""" % map['cvtype'] else: nullcode = "" if 'copy' in flags: print >>gen_c[3], gensimple.substitute(map, getset_funcs = gsf, getset_inits = gsi, allownull = nullcode) else: print >>gen_c[3], genptr.substitute(map, getset_funcs = gsf, getset_inits = gsi, allownull = nullcode) print >>gen_c[4], "MKTYPE(%s);" % map['ourname'] for f in gen_c: f.close()