diff --git a/src/Corrade/Containers/StridedArrayViewPythonBindings.h b/src/Corrade/Containers/StridedArrayViewPythonBindings.h index 6d2154f..ff6e603 100644 --- a/src/Corrade/Containers/StridedArrayViewPythonBindings.h +++ b/src/Corrade/Containers/StridedArrayViewPythonBindings.h @@ -98,6 +98,9 @@ template class PyStridedArrayView: public StridedA ElementType operator[](std::size_t i) const { return Implementation::PyStridedElement::wrap(StridedArrayView::operator[](i), format, itemsize, getitem, setitem); } + T& operator[](const Size& i) const { + return StridedArrayView::operator[](i); + } PyStridedArrayView slice(std::size_t begin, std::size_t end) const { return PyStridedArrayView{StridedArrayView::slice(begin, end), format, itemsize, getitem, setitem}; diff --git a/src/python/corrade/containers.cpp b/src/python/corrade/containers.cpp index 64efe03..a42649c 100644 --- a/src/python/corrade/containers.cpp +++ b/src/python/corrade/containers.cpp @@ -305,6 +305,86 @@ 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 */ } +template struct StridedOperation; +template<> struct StridedOperation<1> { + template class View, unsigned dimensions, class T> static View flipped(const View& view, unsigned dimension) { + if(dimension == 0) + return view.template flipped<0>(); + PyErr_Format(PyExc_ValueError, "dimension %u out of range for a %iD view", dimension, dimensions); + throw py::error_already_set{}; + } + template class View, unsigned dimensions, class T> static View broadcasted(const View& view, unsigned dimension, std::size_t size) { + if(dimension == 0) + return view.template broadcasted<0>(size); + PyErr_Format(PyExc_ValueError, "dimension %u out of range for a %iD view", dimension, dimensions); + throw py::error_already_set{}; + } +}; +template<> struct StridedOperation<2> { + template class View, unsigned dimensions, class T> static View transposed(const View& view, unsigned a, unsigned b) { + if((a == 0 && b == 1) || + (a == 1 && b == 0)) + return view.template transposed<0, 1>(); + PyErr_Format(PyExc_ValueError, "dimensions %u, %u can't be transposed in a %iD view", a, b, dimensions); + throw py::error_already_set{}; + } + template class View, unsigned dimensions, class T> static View flipped(const View& view, unsigned dimension) { + if(dimension == 1) + return view.template flipped<1>(); + return StridedOperation<1>::flipped(view, dimension); + } + template class View, unsigned dimensions, class T> static View broadcasted(const View& view, unsigned dimension, std::size_t size) { + if(dimension == 1) + return view.template broadcasted<1>(size); + return StridedOperation<1>::broadcasted(view, dimension, size); + } +}; +template<> struct StridedOperation<3> { + template class View, unsigned dimensions, class T> static View transposed(const View& view, unsigned a, unsigned b) { + if((a == 0 && b == 2) || + (a == 2 && b == 0)) + return view.template transposed<0, 2>(); + if((a == 1 && b == 2) || + (a == 2 && b == 1)) + return view.template transposed<1, 2>(); + return StridedOperation<2>::transposed(view, a, b); + } + template class View, unsigned dimensions, class T> static View flipped(const View& view, unsigned dimension) { + if(dimension == 2) + return view.template flipped<2>(); + return StridedOperation<2>::flipped(view, dimension); + } + template class View, unsigned dimensions, class T> static View broadcasted(const View& view, unsigned dimension, std::size_t size) { + if(dimension == 2) + return view.template broadcasted<2>(size); + return StridedOperation<2>::broadcasted(view, dimension, size); + } +}; +template<> struct StridedOperation<4> { + template class View, unsigned dimensions, class T> static View transposed(const View& view, unsigned a, unsigned b) { + if((a == 0 && b == 3) || + (a == 3 && b == 0)) + return view.template transposed<0, 3>(); + if((a == 1 && b == 3) || + (a == 3 && b == 1)) + return view.template transposed<1, 3>(); + if((a == 2 && b == 3) || + (a == 3 && b == 2)) + return view.template transposed<2, 3>(); + return StridedOperation<3>::transposed(view, a, b); + } + template class View, unsigned dimensions, class T> static View flipped(const View& view, unsigned dimension) { + if(dimension == 3) + return view.template flipped<3>(); + return StridedOperation<3>::flipped(view, dimension); + } + template class View, unsigned dimensions, class T> static View broadcasted(const View& view, unsigned dimension, std::size_t size) { + if(dimension == 3) + return view.template broadcasted<3>(size); + return StridedOperation<3>::broadcasted(view, dimension, size); + } +}; + template void stridedArrayView(py::class_, Containers::PyArrayViewHolder>>& c) { /* Implicitly convertible from a buffer */ py::implicitly_convertible>(); @@ -381,7 +461,15 @@ template void stridedArrayView(py::class_{self.size()}[0]); const auto sliced = self.slice(calculated.start, calculated.stop).every(calculated.step); return Containers::pyArrayViewHolder(sliced, calculated.start == calculated.stop ? py::none{} : pyObjectHolderFor(self).owner); - }, "Slice the view"); + }, "Slice the view") + + /* Fancy operations */ + .def("flipped", [](const Containers::PyStridedArrayView& self, const std::size_t dimension) { + return Containers::pyArrayViewHolder(StridedOperation::flipped(self, dimension), pyObjectHolderFor(self).owner); + }, "Flip a dimension") + .def("broadcasted", [](const Containers::PyStridedArrayView& self, const std::size_t dimension, std::size_t size) { + return Containers::pyArrayViewHolder(StridedOperation::broadcasted(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Broadcast a dimension"); enableBetterBufferProtocol, stridedArrayViewBufferProtocol>(c); } @@ -401,8 +489,8 @@ template void stridedArrayView1D(py::class_ void stridedArrayViewND(py::class_, Containers::PyArrayViewHolder>>& c) { c - /* Sub-view retrieval. Need to raise IndexError in order to allow - iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ + /* Sub-view and single item retrieval. Need to raise IndexError in + order to allow iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ .def("__getitem__", [](const Containers::PyStridedArrayView& self, std::size_t i) { if(i >= Containers::Size{self.size()}[0]) { PyErr_SetNone(PyExc_IndexError); @@ -410,6 +498,18 @@ template void stridedArrayViewND(py::class_(self).owner); }, "Sub-view at given position") + .def("__getitem__", [](const Containers::PyStridedArrayView& self, const typename DimensionsTuple::Type& iTuple) { + Containers::Size iSize{NoInit}; + for(std::size_t j = 0; j != dimensions; ++j) { + const std::size_t i = dimensionsTupleGet(iTuple, j); + if(i >= self.size()[j]) { + PyErr_SetNone(PyExc_IndexError); + throw py::error_already_set{}; + } + iSize[j] = i; + } + return self.getitem(&self[iSize]); + }, "Value at given position") /* Multi-dimensional slicing */ .def("__getitem__", [](const Containers::PyStridedArrayView& self, const typename DimensionsTuple::Type& slice) { @@ -429,154 +529,12 @@ template void stridedArrayViewND(py::class_(self).owner); - }, "Slice the view"); -} - -template void stridedArrayView2D(py::class_, Containers::PyArrayViewHolder>>& c) { - c - /* Single-item retrieval. Need to raise IndexError in order to allow - iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ - .def("__getitem__", [](const Containers::PyStridedArrayView<2, T>& self, const std::tuple& i) { - if(std::get<0>(i) >= self.size()[0] || - std::get<1>(i) >= self.size()[1]) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return self.getitem(&self[std::get<0>(i)][std::get<1>(i)]); - }, "Value at given position") - .def("transposed", [](const Containers::PyStridedArrayView<2, T>& self, const std::size_t a, std::size_t b) { - if((a == 0 && b == 1) || - (a == 1 && b == 0)) - return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor(self).owner); - PyErr_Format(PyExc_ValueError, "dimensions %zu, %zu can't be transposed in a %iD view", a, b, 2); - throw py::error_already_set{}; - }, "Transpose two dimensions") - .def("flipped", [](const Containers::PyStridedArrayView<2, T>& self, const std::size_t dimension) { - if(dimension == 0) - return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor(self).owner); - if(dimension == 1) - return Containers::pyArrayViewHolder(self.template flipped<1>(), pyObjectHolderFor(self).owner); - PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 2); - throw py::error_already_set{}; - }, "Flip a dimension") - .def("broadcasted", [](const Containers::PyStridedArrayView<2, T>& self, const std::size_t dimension, std::size_t size) { - if(dimension == 0) - return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor(self).owner); - if(dimension == 1) - return Containers::pyArrayViewHolder(self.template broadcasted<1>(size), pyObjectHolderFor(self).owner); - PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 2); - throw py::error_already_set{}; - }, "Broadcast a dimension"); -} - -template void stridedArrayView3D(py::class_, Containers::PyArrayViewHolder>>& c) { - c - /* Single-item retrieval. Need to raise IndexError in order to allow - iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ - .def("__getitem__", [](const Containers::PyStridedArrayView<3, T>& self, const std::tuple& i) { - if(std::get<0>(i) >= self.size()[0] || - std::get<1>(i) >= self.size()[1] || - std::get<2>(i) >= self.size()[2]) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return self.getitem(&self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)]); - }, "Value at given position") - .def("transposed", [](const Containers::PyStridedArrayView<3, T>& self, const std::size_t a, std::size_t b) { - if((a == 0 && b == 1) || - (a == 1 && b == 0)) - return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor(self).owner); - if((a == 0 && b == 2) || - (a == 2 && b == 0)) - return Containers::pyArrayViewHolder(self.template transposed<0, 2>(), pyObjectHolderFor(self).owner); - if((a == 1 && b == 2) || - (a == 2 && b == 1)) - return Containers::pyArrayViewHolder(self.template transposed<1, 2>(), pyObjectHolderFor(self).owner); - PyErr_Format(PyExc_ValueError, "dimensions %zu, %zu can't be transposed in a %iD view", a, b, 2); - throw py::error_already_set{}; - }, "Transpose two dimensions") - .def("flipped", [](const Containers::PyStridedArrayView<3, T>& self, const std::size_t dimension) { - if(dimension == 0) - return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor(self).owner); - if(dimension == 1) - return Containers::pyArrayViewHolder(self.template flipped<1>(), pyObjectHolderFor(self).owner); - if(dimension == 2) - return Containers::pyArrayViewHolder(self.template flipped<2>(), pyObjectHolderFor(self).owner); - PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 3); - throw py::error_already_set{}; - }, "Flip a dimension") - .def("broadcasted", [](const Containers::PyStridedArrayView<3, T>& self, const std::size_t dimension, std::size_t size) { - if(dimension == 0) - return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor(self).owner); - if(dimension == 1) - return Containers::pyArrayViewHolder(self.template broadcasted<1>(size), pyObjectHolderFor(self).owner); - if(dimension == 2) - return Containers::pyArrayViewHolder(self.template broadcasted<2>(size), pyObjectHolderFor(self).owner); - PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 3); - throw py::error_already_set{}; - }, "Broadcast a dimension"); -} + }, "Slice the view") -template void stridedArrayView4D(py::class_, Containers::PyArrayViewHolder>>& c) { - c - /* Single-item retrieval. Need to raise IndexError in order to allow - iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ - .def("__getitem__", [](const Containers::PyStridedArrayView<4, T>& self, const std::tuple& i) { - if(std::get<0>(i) >= self.size()[0] || - std::get<1>(i) >= self.size()[1] || - std::get<2>(i) >= self.size()[2] || - std::get<3>(i) >= self.size()[3]) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return self.getitem(&self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)][std::get<3>(i)]); - }, "Value at given position") - .def("transposed", [](const Containers::PyStridedArrayView<4, T>& self, const std::size_t a, std::size_t b) { - if((a == 0 && b == 1) || - (a == 1 && b == 0)) - return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor(self).owner); - if((a == 0 && b == 2) || - (a == 2 && b == 0)) - return Containers::pyArrayViewHolder(self.template transposed<0, 2>(), pyObjectHolderFor(self).owner); - if((a == 0 && b == 3) || - (a == 3 && b == 0)) - return Containers::pyArrayViewHolder(self.template transposed<0, 3>(), pyObjectHolderFor(self).owner); - if((a == 1 && b == 2) || - (a == 2 && b == 1)) - return Containers::pyArrayViewHolder(self.template transposed<1, 2>(), pyObjectHolderFor(self).owner); - if((a == 1 && b == 3) || - (a == 3 && b == 1)) - return Containers::pyArrayViewHolder(self.template transposed<1, 3>(), pyObjectHolderFor(self).owner); - if((a == 2 && b == 3) || - (a == 3 && b == 2)) - return Containers::pyArrayViewHolder(self.template transposed<2, 3>(), pyObjectHolderFor(self).owner); - PyErr_Format(PyExc_ValueError, "dimensions %zu, %zu can't be transposed in a %iD view", a, b, 4); - throw py::error_already_set{}; - }, "Transpose two dimensions") - .def("flipped", [](const Containers::PyStridedArrayView<4, T>& self, const std::size_t dimension) { - if(dimension == 0) - return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor(self).owner); - if(dimension == 1) - return Containers::pyArrayViewHolder(self.template flipped<1>(), pyObjectHolderFor(self).owner); - if(dimension == 2) - return Containers::pyArrayViewHolder(self.template flipped<2>(), pyObjectHolderFor(self).owner); - if(dimension == 3) - return Containers::pyArrayViewHolder(self.template flipped<3>(), pyObjectHolderFor(self).owner); - PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 4); - throw py::error_already_set{}; - }, "Flip a dimension") - .def("broadcasted", [](const Containers::PyStridedArrayView<4, T>& self, const std::size_t dimension, std::size_t size) { - if(dimension == 0) - return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor(self).owner); - if(dimension == 1) - return Containers::pyArrayViewHolder(self.template broadcasted<1>(size), pyObjectHolderFor(self).owner); - if(dimension == 2) - return Containers::pyArrayViewHolder(self.template broadcasted<2>(size), pyObjectHolderFor(self).owner); - if(dimension == 3) - return Containers::pyArrayViewHolder(self.template broadcasted<3>(size), pyObjectHolderFor(self).owner); - PyErr_Format(PyExc_ValueError, "dimension %zu out of range for a %iD view", dimension, 4); - throw py::error_already_set{}; - }, "Broadcast a dimension"); + /* Fancy operations */ + .def("transposed", [](const Containers::PyStridedArrayView& self, const std::size_t a, std::size_t b) { + return Containers::pyArrayViewHolder(StridedOperation::transposed(self, a, b), pyObjectHolderFor(self).owner); + }, "Transpose two dimensions"); } void mutableStridedArrayView1D(py::class_, Containers::PyArrayViewHolder>>& c) { @@ -590,42 +548,19 @@ void mutableStridedArrayView1D(py::class_, Containers::PyArrayViewHolder>>& c) { - c - .def("__setitem__", [](const Containers::PyStridedArrayView<2, char>& self, const std::tuple& i, py::handle value) { - if(std::get<0>(i) >= self.size()[0] || - std::get<1>(i) >= self.size()[1]) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - self.setitem(&self[std::get<0>(i)][std::get<1>(i)], value); - }, "Set a value at given position"); -} - -void mutableStridedArrayView3D(py::class_, Containers::PyArrayViewHolder>>& c) { +template void mutableStridedArrayViewND(py::class_, Containers::PyArrayViewHolder>>& c) { c - .def("__setitem__", [](const Containers::PyStridedArrayView<3, char>& self, const std::tuple& i, py::handle value) { - if(std::get<0>(i) >= self.size()[0] || - std::get<1>(i) >= self.size()[1] || - std::get<2>(i) >= self.size()[2]) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - self.setitem(&self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)], value); - }, "Set a value at given position"); -} - -void mutableStridedArrayView4D(py::class_, Containers::PyArrayViewHolder>>& c) { - c - .def("__setitem__", [](const Containers::PyStridedArrayView<4, char>& self, const std::tuple& i, py::handle value) { - if(std::get<0>(i) >= self.size()[0] || - std::get<1>(i) >= self.size()[1] || - std::get<2>(i) >= self.size()[2] || - std::get<3>(i) >= self.size()[3]) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; + .def("__setitem__", [](const Containers::PyStridedArrayView& self, const typename DimensionsTuple::Type& iTuple, py::handle value) { + Containers::Size iSize{NoInit}; + for(std::size_t j = 0; j != dimensions; ++j) { + const std::size_t i = dimensionsTupleGet(iTuple, j); + if(i >= self.size()[j]) { + PyErr_SetNone(PyExc_IndexError); + throw py::error_already_set{}; + } + iSize[j] = i; } - self.setitem(&self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)][std::get<3>(i)], value); + self.setitem(&self[iSize], value); }, "Set a value at given position"); } @@ -655,13 +590,10 @@ void containers(py::module_& m) { stridedArrayView1D(stridedArrayView1D_); stridedArrayView(stridedArrayView2D_); stridedArrayViewND(stridedArrayView2D_); - stridedArrayView2D(stridedArrayView2D_); stridedArrayView(stridedArrayView3D_); stridedArrayViewND(stridedArrayView3D_); - stridedArrayView3D(stridedArrayView3D_); stridedArrayView(stridedArrayView4D_); stridedArrayViewND(stridedArrayView4D_); - stridedArrayView4D(stridedArrayView4D_); py::class_, Containers::PyArrayViewHolder>> mutableStridedArrayView1D_{m, "MutableStridedArrayView1D", "Mutable one-dimensional array view with stride information", py::buffer_protocol{}}; @@ -675,17 +607,14 @@ void containers(py::module_& m) { stridedArrayView1D(mutableStridedArrayView1D_); stridedArrayView(mutableStridedArrayView2D_); stridedArrayViewND(mutableStridedArrayView2D_); - stridedArrayView2D(mutableStridedArrayView2D_); stridedArrayView(mutableStridedArrayView3D_); stridedArrayViewND(mutableStridedArrayView3D_); - stridedArrayView3D(mutableStridedArrayView3D_); stridedArrayView(mutableStridedArrayView4D_); stridedArrayViewND(mutableStridedArrayView4D_); - stridedArrayView4D(mutableStridedArrayView4D_); mutableStridedArrayView1D(mutableStridedArrayView1D_); - mutableStridedArrayView2D(mutableStridedArrayView2D_); - mutableStridedArrayView3D(mutableStridedArrayView3D_); - mutableStridedArrayView4D(mutableStridedArrayView4D_); + mutableStridedArrayViewND(mutableStridedArrayView2D_); + mutableStridedArrayViewND(mutableStridedArrayView3D_); + mutableStridedArrayViewND(mutableStridedArrayView4D_); } }