Browse Source

python: basic support for arbitrary types in strided array views.

Not ArrayView yet, and also no documentation on this whatsoever. That'll
come next. Also not everything works with arbitrary types yet,
converting from buffer protocol doesn't remember the format and
conversion to bytes doesn't take the actual type size into account
either.
pull/11/head
Vladimír Vondruš 5 years ago
parent
commit
98a3dcf59c
  1. 8
      src/Corrade/Containers/CMakeLists.txt
  2. 159
      src/Corrade/Containers/StridedArrayViewPythonBindings.h
  3. 4
      src/python/corrade/CMakeLists.txt
  4. 140
      src/python/corrade/containers.cpp
  5. 31
      src/python/corrade/test/CMakeLists.txt
  6. 48
      src/python/corrade/test/test_containers.py
  7. 132
      src/python/corrade/test/test_containers_numpy.py
  8. 89
      src/python/corrade/test/test_stridedarrayview.cpp
  9. 5
      src/python/magnum/magnum.cpp
  10. 3
      src/python/magnum/trade.cpp

8
src/Corrade/Containers/CMakeLists.txt

@ -24,7 +24,11 @@
# #
if(WITH_PYTHON) if(WITH_PYTHON)
add_custom_target(CorradeContainersPython SOURCES PythonBindings.h) set(CorradeContainersPython_HEADERS
PythonBindings.h
StridedArrayViewPythonBindings.h)
add_custom_target(CorradeContainersPython SOURCES ${CorradeContainersPython_HEADERS})
set_target_properties(CorradeContainersPython PROPERTIES FOLDER "Corrade/Python") set_target_properties(CorradeContainersPython PROPERTIES FOLDER "Corrade/Python")
install(FILES PythonBindings.h DESTINATION ${CORRADE_INCLUDE_INSTALL_DIR}/Containers) install(FILES ${CorradeContainersPython_HEADERS} DESTINATION ${CORRADE_INCLUDE_INSTALL_DIR}/Containers)
endif() endif()

159
src/Corrade/Containers/StridedArrayViewPythonBindings.h

@ -0,0 +1,159 @@
#ifndef Corrade_Containers_StridedArrayViewPythonBindings_h
#define Corrade_Containers_StridedArrayViewPythonBindings_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <pybind11/pybind11.h>
#include <Corrade/Containers/StridedArrayView.h>
namespace Corrade { namespace Containers {
namespace Implementation {
/* For maintainability please keep in the same order as
https://docs.python.org/3/library/struct.html#format-characters */
template<class T> constexpr const char* formatString() {
static_assert(sizeof(T) == 0, "format string unknown for this type, supply it explicitly");
return {};
}
/* Representing bytes as unsigned. Not using 'c' because then it behaves
differently from bytes/bytearray, where you can do `a[0] = ord('A')`. */
template<> constexpr const char* formatString<char>() { return "B"; }
template<> constexpr const char* formatString<std::int8_t>() { return "b"; }
template<> constexpr const char* formatString<std::uint8_t>() { return "B"; }
template<> constexpr const char* formatString<std::int16_t>() { return "h"; }
template<> constexpr const char* formatString<std::uint16_t>() { return "H"; }
template<> constexpr const char* formatString<std::int32_t>() { return "i"; }
template<> constexpr const char* formatString<std::uint32_t>() { return "I"; }
/* *not* l / L, that's 4 bytes in Python */
template<> constexpr const char* formatString<std::int64_t>() { return "q"; }
template<> constexpr const char* formatString<std::uint64_t>() { return "Q"; }
/** @todo how to represent std::size_t? conflicts with uint32_t/uint64_t above */
/** @todo half? take from Magnum? */
template<> constexpr const char* formatString<float>() { return "f"; }
template<> constexpr const char* formatString<double>() { return "d"; }
template<class T, class U> struct PyStridedArrayViewSetItem;
template<class U> struct PyStridedArrayViewSetItem<const char, U> {
/* __setitem__ is not even exposed for immutable views so this is fine */
constexpr static std::nullptr_t set = nullptr;
};
template<class U> struct PyStridedArrayViewSetItem<char, U> {
static void set(char* item, pybind11::handle object) {
*reinterpret_cast<U*>(item) = pybind11::cast<U>(object);
}
};
template<unsigned, class> struct PyStridedElement;
}
template<unsigned dimensions, class T> class PyStridedArrayView: public StridedArrayView<dimensions, T> {
public:
/* Null function pointers should be okay as it shouldn't ever get to
them -- IndexError gets fired first. Not really sure about the
format, choosing bytes for safety. */
/*implicit*/ PyStridedArrayView(): format{"B"}, getitem{} {}
template<class U> explicit PyStridedArrayView(const StridedArrayView<dimensions, U>& view): PyStridedArrayView{view, Implementation::formatString<typename std::decay<U>::type>(), sizeof(U)} {}
template<class U> explicit PyStridedArrayView(const StridedArrayView<dimensions, U>& view, const char* format, std::size_t itemsize): PyStridedArrayView<dimensions, T>{
arrayCast<T>(view),
format,
itemsize,
[](const char* item) {
return pybind11::cast(*reinterpret_cast<const U*>(item));
},
Implementation::PyStridedArrayViewSetItem<T, U>::set
} {}
explicit PyStridedArrayView(const StridedArrayView<dimensions, T>& view, const char* format, std::size_t itemsize, pybind11::object(*getitem)(const char*), void(*setitem)(char*, pybind11::handle)): StridedArrayView<dimensions, T>{view}, format{format}, itemsize{itemsize}, getitem{getitem}, setitem{setitem} {}
/* All APIs that are exposed by bindings and return a StridedArrayView
have to return the wrapper now */
typedef typename std::conditional<dimensions == 1, T&, PyStridedArrayView<dimensions - 1, T>>::type ElementType;
ElementType operator[](std::size_t i) const {
return Implementation::PyStridedElement<dimensions, T>::wrap(StridedArrayView<dimensions, T>::operator[](i), format, itemsize, getitem, setitem);
}
PyStridedArrayView<dimensions, T> slice(std::size_t begin, std::size_t end) const {
return PyStridedArrayView<dimensions, T>{StridedArrayView<dimensions, T>::slice(begin, end), format, itemsize, getitem, setitem};
}
PyStridedArrayView<dimensions, T> slice(const typename StridedArrayView<dimensions, T>::Size& begin, const typename StridedArrayView<dimensions, T>::Size& end) const {
return PyStridedArrayView<dimensions, T>{StridedArrayView<dimensions, T>::slice(begin, end), format, itemsize, getitem, setitem};
}
/* slice() with templated dimensions not used */
/* slice(&T::member) not used */
/* prefix(), suffix(), except() not used */
PyStridedArrayView<dimensions, T> every(std::size_t skip) const {
return PyStridedArrayView<dimensions, T>{StridedArrayView<dimensions, T>::every(skip), format, itemsize, getitem, setitem};
}
PyStridedArrayView<dimensions, T> every(const typename StridedArrayView<dimensions, T>::Stride& skip) const {
return PyStridedArrayView<dimensions, T>{StridedArrayView<dimensions, T>::every(skip), format, itemsize, getitem, setitem};
}
template<unsigned dimensionA, unsigned dimensionB> PyStridedArrayView<dimensions, T> transposed() const {
return PyStridedArrayView<dimensions, T>{StridedArrayView<dimensions, T>::template transposed<dimensionA, dimensionB>(), format, itemsize, getitem, setitem};
}
template<unsigned dimension> PyStridedArrayView<dimensions, T> flipped() const {
return PyStridedArrayView<dimensions, T>{StridedArrayView<dimensions, T>::template flipped<dimension>(), format, itemsize, getitem, setitem};
}
template<unsigned dimension> PyStridedArrayView<dimensions, T> broadcasted(std::size_t size) const {
return PyStridedArrayView<dimensions, T>{StridedArrayView<dimensions, T>::template broadcasted<dimension>(size), format, itemsize, getitem, setitem};
}
/* has to be public as it's accessed by the bindings directly */
const char* format;
std::size_t itemsize;
pybind11::object(*getitem)(const char*);
void(*setitem)(char*, pybind11::handle);
};
namespace Implementation {
template<unsigned dimensions, class T> struct PyStridedElement {
static PyStridedArrayView<dimensions - 1, T> wrap(const StridedArrayView<dimensions - 1, T>& element, const char* format, std::size_t itemsize, pybind11::object(*getitem)(const char*), void(*setitem)(char*, pybind11::handle)) {
return PyStridedArrayView<dimensions - 1, T>{element, format, itemsize, getitem, setitem};
}
};
template<class T> struct PyStridedElement<1, T> {
static T& wrap(T& element, const char*, std::size_t, pybind11::object(*)(const char*), void(*)(char*, pybind11::handle)) {
return element;
}
};
}
}}
#endif

