diff --git a/modules/python/src2/cv2_convert.cpp b/modules/python/src2/cv2_convert.cpp index bba7039b36..777aa4235b 100644 --- a/modules/python/src2/cv2_convert.cpp +++ b/modules/python/src2/cv2_convert.cpp @@ -334,30 +334,22 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info) return true; } -// Wraps m in a NumPy array of dtype `descr`; the caller guarantees it matches m.depth(). +// Builds an empty or 0-D array of dtype `descr`; the caller guarantees it matches m.depth(). static PyObject* pyopencv_from_nonconventional(const cv::Mat& m, PyArray_Descr* descr) { - cv::Mat cont; - if( m.isContinuous() ) - cont = m; - else - ERRWRAP2(cont = m.clone()); - - const int cn = cont.channels(); - int dims = cont.dims; + const int cn = m.channels(); + int dims = m.dims; cv::AutoBuffer _sizes(dims + 1); for( int i = 0; i < dims; i++ ) - _sizes[i] = (npy_intp)cont.size[i]; + _sizes[i] = (npy_intp)m.size[i]; if( cn > 1 ) _sizes[dims++] = cn; Py_INCREF(descr); // PyArray_NewFromDescr steals the reference PyObject* o = PyArray_NewFromDescr(&PyArray_Type, descr, dims, _sizes.data(), nullptr, nullptr, 0, nullptr); - if( !o ) - return nullptr; - if( !cont.empty() ) - memcpy(PyArray_DATA((PyArrayObject*)o), cont.data, cont.total() * cont.elemSize()); + if( o && !m.empty() ) + memcpy(PyArray_DATA((PyArrayObject*)o), m.data, m.elemSize()); return o; } @@ -366,16 +358,21 @@ PyObject* pyopencv_from(const cv::Mat& m) { if( isNonConventionalDepth(m.depth()) ) { - if( PyArray_Descr* descr = nonConventionalDescrForCvDepth(m.depth()) ) + PyArray_Descr* descr = nonConventionalDescrForCvDepth(m.depth()); + if( !descr ) + { + if( getAllowNonConventionalDataTypes() ) + CV_LOG_ONCE_WARNING(NULL, "Python bindings: non-conventional data types are allowed, " + "but NumPy does not know bfloat16 / float8 and they are returned as float32. " + "Import ml_dtypes to keep the exact dtype."); + // NumPy alone cannot name these; widen to float32 (lossless). + cv::Mat m32f; + ERRWRAP2(m.convertTo(m32f, CV_32F)); + return pyopencv_from(m32f); + } + if( m.empty() || m.dims == 0 ) return pyopencv_from_nonconventional(m, descr); - if( getAllowNonConventionalDataTypes() ) - CV_LOG_ONCE_WARNING(NULL, "Python bindings: non-conventional data types are allowed, " - "but NumPy does not know bfloat16 / float8 and they are returned as float32. " - "Import ml_dtypes to keep the exact dtype."); - // NumPy alone cannot name these; widen to float32 (lossless). - cv::Mat m32f; - ERRWRAP2(m.convertTo(m32f, CV_32F)); - return pyopencv_from(m32f); + // Otherwise share the path below: NumpyAllocator allocates with `descr`. } if( m.empty() ) { diff --git a/modules/python/test/test_ml_dtypes.py b/modules/python/test/test_ml_dtypes.py index 912defee0b..c8afb7eaa7 100644 --- a/modules/python/test/test_ml_dtypes.py +++ b/modules/python/test/test_ml_dtypes.py @@ -59,6 +59,12 @@ class ml_dtypes_test(NewOpenCVTests): strided = a[:, ::2] self.assertSameBits(cv.flip(strided, 0), strided[::-1]) + def test_dst_is_returned(self): + for dt, _, _, _ in self.cases: + a = np.arange(6, dtype=np.float32).reshape(2, 3).astype(dt) + d = np.empty_like(a) + self.assertIs(cv.flip(a, 1, dst=d), d) + def test_mat_allocated_in_cpp_to_numpy(self): ref = cv.utils.generateVectorOfMat(2, 3, 4, cv.CV_32F) for dt, depth, _, rtol in self.cases: