Merge remote-tracking branch 'upstream/3.4' into merge-3.4
authorAlexander Alekhin <alexander.a.alekhin@gmail.com>
Mon, 25 Jan 2021 22:34:41 +0000 (22:34 +0000)
committerAlexander Alekhin <alexander.a.alekhin@gmail.com>
Mon, 25 Jan 2021 22:42:13 +0000 (22:42 +0000)
modules/core/include/opencv2/core/bindings_utils.hpp
modules/core/src/ocl.cpp
modules/dnn/misc/python/test/test_dnn.py
modules/dnn/src/onnx/onnx_importer.cpp
modules/dnn/src/tensorflow/tf_importer.cpp
modules/dnn/test/test_onnx_importer.cpp
modules/highgui/src/window_w32.cpp
modules/python/src2/cv2.cpp
modules/python/test/test_misc.py

index 5c8df7a..bbcc3a8 100644 (file)
@@ -79,6 +79,41 @@ String testOverloadResolution(const Rect& rect)
 }
 
 CV_WRAP static inline
+String dumpRect(const Rect& argument)
+{
+    return format("rect: (x=%d, y=%d, w=%d, h=%d)", argument.x, argument.y,
+                  argument.width, argument.height);
+}
+
+CV_WRAP static inline
+String dumpTermCriteria(const TermCriteria& argument)
+{
+    return format("term_criteria: (type=%d, max_count=%d, epsilon=%lf",
+                  argument.type, argument.maxCount, argument.epsilon);
+}
+
+CV_WRAP static inline
+String dumpRotatedRect(const RotatedRect& argument)
+{
+    return format("rotated_rect: (c_x=%f, c_y=%f, w=%f, h=%f, a=%f)",
+                  argument.center.x, argument.center.y, argument.size.width,
+                  argument.size.height, argument.angle);
+}
+
+CV_WRAP static inline
+String dumpRange(const Range& argument)
+{
+    if (argument == Range::all())
+    {
+        return "range: all";
+    }
+    else
+    {
+        return format("range: (s=%d, e=%d)", argument.start, argument.end);
+    }
+}
+
+CV_WRAP static inline
 AsyncArray testAsyncArray(InputArray argument)
 {
     AsyncPromise p;
index acc35bb..53b21e0 100644 (file)
@@ -1149,14 +1149,14 @@ void OpenCLExecutionContext::release()
 }
 
 
+
 // true if we have initialized OpenCL subsystem with available platforms
-static bool g_isOpenCLActivated = false;
+static bool g_isOpenCLInitialized = false;
+static bool g_isOpenCLAvailable = false;
 
 bool haveOpenCL()
 {
     CV_TRACE_FUNCTION();
-    static bool g_isOpenCLInitialized = false;
-    static bool g_isOpenCLAvailable = false;
 
     if (!g_isOpenCLInitialized)
     {
@@ -1178,7 +1178,7 @@ bool haveOpenCL()
         {
             cl_uint n = 0;
             g_isOpenCLAvailable = ::clGetPlatformIDs(0, NULL, &n) == CL_SUCCESS;
-            g_isOpenCLActivated = n > 0;
+            g_isOpenCLAvailable &= n > 0;
             CV_LOG_INFO(NULL, "OpenCL: found " << n << " platforms");
         }
         catch (...)
@@ -1214,7 +1214,7 @@ bool useOpenCL()
 
 bool isOpenCLActivated()
 {
-    if (!g_isOpenCLActivated)
+    if (!g_isOpenCLAvailable)
         return false; // prevent unnecessary OpenCL activation via useOpenCL()->haveOpenCL() calls
     return useOpenCL();
 }
index afc960e..746dabf 100644 (file)
@@ -177,7 +177,7 @@ class dnn_test(NewOpenCVTests):
             cv.rectangle(frame, box, (0, 255, 0))
             cv.rectangle(frame, np.array(box), (0, 255, 0))
             cv.rectangle(frame, tuple(box), (0, 255, 0))
-            # FIXIT never properly work: cv.rectangle(frame, list(box), (0, 255, 0))
+            cv.rectangle(frame, list(box), (0, 255, 0))
 
 
     def test_classification_model(self):
index 859b595..6c106e2 100644 (file)
@@ -1162,6 +1162,53 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto_)
                     layerParams.type = "Scale";
                 }
             }