4
src/python/corrade/CMakeLists.txt

@ -96,3 +96,7 @@ set_target_properties(corrade PROPERTIES
file(GENERATE OUTPUT ${output_dir}/corrade/__init__.py file(GENERATE OUTPUT ${output_dir}/corrade/__init__.py
INPUT ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py) INPUT ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py)
if(BUILD_TESTS)
add_subdirectory(test)
endif()

140
src/python/corrade/containers.cpp

@ -29,6 +29,7 @@
#include <Corrade/Containers/ScopeGuard.h> #include <Corrade/Containers/ScopeGuard.h>
#include "Corrade/Containers/PythonBindings.h" #include "Corrade/Containers/PythonBindings.h"
#include "Corrade/Containers/StridedArrayViewPythonBindings.h"
#include "corrade/bootstrap.h" #include "corrade/bootstrap.h"
#include "corrade/PyBuffer.h" #include "corrade/PyBuffer.h"
@ -37,14 +38,6 @@ namespace corrade {
namespace { namespace {
const char* const FormatStrings[]{
/* 0. Representing bytes as unsigned. Not using 'c' because then it behaves
differently from bytes/bytearray, where you can do `a[0] = ord('A')`. */
"B",
};
template<class> constexpr std::size_t formatIndex();
template<> constexpr std::size_t formatIndex<char>() { return 0; }
struct Slice { struct Slice {
std::size_t start; std::size_t start;
std::size_t stop; std::size_t stop;
@ -84,7 +77,7 @@ template<class T> bool arrayViewBufferProtocol(T& self, Py_buffer& buffer, int f
buffer.buf = const_cast<typename std::decay<typename T::Type>::type*>(self.data()); buffer.buf = const_cast<typename std::decay<typename T::Type>::type*>(self.data());
buffer.readonly = std::is_const<typename T::Type>::value; buffer.readonly = std::is_const<typename T::Type>::value;
if((flags & PyBUF_FORMAT) == PyBUF_FORMAT) if((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
buffer.format = const_cast<char*>(FormatStrings[formatIndex<typename std::decay<typename T::Type>::type>()]); buffer.format = const_cast<char*>(Containers::Implementation::formatString<typename std::decay<typename T::Type>::type>());
if(flags != PyBUF_SIMPLE) { if(flags != PyBUF_SIMPLE) {
/* The view is immutable (can't change its size after it has been /* The view is immutable (can't change its size after it has been
constructed), so referencing the size directly is okay */ constructed), so referencing the size directly is okay */
@ -157,8 +150,10 @@ template<class T> void arrayView(py::class_<Containers::ArrayView<T>, Containers
const Slice calculated = calculateSlice(slice, self.size()); const Slice calculated = calculateSlice(slice, self.size());
/* Non-trivial stride, return a different type */ /* Non-trivial stride, return a different type */
/** @todo this always assumes bytes for now -- remember the format
and provide a checked typed conversion API */
if(calculated.step != 1) { if(calculated.step != 1) {
auto sliced = Containers::stridedArrayView(self).slice(calculated.start, calculated.stop).every(calculated.step); auto sliced = Containers::PyStridedArrayView<1, T>{ Containers::stridedArrayView(self)}.slice(calculated.start, calculated.stop).every(calculated.step);
return pyCastButNotShitty(Containers::pyArrayViewHolder(sliced, sliced.size() ? pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner : py::none{})); return pyCastButNotShitty(Containers::pyArrayViewHolder(sliced, sliced.size() ? pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner : py::none{}));
} }
@ -290,14 +285,14 @@ template<class T> bool stridedArrayViewBufferProtocol(T& self, Py_buffer& buffer
/* I hate the const_casts but I assume this is to make editing easier, NOT /* I hate the const_casts but I assume this is to make editing easier, NOT
to make it possible for users to stomp on these values. */ to make it possible for users to stomp on these values. */
buffer.ndim = T::Dimensions; buffer.ndim = T::Dimensions;
buffer.itemsize = sizeof(typename T::Type); buffer.itemsize = self.itemsize;
buffer.len = sizeof(typename T::Type); buffer.len = self.itemsize;
for(std::size_t i = 0; i != T::Dimensions; ++i) for(std::size_t i = 0; i != T::Dimensions; ++i)
buffer.len *= Containers::Implementation::sizeRef(self)[i]; buffer.len *= Containers::Implementation::sizeRef(self)[i];
buffer.buf = const_cast<typename std::decay<typename T::ErasedType>::type*>(self.data()); buffer.buf = const_cast<typename std::decay<typename T::ErasedType>::type*>(self.data());
buffer.readonly = std::is_const<typename T::Type>::value; buffer.readonly = std::is_const<typename T::Type>::value;
if((flags & PyBUF_FORMAT) == PyBUF_FORMAT) if((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
buffer.format = const_cast<char*>(FormatStrings[formatIndex<typename std::decay<typename T::Type>::type>()]); buffer.format = const_cast<char*>(self.format);
/* The view is immutable (can't change its size after it has been /* The view is immutable (can't change its size after it has been
constructed), so referencing the size/stride directly is okay */ constructed), so referencing the size/stride directly is okay */
buffer.shape = const_cast<Py_ssize_t*>(reinterpret_cast<const Py_ssize_t*>(Containers::Implementation::sizeRef(self).begin())); buffer.shape = const_cast<Py_ssize_t*>(reinterpret_cast<const Py_ssize_t*>(Containers::Implementation::sizeRef(self).begin()));
@ -310,11 +305,11 @@ inline std::size_t largerStride(std::size_t a, std::size_t b) {
return a < b ? b : a; /* max(), but named like this to avoid clashes */ return a < b ? b : a; /* max(), but named like this to avoid clashes */
} }
template<unsigned dimensions, class T> void stridedArrayView(py::class_<Containers::StridedArrayView<dimensions, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<dimensions, T>>>& c) { template<unsigned dimensions, class T> void stridedArrayView(py::class_<Containers::PyStridedArrayView<dimensions, T>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<dimensions, T>>>& c) {
/* Implicitly convertible from a buffer */ /* Implicitly convertible from a buffer */
py::implicitly_convertible<py::buffer, Containers::StridedArrayView<dimensions, T>>(); py::implicitly_convertible<py::buffer, Containers::PyStridedArrayView<dimensions, T>>();
/* This is needed for implicit conversion from np.array */ /* This is needed for implicit conversion from np.array */
py::implicitly_convertible<py::array, Containers::StridedArrayView<dimensions, T>>(); py::implicitly_convertible<py::array, Containers::PyStridedArrayView<dimensions, T>>();
c c
/* Constructor */ /* Constructor */
@ -347,63 +342,68 @@ template<unsigned dimensions, class T> void stridedArrayView(py::class_<Containe
the buffer because we no longer care about the buffer the buffer because we no longer care about the buffer
descriptor -- that could allow the GC to haul away a bit more descriptor -- that could allow the GC to haul away a bit more
garbage */ garbage */
return Containers::pyArrayViewHolder(Containers::StridedArrayView<dimensions, T>{ /** @todo this always assumes bytes for now -- remember the format
and provide a checked typed conversion API */
return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<dimensions, T>{Containers::StridedArrayView<dimensions, T>{
{static_cast<T*>(buffer.buf), size}, {static_cast<T*>(buffer.buf), size},
Containers::StaticArrayView<dimensions, const std::size_t>{reinterpret_cast<std::size_t*>(buffer.shape)}, Containers::StaticArrayView<dimensions, const std::size_t>{reinterpret_cast<std::size_t*>(buffer.shape)},
Containers::StaticArrayView<dimensions, const std::ptrdiff_t>{reinterpret_cast<std::ptrdiff_t*>(buffer.strides)}}, Containers::StaticArrayView<dimensions, const std::ptrdiff_t>{reinterpret_cast<std::ptrdiff_t*>(buffer.strides)}}},
buffer.len ? py::reinterpret_borrow<py::object>(buffer.obj) : py::none{}); buffer.len ? py::reinterpret_borrow<py::object>(buffer.obj) : py::none{});
}), "Construct from a buffer") }), "Construct from a buffer")
/* Length, size/stride tuple, dimension count and memory owning object */ /* Length, size/stride tuple, dimension count and memory owning object */
.def("__len__", [](const Containers::StridedArrayView<dimensions, T>& self) { .def("__len__", [](const Containers::PyStridedArrayView<dimensions, T>& self) {
return Containers::StridedDimensions<dimensions, std::size_t>(self.size())[0]; return Containers::StridedDimensions<dimensions, std::size_t>(self.size())[0];
}, "View size in the top-level dimension") }, "View size in the top-level dimension")
.def_property_readonly("size", [](const Containers::StridedArrayView<dimensions, T>& self) { .def_property_readonly("size", [](const Containers::PyStridedArrayView<dimensions, T>& self) {
return size<dimensions>(self.size()); return size<dimensions>(self.size());
}, "View size in each dimension") }, "View size in each dimension")
.def_property_readonly("stride", [](const Containers::StridedArrayView<dimensions, T>& self) { .def_property_readonly("stride", [](const Containers::PyStridedArrayView<dimensions, T>& self) {
return stride<dimensions>(self.stride()); return stride<dimensions>(self.stride());
}, "View stride in each dimension") }, "View stride in each dimension")
.def_property_readonly("dimensions", [](const Containers::StridedArrayView<dimensions, T>&) { return dimensions; }, "Dimension count") .def_property_readonly("dimensions", [](const Containers::PyStridedArrayView<dimensions, T>&) { return dimensions; }, "Dimension count")
.def_property_readonly("owner", [](const Containers::StridedArrayView<dimensions, T>& self) { .def_property_readonly("format", [](const Containers::PyStridedArrayView<dimensions, T>& self) {
return self.format;
}, "Format of each item")
.def_property_readonly("owner", [](const Containers::PyStridedArrayView<dimensions, T>& self) {
return pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner; return pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner;
}, "Memory owner object") }, "Memory owner object")
/* Conversion to bytes */ /* Conversion to bytes */
.def("__bytes__", [](const Containers::StridedArrayView<dimensions, T>& self) { .def("__bytes__", [](const Containers::PyStridedArrayView<dimensions, T>& self) {
/* TODO: use _PyBytes_Resize() to avoid the double copy */ /* TODO: use _PyBytes_Resize() to avoid the double copy */
const Containers::Array<char> out = bytes(Containers::arrayCast<const char>(self)); const Containers::Array<char> out = bytes(Containers::arrayCast<const char>(self));
return py::bytes(out.data(), out.size()); return py::bytes(out.data(), out.size());
}, "Convert to bytes") }, "Convert to bytes")
/* Slicing of the top dimension */ /* Slicing of the top dimension */
.def("__getitem__", [](const Containers::StridedArrayView<dimensions, T>& self, py::slice slice) { .def("__getitem__", [](const Containers::PyStridedArrayView<dimensions, T>& self, py::slice slice) {
const Slice calculated = calculateSlice(slice, Containers::StridedDimensions<dimensions, const std::size_t>{self.size()}[0]); const Slice calculated = calculateSlice(slice, Containers::StridedDimensions<dimensions, const std::size_t>{self.size()}[0]);
const auto sliced = self.slice(calculated.start, calculated.stop).every(calculated.step); const auto sliced = self.slice(calculated.start, calculated.stop).every(calculated.step);
return Containers::pyArrayViewHolder(sliced, calculated.start == calculated.stop ? py::none{} : pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(sliced, calculated.start == calculated.stop ? py::none{} : pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
}, "Slice the view"); }, "Slice the view");
enableBetterBufferProtocol<Containers::StridedArrayView<dimensions, T>, stridedArrayViewBufferProtocol>(c); enableBetterBufferProtocol<Containers::PyStridedArrayView<dimensions, T>, stridedArrayViewBufferProtocol>(c);
} }
template<class T> void stridedArrayView1D(py::class_<Containers::StridedArrayView<1, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<1, T>>>& c) { template<class T> void stridedArrayView1D(py::class_<Containers::PyStridedArrayView<1, T>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<1, T>>>& c) {
c c
/* Single item retrieval. Need to raise IndexError in order to allow /* Single item retrieval. Need to raise IndexError in order to allow
iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */
.def("__getitem__", [](const Containers::StridedArrayView<1, T>& self, std::size_t i) { .def("__getitem__", [](const Containers::PyStridedArrayView<1, T>& self, std::size_t i) {
if(i >= self.size()) { if(i >= self.size()) {
PyErr_SetNone(PyExc_IndexError); PyErr_SetNone(PyExc_IndexError);
throw py::error_already_set{}; throw py::error_already_set{};
} }
return self[i]; return self.getitem(&self[i]);
}, "Value at given position"); }, "Value at given position");
} }
template<unsigned dimensions, class T> void stridedArrayViewND(py::class_<Containers::StridedArrayView<dimensions, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<dimensions, T>>>& c) { template<unsigned dimensions, class T> void stridedArrayViewND(py::class_<Containers::PyStridedArrayView<dimensions, T>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<dimensions, T>>>& c) {
c c
/* Sub-view retrieval. Need to raise IndexError in order to allow /* Sub-view retrieval. Need to raise IndexError in order to allow
iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */
.def("__getitem__", [](const Containers::StridedArrayView<dimensions, T>& self, std::size_t i) { .def("__getitem__", [](const Containers::PyStridedArrayView<dimensions, T>& self, std::size_t i) {
if(i >= Containers::StridedDimensions<dimensions, const std::size_t>{self.size()}[0]) { if(i >= Containers::StridedDimensions<dimensions, const std::size_t>{self.size()}[0]) {
PyErr_SetNone(PyExc_IndexError); PyErr_SetNone(PyExc_IndexError);
throw py::error_already_set{}; throw py::error_already_set{};
@ -412,7 +412,7 @@ template<unsigned dimensions, class T> void stridedArrayViewND(py::class_<Contai
}, "Sub-view at given position") }, "Sub-view at given position")
/* Multi-dimensional slicing */ /* Multi-dimensional slicing */
.def("__getitem__", [](const Containers::StridedArrayView<dimensions, T>& self, const typename DimensionsTuple<dimensions, py::slice>::Type& slice) { .def("__getitem__", [](const Containers::PyStridedArrayView<dimensions, T>& self, const typename DimensionsTuple<dimensions, py::slice>::Type& slice) {
Containers::StridedDimensions<dimensions, std::size_t> starts; Containers::StridedDimensions<dimensions, std::size_t> starts;
Containers::StridedDimensions<dimensions, std::size_t> stops; Containers::StridedDimensions<dimensions, std::size_t> stops;
Containers::StridedDimensions<dimensions, std::ptrdiff_t> steps; Containers::StridedDimensions<dimensions, std::ptrdiff_t> steps;
@ -432,26 +432,26 @@ template<unsigned dimensions, class T> void stridedArrayViewND(py::class_<Contai
}, "Slice the view"); }, "Slice the view");
} }
template<class T> void stridedArrayView2D(py::class_<Containers::StridedArrayView<2, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<2, T>>>& c) { template<class T> void stridedArrayView2D(py::class_<Containers::PyStridedArrayView<2, T>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<2, T>>>& c) {
c c
/* Single-item retrieval. Need to raise IndexError in order to allow /* Single-item retrieval. Need to raise IndexError in order to allow
iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */
.def("__getitem__", [](const Containers::StridedArrayView<2, T>& self, const std::tuple<std::size_t, std::size_t>& i) { .def("__getitem__", [](const Containers::PyStridedArrayView<2, T>& self, const std::tuple<std::size_t, std::size_t>& i) {
if(std::get<0>(i) >= self.size()[0] || if(std::get<0>(i) >= self.size()[0] ||
std::get<1>(i) >= self.size()[1]) { std::get<1>(i) >= self.size()[1]) {
PyErr_SetNone(PyExc_IndexError); PyErr_SetNone(PyExc_IndexError);
throw py::error_already_set{}; throw py::error_already_set{};
} }
return self[std::get<0>(i)][std::get<1>(i)]; return self.getitem(&self[std::get<0>(i)][std::get<1>(i)]);
}, "Value at given position") }, "Value at given position")
.def("transposed", [](const Containers::StridedArrayView<2, T>& self, const std::size_t a, std::size_t b) { .def("transposed", [](const Containers::PyStridedArrayView<2, T>& self, const std::size_t a, std::size_t b) {
if((a == 0 && b == 1) || if((a == 0 && b == 1) ||
(a == 1 && b == 0)) (a == 1 && b == 0))
return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
PyErr_Format(PyExc_ValueError, "dimensions %zu, %zu can't be transposed in a %iD view", a, b, 2); PyErr_Format(PyExc_ValueError, "dimensions %zu, %zu can't be transposed in a %iD view", a, b, 2);
throw py::error_already_set{}; throw py::error_already_set{};
}, "Transpose two dimensions") }, "Transpose two dimensions")
.def("flipped", [](const Containers::StridedArrayView<2, T>& self, const std::size_t dimension) { .def("flipped", [](const Containers::PyStridedArrayView<2, T>& self, const std::size_t dimension) {
if(dimension == 0) if(dimension == 0)
return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
if(dimension == 1) if(dimension == 1)
@ -459,7 +459,7 @@ template<class T> void stridedArrayView2D(py::class_<Containers::StridedArrayVie
PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 2); PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 2);
throw py::error_already_set{}; throw py::error_already_set{};
}, "Flip a dimension") }, "Flip a dimension")
.def("broadcasted", [](const Containers::StridedArrayView<2, T>& self, const std::size_t dimension, std::size_t size) { .def("broadcasted", [](const Containers::PyStridedArrayView<2, T>& self, const std::size_t dimension, std::size_t size) {
if(dimension == 0) if(dimension == 0)
return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
if(dimension == 1) if(dimension == 1)
@ -469,20 +469,20 @@ template<class T> void stridedArrayView2D(py::class_<Containers::StridedArrayVie
}, "Broadcast a dimension"); }, "Broadcast a dimension");
} }
template<class T> void stridedArrayView3D(py::class_<Containers::StridedArrayView<3, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<3, T>>>& c) { template<class T> void stridedArrayView3D(py::class_<Containers::PyStridedArrayView<3, T>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<3, T>>>& c) {
c c
/* Single-item retrieval. Need to raise IndexError in order to allow /* Single-item retrieval. Need to raise IndexError in order to allow
iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */
.def("__getitem__", [](const Containers::StridedArrayView<3, T>& self, const std::tuple<std::size_t, std::size_t, std::size_t>& i) { .def("__getitem__", [](const Containers::PyStridedArrayView<3, T>& self, const std::tuple<std::size_t, std::size_t, std::size_t>& i) {
if(std::get<0>(i) >= self.size()[0] || if(std::get<0>(i) >= self.size()[0] ||
std::get<1>(i) >= self.size()[1] || std::get<1>(i) >= self.size()[1] ||
std::get<2>(i) >= self.size()[2]) { std::get<2>(i) >= self.size()[2]) {
PyErr_SetNone(PyExc_IndexError); PyErr_SetNone(PyExc_IndexError);
throw py::error_already_set{}; throw py::error_already_set{};
} }
return self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)]; return self.getitem(&self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)]);
}, "Value at given position") }, "Value at given position")
.def("transposed", [](const Containers::StridedArrayView<3, T>& self, const std::size_t a, std::size_t b) { .def("transposed", [](const Containers::PyStridedArrayView<3, T>& self, const std::size_t a, std::size_t b) {
if((a == 0 && b == 1) || if((a == 0 && b == 1) ||
(a == 1 && b == 0)) (a == 1 && b == 0))
return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
@ -495,7 +495,7 @@ template<class T> void stridedArrayView3D(py::class_<Containers::StridedArrayVie
PyErr_Format(PyExc_ValueError, "dimensions %zu, %zu can't be transposed in a %iD view", a, b, 2); PyErr_Format(PyExc_ValueError, "dimensions %zu, %zu can't be transposed in a %iD view", a, b, 2);
throw py::error_already_set{}; throw py::error_already_set{};
}, "Transpose two dimensions") }, "Transpose two dimensions")
.def("flipped", [](const Containers::StridedArrayView<3, T>& self, const std::size_t dimension) { .def("flipped", [](const Containers::PyStridedArrayView<3, T>& self, const std::size_t dimension) {
if(dimension == 0) if(dimension == 0)
return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
if(dimension == 1) if(dimension == 1)
@ -505,7 +505,7 @@ template<class T> void stridedArrayView3D(py::class_<Containers::StridedArrayVie
PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 3); PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 3);
throw py::error_already_set{}; throw py::error_already_set{};
}, "Flip a dimension") }, "Flip a dimension")
.def("broadcasted", [](const Containers::StridedArrayView<3, T>& self, const std::size_t dimension, std::size_t size) { .def("broadcasted", [](const Containers::PyStridedArrayView<3, T>& self, const std::size_t dimension, std::size_t size) {
if(dimension == 0) if(dimension == 0)
return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
if(dimension == 1) if(dimension == 1)
@ -517,11 +517,11 @@ template<class T> void stridedArrayView3D(py::class_<Containers::StridedArrayVie
}, "Broadcast a dimension"); }, "Broadcast a dimension");
} }
template<class T> void stridedArrayView4D(py::class_<Containers::StridedArrayView<4, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<4, T>>>& c) { template<class T> void stridedArrayView4D(py::class_<Containers::PyStridedArrayView<4, T>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<4, T>>>& c) {
c c
/* Single-item retrieval. Need to raise IndexError in order to allow /* Single-item retrieval. Need to raise IndexError in order to allow
iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */
.def("__getitem__", [](const Containers::StridedArrayView<4, T>& self, const std::tuple<std::size_t, std::size_t, std::size_t, std::size_t>& i) { .def("__getitem__", [](const Containers::PyStridedArrayView<4, T>& self, const std::tuple<std::size_t, std::size_t, std::size_t, std::size_t>& i) {
if(std::get<0>(i) >= self.size()[0] || if(std::get<0>(i) >= self.size()[0] ||
std::get<1>(i) >= self.size()[1] || std::get<1>(i) >= self.size()[1] ||
std::get<2>(i) >= self.size()[2] || std::get<2>(i) >= self.size()[2] ||
@ -529,9 +529,9 @@ template<class T> void stridedArrayView4D(py::class_<Containers::StridedArrayVie
PyErr_SetNone(PyExc_IndexError); PyErr_SetNone(PyExc_IndexError);
throw py::error_already_set{}; throw py::error_already_set{};
} }
return self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)][std::get<3>(i)]; return self.getitem(&self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)][std::get<3>(i)]);
}, "Value at given position") }, "Value at given position")
.def("transposed", [](const Containers::StridedArrayView<4, T>& self, const std::size_t a, std::size_t b) { .def("transposed", [](const Containers::PyStridedArrayView<4, T>& self, const std::size_t a, std::size_t b) {
if((a == 0 && b == 1) || if((a == 0 && b == 1) ||
(a == 1 && b == 0)) (a == 1 && b == 0))
return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
@ -553,7 +553,7 @@ template<class T> void stridedArrayView4D(py::class_<Containers::StridedArrayVie
PyErr_Format(PyExc_ValueError, "dimensions %zu, %zu can't be transposed in a %iD view", a, b, 4); PyErr_Format(PyExc_ValueError, "dimensions %zu, %zu can't be transposed in a %iD view", a, b, 4);
throw py::error_already_set{}; throw py::error_already_set{};
}, "Transpose two dimensions") }, "Transpose two dimensions")
.def("flipped", [](const Containers::StridedArrayView<4, T>& self, const std::size_t dimension) { .def("flipped", [](const Containers::PyStridedArrayView<4, T>& self, const std::size_t dimension) {
if(dimension == 0) if(dimension == 0)
return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
if(dimension == 1) if(dimension == 1)
@ -565,7 +565,7 @@ template<class T> void stridedArrayView4D(py::class_<Containers::StridedArrayVie
PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 4); PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 4);
throw py::error_already_set{}; throw py::error_already_set{};
}, "Flip a dimension") }, "Flip a dimension")
.def("broadcasted", [](const Containers::StridedArrayView<4, T>& self, const std::size_t dimension, std::size_t size) { .def("broadcasted", [](const Containers::PyStridedArrayView<4, T>& self, const std::size_t dimension, std::size_t size) {
if(dimension == 0) if(dimension == 0)
return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner); return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
if(dimension == 1) if(dimension == 1)
@ -579,45 +579,45 @@ template<class T> void stridedArrayView4D(py::class_<Containers::StridedArrayVie
}, "Broadcast a dimension"); }, "Broadcast a dimension");
} }
template<class T> void mutableStridedArrayView1D(py::class_<Containers::StridedArrayView<1, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<1, T>>>& c) { void mutableStridedArrayView1D(py::class_<Containers::PyStridedArrayView<1, char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<1, char>>>& c) {
c c
.def("__setitem__", [](const Containers::StridedArrayView<1, T>& self, const std::size_t i, const T& value) { .def("__setitem__", [](const Containers::PyStridedArrayView<1, char>& self, const std::size_t i, py::handle value) {
if(i >= self.size()) { if(i >= self.size()) {
PyErr_SetNone(PyExc_IndexError); PyErr_SetNone(PyExc_IndexError);
throw py::error_already_set{}; throw py::error_already_set{};
} }
self[i] = value; self.setitem(&self[i], value);
}, "Set a value at given position"); }, "Set a value at given position");
} }
template<class T> void mutableStridedArrayView2D(py::class_<Containers::StridedArrayView<2, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<2, T>>>& c) { void mutableStridedArrayView2D(py::class_<Containers::PyStridedArrayView<2, char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<2, char>>>& c) {
c c
.def("__setitem__", [](const Containers::StridedArrayView<2, T>& self, const std::tuple<std::size_t, std::size_t>& i, const T& value) { .def("__setitem__", [](const Containers::PyStridedArrayView<2, char>& self, const std::tuple<std::size_t, std::size_t>& i, py::handle value) {
if(std::get<0>(i) >= self.size()[0] || if(std::get<0>(i) >= self.size()[0] ||
std::get<1>(i) >= self.size()[1]) { std::get<1>(i) >= self.size()[1]) {
PyErr_SetNone(PyExc_IndexError); PyErr_SetNone(PyExc_IndexError);
throw py::error_already_set{}; throw py::error_already_set{};
} }
self[std::get<0>(i)][std::get<1>(i)] = value; self.setitem(&self[std::get<0>(i)][std::get<1>(i)], value);
}, "Set a value at given position"); }, "Set a value at given position");
} }
template<class T> void mutableStridedArrayView3D(py::class_<Containers::StridedArrayView<3, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<3, T>>>& c) { void mutableStridedArrayView3D(py::class_<Containers::PyStridedArrayView<3, char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<3, char>>>& c) {
c c
.def("__setitem__", [](const Containers::StridedArrayView<3, T>& self, const std::tuple<std::size_t, std::size_t, std::size_t>& i, const T& value) { .def("__setitem__", [](const Containers::PyStridedArrayView<3, char>& self, const std::tuple<std::size_t, std::size_t, std::size_t>& i, py::handle value) {
if(std::get<0>(i) >= self.size()[0] || if(std::get<0>(i) >= self.size()[0] ||
std::get<1>(i) >= self.size()[1] || std::get<1>(i) >= self.size()[1] ||
std::get<2>(i) >= self.size()[2]) { std::get<2>(i) >= self.size()[2]) {
PyErr_SetNone(PyExc_IndexError); PyErr_SetNone(PyExc_IndexError);
throw py::error_already_set{}; throw py::error_already_set{};
} }
self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)] = value; self.setitem(&self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)], value);
}, "Set a value at given position"); }, "Set a value at given position");
} }
template<class T> void mutableStridedArrayView4D(py::class_<Containers::StridedArrayView<4, T>, Containers::PyArrayViewHolder<Containers::StridedArrayView<4, T>>>& c) { void mutableStridedArrayView4D(py::class_<Containers::PyStridedArrayView<4, char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<4, char>>>& c) {
c c
.def("__setitem__", [](const Containers::StridedArrayView<4, T>& self, const std::tuple<std::size_t, std::size_t, std::size_t, std::size_t>& i, const T& value) { .def("__setitem__", [](const Containers::PyStridedArrayView<4, char>& self, const std::tuple<std::size_t, std::size_t, std::size_t, std::size_t>& i, py::handle value) {
if(std::get<0>(i) >= self.size()[0] || if(std::get<0>(i) >= self.size()[0] ||
std::get<1>(i) >= self.size()[1] || std::get<1>(i) >= self.size()[1] ||
std::get<2>(i) >= self.size()[2] || std::get<2>(i) >= self.size()[2] ||
@ -625,7 +625,7 @@ template<class T> void mutableStridedArrayView4D(py::class_<Containers::StridedA
PyErr_SetNone(PyExc_IndexError); PyErr_SetNone(PyExc_IndexError);
throw py::error_already_set{}; throw py::error_already_set{};
} }
self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)][std::get<3>(i)] = value; self.setitem(&self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)][std::get<3>(i)], value);
}, "Set a value at given position"); }, "Set a value at given position");
} }
@ -643,13 +643,13 @@ void containers(py::module_& m) {
arrayView(mutableArrayView_); arrayView(mutableArrayView_);
mutableArrayView(mutableArrayView_); mutableArrayView(mutableArrayView_);
py::class_<Containers::StridedArrayView<1, const char>, Containers::PyArrayViewHolder<Containers::StridedArrayView<1, const char>>> stridedArrayView1D_{m, py::class_<Containers::PyStridedArrayView<1, const char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<1, const char>>> stridedArrayView1D_{m,
"StridedArrayView1D", "One-dimensional array view with stride information", py::buffer_protocol{}}; "StridedArrayView1D", "One-dimensional array view with stride information", py::buffer_protocol{}};
py::class_<Containers::StridedArrayView<2, const char>, Containers::PyArrayViewHolder<Containers::StridedArrayView<2, const char>>> stridedArrayView2D_{m, py::class_<Containers::PyStridedArrayView<2, const char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<2, const char>>> stridedArrayView2D_{m,
"StridedArrayView2D", "Two-dimensional array view with stride information", py::buffer_protocol{}}; "StridedArrayView2D", "Two-dimensional array view with stride information", py::buffer_protocol{}};
py::class_<Containers::StridedArrayView<3, const char>, Containers::PyArrayViewHolder<Containers::StridedArrayView<3, const char>>> stridedArrayView3D_{m, py::class_<Containers::PyStridedArrayView<3, const char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<3, const char>>> stridedArrayView3D_{m,
"StridedArrayView3D", "Three-dimensional array view with stride information", py::buffer_protocol{}}; "StridedArrayView3D", "Three-dimensional array view with stride information", py::buffer_protocol{}};
py::class_<Containers::StridedArrayView<4, const char>, Containers::PyArrayViewHolder<Containers::StridedArrayView<4, const char>>> stridedArrayView4D_{m, py::class_<Containers::PyStridedArrayView<4, const char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<4, const char>>> stridedArrayView4D_{m,
"StridedArrayView4D", "Four-dimensional array view with stride information", py::buffer_protocol{}}; "StridedArrayView4D", "Four-dimensional array view with stride information", py::buffer_protocol{}};
stridedArrayView(stridedArrayView1D_); stridedArrayView(stridedArrayView1D_);
stridedArrayView1D(stridedArrayView1D_); stridedArrayView1D(stridedArrayView1D_);
@ -663,13 +663,13 @@ void containers(py::module_& m) {
stridedArrayViewND(stridedArrayView4D_); stridedArrayViewND(stridedArrayView4D_);
stridedArrayView4D(stridedArrayView4D_); stridedArrayView4D(stridedArrayView4D_);
py::class_<Containers::StridedArrayView<1, char>, Containers::PyArrayViewHolder<Containers::StridedArrayView<1, char>>> mutableStridedArrayView1D_{m, py::class_<Containers::PyStridedArrayView<1, char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<1, char>>> mutableStridedArrayView1D_{m,
"MutableStridedArrayView1D", "Mutable one-dimensional array view with stride information", py::buffer_protocol{}}; "MutableStridedArrayView1D", "Mutable one-dimensional array view with stride information", py::buffer_protocol{}};
py::class_<Containers::StridedArrayView<2, char>, Containers::PyArrayViewHolder<Containers::StridedArrayView<2, char>>> mutableStridedArrayView2D_{m, py::class_<Containers::PyStridedArrayView<2, char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<2, char>>> mutableStridedArrayView2D_{m,
"MutableStridedArrayView2D", "Mutable two-dimensional array view with stride information", py::buffer_protocol{}}; "MutableStridedArrayView2D", "Mutable two-dimensional array view with stride information", py::buffer_protocol{}};
py::class_<Containers::StridedArrayView<3, char>, Containers::PyArrayViewHolder<Containers::StridedArrayView<3, char>>> mutableStridedArrayView3D_{m, py::class_<Containers::PyStridedArrayView<3, char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<3, char>>> mutableStridedArrayView3D_{m,
"MutableStridedArrayView3D", "Mutable three-dimensional array view with stride information", py::buffer_protocol{}}; "MutableStridedArrayView3D", "Mutable three-dimensional array view with stride information", py::buffer_protocol{}};
py::class_<Containers::StridedArrayView<4, char>, Containers::PyArrayViewHolder<Containers::StridedArrayView<4, char>>> mutableStridedArrayView4D_{m, py::class_<Containers::PyStridedArrayView<4, char>, Containers::PyArrayViewHolder<Containers::PyStridedArrayView<4, char>>> mutableStridedArrayView4D_{m,
"MutableStridedArrayView4D", "Mutable four-dimensional array view with stride information", py::buffer_protocol{}}; "MutableStridedArrayView4D", "Mutable four-dimensional array view with stride information", py::buffer_protocol{}};
stridedArrayView(mutableStridedArrayView1D_); stridedArrayView(mutableStridedArrayView1D_);
stridedArrayView1D(mutableStridedArrayView1D_); stridedArrayView1D(mutableStridedArrayView1D_);

31
src/python/corrade/test/CMakeLists.txt

@ -0,0 +1,31 @@
#
# This file is part of Magnum.
#
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
# 2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
pybind11_add_module(test_stridedarrayview ${pybind11_add_module_SYSTEM} test_stridedarrayview.cpp)
target_include_directories(test_stridedarrayview PRIVATE ${PROJECT_SOURCE_DIR}/src)
target_link_libraries(test_stridedarrayview PRIVATE Corrade::Containers)
set_target_properties(test_stridedarrayview PROPERTIES
FOLDER "python"
LIBRARY_OUTPUT_DIRECTORY ${output_dir})

48
src/python/corrade/test/test_containers.py

@ -28,6 +28,7 @@ import sys
import unittest import unittest
from corrade import containers from corrade import containers
import test_stridedarrayview
class ArrayView(unittest.TestCase): class ArrayView(unittest.TestCase):
def test_init(self): def test_init(self):
@ -852,3 +853,50 @@ class StridedArrayView4D(unittest.TestCase):
self.assertEqual(f.size, (2, 1, 3, 5)) self.assertEqual(f.size, (2, 1, 3, 5))
self.assertEqual(f.stride, (24, 24, 8, 0)) self.assertEqual(f.stride, (24, 24, 8, 0))
self.assertEqual(bytes(f), b'000004444488888ccccc0000044444') self.assertEqual(bytes(f), b'000004444488888ccccc0000044444')
class StridedArrayViewCustomType(unittest.TestCase):
def test_short(self):
a = test_stridedarrayview.get_containers()
self.assertEqual(type(a.view), containers.StridedArrayView2D)
self.assertEqual(a.view.size, (2, 3))
self.assertEqual(a.view.stride, (3*2, 2))
self.assertEqual(a.view.format, 'h')
self.assertEqual(a.list, [3, -17565, 5, 3, -17565, 5])
self.assertEqual(a.view[0][0], 3)
self.assertEqual(a.view[0][1], -17565)
self.assertEqual(a.view[0][2], 5)
self.assertEqual(a.view[1][0], 3)
self.assertEqual(a.view[1][1], -17565)
self.assertEqual(a.view[1][2], 5)
with self.assertRaisesRegex(TypeError, "object does not support item assignment"):
a.view[1][1] = 15
# Test that memoryview understands the type
av = memoryview(a.view[0])
self.assertEqual(av[0], 3)
self.assertEqual(av[1], -17565)
self.assertEqual(av[2], 5)
def test_mutable_int(self):
a = test_stridedarrayview.MutableContaineri()
self.assertEqual(type(a.view), containers.MutableStridedArrayView2D)
self.assertEqual(a.view.format, 'i')
self.assertEqual(a.list, [0, 0, 0, 0, 0, 0])
a.view[0][1] = -7656581
a.view[1][2] = 4666
self.assertEqual(a.list, [0, -7656581, 0, 0, 0, 4666])
# Test that memoryview understands the type and has changes reflected
av = memoryview(a.view[1])
a.view[1][0] = -333
self.assertEqual(av[0], -333)
self.assertEqual(av[1], 0)
self.assertEqual(av[2], 4666)
# And the other way around as well
av[1] = 11111
self.assertEqual(a.list, [0, -7656581, 0, -333, 11111, 4666])
# mutable_vector3d and mutable_long_float tested in test_containers_numpy
# as memoryview can't handle their types

132
src/python/corrade/test/test_containers_numpy.py

@ -0,0 +1,132 @@
#
# This file is part of Magnum.
#
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
# 2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
import unittest
from corrade import containers
import test_stridedarrayview
try:
import numpy as np
except ModuleNotFoundError:
raise unittest.SkipTest("numpy not installed")
class StridedArrayViewCustomType(unittest.TestCase):
# short and mutable_int tested in test_containers, as for those memoryview
# works well... well, for one dimension it does
def test_mutable_vector3d(self):
a = test_stridedarrayview.MutableContainer3d()
self.assertEqual(type(a.view), containers.MutableStridedArrayView2D)
self.assertEqual(a.view.format, 'ddd')
self.assertEqual(a.list, [
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0]
])
a.view[0][1] = [-765.6581, 3.5, 1.125]
a.view[1][2] = [4.666, 0.25, -7.5]
self.assertEqual(a.list, [
[0.0, 0.0, 0.0],
[-765.6581, 3.5, 1.125],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[4.666, 0.25, -7.5]
])
# memoryview ... doesn't understand the type. HAH
mav = memoryview(a.view[0])
with self.assertRaisesRegex(NotImplementedError, "unsupported format ddd"):
self.assertEqual(mav[1], [-765.6581, 3.5, 1.125])
# Test that numpy understands the type and has changes reflected
av = np.array(a.view, copy=False)
a.view[1][0] = [-3.33, 1.0, 0.0]
# Converting to a tuple, otherwise numpy always compares to False
self.assertEqual(tuple(av[1][0]), (-3.33, 1.0, 0.0))
self.assertEqual(tuple(av[1][1]), (0.0, 0.0, 0.0))
self.assertEqual(tuple(av[1][2]), (4.666, 0.25, -7.5))
# And the other way around as well
av[1][1] = (1.0, 0.125, 1.125)
self.assertEqual(a.list, [
[0.0, 0.0, 0.0],
[-765.6581, 3.5, 1.125],
[0.0, 0.0, 0.0],
[-3.33, 1.0, 0.0],
[1.0, 0.125, 1.125],
[4.666, 0.25, -7.5]
])
def test_mutable_long_float(self):
a = test_stridedarrayview.MutableContainerlf()
self.assertEqual(type(a.view), containers.MutableStridedArrayView2D)
self.assertEqual(a.view.format, 'Qf')
self.assertEqual(a.list, [
(0, 0.0),
(0, 0.0),
(0, 0.0),
(0, 0.0),
(0, 0.0),
(0, 0.0)
])
a.view[0][1] = (7656581356781257, 1.125)
a.view[1][2] = (4666025, -7.5)
self.assertEqual(a.list, [
(0, 0.0),
(7656581356781257, 1.125),
(0, 0.0),
(0, 0.0),
(0, 0.0),
(4666025, -7.5)
])
# memoryview ... doesn't understand the type. HAH
mav = memoryview(a.view[0])
with self.assertRaisesRegex(NotImplementedError, "unsupported format Qf"):
self.assertEqual(mav[1], (7656581356781257, 1.125))
# Test that numpy understands the type and has changes reflected
av = np.array(a.view, copy=False)
a.view[1][0] = (333106832, 0.0)
# Converting to a tuple, otherwise numpy always compares to False
self.assertEqual(tuple(av[1][0]), (333106832, 0.0))
self.assertEqual(tuple(av[1][1]), (0, 0.0))
self.assertEqual(tuple(av[1][2]), (4666025, -7.5))
# And the other way around as well
av[1][1] = (1001, 1.125)
self.assertEqual(a.list, [
(0, 0.0),
(7656581356781257, 1.125),
(0, 0.0),
(333106832, 0.0),
(1001, 1.125),
(4666025, -7.5)
])

89
src/python/corrade/test/test_stridedarrayview.cpp

@ -0,0 +1,89 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "../bootstrap.h" /* for module / _module alias */
#include "Corrade/Containers/PythonBindings.h"
#include "Corrade/Containers/StridedArrayViewPythonBindings.h"
namespace Corrade { namespace Containers { namespace Implementation {
template<> constexpr const char* formatString<std::array<double, 3>>() {
return "ddd";
}
template<> constexpr const char* formatString<std::pair<std::uint64_t, float>>() {
return "Qf";
}
}}}
using namespace Corrade;
namespace py = pybind11;
template<class T> struct Container {
Container(T a = {}, T b = {}, T c = {}): data{a, b, c, a, b, c} {}
Containers::StridedArrayView2D<T> view() {
return {Containers::arrayView(data), {2, 3}};
}
std::vector<typename std::remove_const<T>::type> list() const {
return {data, data + 6};
}
T data[3*2]{};
};
template<class T> void container(py::class_<Container<T>>& c) {
c
.def(py::init())
.def_property_readonly("view", [](Container<T>& self) {
return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<2, typename std::conditional<std::is_const<T>::value, const char, char>::type>{self.view()}, py::cast(self));
})
.def_property_readonly("list", &Container<T>::list);
}
/* TODO: remove declaration when https://github.com/pybind/pybind11/pull/1863
is released */
extern "C" PYBIND11_EXPORT PyObject* PyInit_test_stridedarrayview();
PYBIND11_MODULE(test_stridedarrayview, m) {
/* These are a part of the same module in the static build, no need to
import (also can't import because there it's _magnum.*) */
py::module_::import("corrade.containers");
py::class_<Container<const std::int16_t>> containers{m, "Containers"};
py::class_<Container<int>> mutableContaineri{m, "MutableContaineri"};
py::class_<Container<std::array<double, 3>>> mutableContainer3d{m, "MutableContainer3d"};
py::class_<Container<std::pair<std::uint64_t, float>>> mutableContainerlf{m, "MutableContainerlf"};
container(containers);
container(mutableContaineri);
container(mutableContainer3d);
container(mutableContainerlf);
m.def("get_containers", []() {
return Container<const std::int16_t>{3, -17565, 5};
});
}

