From a12e43b1767e32453662ba56c9ee1380b9c4f02a Mon Sep 17 00:00:00 2001 From: Mazzoleni Alice Francesca Date: Mon, 7 Apr 2025 12:27:44 +0200 Subject: [PATCH] underlying container of ClusterVcetor is now a std::vector --- include/aare/ClusterVector.hpp | 128 +++++++++++++++++++++++++++++++-- python/src/cluster.hpp | 38 +++++----- python/src/np_helper.hpp | 21 +++++- src/ClusterVector.test.cpp | 5 +- 4 files changed, 162 insertions(+), 30 deletions(-) diff --git a/include/aare/ClusterVector.hpp b/include/aare/ClusterVector.hpp index f3b55be..13ec882 100644 --- a/include/aare/ClusterVector.hpp +++ b/include/aare/ClusterVector.hpp @@ -29,6 +29,7 @@ class ClusterVector; // Forward declaration * @tparam CoordType data type of the x and y coordinates of the cluster * (normally int16_t) */ +#if 0 template class ClusterVector> { @@ -37,7 +38,7 @@ class ClusterVector> { size_t m_size{0}; size_t m_capacity; uint64_t m_frame_number{0}; // TODO! Check frame number size and type - /* + /** Format string used in the python bindings to create a numpy array from the buffer = - native byte order @@ -59,7 +60,7 @@ class ClusterVector> { */ ClusterVector(size_t capacity = 1024, uint64_t frame_number = 0) : m_capacity(capacity), m_frame_number(frame_number) { - allocate_buffer(capacity); + allocate_buffer(m_capacity); } ~ClusterVector() { delete[] m_data; } @@ -230,7 +231,7 @@ class ClusterVector> { return m_fmt_base; } - /** + /** * @brief Return the frame number of the clusters. 0 is used to indicate * that the clusters come from many frames */ @@ -240,7 +241,7 @@ class ClusterVector> { m_frame_number = frame_number; } - /** + /** * @brief Resize the vector to contain new_size clusters. If new_size is * greater than the current capacity, a new buffer is allocated. If the size * is smaller no memory is freed, size is just updated. @@ -265,5 +266,124 @@ class ClusterVector> { m_capacity = new_capacity; } }; +#endif + +/** + * @brief ClusterVector is a container for clusters of various sizes. It + * uses a contiguous memory buffer to store the clusters. It is templated on + * the data type and the coordinate type of the clusters. + * @note push_back can invalidate pointers to elements in the container + * @warning ClusterVector is currently move only to catch unintended copies, + * but this might change since there are probably use cases where copying is + * needed. + * @tparam T data type of the pixels in the cluster + * @tparam CoordType data type of the x and y coordinates of the cluster + * (normally int16_t) + */ +template +class ClusterVector> { + + std::vector> m_data{}; + uint64_t m_frame_number{0}; // TODO! Check frame number size and type + + public: + using value_type = T; + using ClusterType = Cluster; + + /** + * @brief Construct a new ClusterVector object + * @param capacity initial capacity of the buffer in number of clusters + * @param frame_number frame number of the clusters. Default is 0, which is + * also used to indicate that the clusters come from many frames + */ + ClusterVector(size_t capacity = 1024, uint64_t frame_number = 0) + : m_frame_number(frame_number) { + m_data.reserve(capacity); + } + + // Move constructor + ClusterVector(ClusterVector &&other) noexcept + : m_data(other.m_data), m_frame_number(other.m_frame_number) { + other.m_data.clear(); + } + + // Move assignment operator + ClusterVector &operator=(ClusterVector &&other) noexcept { + if (this != &other) { + m_data = other.m_data; + m_frame_number = other.m_frame_number; + other.m_data.clear(); + other.m_frame_number = 0; + } + return *this; + } + + /** + * @brief Reserve space for at least capacity clusters + * @param capacity number of clusters to reserve space for + * @note If capacity is less than the current capacity, the function does + * nothing. + */ + void reserve(size_t capacity) { m_data.reserve(capacity); } + + void resize(size_t size) { m_data.resize(size); } + + void push_back(const ClusterType &cluster) { m_data.push_back(cluster); } + + ClusterVector &operator+=(const ClusterVector &other) { + m_data.insert(m_data.end(), other.begin(), other.end()); + + return *this; + } + + /** + * @brief Return the number of clusters in the vector + */ + size_t size() const { return m_data.size(); } + + uint8_t cluster_size_x() const { return ClusterSizeX; } + + uint8_t cluster_size_y() const { return ClusterSizeY; } + + /** + * @brief Return the capacity of the buffer in number of clusters. This is + * the number of clusters that can be stored in the current buffer without + * reallocation. + */ + size_t capacity() const { return m_data.capacity(); } + + const auto begin() const { return m_data.begin(); } + + const auto end() const { return m_data.end(); } + + /** + * @brief Return the size in bytes of a single cluster + */ + size_t item_size() const { + return 2 * sizeof(CoordType) + ClusterSizeX * ClusterSizeY * sizeof(T); + } + + ClusterType *data() { return m_data.data(); } + ClusterType const *data() const { return m_data.data(); } + + /** + * @brief Return a reference to the i-th cluster casted to type V + * @tparam V type of the cluster + */ + ClusterType &at(size_t i) { return m_data[i]; } + + const ClusterType &at(size_t i) const { return m_data[i]; } + + /** + * @brief Return the frame number of the clusters. 0 is used to indicate + * that the clusters come from many frames + */ + uint64_t frame_number() const { return m_frame_number; } + + void set_frame_number(uint64_t frame_number) { + m_frame_number = frame_number; + } +}; } // namespace aare \ No newline at end of file diff --git a/python/src/cluster.hpp b/python/src/cluster.hpp index 7dcb338..b414ae1 100644 --- a/python/src/cluster.hpp +++ b/python/src/cluster.hpp @@ -58,7 +58,8 @@ void define_cluster(py::module &m, const std::string &typestr) { [](ClusterType &c, py::array_t arr) { py::buffer_info buf_info = arr.request(); Type *ptr = static_cast(buf_info.ptr); - std::copy(ptr, ptr + ClusterSizeX * ClusterSizeY, c.data); + std::copy(ptr, ptr + ClusterSizeX * ClusterSizeY, + c.data); // TODO dont iterate over centers!!! }); } @@ -68,14 +69,15 @@ void define_cluster_vector(py::module &m, const std::string &typestr) { py::class_>(m, class_name.c_str(), py::buffer_protocol()) + .def(py::init()) // TODO change!!! - /* - .def("push_back", - [](ClusterVector &self, ClusterType &cl) { - // auto view = make_view_2d(data); - self.push_back(cl); - }) - */ + /* + .def("push_back", + [](ClusterVector &self, ClusterType &cl) { + // auto view = make_view_2d(data); + self.push_back(cl); + }) + */ /* .def( "push_back", @@ -92,15 +94,11 @@ void define_cluster_vector(py::module &m, const std::string &typestr) { }) */ //.def("push_back", &ClusterVector::push_back) //TODO - //implement push_back + // implement push_back .def_property_readonly("size", &ClusterVector::size) .def("item_size", &ClusterVector::item_size) .def_property_readonly("fmt", - [typestr](ClusterVector &self) { - return fmt::format( - self.fmt_base(), self.cluster_size_x(), - self.cluster_size_y(), typestr); - }) + [typestr]() { return fmt_format; }) /* .def("sum", [](ClusterVector &self) { @@ -124,13 +122,11 @@ void define_cluster_vector(py::module &m, const std::string &typestr) { .def_buffer( [typestr](ClusterVector &self) -> py::buffer_info { return py::buffer_info( - self.data(), /* Pointer to buffer */ - self.item_size(), /* Size of one scalar */ - fmt::format(self.fmt_base(), self.cluster_size_x(), - self.cluster_size_y(), - typestr), /* Format descriptor */ - 1, /* Number of dimensions */ - {self.size()}, /* Buffer dimensions */ + self.data(), /* Pointer to buffer */ + self.item_size(), /* Size of one scalar */ + fmt_format, /* Format descriptor */ + 1, /* Number of dimensions */ + {self.size()}, /* Buffer dimensions */ {self.item_size()} /* Strides (in bytes) for each index */ ); }); diff --git a/python/src/np_helper.hpp b/python/src/np_helper.hpp index 768efac..98be52f 100644 --- a/python/src/np_helper.hpp +++ b/python/src/np_helper.hpp @@ -10,6 +10,7 @@ #include "aare/NDView.hpp" namespace py = pybind11; +using namespace aare; // Pass image data back to python as a numpy array template @@ -64,4 +65,22 @@ template auto make_view_2d(py::array_t &arr) { } template auto make_view_1d(py::array_t &arr) { return aare::NDView(arr.mutable_data(), get_shape_1d(arr)); -} \ No newline at end of file +} + +template struct fmt_format_trait; // forward declaration + +template +struct fmt_format_trait> { + + static std::string value() { + return fmt::format("T{{{}:x;{}:y;{}:data;}}", + py::format_descriptor::format(), + py::format_descriptor::format(), + fmt::format("{}{}", ClusterSizeX * ClusterSizeY, + py::format_descriptor::format())); + } +}; + +template +auto fmt_format = fmt_format_trait::value(); \ No newline at end of file diff --git a/src/ClusterVector.test.cpp b/src/ClusterVector.test.cpp index 096abfa..1880355 100644 --- a/src/ClusterVector.test.cpp +++ b/src/ClusterVector.test.cpp @@ -25,10 +25,7 @@ TEST_CASE("ClusterVector 2x2 int32_t capacity 4, push back then read", REQUIRE(cv.size() == 1); REQUIRE(cv.capacity() == 4); - // Read the cluster back out using copy. TODO! Can we improve the API? - Cluster c2; - std::byte *ptr = cv.element_ptr(0); - std::copy(ptr, ptr + cv.item_size(), reinterpret_cast(&c2)); + auto c2 = cv.at(0); // Check that the data is the same REQUIRE(c1.x == c2.x);