+            else if (!haveVariables)
+            {
+                Mat inp0 = getBlob(node_proto, 0);
+                Mat inp1 = getBlob(node_proto, 1);
+
+                if (inp0.size != inp1.size && (inp0.total() != 1 || inp1.total() != 1))
+                    CV_Error_(Error::StsNotImplemented, ("Different shapes case is not supported with constant inputs: %s", layer_type.c_str()));
+
+                if (inp0.total() == 1 && inp1.total() == 1 && inp0.dims != inp1.dims)
+                {
+                    if (inp0.dims < inp1.dims)
+                    {
+                        inp0 = inp0.reshape(1, inp1.dims, inp1.size);
+                        inp0.dims = inp1.dims;
+                    }
+                    else
+                    {
+                        inp1 = inp1.reshape(1, inp0.dims, inp0.size);
+                        inp1.dims = inp0.dims;
+                    }
+                }
+
+                Mat out;
+                if (inp0.total() != inp1.total())
+                {
+                    if (inp0.total() == 1)
+                    {
+                        float coeff = isDiv ? 1.0 / inp0.at<float>(0) : inp0.at<float>(0);
+                        multiply(inp1, coeff, out);
+                    }
+                    else
+                    {
+                        float coeff = isDiv ? 1.0 / inp1.at<float>(0) : inp1.at<float>(0);
+                        multiply(inp0, coeff, out);
+                    }
+
+                }
+                else
+                {
+                    out = isDiv ? inp0 / inp1 : inp0.mul(inp1);
+                }
+
+                if (inp0.dims == 1 && inp1.dims == 1)
+                    out.dims = 1;  // to workaround dims == 1
+                addConstant(layerParams.name, out);
+                return;
+            }
             else if (outShapes[node_proto.input(0)] == outShapes[node_proto.input(1)])
             {
                 layerParams.type = "Eltwise";
@@ -1201,20 +1248,6 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto_)
                 }
                 layerParams.type = "Scale";
             }
