diff --git a/modules/gapi/include/opencv2/gapi/python/python.hpp b/modules/gapi/include/opencv2/gapi/python/python.hpp index 72ed6f5850..1857a938d5 100644 --- a/modules/gapi/include/opencv2/gapi/python/python.hpp +++ b/modules/gapi/include/opencv2/gapi/python/python.hpp @@ -32,9 +32,7 @@ struct GPythonContext const cv::GMetaArgs &in_metas; const cv::GTypesInfo &out_info; - bool m_isStateful; - - GArg m_state; + cv::optional m_state; }; using Impl = std::function; @@ -46,13 +44,9 @@ public: GPythonKernel() = default; GPythonKernel(Impl run, Setup setup); - cv::GRunArgs operator()(const GPythonContext& ctx); - - bool m_isStateful = false; - Setup m_setup = nullptr; - -private: - Impl m_run; + Impl run; + Setup setup = nullptr; + bool is_stateful = false; }; class GAPI_EXPORTS GPythonFunctor : public cv::gapi::GFunctor diff --git a/modules/gapi/misc/python/pyopencv_gapi.hpp b/modules/gapi/misc/python/pyopencv_gapi.hpp index 0812b283b7..ce1dbf6a48 100644 --- a/modules/gapi/misc/python/pyopencv_gapi.hpp +++ b/modules/gapi/misc/python/pyopencv_gapi.hpp @@ -660,7 +660,8 @@ static cv::GRunArgs run_py_kernel(cv::detail::PyObjectHolder kernel, // NB: Doesn't increase reference counter (false), // because PyObject already have ownership. // In case exception decrement reference counter. - cv::detail::PyObjectHolder args(PyTuple_New(ctx.m_isStateful ? ins.size() + 1: ins.size()), false); + cv::detail::PyObjectHolder args( + PyTuple_New(ctx.m_state.has_value() ? ins.size() + 1 : ins.size()), false); for (size_t i = 0; i < ins.size(); ++i) { // NB: If meta is monostate then object isn't associated with G-TYPE. @@ -691,9 +692,9 @@ static cv::GRunArgs run_py_kernel(cv::detail::PyObjectHolder kernel, ++in_idx; } - if (ctx.m_isStateful) + if (ctx.m_state.has_value()) { - PyTuple_SetItem(args.get(), ins.size(), pyopencv_from(ctx.m_state)); + PyTuple_SetItem(args.get(), ins.size(), pyopencv_from(ctx.m_state.value())); } // NB: Doesn't increase reference counter (false). @@ -742,8 +743,45 @@ static cv::GRunArgs run_py_kernel(cv::detail::PyObjectHolder kernel, return outs; } -static cv::GArg setup_py(cv::detail::PyObjectHolder out_meta, const cv::GMetaArgs& meta, - const cv::GArgs& gargs) +static void unpackMetasToTuple(const cv::GMetaArgs& meta, + const cv::GArgs& gargs, + cv::detail::PyObjectHolder& tuple) +{ + size_t idx = 0; + for (auto&& m : meta) + { + switch (m.index()) + { + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, + pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, + pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, + pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, pyopencv_from(gargs[idx])); + break; + case cv::GMetaArg::index_of(): + util::throw_error( + std::logic_error("GFrame isn't supported for custom operation")); + break; + } + ++idx; + } +} + +static cv::GArg setup_py(cv::detail::PyObjectHolder setup, + const cv::GMetaArgs& meta, + const cv::GArgs& gargs) { PyGILState_STATE gstate; gstate = PyGILState_Ensure(); @@ -756,42 +794,11 @@ static cv::GArg setup_py(cv::detail::PyObjectHolder out_meta, const cv::GMetaArg // because PyObject already have ownership. // In case exception decrement reference counter. cv::detail::PyObjectHolder args(PyTuple_New(meta.size()), false); - size_t idx = 0; - for (auto&& m : meta) - { - switch (m.index()) - { - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, - pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, - pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, - pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(gargs[idx])); - break; - case cv::GMetaArg::index_of(): - util::throw_error( - std::logic_error("GFrame isn't supported for custom operation")); - break; - } - ++idx; - } - - // PyTuple_SetItem(args.get(), idx, pyopencv_from(gmarg)); - - // NB: Doesn't increase reference counter (false). - // In case PyObject_CallObject return NULL, do nothing in destructor. - cv::detail::PyObjectHolder result(PyObject_CallObject(out_meta.get(), args.get()), false); + unpackMetasToTuple(meta, gargs, args); + // NB: Take an onwership because this state is "Python" type so it will be wrapped as-is + // into cv::GArg and stored in GPythonBackend. Object without ownership can't + // be dealocated outside this function. + cv::detail::PyObjectHolder result(PyObject_CallObject(setup.get(), args.get()), true); if (PyErr_Occurred()) { @@ -841,8 +848,8 @@ static cv::GMetaArgs get_meta_args(PyObject* tuple) } static GMetaArgs run_py_meta(cv::detail::PyObjectHolder out_meta, - const cv::GMetaArgs &meta, - const cv::GArgs &gargs) + const cv::GMetaArgs &meta, + const cv::GArgs &gargs) { PyGILState_STATE gstate; gstate = PyGILState_Ensure(); @@ -854,32 +861,7 @@ static GMetaArgs run_py_meta(cv::detail::PyObjectHolder out_meta, // because PyObject already have ownership. // In case exception decrement reference counter. cv::detail::PyObjectHolder args(PyTuple_New(meta.size()), false); - size_t idx = 0; - for (auto&& m : meta) - { - switch (m.index()) - { - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(gargs[idx])); - break; - case cv::GMetaArg::index_of(): - util::throw_error(std::logic_error("GFrame isn't supported for custom operation")); - break; - } - ++idx; - } + unpackMetasToTuple(meta, gargs, args); // NB: Doesn't increase reference counter (false). // In case PyObject_CallObject return NULL, do nothing in destructor. cv::detail::PyObjectHolder result( diff --git a/modules/gapi/misc/python/test/test_gapi_stateful_kernel.py b/modules/gapi/misc/python/test/test_gapi_stateful_kernel.py index f24ceccabf..9b3b614523 100644 --- a/modules/gapi/misc/python/test/test_gapi_stateful_kernel.py +++ b/modules/gapi/misc/python/test/test_gapi_stateful_kernel.py @@ -78,6 +78,52 @@ try: self.assertEqual(ref, acc1) + def test_stateful_throw_setup(self): + @cv.gapi.kernel(GStatefulCounter) + class GThrowStatefulCounterImpl: + """Implementation for GStatefulCounter operation + that throw exception in setup method""" + + @staticmethod + def setup(desc): + raise Exception('Throw from setup method') + + @staticmethod + def run(value, state): + raise Exception('Unreachable') + + g_in = cv.GOpaque.Int() + g_out = GStatefulCounter.on(g_in) + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out)) + pkg = cv.gapi.kernels(GThrowStatefulCounterImpl) + + with self.assertRaises(Exception): comp.apply(cv.gin(42), + args=cv.gapi.compile_args(pkg)) + + + def test_stateful_reset(self): + g_in = cv.GOpaque.Int() + g_out = GStatefulCounter.on(g_in) + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out)) + pkg = cv.gapi.kernels(GStatefulCounterImpl) + + cc = comp.compileStreaming(args=cv.gapi.compile_args(pkg)) + + cc.setSource(cv.gin(1)) + cc.start() + for i in range(1, 10): + _, actual = cc.pull() + self.assertEqual(i, actual) + cc.stop() + + cc.setSource(cv.gin(2)) + cc.start() + for i in range(2, 10, 2): + _, actual = cc.pull() + self.assertEqual(i, actual) + cc.stop() + + except unittest.SkipTest as e: message = str(e) diff --git a/modules/gapi/src/backends/python/gpythonbackend.cpp b/modules/gapi/src/backends/python/gpythonbackend.cpp index 4bfdc7d3ec..0c65f4cba8 100644 --- a/modules/gapi/src/backends/python/gpythonbackend.cpp +++ b/modules/gapi/src/backends/python/gpythonbackend.cpp @@ -14,16 +14,10 @@ #include "api/gbackend_priv.hpp" #include "backends/common/gbackend.hpp" -cv::gapi::python::GPythonKernel::GPythonKernel(cv::gapi::python::Impl run, - cv::gapi::python::Setup setup) - : m_run(run), m_setup(setup) +cv::gapi::python::GPythonKernel::GPythonKernel(cv::gapi::python::Impl runf, + cv::gapi::python::Setup setupf) + : run(runf), setup(setupf), is_stateful(setup != nullptr) { - m_isStateful = (setup != nullptr); -} - -cv::GRunArgs cv::gapi::python::GPythonKernel::operator()(const cv::gapi::python::GPythonContext& ctx) -{ - return m_run(ctx); } cv::gapi::python::GPythonFunctor::GPythonFunctor(const char* id, @@ -162,11 +156,11 @@ static void writeBack(cv::GRunArg& arg, cv::GRunArgP& out) void GPythonExecutable::handleNewStream() { - if (!m_kernel.m_isStateful) + if (!m_kernel.is_stateful) return; - m_node_state = m_kernel.m_setup(cv::gimpl::GModel::collectInputMeta(m_gm, m_op), - m_gm.metadata(m_op).get().args); + m_node_state = m_kernel.setup(cv::gimpl::GModel::collectInputMeta(m_gm, m_op), + m_gm.metadata(m_op).get().args); } void GPythonExecutable::run(std::vector &&input_objs, @@ -181,16 +175,15 @@ void GPythonExecutable::run(std::vector &&input_objs, std::back_inserter(inputs), std::bind(&packArg, std::ref(m_res), _1)); - cv::gapi::python::GPythonContext ctx{inputs, m_in_metas, m_out_info, false}; + cv::gapi::python::GPythonContext ctx{inputs, m_in_metas, m_out_info, /*state*/{}}; - // For stateful kernel add state to its execution context - if (m_kernel.m_isStateful) + // NB: For stateful kernel add state to its execution context + if (m_kernel.is_stateful) { - ctx.m_state = m_node_state; - ctx.m_isStateful = true; + ctx.m_state = cv::optional(m_node_state); } - auto outs = m_kernel(ctx); + auto outs = m_kernel.run(ctx); for (auto&& it : ade::util::zip(outs, output_objs)) { @@ -249,7 +242,7 @@ GPythonExecutable::GPythonExecutable(const ade::Graph& g, m_kernel = cag.metadata(m_op).get().kernel; // If kernel is stateful then prepare storage for its state. - if (m_kernel.m_isStateful) + if (m_kernel.is_stateful) { m_node_state = cv::GArg{ }; }