bindings: backport generator from OpenCV 4.x

- better handling of enum arguments
- less merge conflicts
This commit is contained in:
Alexander Alekhin
2019-03-01 18:45:33 +03:00
committed by Alexander Alekhin
parent d034ef6f27
commit f5b58e5fc9
9 changed files with 519 additions and 443 deletions
+155 -68
View File
@@ -10,19 +10,23 @@ if sys.version_info[0] >= 3:
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* _self_ = NULL;
if(PyObject_TypeCheck(self, &pyopencv_${name}_Type))
_self_ = ${amp}((pyopencv_${name}_t*)self)->v${get};
if (_self_ == NULL)
if (!_self_)
return failmsgp("Incorrect type of self (must be '${name}' or its derivative)");
""")
gen_template_check_self_algo = Template(""" $cname* _self_ = NULL;
if(PyObject_TypeCheck(self, &pyopencv_${name}_Type))
_self_ = dynamic_cast<$cname*>(${amp}((pyopencv_${name}_t*)self)->v.get());
if (_self_ == NULL)
if (!_self_)
return failmsgp("Incorrect type of self (must be '${name}' or its derivative)");
""")
@@ -68,27 +72,40 @@ static void pyopencv_${name}_dealloc(PyObject* self)
PyObject_Del(self);
}
template<> PyObject* pyopencv_from(const ${cname}& r)
template<>
struct PyOpenCV_Converter< ${cname} >
{
pyopencv_${name}_t *m = PyObject_NEW(pyopencv_${name}_t, &pyopencv_${name}_Type);
new (&m->v) ${cname}(r); //Copy constructor
return (PyObject*)m;
}
template<> bool pyopencv_to(PyObject* src, ${cname}& dst, const char* name)
{
if( src == NULL || src == Py_None )
return true;
if(!PyObject_TypeCheck(src, &pyopencv_${name}_Type))
static PyObject* from(const ${cname}& r)
{
pyopencv_${name}_t *m = PyObject_NEW(pyopencv_${name}_t, &pyopencv_${name}_Type);
new (&m->v) ${cname}(r); //Copy constructor
return (PyObject*)m;
}
static bool to(PyObject* src, ${cname}& dst, const char* name)
{
if(!src || src == Py_None)
return true;
if(PyObject_TypeCheck(src, &pyopencv_${name}_Type))
{
dst = ((pyopencv_${name}_t*)src)->v;
return true;
}
failmsg("Expected ${cname} for argument '%%s'", name);
return false;
}
dst = ((pyopencv_${name}_t*)src)->v;
return true;
}
};
""" % head_init_str)
gen_template_mappable = Template("""
{
${mappable} _src;
if (pyopencv_to(src, _src, name))
{
return cv_mappable_to(_src, dst);
}
}
""")
gen_template_type_decl = Template("""
struct pyopencv_${name}_t
@@ -110,26 +127,31 @@ static void pyopencv_${name}_dealloc(PyObject* self)
PyObject_Del(self);
}
template<> PyObject* pyopencv_from(const Ptr<${cname}>& r)
template<>
struct PyOpenCV_Converter< Ptr<${cname}> >
{
pyopencv_${name}_t *m = PyObject_NEW(pyopencv_${name}_t, &pyopencv_${name}_Type);
new (&(m->v)) Ptr<$cname1>(); // init Ptr with placement new
m->v = r;
return (PyObject*)m;
}
template<> bool pyopencv_to(PyObject* src, Ptr<${cname}>& dst, const char* name)
{
if( src == NULL || src == Py_None )
return true;
if(!PyObject_TypeCheck(src, &pyopencv_${name}_Type))
static PyObject* from(const Ptr<${cname}>& r)
{
pyopencv_${name}_t *m = PyObject_NEW(pyopencv_${name}_t, &pyopencv_${name}_Type);
new (&(m->v)) Ptr<$cname1>(); // init Ptr with placement new
m->v = r;
return (PyObject*)m;
}
static bool to(PyObject* src, Ptr<${cname}>& dst, const char* name)
{
if(!src || src == Py_None)
return true;
if(PyObject_TypeCheck(src, &pyopencv_${name}_Type))
{
dst = ((pyopencv_${name}_t*)src)->v.dynamicCast<${cname}>();
return true;
}
${mappable_code}
failmsg("Expected ${cname} for argument '%%s'", name);
return false;
}
dst = ((pyopencv_${name}_t*)src)->v.dynamicCast<${cname}>();
return true;
}
};
""" % head_init_str)
@@ -192,7 +214,7 @@ 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_ == NULL)
if (!_self_)
return failmsgp("Incorrect type of object (must be '${name}' or its derivative)");
return pyopencv_from(_self_${access}${member});
}
@@ -201,7 +223,7 @@ static PyObject* pyopencv_${name}_get_${member}(pyopencv_${name}_t* p, void *clo
gen_template_set_prop = Template("""
static int pyopencv_${name}_set_${member}(pyopencv_${name}_t* p, PyObject *value, void *closure)
{
if (value == NULL)
if (!value)
{
PyErr_SetString(PyExc_TypeError, "Cannot delete the ${member} attribute");
return -1;
@@ -213,13 +235,13 @@ static int pyopencv_${name}_set_${member}(pyopencv_${name}_t* p, PyObject *value
gen_template_set_prop_algo = Template("""
static int pyopencv_${name}_set_${member}(pyopencv_${name}_t* p, PyObject *value, void *closure)
{
if (value == NULL)
if (!value)
{
PyErr_SetString(PyExc_TypeError, "Cannot delete the ${member} attribute");
return -1;
}
$cname* _self_ = dynamic_cast<$cname*>(p->v.get());
if (_self_ == NULL)
if (!_self_)
{
failmsgp("Incorrect type of object (must be '${name}' or its derivative)");
return -1;
@@ -265,6 +287,7 @@ class ClassInfo(object):
self.isalgorithm = False
self.methods = {}
self.props = []
self.mappables = []
self.consts = {}
self.base = None
self.constructor = None
@@ -402,7 +425,7 @@ class ArgInfo(object):
self.py_outputarg = False
def isbig(self):
return self.tp == "Mat" or self.tp == "vector_Mat"\
return self.tp == "Mat" or self.tp == "vector_Mat" or self.tp == "cuda::GpuMat"\
or self.tp == "UMat" or self.tp == "vector_UMat" # or self.tp.startswith("vector")
def crepr(self):
@@ -410,10 +433,11 @@ class ArgInfo(object):
class FuncVariant(object):
def __init__(self, classname, name, decl, isconstructor):
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]
@@ -461,6 +485,7 @@ class FuncVariant(object):
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:
@@ -520,17 +545,17 @@ class FuncVariant(object):
class FuncInfo(object):
def __init__(self, classname, name, cname, isconstructor, namespace, isclassmethod):
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.isclassmethod = isclassmethod
self.is_static = is_static
self.variants = []
def add_variant(self, decl):
self.variants.append(FuncVariant(self.classname, self.name, decl, self.isconstructor))
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
@@ -541,8 +566,8 @@ class FuncInfo(object):
else:
classname = ""
if self.isclassmethod:
name += "_cls"
if self.is_static:
name += "_static"
return "pyopencv_" + self.namespace.replace('.','_') + '_' + classname + name
@@ -601,7 +626,7 @@ class FuncInfo(object):
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_CLASS' if self.isclassmethod else '0', py_docstring = full_docstring)
flags = 'METH_STATIC' if self.is_static else '0', py_docstring = full_docstring)
def gen_code(self, codegen):
all_classes = codegen.classes
@@ -618,7 +643,7 @@ class FuncInfo(object):
selfinfo = all_classes[self.classname]
if not self.isconstructor:
amp = "&" if selfinfo.issimple else ""
if self.isclassmethod:
if self.is_static:
pass
elif selfinfo.isalgorithm:
code += gen_template_check_self_algo.substitute(name=selfinfo.name, cname=selfinfo.cname, amp=amp)
@@ -638,6 +663,9 @@ class FuncInfo(object):
all_cargs = []
parse_arglist = []
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,
@@ -646,7 +674,7 @@ class FuncInfo(object):
if a.tp in ignored_arg_types:
defval = a.defval
if not defval and a.tp.endswith("*"):
defval = 0
defval = "0"
assert defval
if not code_args.endswith("("):
code_args += ", "
@@ -656,15 +684,15 @@ class FuncInfo(object):
tp1 = tp = a.tp
amp = ""
defval0 = ""
if tp.endswith("*"):
if tp in pass_by_val_types:
tp = tp1 = tp[:-1]
amp = "&"
if tp.endswith("*"):
defval0 = "0"
tp1 = tp.replace("*", "_ptr")
if tp1.endswith("*"):
print("Error: type with star: a.tp=%s, tp=%s, tp1=%s" % (a.tp, tp, tp1))
sys.exit(-1)
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)
amapping = simple_argtype_mapping.get(tp, (tp, "O", defval0))
parse_name = a.name
@@ -686,6 +714,9 @@ class FuncInfo(object):
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 amapping[1] == "O":
defval = ""
@@ -712,13 +743,15 @@ class FuncInfo(object):
code_prelude = templ_prelude.substitute(name=selfinfo.name, cname=selfinfo.cname)
code_fcall = templ.substitute(name=selfinfo.name, cname=selfinfo.cname, 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 ismethod and not self.isclassmethod:
if not v.isphantom and ismethod and not self.is_static:
code_fcall += "_self_->" + self.cname
else:
code_fcall += self.cname
@@ -754,7 +787,7 @@ class FuncInfo(object):
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(args) == 0 && (kw == NULL || PyObject_Size(kw) == 0))"
code_parse = "if(PyObject_Size(args) == 0 && (!kw || PyObject_Size(kw) == 0))"
if len(v.py_outlist) == 0:
code_ret = "Py_RETURN_NONE"
@@ -799,7 +832,7 @@ class FuncInfo(object):
#if dump: pprint(vars(classinfo))
if self.isconstructor:
py_name = 'cv.' + classinfo.wname
elif self.isclassmethod:
elif self.is_static:
py_name = '.'.join([self.namespace, classinfo.sname + '_' + self.variants[0].wname])
else:
cname = classinfo.cname + '::' + cname
@@ -833,7 +866,9 @@ class PythonWrapperGenerator(object):
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_type_reg = StringIO()
@@ -890,6 +925,18 @@ class PythonWrapperGenerator(object):
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])
@@ -902,35 +949,46 @@ class PythonWrapperGenerator(object):
namespace = '.'.join(namespace)
isconstructor = name == bareclassname
isclassmethod = False
is_static = False
isphantom = False
mappable = None
for m in decl[2]:
if m == "/S":
isclassmethod = True
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 isclassmethod:
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, isclassmethod))
func.add_variant(decl)
func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace, is_static))
func.add_variant(decl, isphantom)
# Add it as global function
g_name = "_".join(classes+[name])
func_map = self.namespaces.setdefault(namespace, Namespace()).funcs
func = func_map.setdefault(g_name, FuncInfo("", g_name, cname, isconstructor, namespace, False))
func.add_variant(decl)
func.add_variant(decl, isphantom)
else:
if classname and not isconstructor:
cname = barename
if not isphantom:
cname = barename
func_map = self.classes[classname].methods
else:
func_map = self.namespaces.setdefault(namespace, Namespace()).funcs
func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace, isclassmethod))
func.add_variant(decl)
func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace, is_static))
func.add_variant(decl, isphantom)
if classname and isconstructor:
self.classes[classname].constructor = func
@@ -949,10 +1007,10 @@ class PythonWrapperGenerator(object):
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", %s},\n'%(name, cname))
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", %s},\n'%(compat_name, cname))
self.code_ns_reg.write(' {"%s", static_cast<long>(%s)},\n'%(compat_name, cname))
self.code_ns_reg.write(' {NULL, 0}\n};\n\n')
def gen_namespaces_reg(self):
@@ -963,6 +1021,21 @@ class PythonWrapperGenerator(object):
self.code_ns_reg.write(' init_submodule(root, MODULESTR"%s", methods_%s, consts_%s);\n' % (ns_name[2:], wname, wname))
self.code_ns_reg.write('};\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 enum {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:
@@ -975,14 +1048,15 @@ class PythonWrapperGenerator(object):
def gen(self, srcfiles, output_path):
self.clear()
self.parser = hdr_parser.CppHeaderParser(generate_umat_decls=True)
self.parser = hdr_parser.CppHeaderParser(generate_umat_decls=True, generate_gpumat_decls=False)
# 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
self.code_include.write( '#include "{0}"\n'.format(hdr[hdr.rindex('opencv2/'):]) )
if hdr.find('opencv2/') >= 0: #Avoid including the shadow files
self.code_include.write( '#include "{0}"\n'.format(hdr[hdr.rindex('opencv2/'):]) )
for decl in decls:
name = decl[0]
if name.startswith("struct") or name.startswith("class"):
@@ -994,6 +1068,9 @@ class PythonWrapperGenerator(object):
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)
@@ -1043,8 +1120,11 @@ class PythonWrapperGenerator(object):
templ = gen_template_simple_type_decl
else:
templ = gen_template_type_decl
mappable_code = "\n".join([
gen_template_mappable.substitute(cname=classinfo.cname, mappable=mappable)
for mappable in classinfo.mappables])
self.code_types.write(templ.substitute(name=name, wname=classinfo.wname, cname=classinfo.cname, sname=classinfo.sname,
cname1=("cv::Algorithm" if classinfo.isalgorithm else classinfo.cname)))
cname1=("cv::Algorithm" if classinfo.isalgorithm else classinfo.cname), mappable_code=mappable_code))
# register classes in the same order as they have been declared.
# this way, base classes will be registered in Python before their derivatives.
@@ -1070,7 +1150,13 @@ class PythonWrapperGenerator(object):
self.gen_namespace(ns_name)
self.gen_namespaces_reg()
# step 4: generate the code for constants
# 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:
@@ -1079,6 +1165,7 @@ class PythonWrapperGenerator(object):
# 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_types)
self.save(output_path, "pyopencv_generated_type_reg.h", self.code_type_reg)
self.save(output_path, "pyopencv_generated_ns_reg.h", self.code_ns_reg)