Merge pull request #20298 from mpashchenkov:mp/python-desync
authorMaxim Pashchenkov <maxim.pashchenkov@intel.com>
Thu, 1 Jul 2021 19:06:35 +0000 (22:06 +0300)
committerGitHub <noreply@github.com>
Thu, 1 Jul 2021 19:06:35 +0000 (19:06 +0000)
G-API: Python. Desync.

* Desync. GMat.

* Alignment

modules/gapi/include/opencv2/gapi/gstreaming.hpp
modules/gapi/misc/python/pyopencv_gapi.hpp
modules/gapi/misc/python/shadow_gapi.hpp
modules/gapi/misc/python/test/test_gapi_streaming.py
modules/gapi/src/compiler/gstreaming.cpp
modules/gapi/src/compiler/gstreaming_priv.hpp
modules/gapi/src/executor/gstreamingexecutor.cpp
modules/gapi/src/executor/gstreamingexecutor.hpp
modules/gapi/test/streaming/gapi_streaming_tests.cpp

index 47e103fd0ea7ab325128d094e16f91fb74e10d97..50abe69f87b77098aa5d1b1ecc719806c0cd7771 100644 (file)
@@ -71,6 +71,15 @@ using GOptRunArgP = util::variant<
 >;
 using GOptRunArgsP = std::vector<GOptRunArgP>;
 
