From 45fd4d821742ec07fe0757ede7ba410db97aa59b Mon Sep 17 00:00:00 2001 From: Abduragim Shtanchaev <44877829+Abdurrahheem@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:38:21 +0400 Subject: [PATCH] Merge pull request #26026 from Abdurrahheem:ash/python_bool_binding Add support for boolan input/outputs in python bindings #26026 This PR add support boolean input/output binding in python. The issue what mention in ticket https://github.com/opencv/opencv/issues/26024 and the PR soleves it. Data and models are located in [here](https://github.com/opencv/opencv_extra/pull/1201) ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/core/misc/python/pyopencv_core.hpp | 3 ++- modules/dnn/misc/python/test/test_dnn.py | 19 +++++++++++++++++++ modules/python/src2/cv2.cpp | 5 +++++ modules/python/src2/cv2_convert.cpp | 4 +++- modules/python/src2/cv2_numpy.cpp | 2 +- .../typing_stubs_generation/api_refinement.py | 2 +- modules/python/test/test_misc.py | 6 +++++- 7 files changed, 36 insertions(+), 5 deletions(-) diff --git a/modules/core/misc/python/pyopencv_core.hpp b/modules/core/misc/python/pyopencv_core.hpp index 1aecd5b864..40bc45a836 100644 --- a/modules/core/misc/python/pyopencv_core.hpp +++ b/modules/core/misc/python/pyopencv_core.hpp @@ -29,7 +29,8 @@ static PyObject* pycvMakeTypeCh(PyObject*, PyObject *value) { {"CV_32SC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_32SC(channels) -> retval"}, \ {"CV_32FC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_32FC(channels) -> retval"}, \ {"CV_64FC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_64FC(channels) -> retval"}, \ - {"CV_16FC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_16FC(channels) -> retval"}, + {"CV_16FC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_16FC(channels) -> retval"}, \ + {"CV_BoolC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_BoolC(channels) -> retval"}, #endif // HAVE_OPENCV_CORE #endif // OPENCV_CORE_PYOPENCV_CORE_HPP diff --git a/modules/dnn/misc/python/test/test_dnn.py b/modules/dnn/misc/python/test/test_dnn.py index e9a5b1e075..af67c1ffbf 100644 --- a/modules/dnn/misc/python/test/test_dnn.py +++ b/modules/dnn/misc/python/test/test_dnn.py @@ -546,5 +546,24 @@ class dnn_test(NewOpenCVTests): out = net.forward() self.assertEqual(out.shape, (1, 2, 3, 4)) + def test_bool_operator(self): + n = self.find_dnn_file('dnn/onnx/models/and_op.onnx') + + x = np.random.randint(0, 2, [5], dtype=np.bool_) + y = np.random.randint(0, 2, [5], dtype=np.bool_) + o = x & y + + net = cv.dnn.readNet(n) + + names = ["x", "y"] + net.setInputsNames(names) + net.setInput(x, names[0]) + net.setInput(y, names[1]) + + out = net.forward() + + self.assertTrue(np.all(out == o)) + + if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index a7837a6ff8..bbb03695ab 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -562,6 +562,11 @@ static bool init_body(PyObject * m) PUBLISH(CV_16FC2); PUBLISH(CV_16FC3); PUBLISH(CV_16FC4); + PUBLISH(CV_Bool); + PUBLISH(CV_BoolC1); + PUBLISH(CV_BoolC2); + PUBLISH(CV_BoolC3); + PUBLISH(CV_BoolC4); #undef PUBLISH_ #undef PUBLISH diff --git a/modules/python/src2/cv2_convert.cpp b/modules/python/src2/cv2_convert.cpp index 0626e42e53..cd1e4a05e9 100644 --- a/modules/python/src2/cv2_convert.cpp +++ b/modules/python/src2/cv2_convert.cpp @@ -137,7 +137,9 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info) typenum == NPY_INT32 ? CV_32S : typenum == NPY_HALF ? CV_16F : typenum == NPY_FLOAT ? CV_32F : - typenum == NPY_DOUBLE ? CV_64F : -1; + typenum == NPY_DOUBLE ? CV_64F : + typenum == NPY_BOOL ? CV_Bool : + -1; if( type < 0 ) { diff --git a/modules/python/src2/cv2_numpy.cpp b/modules/python/src2/cv2_numpy.cpp index 25922d6c61..0b6ca60121 100644 --- a/modules/python/src2/cv2_numpy.cpp +++ b/modules/python/src2/cv2_numpy.cpp @@ -37,7 +37,7 @@ UMatData* NumpyAllocator::allocate(int dims0, const int* sizes, int type, void* int typenum = depth == CV_8U ? NPY_UBYTE : depth == CV_8S ? NPY_BYTE : depth == CV_16U ? NPY_USHORT : depth == CV_16S ? NPY_SHORT : depth == CV_32S ? NPY_INT : depth == CV_32F ? NPY_FLOAT : - depth == CV_64F ? NPY_DOUBLE : depth == CV_16F ? NPY_HALF : f*NPY_ULONGLONG + (f^1)*NPY_UINT; + depth == CV_64F ? NPY_DOUBLE : depth == CV_16F ? NPY_HALF : depth == CV_Bool ? NPY_BOOL : f*NPY_ULONGLONG + (f^1)*NPY_UINT; int i, dims = dims0; cv::AutoBuffer _sizes(dims + 1); for( i = 0; i < dims; i++ ) diff --git a/modules/python/src2/typing_stubs_generation/api_refinement.py b/modules/python/src2/typing_stubs_generation/api_refinement.py index 994b802018..20fa6dd73e 100644 --- a/modules/python/src2/typing_stubs_generation/api_refinement.py +++ b/modules/python/src2/typing_stubs_generation/api_refinement.py @@ -56,7 +56,7 @@ def export_matrix_type_constants(root: NamespaceNode) -> None: MAX_PREDEFINED_CHANNELS = 4 depth_names = ("CV_8U", "CV_8S", "CV_16U", "CV_16S", "CV_32S", - "CV_32F", "CV_64F", "CV_16F") + "CV_32F", "CV_64F", "CV_16F", "CV_Bool") for depth_value, depth_name in enumerate(depth_names): # Export depth constants root.add_constant(depth_name, str(depth_value)) diff --git a/modules/python/test/test_misc.py b/modules/python/test/test_misc.py index 3d714386fc..7c7646e6c0 100644 --- a/modules/python/test/test_misc.py +++ b/modules/python/test/test_misc.py @@ -234,6 +234,7 @@ class Bindings(NewOpenCVTests): cv.CV_16UC2: [cv.CV_16U, 2, cv.CV_16UC], cv.CV_32SC1: [cv.CV_32S, 1, cv.CV_32SC], cv.CV_16FC3: [cv.CV_16F, 3, cv.CV_16FC], + cv.CV_BoolC1: [cv.CV_Bool, 1, cv.CV_BoolC], } for ref, (depth, channels, func) in data.items(): self.assertEqual(ref, cv.CV_MAKETYPE(depth, channels)) @@ -277,6 +278,9 @@ class Arguments(NewOpenCVTests): a = np.zeros((2,3,4,5), dtype='f') res7 = cv.utils.dumpInputArray(a) self.assertEqual(res7, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=120 dims(-1)=4 size(-1)=[2 3 4 5] type(-1)=CV_32FC1") + a = np.array([0, 1, 0, 1], dtype=bool) + res8 = cv.utils.dumpInputArray(a) + self.assertEqual(res8, "InputArray: empty()=false kind=0x00010000 flags=0x01010000 total(-1)=4 dims(-1)=1 size(-1)=4x1 type(-1)=CV_BoolC1") def test_InputArrayOfArrays(self): res1 = cv.utils.dumpInputArrayOfArrays(None) @@ -339,7 +343,7 @@ class Arguments(NewOpenCVTests): def test_parse_to_bool_not_convertible(self): for not_convertible in (1.2, np.float32(2.3), 's', 'str', (1, 2), [1, 2], complex(1, 1), - complex(imag=2), complex(1.1), np.array([1, 0], dtype=bool)): + complex(imag=2), complex(1.1)): with self.assertRaises((TypeError, OverflowError), msg=get_no_exception_msg(not_convertible)): _ = cv.utils.dumpBool(not_convertible)