From c708f506a48f51c1520b80fb63bbc0026f220ea9 Mon Sep 17 00:00:00 2001 From: Anatoliy Talamanov Date: Wed, 29 Jul 2020 16:18:52 +0300 Subject: [PATCH] Merge pull request #17493 from TolyaTalamanov:at/python-bindings-gapi * Implement G-API python bindings * Fix hdr_parser * Drop initlization with brackets using regexp * Handle bracket initilization another way * Add test for core operations * Declaration and definition of View constructor now in different files * Refactor tests * Remove combine decorator from tests * Fix comment to review * Fix test * Fix comments to review * Remove GCompilerArgs implementation from python Co-authored-by: Pinaev --- modules/gapi/CMakeLists.txt | 2 + modules/gapi/include/opencv2/gapi/core.hpp | 2 +- .../gapi/include/opencv2/gapi/cpu/core.hpp | 2 +- .../gapi/include/opencv2/gapi/fluid/core.hpp | 2 +- .../opencv2/gapi/fluid/gfluidbuffer.hpp | 3 +- modules/gapi/include/opencv2/gapi/gcommon.hpp | 5 ++- .../include/opencv2/gapi/gcomputation.hpp | 10 ++--- modules/gapi/include/opencv2/gapi/gkernel.hpp | 3 +- modules/gapi/include/opencv2/gapi/gmat.hpp | 4 +- .../gapi/include/opencv2/gapi/ocl/core.hpp | 2 +- .../gapi/include/opencv2/gapi/own/exports.hpp | 7 ++++ .../include/opencv2/gapi/plaidml/core.hpp | 2 +- modules/gapi/misc/python/pyopencv_gapi.hpp | 13 ++++++ modules/gapi/misc/python/shadow_gapi.hpp | 7 ++++ .../gapi/misc/python/test/test_gapi_core.py | 42 +++++++++++++++++++ .../gapi/src/backends/fluid/gfluidbuffer.cpp | 2 + modules/python/src2/gen2.py | 21 ++++++---- modules/python/src2/hdr_parser.py | 23 +++++++++- 18 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 modules/gapi/misc/python/pyopencv_gapi.hpp create mode 100644 modules/gapi/misc/python/shadow_gapi.hpp create mode 100644 modules/gapi/misc/python/test/test_gapi_core.py diff --git a/modules/gapi/CMakeLists.txt b/modules/gapi/CMakeLists.txt index ffb83865aa..82f04f8611 100644 --- a/modules/gapi/CMakeLists.txt +++ b/modules/gapi/CMakeLists.txt @@ -24,6 +24,8 @@ ocv_add_module(gapi opencv_imgproc OPTIONAL opencv_video + WRAP + python ) if(MSVC) diff --git a/modules/gapi/include/opencv2/gapi/core.hpp b/modules/gapi/include/opencv2/gapi/core.hpp index 9e765f6ece..ff59c621f2 100644 --- a/modules/gapi/include/opencv2/gapi/core.hpp +++ b/modules/gapi/include/opencv2/gapi/core.hpp @@ -528,7 +528,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param ddepth optional depth of the output matrix. @sa sub, addWeighted */ -GAPI_EXPORTS GMat add(const GMat& src1, const GMat& src2, int ddepth = -1); +GAPI_EXPORTS_W GMat add(const GMat& src1, const GMat& src2, int ddepth = -1); /** @brief Calculates the per-element sum of matrix and given scalar. diff --git a/modules/gapi/include/opencv2/gapi/cpu/core.hpp b/modules/gapi/include/opencv2/gapi/cpu/core.hpp index ffd3596f5a..ac08f91c78 100644 --- a/modules/gapi/include/opencv2/gapi/cpu/core.hpp +++ b/modules/gapi/include/opencv2/gapi/cpu/core.hpp @@ -16,7 +16,7 @@ namespace gapi { namespace core { namespace cpu { -GAPI_EXPORTS GKernelPackage kernels(); +GAPI_EXPORTS_W cv::gapi::GKernelPackage kernels(); } // namespace cpu } // namespace core diff --git a/modules/gapi/include/opencv2/gapi/fluid/core.hpp b/modules/gapi/include/opencv2/gapi/fluid/core.hpp index 8c21f5760a..9eceb82cb2 100644 --- a/modules/gapi/include/opencv2/gapi/fluid/core.hpp +++ b/modules/gapi/include/opencv2/gapi/fluid/core.hpp @@ -13,7 +13,7 @@ namespace cv { namespace gapi { namespace core { namespace fluid { -GAPI_EXPORTS GKernelPackage kernels(); +GAPI_EXPORTS_W cv::gapi::GKernelPackage kernels(); }}}} diff --git a/modules/gapi/include/opencv2/gapi/fluid/gfluidbuffer.hpp b/modules/gapi/include/opencv2/gapi/fluid/gfluidbuffer.hpp index 8130e2ba91..daa9d4153e 100644 --- a/modules/gapi/include/opencv2/gapi/fluid/gfluidbuffer.hpp +++ b/modules/gapi/include/opencv2/gapi/fluid/gfluidbuffer.hpp @@ -56,8 +56,6 @@ public: } }; - View() = default; - const inline uint8_t* InLineB(int index) const // -(w-1)/2...0...+(w-1)/2 for Filters { return m_cache->linePtr(index); @@ -80,6 +78,7 @@ public: Priv& priv(); // internal use only const Priv& priv() const; // internal use only + View(); View(std::unique_ptr&& p); View(View&& v); View& operator=(View&& v); diff --git a/modules/gapi/include/opencv2/gapi/gcommon.hpp b/modules/gapi/include/opencv2/gapi/gcommon.hpp index 5bc01eb7d9..adece19292 100644 --- a/modules/gapi/include/opencv2/gapi/gcommon.hpp +++ b/modules/gapi/include/opencv2/gapi/gcommon.hpp @@ -93,9 +93,12 @@ namespace detail { * passed in (a variadic template parameter pack) into a vector of * cv::GCompileArg objects. */ -struct GAPI_EXPORTS GCompileArg +struct GAPI_EXPORTS_W_SIMPLE GCompileArg { public: + // NB: Required for pythnon bindings + GCompileArg() = default; + std::string tag; // FIXME: use decay in GArg/other trait-based wrapper before leg is shot! diff --git a/modules/gapi/include/opencv2/gapi/gcomputation.hpp b/modules/gapi/include/opencv2/gapi/gcomputation.hpp index 2f0e685ae6..c600de9007 100644 --- a/modules/gapi/include/opencv2/gapi/gcomputation.hpp +++ b/modules/gapi/include/opencv2/gapi/gcomputation.hpp @@ -118,7 +118,7 @@ namespace I { * * @sa GCompiled */ -class GAPI_EXPORTS GComputation +class GAPI_EXPORTS_W GComputation { public: class Priv; @@ -191,7 +191,7 @@ public: * @param in2 second input GMat of the defined binary computation * @param out output GMat of the defined binary computation */ - GComputation(GMat in1, GMat in2, GMat out); // Binary overload + GAPI_WRAP GComputation(GMat in1, GMat in2, GMat out); // Binary overload /** * @brief Defines a binary (two inputs -- one output) computation @@ -275,7 +275,7 @@ public: * @param args compilation arguments for underlying compilation * process. */ - void apply(cv::Mat in, cv::Mat &out, GCompileArgs &&args = {}); // Unary overload + void apply(cv::Mat in, cv::Mat &out, GCompileArgs &&args = {}); // Unary overload /** * @brief Execute an unary computation (with compilation on the fly) @@ -286,7 +286,7 @@ public: * @param args compilation arguments for underlying compilation * process. */ - void apply(cv::Mat in, cv::Scalar &out, GCompileArgs &&args = {}); // Unary overload (scalar) + void apply(cv::Mat in, cv::Scalar &out, GCompileArgs &&args = {}); // Unary overload (scalar) /** * @brief Execute a binary computation (with compilation on the fly) @@ -298,7 +298,7 @@ public: * @param args compilation arguments for underlying compilation * process. */ - void apply(cv::Mat in1, cv::Mat in2, cv::Mat &out, GCompileArgs &&args = {}); // Binary overload + GAPI_WRAP void apply(cv::Mat in1, cv::Mat in2, CV_OUT cv::Mat &out, GCompileArgs &&args = {}); // Binary overload /** * @brief Execute an binary computation (with compilation on the fly) diff --git a/modules/gapi/include/opencv2/gapi/gkernel.hpp b/modules/gapi/include/opencv2/gapi/gkernel.hpp index 7a18d8966f..bd8cf63cec 100644 --- a/modules/gapi/include/opencv2/gapi/gkernel.hpp +++ b/modules/gapi/include/opencv2/gapi/gkernel.hpp @@ -445,7 +445,7 @@ namespace gapi { * Finally, two kernel packages can be combined into a new one * with function cv::gapi::combine(). */ - class GAPI_EXPORTS GKernelPackage + class GAPI_EXPORTS_W_SIMPLE GKernelPackage { /// @private @@ -712,6 +712,7 @@ namespace detail static const char* tag() { return "gapi.use_only"; } }; } // namespace detail + } // namespace cv #endif // OPENCV_GAPI_GKERNEL_HPP diff --git a/modules/gapi/include/opencv2/gapi/gmat.hpp b/modules/gapi/include/opencv2/gapi/gmat.hpp index 5430f14581..6e04f3ce1a 100644 --- a/modules/gapi/include/opencv2/gapi/gmat.hpp +++ b/modules/gapi/include/opencv2/gapi/gmat.hpp @@ -46,10 +46,10 @@ struct GOrigin; * `cv::GArray` | std::vector * `cv::GOpaque` | T */ -class GAPI_EXPORTS GMat +class GAPI_EXPORTS_W_SIMPLE GMat { public: - GMat(); // Empty constructor + GAPI_WRAP GMat(); // Empty constructor GMat(const GNode &n, std::size_t out); // Operation result constructor GOrigin& priv(); // Internal use only diff --git a/modules/gapi/include/opencv2/gapi/ocl/core.hpp b/modules/gapi/include/opencv2/gapi/ocl/core.hpp index 4ab85e2098..6c7587096c 100644 --- a/modules/gapi/include/opencv2/gapi/ocl/core.hpp +++ b/modules/gapi/include/opencv2/gapi/ocl/core.hpp @@ -16,7 +16,7 @@ namespace gapi { namespace core { namespace ocl { - GAPI_EXPORTS GKernelPackage kernels(); + GAPI_EXPORTS_W cv::gapi::GKernelPackage kernels(); } // namespace ocl } // namespace core diff --git a/modules/gapi/include/opencv2/gapi/own/exports.hpp b/modules/gapi/include/opencv2/gapi/own/exports.hpp index 53bff2aef4..da42a3238c 100644 --- a/modules/gapi/include/opencv2/gapi/own/exports.hpp +++ b/modules/gapi/include/opencv2/gapi/own/exports.hpp @@ -11,8 +11,15 @@ # if defined(__OPENCV_BUILD) # include # define GAPI_EXPORTS CV_EXPORTS + /* special informative macros for wrapper generators */ +# define GAPI_WRAP CV_WRAP +# define GAPI_EXPORTS_W_SIMPLE CV_EXPORTS_W_SIMPLE +# define GAPI_EXPORTS_W CV_EXPORTS_W # else +# define GAPI_WRAP # define GAPI_EXPORTS +# define GAPI_EXPORTS_W_SIMPLE +# define GAPI_EXPORTS_W #if 0 // Note: the following version currently is not needed for non-OpenCV build # if defined _WIN32 diff --git a/modules/gapi/include/opencv2/gapi/plaidml/core.hpp b/modules/gapi/include/opencv2/gapi/plaidml/core.hpp index 47ac486e5c..3c63fed93d 100644 --- a/modules/gapi/include/opencv2/gapi/plaidml/core.hpp +++ b/modules/gapi/include/opencv2/gapi/plaidml/core.hpp @@ -13,7 +13,7 @@ namespace cv { namespace gapi { namespace core { namespace plaidml { -GAPI_EXPORTS GKernelPackage kernels(); +GAPI_EXPORTS cv::gapi::GKernelPackage kernels(); }}}} diff --git a/modules/gapi/misc/python/pyopencv_gapi.hpp b/modules/gapi/misc/python/pyopencv_gapi.hpp new file mode 100644 index 0000000000..02f2624048 --- /dev/null +++ b/modules/gapi/misc/python/pyopencv_gapi.hpp @@ -0,0 +1,13 @@ +using gapi_GKernelPackage = cv::gapi::GKernelPackage; + +template<> +bool pyopencv_to(PyObject* obj, std::vector& value, const ArgInfo& info) +{ + return pyopencv_to_generic_vec(obj, value, info); +} + +template<> +PyObject* pyopencv_from(const std::vector& value) +{ + return pyopencv_from_generic_vec(value); +} diff --git a/modules/gapi/misc/python/shadow_gapi.hpp b/modules/gapi/misc/python/shadow_gapi.hpp new file mode 100644 index 0000000000..b36f046b53 --- /dev/null +++ b/modules/gapi/misc/python/shadow_gapi.hpp @@ -0,0 +1,7 @@ +#error This is a shadow header file, which is not intended for processing by any compiler. \ + Only bindings parser should handle this file. + +namespace cv +{ + GAPI_EXPORTS_W GCompileArgs compile_args(gapi::GKernelPackage pkg); +} // namespace cv diff --git a/modules/gapi/misc/python/test/test_gapi_core.py b/modules/gapi/misc/python/test/test_gapi_core.py new file mode 100644 index 0000000000..9e22725d1f --- /dev/null +++ b/modules/gapi/misc/python/test/test_gapi_core.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +import numpy as np +import cv2 as cv + +from tests_common import NewOpenCVTests + + +# Plaidml is an optional backend +pkgs = [ + cv.gapi.core.ocl.kernels(), + cv.gapi.core.cpu.kernels(), + cv.gapi.core.fluid.kernels() + # cv.gapi.core.plaidml.kernels() + ] + + +class gapi_core_test(NewOpenCVTests): + + def test_add(self): + # TODO: Extend to use any type and size here + sz = (1280, 720) + in1 = np.random.randint(0, 100, sz).astype(np.uint8) + in2 = np.random.randint(0, 100, sz).astype(np.uint8) + + # OpenCV + expected = in1 + in2 + + # G-API + g_in1 = cv.GMat() + g_in2 = cv.GMat() + g_out = cv.gapi.add(g_in1, g_in2) + comp = cv.GComputation(g_in1, g_in2, g_out) + + for pkg in pkgs: + actual = comp.apply(in1, in2, args=cv.compile_args(pkg)) + # Comparison + self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF)) + + +if __name__ == '__main__': + NewOpenCVTests.bootstrap() diff --git a/modules/gapi/src/backends/fluid/gfluidbuffer.cpp b/modules/gapi/src/backends/fluid/gfluidbuffer.cpp index aea51d5946..9c2ec000ba 100644 --- a/modules/gapi/src/backends/fluid/gfluidbuffer.cpp +++ b/modules/gapi/src/backends/fluid/gfluidbuffer.cpp @@ -694,6 +694,8 @@ fluid::View::View(std::unique_ptr&& p) : m_priv(std::move(p)), m_cache(&m_priv->cache()) { /* nothing */ } + +fluid::View::View() = default; fluid::View::View(View&&) = default; fluid::View& fluid::View::operator=(View&&) = default; fluid::View::~View() = default; diff --git a/modules/python/src2/gen2.py b/modules/python/src2/gen2.py index 140f8196d6..233587c9cb 100755 --- a/modules/python/src2/gen2.py +++ b/modules/python/src2/gen2.py @@ -27,14 +27,14 @@ gen_template_check_self = Template(""" 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}${args})""") +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}${args}""") +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(args, kw, "$fmtspec", (char**)keywords, $parse_arglist)$code_cvt )""") + if( PyArg_ParseTupleAndKeywords(py_args, kw, "$fmtspec", (char**)keywords, $parse_arglist)$code_cvt )""") gen_template_func_body = Template("""$code_decl $code_parse @@ -362,6 +362,7 @@ class ArgInfo(object): self.inputarg = True self.outputarg = False self.returnarg = False + self.isrvalueref = False for m in arg_tuple[3]: if m == "/O": self.inputarg = False @@ -377,6 +378,8 @@ class ArgInfo(object): 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 @@ -529,14 +532,14 @@ class FuncInfo(object): 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* args, PyObject* kw)".format( + 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* args, PyObject* kw)" % (full_fname, self_arg) + return "static PyObject* %s(PyObject* %s, PyObject* py_args, PyObject* kw)" % (full_fname, self_arg) def get_tab_entry(self): prototype_list = [] @@ -682,6 +685,10 @@ class FuncInfo(object): if not code_args.endswith("("): code_args += ", " + + if a.isrvalueref: + a.name = 'std::move(' + a.name + ')' + code_args += amp + a.name code_args += ")" @@ -695,7 +702,7 @@ class FuncInfo(object): 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, args=code_args) + 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: @@ -744,7 +751,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 || PyObject_Size(kw) == 0))" + code_parse = "if(PyObject_Size(py_args) == 0 && (!kw || PyObject_Size(kw) == 0))" if len(v.py_outlist) == 0: code_ret = "Py_RETURN_NONE" diff --git a/modules/python/src2/hdr_parser.py b/modules/python/src2/hdr_parser.py index 462b53e826..a486e0b71a 100755 --- a/modules/python/src2/hdr_parser.py +++ b/modules/python/src2/hdr_parser.py @@ -111,6 +111,11 @@ class CppHeaderParser(object): if npos >= 0: modlist.append("/C") + npos = arg_str.find("&&") + if npos >= 0: + arg_str = arg_str.replace("&&", '') + modlist.append("/RRef") + npos = arg_str.find("&") if npos >= 0: modlist.append("/Ref") @@ -715,6 +720,8 @@ class CppHeaderParser(object): return stmt_type, classname, True, decl if stmt.startswith("enum") or stmt.startswith("namespace"): + # NB: Drop inheritance syntax for enum + stmt = stmt.split(':')[0] stmt_list = stmt.rsplit(" ", 1) if len(stmt_list) < 2: stmt_list.append("") @@ -812,6 +819,15 @@ class CppHeaderParser(object): l = l0.strip() + # G-API specific aliases + l = self.batch_replace(l, [ + ("GAPI_EXPORTS", "CV_EXPORTS"), + ("GAPI_EXPORTS_W", "CV_EXPORTS_W"), + ("GAPI_EXPORTS_W_SIMPLE","CV_EXPORTS_W_SIMPLE"), + ("GAPI_WRAP", "CV_WRAP"), + ('defined(GAPI_STANDALONE)', '0'), + ]) + if state == SCAN and l.startswith("#"): state = DIRECTIVE # fall through to the if state == DIRECTIVE check @@ -867,7 +883,12 @@ class CppHeaderParser(object): sys.exit(-1) while 1: - token, pos = self.find_next_token(l, [";", "\"", "{", "}", "//", "/*"]) + # NB: Avoid parsing '{' for case: + # foo(Obj&& = {}); + if re.search(r'=\s*\{\s*\}', l): + token, pos = ';', len(l) + else: + token, pos = self.find_next_token(l, [";", "\"", "{", "}", "//", "/*"]) if not token: block_head += " " + l