+using GOptRunArg = util::variant<
+    optional<cv::Mat>,
+    optional<cv::RMat>,
+    optional<cv::Scalar>,
+    optional<cv::detail::VectorRef>,
+    optional<cv::detail::OpaqueRef>
+>;
+using GOptRunArgs = std::vector<GOptRunArg>;
+
 namespace detail {
 
 template<typename T> inline GOptRunArgP wrap_opt_arg(optional<T>& arg) {
@@ -255,7 +264,7 @@ public:
 
     // NB: Used from python
     /// @private -- Exclude this function from OpenCV documentation
-    GAPI_WRAP std::tuple<bool, cv::GRunArgs> pull();
+    GAPI_WRAP std::tuple<bool, cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>> pull();
 
     /**
      * @brief Get some next available data from the pipeline.
index 3c428dde6d82e2a4779a7728e1fa229381ef3b1d..d378a91b5fd6faeedb4d5c8973aea1d20e2e2b3b 100644 (file)
@@ -131,7 +131,8 @@ PyObject* pyopencv_from(const cv::detail::PyObjectHolder& v)
 template <>
 PyObject* pyopencv_from(const cv::gapi::wip::draw::Prim& prim)
 {
-    switch (prim.index()) {
+    switch (prim.index())
+    {
         case cv::gapi::wip::draw::Prim::index_of<cv::gapi::wip::draw::Rect>():
             return pyopencv_from(cv::util::get<cv::gapi::wip::draw::Rect>(prim));
         case cv::gapi::wip::draw::Prim::index_of<cv::gapi::wip::draw::Text>():
@@ -319,40 +320,69 @@ PyObject* pyopencv_from(const GRunArg& v)
             return pyopencv_from(util::get<cv::detail::OpaqueRef>(v));
     }
 
-    PyErr_SetString(PyExc_TypeError, "Failed to unpack GRunArgs");
+    PyErr_SetString(PyExc_TypeError, "Failed to unpack GRunArgs. Index of variant is unknown");
     return NULL;
 }
 
-template<>
-PyObject* pyopencv_from(const GRunArgs& value)
+template <typename T>
+PyObject* pyopencv_from(const cv::optional<T>& opt)
 {
-    size_t i, n = value.size();
+    if (!opt.has_value())
+    {
+        Py_RETURN_NONE;
+    }
+    return pyopencv_from(*opt);
+}
 
-    // NB: It doesn't make sense to return list with a single element
-    if (n == 1)
+template <>
+PyObject* pyopencv_from(const GOptRunArg& v)
+{
+    switch (v.index())
     {
-        PyObject* item = pyopencv_from(value[0]);
-        if(!item)
-        {
-            return NULL;
-        }
-        return item;
+        case GOptRunArg::index_of<cv::optional<cv::Mat>>():
+            return pyopencv_from(util::get<cv::optional<cv::Mat>>(v));
+
+        case GOptRunArg::index_of<cv::optional<cv::Scalar>>():
+            return pyopencv_from(util::get<cv::optional<cv::Scalar>>(v));
+
+        case GOptRunArg::index_of<optional<cv::detail::VectorRef>>():
+            return pyopencv_from(util::get<optional<cv::detail::VectorRef>>(v));
+
+        case GOptRunArg::index_of<optional<cv::detail::OpaqueRef>>():
+            return pyopencv_from(util::get<optional<cv::detail::OpaqueRef>>(v));
     }
 
-    PyObject* list = PyList_New(n);
-    for(i = 0; i < n; ++i)
+    PyErr_SetString(PyExc_TypeError, "Failed to unpack GOptRunArg. Index of variant is unknown");
+    return NULL;
+}
+
+template<>
+PyObject* pyopencv_from(const GRunArgs& value)
+{
+     return value.size() == 1 ? pyopencv_from(value[0]) : pyopencv_from_generic_vec(value);
+}
+
+template<>
+PyObject* pyopencv_from(const GOptRunArgs& value)
+{
+    return value.size() == 1 ? pyopencv_from(value[0]) : pyopencv_from_generic_vec(value);
+}
+
+// FIXME: cv::variant should be wrapped once for all types.
+template <>
+PyObject* pyopencv_from(const cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>& v)
+{
+    using RunArgs = cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>;
+    switch (v.index())
     {
-        PyObject* item = pyopencv_from(value[i]);
-        if(!item)
-        {
-            Py_DECREF(list);
-            PyErr_SetString(PyExc_TypeError, "Failed to unpack GRunArgs");
-            return NULL;
-        }
-        PyList_SetItem(list, i, item);
+        case RunArgs::index_of<cv::GRunArgs>():
+            return pyopencv_from(util::get<cv::GRunArgs>(v));
+        case RunArgs::index_of<cv::GOptRunArgs>():
+            return pyopencv_from(util::get<cv::GOptRunArgs>(v));
     }
 
-    return list;
+    PyErr_SetString(PyExc_TypeError, "Failed to recognize kind of RunArgs. Index of variant is unknown");
+    return NULL;
 }
 
 template <typename T>
@@ -634,7 +664,8 @@ static cv::GRunArgs run_py_kernel(cv::detail::PyObjectHolder kernel,
         cv::detail::PyObjectHolder result(
                 PyObject_CallObject(kernel.get(), args.get()), false);
 
-        if (PyErr_Occurred()) {
+        if (PyErr_Occurred())
+        {
             PyErr_PrintEx(0);
             PyErr_Clear();
             throw std::logic_error("Python kernel failed with error!");
@@ -717,8 +748,9 @@ 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();
 
@@ -760,7 +792,8 @@ static GMetaArgs run_py_meta(cv::detail::PyObjectHolder out_meta,
         cv::detail::PyObjectHolder result(
                 PyObject_CallObject(out_meta.get(), args.get()), false);
 
-        if (PyErr_Occurred()) {
+        if (PyErr_Occurred())
+        {
             PyErr_PrintEx(0);
             PyErr_Clear();
             throw std::logic_error("Python outMeta failed with error!");
@@ -792,21 +825,24 @@ static PyObject* pyopencv_cv_gapi_kernels(PyObject* , PyObject* py_args, PyObjec
         PyObject* user_kernel = PyTuple_GetItem(py_args, i);
 
         PyObject* id_obj = PyObject_GetAttrString(user_kernel, "id");
-        if (!id_obj) {
+        if (!id_obj)
+        {
             PyErr_SetString(PyExc_TypeError,
                     "Python kernel should contain id, please use cv.gapi.kernel to define kernel");
             return NULL;
         }
 
         PyObject* out_meta = PyObject_GetAttrString(user_kernel, "outMeta");
-        if (!out_meta) {
+        if (!out_meta)
+        {
             PyErr_SetString(PyExc_TypeError,
                     "Python kernel should contain outMeta, please use cv.gapi.kernel to define kernel");
             return NULL;
         }
 
         PyObject* run  = PyObject_GetAttrString(user_kernel, "run");
-        if (!run) {
+        if (!run)
+        {
             PyErr_SetString(PyExc_TypeError,
                     "Python kernel should contain run, please use cv.gapi.kernel to define kernel");
             return NULL;
@@ -951,9 +987,12 @@ struct PyOpenCV_Converter<cv::GArray<T>>
         if (PyObject_TypeCheck(obj, reinterpret_cast<PyTypeObject*>(pyopencv_GArrayT_TypePtr)))
         {
             auto& array = reinterpret_cast<pyopencv_GArrayT_t*>(obj)->v;
-            try {
+            try
+            {
                 value = cv::util::get<cv::GArray<T>>(array.arg());
-            } catch (...) {
+            }
+            catch (...)
+            {
                 return false;
             }
             return true;
@@ -974,9 +1013,12 @@ struct PyOpenCV_Converter<cv::GOpaque<T>>
         if (PyObject_TypeCheck(obj, reinterpret_cast<PyTypeObject*>(pyopencv_GOpaqueT_TypePtr)))
         {
             auto& opaque = reinterpret_cast<pyopencv_GOpaqueT_t*>(obj)->v;
-            try {
+            try
+            {
                 value = cv::util::get<cv::GOpaque<T>>(opaque.arg());
-            } catch (...) {
+            }
+            catch (...)
+            {
                 return false;
             }
             return true;
index 41d0f19732231329019a4b93bf6c8ad6805d0a36..0b489dde0f5518d73f15d76852f51f1561c16034 100644 (file)
@@ -3,39 +3,40 @@
 
 namespace cv
 {
-struct GAPI_EXPORTS_W_SIMPLE GCompileArg {
-   GAPI_WRAP GCompileArg(gapi::GKernelPackage pkg);
-   GAPI_WRAP GCompileArg(gapi::GNetPackage pkg);
+struct GAPI_EXPORTS_W_SIMPLE GCompileArg
+{
+    GAPI_WRAP GCompileArg(gapi::GKernelPackage pkg);
+    GAPI_WRAP GCompileArg(gapi::GNetPackage pkg);
 };
 
 class GAPI_EXPORTS_W_SIMPLE GInferInputs
 {
 public:
-   GAPI_WRAP GInferInputs();
-   GAPI_WRAP GInferInputs& setInput(const std::string& name, const cv::GMat&   value);
-   GAPI_WRAP GInferInputs& setInput(const std::string& name, const cv::GFrame& value);
+    GAPI_WRAP GInferInputs();
+    GAPI_WRAP GInferInputs& setInput(const std::string& name, const cv::GMat&   value);
+    GAPI_WRAP GInferInputs& setInput(const std::string& name, const cv::GFrame& value);
 };
 
 class GAPI_EXPORTS_W_SIMPLE GInferListInputs
 {
 public:
-   GAPI_WRAP GInferListInputs();
-   GAPI_WRAP GInferListInputs setInput(const std::string& name, const cv::GArray<cv::GMat>& value);
-   GAPI_WRAP GInferListInputs setInput(const std::string& name, const cv::GArray<cv::Rect>& value);
+    GAPI_WRAP GInferListInputs();
+    GAPI_WRAP GInferListInputs setInput(const std::string& name, const cv::GArray<cv::GMat>& value);
+    GAPI_WRAP GInferListInputs setInput(const std::string& name, const cv::GArray<cv::Rect>& value);
 };
 
 class GAPI_EXPORTS_W_SIMPLE GInferOutputs
 {
 public:
-   GAPI_WRAP GInferOutputs();
-   GAPI_WRAP cv::GMat at(const std::string& name);
+    GAPI_WRAP GInferOutputs();
+    GAPI_WRAP cv::GMat at(const std::string& name);
 };
 
 class GAPI_EXPORTS_W_SIMPLE GInferListOutputs
 {
 public:
-   GAPI_WRAP GInferListOutputs();
-   GAPI_WRAP cv::GArray<cv::GMat> at(const std::string& name);
+    GAPI_WRAP GInferListOutputs();
+    GAPI_WRAP cv::GArray<cv::GMat> at(const std::string& name);
 };
 
 namespace gapi
@@ -69,11 +70,13 @@ namespace streaming
     cv::GOpaque<int64_t> GAPI_EXPORTS_W timestamp(cv::GMat);
     cv::GOpaque<int64_t> GAPI_EXPORTS_W seqNo(cv::GMat);
     cv::GOpaque<int64_t> GAPI_EXPORTS_W seq_id(cv::GMat);
+
+    GAPI_EXPORTS_W cv::GMat desync(const cv::GMat &g);
 } // namespace streaming
 } // namespace gapi
 
 namespace detail
 {
-   gapi::GNetParam GAPI_EXPORTS_W strip(gapi::ie::PyParams params);
+    gapi::GNetParam GAPI_EXPORTS_W strip(gapi::ie::PyParams params);
 } // namespace detail
 } // namespace cv
index 4ea88878eeabd6d5ee2663399aebcc5b493b66cd..7ede1b5cf38dcc1f27d4caa0c65c06c50836c915 100644 (file)
@@ -5,16 +5,35 @@ import cv2 as cv
 import os
 import sys
 import unittest
+import time
 
 from tests_common import NewOpenCVTests
 
 
 try:
-
     if sys.version_info[:2] < (3, 0):
         raise unittest.SkipTest('Python 2.x is not supported')
 
 
+    @cv.gapi.op('custom.delay', in_types=[cv.GMat], out_types=[cv.GMat])
+    class GDelay:
+        """Delay for 10 ms."""
+
+        @staticmethod
+        def outMeta(desc):
+            return desc
+
+
+    @cv.gapi.kernel(GDelay)
+    class GDelayImpl:
+        """Implementation for GDelay operation."""
+
+        @staticmethod
+        def run(img):
+            time.sleep(0.01)
+            return img
+
+
     class test_gapi_streaming(NewOpenCVTests):
 
         def test_image_input(self):
@@ -148,7 +167,7 @@ try:
 
                 proc_num_frames += 1
                 if proc_num_frames == max_num_frames:
-                    break;
+                    break
 
 
         def test_video_good_features_to_track(self):
@@ -242,6 +261,51 @@ try:
                 if curr_frame_number == max_num_frames:
                     break
 
+        def test_desync(self):
+            path = self.find_file('cv/video/768x576.avi', [os.environ['OPENCV_TEST_DATA_PATH']])
+
+            # G-API
+            g_in = cv.GMat()
+            g_out1 = cv.gapi.copy(g_in)
+            des = cv.gapi.streaming.desync(g_in)
+            g_out2 = GDelay.on(des)
+
+            c = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out1, g_out2))
+
+            kernels = cv.gapi.kernels(GDelayImpl)
+            ccomp = c.compileStreaming(args=cv.gapi.compile_args(kernels))
+            source = cv.gapi.wip.make_capture_src(path)
+            ccomp.setSource(cv.gin(source))
+            ccomp.start()
+
+            # Assert
+            max_num_frames  = 10
+            proc_num_frames = 0
+
+            out_counter = 0
+            desync_out_counter = 0
+            none_counter = 0
+            while True:
+                has_frame, (out1, out2) = ccomp.pull()
+                if not has_frame:
+                    break
+
+                if not out1 is None:
+                    out_counter += 1
+                if not out2 is None:
+                    desync_out_counter += 1
+                else:
+                    none_counter += 1
+
+                proc_num_frames += 1
+                if proc_num_frames == max_num_frames:
+                    ccomp.stop()
+                    break
+
+            self.assertLess(0, proc_num_frames)
+            self.assertLess(desync_out_counter, out_counter)
+            self.assertLess(0, none_counter)
+
 
 except unittest.SkipTest as e:
 
index 3bdc0323b5c7dc65a78e4f765165336bf449f947..e45e770427554947a86a3b5bbf66ee7cb475efe3 100644 (file)
@@ -75,6 +75,11 @@ bool cv::GStreamingCompiled::Priv::pull(cv::GOptRunArgsP &&outs)
     return m_exec->pull(std::move(outs));
 }
 
+std::tuple<bool, cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>> cv::GStreamingCompiled::Priv::pull()
+{
+    return m_exec->pull();
+}
+
 bool cv::GStreamingCompiled::Priv::try_pull(cv::GRunArgsP &&outs)
 {
     return m_exec->try_pull(std::move(outs));
@@ -123,18 +128,9 @@ bool cv::GStreamingCompiled::pull(cv::GRunArgsP &&outs)
     return m_priv->pull(std::move(outs));
 }
 
-std::tuple<bool, cv::GRunArgs> cv::GStreamingCompiled::pull()
+std::tuple<bool, cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>> cv::GStreamingCompiled::pull()
 {
-    GRunArgs run_args;
-    GRunArgsP outs;
-    const auto& out_info = m_priv->outInfo();
-    run_args.reserve(out_info.size());
-    outs.reserve(out_info.size());
-
-    cv::detail::constructGraphOutputs(m_priv->outInfo(), run_args, outs);
-
-    bool is_over = m_priv->pull(std::move(outs));
-    return std::make_tuple(is_over, run_args);
+    return m_priv->pull();
 }
 
 bool cv::GStreamingCompiled::pull(cv::GOptRunArgsP &&outs)
index 59b19d425261e214bd71d4eac249ab12439c9e57..1b559ba31030b248d1890222832da40254e151c2 100644 (file)
@@ -46,6 +46,7 @@ public:
     void start();
     bool pull(cv::GRunArgsP &&outs);
     bool pull(cv::GOptRunArgsP &&outs);
+    std::tuple<bool, cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>> pull();
     bool try_pull(cv::GRunArgsP &&outs);
     void stop();
 
index 74c96bdf3ef3c885cd6776838343e04f981e0659..27049aef63275f39bd2861318fab3fdecdf8910d 100644 (file)
@@ -1017,6 +1017,49 @@ void check_DesyncObjectConsumedByMultipleIslands(const cv::gimpl::GIslandModel::
     } // for(nodes)
 }
 
+// NB: Construct GRunArgsP based on passed info and store the memory in passed cv::GRunArgs.
+// Needed for python bridge, because in case python user doesn't pass output arguments to apply.
+void constructOptGraphOutputs(const cv::GTypesInfo &out_info,
+                                    cv::GOptRunArgs &args,
+                                    cv::GOptRunArgsP &outs)
+{
+    for (auto&& info : out_info)
+    {
+        switch (info.shape)
+        {
+            case cv::GShape::GMAT:
+            {
+                args.emplace_back(cv::optional<cv::Mat>{});
+                outs.emplace_back(&cv::util::get<cv::optional<cv::Mat>>(args.back()));
+                break;
+            }
+            case cv::GShape::GSCALAR:
+            {
+                args.emplace_back(cv::optional<cv::Scalar>{});
+                outs.emplace_back(&cv::util::get<cv::optional<cv::Scalar>>(args.back()));
+                break;
+            }
+            case cv::GShape::GARRAY:
+            {
+                cv::detail::VectorRef ref;
+                cv::util::get<cv::detail::ConstructVec>(info.ctor)(ref);
+                args.emplace_back(cv::util::make_optional(std::move(ref)));
+                outs.emplace_back(wrap_opt_arg(cv::util::get<cv::optional<cv::detail::VectorRef>>(args.back())));
+                break;
+            }
+            case cv::GShape::GOPAQUE:
+            {
+                cv::detail::OpaqueRef ref;
+                cv::util::get<cv::detail::ConstructOpaque>(info.ctor)(ref);
+                args.emplace_back(cv::util::make_optional(std::move(ref)));
+                outs.emplace_back(wrap_opt_arg(cv::util::get<cv::optional<cv::detail::OpaqueRef>>(args.back())));
+                break;
+            }
+            default:
+                cv::util::throw_error(std::logic_error("Unsupported optional output shape for Python"));
+        }
+    }
+}
 } // anonymous namespace
 
 class cv::gimpl::GStreamingExecutor::Synchronizer final {
@@ -1320,6 +1363,16 @@ cv::gimpl::GStreamingExecutor::GStreamingExecutor(std::unique_ptr<ade::Graph> &&
     // per the same input frame, so the output traffic multiplies)
     GAPI_Assert(m_collector_map.size() > 0u);
     m_out_queue.set_capacity(queue_capacity * m_collector_map.size());
+
+    // FIXME: The code duplicates logic of collectGraphInfo()
+    cv::gimpl::GModel::ConstGraph cgr(*m_orig_graph);
+    auto meta = cgr.metadata().get<cv::gimpl::Protocol>().out_nhs;
+    out_info.reserve(meta.size());
+
+    ade::util::transform(meta, std::back_inserter(out_info), [&cgr](const ade::NodeHandle& nh) {
+        const auto& data = cgr.metadata(nh).get<cv::gimpl::Data>();
+        return cv::GTypeInfo{data.shape, data.kind, data.ctor};
+    });
 }
 
 cv::gimpl::GStreamingExecutor::~GStreamingExecutor()
@@ -1653,6 +1706,31 @@ bool cv::gimpl::GStreamingExecutor::pull(cv::GOptRunArgsP &&outs)
     return true;
 }
 
+std::tuple<bool, cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>> cv::gimpl::GStreamingExecutor::pull()
+{
+    using RunArgs = cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>;
+    bool is_over = false;
+
+    if (m_desync) {
+        GOptRunArgs opt_run_args;
+        GOptRunArgsP opt_outs;
+        opt_outs.reserve(out_info.size());
+        opt_run_args.reserve(out_info.size());
+
+        constructOptGraphOutputs(out_info, opt_run_args, opt_outs);
+        is_over = pull(std::move(opt_outs));
+        return std::make_tuple(is_over, RunArgs(opt_run_args));
+    }
+
+    GRunArgs run_args;
+    GRunArgsP outs;
+    run_args.reserve(out_info.size());
+    outs.reserve(out_info.size());
+
+    constructGraphOutputs(out_info, run_args, outs);
+    is_over = pull(std::move(outs));
+    return std::make_tuple(is_over, RunArgs(run_args));
+}
 
 bool cv::gimpl::GStreamingExecutor::try_pull(cv::GRunArgsP &&outs)
 {
index 40b787268228859ff365304d814d97d5fbdf37b5..b4aadcbbaf4dc25c68e3c4bb7ee19b1193e5dccd 100644 (file)
@@ -195,6 +195,8 @@ protected:
 
     void wait_shutdown();
 
+    cv::GTypesInfo out_info;
+
 public:
     explicit GStreamingExecutor(std::unique_ptr<ade::Graph> &&g_model,
                                 const cv::GCompileArgs &comp_args);
@@ -203,6 +205,7 @@ public:
     void start();
     bool pull(cv::GRunArgsP &&outs);
     bool pull(cv::GOptRunArgsP &&outs);
+    std::tuple<bool, cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>> pull();
     bool try_pull(cv::GRunArgsP &&outs);
     void stop();
     bool running() const;
index f3179a70813a75f7a0882a8bcdc07abd0b14b191..5386d1736f67a3710f0d2fb01409254b20c92a44 100644 (file)
@@ -244,6 +244,35 @@ public:
     }
 };
 
+void checkPullOverload(const cv::Mat& ref,
+                       const bool has_output,
+                       cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>& args) {
+    EXPECT_TRUE(has_output);
+    using runArgs = cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>;
+    cv::Mat out_mat;
+    switch (args.index()) {
+        case runArgs::index_of<cv::GRunArgs>():
+        {
+            auto outputs = util::get<cv::GRunArgs>(args);
+            EXPECT_EQ(1u, outputs.size());
+            out_mat = cv::util::get<cv::Mat>(outputs[0]);
+            break;
+        }
+        case runArgs::index_of<cv::GOptRunArgs>():
+        {
+            auto outputs = util::get<cv::GOptRunArgs>(args);
+            EXPECT_EQ(1u, outputs.size());
+            auto opt_mat = cv::util::get<cv::optional<cv::Mat>>(outputs[0]);
+            ASSERT_TRUE(opt_mat.has_value());
+            out_mat = *opt_mat;
+            break;
+        }
+        default: GAPI_Assert(false && "Incorrect type of Args");
+    }
+
+    EXPECT_EQ(0., cv::norm(ref, out_mat, cv::NORM_INF));
+}
+
 } // anonymous namespace
 
 TEST_P(GAPI_Streaming, SmokeTest_ConstInput_GMat)
@@ -1336,13 +1365,45 @@ TEST(Streaming, Python_Pull_Overload)
 
     bool has_output;
     cv::GRunArgs outputs;
-    std::tie(has_output, outputs) = ccomp.pull();
+    using RunArgs = cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>;
+    RunArgs args;
 
-    EXPECT_TRUE(has_output);
-    EXPECT_EQ(1u, outputs.size());
+    std::tie(has_output, args) = ccomp.pull();
+
+    checkPullOverload(in_mat, has_output, args);
+
+    ccomp.stop();
+    EXPECT_FALSE(ccomp.running());
+}
+
+TEST(GAPI_Streaming_Desync, Python_Pull_Overload)
+{
+    cv::GMat in;
+    cv::GMat out = cv::gapi::streaming::desync(in);
+    cv::GComputation c(in, out);
+
+    cv::Size sz(3,3);
+    cv::Mat in_mat(sz, CV_8UC3);
+    cv::randu(in_mat, cv::Scalar::all(0), cv::Scalar(255));
 
-    auto out_mat = cv::util::get<cv::Mat>(outputs[0]);
-    EXPECT_EQ(0., cv::norm(in_mat, out_mat, cv::NORM_INF));
+    auto ccomp = c.compileStreaming();
+
+    EXPECT_TRUE(ccomp);
+    EXPECT_FALSE(ccomp.running());
+
+    ccomp.setSource(cv::gin(in_mat));
+
+    ccomp.start();
+    EXPECT_TRUE(ccomp.running());
+
+    bool has_output;
+    cv::GRunArgs outputs;
+    using RunArgs = cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>;
+    RunArgs args;
+
+    std::tie(has_output, args) = ccomp.pull();
+
+    checkPullOverload(in_mat, has_output, args);
 
     ccomp.stop();
     EXPECT_FALSE(ccomp.running());
@@ -2132,9 +2193,17 @@ TEST(GAPI_Streaming, TestPythonAPI)
 
     bool is_over = false;
     cv::GRunArgs out_args;
+    using RunArgs = cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>;
+    RunArgs args;
 
     // NB: Used by python bridge
-    std::tie(is_over, out_args) = cc.pull();
+    std::tie(is_over, args) = cc.pull();
+
+    switch (args.index()) {
+        case RunArgs::index_of<cv::GRunArgs>():
+            out_args = util::get<cv::GRunArgs>(args); break;
+        default: GAPI_Assert(false && "Incorrect type of return value");
+    }
 
     ASSERT_EQ(1u, out_args.size());
     ASSERT_TRUE(cv::util::holds_alternative<cv::Mat>(out_args[0]));