mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-09-07 18:12:38 +02:00
Fixed ClusterVector move, filtering, and improved documentation (#357)
- Default `ClusterVector` move operations simplifying the code and fixing a bug. - Fixed inconsistent frame number type (uint64/int32) - Validate Python masks as one-dimensional, C-contiguous Boolean arrays, handle empty masks safely, and reserve filtered storage based on the selected cluster count. - Align the C++ and Python API documentation with the implementation, including concise `hitmap` and reduction documentation and a correctly rendered constructor example.
This commit is contained in:
@@ -43,6 +43,12 @@
|
||||
- ``TimingMode::Auto`` changed to ``TimingMode::AUTO_TIMING``, ``TimingMode::Trigger`` changed to ``TimingMode::TRIGGER_EXPOSURE``
|
||||
|
||||
### Bugfixes:
|
||||
- Fixed ``ClusterVector`` move operations to transfer storage instead of
|
||||
copying every cluster.
|
||||
- Validate that ``ClusterVector`` masks are one-dimensional, C-contiguous
|
||||
Boolean arrays.
|
||||
- Preserve signed ``ClusterVector`` frame numbers when filtering or reducing
|
||||
cluster dimensions.
|
||||
- Fixed broken reading of old (pre reordering) Moench03
|
||||
- Supports reading all timing modes supported in slsDetectorPackage (auto, trigger, gating, burst_trigger, trigger_gating)
|
||||
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
ClusterVector
|
||||
================
|
||||
|
||||
The ClusterVector, holds clusters from the ClusterFinder. Since it is templated
|
||||
in C++ we use a suffix indicating the type of cluster it holds. The suffix follows
|
||||
the same pattern as for ClusterFile i.e. ``ClusterVector_Cluster3x3i``
|
||||
for a vector holding 3x3 integer clusters.
|
||||
A ClusterVector stores fixed-size clusters contiguously. Since it is templated
|
||||
in C++, each bound class has a suffix indicating the cluster type. The suffix
|
||||
follows the same pattern as ClusterFile; for example,
|
||||
``ClusterVector_Cluster3x3i`` stores 3x3 clusters with 32-bit integer pixels.
|
||||
|
||||
|
||||
At the moment the functionality from python is limited and it is not supported
|
||||
to push_back clusters to the vector. The intended use case is to pass it to
|
||||
C++ functions that support the ClusterVector or to view it as a numpy array.
|
||||
The intended use case is to pass a ClusterVector to C++ functions that support
|
||||
it or to view it as a NumPy array.
|
||||
|
||||
**View ClusterVector as numpy array**
|
||||
**View ClusterVector as a NumPy array**
|
||||
|
||||
.. code:: python
|
||||
|
||||
@@ -25,7 +24,16 @@ C++ functions that support the ClusterVector or to view it as a numpy array.
|
||||
clusters = np.array(cluster_vector)
|
||||
|
||||
# Avoid copying the data by passing copy=False
|
||||
clusters = np.array(cluster_vector, copy = False)
|
||||
clusters = np.array(cluster_vector, copy=False)
|
||||
|
||||
.. warning::
|
||||
|
||||
A NumPy array created with ``copy=False`` is a view of the ClusterVector's
|
||||
current storage. Do not call ``push_back`` or otherwise change the
|
||||
ClusterVector while using the view. A ``push_back`` that reallocates the
|
||||
backing buffer leaves existing NumPy views pointing to invalid memory, and
|
||||
appending without reallocation does not update their shape. Use
|
||||
``copy=True`` if the ClusterVector may change after creating the array.
|
||||
|
||||
|
||||
.. py:currentmodule:: aare
|
||||
@@ -35,7 +43,8 @@ C++ functions that support the ClusterVector or to view it as a numpy array.
|
||||
:undoc-members:
|
||||
:inherited-members:
|
||||
|
||||
Below is the API of the ClusterVector_Cluster3x3i but all variants share the same API.
|
||||
Below is the API of ``ClusterVector_Cluster3x3i``. All variants share the same
|
||||
API.
|
||||
|
||||
.. autoclass:: aare._aare.ClusterVector_Cluster3x3i
|
||||
:special-members: __init__, __call__
|
||||
@@ -45,14 +54,39 @@ Below is the API of the ClusterVector_Cluster3x3i but all variants share the sam
|
||||
:inherited-members:
|
||||
|
||||
|
||||
**Free Functions:**
|
||||
**Free Functions:**
|
||||
|
||||
.. autofunction:: reduce_to_3x3
|
||||
.. py:function:: hitmap(image_size, clusters)
|
||||
:noindex:
|
||||
|
||||
Reduce a single Cluster to 3x3 by taking the 3x3 subcluster with highest photon energy.
|
||||
Count cluster centers into an ``int32`` image. ``image_size`` is given as
|
||||
``(rows, columns)``, and output element ``[y, x]`` contains the number of
|
||||
photon hits at that coordinate. Out-of-bounds hits are ignored. All registered
|
||||
ClusterVector variants are accepted.
|
||||
|
||||
.. autofunction:: reduce_to_2x2
|
||||
:param tuple[int, int] image_size: Shape of the output image.
|
||||
:param ClusterVector clusters: Clusters whose centers are counted.
|
||||
:return: Hit counts with shape ``image_size``.
|
||||
:rtype: numpy.ndarray
|
||||
|
||||
.. py:function:: reduce_to_3x3(clustervector)
|
||||
:noindex:
|
||||
|
||||
Reduce a single Cluster to 2x2 by taking the 2x2 subcluster with highest photon energy.
|
||||
Return a new vector containing the central 3x3 block of every input cluster.
|
||||
Cluster order, coordinates, frame number, and pixel dtype are preserved.
|
||||
|
||||
:param ClusterVector clustervector: Input clusters with both dimensions at
|
||||
least 3 and at least one dimension greater than 3.
|
||||
:return: Reduced 3x3 clusters.
|
||||
:rtype: ClusterVector
|
||||
|
||||
.. py:function:: reduce_to_2x2(clustervector)
|
||||
:noindex:
|
||||
|
||||
Return a new vector containing the highest-sum center-adjacent 2x2 block of
|
||||
every input cluster. Cluster order, coordinates, frame number, and pixel
|
||||
dtype are preserved.
|
||||
|
||||
:param ClusterVector clustervector: Input clusters of size 2x2 or larger.
|
||||
:return: Reduced 2x2 clusters.
|
||||
:rtype: ClusterVector
|
||||
|
||||
+13
-12
@@ -52,9 +52,9 @@ struct Cluster {
|
||||
|
||||
// TODO: handle 1 dimensional clusters
|
||||
/**
|
||||
* @brief sum of 2x2 subcluster with highest energy
|
||||
* @return photon energy of subcluster, 2x2 subcluster index relative to
|
||||
* cluster center
|
||||
* @brief Find the highest-sum center-adjacent 2x2 subcluster.
|
||||
* @return Sum and corner index of the selected 2x2 subcluster relative to
|
||||
* the cluster center
|
||||
*/
|
||||
Sum_index_pair<T, corner> max_sum_2x2() const {
|
||||
|
||||
@@ -117,13 +117,10 @@ struct Cluster {
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Reduce a cluster to a 2x2 cluster by selecting the 2x2 block with the
|
||||
* highest sum.
|
||||
* @brief Reduce a cluster to its highest-sum center-adjacent 2x2 block.
|
||||
* @param c Cluster to reduce
|
||||
* @return reduced cluster
|
||||
* @return Reduced cluster with the input coordinates
|
||||
* @note The cluster is filled using row major ordering starting at the top-left
|
||||
* (thus for a max subcluster in the top left cornern the photon hit is at
|
||||
* the fourth position)
|
||||
*/
|
||||
template <typename T, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
|
||||
typename CoordType = uint16_t>
|
||||
@@ -194,17 +191,21 @@ Cluster<T, 2, 2, uint16_t> reduce_to_2x2(const Cluster<T, 3, 3, uint16_t> &c) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reduce a cluster to a 3x3 cluster
|
||||
* @brief Reduce a cluster to the 3x3 block around its center index.
|
||||
* @param c Cluster to reduce
|
||||
* @return reduced cluster
|
||||
* @pre ClusterSizeX and ClusterSizeY must both be at least 3, and at least one
|
||||
* must be greater than 3.
|
||||
* @return Reduced cluster with the input coordinates are preserved.
|
||||
*/
|
||||
template <typename T, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
|
||||
typename CoordType = int16_t>
|
||||
Cluster<T, 3, 3, CoordType>
|
||||
reduce_to_3x3(const Cluster<T, ClusterSizeX, ClusterSizeY, CoordType> &c) {
|
||||
|
||||
static_assert(ClusterSizeX >= 3 && ClusterSizeY >= 3,
|
||||
"Cluster sizes must be at least 3x3 for reduction to 3x3");
|
||||
static_assert(ClusterSizeX >= 3 && ClusterSizeY >= 3 &&
|
||||
(ClusterSizeX > 3 || ClusterSizeY > 3),
|
||||
"Cluster sizes must both be at least 3, and at least one "
|
||||
"must be greater than 3 for reduction to 3x3");
|
||||
|
||||
Cluster<T, 3, 3, CoordType> result{};
|
||||
|
||||
|
||||
@@ -20,14 +20,19 @@ template <typename ClusterType,
|
||||
class ClusterVector; // Forward declaration
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @brief Move-only container that stores fixed-size clusters contiguously.
|
||||
*
|
||||
* The pixel type, cluster dimensions, and coordinate type are determined by
|
||||
* the Cluster specialization supplied as the template argument.
|
||||
*
|
||||
* @note push_back, reserve, and resize can invalidate pointers, references, and
|
||||
* iterators 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 ClusterSizeX cluster size in the x dimension
|
||||
* @tparam ClusterSizeY cluster size in the y dimension
|
||||
* @tparam CoordType data type of the x and y coordinates of the cluster
|
||||
* (normally uint16_t)
|
||||
*/
|
||||
@@ -44,11 +49,11 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
|
||||
|
||||
/**
|
||||
* @brief Construct a new ClusterVector object
|
||||
* @param capacity initial capacity of the buffer in number of clusters
|
||||
* @param capacity minimum initial capacity 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)
|
||||
ClusterVector(size_t capacity = 1024, int32_t frame_number = 0)
|
||||
: m_frame_number(frame_number) {
|
||||
m_data.reserve(capacity);
|
||||
}
|
||||
@@ -57,18 +62,24 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
|
||||
ClusterVector &operator=(ClusterVector &&other) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Create a copy of the clustervector by filtering clusters in the
|
||||
* ClusterVector using a boolean mask.
|
||||
* @param mask boolean 1d mask
|
||||
* @return ClusterVector containing only the clusters where the mask is
|
||||
* true
|
||||
* @brief Return a filtered copy selected by a one-dimensional Boolean mask.
|
||||
* @param mask boolean 1d mask true if element in ClusterVector will be
|
||||
* included
|
||||
* @return ClusterVector containing the selected clusters in their original
|
||||
* order and with the original frame number
|
||||
* @throws std::runtime_error if the mask length differs from size()
|
||||
*/
|
||||
ClusterVector operator()(NDView<bool, 1> mask) {
|
||||
if (static_cast<size_t>(mask.size()) != m_data.size()) {
|
||||
throw std::runtime_error(
|
||||
LOCATION + "Mask size does not match number of clusters");
|
||||
}
|
||||
ClusterVector result(capacity(), frame_number());
|
||||
if (m_data.empty()) {
|
||||
return ClusterVector(0, frame_number());
|
||||
}
|
||||
const auto selected =
|
||||
static_cast<size_t>(std::count(mask.begin(), mask.end(), true));
|
||||
ClusterVector result(selected, frame_number());
|
||||
for (size_t i = 0; i < m_data.size(); ++i) {
|
||||
if (mask(i)) {
|
||||
result.push_back(m_data[i]);
|
||||
@@ -77,20 +88,9 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
|
||||
return result;
|
||||
}
|
||||
|
||||
// // 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 Sum the pixels in each cluster
|
||||
* @return std::vector<T> vector of sums for each cluster
|
||||
* @brief Sum the pixels in each cluster.
|
||||
* @return One sum for every cluster, in container order
|
||||
*/
|
||||
std::vector<T> sum() {
|
||||
std::vector<T> sums(m_data.size());
|
||||
@@ -103,9 +103,10 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sum the pixels in the 2x2 subcluster with the biggest pixel sum in
|
||||
* each cluster
|
||||
* @return vector of sums index pairs for each cluster
|
||||
* @brief Find the highest-sum center-adjacent 2x2 subcluster in each
|
||||
* cluster.
|
||||
* @return One sum and corner-index pair for every cluster, in container
|
||||
* order
|
||||
*/
|
||||
std::vector<Sum_index_pair<T, corner>> sum_2x2() {
|
||||
std::vector<Sum_index_pair<T, corner>> sums_2x2(m_data.size());
|
||||
@@ -125,10 +126,29 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
|
||||
*/
|
||||
void reserve(size_t capacity) { m_data.reserve(capacity); }
|
||||
|
||||
/**
|
||||
* @brief Change the number of stored clusters.
|
||||
* @param size new number of clusters
|
||||
* @note Growing the vector value-initializes new clusters and can
|
||||
* invalidate pointers, references, and iterators.
|
||||
*/
|
||||
void resize(size_t size) { m_data.resize(size); }
|
||||
|
||||
/**
|
||||
* @brief Append a cluster to the vector.
|
||||
* @param cluster cluster to append
|
||||
* @note Reallocation invalidates pointers, references, iterators, and
|
||||
* zero-copy NumPy views of the storage.
|
||||
*/
|
||||
void push_back(const ClusterType &cluster) { m_data.push_back(cluster); }
|
||||
|
||||
/**
|
||||
* @brief Append all clusters from another vector.
|
||||
* @param other vector whose clusters are appended
|
||||
* @return Reference to this vector
|
||||
* @note The frame number of this vector is unchanged.
|
||||
* @warning other must not refer to this vector.
|
||||
*/
|
||||
ClusterVector &operator+=(const ClusterVector &other) {
|
||||
m_data.insert(m_data.end(), other.begin(), other.end());
|
||||
|
||||
@@ -145,8 +165,10 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
|
||||
*/
|
||||
bool empty() const { return m_data.empty(); }
|
||||
|
||||
/** @brief Return the cluster size in the x dimension. */
|
||||
uint8_t cluster_size_x() const { return ClusterSizeX; }
|
||||
|
||||
/** @brief Return the cluster size in the y dimension. */
|
||||
uint8_t cluster_size_y() const { return ClusterSizeY; }
|
||||
|
||||
/**
|
||||
@@ -161,7 +183,7 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
|
||||
auto end() const { return m_data.end(); }
|
||||
|
||||
/**
|
||||
* @brief Return the size in bytes of a single cluster
|
||||
* @brief Return the size in bytes of one stored cluster, including padding.
|
||||
*/
|
||||
size_t item_size() const {
|
||||
return sizeof(ClusterType); // 2 * sizeof(CoordType) + ClusterSizeX *
|
||||
@@ -172,8 +194,8 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
|
||||
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
|
||||
* @brief Return a reference to the i-th cluster without bounds checking.
|
||||
* @param i zero-based cluster index
|
||||
*/
|
||||
ClusterType &operator[](size_t i) { return m_data[i]; }
|
||||
|
||||
@@ -185,16 +207,20 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
|
||||
*/
|
||||
int32_t frame_number() const { return m_frame_number; }
|
||||
|
||||
/**
|
||||
* @brief Set the signed 32-bit frame number associated with the clusters.
|
||||
* @param frame_number frame number, or 0 for clusters from multiple frames
|
||||
*/
|
||||
void set_frame_number(int32_t frame_number) {
|
||||
m_frame_number = frame_number;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Reduce a cluster to a 2x2 cluster by selecting the 2x2 block with the
|
||||
* highest sum.
|
||||
* @param cv Clustervector containing clusters to reduce
|
||||
* @return Clustervector with reduced clusters
|
||||
* @brief Reduce every cluster to its highest-sum center-adjacent 2x2 block.
|
||||
* @param cv ClusterVector containing clusters to reduce
|
||||
* @return ClusterVector of 2x2 clusters in the original order and with the
|
||||
* original frame number
|
||||
* @note The cluster is filled using row major ordering starting at the top-left
|
||||
* (thus for a max subcluster in the top left cornern the photon hit is at
|
||||
* the fourth position)
|
||||
@@ -204,7 +230,8 @@ template <typename T, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
|
||||
ClusterVector<Cluster<T, 2, 2, CoordType>> reduce_to_2x2(
|
||||
const ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>>
|
||||
&cv) {
|
||||
ClusterVector<Cluster<T, 2, 2, CoordType>> result;
|
||||
ClusterVector<Cluster<T, 2, 2, CoordType>> result(cv.size(),
|
||||
cv.frame_number());
|
||||
for (const auto &c : cv) {
|
||||
result.push_back(reduce_to_2x2(c));
|
||||
}
|
||||
@@ -212,20 +239,25 @@ ClusterVector<Cluster<T, 2, 2, CoordType>> reduce_to_2x2(
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reduce a cluster to a 3x3 cluster
|
||||
* @param cv Clustervector containing clusters to reduce
|
||||
* @return Clustervector with reduced clusters
|
||||
* @brief Reduce every cluster to the 3x3 block around its center index.
|
||||
* @param cv ClusterVector containing clusters to reduce
|
||||
* @pre ClusterSizeX and ClusterSizeY must both be at least 3, and at least one
|
||||
* must be greater than 3.
|
||||
* @return ClusterVector of 3x3 clusters in the original order and with the
|
||||
* original frame number
|
||||
* @note Coordinates are preserved.
|
||||
*/
|
||||
template <typename T, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
|
||||
typename CoordType>
|
||||
ClusterVector<Cluster<T, 3, 3, CoordType>> reduce_to_3x3(
|
||||
const ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>>
|
||||
&cv) {
|
||||
ClusterVector<Cluster<T, 3, 3, CoordType>> result;
|
||||
ClusterVector<Cluster<T, 3, 3, CoordType>> result(cv.size(),
|
||||
cv.frame_number());
|
||||
for (const auto &c : cv) {
|
||||
result.push_back(reduce_to_3x3(c));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace aare
|
||||
} // namespace aare
|
||||
|
||||
@@ -7,16 +7,33 @@ from .ClusterFinder import _get_class
|
||||
|
||||
def ClusterVector(cluster_size=(3,3), dtype = np.int32):
|
||||
"""
|
||||
Factory function to create a ClusterVector object. Provides a cleaner syntax for
|
||||
the templated ClusterVector in C++.
|
||||
Create an empty ClusterVector for a supported cluster size and pixel dtype.
|
||||
|
||||
.. code-block:: python
|
||||
Parameters
|
||||
----------
|
||||
cluster_size : tuple[int, int], default=(3, 3)
|
||||
Cluster dimensions in the x and y directions.
|
||||
dtype : numpy.dtype, default=numpy.int32
|
||||
Pixel storage type. The cluster size and dtype combination must have a
|
||||
compiled binding.
|
||||
|
||||
from aare import ClusterVector
|
||||
|
||||
ClusterVector(cluster_size=(3,3), dtype=np.float64)
|
||||
Returns
|
||||
-------
|
||||
ClusterVector
|
||||
An empty vector with frame number 0 and space reserved for at least
|
||||
1024 clusters.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the requested size and dtype combination is unavailable.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from aare import ClusterVector
|
||||
>>> clusters = ClusterVector(cluster_size=(3, 3), dtype=np.float64)
|
||||
"""
|
||||
|
||||
cls = _get_class("ClusterVector", cluster_size, dtype)
|
||||
return cls()
|
||||
|
||||
|
||||
+24
-13
@@ -67,8 +67,16 @@ void define_Cluster(py::module &m, const std::string &typestr) {
|
||||
return py::make_tuple(max_sum.sum,
|
||||
static_cast<int>(max_sum.index));
|
||||
},
|
||||
R"(calculates sum of 2x2 subcluster with highest energy and index relative to cluster center 0: top_left, 1: top_right, 2: bottom_left, 3: bottom_right
|
||||
)");
|
||||
R"doc(
|
||||
Return the highest-sum center-adjacent 2x2 subcluster.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
``(sum, index)``, where index is 0 for top-left, 1 for
|
||||
top-right, 2 for bottom-left, or 3 for bottom-right relative to
|
||||
the cluster center.
|
||||
)doc");
|
||||
}
|
||||
|
||||
template <typename T, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
|
||||
@@ -80,7 +88,14 @@ void reduce_to_3x3(py::module &m) {
|
||||
[](const Cluster<T, ClusterSizeX, ClusterSizeY, CoordType> &cl) {
|
||||
return reduce_to_3x3(cl);
|
||||
},
|
||||
py::return_value_policy::move, R"(Reduce cluster to 3x3 subcluster)");
|
||||
py::return_value_policy::move, py::arg("cluster"), R"doc(
|
||||
Return the 3x3 block around the cluster's center index.
|
||||
|
||||
Both input dimensions must be at least 3, and at least one must be
|
||||
greater than 3.
|
||||
The input coordinates are preserved and output data is stored in
|
||||
row-major order.
|
||||
)doc");
|
||||
}
|
||||
|
||||
template <typename T, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
|
||||
@@ -92,16 +107,12 @@ void reduce_to_2x2(py::module &m) {
|
||||
[](const Cluster<T, ClusterSizeX, ClusterSizeY, CoordType> &cl) {
|
||||
return reduce_to_2x2(cl);
|
||||
},
|
||||
py::return_value_policy::move,
|
||||
R"(
|
||||
Reduce cluster to 2x2 subcluster by taking the 2x2 subcluster with
|
||||
the highest photon energy.
|
||||
py::return_value_policy::move, py::arg("cluster"), R"doc(
|
||||
Return the highest-sum center-adjacent 2x2 block.
|
||||
|
||||
RETURN:
|
||||
|
||||
reduced cluster (cluster is filled in row major ordering starting at the top left. Thus for a max subcluster in the top left corner the photon hit is at the fourth position.)
|
||||
|
||||
)");
|
||||
The input coordinates are preserved and output data is stored in
|
||||
row-major order.
|
||||
)doc");
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
@@ -29,37 +29,76 @@ void define_ClusterVector(py::module &m, const std::string &typestr) {
|
||||
|
||||
py::class_<ClusterVector<
|
||||
Cluster<Type, ClusterSizeX, ClusterSizeY, CoordType>, void>>(
|
||||
m, class_name.c_str(),
|
||||
m, class_name.c_str(), R"doc(
|
||||
A contiguous, move-only container of fixed-size clusters.
|
||||
|
||||
The class supports the Python buffer protocol, so ``numpy.array`` can
|
||||
either copy its data or create a zero-copy view. A zero-copy view is
|
||||
valid only while the ClusterVector's underlying allocation and size
|
||||
remain unchanged.
|
||||
)doc",
|
||||
py::buffer_protocol())
|
||||
|
||||
.def(py::init()) // TODO change!!!
|
||||
.def(py::init(), R"doc(
|
||||
Create an empty ClusterVector with frame number 0 and space reserved
|
||||
for at least 1024 clusters.
|
||||
)doc")
|
||||
|
||||
.def(
|
||||
"__call__",
|
||||
[](ClusterVector<ClusterType> &self, py::array_t<bool> mask) {
|
||||
[](ClusterVector<ClusterType> &self,
|
||||
py::array_t<bool, py::array::c_style> mask) {
|
||||
if (mask.ndim() != 1) {
|
||||
throw py::value_error("Mask must be one-dimensional");
|
||||
}
|
||||
return self(make_view_1d(mask));
|
||||
},
|
||||
py::arg("mask"), R"(
|
||||
Create a copy of the clustervector and apply a boolean mask to the ClusterVector.
|
||||
py::arg("mask").noconvert(), R"doc(
|
||||
Return a filtered copy of this ClusterVector.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mask : numpy.ndarray
|
||||
One-dimensional, writable, C-contiguous array with dtype
|
||||
``numpy.bool_`` and one element per cluster.
|
||||
|
||||
mask : 1d boolean numpy array
|
||||
Mask to apply to the ClusterVector. Must be the same length as the number of clusters in the ClusterVector.
|
||||
Returns
|
||||
-------
|
||||
ClusterVector
|
||||
Selected clusters in their original order. The frame number is
|
||||
preserved.
|
||||
)doc")
|
||||
|
||||
)")
|
||||
.def(
|
||||
"push_back",
|
||||
[](ClusterVector<ClusterType> &self, const ClusterType &cluster) {
|
||||
self.push_back(cluster);
|
||||
},
|
||||
py::arg("cluster"), R"doc(
|
||||
Append one cluster.
|
||||
|
||||
.def("push_back",
|
||||
[](ClusterVector<ClusterType> &self, const ClusterType &cluster) {
|
||||
self.push_back(cluster);
|
||||
})
|
||||
Notes
|
||||
-----
|
||||
Do not call this method while a zero-copy NumPy view of the
|
||||
ClusterVector exists. Reallocation invalidates the view, while an
|
||||
append without reallocation leaves its shape unchanged.
|
||||
)doc")
|
||||
|
||||
.def("sum",
|
||||
[](ClusterVector<ClusterType> &self) {
|
||||
auto *vec = new std::vector<Type>(self.sum());
|
||||
return return_vector(vec);
|
||||
})
|
||||
.def(
|
||||
"sum",
|
||||
[](ClusterVector<ClusterType> &self) {
|
||||
auto *vec = new std::vector<Type>(self.sum());
|
||||
return return_vector(vec);
|
||||
},
|
||||
R"doc(
|
||||
Return the sum of all pixels in each cluster.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
One value per cluster, in container order and with the cluster
|
||||
pixel dtype.
|
||||
)doc")
|
||||
.def(
|
||||
"sum_2x2",
|
||||
[](ClusterVector<ClusterType> &self) {
|
||||
@@ -68,24 +107,45 @@ void define_ClusterVector(py::module &m, const std::string &typestr) {
|
||||
|
||||
return return_vector(vec);
|
||||
},
|
||||
R"(calculates sum of 2x2 subcluster with highest energy and index relative to cluster center 0: top_left, 1: top_right, 2: bottom_left, 3: bottom_right
|
||||
)")
|
||||
.def_property_readonly("size", &ClusterVector<ClusterType>::size)
|
||||
.def("empty", &ClusterVector<ClusterType>::empty)
|
||||
.def("item_size", &ClusterVector<ClusterType>::item_size)
|
||||
.def_property_readonly("fmt",
|
||||
[typestr](ClusterVector<ClusterType> &self) {
|
||||
return fmt_format<ClusterType>;
|
||||
})
|
||||
R"doc(
|
||||
Return the highest-sum center-adjacent 2x2 subcluster for each
|
||||
cluster.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
Structured array with ``sum`` and ``index`` fields. Indices are
|
||||
0 for top-left, 1 for top-right, 2 for bottom-left, and 3 for
|
||||
bottom-right, relative to the cluster center.
|
||||
)doc")
|
||||
.def_property_readonly("size", &ClusterVector<ClusterType>::size,
|
||||
"Number of stored clusters.")
|
||||
.def("empty", &ClusterVector<ClusterType>::empty,
|
||||
"Return True when no clusters are stored.")
|
||||
.def("item_size", &ClusterVector<ClusterType>::item_size,
|
||||
"Return the size in bytes of one stored cluster, including "
|
||||
"padding.")
|
||||
.def_property_readonly(
|
||||
"fmt",
|
||||
[typestr](ClusterVector<ClusterType> &self) {
|
||||
return fmt_format<ClusterType>;
|
||||
},
|
||||
"PEP 3118 format string for one stored cluster.")
|
||||
|
||||
.def_property_readonly("cluster_size_x",
|
||||
&ClusterVector<ClusterType>::cluster_size_x)
|
||||
&ClusterVector<ClusterType>::cluster_size_x,
|
||||
"Cluster size in the x dimension.")
|
||||
.def_property_readonly("cluster_size_y",
|
||||
&ClusterVector<ClusterType>::cluster_size_y)
|
||||
&ClusterVector<ClusterType>::cluster_size_y,
|
||||
"Cluster size in the y dimension.")
|
||||
.def_property_readonly("capacity",
|
||||
&ClusterVector<ClusterType>::capacity)
|
||||
&ClusterVector<ClusterType>::capacity,
|
||||
"Number of clusters that fit without "
|
||||
"reallocation.")
|
||||
.def_property("frame_number", &ClusterVector<ClusterType>::frame_number,
|
||||
&ClusterVector<ClusterType>::set_frame_number)
|
||||
&ClusterVector<ClusterType>::set_frame_number,
|
||||
"Signed 32-bit frame number; 0 can indicate clusters "
|
||||
"from multiple frames.")
|
||||
.def_buffer(
|
||||
[typestr](ClusterVector<ClusterType> &self) -> py::buffer_info {
|
||||
return py::buffer_info(
|
||||
@@ -99,31 +159,38 @@ void define_ClusterVector(py::module &m, const std::string &typestr) {
|
||||
});
|
||||
|
||||
// Free functions using ClusterVector
|
||||
m.def("hitmap",
|
||||
[](std::array<size_t, 2> image_size, ClusterVector<ClusterType> &cv) {
|
||||
// Create a numpy array to hold the hitmap
|
||||
// The shape of the array is (image_size[0], image_size[1])
|
||||
// note that the python array is passed as [row, col] which
|
||||
// is the opposite of the clusters [x,y]
|
||||
py::array_t<int32_t> hitmap(image_size);
|
||||
auto r = hitmap.mutable_unchecked<2>();
|
||||
m.def(
|
||||
"hitmap",
|
||||
[](std::array<size_t, 2> image_size, ClusterVector<ClusterType> &cv) {
|
||||
// Create a numpy array to hold the hitmap
|
||||
// The shape of the array is (image_size[0], image_size[1])
|
||||
// note that the python array is passed as [row, col] which
|
||||
// is the opposite of the clusters [x,y]
|
||||
py::array_t<int32_t> hitmap(image_size);
|
||||
auto r = hitmap.mutable_unchecked<2>();
|
||||
|
||||
// Initialize hitmap to 0
|
||||
for (py::ssize_t i = 0; i < r.shape(0); i++)
|
||||
for (py::ssize_t j = 0; j < r.shape(1); j++)
|
||||
r(i, j) = 0;
|
||||
// Initialize hitmap to 0
|
||||
for (py::ssize_t i = 0; i < r.shape(0); i++)
|
||||
for (py::ssize_t j = 0; j < r.shape(1); j++)
|
||||
r(i, j) = 0;
|
||||
|
||||
// Loop over the clusters and increment the hitmap
|
||||
// Skip out of bound clusters
|
||||
for (const auto &cluster : cv) {
|
||||
auto x = cluster.x;
|
||||
auto y = cluster.y;
|
||||
if (x < image_size[1] && y < image_size[0])
|
||||
r(cluster.y, cluster.x) += 1;
|
||||
}
|
||||
// Loop over the clusters and increment the hitmap
|
||||
// Skip out of bound clusters
|
||||
for (const auto &cluster : cv) {
|
||||
auto x = cluster.x;
|
||||
auto y = cluster.y;
|
||||
if (x < image_size[1] && y < image_size[0])
|
||||
r(cluster.y, cluster.x) += 1;
|
||||
}
|
||||
|
||||
return hitmap;
|
||||
});
|
||||
return hitmap;
|
||||
},
|
||||
py::arg("image_size"), py::arg("clusters"), R"doc(
|
||||
Count cluster centers into an ``int32`` image whose shape is given by
|
||||
``image_size`` as ``(rows, columns)``. Element ``[y, x]`` contains the
|
||||
number of cluster centers at that coordinate. Out-of-bounds centers are
|
||||
ignored.
|
||||
)doc");
|
||||
}
|
||||
|
||||
template <typename Type, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
|
||||
@@ -136,15 +203,12 @@ void define_2x2_reduction(py::module &m) {
|
||||
return new ClusterVector<Cluster<Type, 2, 2, CoordType>>(
|
||||
reduce_to_2x2(cv));
|
||||
},
|
||||
R"(
|
||||
Reduce cluster to 2x2 subcluster by taking the 2x2 subcluster with
|
||||
the highest photon energy.
|
||||
|
||||
Parameters
|
||||
|
||||
cv : ClusterVector (clusters are filled in row-major ordering starting at the top left. Thus for a max subcluster in the top left corner the photon hit is at the fourth position.)
|
||||
|
||||
)",
|
||||
R"doc(
|
||||
Reduce every cluster to its highest-sum center-adjacent 2x2 block.
|
||||
Returns a new ClusterVector; cluster order, coordinates, and frame
|
||||
number are preserved. Input pixel data is interpreted in row-major
|
||||
order.
|
||||
)doc",
|
||||
py::arg("clustervector"));
|
||||
}
|
||||
|
||||
@@ -159,13 +223,13 @@ void define_3x3_reduction(py::module &m) {
|
||||
return new ClusterVector<Cluster<Type, 3, 3, CoordType>>(
|
||||
reduce_to_3x3(cv));
|
||||
},
|
||||
R"(
|
||||
Reduce cluster to 3x3 subcluster
|
||||
|
||||
Parameters
|
||||
|
||||
cv : ClusterVector
|
||||
)",
|
||||
R"doc(
|
||||
Reduce every cluster to the 3x3 block around its center index.
|
||||
Both input dimensions must be at least 3, and at least one must be
|
||||
greater than 3.
|
||||
Returns a new ClusterVector; cluster order, coordinates, and frame
|
||||
number are preserved.
|
||||
)doc",
|
||||
py::arg("clustervector"));
|
||||
}
|
||||
|
||||
|
||||
@@ -128,9 +128,6 @@ PYBIND11_MODULE(_aare, m) {
|
||||
DEFINE_BINDINGS_CLUSTERFINDER(double, 9, 9, uint16_t, d);
|
||||
DEFINE_BINDINGS_CLUSTERFINDER(float, 9, 9, uint16_t, f);
|
||||
|
||||
define_3x3_reduction<int, 3, 3, uint16_t>(m);
|
||||
define_3x3_reduction<double, 3, 3, uint16_t>(m);
|
||||
define_3x3_reduction<float, 3, 3, uint16_t>(m);
|
||||
define_3x3_reduction<int, 5, 5, uint16_t>(m);
|
||||
define_3x3_reduction<double, 5, 5, uint16_t>(m);
|
||||
define_3x3_reduction<float, 5, 5, uint16_t>(m);
|
||||
@@ -141,9 +138,6 @@ PYBIND11_MODULE(_aare, m) {
|
||||
define_3x3_reduction<double, 9, 9, uint16_t>(m);
|
||||
define_3x3_reduction<float, 9, 9, uint16_t>(m);
|
||||
|
||||
reduce_to_3x3<int, 3, 3, uint16_t>(m);
|
||||
reduce_to_3x3<double, 3, 3, uint16_t>(m);
|
||||
reduce_to_3x3<float, 3, 3, uint16_t>(m);
|
||||
reduce_to_3x3<int, 5, 5, uint16_t>(m);
|
||||
reduce_to_3x3<double, 5, 5, uint16_t>(m);
|
||||
reduce_to_3x3<float, 5, 5, uint16_t>(m);
|
||||
|
||||
@@ -83,12 +83,15 @@ def test_make_a_hitmap_from_cluster_vector():
|
||||
|
||||
def test_2x2_reduction():
|
||||
cv = ClusterVector((3,3))
|
||||
cv.frame_number = -135
|
||||
|
||||
cv.push_back(_aare.Cluster3x3i(5, 5, np.array([1, 1, 1, 2, 3, 1, 2, 2, 1], dtype=np.int32)))
|
||||
cv.push_back(_aare.Cluster3x3i(5, 5, np.array([2, 2, 1, 2, 3, 1, 1, 1, 1], dtype=np.int32)))
|
||||
|
||||
reduced_cv = np.array(_aare.reduce_to_2x2(cv), copy=False)
|
||||
reduced = _aare.reduce_to_2x2(cv)
|
||||
reduced_cv = np.array(reduced, copy=False)
|
||||
|
||||
assert reduced.frame_number == cv.frame_number
|
||||
assert reduced_cv.size == 2
|
||||
assert reduced_cv[0]["x"] == 5
|
||||
assert reduced_cv[0]["y"] == 5
|
||||
@@ -100,14 +103,17 @@ def test_2x2_reduction():
|
||||
|
||||
def test_3x3_reduction():
|
||||
cv = _aare.ClusterVector_Cluster5x5d()
|
||||
cv.frame_number = 246
|
||||
|
||||
cv.push_back(_aare.Cluster5x5d(5,5,np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 2.0, 2.0, 3.0,
|
||||
1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], dtype=np.double)))
|
||||
cv.push_back(_aare.Cluster5x5d(5,5,np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 2.0, 2.0, 3.0,
|
||||
1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], dtype=np.double)))
|
||||
|
||||
reduced_cv = np.array(_aare.reduce_to_3x3(cv), copy=False)
|
||||
reduced = _aare.reduce_to_3x3(cv)
|
||||
reduced_cv = np.array(reduced, copy=False)
|
||||
|
||||
assert reduced.frame_number == cv.frame_number
|
||||
assert reduced_cv.size == 2
|
||||
assert reduced_cv[0]["x"] == 5
|
||||
assert reduced_cv[0]["y"] == 5
|
||||
@@ -130,3 +136,26 @@ def test_masking():
|
||||
assert cv_masked_array[0]["x"] == 1
|
||||
assert cv_masked_array[0]["y"] == 2
|
||||
assert (cv_masked_array[0]["data"] == np.ones((3,3),dtype=np.int32)).all()
|
||||
|
||||
|
||||
def test_masking_requires_c_contiguous_array():
|
||||
cv = _aare.ClusterVector_Cluster3x3i()
|
||||
cv.push_back(_aare.Cluster3x3i(1, 2, np.ones(9, dtype=np.int32)))
|
||||
cv.push_back(_aare.Cluster3x3i(3, 4, np.ones(9, dtype=np.int32)))
|
||||
|
||||
mask = np.array([True, False, True, False], dtype=bool)[::2]
|
||||
assert not mask.flags.c_contiguous
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
cv(mask)
|
||||
|
||||
|
||||
def test_masking_requires_one_dimension():
|
||||
cv = _aare.ClusterVector_Cluster3x3i()
|
||||
cv.push_back(_aare.Cluster3x3i(1, 2, np.ones(9, dtype=np.int32)))
|
||||
cv.push_back(_aare.Cluster3x3i(3, 4, np.ones(9, dtype=np.int32)))
|
||||
|
||||
mask = np.array([[True, False]], dtype=bool)
|
||||
|
||||
with pytest.raises(ValueError, match="one-dimensional"):
|
||||
cv(mask)
|
||||
|
||||
+11
-10
@@ -24,8 +24,8 @@ TEST_CASE("Test sum of Cluster", "[cluster]") {
|
||||
using ClusterTypes = std::variant<Cluster<int, 2, 2>, Cluster<int, 3, 3>,
|
||||
Cluster<int, 5, 5>, Cluster<int, 2, 3>>;
|
||||
|
||||
using ClusterTypesLargerThan2x2 =
|
||||
std::variant<Cluster<int, 3, 3>, Cluster<int, 4, 4>, Cluster<int, 5, 5>>;
|
||||
using ReducibleTo3x3ClusterTypes =
|
||||
std::variant<Cluster<int, 3, 4>, Cluster<int, 4, 3>, Cluster<int, 5, 5>>;
|
||||
|
||||
TEST_CASE("Test reduce to 2x2 Cluster", "[cluster]") {
|
||||
auto [cluster, expected_reduced_cluster] = GENERATE(
|
||||
@@ -68,14 +68,15 @@ TEST_CASE("Test reduce to 2x2 Cluster", "[cluster]") {
|
||||
|
||||
TEST_CASE("Test reduce to 3x3 Cluster", "[cluster]") {
|
||||
auto [cluster, expected_reduced_cluster] = GENERATE(
|
||||
std::make_tuple(ClusterTypesLargerThan2x2{Cluster<int, 3, 3>{
|
||||
5, 5, {1, 1, 1, 1, 3, 1, 1, 1, 1}}},
|
||||
Cluster<int, 3, 3>{5, 5, {1, 1, 1, 1, 3, 1, 1, 1, 1}}),
|
||||
std::make_tuple(
|
||||
ClusterTypesLargerThan2x2{Cluster<int, 4, 4>{
|
||||
5, 5, {2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1}}},
|
||||
Cluster<int, 3, 3>{5, 5, {2, 1, 1, 1, 3, 1, 1, 1, 1}}),
|
||||
std::make_tuple(ClusterTypesLargerThan2x2{Cluster<int, 5, 5>{
|
||||
ReducibleTo3x3ClusterTypes{Cluster<int, 3, 4>{
|
||||
5, 5, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}}},
|
||||
Cluster<int, 3, 3>{5, 5, {3, 4, 5, 6, 7, 8, 9, 10, 11}}),
|
||||
std::make_tuple(
|
||||
ReducibleTo3x3ClusterTypes{Cluster<int, 4, 3>{
|
||||
5, 5, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}}},
|
||||
Cluster<int, 3, 3>{5, 5, {1, 2, 3, 5, 6, 7, 9, 10, 11}}),
|
||||
std::make_tuple(ReducibleTo3x3ClusterTypes{Cluster<int, 5, 5>{
|
||||
5, 5, {1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 3,
|
||||
1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1}}},
|
||||
Cluster<int, 3, 3>{5, 5, {2, 1, 1, 2, 3, 1, 2, 1, 1}}));
|
||||
@@ -89,4 +90,4 @@ TEST_CASE("Test reduce to 3x3 Cluster", "[cluster]") {
|
||||
CHECK(std::equal(reduced_cluster.data.begin(),
|
||||
reduced_cluster.data.begin() + 9,
|
||||
expected_reduced_cluster.data.begin()));
|
||||
}
|
||||
}
|
||||
|
||||
+87
-13
@@ -22,27 +22,79 @@ TEST_CASE("After pushing back one element the ClusterVector is not empty") {
|
||||
REQUIRE(!cv.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("ClusterVector move constructor preserves contents") {
|
||||
ClusterVector<C1> source(4, 42);
|
||||
TEST_CASE("Filtering a ClusterVector selects the masked elements") {
|
||||
ClusterVector<C1> source(8, 123);
|
||||
source.push_back(C1{1, 2, {3, 4, 5, 6}});
|
||||
source.push_back(C1{7, 8, {9, 10, 11, 12}});
|
||||
source.push_back(C1{13, 14, {15, 16, 17, 18}});
|
||||
source.push_back(C1{19, 20, {21, 22, 23, 24}});
|
||||
|
||||
ClusterVector<C1> moved(std::move(source));
|
||||
SECTION("Some elements are selected") {
|
||||
std::array<bool, 4> mask{true, false, false, true};
|
||||
|
||||
REQUIRE(moved.size() == 1);
|
||||
CHECK(moved.frame_number() == 42);
|
||||
CHECK(moved[0].data[3] == 6);
|
||||
auto filtered = source(aare::NDView<bool, 1>{mask.data(), {4}});
|
||||
|
||||
CHECK(filtered.size() == 2);
|
||||
CHECK(filtered.frame_number() == 123);
|
||||
CHECK(filtered[0].x == 1);
|
||||
CHECK(filtered[1].x == 19);
|
||||
}
|
||||
|
||||
SECTION("No elements are selected") {
|
||||
std::array<bool, 4> mask{false, false, false, false};
|
||||
|
||||
auto filtered = source(aare::NDView<bool, 1>{mask.data(), {4}});
|
||||
|
||||
CHECK(filtered.empty());
|
||||
CHECK(filtered.frame_number() == 123);
|
||||
}
|
||||
|
||||
SECTION("An empty vector accepts a default empty mask") {
|
||||
ClusterVector<C1> empty_source(0, -123);
|
||||
|
||||
auto filtered = empty_source(aare::NDView<bool, 1>{});
|
||||
|
||||
CHECK(filtered.empty());
|
||||
CHECK(filtered.frame_number() == -123);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ClusterVector move assignment preserves contents") {
|
||||
ClusterVector<C1> source(4, 42);
|
||||
TEST_CASE("Move constructing a ClusterVector transfers its storage") {
|
||||
ClusterVector<C1> source(4, 123);
|
||||
source.push_back(C1{1, 2, {3, 4, 5, 6}});
|
||||
ClusterVector<C1> moved(1);
|
||||
source.push_back(C1{7, 8, {9, 10, 11, 12}});
|
||||
|
||||
moved = std::move(source);
|
||||
const auto *source_data = source.data();
|
||||
const auto source_capacity = source.capacity();
|
||||
|
||||
REQUIRE(moved.size() == 1);
|
||||
CHECK(moved.frame_number() == 42);
|
||||
CHECK(moved[0].data[3] == 6);
|
||||
ClusterVector<C1> destination(std::move(source));
|
||||
|
||||
CHECK(destination.data() == source_data);
|
||||
CHECK(destination.capacity() == source_capacity);
|
||||
CHECK(destination.size() == 2);
|
||||
CHECK(destination.frame_number() == 123);
|
||||
CHECK(destination[0].x == 1);
|
||||
CHECK(destination[1].x == 7);
|
||||
}
|
||||
|
||||
TEST_CASE("Move assigning a ClusterVector transfers its storage") {
|
||||
ClusterVector<C1> source(4, 123);
|
||||
source.push_back(C1{1, 2, {3, 4, 5, 6}});
|
||||
source.push_back(C1{7, 8, {9, 10, 11, 12}});
|
||||
|
||||
const auto *source_data = source.data();
|
||||
const auto source_capacity = source.capacity();
|
||||
|
||||
ClusterVector<C1> destination(2, 456);
|
||||
destination.push_back(C1{13, 14, {15, 16, 17, 18}});
|
||||
destination = std::move(source);
|
||||
|
||||
CHECK(destination.data() == source_data);
|
||||
CHECK(destination.capacity() == source_capacity);
|
||||
CHECK(destination.size() == 2);
|
||||
CHECK(destination.frame_number() == 123);
|
||||
CHECK(destination[0].x == 1);
|
||||
CHECK(destination[1].x == 7);
|
||||
}
|
||||
|
||||
TEST_CASE("item_size return the size of the cluster stored") {
|
||||
@@ -256,6 +308,28 @@ TEST_CASE("Concatenate two cluster vectors where we need to allocate") {
|
||||
REQUIRE(ptr[3].y == 17);
|
||||
}
|
||||
|
||||
TEST_CASE("Reducing a ClusterVector preserves its frame number") {
|
||||
SECTION("Reduce to 2x2") {
|
||||
ClusterVector<Cluster<int32_t, 3, 3>> source(1, -135);
|
||||
source.push_back(Cluster<int32_t, 3, 3>{});
|
||||
|
||||
auto reduced = aare::reduce_to_2x2(source);
|
||||
|
||||
CHECK(reduced.size() == source.size());
|
||||
CHECK(reduced.frame_number() == source.frame_number());
|
||||
}
|
||||
|
||||
SECTION("Reduce to 3x3") {
|
||||
ClusterVector<Cluster<int32_t, 5, 5>> source(1, -246);
|
||||
source.push_back(Cluster<int32_t, 5, 5>{});
|
||||
|
||||
auto reduced = aare::reduce_to_3x3(source);
|
||||
|
||||
CHECK(reduced.size() == source.size());
|
||||
CHECK(reduced.frame_number() == source.frame_number());
|
||||
}
|
||||
}
|
||||
|
||||
struct ClusterTestData {
|
||||
uint8_t ClusterSizeX;
|
||||
uint8_t ClusterSizeY;
|
||||
|
||||
Reference in New Issue
Block a user