diff --git a/modules/core/include/opencv2/core/bindings_utils.hpp b/modules/core/include/opencv2/core/bindings_utils.hpp index 5c8df7a52b..bbcc3a88d0 100644 --- a/modules/core/include/opencv2/core/bindings_utils.hpp +++ b/modules/core/include/opencv2/core/bindings_utils.hpp @@ -78,6 +78,41 @@ String testOverloadResolution(const Rect& rect) rect.width, rect.height); } +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) { diff --git a/modules/core/src/ocl.cpp b/modules/core/src/ocl.cpp index acc35bb5af..53b21e0f77 100644 --- a/modules/core/src/ocl.cpp +++ b/modules/core/src/ocl.cpp @@ -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(); } diff --git a/modules/dnn/misc/python/test/test_dnn.py b/modules/dnn/misc/python/test/test_dnn.py index afc960ef4c..746dabf4ea 100644 --- a/modules/dnn/misc/python/test/test_dnn.py +++ b/modules/dnn/misc/python/test/test_dnn.py @@ -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): diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 859b595b7f..6c106e298d 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -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(0) : inp0.at(0); + multiply(inp1, coeff, out); + } + else + { + float coeff = isDiv ? 1.0 / inp1.at(0) : inp1.at(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 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("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); diff --git a/modules/dnn/src/tensorflow/tf_importer.cpp b/modules/dnn/src/tensorflow/tf_importer.cpp index e704911c12..cf9fa35294 100644 --- a/modules/dnn/src/tensorflow/tf_importer.cpp +++ b/modules/dnn/src/tensorflow/tf_importer.cpp @@ -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(&kernelTensor)); + const String kernelTensorName = layer.input(kernel_blob_index); + std::map::iterator sharedWeightsIt = sharedWeights.find(kernelTensorName); + if (sharedWeightsIt == sharedWeights.end()) + { + blobFromTensor(kernelTensor, layerParams.blobs[0]); + releaseTensor(const_cast(&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(); diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index a9d781dfee..ff36721af0 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -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"); diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp index afc9ce27b2..67fbc380e1 100644 --- a/modules/highgui/src/window_w32.cpp +++ b/modules/highgui/src/window_w32.cpp @@ -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; } diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index 151524074b..1e7bfd62a8 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -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 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 +template +bool parseSequence(PyObject* obj, RefWrapper (&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 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 values[] = {RefWrapper(sz.width), + RefWrapper(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_& 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 values[] = {RefWrapper(sz.width), + RefWrapper(sz.height)}; + return parseSequence(obj, values, info); } template<> @@ -1165,6 +1213,15 @@ PyObject* pyopencv_from(const Size_& sz) return Py_BuildValue("(ff)", sz.width, sz.height); } +template<> +bool pyopencv_to(PyObject* obj, Rect& r, const ArgInfo& info) +{ + RefWrapper values[] = {RefWrapper(r.x), RefWrapper(r.y), + RefWrapper(r.width), + RefWrapper(r.height)}; + return parseSequence(obj, values, info); +} + template<> PyObject* pyopencv_from(const Rect& r) { @@ -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 values[] = { + RefWrapper(r.x), RefWrapper(r.y), + RefWrapper(r.width), RefWrapper(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 values[] = {RefWrapper(r.start), RefWrapper(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(PyComplex_RealAsDouble(obj)); - p.y = saturate_cast(PyComplex_ImagAsDouble(obj)); - return true; - } - return PyArg_ParseTuple(obj, "ii", &p.x, &p.y) > 0; + RefWrapper values[] = {RefWrapper(p.x), RefWrapper(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(PyComplex_RealAsDouble(obj)); - p.y = saturate_cast(PyComplex_ImagAsDouble(obj)); - return true; - } - return PyArg_ParseTuple(obj, "ff", &p.x, &p.y) > 0; + RefWrapper values[] = {RefWrapper(p.x), + RefWrapper(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 values[] = {RefWrapper(p.x), + RefWrapper(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 values[] = {RefWrapper(p.x), + RefWrapper(p.y), + RefWrapper(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 values[] = {RefWrapper(p.x), + RefWrapper(p.y), + RefWrapper(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 values[] = {RefWrapper(v[0]), RefWrapper(v[1]), + RefWrapper(v[2]), RefWrapper(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 values[] = {RefWrapper(v[0]), RefWrapper(v[1]), + RefWrapper(v[2]), RefWrapper(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 values[] = {RefWrapper(v[0]), RefWrapper(v[1]), + RefWrapper(v[2]), RefWrapper(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 values[] = {RefWrapper(v[0]), + RefWrapper(v[1]), + RefWrapper(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 values[] = {RefWrapper(v[0]), + RefWrapper(v[1]), + RefWrapper(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 values[] = {RefWrapper(v[0]), RefWrapper(v[1]), + RefWrapper(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 values[] = {RefWrapper(v[0]), + RefWrapper(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 values[] = {RefWrapper(v[0]), + RefWrapper(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 values[] = {RefWrapper(v[0]), RefWrapper(v[1])}; + return parseSequence(obj, values, info); } template<> @@ -1766,39 +1766,54 @@ template<> struct pyopencvVecConverter }; 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) - return true; - - if (PyTuple_Check(obj)) - return PyArg_ParseTuple(obj, "iiii", &r.x, &r.y, &r.width, &r.height) > 0; - else + if (!obj || obj == Py_None) { - std::vector value(4); - if (!pyopencvVecConverter::to(obj, value, info)) - { - return false; - } - if (value.size() != 4) - { - 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; + 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; + } + { + 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; + } + } + { + 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)) + { + return false; + } + } + { + 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(PyObject* obj, 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<> diff --git a/modules/python/test/test_misc.py b/modules/python/test/test_misc.py index 171139dab4..4a380e69ef 100644 --- a/modules/python/test/test_misc.py +++ b/modules/python/test/test_misc.py @@ -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):