-
-            if (!haveVariables)
-            {
-                Mat inp0 = getBlob(node_proto, 0);
-                Mat inp1 = getBlob(node_proto, 1);
-                if (inp0.size != inp1.size && inp1.total() != 1)
-                    CV_Error(Error::StsNotImplemented, "Constant multiply with different shapes");
-
-                Mat out = isDiv ? inp0 / inp1 : inp0.mul(inp1);
-                out = out.reshape(1, inp0.dims, inp0.size);
-                out.dims = inp0.dims;  // to workaround dims == 1
-                addConstant(layerParams.name, out);
-                return;
-            }
         }
         else if (layer_type == "Conv")
         {
@@ -1733,9 +1766,26 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto_)
             if (!hasVariableInps)
             {
                 std::vector<Mat> inputs(node_proto.input_size()), concatenated;
+                // Due constant folding we can get inputs with different number of dimensions
+                // Insert the missing dimension to inputs
+                MatShape inputShape;
                 for (size_t i = 0; i < inputs.size(); ++i)
                 {
                     inputs[i] = getBlob(node_proto, i);
+                    if (inputs[i].size.dims() > inputShape.size())
+                    {
+                        inputShape = shape(inputs[i]);
+                    }
+                }
+
+                // Concat-1 has default value for axis is 1: https://github.com/onnx/onnx/blob/master/docs/Changelog.md#Concat-1
+                int axis = layerParams.get<int>("axis", 1);
+                for (size_t i = 0; i < inputs.size(); ++i)
+                {
+                    MatShape targetShape = inputShape;
+                    targetShape[axis] = shape(inputs[i])[axis];
+                    CV_CheckEQ(total(targetShape), total(shape(inputs[i])), "");
+                    inputs[i] = inputs[i].reshape(0, targetShape);
                 }
                 runLayer(layerParams, inputs, concatenated);
 
index e704911..cf9fa35 100644 (file)
@@ -1228,8 +1228,18 @@ void TFImporter::parseNode(const tensorflow::NodeDef& layer_)
 
             int kernel_blob_index = -1;
             const tensorflow::TensorProto& kernelTensor = getConstBlob(layer, value_id, -1, &kernel_blob_index);
-            blobFromTensor(kernelTensor, layerParams.blobs[0]);
-            releaseTensor(const_cast<tensorflow::TensorProto*>(&kernelTensor));
+            const String kernelTensorName = layer.input(kernel_blob_index);
+            std::map<String, Mat>::iterator sharedWeightsIt = sharedWeights.find(kernelTensorName);
+            if (sharedWeightsIt == sharedWeights.end())
+            {
+                blobFromTensor(kernelTensor, layerParams.blobs[0]);
+                releaseTensor(const_cast<tensorflow::TensorProto*>(&kernelTensor));
+                sharedWeights[kernelTensorName] = layerParams.blobs[0];
+            }
+            else
+            {
+                layerParams.blobs[0] = sharedWeightsIt->second;
+            }
 
             if (kernel_blob_index == 1) { // In this case output is computed by x*W formula - W should be transposed
                 Mat data = layerParams.blobs[0].t();
index a9d781d..ff36721 100644 (file)
@@ -706,6 +706,11 @@ TEST_P(Test_ONNX_layers, Mish)
     testONNXModels("mish");
 }
 
+TEST_P(Test_ONNX_layers, CalculatePads)
+{
+    testONNXModels("calc_pads");
+}
+
 TEST_P(Test_ONNX_layers, Conv1d)
 {
     testONNXModels("conv1d");
index afc9ce2..67fbc38 100644 (file)
@@ -1118,26 +1118,20 @@ static void icvScreenToClient( HWND hwnd, RECT* rect )
 /* Calculatess the window coordinates relative to the upper left corner of the mainhWnd window */
 static RECT icvCalcWindowRect( CvWindow* window )
 {
-    const int gutter = 1;
-    RECT crect = { 0 }, trect = { 0 } , rect = { 0 };
+    RECT crect = { 0 }, trect = { 0 }, rect = { 0 };
 
     assert(window);
 
     GetClientRect(window->frame, &crect);
-    if(window->toolbar.toolbar)
+    if (window->toolbar.toolbar)
     {
         GetWindowRect(window->toolbar.toolbar, &trect);
         icvScreenToClient(window->frame, &trect);
-        SubtractRect( &rect, &crect, &trect);
+        SubtractRect(&rect, &crect, &trect);
     }
     else
         rect = crect;
 
-    rect.top += gutter;
-    rect.left += gutter;
-    rect.bottom -= gutter;
-    rect.right -= gutter;
-
     return rect;
 }
 
index 1515240..1e7bfd6 100644 (file)
@@ -407,6 +407,63 @@ void pyPopulateArgumentConversionErrors()
     }
 }
 
+struct SafeSeqItem
+{
+    PyObject * item;
+    SafeSeqItem(PyObject *obj, size_t idx) { item = PySequence_GetItem(obj, idx); }
+    ~SafeSeqItem() { Py_XDECREF(item); }
+
+private:
+    SafeSeqItem(const SafeSeqItem&); // = delete
+    SafeSeqItem& operator=(const SafeSeqItem&); // = delete
+};
+
+template <class T>
+class RefWrapper
+{
+public:
+    RefWrapper(T& item) : item_(item) {}
+
+    T& get() CV_NOEXCEPT { return item_; }
+
+private:
+    T& item_;
+};
+
+// In order to support this conversion on 3.x branch - use custom reference_wrapper
+// and C-style array instead of std::array<T, N>
+template <class T, std::size_t N>
+bool parseSequence(PyObject* obj, RefWrapper<T> (&value)[N], const ArgInfo& info)
+{
+    if (!obj || obj == Py_None)
+    {
+        return true;
+    }
+    if (!PySequence_Check(obj))
+    {
+        failmsg("Can't parse '%s'. Input argument doesn't provide sequence "
+                "protocol", info.name);
+        return false;
+    }
+    const std::size_t sequenceSize = PySequence_Size(obj);
+    if (sequenceSize != N)
+    {
+        failmsg("Can't parse '%s'. Expected sequence length %lu, got %lu",
+                info.name, N, sequenceSize);
+        return false;
+    }
+    for (std::size_t i = 0; i < N; ++i)
+    {
+        SafeSeqItem seqItem(obj, i);
+        if (!pyopencv_to(seqItem.item, value[i].get(), info))
+        {
+            failmsg("Can't parse '%s'. Sequence item with index %lu has a "
+                    "wrong type", info.name, i);
+            return false;
+        }
+    }
+    return true;
+}
 } // namespace
 
 typedef std::vector<uchar> vector_uchar;
