123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194 |
- #!/usr/bin/env python
- from __future__ import print_function
- import hdr_parser, sys, re, os
- from string import Template
- from pprint import pprint
- from collections import namedtuple
- if sys.version_info[0] >= 3:
- from io import StringIO
- else:
- from cStringIO import StringIO
- forbidden_arg_types = ["void*"]
- ignored_arg_types = ["RNG*"]
- pass_by_val_types = ["Point*", "Point2f*", "Rect*", "String*", "double*", "float*", "int*"]
- gen_template_check_self = Template("""
- ${cname} * self1 = 0;
- if (!pyopencv_${name}_getp(self, self1))
- return failmsgp("Incorrect type of self (must be '${name}' or its derivative)");
- ${pname} _self_ = ${cvt}(self1);
- """)
- gen_template_call_constructor_prelude = Template("""new (&(self->v)) Ptr<$cname>(); // init Ptr with placement new
- if(self) """)
- gen_template_call_constructor = Template("""self->v.reset(new ${cname}${py_args})""")
- gen_template_simple_call_constructor_prelude = Template("""if(self) """)
- gen_template_simple_call_constructor = Template("""new (&(self->v)) ${cname}${py_args}""")
- gen_template_parse_args = Template("""const char* keywords[] = { $kw_list, NULL };
- if( PyArg_ParseTupleAndKeywords(py_args, kw, "$fmtspec", (char**)keywords, $parse_arglist)$code_cvt )""")
- gen_template_func_body = Template("""$code_decl
- $code_parse
- {
- ${code_prelude}ERRWRAP2($code_fcall);
- $code_ret;
- }
- """)
- gen_template_mappable = Template("""
- {
- ${mappable} _src;
- if (pyopencv_to_safe(src, _src, info))
- {
- return cv_mappable_to(_src, dst);
- }
- }
- """)
- gen_template_type_decl = Template("""
- // Converter (${name})
- template<>
- struct PyOpenCV_Converter< ${cname} >
- {
- static PyObject* from(const ${cname}& r)
- {
- return pyopencv_${name}_Instance(r);
- }
- static bool to(PyObject* src, ${cname}& dst, const ArgInfo& info)
- {
- if(!src || src == Py_None)
- return true;
- ${cname} * dst_;
- if (pyopencv_${name}_getp(src, dst_))
- {
- dst = *dst_;
- return true;
- }
- ${mappable_code}
- failmsg("Expected ${cname} for argument '%s'", info.name);
- return false;
- }
- };
- """)
- gen_template_map_type_cvt = Template("""
- template<> bool pyopencv_to(PyObject* src, ${cname}& dst, const ArgInfo& info);
- """)
- gen_template_set_prop_from_map = Template("""
- if( PyMapping_HasKeyString(src, (char*)"$propname") )
- {
- tmp = PyMapping_GetItemString(src, (char*)"$propname");
- ok = tmp && pyopencv_to_safe(tmp, dst.$propname, ArgInfo("$propname", false));
- Py_DECREF(tmp);
- if(!ok) return false;
- }""")
- gen_template_type_impl = Template("""
- // GetSet (${name})
- ${getset_code}
- // Methods (${name})
- ${methods_code}
- // Tables (${name})
- static PyGetSetDef pyopencv_${name}_getseters[] =
- {${getset_inits}
- {NULL} /* Sentinel */
- };
- static PyMethodDef pyopencv_${name}_methods[] =
- {
- ${methods_inits}
- {NULL, NULL}
- };
- """)
- gen_template_get_prop = Template("""
- static PyObject* pyopencv_${name}_get_${member}(pyopencv_${name}_t* p, void *closure)
- {
- return pyopencv_from(p->v${access}${member});
- }
- """)
- gen_template_get_prop_algo = Template("""
- static PyObject* pyopencv_${name}_get_${member}(pyopencv_${name}_t* p, void *closure)
- {
- $cname* _self_ = dynamic_cast<$cname*>(p->v.get());
- if (!_self_)
- return failmsgp("Incorrect type of object (must be '${name}' or its derivative)");
- return pyopencv_from(_self_${access}${member});
- }
- """)
- gen_template_set_prop = Template("""
- static int pyopencv_${name}_set_${member}(pyopencv_${name}_t* p, PyObject *value, void *closure)
- {
- if (!value)
- {
- PyErr_SetString(PyExc_TypeError, "Cannot delete the ${member} attribute");
- return -1;
- }
- return pyopencv_to_safe(value, p->v${access}${member}, ArgInfo("value", false)) ? 0 : -1;
- }
- """)
- gen_template_set_prop_algo = Template("""
- static int pyopencv_${name}_set_${member}(pyopencv_${name}_t* p, PyObject *value, void *closure)
- {
- if (!value)
- {
- PyErr_SetString(PyExc_TypeError, "Cannot delete the ${member} attribute");
- return -1;
- }
- $cname* _self_ = dynamic_cast<$cname*>(p->v.get());
- if (!_self_)
- {
- failmsgp("Incorrect type of object (must be '${name}' or its derivative)");
- return -1;
- }
- return pyopencv_to_safe(value, _self_${access}${member}, ArgInfo("value", false)) ? 0 : -1;
- }
- """)
- gen_template_prop_init = Template("""
- {(char*)"${member}", (getter)pyopencv_${name}_get_${member}, NULL, (char*)"${member}", NULL},""")
- gen_template_rw_prop_init = Template("""
- {(char*)"${member}", (getter)pyopencv_${name}_get_${member}, (setter)pyopencv_${name}_set_${member}, (char*)"${member}", NULL},""")
- gen_template_overloaded_function_call = Template("""
- {
- ${variant}
- pyPopulateArgumentConversionErrors();
- }
- """)
- class FormatStrings:
- string = 's'
- unsigned_char = 'b'
- short_int = 'h'
- int = 'i'
- unsigned_int = 'I'
- long = 'l'
- unsigned_long = 'k'
- long_long = 'L'
- unsigned_long_long = 'K'
- size_t = 'n'
- float = 'f'
- double = 'd'
- object = 'O'
- ArgTypeInfo = namedtuple('ArgTypeInfo',
- ['atype', 'format_str', 'default_value',
- 'strict_conversion'])
- # strict_conversion is False by default
- ArgTypeInfo.__new__.__defaults__ = (False,)
- simple_argtype_mapping = {
- "bool": ArgTypeInfo("bool", FormatStrings.unsigned_char, "0", True),
- "size_t": ArgTypeInfo("size_t", FormatStrings.unsigned_long_long, "0", True),
- "int": ArgTypeInfo("int", FormatStrings.int, "0", True),
- "float": ArgTypeInfo("float", FormatStrings.float, "0.f", True),
- "double": ArgTypeInfo("double", FormatStrings.double, "0", True),
- "c_string": ArgTypeInfo("char*", FormatStrings.string, '(char*)""'),
- "string": ArgTypeInfo("std::string", FormatStrings.object, None, True),
- "Stream": ArgTypeInfo("Stream", FormatStrings.object, 'Stream::Null()', True),
- }
- # Set of reserved keywords for Python. Can be acquired via the following call
- # $ python -c "help('keywords')"
- # Keywords that are reserved in C/C++ are excluded because they can not be
- # used as variables identifiers
- python_reserved_keywords = {
- "True", "None", "False", "as", "assert", "def", "del", "elif", "except", "exec",
- "finally", "from", "global", "import", "in", "is", "lambda", "nonlocal",
- "pass", "print", "raise", "with", "yield"
- }
- def normalize_class_name(name):
- return re.sub(r"^cv\.", "", name).replace(".", "_")
- def get_type_format_string(arg_type_info):
- if arg_type_info.strict_conversion:
- return FormatStrings.object
- else:
- return arg_type_info.format_str
- class ClassProp(object):
- def __init__(self, decl):
- self.tp = decl[0].replace("*", "_ptr")
- self.name = decl[1]
- self.readonly = True
- if "/RW" in decl[3]:
- self.readonly = False
- class ClassInfo(object):
- def __init__(self, name, decl=None):
- self.cname = name.replace(".", "::")
- self.name = self.wname = normalize_class_name(name)
- self.sname = name[name.rfind('.') + 1:]
- self.ismap = False
- self.issimple = False
- self.isalgorithm = False
- self.methods = {}
- self.props = []
- self.mappables = []
- self.consts = {}
- self.base = None
- self.constructor = None
- customname = False
- if decl:
- bases = decl[1].split()[1:]
- if len(bases) > 1:
- print("Note: Class %s has more than 1 base class (not supported by Python C extensions)" % (self.name,))
- print(" Bases: ", " ".join(bases))
- print(" Only the first base class will be used")
- #return sys.exit(-1)
- elif len(bases) == 1:
- self.base = bases[0].strip(",")
- if self.base.startswith("cv::"):
- self.base = self.base[4:]
- if self.base == "Algorithm":
- self.isalgorithm = True
- self.base = self.base.replace("::", "_")
- for m in decl[2]:
- if m.startswith("="):
- wname = m[1:]
- npos = name.rfind('.')
- if npos >= 0:
- self.wname = normalize_class_name(name[:npos] + '.' + wname)
- else:
- self.wname = wname
- customname = True
- elif m == "/Map":
- self.ismap = True
- elif m == "/Simple":
- self.issimple = True
- self.props = [ClassProp(p) for p in decl[3]]
- if not customname and self.wname.startswith("Cv"):
- self.wname = self.wname[2:]
- def gen_map_code(self, codegen):
- all_classes = codegen.classes
- code = "static bool pyopencv_to(PyObject* src, %s& dst, const ArgInfo& info)\n{\n PyObject* tmp;\n bool ok;\n" % (self.cname)
- code += "".join([gen_template_set_prop_from_map.substitute(propname=p.name,proptype=p.tp) for p in self.props])
- if self.base:
- code += "\n return pyopencv_to_safe(src, (%s&)dst, info);\n}\n" % all_classes[self.base].cname
- else:
- code += "\n return true;\n}\n"
- return code
- def gen_code(self, codegen):
- all_classes = codegen.classes
- if self.ismap:
- return self.gen_map_code(codegen)
- getset_code = StringIO()
- getset_inits = StringIO()
- sorted_props = [(p.name, p) for p in self.props]
- sorted_props.sort()
- access_op = "->"
- if self.issimple:
- access_op = "."
- for pname, p in sorted_props:
- if self.isalgorithm:
- getset_code.write(gen_template_get_prop_algo.substitute(name=self.name, cname=self.cname, member=pname, membertype=p.tp, access=access_op))
- else:
- getset_code.write(gen_template_get_prop.substitute(name=self.name, member=pname, membertype=p.tp, access=access_op))
- if p.readonly:
- getset_inits.write(gen_template_prop_init.substitute(name=self.name, member=pname))
- else:
- if self.isalgorithm:
- getset_code.write(gen_template_set_prop_algo.substitute(name=self.name, cname=self.cname, member=pname, membertype=p.tp, access=access_op))
- else:
- getset_code.write(gen_template_set_prop.substitute(name=self.name, member=pname, membertype=p.tp, access=access_op))
- getset_inits.write(gen_template_rw_prop_init.substitute(name=self.name, member=pname))
- methods_code = StringIO()
- methods_inits = StringIO()
- sorted_methods = list(self.methods.items())
- sorted_methods.sort()
- if self.constructor is not None:
- methods_code.write(self.constructor.gen_code(codegen))
- for mname, m in sorted_methods:
- methods_code.write(m.gen_code(codegen))
- methods_inits.write(m.get_tab_entry())
- code = gen_template_type_impl.substitute(name=self.name, wname=self.wname, cname=self.cname,
- getset_code=getset_code.getvalue(), getset_inits=getset_inits.getvalue(),
- methods_code=methods_code.getvalue(), methods_inits=methods_inits.getvalue())
- return code
- def gen_def(self, codegen):
- all_classes = codegen.classes
- baseptr = "NoBase"
- if self.base and self.base in all_classes:
- baseptr = all_classes[self.base].name
- constructor_name = "0"
- if self.constructor is not None:
- constructor_name = self.constructor.get_wrapper_name()
- return "CVPY_TYPE({}, {}, {}, {}, {}, {});\n".format(
- self.wname,
- self.name,
- self.cname if self.issimple else "Ptr<{}>".format(self.cname),
- self.sname if self.issimple else "Ptr",
- baseptr,
- constructor_name
- )
- def handle_ptr(tp):
- if tp.startswith('Ptr_'):
- tp = 'Ptr<' + "::".join(tp.split('_')[1:]) + '>'
- return tp
- class ArgInfo(object):
- def __init__(self, arg_tuple):
- self.tp = handle_ptr(arg_tuple[0])
- self.name = arg_tuple[1]
- if self.name in python_reserved_keywords:
- self.name += "_"
- self.defval = arg_tuple[2]
- self.isarray = False
- self.arraylen = 0
- self.arraycvt = None
- self.inputarg = True
- self.outputarg = False
- self.returnarg = False
- self.isrvalueref = False
- for m in arg_tuple[3]:
- if m == "/O":
- self.inputarg = False
- self.outputarg = True
- self.returnarg = True
- elif m == "/IO":
- self.inputarg = True
- self.outputarg = True
- self.returnarg = True
- elif m.startswith("/A"):
- self.isarray = True
- self.arraylen = m[2:].strip()
- elif m.startswith("/CA"):
- self.isarray = True
- self.arraycvt = m[2:].strip()
- elif m == "/RRef":
- self.isrvalueref = True
- self.py_inputarg = False
- self.py_outputarg = False
- def isbig(self):
- return self.tp in ["Mat", "vector_Mat", "cuda::GpuMat", "GpuMat", "vector_GpuMat", "UMat", "vector_UMat"] # or self.tp.startswith("vector")
- def crepr(self):
- return "ArgInfo(\"%s\", %d)" % (self.name, self.outputarg)
- class FuncVariant(object):
- def __init__(self, classname, name, decl, isconstructor, isphantom=False):
- self.classname = classname
- self.name = self.wname = name
- self.isconstructor = isconstructor
- self.isphantom = isphantom
- self.docstring = decl[5]
- self.rettype = decl[4] or handle_ptr(decl[1])
- if self.rettype == "void":
- self.rettype = ""
- self.args = []
- self.array_counters = {}
- for a in decl[3]:
- ainfo = ArgInfo(a)
- if ainfo.isarray and not ainfo.arraycvt:
- c = ainfo.arraylen
- c_arrlist = self.array_counters.get(c, [])
- if c_arrlist:
- c_arrlist.append(ainfo.name)
- else:
- self.array_counters[c] = [ainfo.name]
- self.args.append(ainfo)
- self.init_pyproto()
- def init_pyproto(self):
- # string representation of argument list, with '[', ']' symbols denoting optional arguments, e.g.
- # "src1, src2[, dst[, mask]]" for cv.add
- argstr = ""
- # list of all input arguments of the Python function, with the argument numbers:
- # [("src1", 0), ("src2", 1), ("dst", 2), ("mask", 3)]
- # we keep an argument number to find the respective argument quickly, because
- # some of the arguments of C function may not present in the Python function (such as array counters)
- # or even go in a different order ("heavy" output parameters of the C function
- # become the first optional input parameters of the Python function, and thus they are placed right after
- # non-optional input parameters)
- arglist = []
- # the list of "heavy" output parameters. Heavy parameters are the parameters
- # that can be expensive to allocate each time, such as vectors and matrices (see isbig).
- outarr_list = []
- # the list of output parameters. Also includes input/output parameters.
- outlist = []
- firstoptarg = 1000000
- argno = -1
- for a in self.args:
- argno += 1
- if a.name in self.array_counters:
- continue
- assert not a.tp in forbidden_arg_types, 'Forbidden type "{}" for argument "{}" in "{}" ("{}")'.format(a.tp, a.name, self.name, self.classname)
- if a.tp in ignored_arg_types:
- continue
- if a.returnarg:
- outlist.append((a.name, argno))
- if (not a.inputarg) and a.isbig():
- outarr_list.append((a.name, argno))
- continue
- if not a.inputarg:
- continue
- if not a.defval:
- arglist.append((a.name, argno))
- else:
- firstoptarg = min(firstoptarg, len(arglist))
- # if there are some array output parameters before the first default parameter, they
- # are added as optional parameters before the first optional parameter
- if outarr_list:
- arglist += outarr_list
- outarr_list = []
- arglist.append((a.name, argno))
- if outarr_list:
- firstoptarg = min(firstoptarg, len(arglist))
- arglist += outarr_list
- firstoptarg = min(firstoptarg, len(arglist))
- noptargs = len(arglist) - firstoptarg
- argnamelist = [aname for aname, argno in arglist]
- argstr = ", ".join(argnamelist[:firstoptarg])
- argstr = "[, ".join([argstr] + argnamelist[firstoptarg:])
- argstr += "]" * noptargs
- if self.rettype:
- outlist = [("retval", -1)] + outlist
- elif self.isconstructor:
- assert outlist == []
- outlist = [("self", -1)]
- if self.isconstructor:
- classname = self.classname
- if classname.startswith("Cv"):
- classname=classname[2:]
- outstr = "<%s object>" % (classname,)
- elif outlist:
- outstr = ", ".join([o[0] for o in outlist])
- else:
- outstr = "None"
- self.py_arg_str = argstr
- self.py_return_str = outstr
- self.py_prototype = "%s(%s) -> %s" % (self.wname, argstr, outstr)
- self.py_noptargs = noptargs
- self.py_arglist = arglist
- for aname, argno in arglist:
- self.args[argno].py_inputarg = True
- for aname, argno in outlist:
- if argno >= 0:
- self.args[argno].py_outputarg = True
- self.py_outlist = outlist
- class FuncInfo(object):
- def __init__(self, classname, name, cname, isconstructor, namespace, is_static):
- self.classname = classname
- self.name = name
- self.cname = cname
- self.isconstructor = isconstructor
- self.namespace = namespace
- self.is_static = is_static
- self.variants = []
- def add_variant(self, decl, isphantom=False):
- self.variants.append(FuncVariant(self.classname, self.name, decl, self.isconstructor, isphantom))
- def get_wrapper_name(self):
- name = self.name
- if self.classname:
- classname = self.classname + "_"
- if "[" in name:
- name = "getelem"
- else:
- classname = ""
- if self.is_static:
- name += "_static"
- return "pyopencv_" + self.namespace.replace('.','_') + '_' + classname + name
- def get_wrapper_prototype(self, codegen):
- full_fname = self.get_wrapper_name()
- if self.isconstructor:
- return "static int {fn_name}(pyopencv_{type_name}_t* self, PyObject* py_args, PyObject* kw)".format(
- fn_name=full_fname, type_name=codegen.classes[self.classname].name)
- if self.classname:
- self_arg = "self"
- else:
- self_arg = ""
- return "static PyObject* %s(PyObject* %s, PyObject* py_args, PyObject* kw)" % (full_fname, self_arg)
- def get_tab_entry(self):
- prototype_list = []
- docstring_list = []
- have_empty_constructor = False
- for v in self.variants:
- s = v.py_prototype
- if (not v.py_arglist) and self.isconstructor:
- have_empty_constructor = True
- if s not in prototype_list:
- prototype_list.append(s)
- docstring_list.append(v.docstring)
- # if there are just 2 constructors: default one and some other,
- # we simplify the notation.
- # Instead of ClassName(args ...) -> object or ClassName() -> object
- # we write ClassName([args ...]) -> object
- if have_empty_constructor and len(self.variants) == 2:
- idx = self.variants[1].py_arglist != []
- s = self.variants[idx].py_prototype
- p1 = s.find("(")
- p2 = s.rfind(")")
- prototype_list = [s[:p1+1] + "[" + s[p1+1:p2] + "]" + s[p2:]]
- # The final docstring will be: Each prototype, followed by
- # their relevant doxygen comment
- full_docstring = ""
- for prototype, body in zip(prototype_list, docstring_list):
- full_docstring += Template("$prototype\n$docstring\n\n\n\n").substitute(
- prototype=prototype,
- docstring='\n'.join(
- ['. ' + line
- for line in body.split('\n')]
- )
- )
- # Escape backslashes, newlines, and double quotes
- full_docstring = full_docstring.strip().replace("\\", "\\\\").replace('\n', '\\n').replace("\"", "\\\"")
- # Convert unicode chars to xml representation, but keep as string instead of bytes
- full_docstring = full_docstring.encode('ascii', errors='xmlcharrefreplace').decode()
- return Template(' {"$py_funcname", CV_PY_FN_WITH_KW_($wrap_funcname, $flags), "$py_docstring"},\n'
- ).substitute(py_funcname = self.variants[0].wname, wrap_funcname=self.get_wrapper_name(),
- flags = 'METH_STATIC' if self.is_static else '0', py_docstring = full_docstring)
- def gen_code(self, codegen):
- all_classes = codegen.classes
- proto = self.get_wrapper_prototype(codegen)
- code = "%s\n{\n" % (proto,)
- code += " using namespace %s;\n\n" % self.namespace.replace('.', '::')
- selfinfo = None
- ismethod = self.classname != "" and not self.isconstructor
- # full name is needed for error diagnostic in PyArg_ParseTupleAndKeywords
- fullname = self.name
- if self.classname:
- selfinfo = all_classes[self.classname]
- if not self.isconstructor:
- if not self.is_static:
- code += gen_template_check_self.substitute(
- name=selfinfo.name,
- cname=selfinfo.cname if selfinfo.issimple else "Ptr<{}>".format(selfinfo.cname),
- pname=(selfinfo.cname + '*') if selfinfo.issimple else "Ptr<{}>".format(selfinfo.cname),
- cvt='' if selfinfo.issimple else '*'
- )
- fullname = selfinfo.wname + "." + fullname
- all_code_variants = []
- for v in self.variants:
- code_decl = ""
- code_ret = ""
- code_cvt_list = []
- code_args = "("
- all_cargs = []
- if v.isphantom and ismethod and not self.is_static:
- code_args += "_self_"
- # declare all the C function arguments,
- # add necessary conversions from Python objects to code_cvt_list,
- # form the function/method call,
- # for the list of type mappings
- for a in v.args:
- if a.tp in ignored_arg_types:
- defval = a.defval
- if not defval and a.tp.endswith("*"):
- defval = "0"
- assert defval
- if not code_args.endswith("("):
- code_args += ", "
- code_args += defval
- all_cargs.append([[None, ""], ""])
- continue
- tp1 = tp = a.tp
- amp = ""
- defval0 = ""
- if tp in pass_by_val_types:
- tp = tp1 = tp[:-1]
- amp = "&"
- if tp.endswith("*"):
- defval0 = "0"
- tp1 = tp.replace("*", "_ptr")
- tp_candidates = [a.tp, normalize_class_name(self.namespace + "." + a.tp)]
- if any(tp in codegen.enums.keys() for tp in tp_candidates):
- defval0 = "static_cast<%s>(%d)" % (a.tp, 0)
- arg_type_info = simple_argtype_mapping.get(tp, ArgTypeInfo(tp, FormatStrings.object, defval0, True))
- parse_name = a.name
- if a.py_inputarg:
- if arg_type_info.strict_conversion:
- code_decl += " PyObject* pyobj_%s = NULL;\n" % (a.name,)
- parse_name = "pyobj_" + a.name
- if a.tp == 'char':
- code_cvt_list.append("convert_to_char(pyobj_%s, &%s, %s)" % (a.name, a.name, a.crepr()))
- else:
- code_cvt_list.append("pyopencv_to_safe(pyobj_%s, %s, %s)" % (a.name, a.name, a.crepr()))
- all_cargs.append([arg_type_info, parse_name])
- defval = a.defval
- if not defval:
- defval = arg_type_info.default_value
- else:
- if "UMat" in tp:
- if "Mat" in defval and "UMat" not in defval:
- defval = defval.replace("Mat", "UMat")
- if "cuda::GpuMat" in tp:
- if "Mat" in defval and "GpuMat" not in defval:
- defval = defval.replace("Mat", "cuda::GpuMat")
- # "tp arg = tp();" is equivalent to "tp arg;" in the case of complex types
- if defval == tp + "()" and arg_type_info.format_str == FormatStrings.object:
- defval = ""
- if a.outputarg and not a.inputarg:
- defval = ""
- if defval:
- code_decl += " %s %s=%s;\n" % (arg_type_info.atype, a.name, defval)
- else:
- code_decl += " %s %s;\n" % (arg_type_info.atype, a.name)
- if not code_args.endswith("("):
- code_args += ", "
- if a.isrvalueref:
- a.name = 'std::move(' + a.name + ')'
- code_args += amp + a.name
- code_args += ")"
- if self.isconstructor:
- if selfinfo.issimple:
- templ_prelude = gen_template_simple_call_constructor_prelude
- templ = gen_template_simple_call_constructor
- else:
- templ_prelude = gen_template_call_constructor_prelude
- templ = gen_template_call_constructor
- code_prelude = templ_prelude.substitute(name=selfinfo.name, cname=selfinfo.cname)
- code_fcall = templ.substitute(name=selfinfo.name, cname=selfinfo.cname, py_args=code_args)
- if v.isphantom:
- code_fcall = code_fcall.replace("new " + selfinfo.cname, self.cname.replace("::", "_"))
- else:
- code_prelude = ""
- code_fcall = ""
- if v.rettype:
- code_decl += " " + v.rettype + " retval;\n"
- code_fcall += "retval = "
- if not v.isphantom and ismethod and not self.is_static:
- code_fcall += "_self_->" + self.cname
- else:
- code_fcall += self.cname
- code_fcall += code_args
- if code_cvt_list:
- code_cvt_list = [""] + code_cvt_list
- # add info about return value, if any, to all_cargs. if there non-void return value,
- # it is encoded in v.py_outlist as ("retval", -1) pair.
- # As [-1] in Python accesses the last element of a list, we automatically handle the return value by
- # adding the necessary info to the end of all_cargs list.
- if v.rettype:
- tp = v.rettype
- tp1 = tp.replace("*", "_ptr")
- default_info = ArgTypeInfo(tp, FormatStrings.object, "0")
- arg_type_info = simple_argtype_mapping.get(tp, default_info)
- all_cargs.append(arg_type_info)
- if v.args and v.py_arglist:
- # form the format spec for PyArg_ParseTupleAndKeywords
- fmtspec = "".join([
- get_type_format_string(all_cargs[argno][0])
- for aname, argno in v.py_arglist
- ])
- if v.py_noptargs > 0:
- fmtspec = fmtspec[:-v.py_noptargs] + "|" + fmtspec[-v.py_noptargs:]
- fmtspec += ":" + fullname
- # form the argument parse code that:
- # - declares the list of keyword parameters
- # - calls PyArg_ParseTupleAndKeywords
- # - converts complex arguments from PyObject's to native OpenCV types
- code_parse = gen_template_parse_args.substitute(
- kw_list = ", ".join(['"' + aname + '"' for aname, argno in v.py_arglist]),
- fmtspec = fmtspec,
- parse_arglist = ", ".join(["&" + all_cargs[argno][1] for aname, argno in v.py_arglist]),
- code_cvt = " &&\n ".join(code_cvt_list))
- else:
- code_parse = "if(PyObject_Size(py_args) == 0 && (!kw || PyObject_Size(kw) == 0))"
- if len(v.py_outlist) == 0:
- code_ret = "Py_RETURN_NONE"
- elif len(v.py_outlist) == 1:
- if self.isconstructor:
- code_ret = "return 0"
- else:
- aname, argno = v.py_outlist[0]
- code_ret = "return pyopencv_from(%s)" % (aname,)
- else:
- # there is more than 1 return parameter; form the tuple out of them
- fmtspec = "N"*len(v.py_outlist)
- code_ret = "return Py_BuildValue(\"(%s)\", %s)" % \
- (fmtspec, ", ".join(["pyopencv_from(" + aname + ")" for aname, argno in v.py_outlist]))
- all_code_variants.append(gen_template_func_body.substitute(code_decl=code_decl,
- code_parse=code_parse, code_prelude=code_prelude, code_fcall=code_fcall, code_ret=code_ret))
- if len(all_code_variants)==1:
- # if the function/method has only 1 signature, then just put it
- code += all_code_variants[0]
- else:
- # try to execute each signature, add an interlude between function
- # calls to collect error from all conversions
- code += ' pyPrepareArgumentConversionErrorsStorage({});\n'.format(len(all_code_variants))
- code += ' \n'.join(gen_template_overloaded_function_call.substitute(variant=v)
- for v in all_code_variants)
- code += ' pyRaiseCVOverloadException("{}");\n'.format(self.name)
- def_ret = "NULL"
- if self.isconstructor:
- def_ret = "-1"
- code += "\n return %s;\n}\n\n" % def_ret
- cname = self.cname
- classinfo = None
- #dump = False
- #if dump: pprint(vars(self))
- #if dump: pprint(vars(self.variants[0]))
- if self.classname:
- classinfo = all_classes[self.classname]
- #if dump: pprint(vars(classinfo))
- if self.isconstructor:
- py_name = 'cv.' + classinfo.wname
- elif self.is_static:
- py_name = '.'.join([self.namespace, classinfo.sname + '_' + self.variants[0].wname])
- else:
- cname = classinfo.cname + '::' + cname
- py_name = 'cv.' + classinfo.wname + '.' + self.variants[0].wname
- else:
- py_name = '.'.join([self.namespace, self.variants[0].wname])
- #if dump: print(cname + " => " + py_name)
- py_signatures = codegen.py_signatures.setdefault(cname, [])
- for v in self.variants:
- s = dict(name=py_name, arg=v.py_arg_str, ret=v.py_return_str)
- for old in py_signatures:
- if s == old:
- break
- else:
- py_signatures.append(s)
- return code
- class Namespace(object):
- def __init__(self):
- self.funcs = {}
- self.consts = {}
- class PythonWrapperGenerator(object):
- def __init__(self):
- self.clear()
- def clear(self):
- self.classes = {}
- self.namespaces = {}
- self.consts = {}
- self.enums = {}
- self.code_include = StringIO()
- self.code_enums = StringIO()
- self.code_types = StringIO()
- self.code_funcs = StringIO()
- self.code_ns_reg = StringIO()
- self.code_ns_init = StringIO()
- self.code_type_publish = StringIO()
- self.py_signatures = dict()
- self.class_idx = 0
- def add_class(self, stype, name, decl):
- classinfo = ClassInfo(name, decl)
- classinfo.decl_idx = self.class_idx
- self.class_idx += 1
- if classinfo.name in self.classes:
- print("Generator error: class %s (cname=%s) already exists" \
- % (classinfo.name, classinfo.cname))
- sys.exit(-1)
- self.classes[classinfo.name] = classinfo
- # Add Class to json file.
- namespace, classes, name = self.split_decl_name(name)
- namespace = '.'.join(namespace)
- name = '_'.join(classes+[name])
- py_name = 'cv.' + classinfo.wname # use wrapper name
- py_signatures = self.py_signatures.setdefault(classinfo.cname, [])
- py_signatures.append(dict(name=py_name))
- #print('class: ' + classinfo.cname + " => " + py_name)
- def split_decl_name(self, name):
- chunks = name.split('.')
- namespace = chunks[:-1]
- classes = []
- while namespace and '.'.join(namespace) not in self.parser.namespaces:
- classes.insert(0, namespace.pop())
- return namespace, classes, chunks[-1]
- def add_const(self, name, decl):
- cname = name.replace('.','::')
- namespace, classes, name = self.split_decl_name(name)
- namespace = '.'.join(namespace)
- name = '_'.join(classes+[name])
- ns = self.namespaces.setdefault(namespace, Namespace())
- if name in ns.consts:
- print("Generator error: constant %s (cname=%s) already exists" \
- % (name, cname))
- sys.exit(-1)
- ns.consts[name] = cname
- value = decl[1]
- py_name = '.'.join([namespace, name])
- py_signatures = self.py_signatures.setdefault(cname, [])
- py_signatures.append(dict(name=py_name, value=value))
- #print(cname + ' => ' + str(py_name) + ' (value=' + value + ')')
- def add_enum(self, name, decl):
- wname = normalize_class_name(name)
- if wname.endswith("<unnamed>"):
- wname = None
- else:
- self.enums[wname] = name
- const_decls = decl[3]
- for decl in const_decls:
- name = decl[0]
- self.add_const(name.replace("const ", "").strip(), decl)
- def add_func(self, decl):
- namespace, classes, barename = self.split_decl_name(decl[0])
- cname = "::".join(namespace+classes+[barename])
- name = barename
- classname = ''
- bareclassname = ''
- if classes:
- classname = normalize_class_name('.'.join(namespace+classes))
- bareclassname = classes[-1]
- namespace_str = '.'.join(namespace)
- isconstructor = name == bareclassname
- is_static = False
- isphantom = False
- mappable = None
- for m in decl[2]:
- if m == "/S":
- is_static = True
- elif m == "/phantom":
- isphantom = True
- cname = cname.replace("::", "_")
- elif m.startswith("="):
- name = m[1:]
- elif m.startswith("/mappable="):
- mappable = m[10:]
- self.classes[classname].mappables.append(mappable)
- return
- if isconstructor:
- name = "_".join(classes[:-1]+[name])
- if is_static:
- # Add it as a method to the class
- func_map = self.classes[classname].methods
- func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace_str, is_static))
- func.add_variant(decl, isphantom)
- # Add it as global function
- g_name = "_".join(classes+[name])
- w_classes = []
- for i in range(0, len(classes)):
- classes_i = classes[:i+1]
- classname_i = normalize_class_name('.'.join(namespace+classes_i))
- w_classname = self.classes[classname_i].wname
- namespace_prefix = normalize_class_name('.'.join(namespace)) + '_'
- if w_classname.startswith(namespace_prefix):
- w_classname = w_classname[len(namespace_prefix):]
- w_classes.append(w_classname)
- g_wname = "_".join(w_classes+[name])
- func_map = self.namespaces.setdefault(namespace_str, Namespace()).funcs
- func = func_map.setdefault(g_name, FuncInfo("", g_name, cname, isconstructor, namespace_str, False))
- func.add_variant(decl, isphantom)
- if g_wname != g_name: # TODO OpenCV 5.0
- wfunc = func_map.setdefault(g_wname, FuncInfo("", g_wname, cname, isconstructor, namespace_str, False))
- wfunc.add_variant(decl, isphantom)
- else:
- if classname and not isconstructor:
- if not isphantom:
- cname = barename
- func_map = self.classes[classname].methods
- else:
- func_map = self.namespaces.setdefault(namespace_str, Namespace()).funcs
- func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace_str, is_static))
- func.add_variant(decl, isphantom)
- if classname and isconstructor:
- self.classes[classname].constructor = func
- def gen_namespace(self, ns_name):
- ns = self.namespaces[ns_name]
- wname = normalize_class_name(ns_name)
- self.code_ns_reg.write('static PyMethodDef methods_%s[] = {\n'%wname)
- for name, func in sorted(ns.funcs.items()):
- if func.isconstructor:
- continue
- self.code_ns_reg.write(func.get_tab_entry())
- custom_entries_macro = 'PYOPENCV_EXTRA_METHODS_{}'.format(wname.upper())
- self.code_ns_reg.write('#ifdef {}\n {}\n#endif\n'.format(custom_entries_macro, custom_entries_macro))
- self.code_ns_reg.write(' {NULL, NULL}\n};\n\n')
- self.code_ns_reg.write('static ConstDef consts_%s[] = {\n'%wname)
- for name, cname in sorted(ns.consts.items()):
- self.code_ns_reg.write(' {"%s", static_cast<long>(%s)},\n'%(name, cname))
- compat_name = re.sub(r"([a-z])([A-Z])", r"\1_\2", name).upper()
- if name != compat_name:
- self.code_ns_reg.write(' {"%s", static_cast<long>(%s)},\n'%(compat_name, cname))
- custom_entries_macro = 'PYOPENCV_EXTRA_CONSTANTS_{}'.format(wname.upper())
- self.code_ns_reg.write('#ifdef {}\n {}\n#endif\n'.format(custom_entries_macro, custom_entries_macro))
- self.code_ns_reg.write(' {NULL, 0}\n};\n\n')
- def gen_enum_reg(self, enum_name):
- name_seg = enum_name.split(".")
- is_enum_class = False
- if len(name_seg) >= 2 and name_seg[-1] == name_seg[-2]:
- enum_name = ".".join(name_seg[:-1])
- is_enum_class = True
- wname = normalize_class_name(enum_name)
- cname = enum_name.replace(".", "::")
- code = ""
- if re.sub(r"^cv\.", "", enum_name) != wname:
- code += "typedef {0} {1};\n".format(cname, wname)
- code += "CV_PY_FROM_ENUM({0});\nCV_PY_TO_ENUM({0});\n\n".format(wname)
- self.code_enums.write(code)
- def save(self, path, name, buf):
- with open(path + "/" + name, "wt") as f:
- f.write(buf.getvalue())
- def save_json(self, path, name, value):
- import json
- with open(path + "/" + name, "wt") as f:
- json.dump(value, f)
- def gen(self, srcfiles, output_path):
- self.clear()
- self.parser = hdr_parser.CppHeaderParser(generate_umat_decls=True, generate_gpumat_decls=True)
- # step 1: scan the headers and build more descriptive maps of classes, consts, functions
- for hdr in srcfiles:
- decls = self.parser.parse(hdr)
- if len(decls) == 0:
- continue
- if hdr.find('misc/python/shadow_') < 0: # Avoid including the "shadow_" files
- if hdr.find('opencv2/') >= 0:
- # put relative path
- self.code_include.write('#include "{0}"\n'.format(hdr[hdr.rindex('opencv2/'):]))
- else:
- self.code_include.write('#include "{0}"\n'.format(hdr))
- for decl in decls:
- name = decl[0]
- if name.startswith("struct") or name.startswith("class"):
- # class/struct
- p = name.find(" ")
- stype = name[:p]
- name = name[p+1:].strip()
- self.add_class(stype, name, decl)
- elif name.startswith("const"):
- # constant
- self.add_const(name.replace("const ", "").strip(), decl)
- elif name.startswith("enum"):
- # enum
- self.add_enum(name.rsplit(" ", 1)[1], decl)
- else:
- # function
- self.add_func(decl)
- # step 1.5 check if all base classes exist
- for name, classinfo in self.classes.items():
- if classinfo.base:
- chunks = classinfo.base.split('_')
- base = '_'.join(chunks)
- while base not in self.classes and len(chunks)>1:
- del chunks[-2]
- base = '_'.join(chunks)
- if base not in self.classes:
- print("Generator error: unable to resolve base %s for %s"
- % (classinfo.base, classinfo.name))
- sys.exit(-1)
- base_instance = self.classes[base]
- classinfo.base = base
- classinfo.isalgorithm |= base_instance.isalgorithm # wrong processing of 'isalgorithm' flag:
- # doesn't work for trees(graphs) with depth > 2
- self.classes[name] = classinfo
- # tree-based propagation of 'isalgorithm'
- processed = dict()
- def process_isalgorithm(classinfo):
- if classinfo.isalgorithm or classinfo in processed:
- return classinfo.isalgorithm
- res = False
- if classinfo.base:
- res = process_isalgorithm(self.classes[classinfo.base])
- #assert not (res == True or classinfo.isalgorithm is False), "Internal error: " + classinfo.name + " => " + classinfo.base
- classinfo.isalgorithm |= res
- res = classinfo.isalgorithm
- processed[classinfo] = True
- return res
- for name, classinfo in self.classes.items():
- process_isalgorithm(classinfo)
- # step 2: generate code for the classes and their methods
- classlist = list(self.classes.items())
- classlist.sort()
- for name, classinfo in classlist:
- self.code_types.write("//{}\n".format(80*"="))
- self.code_types.write("// {} ({})\n".format(name, 'Map' if classinfo.ismap else 'Generic'))
- self.code_types.write("//{}\n".format(80*"="))
- self.code_types.write(classinfo.gen_code(self))
- if classinfo.ismap:
- self.code_types.write(gen_template_map_type_cvt.substitute(name=classinfo.name, cname=classinfo.cname))
- else:
- mappable_code = "\n".join([
- gen_template_mappable.substitute(cname=classinfo.cname, mappable=mappable)
- for mappable in classinfo.mappables])
- code = gen_template_type_decl.substitute(
- name=classinfo.name,
- cname=classinfo.cname if classinfo.issimple else "Ptr<{}>".format(classinfo.cname),
- mappable_code=mappable_code
- )
- self.code_types.write(code)
- # register classes in the same order as they have been declared.
- # this way, base classes will be registered in Python before their derivatives.
- classlist1 = [(classinfo.decl_idx, name, classinfo) for name, classinfo in classlist]
- classlist1.sort()
- for decl_idx, name, classinfo in classlist1:
- if classinfo.ismap:
- continue
- self.code_type_publish.write(classinfo.gen_def(self))
- # step 3: generate the code for all the global functions
- for ns_name, ns in sorted(self.namespaces.items()):
- if ns_name.split('.')[0] != 'cv':
- continue
- for name, func in sorted(ns.funcs.items()):
- if func.isconstructor:
- continue
- code = func.gen_code(self)
- self.code_funcs.write(code)
- self.gen_namespace(ns_name)
- self.code_ns_init.write('CVPY_MODULE("{}", {});\n'.format(ns_name[2:], normalize_class_name(ns_name)))
- # step 4: generate the code for enum types
- enumlist = list(self.enums.values())
- enumlist.sort()
- for name in enumlist:
- self.gen_enum_reg(name)
- # step 5: generate the code for constants
- constlist = list(self.consts.items())
- constlist.sort()
- for name, constinfo in constlist:
- self.gen_const_reg(constinfo)
- # That's it. Now save all the files
- self.save(output_path, "pyopencv_generated_include.h", self.code_include)
- self.save(output_path, "pyopencv_generated_funcs.h", self.code_funcs)
- self.save(output_path, "pyopencv_generated_enums.h", self.code_enums)
- self.save(output_path, "pyopencv_generated_types.h", self.code_type_publish)
- self.save(output_path, "pyopencv_generated_types_content.h", self.code_types)
- self.save(output_path, "pyopencv_generated_modules.h", self.code_ns_init)
- self.save(output_path, "pyopencv_generated_modules_content.h", self.code_ns_reg)
- self.save_json(output_path, "pyopencv_signatures.json", self.py_signatures)
- if __name__ == "__main__":
- srcfiles = hdr_parser.opencv_hdr_list
- dstdir = "/Users/vp/tmp"
- if len(sys.argv) > 1:
- dstdir = sys.argv[1]
- if len(sys.argv) > 2:
- with open(sys.argv[2], 'r') as f:
- srcfiles = [l.strip() for l in f.readlines()]
- generator = PythonWrapperGenerator()
- generator.gen(srcfiles, dstdir)
|