5
src/python/magnum/magnum.cpp

@ -35,6 +35,7 @@
#include "Corrade/PythonBindings.h" #include "Corrade/PythonBindings.h"
#include "Corrade/Containers/PythonBindings.h" #include "Corrade/Containers/PythonBindings.h"
#include "Corrade/Containers/StridedArrayViewPythonBindings.h"
#include "Magnum/PythonBindings.h" #include "Magnum/PythonBindings.h"
#include "magnum/bootstrap.h" #include "magnum/bootstrap.h"
@ -66,7 +67,7 @@ template<class T> void image(py::class_<T>& c) {
return Containers::pyArrayViewHolder(self.data(), self.data() ? py::cast(self) : py::none{}); return Containers::pyArrayViewHolder(self.data(), self.data() ? py::cast(self) : py::none{});
}, "Image data") }, "Image data")
.def_property_readonly("pixels", [](T& self) { .def_property_readonly("pixels", [](T& self) {
return Containers::pyArrayViewHolder(self.pixels(), self.data() ? py::cast(self) : py::none{}); return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<T::Dimensions + 1, const char>{self.pixels()}, self.data() ? py::cast(self) : py::none{});
}, "View on pixel data"); }, "View on pixel data");
} }
@ -136,7 +137,7 @@ template<class T> void imageView(py::class_<T, PyImageViewHolder<T>>& c) {
pyObjectHolderFor<Containers::PyArrayViewHolder>(data).owner; pyObjectHolderFor<Containers::PyArrayViewHolder>(data).owner;
}, "Image data") }, "Image data")
.def_property_readonly("pixels", [](T& self) { .def_property_readonly("pixels", [](T& self) {
return Containers::pyArrayViewHolder(self.pixels(), pyObjectHolderFor<PyImageViewHolder>(self).owner); return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<T::Dimensions + 1, typename T::Type>{self.pixels()}, pyObjectHolderFor<PyImageViewHolder>(self).owner);
}, "View on pixel data") }, "View on pixel data")
.def_property_readonly("owner", [](T& self) { .def_property_readonly("owner", [](T& self) {

3
src/python/magnum/trade.cpp

@ -32,6 +32,7 @@
#include <Magnum/Trade/MeshData.h> #include <Magnum/Trade/MeshData.h>
#include "Corrade/Containers/PythonBindings.h" #include "Corrade/Containers/PythonBindings.h"
#include "Corrade/Containers/StridedArrayViewPythonBindings.h"
#include "Magnum/PythonBindings.h" #include "Magnum/PythonBindings.h"
#include "corrade/pluginmanager.h" #include "corrade/pluginmanager.h"
@ -132,7 +133,7 @@ template<UnsignedInt dimensions> void imageData(py::class_<Trade::ImageData<dime
throw py::error_already_set{}; throw py::error_already_set{};
} }
return Containers::pyArrayViewHolder(self.pixels(), py::cast(self)); return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<dimensions + 1, const char>{self.pixels()}, py::cast(self));
}, "View on pixel data"); }, "View on pixel data");
} }

Loading…
Cancel
Save