@@ -781,13 +838,6 @@ static PyObject* pyopencv_from(void*& ptr)
     return PyLong_FromVoidPtr(ptr);
 }
 
-struct SafeSeqItem
-{
-    PyObject * item;
-    SafeSeqItem(PyObject *obj, size_t idx) { item = PySequence_GetItem(obj, idx); }
-    ~SafeSeqItem() { Py_XDECREF(item); }
-};
-
 static bool pyopencv_to(PyObject *o, Scalar& s, const ArgInfo& info)
 {
     if(!o || o == Py_None)
@@ -1138,10 +1188,9 @@ bool pyopencv_to(PyObject* obj, String &value, const ArgInfo& info)
 template<>
 bool pyopencv_to(PyObject* obj, Size& sz, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
-        return true;
-    return PyArg_ParseTuple(obj, "ii", &sz.width, &sz.height) > 0;
+    RefWrapper<int> values[] = {RefWrapper<int>(sz.width),
+                                RefWrapper<int>(sz.height)};
+    return parseSequence(obj, values, info);
 }
 
 template<>
@@ -1153,10 +1202,9 @@ PyObject* pyopencv_from(const Size& sz)
 template<>
 bool pyopencv_to(PyObject* obj, Size_<float>& sz, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
-        return true;
-    return PyArg_ParseTuple(obj, "ff", &sz.width, &sz.height) > 0;
+    RefWrapper<float> values[] = {RefWrapper<float>(sz.width),
+                                  RefWrapper<float>(sz.height)};
+    return parseSequence(obj, values, info);
 }
 
 template<>
@@ -1166,6 +1214,15 @@ PyObject* pyopencv_from(const Size_<float>& sz)
 }
 
 template<>
+bool pyopencv_to(PyObject* obj, Rect& r, const ArgInfo& info)
+{
+    RefWrapper<int> values[] = {RefWrapper<int>(r.x), RefWrapper<int>(r.y),
+                                RefWrapper<int>(r.width),
+                                RefWrapper<int>(r.height)};
+    return parseSequence(obj, values, info);
+}
+
+template<>
 PyObject* pyopencv_from(const Rect& r)
 {
     return Py_BuildValue("(iiii)", r.x, r.y, r.width, r.height);
@@ -1174,10 +1231,10 @@ PyObject* pyopencv_from(const Rect& r)
 template<>
 bool pyopencv_to(PyObject* obj, Rect2d& r, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
-        return true;
-    return PyArg_ParseTuple(obj, "dddd", &r.x, &r.y, &r.width, &r.height) > 0;
+    RefWrapper<double> values[] = {
+        RefWrapper<double>(r.x), RefWrapper<double>(r.y),
+        RefWrapper<double>(r.width), RefWrapper<double>(r.height)};
+    return parseSequence(obj, values, info);
 }
 
 template<>
@@ -1189,44 +1246,17 @@ PyObject* pyopencv_from(const Rect2d& r)
 template<>
 bool pyopencv_to(PyObject* obj, Range& r, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
-        return true;
-    while (PySequence_Check(obj))
+    if (!obj || obj == Py_None)
     {
-        if (2 != PySequence_Size(obj))
-        {
-            failmsg("Range value for argument '%s' is longer than 2", info.name);
-            return false;
-        }
-        {
-            SafeSeqItem item_wrap(obj, 0);
-            PyObject *item = item_wrap.item;
-            if (PyInt_Check(item)) {
-                r.start = (int)PyInt_AsLong(item);
-            } else {
-                failmsg("Range.start value for argument '%s' is not integer", info.name);
-                break;
-            }
-        }
-        {
-            SafeSeqItem item_wrap(obj, 1);
-            PyObject *item = item_wrap.item;
-            if (PyInt_Check(item)) {
-                r.end = (int)PyInt_AsLong(item);
-            } else {
-                failmsg("Range.end value for argument '%s' is not integer", info.name);
-                break;
-            }
-        }
         return true;
     }
-    if(PyObject_Size(obj) == 0)
+    if (PyObject_Size(obj) == 0)
     {
         r = Range::all();
         return true;
     }
-    return PyArg_ParseTuple(obj, "ii", &r.start, &r.end) > 0;
+    RefWrapper<int> values[] = {RefWrapper<int>(r.start), RefWrapper<int>(r.end)};
+    return parseSequence(obj, values, info);
 }
 
 template<>
@@ -1238,64 +1268,42 @@ PyObject* pyopencv_from(const Range& r)
 template<>
 bool pyopencv_to(PyObject* obj, Point& p, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
-        return true;
-    if(PyComplex_Check(obj))
-    {
-        p.x = saturate_cast<int>(PyComplex_RealAsDouble(obj));
-        p.y = saturate_cast<int>(PyComplex_ImagAsDouble(obj));
-        return true;
-    }
-    return PyArg_ParseTuple(obj, "ii", &p.x, &p.y) > 0;
+    RefWrapper<int> values[] = {RefWrapper<int>(p.x), RefWrapper<int>(p.y)};
+    return parseSequence(obj, values, info);
 }
 
-template<>
+template <>
 bool pyopencv_to(PyObject* obj, Point2f& p, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
-        return true;
-    if (PyComplex_Check(obj))
-    {
-        p.x = saturate_cast<float>(PyComplex_RealAsDouble(obj));
-        p.y = saturate_cast<float>(PyComplex_ImagAsDouble(obj));
-        return true;
-    }
-    return PyArg_ParseTuple(obj, "ff", &p.x, &p.y) > 0;
+    RefWrapper<float> values[] = {RefWrapper<float>(p.x),
+                                  RefWrapper<float>(p.y)};
+    return parseSequence(obj, values, info);
 }
 
 template<>
 bool pyopencv_to(PyObject* obj, Point2d& p, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
-        return true;
-    if(PyComplex_Check(obj))
-    {
-        p.x = PyComplex_RealAsDouble(obj);
-        p.y = PyComplex_ImagAsDouble(obj);
-        return true;
-    }
-    return PyArg_ParseTuple(obj, "dd", &p.x, &p.y) > 0;
+    RefWrapper<double> values[] = {RefWrapper<double>(p.x),
+                                   RefWrapper<double>(p.y)};
+    return parseSequence(obj, values, info);
 }
 
 template<>
 bool pyopencv_to(PyObject* obj, Point3f& p, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
-        return true;
-    return PyArg_ParseTuple(obj, "fff", &p.x, &p.y, &p.z) > 0;
+    RefWrapper<float> values[] = {RefWrapper<float>(p.x),
+                                  RefWrapper<float>(p.y),
+                                  RefWrapper<float>(p.z)};
+    return parseSequence(obj, values, info);
 }
 
 template<>
 bool pyopencv_to(PyObject* obj, Point3d& p, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
-        return true;
-    return PyArg_ParseTuple(obj, "ddd", &p.x, &p.y, &p.z) > 0;
+    RefWrapper<double> values[] = {RefWrapper<double>(p.x),
+                                   RefWrapper<double>(p.y),
+                                   RefWrapper<double>(p.z)};
+    return parseSequence(obj, values, info);
 }
 
 template<>
@@ -1318,74 +1326,66 @@ PyObject* pyopencv_from(const Point3f& p)
 
 static bool pyopencv_to(PyObject* obj, Vec4d& v, ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if (!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "dddd", &v[0], &v[1], &v[2], &v[3]) > 0;
+    RefWrapper<double> values[] = {RefWrapper<double>(v[0]), RefWrapper<double>(v[1]),
+                                   RefWrapper<double>(v[2]), RefWrapper<double>(v[3])};
+    return parseSequence(obj, values, info);
 }
 
 static bool pyopencv_to(PyObject* obj, Vec4f& v, ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if (!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "ffff", &v[0], &v[1], &v[2], &v[3]) > 0;
+    RefWrapper<float> values[] = {RefWrapper<float>(v[0]), RefWrapper<float>(v[1]),
+                                  RefWrapper<float>(v[2]), RefWrapper<float>(v[3])};
+    return parseSequence(obj, values, info);
 }
 
 static bool pyopencv_to(PyObject* obj, Vec4i& v, ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if (!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "iiii", &v[0], &v[1], &v[2], &v[3]) > 0;
+    RefWrapper<int> values[] = {RefWrapper<int>(v[0]), RefWrapper<int>(v[1]),
+                                RefWrapper<int>(v[2]), RefWrapper<int>(v[3])};
+    return parseSequence(obj, values, info);
 }
 
 static bool pyopencv_to(PyObject* obj, Vec3d& v, ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if (!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "ddd", &v[0], &v[1], &v[2]) > 0;
+    RefWrapper<double> values[] = {RefWrapper<double>(v[0]),
+                                   RefWrapper<double>(v[1]),
+                                   RefWrapper<double>(v[2])};
+    return parseSequence(obj, values, info);
 }
 
 static bool pyopencv_to(PyObject* obj, Vec3f& v, ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if (!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "fff", &v[0], &v[1], &v[2]) > 0;
+    RefWrapper<float> values[] = {RefWrapper<float>(v[0]),
+                                  RefWrapper<float>(v[1]),
+                                  RefWrapper<float>(v[2])};
+    return parseSequence(obj, values, info);
 }
 
 static bool pyopencv_to(PyObject* obj, Vec3i& v, ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if (!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "iii", &v[0], &v[1], &v[2]) > 0;
+    RefWrapper<int> values[] = {RefWrapper<int>(v[0]), RefWrapper<int>(v[1]),
+                                RefWrapper<int>(v[2])};
+    return parseSequence(obj, values, info);
 }
 
 static bool pyopencv_to(PyObject* obj, Vec2d& v, ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if (!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "dd", &v[0], &v[1]) > 0;
+    RefWrapper<double> values[] = {RefWrapper<double>(v[0]),
+                                   RefWrapper<double>(v[1])};
+    return parseSequence(obj, values, info);
 }
 
 static bool pyopencv_to(PyObject* obj, Vec2f& v, ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if (!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "ff", &v[0], &v[1]) > 0;
+    RefWrapper<float> values[] = {RefWrapper<float>(v[0]),
+                                  RefWrapper<float>(v[1])};
+    return parseSequence(obj, values, info);
 }
 
 static bool pyopencv_to(PyObject* obj, Vec2i& v, ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if (!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "ii", &v[0], &v[1]) > 0;
+    RefWrapper<int> values[] = {RefWrapper<int>(v[0]), RefWrapper<int>(v[1])};
+    return parseSequence(obj, values, info);
 }
 
 template<>
@@ -1766,39 +1766,54 @@ template<> struct pyopencvVecConverter<RotatedRect>
 };
 
 template<>
