Merge pull request #30183 from abhishek-gola/python-ml-dtypes

python: keep exact bfloat16 / float8 dtypes in the bindings via ml_dtypes
This commit is contained in:
Abhishek Gola
2026-10-31 20:26:08 +05:30
committed by GitHub
11 changed files with 441 additions and 47 deletions

View File

@@ -118,9 +118,30 @@ static PyObject* pycvRegisterMatType(PyObject *self, PyObject *value)
Py_RETURN_NONE;
}
static PyObject* pycvSetUseExactNumpyDtypes(PyObject*, PyObject* value)
{
int flag = PyObject_IsTrue(value);
if (flag < 0)
return NULL;
setUseExactNumpyDtypes(flag != 0);
Py_RETURN_NONE;
}
static PyObject* pycvUseExactNumpyDtypes(PyObject*, PyObject*)
{
return PyBool_FromLong(useExactNumpyDtypes());
}
static PyMethodDef special_methods[] = {
{"_registerMatType", (PyCFunction)(pycvRegisterMatType), METH_O, "_registerMatType(cv.Mat) -> None (Internal)"},
{"redirectError", CV_PY_FN_WITH_KW(pycvRedirectError), "redirectError(onError) -> None"},
{"setUseExactNumpyDtypes", (PyCFunction)(pycvSetUseExactNumpyDtypes), METH_O,
"setUseExactNumpyDtypes(flag) -> None\n. Keep output bfloat16 / float8 arrays at their exact dtype "
"(registered by the ml_dtypes package, which must be imported) instead of widening them "
"to float32. Off by default. Input arrays are accepted regardless of this flag. "
"The flag is process-wide: set it in the application, libraries should not change it."},
{"useExactNumpyDtypes", (PyCFunction)(pycvUseExactNumpyDtypes), METH_NOARGS,
"useExactNumpyDtypes() -> retval"},
#ifdef HAVE_OPENCV_HIGHGUI
{"createTrackbar", (PyCFunction)pycvCreateTrackbar, METH_VARARGS, "createTrackbar(trackbarName, windowName, value, count, onChange) -> None"},
{"createButton", CV_PY_FN_WITH_KW(pycvCreateButton), "createButton(buttonName, onChange [, userData, buttonType, initialButtonState]) -> None"},
@@ -594,6 +615,21 @@ static bool init_body(PyObject * m)
PUBLISH(CV_BoolC2);
PUBLISH(CV_BoolC3);
PUBLISH(CV_BoolC4);
PUBLISH(CV_16BF);
PUBLISH(CV_16BFC1);
PUBLISH(CV_16BFC2);
PUBLISH(CV_16BFC3);
PUBLISH(CV_16BFC4);
PUBLISH(CV_8F_E4M3FN);
PUBLISH(CV_8F_E4M3FNC1);
PUBLISH(CV_8F_E4M3FNC2);
PUBLISH(CV_8F_E4M3FNC3);
PUBLISH(CV_8F_E4M3FNC4);
PUBLISH(CV_8F_E4M3FNUZ);
PUBLISH(CV_8F_E4M3FNUZC1);
PUBLISH(CV_8F_E4M3FNUZC2);
PUBLISH(CV_8F_E4M3FNUZC3);
PUBLISH(CV_8F_E4M3FNUZC4);
#undef PUBLISH_
#undef PUBLISH

View File

@@ -151,6 +151,8 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info)
bool needcopy = !PyArray_ISNOTSWAPPED(oarr), needcast = needcopy;
int typenum = PyArray_TYPE(oarr), new_typenum = typenum;
int type = numpyTypeToCvDepth(typenum);
if( type < 0 )
type = nonConventionalTypeToCvDepth(typenum); // bfloat16 / float8 registered by the caller
if( type < 0 )
{
@@ -285,18 +287,18 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info)
for(int i = 0; i < sz && filled; i++ )
{
PyObject* oi = PyArray_GETITEM(oarr, base_ptr + step[0] * i); // new object
if( PyInt_Check(oi) )
m.at<double>(i) = (double)PyInt_AsLong(oi);
else if( PyFloat_Check(oi) )
m.at<double>(i) = (double)PyFloat_AsDouble(oi);
else
double v = oi ? PyFloat_AsDouble(oi) : -1.0;
if( v == -1.0 && PyErr_Occurred() )
{
PyErr_Clear();
failmsg("%s has some non-numerical elements", info.name);
m.release();
filled = false;
}
else
m.at<double>(i) = v;
Py_DECREF(oi);
Py_XDECREF(oi);
}
if(needcopy)
@@ -332,42 +334,67 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info)
return true;
}
// Copies an empty or 0-D Mat into a new array of dtype `descr`, which must match m.depth().
// Steals the reference to `descr`. Channels become the last axis, so a 0-D Mat has shape (cn,).
static PyObject* pyopencv_from_new_array(const cv::Mat& m, PyArray_Descr* descr)
{
const int cn = m.channels();
int dims = m.dims;
cv::AutoBuffer<npy_intp> _sizes(dims + 1);
for( int i = 0; i < dims; i++ )
_sizes[i] = (npy_intp)m.size[i];
if( cn > 1 )
_sizes[dims++] = cn;
PyObject* o = PyArray_NewFromDescr(&PyArray_Type, descr, dims, _sizes.data(),
nullptr, nullptr, 0, nullptr);
if( o && !m.empty() )
memcpy(PyArray_DATA((PyArrayObject*)o), m.data, m.elemSize());
return o;
}
template<>
PyObject* pyopencv_from(const cv::Mat& m)
{
// NumPy has no bfloat16 or float8 dtype: widen these to float32 (lossless).
if( m.depth() == CV_16BF || m.depth() == CV_8F_E4M3FN || m.depth() == CV_8F_E4M3FNUZ )
PyArray_Descr* exotic = nullptr;
if( isNonConventionalDepth(m.depth()) )
{
cv::Mat m32f;
ERRWRAP2(m.convertTo(m32f, CV_32F));
return pyopencv_from(m32f);
}
if( m.empty() )
{
// empty() also catches a live buffer with a zero-length dim: return an empty array, not None.
if( m.dims >= 1 )
exotic = nonConventionalDescrForCvDepth(m.depth());
if( !exotic )
{
int cn = m.channels();
int typenum = cvDepthToNumpyType(m.depth());
int dims = m.dims;
cv::AutoBuffer<npy_intp> _sizes(dims + 1);
for( int i = 0; i < dims; i++ )
_sizes[i] = (npy_intp)m.size[i];
if( cn > 1 )
_sizes[dims++] = cn;
return PyArray_SimpleNew(dims, _sizes.data(), typenum);
// The caller's own array (e.g. dst=) already has the exact dtype: return it as is.
// Like the shared path below, this assumes m spans the whole array, not a ROI of it.
if( m.u && m.allocator == &GetNumpyAllocator() )
{
PyObject* o = (PyObject*)m.u->userdata;
Py_INCREF(o);
return o;
}
if( useExactNumpyDtypes() )
CV_LOG_ONCE_WARNING(NULL, "Python bindings: setUseExactNumpyDtypes(True) is set, "
"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 )
Py_RETURN_NONE;
}
// 0D (scalar) Mat: return a true 0D numpy array.
if( m.dims == 0 )
// empty() also catches a live buffer with a zero-length dim: return an empty array, not None.
// A 0D (scalar) Mat becomes a true 0D numpy array.
if( m.empty() || m.dims == 0 )
{
int typenum = cvDepthToNumpyType(CV_MAT_DEPTH(m.type()));
PyObject* o = PyArray_SimpleNew(0, nullptr, typenum);
if( o )
memcpy(PyArray_DATA((PyArrayObject*)o), m.data, CV_ELEM_SIZE(m.type()));
return o;
PyArray_Descr* descr = exotic;
if( descr )
Py_INCREF(descr);
else
descr = PyArray_DescrFromType(cvDepthToNumpyType(m.depth()));
return pyopencv_from_new_array(m, descr);
}
// NumpyAllocator gives bfloat16 / float8 arrays their exact dtype here too.
cv::Mat temp, *p = (cv::Mat*)&m;
if(!p->u || p->allocator != &GetNumpyAllocator())
{

View File

@@ -34,19 +34,35 @@ UMatData* NumpyAllocator::allocate(int dims0, const int* sizes, int type, void*
int depth = CV_MAT_DEPTH(type);
int cn = CV_MAT_CN(type);
// Backing a CV_16BF Mat with the float32 buffer cvDepthToNumpyType() asks for
// would pair a 2-byte element step with 4-byte NumPy strides and corrupt the data.
if( depth == CV_16BF )
CV_Error(Error::StsNotImplemented,
"CV_16BF (bfloat16) arrays cannot be allocated through the NumPy allocator: "
"NumPy has no bfloat16 dtype");
int typenum = cvDepthToNumpyType(depth);
int i, dims = dims0;
cv::AutoBuffer<npy_intp> _sizes(dims + 1);
for( i = 0; i < dims; i++ )
_sizes[i] = sizes[i];
if( cn > 1 )
_sizes[dims++] = cn;
// bfloat16 / float8 need their exact NumPy descriptor: cvDepthToNumpyType()'s float32
// fallback would pair a narrow element step with wide NumPy strides and corrupt the buffer.
PyArray_Descr* exotic = nonConventionalDescrForCvDepth(depth);
if( exotic )
{
Py_INCREF(exotic); // PyArray_NewFromDescr steals the reference
PyObject* eo = PyArray_NewFromDescr(&PyArray_Type, exotic, dims, _sizes.data(),
nullptr, nullptr, 0, nullptr);
if(!eo)
CV_Error_(Error::StsError, ("The numpy array of depth=%d, ndims=%d can not be created", depth, dims));
return allocate(eo, dims0, sizes, type, step);
}
// Mat::create() falls back to the default allocator on this error;
// pyopencv_from() then widens the result to float32.
if( isNonConventionalDepth(depth) )
CV_Error(Error::StsNotImplemented, useExactNumpyDtypes()
? "bfloat16 / float8 arrays cannot be allocated through the NumPy allocator: "
"import ml_dtypes first"
: "bfloat16 / float8 arrays cannot be allocated through the NumPy allocator: "
"call cv.setUseExactNumpyDtypes(True) first");
int typenum = cvDepthToNumpyType(depth);
PyObject* o = PyArray_SimpleNew(dims, _sizes.data(), typenum);
if(!o)
CV_Error_(Error::StsError, ("The numpy array of typenum=%d, ndims=%d can not be created", typenum, dims));

View File

@@ -1,8 +1,17 @@
#define NO_IMPORT_ARRAY // the numpy C-API table is imported once, in cv2.cpp
#define PY_ARRAY_UNIQUE_SYMBOL opencv_ARRAY_API
#include "cv2_util.hpp"
#include "opencv2/core.hpp"
#include "opencv2/core/utils/configuration.private.hpp"
#include "opencv2/core/utils/logger.hpp"
// NumPy 2.x headers provide PyDataType_ELSIZE() as an inline function, not a macro;
// 1.x headers expose the field directly.
#if NPY_ABI_VERSION < 0x02000000
#define PyDataType_ELSIZE(descr) ((descr)->elsize)
#endif
PyObject* opencv_error = NULL;
cv::TLSData<std::vector<std::string> > conversionErrorsTLS;
@@ -21,8 +30,6 @@ int cvDepthToNumpyType(int depth)
case CV_32F: return NPY_FLOAT;
case CV_64F: return NPY_DOUBLE;
case CV_16F: return NPY_HALF;
// NumPy has no bfloat16 dtype; pyopencv_from() converts the values to float32.
case CV_16BF: return NPY_FLOAT;
case CV_Bool: return NPY_BOOL;
default:
CV_Error(cv::Error::StsNotImplemented,
@@ -30,6 +37,99 @@ int cvDepthToNumpyType(int depth)
}
}
namespace {
// Dtypes registered by ml_dtypes on import. Their type numbers depend on registration order,
// so they are resolved by name; OpenCV never imports ml_dtypes itself.
struct NonConventionalDtype
{
int depth;
const char* name;
int itemsize;
};
const NonConventionalDtype kNonConventionalDtypes[] = {
{ CV_16BF, "bfloat16", 2 },
{ CV_8F_E4M3FN, "float8_e4m3fn", 1 },
{ CV_8F_E4M3FNUZ, "float8_e4m3fnuz", 1 },
};
const int kNumNonConventionalDtypes =
sizeof(kNonConventionalDtypes) / sizeof(kNonConventionalDtypes[0]);
bool g_useExactNumpyDtypes = false;
// Process-lifetime cache, accessed under the GIL. Misses are not cached, so a later
// `import ml_dtypes` still takes effect.
PyArray_Descr* g_nonConventionalDescrs[kNumNonConventionalDtypes] = {};
PyArray_Descr* nonConventionalDescr(int idx)
{
if (!g_nonConventionalDescrs[idx])
{
const NonConventionalDtype& t = kNonConventionalDtypes[idx];
PyObject* name = PyUnicode_FromString(t.name);
PyArray_Descr* descr = nullptr;
if (!name || !PyArray_DescrConverter(name, &descr))
descr = nullptr;
Py_XDECREF(name);
PyErr_Clear();
// Reject a same-named dtype with another layout.
if (descr && (descr->type_num < NPY_USERDEF || PyDataType_ELSIZE(descr) != t.itemsize))
{
Py_DECREF(descr);
descr = nullptr;
}
g_nonConventionalDescrs[idx] = descr;
}
return g_nonConventionalDescrs[idx];
}
int nonConventionalIndex(int depth)
{
for (int i = 0; i < kNumNonConventionalDtypes; i++)
if (kNonConventionalDtypes[i].depth == depth)
return i;
return -1;
}
} // namespace
void setUseExactNumpyDtypes(bool flag)
{
g_useExactNumpyDtypes = flag;
}
bool useExactNumpyDtypes()
{
return g_useExactNumpyDtypes;
}
bool isNonConventionalDepth(int depth)
{
return nonConventionalIndex(depth) >= 0;
}
PyArray_Descr* nonConventionalDescrForCvDepth(int depth)
{
const int idx = nonConventionalIndex(depth);
if (!g_useExactNumpyDtypes || idx < 0)
return nullptr;
return nonConventionalDescr(idx);
}
int nonConventionalTypeToCvDepth(int typenum)
{
if (typenum < NPY_USERDEF)
return -1;
for (int i = 0; i < kNumNonConventionalDtypes; i++)
{
PyArray_Descr* descr = nonConventionalDescr(i);
if (descr && descr->type_num == typenum)
return kNonConventionalDtypes[i].depth;
}
return -1;
}
int numpyTypeToCvDepth(int typenum)
{
// Only canonical NPY_* values may be used as case labels: the fixed-width

View File

@@ -17,6 +17,15 @@ PyObject* failmsgp(const char *fmt, ...);
int cvDepthToNumpyType(int depth);
int numpyTypeToCvDepth(int typenum);
// Output bfloat16 / float8 Mats keep their exact dtype only when enabled (off by default).
void setUseExactNumpyDtypes(bool flag);
bool useExactNumpyDtypes();
bool isNonConventionalDepth(int depth);
// Output descriptor: nullptr when disabled, unregistered, or without an OpenCV counterpart.
PyArray_Descr* nonConventionalDescrForCvDepth(int depth);
// Input depth: -1 when unregistered or without an OpenCV counterpart; ignores the output flag.
int nonConventionalTypeToCvDepth(int typenum);
//======================================================================================================================
class PyAllowThreads

View File

@@ -58,6 +58,12 @@ def apply_manual_api_refinement(root: NamespaceNode) -> None:
)
])
root.add_function("setUseExactNumpyDtypes", [
FunctionNode.Arg("flag", PrimitiveTypeNode.bool_())
])
root.add_function("useExactNumpyDtypes",
return_type=FunctionNode.RetType(PrimitiveTypeNode.bool_()))
def make_optional_none_return(root_node: NamespaceNode,
function_symbol_name: SymbolName) -> None:
@@ -75,16 +81,20 @@ def make_optional_none_return(root_node: NamespaceNode,
def export_matrix_type_constants(root: NamespaceNode) -> None:
MAX_PREDEFINED_CHANNELS = 4
CN_SHIFT = 5
depth_names = ("CV_8U", "CV_8S", "CV_16U", "CV_16S", "CV_32U", "CV_32S",
"CV_64U", "CV_64S", "CV_32F", "CV_64F", "CV_16F", "CV_16BF" "CV_Bool")
for depth_value, depth_name in enumerate(depth_names):
# Values must match modules/core/include/opencv2/core/hal/interface.h
depths = (("CV_8U", 0), ("CV_8S", 1), ("CV_16U", 2), ("CV_16S", 3),
("CV_32S", 4), ("CV_32F", 5), ("CV_64F", 6), ("CV_16F", 7),
("CV_16BF", 8), ("CV_Bool", 9), ("CV_64U", 10), ("CV_64S", 11),
("CV_32U", 12), ("CV_8F_E4M3FN", 13), ("CV_8F_E4M3FNUZ", 14))
for depth_name, depth_value in depths:
# Export depth constants
root.add_constant(depth_name, str(depth_value))
# Export predefined types
for c in range(MAX_PREDEFINED_CHANNELS):
root.add_constant(f"{depth_name}C{c + 1}",
f"{depth_value + 8 * c}")
f"{depth_value + (c << CN_SHIFT)}")
# Export type creation function
root.add_function(
f"{depth_name}C",

View File

@@ -240,6 +240,19 @@ class Bindings(NewOpenCVTests):
self.assertEqual(ref, cv.CV_MAKETYPE(depth, channels))
self.assertEqual(ref, func(channels))
def test_type_constants_for_all_depths(self):
# Same depth list as export_matrix_type_constants() in api_refinement.py.
depths = ("CV_8U", "CV_8S", "CV_16U", "CV_16S", "CV_32S", "CV_32F", "CV_64F", "CV_16F",
"CV_16BF", "CV_Bool", "CV_64U", "CV_64S", "CV_32U",
"CV_8F_E4M3FN", "CV_8F_E4M3FNUZ")
for name in depths:
depth = getattr(cv, name)
make_type = getattr(cv, name + "C")
for channels in range(1, 5):
ref = cv.CV_MAKETYPE(depth, channels)
self.assertEqual(getattr(cv, "%sC%d" % (name, channels)), ref, name)
self.assertEqual(make_type(channels), ref, name)
class Arguments(NewOpenCVTests):

View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python
from __future__ import print_function
import numpy as np
import cv2 as cv
try:
import ml_dtypes
except ImportError:
ml_dtypes = None
from tests_common import NewOpenCVTests
NON_CONVENTIONAL_DEPTHS = (cv.CV_16BF, cv.CV_8F_E4M3FN, cv.CV_8F_E4M3FNUZ)
class non_conventional_dtypes_test(NewOpenCVTests):
# Runs with or without ml_dtypes.
def setUp(self):
super(non_conventional_dtypes_test, self).setUp()
self.addCleanup(cv.setUseExactNumpyDtypes, cv.useExactNumpyDtypes())
cv.setUseExactNumpyDtypes(False)
def test_flag_roundtrip(self):
cv.setUseExactNumpyDtypes(True)
self.assertTrue(cv.useExactNumpyDtypes())
cv.setUseExactNumpyDtypes(False)
self.assertFalse(cv.useExactNumpyDtypes())
def test_disabled_widens_to_float32(self):
for depth in NON_CONVENTIONAL_DEPTHS:
for m in cv.utils.generateVectorOfMat(1, 3, 4, depth):
self.assertEqual(m.dtype, np.float32)
def test_enabled_without_ml_dtypes_widens_to_float32(self):
if ml_dtypes is not None:
self.skipTest("ml_dtypes is installed")
cv.setUseExactNumpyDtypes(True)
for depth in NON_CONVENTIONAL_DEPTHS:
for m in cv.utils.generateVectorOfMat(1, 3, 4, depth):
self.assertEqual(m.dtype, np.float32)
def test_short_bool_array_scalar_operands(self):
for channels in range(1, 5):
src = np.zeros((2, 3, channels), dtype=np.float32)
operand = np.array([True, False, True, True][:channels])
np.testing.assert_array_equal(cv.add(src, operand),
cv.add(src, tuple(float(v) for v in operand)))
class ml_dtypes_test(NewOpenCVTests):
def setUp(self):
super(ml_dtypes_test, self).setUp()
if ml_dtypes is None:
self.skipTest("ml_dtypes is not installed")
self.addCleanup(cv.setUseExactNumpyDtypes, cv.useExactNumpyDtypes())
cv.setUseExactNumpyDtypes(True)
# (numpy dtype, OpenCV depth, typeToString prefix, relative half-ULP)
self.cases = [
(ml_dtypes.bfloat16, cv.CV_16BF, "CV_16BF", 2.0 ** -8),
(ml_dtypes.float8_e4m3fn, cv.CV_8F_E4M3FN, "CV_8F", 2.0 ** -4),
(ml_dtypes.float8_e4m3fnuz, cv.CV_8F_E4M3FNUZ, "CV_8FNUZ", 2.0 ** -4),
]
def assertSameBits(self, actual, expected):
self.assertEqual(actual.dtype, expected.dtype)
self.assertEqual(actual.shape, expected.shape)
self.assertTrue(np.array_equal(np.ascontiguousarray(actual).view(np.uint8),
np.ascontiguousarray(expected).view(np.uint8)))
def test_numpy_to_mat_keeps_exact_type(self):
for dt, _, name, _ in self.cases:
a = np.arange(6, dtype=np.float32).reshape(2, 3).astype(dt)
self.assertIn("type(-1)=%sC1" % name, cv.utils.dumpInputArray(a))
self.assertIn("type(-1)=%sC3" % name, cv.utils.dumpInputArray(np.stack([a, a, a], -1)))
self.assertIn("type(-1)=%sC1" % name, cv.utils.dumpInputArray(np.array(1.5, dtype=dt)))
def test_inputs_accepted_with_outputs_disabled(self):
cv.setUseExactNumpyDtypes(False)
for dt, _, name, _ in self.cases:
a = np.arange(6, dtype=np.float32).reshape(2, 3).astype(dt)
self.assertIn("type(-1)=%sC1" % name, cv.utils.dumpInputArray(a))
result = cv.flip(a, 1)
self.assertEqual(result.dtype, np.float32)
np.testing.assert_array_equal(result, a[:, ::-1].astype(np.float32))
# The caller's own dst keeps its dtype and is returned as is.
d = np.empty_like(a)
self.assertIs(cv.flip(a, 1, dst=d), d)
self.assertSameBits(d, a[:, ::-1])
def test_short_array_scalar_operands(self):
# A float32 src keeps the result float32, so the operand is checked with the flag off too.
for exact in (False, True):
cv.setUseExactNumpyDtypes(exact)
for dt, _, _, _ in self.cases:
for channels in range(1, 5):
src = np.zeros((2, 3, channels), dtype=np.float32)
values = np.arange(channels, dtype=np.float32) - 1
operand = values.astype(dt)
expected = cv.add(src, tuple(float(v) for v in values))
reversed_expected = cv.add(src, tuple(float(v) for v in values[::-1]))
np.testing.assert_array_equal(cv.add(src, operand), expected)
np.testing.assert_array_equal(cv.add(src, operand[::-1]), reversed_expected)
img = np.zeros((2, 3, 3), dtype=ml_dtypes.bfloat16)
operand = np.array([1, 2, 3], dtype=ml_dtypes.bfloat16)
self.assertSameBits(cv.add(img, operand), img + operand)
def test_roundtrip_through_numpy_allocator(self):
# 240 is the largest finite float8_e4m3fnuz value, so every case keeps these values exactly.
src = np.array([[0.5, -1.25, 3.0, 7.0], [240.0, -240.0, 0.0, -0.5]], dtype=np.float32)
for dt, _, _, _ in self.cases:
a = src.astype(dt)
np.testing.assert_array_equal(a.astype(np.float32), src)
self.assertSameBits(cv.flip(a, 1), a[:, ::-1])
self.assertSameBits(cv.transpose(a), a.T)
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:
mats = cv.utils.generateVectorOfMat(2, 3, 4, depth)
self.assertEqual(len(mats), len(ref))
for m, r in zip(mats, ref):
self.assertEqual(m.dtype, dt)
self.assertEqual(m.shape, (3, 4))
np.testing.assert_allclose(m.astype(np.float32), r, rtol=rtol, atol=2.0 ** -9)
if __name__ == '__main__':
NewOpenCVTests.bootstrap()