feat: update conversion logic for `std::vector<T>` in Python bindings

`PyObject*` to `std::vector<T>` conversion logic:
- If user passed Numpy Array
  - If array is planar and T is a primitive type (doesn't require
    constructor call) that matches with the element type of array, then
    copy element one by one with the respect of the step between array
    elements. If compiler is lucky (or brave enough) copy loop can be
    vectorized.
    For classes that require constructor calls this path is not
    possible, because we can't begin an object lifetime without hacks.
  - Otherwise fall-back to general case
- Otherwise - execute the general case:
  If PyObject* corresponds to Sequence protocol - iterate over the
  sequence elements and invoke the appropriate `pyopencv_to` function.

`std::vector<T>` to `PyObject*` conversion logic:
- If `std::vector<T>` is empty - return empty tuple.
- If `T` has a corresponding `Mat` `DataType` than return
  Numpy array instance of the matching `dtype` e.g.
  `std::vector<cv::Rect>` is returned as `np.ndarray` of shape `Nx4` and
  `dtype=int`.
  This branch helps to optimize further evaluations in user code.
- Otherwise - execute the general case:
  Construct a tuple of length N = `std::vector::size` and insert
  elements one by one.

Unnecessary functions were removed and code was rearranged to allow
compiler select the appropriate conversion function specialization.
pull/20618/head
Vadim Levin 3 years ago
parent df83459721
commit 16b9514543
  1. 47
      modules/core/include/opencv2/core/bindings_utils.hpp
  2. 47
      modules/core/src/bindings_utils.cpp
  3. 493
      modules/python/src2/cv2.cpp
  4. 9
      modules/python/test/test_legacy.py
  5. 103
      modules/python/test/test_misc.py