-bool pyopencv_to(PyObject* obj, Rect& r, const ArgInfo& info)
+bool pyopencv_to(PyObject* obj, TermCriteria& dst, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj || obj == Py_None)
+    if (!obj || obj == Py_None)
+    {
         return true;
-
-    if (PyTuple_Check(obj))
-        return PyArg_ParseTuple(obj, "iiii", &r.x, &r.y, &r.width, &r.height) > 0;
-    else
+    }
+    if (!PySequence_Check(obj))
+    {
+        failmsg("Can't parse '%s' as TermCriteria."
+                "Input argument doesn't provide sequence protocol",
+                info.name);
+        return false;
+    }
+    const std::size_t sequenceSize = PySequence_Size(obj);
+    if (sequenceSize != 3) {
+        failmsg("Can't parse '%s' as TermCriteria. Expected sequence length 3, "
+                "got %lu",
+                info.name, sequenceSize);
+        return false;
+    }
     {
-        std::vector<int> value(4);
-        if (!pyopencvVecConverter<int>::to(obj, value, info))
+        const String typeItemName = format("'%s' criteria type", info.name);
+        const ArgInfo typeItemInfo(typeItemName.c_str(), false);
+        SafeSeqItem typeItem(obj, 0);
+        if (!pyopencv_to(typeItem.item, dst.type, typeItemInfo))
         {
             return false;
         }
-        if (value.size() != 4)
+    }
+    {
+        const String maxCountItemName = format("'%s' max count", info.name);
+        const ArgInfo maxCountItemInfo(maxCountItemName.c_str(), false);
+        SafeSeqItem maxCountItem(obj, 1);
+        if (!pyopencv_to(maxCountItem.item, dst.maxCount, maxCountItemInfo))
         {
-            failmsg("Expected 4 values for '%s', got %d", info.name, (int)value.size());
             return false;
         }
-        r = Rect(value[0], value[1], value[2], value[3]);
-        return true;
     }
