From 93bad514a80376f59e3330c0aebf607acb9e8eac Mon Sep 17 00:00:00 2001 From: Hamdi Sahloul Date: Sun, 19 Aug 2018 02:25:14 +0900 Subject: [PATCH 1/5] Easy binding for python code generator --- modules/python/src2/cv2.cpp | 119 +++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index 03fd912104..bf59d40a4f 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -27,6 +27,80 @@ # define CV_PYTHON_TYPE_HEAD_INIT() PyObject_HEAD_INIT(&PyType_Type) 0, #endif +#define CV_PY_TO_CLASS(TYPE) \ +template<> bool pyopencv_to(PyObject* dst, Ptr& src, const char* name); \ + \ +template<> \ +bool pyopencv_to(PyObject* dst, TYPE& src, const char* name) \ +{ \ + if (!dst || dst == Py_None) \ + return true; \ + Ptr ptr; \ + \ + if (!pyopencv_to(dst, ptr, name)) return false; \ + src = *ptr; \ + return true; \ +} + +#define CV_PY_FROM_CLASS(TYPE) \ +template<> PyObject* pyopencv_from(const Ptr& src); \ + \ +template<> \ +PyObject* pyopencv_from(const TYPE& src) \ +{ \ + Ptr ptr(new TYPE()); \ + \ + *ptr = src; \ + return pyopencv_from(ptr); \ +} + +#define CV_PY_TO_CLASS_PTR(TYPE) \ +template<> bool pyopencv_to(PyObject* dst, Ptr& src, const char* name); \ + \ +template<> \ +bool pyopencv_to(PyObject* dst, TYPE*& src, const char* name) \ +{ \ + if (!dst || dst == Py_None) \ + return true; \ + Ptr ptr; \ + \ + if (!pyopencv_to(dst, ptr, name)) return false; \ + src = ptr; \ + return true; \ +} + +#define CV_PY_FROM_CLASS_PTR(TYPE) \ +template<> PyObject* pyopencv_from(const Ptr& src); \ + \ +static PyObject* pyopencv_from(TYPE*& src) \ +{ \ + return pyopencv_from(Ptr(src)); \ +} + +#define CV_PY_TO_ENUM(TYPE) \ +template<> bool pyopencv_to(PyObject* dst, std::underlying_type::type& src, const char* name); \ + \ +template<> \ +bool pyopencv_to(PyObject* dst, TYPE& src, const char* name) \ +{ \ + if (!dst || dst == Py_None) \ + return true; \ + std::underlying_type::type underlying; \ + \ + if (!pyopencv_to(dst, underlying, name)) return false; \ + src = static_cast(underlying); \ + return true; \ +} + +#define CV_PY_FROM_ENUM(TYPE) \ +template<> PyObject* pyopencv_from(const std::underlying_type::type& src); \ + \ +template<> \ +PyObject* pyopencv_from(const TYPE& src) \ +{ \ + return pyopencv_from(static_cast::type>(src)); \ +} + #include "pyopencv_generated_include.h" #include "opencv2/core/types_c.h" @@ -735,12 +809,31 @@ bool pyopencv_to(PyObject* o, UMat& um, const char* name) } template<> -PyObject* pyopencv_from(const UMat& m) { +PyObject* pyopencv_from(const UMat& m) +{ PyObject *o = PyObject_CallObject((PyObject *) &cv2_UMatWrapperType, NULL); *((cv2_UMatWrapperObject *) o)->um = m; return o; } +template<> +bool pyopencv_to(PyObject* obj, void*& ptr, const char* name) +{ + (void)name; + if (!obj || obj == Py_None) + return true; + + if (!PyLong_Check(obj)) + return false; + ptr = PyLong_AsVoidPtr(obj); + return ptr != NULL && !PyErr_Occurred(); +} + +static PyObject* pyopencv_from(void*& ptr) +{ + return PyLong_FromVoidPtr(ptr); +} + static bool pyopencv_to(PyObject *o, Scalar& s, const ArgInfo info) { if(!o || o == Py_None) @@ -843,6 +936,30 @@ bool pyopencv_to(PyObject* obj, int& value, const char* name) return value != -1 || !PyErr_Occurred(); } +#if defined (_M_AMD64) || defined (__x86_64__) +template<> +PyObject* pyopencv_from(const unsigned int& value) +{ + return PyLong_FromUnsignedLong(value); +} + +template<> + +bool pyopencv_to(PyObject* obj, unsigned int& value, const char* name) +{ + (void)name; + if(!obj || obj == Py_None) + return true; + if(PyInt_Check(obj)) + value = (unsigned int)PyInt_AsLong(obj); + else if(PyLong_Check(obj)) + value = (unsigned int)PyLong_AsLong(obj); + else + return false; + return value != (unsigned int)-1 || !PyErr_Occurred(); +} +#endif + template<> PyObject* pyopencv_from(const uchar& value) { From 900df21b7db00b6d7a6ecb88ded54b9c66d00dff Mon Sep 17 00:00:00 2001 From: Hamdi Sahloul Date: Mon, 20 Aug 2018 23:35:41 +0900 Subject: [PATCH 2/5] Support enum-type detection for binding generator --- modules/java/generator/gen_java.py | 8 ++++++++ modules/python/src2/gen2.py | 11 +++++++++++ modules/python/src2/hdr_parser.py | 14 +++++++------- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/modules/java/generator/gen_java.py b/modules/java/generator/gen_java.py index d3a4664d38..5de9b5df13 100755 --- a/modules/java/generator/gen_java.py +++ b/modules/java/generator/gen_java.py @@ -341,6 +341,7 @@ class JavaWrapperGenerator(object): self.classes = { "Mat" : ClassInfo([ 'class Mat', '', [], [] ], self.namespaces) } self.module = "" self.Module = "" + self.enum_types = [] self.ported_func_list = [] self.skipped_func_list = [] self.def_args_hist = {} # { def_args_cnt : funcs_cnt } @@ -421,6 +422,10 @@ class JavaWrapperGenerator(object): ci.addConst(constinfo) logging.info('ok: %s', constinfo) + def add_enum(self, decl): # [ "enum cname", "", [], [] ] + enumname = decl[0].replace("enum ", "").strip() + self.enum_types.append(enumname) + def add_func(self, decl): fi = FuncInfo(decl, namespaces=self.namespaces) classname = fi.classname or self.Module @@ -479,6 +484,9 @@ class JavaWrapperGenerator(object): self.add_class(decl) elif name.startswith("const"): self.add_const(decl) + elif name.startswith("enum"): + # enum + self.add_enum(decl) else: # function self.add_func(decl) diff --git a/modules/python/src2/gen2.py b/modules/python/src2/gen2.py index 24ffafadd6..5aef0b5c9c 100755 --- a/modules/python/src2/gen2.py +++ b/modules/python/src2/gen2.py @@ -664,6 +664,9 @@ class FuncInfo(object): 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.enum_types 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 @@ -835,6 +838,7 @@ class PythonWrapperGenerator(object): self.classes = {} self.namespaces = {} self.consts = {} + self.enum_types = [] self.code_include = StringIO() self.code_types = StringIO() self.code_funcs = StringIO() @@ -892,6 +896,10 @@ 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): + enumname = normalize_class_name(name) + self.enum_types.append(enumname) + def add_func(self, decl): namespace, classes, barename = self.split_decl_name(decl[0]) cname = "::".join(namespace+classes+[barename]) @@ -996,6 +1004,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.replace("enum ", "").strip(), decl) else: # function self.add_func(decl) diff --git a/modules/python/src2/hdr_parser.py b/modules/python/src2/hdr_parser.py index c5cc3c0946..254f3d5a4f 100755 --- a/modules/python/src2/hdr_parser.py +++ b/modules/python/src2/hdr_parser.py @@ -705,20 +705,19 @@ class CppHeaderParser(object): decl[1] = ": " + ", ".join([self.get_dotted_name(b).replace(".","::") for b in bases]) return stmt_type, classname, True, decl - if stmt.startswith("enum"): - return "enum", "", True, None - - if stmt.startswith("namespace"): + if stmt.startswith("enum") or stmt.startswith("namespace"): stmt_list = stmt.split() if len(stmt_list) < 2: stmt_list.append("") return stmt_list[0], stmt_list[1], True, None + if stmt.startswith("extern") and "\"C\"" in stmt: return "namespace", "", True, None if end_token == "}" and context == "enum": decl = self.parse_enum(stmt) - return "enum", "", False, decl + name = stack_top[self.BLOCK_NAME] + return "enum", name, False, decl if end_token == ";" and stmt.startswith("typedef"): # TODO: handle typedef's more intelligently @@ -896,8 +895,9 @@ class CppHeaderParser(object): stmt_type, name, parse_flag, decl = self.parse_stmt(stmt, token, docstring=docstring) if decl: if stmt_type == "enum": - for d in decl: - decls.append(d) + if name != "": + decls.append(["enum " + self.get_dotted_name(name), "", [], [], None, ""]) + decls.extend(decl) else: decls.append(decl) From f1ca05c822af606f9028b0bec2842e3ef8dc7530 Mon Sep 17 00:00:00 2001 From: Hamdi Sahloul Date: Tue, 21 Aug 2018 12:41:07 +0900 Subject: [PATCH 3/5] Extend python exception `cv.error` to provide `file`, `func`, `line`, `code`, `msg`, and `err` attributes --- modules/python/src2/cv2.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index bf59d40a4f..a16292e7d9 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -110,7 +110,7 @@ PyObject* pyopencv_from(const TYPE& src) #include -static PyObject* opencv_error = 0; +static PyObject* opencv_error = NULL; static int failmsg(const char *fmt, ...) { @@ -171,6 +171,12 @@ try \ } \ catch (const cv::Exception &e) \ { \ + PyObject_SetAttrString(opencv_error, "file", PyString_FromString(e.file.c_str())); \ + PyObject_SetAttrString(opencv_error, "func", PyString_FromString(e.func.c_str())); \ + PyObject_SetAttrString(opencv_error, "line", PyInt_FromLong(e.line)); \ + PyObject_SetAttrString(opencv_error, "code", PyInt_FromLong(e.code)); \ + PyObject_SetAttrString(opencv_error, "msg", PyString_FromString(e.msg.c_str())); \ + PyObject_SetAttrString(opencv_error, "err", PyString_FromString(e.err.c_str())); \ PyErr_SetString(opencv_error, e.what()); \ return 0; \ } @@ -2030,7 +2036,15 @@ void initcv2() PyDict_SetItemString(d, "__version__", PyString_FromString(CV_VERSION)); - opencv_error = PyErr_NewException((char*)MODULESTR".error", NULL, NULL); + PyObject *opencv_error_dict = PyDict_New(); + PyDict_SetItemString(opencv_error_dict, "file", Py_None); + PyDict_SetItemString(opencv_error_dict, "func", Py_None); + PyDict_SetItemString(opencv_error_dict, "line", Py_None); + PyDict_SetItemString(opencv_error_dict, "code", Py_None); + PyDict_SetItemString(opencv_error_dict, "msg", Py_None); + PyDict_SetItemString(opencv_error_dict, "err", Py_None); + opencv_error = PyErr_NewException((char*)MODULESTR".error", NULL, opencv_error_dict); + Py_DECREF(opencv_error_dict); PyDict_SetItemString(d, "error", opencv_error); //Registering UMatWrapper python class in cv2 module: From b5eb65e53e9b5ee00217a0ebf6eca248366c86e8 Mon Sep 17 00:00:00 2001 From: Hamdi Sahloul Date: Sun, 19 Aug 2018 02:34:47 +0900 Subject: [PATCH 4/5] 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 From 64380baa85b9ac1ab3718b139dd2787156df884d Mon Sep 17 00:00:00 2001 From: Hamdi Sahloul Date: Sat, 25 Aug 2018 01:57:35 +0900 Subject: [PATCH 5/5] Documentation for the new bindings-generator features --- doc/Doxyfile.in | 3 + .../py_bindings_basics.markdown | 79 +++++++++++++------ 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 1ac09c4351..ff6c18e34d 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -241,6 +241,9 @@ PREDEFINED = __cplusplus=1 \ CV_PROP_RW= \ CV_WRAP= \ CV_WRAP_AS(x)= \ + CV_WRAP_MAPPABLE(x)= \ + CV_WRAP_PHANTOM(x)= \ + CV_WRAP_DEFAULT(x)= \ CV_CDECL= \ CV_Func = \ CV_DO_PRAGMA(x)= \ diff --git a/doc/py_tutorials/py_bindings/py_bindings_basics/py_bindings_basics.markdown b/doc/py_tutorials/py_bindings/py_bindings_basics/py_bindings_basics.markdown index 4f48ae7799..8b10f27047 100644 --- a/doc/py_tutorials/py_bindings/py_bindings_basics/py_bindings_basics.markdown +++ b/doc/py_tutorials/py_bindings/py_bindings_basics/py_bindings_basics.markdown @@ -20,20 +20,20 @@ A simple example on extending C++ functions to Python can be found in official P documentation[1]. So extending all functions in OpenCV to Python by writing their wrapper functions manually is a time-consuming task. So OpenCV does it in a more intelligent way. OpenCV generates these wrapper functions automatically from the C++ headers using some Python scripts which are -located in modules/python/src2. We will look into what they do. +located in `modules/python/src2`. We will look into what they do. -First, modules/python/CMakeFiles.txt is a CMake script which checks the modules to be extended to +First, `modules/python/CMakeFiles.txt` is a CMake script which checks the modules to be extended to Python. It will automatically check all the modules to be extended and grab their header files. These header files contain list of all classes, functions, constants etc. for that particular modules. -Second, these header files are passed to a Python script, modules/python/src2/gen2.py. This is the -Python bindings generator script. It calls another Python script modules/python/src2/hdr_parser.py. +Second, these header files are passed to a Python script, `modules/python/src2/gen2.py`. This is the +Python bindings generator script. It calls another Python script `modules/python/src2/hdr_parser.py`. This is the header parser script. This header parser splits the complete header file into small Python lists. So these lists contain all details about a particular function, class etc. For example, a function will be parsed to get a list containing function name, return type, input -arguments, argument types etc. Final list contains details of all the functions, structs, classes -etc. in that header file. +arguments, argument types etc. Final list contains details of all the functions, enums, structs, +classes etc. in that header file. But header parser doesn't parse all the functions/classes in the header file. The developer has to specify which functions should be exported to Python. For that, there are certain macros added to @@ -44,15 +44,15 @@ macros will be given in next session. So header parser returns a final big list of parsed functions. Our generator script (gen2.py) will create wrapper functions for all the functions/classes/enums/structs parsed by header parser (You -can find these header files during compilation in the build/modules/python/ folder as +can find these header files during compilation in the `build/modules/python/` folder as pyopencv_generated_\*.h files). But there may be some basic OpenCV datatypes like Mat, Vec4i, Size. They need to be extended manually. For example, a Mat type should be extended to Numpy array, Size should be extended to a tuple of two integers etc. Similarly, there may be some complex structs/classes/functions etc. which need to be extended manually. All such manual wrapper functions -are placed in modules/python/src2/cv2.cpp. +are placed in `modules/python/src2/cv2.cpp`. So now only thing left is the compilation of these wrapper files which gives us **cv2** module. So -when you call a function, say res = equalizeHist(img1,img2) in Python, you pass two numpy arrays and +when you call a function, say `res = equalizeHist(img1,img2)` in Python, you pass two numpy arrays and you expect another numpy array as the output. So these numpy arrays are converted to cv::Mat and then calls the equalizeHist() function in C++. Final result, res will be converted back into a Numpy array. So in short, almost all operations are done in C++ which gives us almost same speed as that @@ -67,19 +67,19 @@ Header parser parse the header files based on some wrapper macros added to funct Enumeration constants don't need any wrapper macros. They are automatically wrapped. But remaining functions, classes etc. need wrapper macros. -Functions are extended using CV_EXPORTS_W macro. An example is shown below. +Functions are extended using `CV_EXPORTS_W` macro. An example is shown below. @code{.cpp} CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst ); @endcode Header parser can understand the input and output arguments from keywords like InputArray, OutputArray etc. But sometimes, we may need to hardcode inputs and outputs. For that, -macros like CV_OUT, CV_IN_OUT etc. are used. +macros like `CV_OUT`, `CV_IN_OUT` etc. are used. @code{.cpp} CV_EXPORTS_W void minEnclosingCircle( InputArray points, CV_OUT Point2f& center, CV_OUT float& radius ); @endcode -For large classes also, CV_EXPORTS_W is used. To extend class methods, CV_WRAP is used. -Similarly, CV_PROP is used for class fields. +For large classes also, `CV_EXPORTS_W` is used. To extend class methods, `CV_WRAP` is used. +Similarly, `CV_PROP` is used for class fields. @code{.cpp} class CV_EXPORTS_W CLAHE : public Algorithm { @@ -90,9 +90,9 @@ public: CV_WRAP virtual double getClipLimit() const = 0; } @endcode -Overloaded functions can be extended using CV_EXPORTS_AS. But we need to pass a new name so that +Overloaded functions can be extended using `CV_EXPORTS_AS`. But we need to pass a new name so that each function will be called by that name in Python. Take the case of integral function below. Three -functions are available, so each one is named with a suffix in Python. Similarly CV_WRAP_AS can be +functions are available, so each one is named with a suffix in Python. Similarly `CV_WRAP_AS` can be used to wrap overloaded methods. @code{.cpp} //! computes the integral image @@ -107,9 +107,9 @@ CV_EXPORTS_AS(integral3) void integral( InputArray src, OutputArray sum, OutputArray sqsum, OutputArray tilted, int sdepth = -1, int sqdepth = -1 ); @endcode -Small classes/structs are extended using CV_EXPORTS_W_SIMPLE. These structs are passed by value -to C++ functions. Examples are KeyPoint, Match etc. Their methods are extended by CV_WRAP and -fields are extended by CV_PROP_RW. +Small classes/structs are extended using `CV_EXPORTS_W_SIMPLE`. These structs are passed by value +to C++ functions. Examples are `KeyPoint`, `Match` etc. Their methods are extended by `CV_WRAP` and +fields are extended by `CV_PROP_RW`. @code{.cpp} class CV_EXPORTS_W_SIMPLE DMatch { @@ -125,8 +125,8 @@ public: CV_PROP_RW float distance; }; @endcode -Some other small classes/structs can be exported using CV_EXPORTS_W_MAP where it is exported to a -Python native dictionary. Moments() is an example of it. +Some other small classes/structs can be exported using `CV_EXPORTS_W_MAP` where it is exported to a +Python native dictionary. `Moments()` is an example of it. @code{.cpp} class CV_EXPORTS_W_MAP Moments { @@ -142,6 +142,41 @@ public: So these are the major extension macros available in OpenCV. Typically, a developer has to put proper macros in their appropriate positions. Rest is done by generator scripts. Sometimes, there may be an exceptional cases where generator scripts cannot create the wrappers. Such functions need -to be handled manually, to do this write your own pyopencv_*.hpp extending headers and put them into +to be handled manually, to do this write your own `pyopencv_*.hpp` extending headers and put them into misc/python subdirectory of your module. But most of the time, a code written according to OpenCV -coding guidelines will be automatically wrapped by generator scripts. \ No newline at end of file +coding guidelines will be automatically wrapped by generator scripts. + +More advanced cases involves providing Python with additional features that does not exist +in the C++ interface such as extra methods, type mappings, or to provide default arguments. +We will take `UMat` datatype as an example of such cases later on. +First, to provide Python-specific methods, `CV_WRAP_PHANTOM` is utilized in a similar manner to +`CV_WRAP`, except that it takes the method header as its argument, and you would need to provide +the method body in your own `pyopencv_*.hpp` extension. `UMat::queue()` and `UMat::context()` are +an example of such phantom methods that does not exist in C++ interface, but are needed to handle +OpenCL functionalities at the Python side. +Second, if an already-existing datatype(s) is mappable to your class, it is highly preferable to +indicate such capacity using `CV_WRAP_MAPPABLE` with the source type as its argument, +rather than crafting your own binding function(s). This is the case of `UMat` which maps from `Mat`. +Finally, if a default argument is needed, but it is not provided in the native C++ interface, +you can provide it for Python side as the argument of `CV_WRAP_DEFAULT`. As per the `UMat::getMat` +example below: +@code{.cpp} +class CV_EXPORTS_W UMat +{ +public: + //! Mat is mappable to UMat. + // You would need to provide `static bool cv_mappable_to(const Ptr& src, Ptr& dst)` + CV_WRAP_MAPPABLE(Ptr); + + /! returns the OpenCL queue used by OpenCV UMat. + // You would need to provide the method body in the binder code + CV_WRAP_PHANTOM(static void* queue()); + + //! returns the OpenCL context used by OpenCV UMat + // You would need to provide the method body in the binder code + CV_WRAP_PHANTOM(static void* context()); + + //! The wrapped method become equvalent to `get(int flags = ACCESS_RW)` + CV_WRAP_AS(get) Mat getMat(int flags CV_WRAP_DEFAULT(ACCESS_RW)) const; +}; +@endcode