@ -122,6 +122,53 @@ String testReservedKeywordConversion(int positional_argument, int lambda = 2, in
return format("arg=%d, lambda=%d, from=%d", positional_argument, lambda, from);
}
CV_EXPORTS_W String dumpVectorOfInt(const std::vector<int>& vec);
CV_EXPORTS_W String dumpVectorOfDouble(const std::vector<double>& vec);
CV_EXPORTS_W String dumpVectorOfRect(const std::vector<Rect>& vec);
CV_WRAP static inline
void generateVectorOfRect(size_t len, CV_OUT std::vector<Rect>& vec)
{
vec.resize(len);
if (len > 0)
{
RNG rng(12345);
Mat tmp(static_cast<int>(len), 1, CV_32SC4);
rng.fill(tmp, RNG::UNIFORM, 10, 20);
tmp.copyTo(vec);
}
}
CV_WRAP static inline
void generateVectorOfInt(size_t len, CV_OUT std::vector<int>& vec)
{
vec.resize(len);
if (len > 0)
{
RNG rng(554433);
Mat tmp(static_cast<int>(len), 1, CV_32SC1);
rng.fill(tmp, RNG::UNIFORM, -10, 10);
tmp.copyTo(vec);
}
}
CV_WRAP static inline
void generateVectorOfMat(size_t len, int rows, int cols, int dtype, CV_OUT std::vector<Mat>& vec)
{
vec.resize(len);
if (len > 0)
{
RNG rng(65431);
for (size_t i = 0; i < len; ++i)
{
vec[i].create(rows, cols, dtype);
rng.fill(vec[i], RNG::UNIFORM, 0, 10);
}
}
}
CV_WRAP static inline
void testRaiseGeneralException()
{

@ -5,6 +5,7 @@
#include "precomp.hpp"
#include "opencv2/core/bindings_utils.hpp"
#include <sstream>
#include <iomanip>
namespace cv { namespace utils {
@ -208,4 +209,50 @@ CV_EXPORTS_W String dumpInputOutputArrayOfArrays(InputOutputArrayOfArrays argume
return ss.str();
}
static inline std::ostream& operator<<(std::ostream& os, const cv::Rect& rect)
{
return os << "[x=" << rect.x << ", y=" << rect.y << ", w=" << rect.width << ", h=" << rect.height << ']';
}
template <class T, class Formatter>
static inline String dumpVector(const std::vector<T>& vec, Formatter format)
{
std::ostringstream oss("[", std::ios::ate);
if (!vec.empty())
{
oss << format << vec[0];
for (std::size_t i = 1; i < vec.size(); ++i)
{
oss << ", " << format << vec[i];
}
}
oss << "]";
return oss.str();
}
static inline std::ostream& noFormat(std::ostream& os)
{
return os;
}
static inline std::ostream& floatFormat(std::ostream& os)
{
return os << std::fixed << std::setprecision(2);
}
String dumpVectorOfInt(const std::vector<int>& vec)
{
return dumpVector(vec, &noFormat);
}
String dumpVectorOfDouble(const std::vector<double>& vec)
{
return dumpVector(vec, &floatFormat);
}
String dumpVectorOfRect(const std::vector<Rect>& vec)
{
return dumpVector(vec, &noFormat);
}
}} // namespace

@ -493,6 +493,33 @@ bool parseSequence(PyObject* obj, RefWrapper<T> (&value)[N], const ArgInfo& info
}
} // namespace
namespace traits {
template <bool Value>
struct BooleanConstant
{
static const bool value = Value;
typedef BooleanConstant<Value> type;
};
typedef BooleanConstant<true> TrueType;
typedef BooleanConstant<false> FalseType;
template <class T>
struct VoidType {
typedef void type;
};
template <class T, class DType = void>
struct IsRepresentableAsMatDataType : FalseType
{
};
template <class T>
struct IsRepresentableAsMatDataType<T, typename VoidType<typename DataType<T>::channel_type>::type> : TrueType
{
};
} // namespace traits
typedef std::vector<uchar> vector_uchar;
typedef std::vector<char> vector_char;
typedef std::vector<int> vector_int;
@ -1042,6 +1069,30 @@ bool pyopencv_to(PyObject* obj, uchar& value, const ArgInfo& info)
return ivalue != -1 || !PyErr_Occurred();
}
template<>
bool pyopencv_to(PyObject* obj, char& value, const ArgInfo& info)
{
if (!obj || obj == Py_None)
{
return true;
}
if (isBool(obj))
{
failmsg("Argument '%s' must be an integer, not bool", info.name);
return false;
}
if (PyArray_IsIntegerScalar(obj))
{
value = saturate_cast<char>(PyArray_PyIntAsInt(obj));
}
else
{
failmsg("Argument '%s' is required to be an integer", info.name);
return false;
}
return !CV_HAS_CONVERSION_ERROR(value);
}
template<>
PyObject* pyopencv_from(const double& value)
{
@ -1454,277 +1505,12 @@ PyObject* pyopencv_from(const Point3d& p)
return Py_BuildValue("(ddd)", p.x, p.y, p.z);
}
template<typename _Tp> struct pyopencvVecConverter
{
typedef typename DataType<_Tp>::channel_type _Cp;
static inline bool copyOneItem(PyObject *obj, size_t start, int channels, _Cp * data)
{
for(size_t j = 0; (int)j < channels; j++ )
{
SafeSeqItem sub_item_wrap(obj, start + j);
PyObject* item_ij = sub_item_wrap.item;
if( PyInt_Check(item_ij))
{
int v = (int)PyInt_AsLong(item_ij);
if( v == -1 && PyErr_Occurred() )
return false;
data[j] = saturate_cast<_Cp>(v);
}
else if( PyLong_Check(item_ij))
{
int v = (int)PyLong_AsLong(item_ij);
if( v == -1 && PyErr_Occurred() )
return false;
data[j] = saturate_cast<_Cp>(v);
}
else if( PyFloat_Check(item_ij))
{
double v = PyFloat_AsDouble(item_ij);
if( PyErr_Occurred() )
return false;
data[j] = saturate_cast<_Cp>(v);
}
else
return false;
}
return true;
}
static bool to(PyObject* obj, std::vector<_Tp>& value, const ArgInfo& info)
{
if(!obj || obj == Py_None)
return true;
if (PyArray_Check(obj))
{
Mat m;
pyopencv_to(obj, m, info);
m.copyTo(value);
return true;
}
else if (PySequence_Check(obj))
{
const int type = traits::Type<_Tp>::value;
const int depth = CV_MAT_DEPTH(type), channels = CV_MAT_CN(type);
size_t i, n = PySequence_Size(obj);
value.resize(n);
for (i = 0; i < n; i++ )
{
SafeSeqItem item_wrap(obj, i);
PyObject* item = item_wrap.item;
_Cp* data = (_Cp*)&value[i];
if( channels == 2 && PyComplex_Check(item) )
{
data[0] = saturate_cast<_Cp>(PyComplex_RealAsDouble(item));
data[1] = saturate_cast<_Cp>(PyComplex_ImagAsDouble(item));
}
else if( channels > 1 )
{
if( PyArray_Check(item))
{
Mat src;
pyopencv_to(item, src, info);
if( src.dims != 2 || src.channels() != 1 ||
((src.cols != 1 || src.rows != channels) &&
(src.cols != channels || src.rows != 1)))
break;
Mat dst(src.rows, src.cols, depth, data);
src.convertTo(dst, type);
if( dst.data != (uchar*)data )
break;
}
else if (PySequence_Check(item))
{
if (!copyOneItem(item, 0, channels, data))
break;
}
else
{
break;
}
}
else if (channels == 1)
{
if (!copyOneItem(obj, i, channels, data))
break;
}
else
{
break;
}
}
return i == n;
}
return false;
}
static PyObject* from(const std::vector<_Tp>& value)
{
if(value.empty())
return PyTuple_New(0);
int type = traits::Type<_Tp>::value;
int depth = CV_MAT_DEPTH(type), channels = CV_MAT_CN(type);
Mat src((int)value.size(), channels, depth, (uchar*)&value[0]);
return pyopencv_from(src);
}
};
template<typename _Tp>
bool pyopencv_to(PyObject* obj, std::vector<_Tp>& value, const ArgInfo& info)
{
return pyopencvVecConverter<_Tp>::to(obj, value, info);
}
template<typename _Tp>
PyObject* pyopencv_from(const std::vector<_Tp>& value)
{
return pyopencvVecConverter<_Tp>::from(value);
}
template<typename _Tp> static inline bool pyopencv_to_generic_vec(PyObject* obj, std::vector<_Tp>& value, const ArgInfo& info)
{
if(!obj || obj == Py_None)
return true;
if (!PySequence_Check(obj))
return false;
size_t n = PySequence_Size(obj);
value.resize(n);
for(size_t i = 0; i < n; i++ )
{
SafeSeqItem item_wrap(obj, i);
if(!pyopencv_to(item_wrap.item, value[i], info))
return false;
}
return true;
}
template<typename _Tp> static inline PyObject* pyopencv_from_generic_vec(const std::vector<_Tp>& value)
{
int i, n = (int)value.size();
PyObject* seq = PyList_New(n);
for( i = 0; i < n; i++ )
{
PyObject* item = pyopencv_from(value[i]);
if(!item)
break;
PyList_SetItem(seq, i, item);
}
if( i < n )
{
Py_DECREF(seq);
return 0;
}
return seq;
}
template<>
PyObject* pyopencv_from(const std::pair<int, double>& src)
{
return Py_BuildValue("(id)", src.first, src.second);
}
template<typename _Tp, typename _Tr> struct pyopencvVecConverter<std::pair<_Tp, _Tr> >
{
static bool to(PyObject* obj, std::vector<std::pair<_Tp, _Tr> >& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<std::pair<_Tp, _Tr> >& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<typename _Tp> struct pyopencvVecConverter<std::vector<_Tp> >
{
static bool to(PyObject* obj, std::vector<std::vector<_Tp> >& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<std::vector<_Tp> >& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<> struct pyopencvVecConverter<Mat>
{
static bool to(PyObject* obj, std::vector<Mat>& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<Mat>& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<> struct pyopencvVecConverter<UMat>
{
static bool to(PyObject* obj, std::vector<UMat>& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<UMat>& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<> struct pyopencvVecConverter<KeyPoint>
{
static bool to(PyObject* obj, std::vector<KeyPoint>& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<KeyPoint>& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<> struct pyopencvVecConverter<DMatch>
{
static bool to(PyObject* obj, std::vector<DMatch>& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<DMatch>& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<> struct pyopencvVecConverter<String>
{
static bool to(PyObject* obj, std::vector<String>& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<String>& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<> struct pyopencvVecConverter<RotatedRect>
{
static bool to(PyObject* obj, std::vector<RotatedRect>& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<RotatedRect>& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<>
bool pyopencv_to(PyObject* obj, TermCriteria& dst, const ArgInfo& info)
{
@ -1852,6 +1638,183 @@ PyObject* pyopencv_from(const Moments& m)
"nu30", m.nu30, "nu21", m.nu21, "nu12", m.nu12, "nu03", m.nu03);
}
template <typename Tp>
struct pyopencvVecConverter;
template <typename Tp>
bool pyopencv_to(PyObject* obj, std::vector<Tp>& value, const ArgInfo& info)
{
if (!obj || obj == Py_None)
{
return true;
}
return pyopencvVecConverter<Tp>::to(obj, value, info);
}
template <typename Tp>
PyObject* pyopencv_from(const std::vector<Tp>& value)
{
return pyopencvVecConverter<Tp>::from(value);
}
template <typename Tp>
static bool pyopencv_to_generic_vec(PyObject* obj, std::vector<Tp>& value, 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 size_t n = static_cast<size_t>(PySequence_Size(obj));
value.resize(n);
for (size_t i = 0; i < n; i++)
{
SafeSeqItem item_wrap(obj, i);
if (!pyopencv_to(item_wrap.item, value[i], info))
{
failmsg("Can't parse '%s'. Sequence item with index %lu has a wrong type", info.name, i);
return false;
}
}
return true;
}
template <typename Tp>
static PyObject* pyopencv_from_generic_vec(const std::vector<Tp>& value)
{
Py_ssize_t n = static_cast<Py_ssize_t>(value.size());
PySafeObject seq(PyTuple_New(n));
for (Py_ssize_t i = 0; i < n; i++)
{
PyObject* item = pyopencv_from(value[i]);
// If item can't be assigned - PyTuple_SetItem raises exception and returns -1.
if (!item || PyTuple_SetItem(seq, i, item) == -1)
{
return NULL;
}
}
return seq.release();
}
template <typename Tp>
struct pyopencvVecConverter
{
typedef typename std::vector<Tp>::iterator VecIt;
static bool to(PyObject* obj, std::vector<Tp>& value, const ArgInfo& info)
{
if (!PyArray_Check(obj))
{
return pyopencv_to_generic_vec(obj, value, info);
}
// If user passed an array it is possible to make faster conversions in several cases
PyArrayObject* array_obj = reinterpret_cast<PyArrayObject*>(obj);
const NPY_TYPES target_type = asNumpyType<Tp>();
const NPY_TYPES source_type = static_cast<NPY_TYPES>(PyArray_TYPE(array_obj));
if (target_type == NPY_OBJECT)
{
// Non-planar arrays representing objects (e.g. array of N Rect is an array of shape Nx4) have NPY_OBJECT
// as their target type.
return pyopencv_to_generic_vec(obj, value, info);
}
if (PyArray_NDIM(array_obj) > 1)
{
failmsg("Can't parse %dD array as '%s' vector argument", PyArray_NDIM(array_obj), info.name);
return false;
}
if (target_type != source_type)
{
// Source type requires conversion
// Allowed conversions for target type is handled in the corresponding pyopencv_to function
return pyopencv_to_generic_vec(obj, value, info);
}
// For all other cases, all array data can be directly copied to std::vector data
// Simple `memcpy` is not possible because NumPy array can reference a slice of the bigger array:
// ```
// arr = np.ones((8, 4, 5), dtype=np.int32)
// convertible_to_vector_of_int = arr[:, 0, 1]
// ```
value.resize(static_cast<size_t>(PyArray_SIZE(array_obj)));
const npy_intp item_step = PyArray_STRIDE(array_obj, 0) / PyArray_ITEMSIZE(array_obj);
const Tp* data_ptr = static_cast<Tp*>(PyArray_DATA(array_obj));
for (VecIt it = value.begin(); it != value.end(); ++it, data_ptr += item_step) {
*it = *data_ptr;
}
return true;
}
static PyObject* from(const std::vector<Tp>& value)
{
if (value.empty())
{
return PyTuple_New(0);
}
return from(value, ::traits::IsRepresentableAsMatDataType<Tp>());
}
private:
static PyObject* from(const std::vector<Tp>& value, ::traits::FalseType)
{
// Underlying type is not representable as Mat Data Type
return pyopencv_from_generic_vec(value);
}
static PyObject* from(const std::vector<Tp>& value, ::traits::TrueType)
{
// Underlying type is representable as Mat Data Type, so faster return type is available
typedef DataType<Tp> DType;
typedef typename DType::channel_type UnderlyingArrayType;
// If Mat is always exposed as NumPy array this code path can be reduced to the following snipped:
// Mat src(value);
// PyObject* array = pyopencv_from(src);
// return PyArray_Squeeze(reinterpret_cast<PyArrayObject*>(array));
// This puts unnecessary restrictions on Mat object those might be avoided without losing the performance.
// Moreover, this version is a bit faster, because it doesn't create temporary objects with reference counting.
const NPY_TYPES target_type = asNumpyType<UnderlyingArrayType>();
const int cols = DType::channels;
PyObject* array;
if (cols == 1)
{
npy_intp dims = static_cast<npy_intp>(value.size());
array = PyArray_SimpleNew(1, &dims, target_type);
}
else
{
npy_intp dims[2] = {static_cast<npy_intp>(value.size()), cols};
array = PyArray_SimpleNew(2, dims, target_type);
}
if(!array)
{
// NumPy arrays with shape (N, 1) and (N) are not equal, so correct error message should distinguish
// them too.
String shape;
if (cols > 1)
{
shape = cv::format("(%d x %d)", static_cast<int>(value.size()), cols);
}
else
{
shape = cv::format("(%d)", static_cast<int>(value.size()));
}
CV_Error_(Error::StsError, ("Can't allocate NumPy array for vector with dtype=%d shape=%s",
static_cast<int>(target_type), static_cast<int>(value.size()), shape.c_str()));
}
// Fill the array
PyArrayObject* array_obj = reinterpret_cast<PyArrayObject*>(array);
UnderlyingArrayType* array_data = static_cast<UnderlyingArrayType*>(PyArray_DATA(array_obj));
// if Tp is representable as Mat DataType, so the following cast is pretty safe...
const UnderlyingArrayType* value_data = reinterpret_cast<const UnderlyingArrayType*>(value.data());
memcpy(array_data, value_data, sizeof(UnderlyingArrayType) * value.size() * static_cast<size_t>(cols));
return array;
}
};
static int OnError(int status, const char *func_name, const char *err_msg, const char *file_name, int line, void *userdata)
{
PyGILState_STATE gstate;

@ -20,8 +20,13 @@ class Hackathon244Tests(NewOpenCVTests):
flag, ajpg = cv.imencode("img_q90.jpg", a, [cv.IMWRITE_JPEG_QUALITY, 90])
self.assertEqual(flag, True)
self.assertEqual(ajpg.dtype, np.uint8)
self.assertGreater(ajpg.shape[0], 1)
self.assertEqual(ajpg.shape[1], 1)
self.assertTrue(isinstance(ajpg, np.ndarray), "imencode returned buffer of wrong type: {}".format(type(ajpg)))
self.assertEqual(len(ajpg.shape), 1, "imencode returned buffer with wrong shape: {}".format(ajpg.shape))
self.assertGreaterEqual(len(ajpg), 1, "imencode length of the returned buffer should be at least 1")
self.assertLessEqual(
len(ajpg), a.size,
"imencode length of the returned buffer shouldn't exceed number of elements in original image"
)
def test_projectPoints(self):
objpt = np.float64([[1,2,3]])

@ -480,6 +480,109 @@ class Arguments(NewOpenCVTests):
cv.utils.testReservedKeywordConversion(20, lambda_=-4, from_=12), format_str.format(20, -4, 12)
)
def test_parse_vector_int_convertible(self):
np.random.seed(123098765)
try_to_convert = partial(self._try_to_convert, cv.utils.dumpVectorOfInt)
arr = np.random.randint(-20, 20, 40).astype(np.int32).reshape(10, 2, 2)
int_min, int_max = get_limits(ctypes.c_int)
for convertible in ((int_min, 1, 2, 3, int_max), [40, 50], tuple(),
np.array([int_min, -10, 24, int_max], dtype=np.int32),
np.array([10, 230, 12], dtype=np.uint8), arr[:, 0, 1],):
expected = "[" + ", ".join(map(str, convertible)) + "]"
actual = try_to_convert(convertible)
self.assertEqual(expected, actual,
msg=get_conversion_error_msg(convertible, expected, actual))
def test_parse_vector_int_not_convertible(self):
np.random.seed(123098765)
arr = np.random.randint(-20, 20, 40).astype(np.float).reshape(10, 2, 2)
int_min, int_max = get_limits(ctypes.c_int)
test_dict = {1: 2, 3: 10, 10: 20}
for not_convertible in ((int_min, 1, 2.5, 3, int_max), [True, 50], 'test', test_dict,
reversed([1, 2, 3]),
np.array([int_min, -10, 24, [1, 2]], dtype=np.object),
np.array([[1, 2], [3, 4]]), arr[:, 0, 1],):
with self.assertRaises(TypeError, msg=get_no_exception_msg(not_convertible)):
_ = cv.utils.dumpVectorOfInt(not_convertible)
def test_parse_vector_double_convertible(self):
np.random.seed(1230965)
try_to_convert = partial(self._try_to_convert, cv.utils.dumpVectorOfDouble)
arr = np.random.randint(-20, 20, 40).astype(np.int32).reshape(10, 2, 2)
for convertible in ((1, 2.12, 3.5), [40, 50], tuple(),
np.array([-10, 24], dtype=np.int32),
np.array([-12.5, 1.4], dtype=np.double),
np.array([10, 230, 12], dtype=np.float), arr[:, 0, 1], ):
expected = "[" + ", ".join(map(lambda v: "{:.2f}".format(v), convertible)) + "]"
actual = try_to_convert(convertible)
self.assertEqual(expected, actual,
msg=get_conversion_error_msg(convertible, expected, actual))
def test_parse_vector_double_not_convertible(self):
test_dict = {1: 2, 3: 10, 10: 20}
for not_convertible in (('t', 'e', 's', 't'), [True, 50.55], 'test', test_dict,
np.array([-10.1, 24.5, [1, 2]], dtype=np.object),
np.array([[1, 2], [3, 4]]),):
with self.assertRaises(TypeError, msg=get_no_exception_msg(not_convertible)):
_ = cv.utils.dumpVectorOfDouble(not_convertible)
def test_parse_vector_rect_convertible(self):
np.random.seed(1238765)
try_to_convert = partial(self._try_to_convert, cv.utils.dumpVectorOfRect)
arr_of_rect_int32 = np.random.randint(5, 20, 4 * 3).astype(np.int32).reshape(3, 4)
arr_of_rect_cast = np.random.randint(10, 40, 4 * 5).astype(np.uint8).reshape(5, 4)
for convertible in (((1, 2, 3, 4), (10, -20, 30, 10)), arr_of_rect_int32, arr_of_rect_cast,
arr_of_rect_int32.astype(np.int8), [[5, 3, 1, 4]],
((np.int8(4), np.uint8(10), np.int(32), np.int16(55)),)):
expected = "[" + ", ".join(map(lambda v: "[x={}, y={}, w={}, h={}]".format(*v), convertible)) + "]"
actual = try_to_convert(convertible)
self.assertEqual(expected, actual,
msg=get_conversion_error_msg(convertible, expected, actual))
def test_parse_vector_rect_not_convertible(self):
np.random.seed(1238765)
arr = np.random.randint(5, 20, 4 * 3).astype(np.float).reshape(3, 4)
for not_convertible in (((1, 2, 3, 4), (10.5, -20, 30.1, 10)), arr,
[[5, 3, 1, 4], []],
((np.float(4), np.uint8(10), np.int(32), np.int16(55)),)):
with self.assertRaises(TypeError, msg=get_no_exception_msg(not_convertible)):
_ = cv.utils.dumpVectorOfRect(not_convertible)
def test_vector_general_return(self):
expected_number_of_mats = 5
expected_shape = (10, 10, 3)
expected_type = np.uint8
mats = cv.utils.generateVectorOfMat(5, 10, 10, cv.CV_8UC3)
self.assertTrue(isinstance(mats, tuple),
"Vector of Mats objects should be returned as tuple. Got: {}".format(type(mats)))
self.assertEqual(len(mats), expected_number_of_mats, "Returned array has wrong length")
for mat in mats:
self.assertEqual(mat.shape, expected_shape, "Returned Mat has wrong shape")
self.assertEqual(mat.dtype, expected_type, "Returned Mat has wrong elements type")
empty_mats = cv.utils.generateVectorOfMat(0, 10, 10, cv.CV_32FC1)
self.assertTrue(isinstance(empty_mats, tuple),
"Empty vector should be returned as empty tuple. Got: {}".format(type(mats)))
self.assertEqual(len(empty_mats), 0, "Vector of size 0 should be returned as tuple of length 0")
def test_vector_fast_return(self):
expected_shape = (5, 4)
rects = cv.utils.generateVectorOfRect(expected_shape[0])
self.assertTrue(isinstance(rects, np.ndarray),
"Vector of rectangles should be returned as numpy array. Got: {}".format(type(rects)))
self.assertEqual(rects.dtype, np.int32, "Vector of rectangles has wrong elements type")
self.assertEqual(rects.shape, expected_shape, "Vector of rectangles has wrong shape")
empty_rects = cv.utils.generateVectorOfRect(0)
self.assertTrue(isinstance(empty_rects, tuple),
"Empty vector should be returned as empty tuple. Got: {}".format(type(empty_rects)))
self.assertEqual(len(empty_rects), 0, "Vector of size 0 should be returned as tuple of length 0")
expected_shape = (10,)
ints = cv.utils.generateVectorOfInt(expected_shape[0])
self.assertTrue(isinstance(ints, np.ndarray),
"Vector of integers should be returned as numpy array. Got: {}".format(type(ints)))
self.assertEqual(ints.dtype, np.int32, "Vector of integers has wrong elements type")
self.assertEqual(ints.shape, expected_shape, "Vector of integers has wrong shape.")
class SamplesFindFile(NewOpenCVTests):

Loading…
Cancel
Save