diff --git a/dlib/python/numpy.h b/dlib/python/numpy.h index 264460a7e..2b5ab9d70 100644 --- a/dlib/python/numpy.h +++ b/dlib/python/numpy.h @@ -7,12 +7,14 @@ #include #include #include +#include +#include // ---------------------------------------------------------------------------------------- template void validate_numpy_array_type ( - boost::python::object& obj + const boost::python::object& obj ) { using namespace boost::python; @@ -30,26 +32,23 @@ void validate_numpy_array_type ( // ---------------------------------------------------------------------------------------- -template -void get_numpy_ndarray_parts ( - boost::python::object& obj, - T*& data, +template +void get_numpy_ndarray_shape ( + const boost::python::object& obj, long (&shape)[dims] ) /*! ensures - - extracts the pointer to the data from the given numpy ndarray. Stores the shape - of the array into #shape. + - stores the shape of the array into #shape. + - the dimension of the given numpy array is not greater than #dims. !*/ { Py_buffer pybuf; - if (PyObject_GetBuffer(obj.ptr(), &pybuf, PyBUF_ND | PyBUF_WRITABLE )) - throw dlib::error("Expected contiguous and writable numpy.ndarray."); + if (PyObject_GetBuffer(obj.ptr(), &pybuf, PyBUF_STRIDES )) + throw dlib::error("Expected numpy.ndarray with shape set."); try { - validate_numpy_array_type(obj); - data = (T*)pybuf.buf; if (pybuf.ndim > dims) throw dlib::error("Expected array with " + dlib::cast_to_string(dims) + " dimensions."); @@ -72,36 +71,94 @@ void get_numpy_ndarray_parts ( // ---------------------------------------------------------------------------------------- +template +void get_numpy_ndarray_parts ( + boost::python::object& obj, + T*& data, + dlib::array& contig_buf, + long (&shape)[dims] +) +/*! + ensures + - extracts the pointer to the data from the given numpy ndarray. Stores the shape + of the array into #shape. + - the dimension of the given numpy array is not greater than #dims. + - #shape[#dims-1] == pixel_traits::num when #dims is greater than 2 +!*/ +{ + Py_buffer pybuf; + if (PyObject_GetBuffer(obj.ptr(), &pybuf, PyBUF_STRIDES | PyBUF_WRITABLE )) + throw dlib::error("Expected writable numpy.ndarray with shape set."); + + try + { + validate_numpy_array_type(obj); + + if (pybuf.ndim > dims) + throw dlib::error("Expected array with " + dlib::cast_to_string(dims) + " dimensions."); + get_numpy_ndarray_shape(obj, shape); + + if (dlib::pixel_traits::num > 1 && dlib::pixel_traits::num != shape[dims-1]) + throw dlib::error("Expected numpy.ndarray with " + dlib::cast_to_string(dlib::pixel_traits::num) + " channels."); + + if (PyBuffer_IsContiguous(&pybuf, 'C')) + data = (T*)pybuf.buf; + else + { + contig_buf.resize(pybuf.len); + if (PyBuffer_ToContiguous(&contig_buf[0], &pybuf, pybuf.len, 'C')) + throw dlib::error("Can't copy numpy.ndarray to a contiguous buffer."); + data = &contig_buf[0]; + } + } + catch(...) + { + PyBuffer_Release(&pybuf); + throw; + } + PyBuffer_Release(&pybuf); +} + +// ---------------------------------------------------------------------------------------- + template void get_numpy_ndarray_parts ( const boost::python::object& obj, const T*& data, + dlib::array& contig_buf, long (&shape)[dims] ) /*! ensures - extracts the pointer to the data from the given numpy ndarray. Stores the shape of the array into #shape. + - the dimension of the given numpy array is not greater than #dims. + - #shape[#dims-1] == pixel_traits::num when #dims is greater than 2 !*/ { Py_buffer pybuf; - if (PyObject_GetBuffer(obj.ptr(), &pybuf, PyBUF_ND )) - throw dlib::error("Expected contiguous numpy.ndarray."); + if (PyObject_GetBuffer(obj.ptr(), &pybuf, PyBUF_STRIDES )) + throw dlib::error("Expected numpy.ndarray with shape set."); try { validate_numpy_array_type(obj); - data = (const T*)pybuf.buf; if (pybuf.ndim > dims) throw dlib::error("Expected array with " + dlib::cast_to_string(dims) + " dimensions."); + get_numpy_ndarray_shape(obj, shape); - for (int i = 0; i < dims; ++i) + if (dlib::pixel_traits::num > 1 && dlib::pixel_traits::num != shape[dims-1]) + throw dlib::error("Expected numpy.ndarray with " + dlib::cast_to_string(dlib::pixel_traits::num) + " channels."); + + if (PyBuffer_IsContiguous(&pybuf, 'C')) + data = (const T*)pybuf.buf; + else { - if (i < pybuf.ndim) - shape[i] = pybuf.shape[i]; - else - shape[i] = 1; + contig_buf.resize(pybuf.len); + if (PyBuffer_ToContiguous(&contig_buf[0], &pybuf, pybuf.len, 'C')) + throw dlib::error("Can't copy numpy.ndarray to a contiguous buffer."); + data = &contig_buf[0]; } } catch(...) diff --git a/dlib/python/numpy_image.h b/dlib/python/numpy_image.h index 61e028a1c..65a46e03b 100644 --- a/dlib/python/numpy_image.h +++ b/dlib/python/numpy_image.h @@ -6,6 +6,7 @@ #include "numpy.h" #include #include +#include // ---------------------------------------------------------------------------------------- @@ -18,7 +19,7 @@ public: numpy_gray_image (boost::python::object& img) { long shape[2]; - get_numpy_ndarray_parts(img, _data, shape); + get_numpy_ndarray_parts(img, _data, _contig_buf, shape); _nr = shape[0]; _nc = shape[1]; } @@ -32,6 +33,7 @@ public: private: unsigned char* _data; + dlib::array _contig_buf; long _nr; long _nc; }; @@ -51,7 +53,8 @@ inline bool is_gray_python_image (boost::python::object& img) { try { - numpy_gray_image temp(img); + long shape[2]; + get_numpy_ndarray_shape(img, shape); return true; } catch (dlib::error&) @@ -70,7 +73,7 @@ public: numpy_rgb_image (boost::python::object& img) { long shape[3]; - get_numpy_ndarray_parts(img, _data, shape); + get_numpy_ndarray_parts(img, _data, _contig_buf, shape); _nr = shape[0]; _nc = shape[1]; if (shape[2] != 3) @@ -87,6 +90,7 @@ public: private: dlib::rgb_pixel* _data; + dlib::array _contig_buf; long _nr; long _nc; }; @@ -107,8 +111,11 @@ inline bool is_rgb_python_image (boost::python::object& img) { try { - numpy_rgb_image temp(img); - return true; + long shape[3]; + get_numpy_ndarray_shape(img, shape); + if (shape[2] == 3) + return true; + return false; } catch (dlib::error&) {