From b5eb65e53e9b5ee00217a0ebf6eca248366c86e8 Mon Sep 17 00:00:00 2001 From: Hamdi Sahloul Date: Sun, 19 Aug 2018 02:34:47 +0900 Subject: [PATCH] Improve Python binding generator with mappable types and phantom headers --- modules/core/include/opencv2/core/cvdef.h | 3 ++ modules/python/src2/gen2.py | 64 +++++++++++++++++------ modules/python/src2/hdr_parser.py | 18 ++++--- 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index 1b38692dc2..7bcc922275 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -305,6 +305,9 @@ Cv64suf; #define CV_PROP_RW #define CV_WRAP #define CV_WRAP_AS(synonym) +#define CV_WRAP_MAPPABLE(mappable) +#define CV_WRAP_PHANTOM(phantom_header) +#define CV_WRAP_DEFAULT(val) /****************************************************************************************\ * Matrix type (Mat) * diff --git a/modules/python/src2/gen2.py b/modules/python/src2/gen2.py index 5aef0b5c9c..63bf72fa22 100755 --- a/modules/python/src2/gen2.py +++ b/modules/python/src2/gen2.py @@ -81,16 +81,25 @@ template<> bool pyopencv_to(PyObject* src, ${cname}& dst, const char* name) { if(!src || src == Py_None) return true; - if(!PyObject_TypeCheck(src, &pyopencv_${name}_Type)) + if(PyObject_TypeCheck(src, &pyopencv_${name}_Type)) { - failmsg("Expected ${cname} for argument '%%s'", name); - return false; + dst = ((pyopencv_${name}_t*)src)->v; + return true; } - dst = ((pyopencv_${name}_t*)src)->v; - return true; + failmsg("Expected ${cname} for argument '%%s'", name); + return false; } """ % 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 @@ -124,13 +133,14 @@ template<> bool pyopencv_to(PyObject* src, Ptr<${cname}>& dst, const char* name) { if(!src || src == Py_None) return true; - if(!PyObject_TypeCheck(src, &pyopencv_${name}_Type)) + if(PyObject_TypeCheck(src, &pyopencv_${name}_Type)) { - failmsg("Expected ${cname} for argument '%%s'", name); - return false; + dst = ((pyopencv_${name}_t*)src)->v.dynamicCast<${cname}>(); + return true; } - dst = ((pyopencv_${name}_t*)src)->v.dynamicCast<${cname}>(); - return true; + ${mappable_code} + failmsg("Expected ${cname} for argument '%%s'", name); + return false; } """ % head_init_str) @@ -267,6 +277,7 @@ class ClassInfo(object): self.isalgorithm = False self.methods = {} self.props = [] + self.mappables = [] self.consts = {} self.base = None self.constructor = None @@ -412,10 +423,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] @@ -531,8 +543,8 @@ class FuncInfo(object): self.isclassmethod = isclassmethod 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 @@ -640,6 +652,9 @@ class FuncInfo(object): all_cargs = [] parse_arglist = [] + if v.isphantom and ismethod and not self.isclassmethod: + code_args += "_self_" + # declare all the C function arguments, # add necessary conversions from Python objects to code_cvt_list, # form the function/method call, @@ -717,6 +732,8 @@ 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 = "" @@ -913,11 +930,21 @@ class PythonWrapperGenerator(object): isconstructor = name == bareclassname isclassmethod = False + isphantom = False + mappable = None for m in decl[2]: if m == "/S": isclassmethod = 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]) @@ -925,13 +952,13 @@ class PythonWrapperGenerator(object): # 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.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 @@ -940,7 +967,7 @@ class PythonWrapperGenerator(object): 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.add_variant(decl, isphantom) if classname and isconstructor: self.classes[classname].constructor = func @@ -1056,8 +1083,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. diff --git a/modules/python/src2/hdr_parser.py b/modules/python/src2/hdr_parser.py index 254f3d5a4f..9fdde15ba1 100755 --- a/modules/python/src2/hdr_parser.py +++ b/modules/python/src2/hdr_parser.py @@ -6,6 +6,7 @@ import os, sys, re, string, io # the list only for debugging. The real list, used in the real OpenCV build, is specified in CMakeLists.txt opencv_hdr_list = [ "../../core/include/opencv2/core.hpp", +"../../core/include/opencv2/core/mat.hpp", "../../core/include/opencv2/core/ocl.hpp", "../../flann/include/opencv2/flann/miniflann.hpp", "../../ml/include/opencv2/ml.hpp", @@ -376,8 +377,6 @@ class CppHeaderParser(object): decl[2].append("/A") if bool(re.match(r".*\)\s*const(\s*=\s*0)?", decl_str)): decl[2].append("/C") - if "virtual" in decl_str: - print(decl_str) return decl def parse_func_decl(self, decl_str, mat="Mat", docstring=""): @@ -393,8 +392,7 @@ class CppHeaderParser(object): """ if self.wrap_mode: - if not (("CV_EXPORTS_AS" in decl_str) or ("CV_EXPORTS_W" in decl_str) or \ - ("CV_WRAP" in decl_str) or ("CV_WRAP_AS" in decl_str)): + if not (("CV_EXPORTS_AS" in decl_str) or ("CV_EXPORTS_W" in decl_str) or ("CV_WRAP" in decl_str)): return [] # ignore old API in the documentation check (for now) @@ -414,6 +412,16 @@ class CppHeaderParser(object): arg, npos3 = self.get_macro_arg(decl_str, npos) func_modlist.append("="+arg) decl_str = decl_str[:npos] + decl_str[npos3+1:] + npos = decl_str.find("CV_WRAP_PHANTOM") + if npos >= 0: + decl_str, _ = self.get_macro_arg(decl_str, npos) + func_modlist.append("/phantom") + npos = decl_str.find("CV_WRAP_MAPPABLE") + if npos >= 0: + mappable, npos3 = self.get_macro_arg(decl_str, npos) + func_modlist.append("/mappable="+mappable) + classname = top[1] + return ['.'.join([classname, classname]), None, func_modlist, [], None, None] virtual_method = False pure_virtual_method = False @@ -527,8 +535,6 @@ class CppHeaderParser(object): t, npos = self.find_next_token(decl_str, ["(", ")", ",", "<", ">"], npos) if not t: print("Error: no closing ')' at %d" % (self.lineno,)) - print(decl_str) - print(decl_str[arg_start:]) sys.exit(-1) if t == "<": angle_balance += 1