13 changed files with 1385 additions and 2 deletions
@ -0,0 +1,40 @@
|
||||
# |
||||
# This file is part of Magnum. |
||||
# |
||||
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
# 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. |
||||
# |
||||
|
||||
set(corrade_containers_SRCS |
||||
containers.cpp) |
||||
|
||||
pybind11_add_module(corrade_containers ${corrade_containers_SRCS}) |
||||
target_include_directories(corrade_containers PRIVATE ${PROJECT_SOURCE_DIR}/src/python) |
||||
target_link_libraries(corrade_containers PRIVATE |
||||
Corrade::Containers |
||||
Corrade::Utility) |
||||
set_target_properties(corrade_containers PROPERTIES |
||||
FOLDER "python/corrade" |
||||
OUTPUT_NAME "containers" |
||||
LIBRARY_OUTPUT_DIRECTORY ${output_dir}/corrade) |
||||
|
||||
file(GENERATE OUTPUT ${output_dir}/corrade/__init__.py |
||||
INPUT ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py) |
||||
@ -0,0 +1,54 @@
|
||||
#ifndef corrade_PyArrayView_h |
||||
#define corrade_PyArrayView_h |
||||
/*
|
||||
This file is part of Magnum. |
||||
|
||||
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
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/ArrayView.h> |
||||
#include <Corrade/Containers/StridedArrayView.h> |
||||
|
||||
#include "corrade/bootstrap.h" |
||||
|
||||
namespace corrade { |
||||
|
||||
/* Wrapper for Containers::ArrayView holding a reference to the memory owner */ |
||||
template<class T> struct PyArrayView: Containers::ArrayView<T> { |
||||
/*implicit*/PyArrayView() noexcept: obj{py::none{}} {} |
||||
explicit PyArrayView(Containers::ArrayView<T> view, py::object obj) noexcept: Containers::ArrayView<T>{view}, obj{std::move(obj)} {} |
||||
|
||||
py::object obj; |
||||
}; |
||||
|
||||
/* Wrapper for Containers::StridedArrayView holding a reference to the memory owner */ |
||||
template<unsigned dimensions, class T> struct PyStridedArrayView: Containers::StridedArrayView<dimensions, T> { |
||||
/*implicit*/ PyStridedArrayView() noexcept: obj{py::none{}} {} |
||||
explicit PyStridedArrayView(Containers::StridedArrayView<dimensions, T> view, py::object obj) noexcept: Containers::StridedArrayView<dimensions, T>{view}, obj{std::move(obj)} {} |
||||
|
||||
py::object obj; |
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -0,0 +1,37 @@
|
||||
#ifndef corrade_PybindExtras_h |
||||
#define corrade_PybindExtras_h |
||||
/*
|
||||
This file is part of Magnum. |
||||
|
||||
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
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> |
||||
|
||||
namespace pybind11 { |
||||
|
||||
// TODO: add this to pybind itself
|
||||
PYBIND11_RUNTIME_EXCEPTION(buffer_error, PyExc_BufferError) |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -0,0 +1,26 @@
|
||||
# |
||||
# This file is part of Magnum. |
||||
# |
||||
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
# 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. |
||||
# |
||||
|
||||
"""Root Corrade module""" |
||||
@ -0,0 +1,38 @@
|
||||
#ifndef corrade_h |
||||
#define corrade_h |
||||
/*
|
||||
This file is part of Magnum. |
||||
|
||||
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
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. |
||||
*/ |
||||
|
||||
namespace pybind11 { class module; } |
||||
namespace Corrade {} |
||||
|
||||
namespace corrade { |
||||
|
||||
using namespace Corrade; |
||||
namespace py = pybind11; |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -0,0 +1,452 @@
|
||||
/*
|
||||
This file is part of Magnum. |
||||
|
||||
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
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/Array.h> |
||||
#include <Corrade/Utility/FormatStl.h> |
||||
|
||||
#include "corrade/bootstrap.h" |
||||
#include "corrade/PyArrayView.h" |
||||
#include "corrade/PybindExtras.h" |
||||
|
||||
namespace corrade { namespace { |
||||
|
||||
struct Slice { |
||||
std::size_t start; |
||||
std::size_t stop; |
||||
std::ptrdiff_t step; |
||||
}; |
||||
|
||||
Slice calculateSlice(py::slice slice, std::size_t containerSize) { |
||||
std::size_t size; |
||||
std::ptrdiff_t start, stop, step; |
||||
|
||||
/* Happens for example when passing a tuple as a slice or a zero step */ |
||||
if(!slice.compute(containerSize, reinterpret_cast<std::size_t*>(&start), reinterpret_cast<std::size_t*>(&stop), reinterpret_cast<std::size_t*>(&step), &size)) |
||||
throw py::error_already_set{}; |
||||
|
||||
/* If step is negative, start > stop and we have to recalculate */ |
||||
CORRADE_INTERNAL_ASSERT((start <= stop) == (step > 0)); |
||||
if(step < 0) { |
||||
std::swap(start, stop); |
||||
start += 1; |
||||
stop += 1; |
||||
} |
||||
|
||||
return Slice{std::size_t(start), std::size_t(stop), step}; |
||||
} |
||||
|
||||
template<class T> void arrayView(py::class_<PyArrayView<T>>& c) { |
||||
/* Implicitly convertible from a buffer */ |
||||
py::implicitly_convertible<py::buffer, PyArrayView<T>>(); |
||||
|
||||
c |
||||
/* Constructor */ |
||||
.def(py::init(), "Default constructor") |
||||
|
||||
/* Buffer protocol */ |
||||
.def(py::init([](py::buffer buffer) { |
||||
py::buffer_info info = buffer.request(!std::is_const<T>::value); |
||||
|
||||
// TODO: test for items that are not 1 byte size
|
||||
|
||||
if(info.ndim != 1) |
||||
throw py::buffer_error{Utility::formatString("expected one dimension but got {}", info.ndim)}; |
||||
if(info.strides[0] != 1) |
||||
throw py::buffer_error{Utility::formatString("expected stride of 1 but got {}", info.strides[0])}; |
||||
|
||||
// TODO: need to take buffer.obj, not buffer!
|
||||
return PyArrayView<T>{{static_cast<T*>(info.ptr), std::size_t(info.shape[0])}, buffer}; |
||||
}), "Construct from a buffer") |
||||
.def_buffer([](const PyArrayView<T>& self) -> py::buffer_info { |
||||
return py::buffer_info{ |
||||
const_cast<char*>(self.data()), |
||||
sizeof(T), |
||||
py::format_descriptor<T>::format(), |
||||
1, |
||||
{self.size()}, |
||||
{1} // TODO: need to pass self.obj to the buffer, not self!
|
||||
}; |
||||
}) |
||||
|
||||
/* Length and memory owning object */ |
||||
.def("__len__", &PyArrayView<T>::size, "View size") |
||||
.def_readonly("obj", &PyArrayView<T>::obj, "Memory owner object") |
||||
|
||||
/* Conversion to bytes */ |
||||
.def("__bytes__", [](const PyArrayView<T>& self) { |
||||
return py::bytes(self.data(), self.size()); |
||||
}, "Convert to bytes") |
||||
|
||||
/* Single item retrieval. Need to throw IndexError in order to allow
|
||||
iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */
|
||||
.def("__getitem__", [](const PyArrayView<T>& self, std::size_t i) { |
||||
if(i >= self.size()) throw pybind11::index_error{}; |
||||
return self[i]; |
||||
}, "Value at given position") |
||||
|
||||
/* Slicing */ |
||||
.def("__getitem__", [](const PyArrayView<T>& self, py::slice slice) -> py::object { |
||||
const Slice calculated = calculateSlice(slice, self.size()); |
||||
|
||||
/* Non-trivial stride, return a different type */ |
||||
if(calculated.step != 1) { |
||||
return py::cast(PyStridedArrayView<1, T>( |
||||
Containers::stridedArrayView(self).slice(calculated.start, calculated.stop).every(calculated.step), self.obj)); |
||||
} |
||||
|
||||
/* Usual business */ |
||||
return py::cast(PyArrayView<T>{self.slice(calculated.start, calculated.stop), self.obj}); |
||||
}, "Slice the view"); |
||||
} |
||||
|
||||
template<class T> void mutableArrayView(py::class_<PyArrayView<T>>& c) { |
||||
c |
||||
.def("__setitem__", [](const PyArrayView<T>& self, std::size_t i, const T& value) { |
||||
if(i >= self.size()) throw pybind11::index_error{}; |
||||
self[i] = value; |
||||
}, "Set a value at given position"); |
||||
} |
||||
|
||||
/* Tuple for given dimension */ |
||||
template<unsigned dimensions, class T> struct DimensionsTuple; |
||||
template<class T> struct DimensionsTuple<1, T> { typedef std::tuple<T> Type; }; |
||||
template<class T> struct DimensionsTuple<2, T> { typedef std::tuple<T, T> Type; }; |
||||
template<class T> struct DimensionsTuple<3, T> { typedef std::tuple<T, T, T> Type; }; |
||||
|
||||
/* Size tuple for given dimension */ |
||||
template<unsigned dimensions> typename DimensionsTuple<dimensions, std::size_t>::Type size(Containers::StridedDimensions<dimensions, std::size_t>); |
||||
template<> std::tuple<std::size_t> size(Containers::StridedDimensions<1, std::size_t> size) { |
||||
return std::make_tuple(size[0]); |
||||
} |
||||
template<> std::tuple<std::size_t, std::size_t> size(Containers::StridedDimensions<2, std::size_t> size) { |
||||
return std::make_tuple(size[0], size[1]); |
||||
} |
||||
template<> std::tuple<std::size_t, std::size_t, std::size_t> size(Containers::StridedDimensions<3, std::size_t> size) { |
||||
return std::make_tuple(size[0], size[1], size[2]); |
||||
} |
||||
|
||||
/* Stride tuple for given dimension */ |
||||
template<unsigned dimensions> typename DimensionsTuple<dimensions, std::ptrdiff_t>::Type stride(Containers::StridedDimensions<dimensions, std::ptrdiff_t>); |
||||
template<> std::tuple<std::ptrdiff_t> stride(Containers::StridedDimensions<1, std::ptrdiff_t> stride) { |
||||
return std::make_tuple(stride[0]); |
||||
} |
||||
template<> std::tuple<std::ptrdiff_t, std::ptrdiff_t> stride(Containers::StridedDimensions<2, std::ptrdiff_t> stride) { |
||||
return std::make_tuple(stride[0], stride[1]); |
||||
} |
||||
template<> std::tuple<std::ptrdiff_t, std::ptrdiff_t, std::ptrdiff_t> stride(Containers::StridedDimensions<3, std::ptrdiff_t> stride) { |
||||
return std::make_tuple(stride[0], stride[1], stride[2]); |
||||
} |
||||
|
||||
/* Byte conversion for given dimension */ |
||||
template<unsigned dimensions> Containers::Array<char> bytes(Containers::StridedArrayView<dimensions, const char>); |
||||
template<> Containers::Array<char> bytes(Containers::StridedArrayView1D<const char> view) { |
||||
Containers::Array<char> out{view.size()}; |
||||
std::size_t pos = 0; |
||||
for(const char i: view) out[pos++] = i; |
||||
return out; |
||||
} |
||||
template<> Containers::Array<char> bytes(Containers::StridedArrayView2D<const char> view) { |
||||
Containers::Array<char> out{view.size()[0]*view.size()[1]}; |
||||
std::size_t pos = 0; |
||||
for(Containers::StridedArrayView1D<const char> i: view) |
||||
for(const char j: i) out[pos++] = j; |
||||
return out; |
||||
} |
||||
template<> Containers::Array<char> bytes(Containers::StridedArrayView3D<const char> view) { |
||||
Containers::Array<char> out{view.size()[0]*view.size()[1]*view.size()[2]}; |
||||
std::size_t pos = 0; |
||||
for(Containers::StridedArrayView2D<const char> i: view) |
||||
for(Containers::StridedArrayView1D<const char> j: i) |
||||
for(const char k: j) out[pos++] = k; |
||||
return out; |
||||
} |
||||
|
||||
/* Getting a runtime tuple index. Ugh. */ |
||||
template<class T> const T& dimensionsTupleGet(const typename DimensionsTuple<1, T>::Type& tuple, std::size_t i) { |
||||
if(i == 0) return std::get<0>(tuple); |
||||
CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
||||
} |
||||
template<class T> const T& dimensionsTupleGet(const typename DimensionsTuple<2, T>::Type& tuple, std::size_t i) { |
||||
if(i == 0) return std::get<0>(tuple); |
||||
if(i == 1) return std::get<1>(tuple); |
||||
CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
||||
} |
||||
template<class T> const T& dimensionsTupleGet(const typename DimensionsTuple<3, T>::Type& tuple, std::size_t i) { |
||||
if(i == 0) return std::get<0>(tuple); |
||||
if(i == 1) return std::get<1>(tuple); |
||||
if(i == 2) return std::get<2>(tuple); |
||||
CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
||||
} |
||||
|
||||
template<unsigned dimensions, class T> void stridedArrayView(py::class_<PyStridedArrayView<dimensions, T>>& c) { |
||||
c |
||||
/* Constructor */ |
||||
.def(py::init(), "Default constructor") |
||||
|
||||
/* Buffer protocol */ |
||||
.def(py::init([](py::buffer buffer) { |
||||
py::buffer_info info = buffer.request(!std::is_const<T>::value); |
||||
|
||||
if(info.ndim != dimensions) |
||||
throw py::buffer_error{Utility::formatString("expected {} dimensions but got {}", dimensions, info.ndim)}; |
||||
|
||||
return PyStridedArrayView<dimensions, T>{{ |
||||
{static_cast<T*>(info.ptr), std::size_t(info.size*info.strides[0])}, |
||||
Containers::StaticArrayView<dimensions, const std::size_t>{reinterpret_cast<std::size_t*>(&info.shape[0])}, |
||||
Containers::StaticArrayView<dimensions, const std::ptrdiff_t>{reinterpret_cast<std::ptrdiff_t*>(&info.strides[0])}}, |
||||
// TODO: need to take buffer.obj, not buffer!
|
||||
buffer}; |
||||
}), "Construct from a buffer") |
||||
.def_buffer([](const PyStridedArrayView<dimensions, T>& self) -> py::buffer_info { |
||||
std::vector<py::ssize_t> shape(dimensions); |
||||
Containers::StridedDimensions<dimensions, std::size_t> selfSize{self.size()}; |
||||
for(std::size_t i = 0; i != dimensions; ++i) shape[i] = selfSize[i]; |
||||
|
||||
std::vector<py::ssize_t> strides(dimensions); |
||||
Containers::StridedDimensions<dimensions, std::ptrdiff_t> selfStride{self.stride()}; |
||||
for(std::size_t i = 0; i != dimensions; ++i) strides[i] = selfStride[i]; |
||||
return py::buffer_info{ |
||||
const_cast<void*>(self.data()), |
||||
sizeof(T), |
||||
py::format_descriptor<T>::format(), |
||||
dimensions, |
||||
shape, strides |
||||
// TODO: need to pass self.obj to the buffer, not self!
|
||||
}; |
||||
}) |
||||
|
||||
// TODO: construct from a buffer + size/stride
|
||||
|
||||
/* Length, size/stride tuple, dimension count and memory owning object */ |
||||
.def("__len__", [](const PyStridedArrayView<dimensions, T>& self) { |
||||
return Containers::StridedDimensions<dimensions, std::size_t>(self.size())[0]; |
||||
}, "View size in the top-level dimension") |
||||
.def_property_readonly("size", [](const PyStridedArrayView<dimensions, T>& self) { |
||||
return size<dimensions>(self.size()); |
||||
}, "View size in each dimension") |
||||
.def_property_readonly("stride", [](const PyStridedArrayView<dimensions, T>& self) { |
||||
return stride<dimensions>(self.stride()); |
||||
}, "View stride in each dimension") |
||||
.def_property_readonly("dimensions", [](const PyStridedArrayView<dimensions, T>&) { return dimensions; }, "Dimension count") |
||||
.def_readonly("obj", &PyStridedArrayView<dimensions, T>::obj, "Memory owner object") |
||||
|
||||
/* Conversion to bytes */ |
||||
.def("__bytes__", [](const PyStridedArrayView<dimensions, T>& self) { |
||||
/* TODO: use _PyBytes_Resize() to avoid the double copy */ |
||||
const Containers::Array<char> out = bytes(Containers::arrayCast<const char>(self)); |
||||
return py::bytes(out.data(), out.size()); |
||||
}, "Convert to bytes") |
||||
|
||||
/* Slicing of the top dimension */ |
||||
.def("__getitem__", [](const PyStridedArrayView<dimensions, T>& self, py::slice slice) -> py::object { |
||||
const Slice calculated = calculateSlice(slice, Containers::StridedDimensions<dimensions, const std::size_t>{self.size()}[0]); |
||||
return py::cast(PyStridedArrayView<dimensions, T>(self.slice(calculated.start, calculated.stop).every(calculated.step), self.obj)); |
||||
}, "Slice the view"); |
||||
} |
||||
|
||||
template<class T> void stridedArrayView1D(py::class_<PyStridedArrayView<1, T>>& c) { |
||||
c |
||||
/* Single item retrieval. Need to throw IndexError in order to allow
|
||||
iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */
|
||||
.def("__getitem__", [](const PyStridedArrayView<1, T>& self, std::size_t i) { |
||||
if(i >= self.size()) throw pybind11::index_error{}; |
||||
return self[i]; |
||||
}, "Value at given position"); |
||||
} |
||||
|
||||
template<unsigned dimensions, class T> void stridedArrayViewND(py::class_<PyStridedArrayView<dimensions, T>>& c) { |
||||
c |
||||
/* Sub-view retrieval. Need to throw IndexError in order to allow
|
||||
iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */
|
||||
.def("__getitem__", [](const PyStridedArrayView<dimensions, T>& self, std::size_t i) { |
||||
if(i >= Containers::StridedDimensions<dimensions, const std::size_t>{self.size()}[0]) throw pybind11::index_error{}; |
||||
return PyStridedArrayView<dimensions - 1, T>{self[i], self.obj}; |
||||
}, "Sub-view at given position") |
||||
|
||||
/* Single-item retrieval. Need to throw IndexError in order to allow
|
||||
iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */
|
||||
|
||||
/* Multi-dimensional slicing */ |
||||
.def("__getitem__", [](const PyStridedArrayView<dimensions, T>& self, const typename DimensionsTuple<dimensions, py::slice>::Type& slice) -> py::object { |
||||
Containers::StridedDimensions<dimensions, std::size_t> starts; |
||||
Containers::StridedDimensions<dimensions, std::size_t> stops; |
||||
Containers::StridedDimensions<dimensions, std::ptrdiff_t> steps; |
||||
|
||||
for(std::size_t i = 0; i != dimensions; ++i) { |
||||
const Slice calculated = calculateSlice(dimensionsTupleGet<py::slice>(slice, i), self.size()[i]); |
||||
starts[i] = calculated.start; |
||||
stops[i] = calculated.stop; |
||||
steps[i] = calculated.step; |
||||
} |
||||
|
||||
return py::cast(PyStridedArrayView<dimensions, T>(self.slice(starts, stops).every(steps), self.obj)); |
||||
}, "Slice the view"); |
||||
} |
||||
|
||||
template<class T> void stridedArrayView2D(py::class_<PyStridedArrayView<2, T>>& c) { |
||||
c |
||||
.def("__getitem__", [](const PyStridedArrayView<2, T>& self, const std::tuple<std::size_t, std::size_t>& i) { |
||||
if(std::get<0>(i) >= self.size()[0] || |
||||
std::get<1>(i) >= self.size()[1]) throw py::index_error{}; |
||||
return self[std::get<0>(i)][std::get<1>(i)]; |
||||
}, "Value at given position") |
||||
.def("transposed", [](const PyStridedArrayView<2, T>& self, const std::size_t a, std::size_t b) { |
||||
if((a == 0 && b == 1) || |
||||
(a == 1 && b == 0)) |
||||
return PyStridedArrayView<2, T>{self.template transposed<0, 1>(), self.obj}; |
||||
throw py::value_error{Utility::formatString("dimensions {}, {} can't be transposed in a {}D view", a, b, 2)}; |
||||
}, "Transpose two dimensions") |
||||
.def("flipped", [](const PyStridedArrayView<2, T>& self, const std::size_t dimension) { |
||||
if(dimension == 0) |
||||
return PyStridedArrayView<2, T>{self.template flipped<0>(), self.obj}; |
||||
if(dimension == 1) |
||||
return PyStridedArrayView<2, T>{self.template flipped<1>(), self.obj}; |
||||
throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 2)}; |
||||
}, "Flip a dimension") |
||||
.def("broadcasted", [](const PyStridedArrayView<2, T>& self, const std::size_t dimension, std::size_t size) { |
||||
if(dimension == 0) |
||||
return PyStridedArrayView<2, T>{self.template broadcasted<0>(size), self.obj}; |
||||
if(dimension == 1) |
||||
return PyStridedArrayView<2, T>{self.template broadcasted<1>(size), self.obj}; |
||||
throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 2)}; |
||||
}, "Broadcast a dimension"); |
||||
} |
||||
|
||||
template<class T> void stridedArrayView3D(py::class_<PyStridedArrayView<3, T>>& c) { |
||||
c |
||||
.def("__getitem__", [](const 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] || |
||||
std::get<1>(i) >= self.size()[1] || |
||||
std::get<2>(i) >= self.size()[2]) throw pybind11::index_error{}; |
||||
return self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)]; |
||||
}, "Value at given position") |
||||
.def("transposed", [](const PyStridedArrayView<3, T>& self, const std::size_t a, std::size_t b) { |
||||
if((a == 0 && b == 1) || |
||||
(a == 1 && b == 0)) |
||||
return PyStridedArrayView<3, T>{self.template transposed<0, 1>(), self.obj}; |
||||
if((a == 0 && b == 2) || |
||||
(a == 2 && b == 0)) |
||||
return PyStridedArrayView<3, T>{self.template transposed<0, 2>(), self.obj}; |
||||
if((a == 1 && b == 2) || |
||||
(a == 2 && b == 1)) |
||||
return PyStridedArrayView<3, T>{self.template transposed<1, 2>(), self.obj}; |
||||
throw py::value_error{Utility::formatString("dimensions {}, {} can't be transposed in a {}D view", a, b, 3)}; |
||||
}, "Transpose two dimensions") |
||||
.def("flipped", [](const PyStridedArrayView<3, T>& self, const std::size_t dimension) { |
||||
if(dimension == 0) |
||||
return PyStridedArrayView<3, T>{self.template flipped<0>(), self.obj}; |
||||
if(dimension == 1) |
||||
return PyStridedArrayView<3, T>{self.template flipped<1>(), self.obj}; |
||||
if(dimension == 2) |
||||
return PyStridedArrayView<3, T>{self.template flipped<2>(), self.obj}; |
||||
throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 3)}; |
||||
}, "Flip a dimension") |
||||
.def("broadcasted", [](const PyStridedArrayView<3, T>& self, const std::size_t dimension, std::size_t size) { |
||||
if(dimension == 0) |
||||
return PyStridedArrayView<3, T>{self.template broadcasted<0>(size), self.obj}; |
||||
if(dimension == 1) |
||||
return PyStridedArrayView<3, T>{self.template broadcasted<1>(size), self.obj}; |
||||
if(dimension == 2) |
||||
return PyStridedArrayView<3, T>{self.template broadcasted<2>(size), self.obj}; |
||||
throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 3)}; |
||||
}, "Broadcast a dimension"); |
||||
} |
||||
|
||||
template<class T> void mutableStridedArrayView1D(py::class_<PyStridedArrayView<1, T>>& c) { |
||||
c |
||||
.def("__setitem__", [](const PyStridedArrayView<1, T>& self, const std::size_t i, const T& value) { |
||||
if(i >= self.size()) throw pybind11::index_error{}; |
||||
self[i] = value; |
||||
}, "Set a value at given position"); |
||||
} |
||||
|
||||
template<class T> void mutableStridedArrayView2D(py::class_<PyStridedArrayView<2, T>>& c) { |
||||
c |
||||
.def("__setitem__", [](const PyStridedArrayView<2, T>& self, const std::tuple<std::size_t, std::size_t>& i, const T& value) { |
||||
if(std::get<0>(i) >= self.size()[0] || |
||||
std::get<1>(i) >= self.size()[1]) throw pybind11::index_error{}; |
||||
self[std::get<0>(i)][std::get<1>(i)] = value; |
||||
}, "Set a value at given position"); |
||||
} |
||||
|
||||
template<class T> void mutableStridedArrayView3D(py::class_<PyStridedArrayView<3, T>>& c) { |
||||
c |
||||
.def("__setitem__", [](const PyStridedArrayView<3, T>& self, const std::tuple<std::size_t, std::size_t, std::size_t>& i, const T& value) { |
||||
if(std::get<0>(i) >= self.size()[0] || |
||||
std::get<1>(i) >= self.size()[1] || |
||||
std::get<2>(i) >= self.size()[2]) throw pybind11::index_error{}; |
||||
self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)] = value; |
||||
}, "Set a value at given position"); |
||||
} |
||||
|
||||
void containers(py::module& m) { |
||||
py::class_<PyArrayView<const char>> arrayView_{m, |
||||
"ArrayView", "Array view", py::buffer_protocol{}}; |
||||
arrayView(arrayView_); |
||||
|
||||
py::class_<PyArrayView<char>> mutableArrayView_{m, |
||||
"MutableArrayView", "Mutable array view", py::buffer_protocol{}}; |
||||
arrayView(mutableArrayView_); |
||||
mutableArrayView(mutableArrayView_); |
||||
|
||||
py::class_<PyStridedArrayView<1, const char>> stridedArrayView1D_{m, |
||||
"StridedArrayView1D", "One-dimensional array view with stride information", py::buffer_protocol{}}; |
||||
py::class_<PyStridedArrayView<2, const char>> stridedArrayView2D_{m, |
||||
"StridedArrayView2D", "Two-dimensional array view with stride information", py::buffer_protocol{}}; |
||||
py::class_<PyStridedArrayView<3, const char>> stridedArrayView3D_{m, |
||||
"StridedArrayView3D", "Three-dimensional array view with stride information", py::buffer_protocol{}}; |
||||
stridedArrayView(stridedArrayView1D_); |
||||
stridedArrayView1D(stridedArrayView1D_); |
||||
stridedArrayView(stridedArrayView2D_); |
||||
stridedArrayViewND(stridedArrayView2D_); |
||||
stridedArrayView2D(stridedArrayView2D_); |
||||
stridedArrayView(stridedArrayView3D_); |
||||
stridedArrayViewND(stridedArrayView3D_); |
||||
stridedArrayView3D(stridedArrayView3D_); |
||||
|
||||
py::class_<PyStridedArrayView<1, char>> mutableStridedArrayView1D_{m, |
||||
"MutableStridedArrayView1D", "Mutable one-dimensional array view with stride information", py::buffer_protocol{}}; |
||||
py::class_<PyStridedArrayView<2, char>> mutableStridedArrayView2D_{m, |
||||
"MutableStridedArrayView2D", "Mutable two-dimensional array view with stride information", py::buffer_protocol{}}; |
||||
py::class_<PyStridedArrayView<3, char>> mutableStridedArrayView3D_{m, |
||||
"MutableStridedArrayView3D", "Mutable three-dimensional array view with stride information", py::buffer_protocol{}}; |
||||
stridedArrayView(mutableStridedArrayView1D_); |
||||
stridedArrayView1D(mutableStridedArrayView1D_); |
||||
stridedArrayView(mutableStridedArrayView2D_); |
||||
stridedArrayViewND(mutableStridedArrayView2D_); |
||||
stridedArrayView(mutableStridedArrayView3D_); |
||||
stridedArrayViewND(mutableStridedArrayView3D_); |
||||
mutableStridedArrayView1D(mutableStridedArrayView1D_); |
||||
mutableStridedArrayView2D(mutableStridedArrayView2D_); |
||||
mutableStridedArrayView3D(mutableStridedArrayView3D_); |
||||
} |
||||
|
||||
}} |
||||
|
||||
PYBIND11_MODULE(containers, m) { |
||||
m.doc() = "Corrade containers module"; |
||||
corrade::containers(m); |
||||
} |
||||
@ -0,0 +1,31 @@
|
||||
# |
||||
# This file is part of Magnum. |
||||
# |
||||
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
# 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 os |
||||
import sys |
||||
|
||||
# TODO: do this differently / more robustly |
||||
sys.path = [os.path.join(os.path.dirname(__file__), '../../../../build/src/python')] + sys.path |
||||
|
||||
@ -0,0 +1,694 @@
|
||||
# |
||||
# This file is part of Magnum. |
||||
# |
||||
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
# 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 sys |
||||
import unittest |
||||
|
||||
from corrade import containers |
||||
|
||||
class ArrayView(unittest.TestCase): |
||||
def test_init(self): |
||||
a = containers.ArrayView() |
||||
b = containers.MutableArrayView() |
||||
self.assertIs(a.obj, None) |
||||
self.assertIs(b.obj, None) |
||||
self.assertEqual(len(a), 0) |
||||
self.assertEqual(len(b), 0) |
||||
self.assertEqual(bytes(a), b'') |
||||
self.assertEqual(bytes(b), b'') |
||||
|
||||
def test_init_buffer(self): |
||||
a = b'hello' |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
b = containers.ArrayView(a) |
||||
self.assertIs(b.obj, a) |
||||
self.assertEqual(len(b), 5) |
||||
self.assertEqual(bytes(b), b'hello') |
||||
self.assertEqual(b[2], 'l') |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
# Not mutable |
||||
with self.assertRaisesRegex(TypeError, "object does not support item assignment"): |
||||
b[4] = '!' |
||||
|
||||
# b should keep a reference to a, so deleting the local reference |
||||
# shouldn't affect it |
||||
del a |
||||
self.assertTrue(sys.getrefcount(b.obj), a_refcount) |
||||
self.assertEqual(b[2], 'l') |
||||
|
||||
# Now, if we delete b, a should not be referenced by anything anymore |
||||
a = b.obj |
||||
del b |
||||
self.assertTrue(sys.getrefcount(a), a_refcount) |
||||
|
||||
def test_init_buffer_mutable(self): |
||||
a = bytearray(b'hello') |
||||
b = containers.MutableArrayView(a) |
||||
b[4] = '!' |
||||
self.assertEqual(b[4], '!') |
||||
self.assertEqual(bytes(b), b'hell!') |
||||
|
||||
def test_init_buffer_unexpected_dimensions(self): |
||||
a = memoryview(b'123456').cast('b', shape=[2, 3]) |
||||
self.assertEqual(bytes(a), b'123456') |
||||
with self.assertRaisesRegex(BufferError, "expected one dimension but got 2"): |
||||
b = containers.ArrayView(a) |
||||
|
||||
def test_init_buffer_unexpected_stride(self): |
||||
a = memoryview(b'hello')[::2] |
||||
self.assertEqual(bytes(a), b'hlo') |
||||
with self.assertRaisesRegex(BufferError, "expected stride of 1 but got 2"): |
||||
b = containers.ArrayView(a) |
||||
|
||||
def test_init_buffer_mutable_from_immutable(self): |
||||
a = b'hello' |
||||
with self.assertRaisesRegex(BufferError, "Object is not writable."): |
||||
b = containers.MutableArrayView(a) |
||||
|
||||
def test_slice(self): |
||||
a = b'World is hell!' |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
b = containers.ArrayView(a) |
||||
b_refcount = sys.getrefcount(b) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
# When slicing, b's refcount should not change but a's refcount should |
||||
# increase |
||||
c = b[4:-4] |
||||
self.assertIsInstance(c, containers.ArrayView) |
||||
self.assertEqual(bytes(c), b'd is h') |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 2) |
||||
|
||||
# Deleting a slice should reduce a's refcount again, keep b's unchanged |
||||
del c |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
def test_slice_empty(self): |
||||
# slice.start = slice.stop |
||||
a = containers.ArrayView(b'hello')[7:8] |
||||
self.assertEqual(len(a), 0) |
||||
|
||||
def test_slice_invalid(self): |
||||
with self.assertRaisesRegex(ValueError, "slice step cannot be zero"): |
||||
containers.ArrayView()[::0] |
||||
|
||||
def test_slice_stride(self): |
||||
a = b'World_ _i_s_ _hell!' |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
b = containers.ArrayView(a) |
||||
b_refcount = sys.getrefcount(b) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
# When slicing to a strided array view, b's refcount should not change |
||||
# but a's refcount should increase. Check consistency with slices on |
||||
# bytes, slicing bytes will make a copy so it doesn't affect the |
||||
# refcount. |
||||
c1 = a[4:-4:2] |
||||
c2 = b[4:-4:2] |
||||
self.assertIsInstance(c2, containers.StridedArrayView1D) |
||||
self.assertEqual(len(c1), 6) |
||||
self.assertEqual(len(c2), 6) |
||||
self.assertEqual(bytes(c1), b'd is h') |
||||
self.assertEqual(bytes(c2), b'd is h') |
||||
self.assertEqual(c2.size, (6,)) |
||||
self.assertEqual(c2.stride, (2,)) |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 2) |
||||
|
||||
# Deleting a slice should reduce a's refcount again, keep b's unchanged |
||||
del c2 |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
def test_slice_stride_negative(self): |
||||
a = b'World_ _i_s_ _hell!' |
||||
b = containers.ArrayView(a) |
||||
|
||||
# Check consistency with slices on bytes |
||||
c1 = a[-5:3:-2] # like [4:-4:2] above, but reverted |
||||
c2 = b[-5:3:-2] |
||||
self.assertEqual(len(c1), 6) |
||||
self.assertEqual(len(c2), 6) |
||||
self.assertEqual(bytes(c1), b'h si d') # like b'd is h' but reverted |
||||
self.assertEqual(bytes(c2), b'h si d') |
||||
self.assertEqual(c2.size, (6,)) |
||||
self.assertEqual(c2.stride, (-2,)) |
||||
|
||||
def test_slice_stride_reverse(self): |
||||
# slice.stop = -1 |
||||
a = containers.ArrayView(b'hello')[::-1] |
||||
self.assertEqual(len(a), 5) |
||||
self.assertEqual(bytes(a), b'olleh') |
||||
|
||||
def test_convert_memoryview(self): |
||||
a = b'World is hell!' |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
b = containers.ArrayView(a) |
||||
b_refcount = sys.getrefcount(b) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
# TODO: fix when pybind is replaced |
||||
|
||||
c = memoryview(b) |
||||
self.assertEqual(c.obj, b) # TODO: should be a |
||||
self.assertEqual(sys.getrefcount(b), b_refcount + 1) # TODO: should not hcange |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be +2 |
||||
|
||||
class StridedArrayView1D(unittest.TestCase): |
||||
def test_init(self): |
||||
a = containers.StridedArrayView1D() |
||||
b = containers.MutableStridedArrayView1D() |
||||
self.assertIs(a.obj, None) |
||||
self.assertIs(b.obj, None) |
||||
self.assertEqual(len(a), 0) |
||||
self.assertEqual(len(b), 0) |
||||
self.assertEqual(bytes(a), b'') |
||||
self.assertEqual(bytes(b), b'') |
||||
self.assertEqual(a.size, (0, )) |
||||
self.assertEqual(b.size, (0, )) |
||||
self.assertEqual(a.stride, (0, )) |
||||
self.assertEqual(b.stride, (0, )) |
||||
self.assertEqual(a.dimensions, 1) |
||||
self.assertEqual(b.dimensions, 1) |
||||
|
||||
def test_init_buffer(self): |
||||
a = b'hello' |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
b = containers.StridedArrayView1D(a) |
||||
self.assertIs(b.obj, a) |
||||
self.assertEqual(len(b), 5) |
||||
self.assertEqual(bytes(b), b'hello') |
||||
self.assertEqual(b.size, (5, )) |
||||
self.assertEqual(b.stride, (1, )) |
||||
self.assertEqual(b[2], 'l') |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
# Not mutable |
||||
with self.assertRaisesRegex(TypeError, "object does not support item assignment"): |
||||
b[4] = '!' |
||||
|
||||
# b should keep a reference to a, so deleting the local reference |
||||
# shouldn't affect it |
||||
del a |
||||
self.assertTrue(sys.getrefcount(b.obj), a_refcount) |
||||
self.assertEqual(b[2], 'l') |
||||
|
||||
# Now, if we delete b, a should not be referenced by anything anymore |
||||
a = b.obj |
||||
del b |
||||
self.assertTrue(sys.getrefcount(a), a_refcount) |
||||
|
||||
@unittest.expectedFailure |
||||
def test_init_buffer_memoryview_obj(self): |
||||
a = b'hello' |
||||
v = memoryview(a) |
||||
b = containers.StridedArrayView1D(v) |
||||
self.assertIs(b.obj, a) # TODO: it's b because pybind is stupid |
||||
|
||||
def test_init_buffer_mutable(self): |
||||
a = bytearray(b'hello') |
||||
b = containers.MutableStridedArrayView1D(a) |
||||
b[4] = '!' |
||||
self.assertEqual(b[4], '!') |
||||
self.assertEqual(bytes(b), b'hell!') |
||||
|
||||
def test_init_buffer_unexpected_dimensions(self): |
||||
a = memoryview(b'123456').cast('b', shape=[2, 3]) |
||||
self.assertEqual(bytes(a), b'123456') |
||||
with self.assertRaisesRegex(BufferError, "expected 1 dimensions but got 2"): |
||||
b = containers.StridedArrayView1D(a) |
||||
|
||||
def test_init_buffer_stride(self): |
||||
a = memoryview(b'hello')[::2] |
||||
self.assertEqual(bytes(a), b'hlo') |
||||
b = containers.StridedArrayView1D(a) |
||||
self.assertEqual(len(b), 3) |
||||
self.assertEqual(bytes(b), b'hlo') |
||||
self.assertEqual(b.size, (3, )) |
||||
self.assertEqual(b.stride, (2, )) |
||||
self.assertEqual(b[2], 'o') |
||||
|
||||
def test_init_buffer_mutable_from_immutable(self): |
||||
a = b'hello' |
||||
with self.assertRaisesRegex(BufferError, "Object is not writable."): |
||||
b = containers.MutableStridedArrayView1D(a) |
||||
|
||||
def test_slice(self): |
||||
a = b'World is hell!' |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
b = containers.StridedArrayView1D(a) |
||||
b_refcount = sys.getrefcount(b) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
# When slicing, b's refcount should not change but a's refcount should |
||||
# increase |
||||
c = b[4:-4] |
||||
self.assertEqual(c.size, (6,)) |
||||
self.assertEqual(c.stride, (1,)) |
||||
self.assertIsInstance(c, containers.StridedArrayView1D) |
||||
self.assertEqual(bytes(c), b'd is h') |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 2) |
||||
|
||||
# Deleting a slice should reduce a's refcount again, keep b's unchanged |
||||
del c |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
def test_slice_invalid(self): |
||||
with self.assertRaisesRegex(TypeError, "indices must be integers"): |
||||
containers.StridedArrayView1D()[-5:3:"boo"] |
||||
|
||||
def test_slice_stride(self): |
||||
a = b'World_ _i_s_ _hell!' |
||||
b = containers.StridedArrayView1D(a) |
||||
|
||||
# Check consistency with slices on bytes |
||||
c1 = a[4:-4:2] |
||||
c2 = b[4:-4:2] |
||||
self.assertIsInstance(c2, containers.StridedArrayView1D) |
||||
self.assertEqual(len(c1), 6) |
||||
self.assertEqual(len(c2), 6) |
||||
self.assertEqual(bytes(c1), b'd is h') |
||||
self.assertEqual(bytes(c2), b'd is h') |
||||
self.assertEqual(c2.size, (6,)) |
||||
self.assertEqual(c2.stride, (2,)) |
||||
|
||||
def test_slice_stride_negative(self): |
||||
a = b'World_ _i_s_ _hell!' |
||||
b = containers.StridedArrayView1D(a) |
||||
|
||||
# Check consistency with slices on bytes |
||||
c1 = a[-5:3:-2] # like [4:-4:2] above, but reverted |
||||
c2 = b[-5:3:-2] |
||||
self.assertEqual(len(c1), 6) |
||||
self.assertEqual(len(c2), 6) |
||||
self.assertEqual(bytes(c1), b'h si d') # like b'd is h' but reverted |
||||
self.assertEqual(bytes(c2), b'h si d') |
||||
self.assertEqual(c2.size, (6,)) |
||||
self.assertEqual(c2.stride, (-2,)) |
||||
|
||||
def test_convert_memoryview(self): |
||||
a = b'World is hell!' |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
b = containers.StridedArrayView1D(a) |
||||
b_refcount = sys.getrefcount(b) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
# TODO: fix when pybind is replaced |
||||
|
||||
c = memoryview(b) |
||||
self.assertEqual(c.ndim, 1) |
||||
self.assertEqual(len(c), len(a)) |
||||
self.assertEqual(bytes(c), a) |
||||
self.assertEqual(c.obj, b) # TODO: should be a |
||||
self.assertEqual(sys.getrefcount(b), b_refcount + 1) # TODO: should not change |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be +2 |
||||
|
||||
c[-1] = ord('?') # TODO: wrong, should fail |
||||
self.assertEqual(a, b'World is hell?') # TODO: wrong |
||||
|
||||
class StridedArrayView2D(unittest.TestCase): |
||||
def test_init(self): |
||||
a = containers.StridedArrayView2D() |
||||
b = containers.MutableStridedArrayView2D() |
||||
self.assertIs(a.obj, None) |
||||
self.assertIs(b.obj, None) |
||||
self.assertEqual(len(a), 0) |
||||
self.assertEqual(len(b), 0) |
||||
self.assertEqual(bytes(a), b'') |
||||
self.assertEqual(bytes(b), b'') |
||||
self.assertEqual(a.size, (0, 0)) |
||||
self.assertEqual(b.size, (0, 0)) |
||||
self.assertEqual(a.stride, (0, 0)) |
||||
self.assertEqual(b.stride, (0, 0)) |
||||
self.assertEqual(a.dimensions, 2) |
||||
self.assertEqual(b.dimensions, 2) |
||||
|
||||
def test_init_buffer(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
b = containers.StridedArrayView2D(memoryview(a).cast('b', shape=[3, 8])) |
||||
# TODO: construct as containers.StridedArrayView2D(a, (3, 8), (8, 1)) |
||||
#self.assertIs(b.obj, a) # TODO |
||||
self.assertEqual(len(b), 3) |
||||
self.assertEqual(bytes(b), b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
self.assertEqual(b.size, (3, 8)) |
||||
self.assertEqual(b.stride, (8, 1)) |
||||
self.assertIsInstance(b[1], containers.StridedArrayView1D) |
||||
self.assertEqual(bytes(b[1]), b'456789ab') |
||||
self.assertEqual(b[1, 2], '6') |
||||
self.assertEqual(b[1][2], '6') |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
|
||||
# Not mutable |
||||
with self.assertRaisesRegex(TypeError, "object does not support item assignment"): |
||||
b[1, 2] = '!' |
||||
|
||||
# b should keep a reference to a, so deleting the local reference |
||||
# shouldn't affect it |
||||
del a |
||||
self.assertTrue(sys.getrefcount(b.obj), a_refcount) |
||||
self.assertEqual(b[1][2], '6') |
||||
|
||||
# Now, if we delete b, a should not be referenced by anything anymore |
||||
a = b.obj |
||||
del b |
||||
self.assertTrue(sys.getrefcount(a), a_refcount) |
||||
|
||||
def test_init_buffer_mutable(self): |
||||
a = bytearray(b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
b = containers.MutableStridedArrayView2D(memoryview(a).cast('b', shape=[3, 8])) |
||||
b[0, 7] = '!' |
||||
b[1, 7] = '!' |
||||
b[2, 7] = '!' |
||||
self.assertEqual(b[0][7], '!') |
||||
self.assertEqual(bytes(b), b'0123456!' |
||||
b'456789a!' |
||||
b'89abcde!') |
||||
|
||||
def test_init_buffer_unexpected_dimensions(self): |
||||
a = b'123456' |
||||
with self.assertRaisesRegex(BufferError, "expected 2 dimensions but got 1"): |
||||
b = containers.StridedArrayView2D(a) |
||||
|
||||
def test_init_buffer_stride(self): |
||||
a = memoryview(b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef').cast('b', shape=[3, 8])[::2] |
||||
self.assertEqual(bytes(a), b'0123456789abcdef') |
||||
b = containers.StridedArrayView2D(a) |
||||
self.assertEqual(len(b), 2) |
||||
self.assertEqual(bytes(b), b'0123456789abcdef') |
||||
self.assertEqual(b.size, (2, 8)) |
||||
self.assertEqual(b.stride, (16, 1)) |
||||
self.assertEqual(bytes(b[1]), b'89abcdef') |
||||
self.assertEqual(b[1][3], 'b') |
||||
|
||||
def test_init_buffer_mutable_from_immutable(self): |
||||
a = memoryview(b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef').cast('b', shape=[3, 8]) |
||||
with self.assertRaisesRegex(BufferError, "underlying buffer is not writable"): |
||||
b = containers.MutableStridedArrayView2D(a) |
||||
|
||||
def test_slice(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
v = memoryview(a).cast('b', shape=[3, 8]) |
||||
v_refcount = sys.getrefcount(v) |
||||
|
||||
# TODO: Pybind refcounts against v, not a |
||||
|
||||
b = containers.StridedArrayView2D(v) |
||||
b_refcount = sys.getrefcount(b) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change |
||||
|
||||
# When slicing, b's refcount should not change but a's refcount should |
||||
# increase |
||||
c = b[0:-1] |
||||
self.assertIsInstance(c, containers.StridedArrayView2D) |
||||
self.assertEqual(c.size, (2, 8)) |
||||
self.assertEqual(c.stride, (8, 1)) |
||||
self.assertEqual(bytes(c), b'01234567456789ab') |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be +2 |
||||
self.assertEqual(sys.getrefcount(v), v_refcount + 2) # TODO: should not change |
||||
|
||||
# Deleting a slice should reduce a's refcount again, keep b's unchanged |
||||
del c |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change |
||||
|
||||
def test_slice_multidimensional(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
a_refcount = sys.getrefcount(a) |
||||
|
||||
v = memoryview(a).cast('b', shape=[3, 8]) |
||||
v_refcount = sys.getrefcount(v) |
||||
|
||||
# TODO: Pybind refcounts against v, not a |
||||
|
||||
b = containers.StridedArrayView2D(v) |
||||
b_refcount = sys.getrefcount(b) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change |
||||
|
||||
# When slicing, b's refcount should not change but a's refcount should |
||||
# increase |
||||
c = b[1:3,4:7] |
||||
self.assertIsInstance(c, containers.StridedArrayView2D) |
||||
self.assertEqual(c.size, (2, 3)) |
||||
self.assertEqual(c.stride, (8, 1)) |
||||
self.assertEqual(bytes(c[0]), b'89a') |
||||
self.assertEqual(bytes(c[1]), b'cde') |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be +2 |
||||
self.assertEqual(sys.getrefcount(v), v_refcount + 2) # TODO: should not change |
||||
|
||||
# Deleting a slice should reduce a's refcount again, keep b's unchanged |
||||
del c |
||||
self.assertEqual(sys.getrefcount(b), b_refcount) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) |
||||
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change |
||||
|
||||
def test_slice_invalid(self): |
||||
with self.assertRaisesRegex(ValueError, "slice step cannot be zero"): |
||||
containers.StridedArrayView1D()[-5:3:0] |
||||
|
||||
def test_slice_stride(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
v = memoryview(a).cast('b', shape=[3, 8]) |
||||
b = containers.StridedArrayView2D(v) |
||||
|
||||
# Check consistency with slices on memoryview |
||||
c1 = v[0:3:2] |
||||
c2 = b[0:3:2] |
||||
self.assertEqual(len(c1), 2) |
||||
self.assertEqual(len(c2), 2) |
||||
self.assertIsInstance(c2, containers.StridedArrayView2D) |
||||
self.assertEqual(bytes(c1), b'0123456789abcdef') |
||||
self.assertEqual(bytes(c2), b'0123456789abcdef') |
||||
self.assertEqual(c2.size, (2, 8)) |
||||
self.assertEqual(c2.stride, (16, 1)) |
||||
self.assertEqual(bytes(c2[1]), b'89abcdef') |
||||
|
||||
def test_slice_stride_negative(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
v = memoryview(a).cast('b', shape=[3, 8]) |
||||
b = containers.StridedArrayView2D(v) |
||||
|
||||
# Check consistency with slices on memoryview |
||||
self.assertEqual(v.shape, (3, 8)) |
||||
self.assertEqual(b.size, (3, 8)) |
||||
self.assertEqual(v.strides, (8, 1)) |
||||
self.assertEqual(b.stride, (8, 1)) |
||||
c1 = v[-1:None:-2] # like [0:3:2] above, but reverted |
||||
c2 = b[-1:None:-2] |
||||
self.assertEqual(len(c1), 2) |
||||
self.assertEqual(len(c2), 2) |
||||
self.assertEqual(bytes(c1), b'89abcdef01234567') # like above but reverted |
||||
self.assertEqual(bytes(c2), b'89abcdef01234567') |
||||
self.assertEqual(c1.shape, (2, 8)) |
||||
self.assertEqual(c2.size, (2, 8)) |
||||
self.assertEqual(c1.strides, (-16, 1)) |
||||
self.assertEqual(c2.stride, (-16, 1)) |
||||
|
||||
def test_slice_stride_negative_multidimensional(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
v = memoryview(a).cast('b', shape=[3, 8]) |
||||
b = containers.StridedArrayView2D(v) |
||||
|
||||
# Check consistency with slices on memoryview |
||||
self.assertEqual(v.shape, (3, 8)) |
||||
self.assertEqual(b.size, (3, 8)) |
||||
self.assertEqual(v.strides, (8, 1)) |
||||
self.assertEqual(b.stride, (8, 1)) |
||||
|
||||
with self.assertRaises(NotImplementedError): |
||||
c1 = v[-1:None:-2, -2:2:-3] # HAH! |
||||
|
||||
c2 = b[-1:None:-2, -2:2:-3] |
||||
self.assertEqual(len(c2), 2) |
||||
self.assertEqual(bytes(c2), b'eb63') |
||||
self.assertEqual(c2.size, (2, 2)) |
||||
self.assertEqual(c2.stride, (-16, -3)) |
||||
|
||||
def test_ops(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
v = memoryview(a).cast('b', shape=[3, 8]) |
||||
|
||||
b = containers.StridedArrayView2D(v).transposed(0, 1).flipped(0) |
||||
self.assertEqual(b.size, (8, 3)) |
||||
self.assertEqual(b.stride, (-1, 8)) |
||||
self.assertEqual(bytes(b), b'7bf6ae59d48c37b26a159048') |
||||
|
||||
c = containers.StridedArrayView2D(v).transposed(1, 0).flipped(1) |
||||
self.assertEqual(c.size, (8, 3)) |
||||
self.assertEqual(c.stride, (1, -8)) |
||||
self.assertEqual(bytes(c), b'840951a62b73c84d95ea6fb7') |
||||
|
||||
d = containers.StridedArrayView2D(v).transposed(0, 1)[3:4].broadcasted(0, 5) |
||||
self.assertEqual(d.size, (5, 3)) |
||||
self.assertEqual(d.stride, (0, 8)) |
||||
self.assertEqual(bytes(d), b'37b37b37b37b37b') |
||||
|
||||
d = containers.StridedArrayView2D(v)[:, 3:4].broadcasted(1, 2) |
||||
self.assertEqual(d.size, (3, 2)) |
||||
self.assertEqual(d.stride, (8, 0)) |
||||
self.assertEqual(bytes(d), b'3377bb') |
||||
|
||||
def test_convert_memoryview(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef') |
||||
a_refcount = sys.getrefcount(a) |
||||
v = memoryview(a).cast('b', shape=[3, 8]) |
||||
v_refcount = sys.getrefcount(v) |
||||
|
||||
b = containers.StridedArrayView2D(v) |
||||
b_refcount = sys.getrefcount(b) |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be + 2 |
||||
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change |
||||
|
||||
c = memoryview(b) |
||||
self.assertEqual(c.ndim, 2) |
||||
self.assertEqual(c.shape, (3, 8)) |
||||
self.assertEqual(c.strides, (8, 1)) |
||||
self.assertEqual(c.obj, v) # TODO: should be a |
||||
self.assertEqual(sys.getrefcount(b), b_refcount + 1) # TODO: should not change |
||||
self.assertEqual(sys.getrefcount(a), a_refcount + 1) # TODO: should be + 2 |
||||
self.assertEqual(sys.getrefcount(v), v_refcount + 1) # TODO: should not change |
||||
|
||||
c[2, 1] = ord('!') # TODO: wrong, should fail |
||||
self.assertEqual(chr(c[2, 1]), '!') # TODO: should be 9 |
||||
|
||||
class StridedArrayView3D(unittest.TestCase): |
||||
def test_init_buffer(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef' |
||||
|
||||
b'cdef0123' |
||||
b'01234567' |
||||
b'456789ab') |
||||
b = containers.StridedArrayView3D(memoryview(a).cast('b', shape=[2, 3, 8])) |
||||
self.assertEqual(len(b), 2) |
||||
self.assertEqual(bytes(b), b'01234567456789ab89abcdefcdef012301234567456789ab') |
||||
self.assertEqual(b.size, (2, 3, 8)) |
||||
self.assertEqual(b.stride, (24, 8, 1)) |
||||
self.assertEqual(b[1, 2, 3], '7') |
||||
self.assertEqual(b[1][2][3], '7') |
||||
|
||||
def test_init_buffer_mutable(self): |
||||
a = bytearray(b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef' |
||||
|
||||
b'cdef0123' |
||||
b'01234567' |
||||
b'456789ab') |
||||
b = containers.MutableStridedArrayView3D(memoryview(a).cast('b', shape=[2, 3, 8])) |
||||
b[0, 0, 7] = '!' |
||||
b[0, 1, 7] = '!' |
||||
b[0, 2, 7] = '!' |
||||
b[1, 0, 7] = '!' |
||||
b[1, 1, 7] = '!' |
||||
b[1, 2, 7] = '!' |
||||
self.assertEqual(b[1][1][7], '!') |
||||
self.assertEqual(bytes(b), b'0123456!' |
||||
b'456789a!' |
||||
b'89abcde!' |
||||
|
||||
b'cdef012!' |
||||
b'0123456!' |
||||
b'456789a!') |
||||
|
||||
def test_ops(self): |
||||
a = (b'01234567' |
||||
b'456789ab' |
||||
b'89abcdef' |
||||
|
||||
b'cdef0123' |
||||
b'01234567' |
||||
b'456789ab') |
||||
v = memoryview(a).cast('b', shape=[2, 3, 8]) |
||||
|
||||
b = containers.StridedArrayView3D(v).transposed(0, 1).flipped(0) |
||||
self.assertEqual(b.size, (3, 2, 8)) |
||||
self.assertEqual(b.stride, (-8, 24, 1)) |
||||
self.assertEqual(bytes(b), b'89abcdef456789ab456789ab0123456701234567cdef0123') |
||||
|
||||
c = containers.StridedArrayView3D(v).transposed(2, 0).flipped(1) |
||||
self.assertEqual(c.size, (8, 3, 2)) |
||||
self.assertEqual(c.stride, (1, -8, 24)) |
||||
self.assertEqual(bytes(c), b'84400c95511da6622eb7733fc88440d99551eaa662fbb773') |
||||
|
||||
d = containers.StridedArrayView3D(v).transposed(1, 2)[0:1, 3:5, :].broadcasted(0, 5) |
||||
self.assertEqual(d.size, (5, 2, 3)) |
||||
self.assertEqual(d.stride, (0, 1, 8)) |
||||
self.assertEqual(bytes(d), b'37b48c37b48c37b48c37b48c37b48c') |
||||
|
||||
e = containers.StridedArrayView3D(v)[:, 1:2, 3:4].flipped(2).broadcasted(1, 2) |
||||
self.assertEqual(e.size, (2, 2, 1)) |
||||
self.assertEqual(e.stride, (24, 0, -1)) |
||||
self.assertEqual(bytes(e), b'7733') |
||||
|
||||
f = containers.StridedArrayView3D(v)[:, :, 0:1].broadcasted(2, 5) |
||||
self.assertEqual(f.size, (2, 3, 5)) |
||||
self.assertEqual(f.stride, (24, 8, 0)) |
||||
self.assertEqual(bytes(f), b'000004444488888ccccc0000044444') |
||||
Loading…
Reference in new issue