-
-}
-
-template<>
-bool pyopencv_to(PyObject *obj, TermCriteria& dst, const ArgInfo& info)
-{
-    CV_UNUSED(info);
-    if(!obj)
-        return true;
-    return PyArg_ParseTuple(obj, "iid", &dst.type, &dst.maxCount, &dst.epsilon) > 0;
+    {
+        const String epsilonItemName = format("'%s' epsilon", info.name);
+        const ArgInfo epsilonItemInfo(epsilonItemName.c_str(), false);
+        SafeSeqItem epsilonItem(obj, 2);
+        if (!pyopencv_to(epsilonItem.item, dst.epsilon, epsilonItemInfo))
+        {
+            return false;
+        }
+    }
+    return true;
 }
 
 template<>
@@ -1808,12 +1823,54 @@ PyObject* pyopencv_from(const TermCriteria& src)
 }
 
 template<>
-bool pyopencv_to(PyObject *obj, RotatedRect& dst, const ArgInfo& info)
+bool pyopencv_to(PyObjectobj, RotatedRect& dst, const ArgInfo& info)
 {
-    CV_UNUSED(info);
-    if(!obj)
+    if (!obj || obj == Py_None)
+    {
         return true;
-    return PyArg_ParseTuple(obj, "(ff)(ff)f", &dst.center.x, &dst.center.y, &dst.size.width, &dst.size.height, &dst.angle) > 0;
+    }
+    if (!PySequence_Check(obj))
+    {
+        failmsg("Can't parse '%s' as RotatedRect."
+                "Input argument doesn't provide sequence protocol",
+                info.name);
+        return false;
+    }
+    const std::size_t sequenceSize = PySequence_Size(obj);
+    if (sequenceSize != 3)
+    {
+        failmsg("Can't parse '%s' as RotatedRect. Expected sequence length 3, got %lu",
+                info.name, sequenceSize);
+        return false;
+    }
+    {
+        const String centerItemName = format("'%s' center point", info.name);
+        const ArgInfo centerItemInfo(centerItemName.c_str(), false);
+        SafeSeqItem centerItem(obj, 0);
+        if (!pyopencv_to(centerItem.item, dst.center, centerItemInfo))
+        {
+            return false;
+        }
+    }
+    {
+        const String sizeItemName = format("'%s' size", info.name);
+        const ArgInfo sizeItemInfo(sizeItemName.c_str(), false);
+        SafeSeqItem sizeItem(obj, 1);
+        if (!pyopencv_to(sizeItem.item, dst.size, sizeItemInfo))
+        {
+            return false;
+        }
+    }
+    {
+        const String angleItemName = format("'%s' angle", info.name);
+        const ArgInfo angleItemInfo(angleItemName.c_str(), false);
+        SafeSeqItem angleItem(obj, 2);
+        if (!pyopencv_to(angleItem.item, dst.angle, angleItemInfo))
+        {
+            return false;
+        }
+    }
+    return true;
 }
 
 template<>
index 171139d..4a380e6 100644 (file)
@@ -3,6 +3,7 @@ from __future__ import print_function
 
 import ctypes
 from functools import partial
+from collections import namedtuple
 
 import numpy as np
 import cv2 as cv
@@ -379,6 +380,84 @@ class Arguments(NewOpenCVTests):
             with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
                 _ = cv.utils.dumpString(not_convertible)
 
