From 58a93d240c5bdd3bc60ccddbc31676bd0fa2eae3 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Fri, 7 Jul 2017 10:54:28 +0200 Subject: Add numpy support Add simple array converters giving access to NumPy arrays. Task-number: PYSIDE-354 Task-number: PYSIDE-516 Change-Id: I2446b0f1ef98eaf12df95e4bd0e03d73e0cd7126 Reviewed-by: Alexandru Croitor --- .../libshiboken/sbknumpyarrayconverter.cpp | 308 +++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 sources/shiboken2/libshiboken/sbknumpyarrayconverter.cpp (limited to 'sources/shiboken2/libshiboken/sbknumpyarrayconverter.cpp') diff --git a/sources/shiboken2/libshiboken/sbknumpyarrayconverter.cpp b/sources/shiboken2/libshiboken/sbknumpyarrayconverter.cpp new file mode 100644 index 000000000..e941fbe26 --- /dev/null +++ b/sources/shiboken2/libshiboken/sbknumpyarrayconverter.cpp @@ -0,0 +1,308 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of PySide2. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "sbkarrayconverter.h" +#include "helper.h" +#include "sbkconverter.h" +#include "sbkconverter_p.h" +#include "sbkarrayconverter_p.h" + +#include + +#include +#include +#include + +enum { debugNumPy = 0 }; + +struct TypeCharMapping +{ + NPY_TYPES type; + const char *name; +}; + +static const TypeCharMapping typeCharMappings[] = { +{NPY_BYTE, "NPY_BYTE"}, +{NPY_UBYTE, "NPY_UBYTE"}, +{NPY_SHORT, "NPY_SHORT"}, +{NPY_USHORT, "NPY_USHORT"}, +{NPY_INT, "NPY_INT"}, +{NPY_UINT, "NPY_UINT"}, +{NPY_LONG, "NPY_LONG"}, +{NPY_ULONG, "NPY_ULONG"}, +{NPY_LONGLONG, "NPY_LONGLONG"}, +{NPY_ULONGLONG, "NPY_ULONGLONG"}, +{NPY_FLOAT, "NPY_FLOAT"}, +{NPY_DOUBLE, "NPY_DOUBLE"} +}; + +const char *npTypeName(npy_intp t) +{ + const TypeCharMapping *end = typeCharMappings + sizeof(typeCharMappings) / sizeof(typeCharMappings[0]); + const TypeCharMapping *result = + std::find_if(typeCharMappings, end, + [t] (const TypeCharMapping &m) { return m.type == t; }); + return result != end ? result->name : nullptr; +} + +std::ostream &operator<<(std::ostream &str, PyArrayObject *o) +{ + str << "PyArrayObject("; + if (o) { + const npy_intp npType = PyArray_TYPE(o); + if (const char *name = npTypeName(npType)) + str << name; + else + str << "type=" << npType; + const int nDim = PyArray_NDIM(o); + const npy_intp *dims = PyArray_DIMS(o); + for (int d = 0; d < nDim; ++d) + str << '[' << dims[d] << ']'; + str << ", "; + const int flags = PyArray_FLAGS(o); + if ((flags & NPY_ARRAY_C_CONTIGUOUS) != 0) + str << " NPY_ARRAY_C_CONTIGUOUS"; + if ((flags & NPY_ARRAY_F_CONTIGUOUS) != 0) + str << " NPY_ARRAY_F_CONTIGUOUS"; + if ((flags & NPY_ARRAY_OWNDATA) != 0) + str << " NPY_ARRAY_OWNDATA"; + if ((flags & NPY_ARRAY_FORCECAST) != 0) + str << " NPY_ARRAY_FORCECAST"; + if ((flags & NPY_ARRAY_ENSURECOPY) != 0) + str << " NPY_ARRAY_ENSURECOPY"; + if ((flags & NPY_ARRAY_ENSUREARRAY) != 0) + str << " NPY_ARRAY_ENSUREARRAY"; + if ((flags & NPY_ARRAY_ELEMENTSTRIDES) != 0) + str << " NPY_ARRAY_ELEMENTSTRIDES"; + if ((flags & NPY_ARRAY_ALIGNED) != 0) + str << " NPY_ARRAY_ALIGNED"; + if ((flags & NPY_ARRAY_NOTSWAPPED) != 0) + str << " NPY_ARRAY_NOTSWAPPED"; + if ((flags & NPY_ARRAY_WRITEABLE) != 0) + str << " NPY_ARRAY_WRITEABLE"; + if ((flags & NPY_ARRAY_UPDATEIFCOPY) != 0) + str << " NPY_ARRAY_UPDATEIFCOPY"; + } else { + str << '0'; + } + str << ')'; + return str; +} + +namespace Shiboken { +namespace Conversions { + +// Internals from sbkarrayconverter.cpp +SbkArrayConverter *createArrayConverter(IsArrayConvertibleToCppFunc toCppCheckFunc); +void setArrayTypeConverter(int index, int dimension, SbkArrayConverter *c); +SbkArrayConverter *unimplementedArrayConverter(); + +template +static bool isPrimitiveArray(PyObject *pyIn, int expectedNpType) +{ + if (!PyArray_Check(pyIn)) + return false; + PyArrayObject *pya = reinterpret_cast(pyIn); + if (debugNumPy) { + std::cerr << __FUNCTION__ << "(expectedNpType=" << expectedNpType; + if (const char *name = npTypeName(expectedNpType)) + std::cerr << " (" << name << ')'; + std::cerr << ' ' << pya << '\n'; + } + + const int dim = PyArray_NDIM(pya); + if (dim != dimension) { + warning(PyExc_RuntimeWarning, 0, + "%d dimensional numpy array passed to a function expecting a %d dimensional array.", + dim, dimension); + return false; + } + if ((PyArray_FLAGS(pya) & NPY_ARRAY_C_CONTIGUOUS) == 0) { + warning(PyExc_RuntimeWarning, 0, + "Cannot handle numpy arrays that do not have NPY_ARRAY_C_CONTIGUOUS set."); + return false; + } + const int actualNpType = PyArray_TYPE(pya); + if (actualNpType != expectedNpType) { + const char *actualName = npTypeName(actualNpType); + const char *expectedName = npTypeName(expectedNpType); + warning(PyExc_RuntimeWarning, 0, + "A numpy array of type %d (%s) was passed to a function expecting type %d (%s).", + actualNpType, actualName ? actualName : "", + expectedNpType, expectedName ? expectedName : ""); + return false; + } + return true; +} + +static inline bool primitiveArrayCheck1(PyObject *pyIn, int expectedNpType, int expectedSize) +{ + if (!isPrimitiveArray<1>(pyIn, expectedNpType)) + return false; + if (expectedSize >= 0) { + PyArrayObject *pya = reinterpret_cast(pyIn); + const int size = int(PyArray_DIMS(pya)[0]); + if (size < expectedSize) { + warning(PyExc_RuntimeWarning, 0, "A numpy array of size %d was passed to a function expects %d.", + size, expectedSize); + return false; + } + } + return true; +} + +// Convert one-dimensional array +template +static void convertArray1(PyObject *pyIn, void *cppOut) +{ + ArrayHandle *handle = reinterpret_cast *>(cppOut); + PyArrayObject *pya = reinterpret_cast(pyIn); + const npy_intp size = PyArray_DIMS(pya)[0]; + if (debugNumPy) + std::cerr << __FUNCTION__ << ' ' << size << '\n'; + handle->setData(reinterpret_cast(PyArray_DATA(pya)), size_t(size)); +} + +// Convert 2 dimensional array +template +static void convertArray2(PyObject *pyIn, void *cppOut) +{ + typedef typename Array2Handle::RowType RowType; + Array2Handle *handle = reinterpret_cast *>(cppOut); + PyArrayObject *pya = reinterpret_cast(pyIn); + handle->setData(reinterpret_cast(PyArray_DATA(pya))); +} + +template +static PythonToCppFunc checkArray1(PyObject *pyIn, int dim1, int /* dim2 */) +{ + return primitiveArrayCheck1(pyIn, NumPyType, dim1) ? convertArray1 : nullptr; +} + +static inline bool primitiveArrayCheck2(PyObject *pyIn, int expectedNpType, int expectedDim1, int expectedDim2) +{ + if (!isPrimitiveArray<2>(pyIn, expectedNpType)) + return false; + if (expectedDim2 >= 0) { + PyArrayObject *pya = reinterpret_cast(pyIn); + const int dim1 = int(PyArray_DIMS(pya)[0]); + const int dim2 = int(PyArray_DIMS(pya)[1]); + if (dim1 != expectedDim1 || dim2 != expectedDim2) { + warning(PyExc_RuntimeWarning, 0, "A numpy array of size %dx%d was passed to a function that expects %dx%d.", + dim1, dim2, expectedDim1, expectedDim2); + return false; + } + } + return true; +} + +template +static PythonToCppFunc checkArray2(PyObject *pyIn, int dim1, int dim2) +{ + return primitiveArrayCheck2(pyIn, NumPyType, dim1, dim2) ? convertArray2 : nullptr; +} + +template +static void setOrExtendArrayConverter(int dimension, IsArrayConvertibleToCppFunc toCppCheckFunc) +{ + SbkArrayConverter *arrayConverter = ArrayTypeConverter(dimension); + if (arrayConverter == unimplementedArrayConverter()) { + arrayConverter = createArrayConverter(toCppCheckFunc); + setArrayTypeConverter(ArrayTypeIndex::index, dimension, arrayConverter); + } else { + arrayConverter->toCppConversions.push_back(toCppCheckFunc); + } +} + +// Extend the converters for primitive type one-dimensional arrays by NumPy ones. +template +static inline void extendArrayConverter1() +{ + setOrExtendArrayConverter(1, checkArray1); +} + +// Extend the converters for primitive type one-dimensional arrays by NumPy ones. +template +static inline void extendArrayConverter2() +{ + setOrExtendArrayConverter(2, checkArray2); +} + +void initNumPyArrayConverters() +{ + // Expanded from macro "import_array" in __multiarray_api.h + // Make sure to read about the magic defines PY_ARRAY_UNIQUE_SYMBOL etc., + // when changing this or spreading the code over several source files. + if (_import_array() < 0) { + PyErr_Print(); + PyErr_SetString(PyExc_ImportError, "numpy.core.multiarray failed to import"); + return; + } + + // Extend the converters for primitive types by NumPy ones. + extendArrayConverter1(); + extendArrayConverter2(); + extendArrayConverter1(); + extendArrayConverter2(); + extendArrayConverter1(); + extendArrayConverter2(); + extendArrayConverter1(); + extendArrayConverter2(); + extendArrayConverter1(); + extendArrayConverter2(); + extendArrayConverter1(); + if (sizeof(long) == 8) { // UNIX/LP64: ints typically come as long + extendArrayConverter1(); + extendArrayConverter2(); + extendArrayConverter1(); + extendArrayConverter2(); + } else if (sizeof(long) == sizeof(int)) { + extendArrayConverter1(); + extendArrayConverter1(); + extendArrayConverter2(); + extendArrayConverter2(); + } + extendArrayConverter1(); + extendArrayConverter2(); + extendArrayConverter1(); + extendArrayConverter2(); +} + +} // namespace Conversions +} // namespace Shiboken -- cgit v1.2.3