Browse Source
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
10 changed files with 544 additions and 75 deletions
@ -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 |
||||
@ -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}) |
||||
@ -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) |
||||
]) |
||||
@ -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}; |
||||
}); |
||||
} |
||||
Loading…
Reference in new issue