+    def test_parse_to_rect_convertible(self):
+        Rect = namedtuple('Rect', ('x', 'y', 'w', 'h'))
+        try_to_convert = partial(self._try_to_convert, cv.utils.dumpRect)
+        for convertible in ((1, 2, 4, 5), [5, 3, 10, 20], np.array([10, 20, 23, 10]),
+                            Rect(10, 30, 40, 55), tuple(np.array([40, 20, 24, 20])),
+                            list(np.array([20, 40, 30, 35]))):
+            expected = 'rect: (x={}, y={}, w={}, h={})'.format(*convertible)
+            actual = try_to_convert(convertible)
+            self.assertEqual(expected, actual,
+                             msg=get_conversion_error_msg(convertible, expected, actual))
+
+    def test_parse_to_rect_not_convertible(self):
+        for not_convertible in (np.empty(shape=(4, 1)), (), [], np.array([]), (12, ),
+                                [3, 4, 5, 10, 123], {1: 2, 3:4, 5:10, 6:30},
+                                '1234', np.array([1, 2, 3, 4], dtype=np.float32),
+                                np.array([[1, 2], [3, 4], [5, 6], [6, 8]]), (1, 2, 5, 1.5)):
+            with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
+                _ = cv.utils.dumpRect(not_convertible)
+
+    def test_parse_to_rotated_rect_convertible(self):
+        RotatedRect = namedtuple('RotatedRect', ('center', 'size', 'angle'))
+        try_to_convert = partial(self._try_to_convert, cv.utils.dumpRotatedRect)
+        for convertible in (((2.5, 2.5), (10., 20.), 12.5), [[1.5, 10.5], (12.5, 51.5), 10],
+                            RotatedRect((10, 40), np.array([10.5, 20.5]), 5),
+                            np.array([[10, 6], [50, 50], 5.5], dtype=object)):
+            center, size, angle = convertible
+            expected = 'rotated_rect: (c_x={:.6f}, c_y={:.6f}, w={:.6f},' \
+                       ' h={:.6f}, a={:.6f})'.format(center[0], center[1],
+                                                     size[0], size[1], angle)
+            actual = try_to_convert(convertible)
+            self.assertEqual(expected, actual,
+                             msg=get_conversion_error_msg(convertible, expected, actual))
+
+    def test_parse_to_rotated_rect_not_convertible(self):
+        for not_convertible in ([], (), np.array([]), (123, (45, 34), 1), {1: 2, 3: 4}, 123,
+                                np.array([[123, 123, 14], [1, 3], 56], dtype=object), '123'):
+            with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
+                _ = cv.utils.dumpRotatedRect(not_convertible)
+
+    def test_parse_to_term_criteria_convertible(self):
+        TermCriteria = namedtuple('TermCriteria', ('type', 'max_count', 'epsilon'))
+        try_to_convert = partial(self._try_to_convert, cv.utils.dumpTermCriteria)
+        for convertible in ((1, 10, 1e-3), [2, 30, 1e-1], np.array([10, 20, 0.5], dtype=object),
+                            TermCriteria(0, 5, 0.1)):
+            expected = 'term_criteria: (type={}, max_count={}, epsilon={:.6f}'.format(*convertible)
+            actual = try_to_convert(convertible)
+            self.assertEqual(expected, actual,
+                             msg=get_conversion_error_msg(convertible, expected, actual))
+
+    def test_parse_to_term_criteria_not_convertible(self):
+        for not_convertible in ([], (), np.array([]), [1, 4], (10,), (1.5, 34, 0.1),
+                                {1: 5, 3: 5, 10: 10}, '145'):
+            with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
+                _ = cv.utils.dumpTermCriteria(not_convertible)
+
+    def test_parse_to_range_convertible_to_all(self):
+        try_to_convert = partial(self._try_to_convert, cv.utils.dumpRange)
+        for convertible in ((), [], np.array([])):
+            expected = 'range: all'
+            actual = try_to_convert(convertible)
+            self.assertEqual(expected, actual,
+                             msg=get_conversion_error_msg(convertible, expected, actual))
+
+    def test_parse_to_range_convertible(self):
+        Range = namedtuple('Range', ('start', 'end'))
+        try_to_convert = partial(self._try_to_convert, cv.utils.dumpRange)
+        for convertible in ((10, 20), [-1, 3], np.array([10, 24]), Range(-4, 6)):
+            expected = 'range: (s={}, e={})'.format(*convertible)
+            actual = try_to_convert(convertible)
+            self.assertEqual(expected, actual,
+                             msg=get_conversion_error_msg(convertible, expected, actual))
+
+    def test_parse_to_range_not_convertible(self):
+        for not_convertible in ((1, ), [40, ], np.array([1, 4, 6]), {'a': 1, 'b': 40},
+                                (1.5, 13.5), [3, 6.7], np.array([6.3, 2.1]), '14, 4'):
+            with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)):
+                _ = cv.utils.dumpRange(not_convertible)
+
 
 class SamplesFindFile(NewOpenCVTests):