From 93eb41e1702f91931f2418e55a7ed03476c8e85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Tue, 1 Sep 2026 11:29:54 +0200 Subject: [PATCH] frame nr and checks on mask --- RELEASE.md | 5 +++-- include/aare/ClusterVector.hpp | 6 ++++-- python/src/bind_ClusterVector.hpp | 8 ++++++-- python/tests/test_ClusterVector.py | 33 ++++++++++++++++++++++++++++-- src/ClusterVector.test.cpp | 22 ++++++++++++++++++++ 5 files changed, 66 insertions(+), 8 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index c5b0c1f8..01d2fed1 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -19,6 +19,9 @@ ### 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 ``ClusterVector`` frame numbers when reducing cluster dimensions. - Fixed broken reading of old (pre reordering) Moench03 ## 2026.7.2 @@ -168,5 +171,3 @@ dhanya.thattil@psi.ch - - diff --git a/include/aare/ClusterVector.hpp b/include/aare/ClusterVector.hpp index 5d44666c..a5b0f3cc 100644 --- a/include/aare/ClusterVector.hpp +++ b/include/aare/ClusterVector.hpp @@ -194,7 +194,8 @@ template > reduce_to_2x2( const ClusterVector> &cv) { - ClusterVector> result; + ClusterVector> result(cv.size(), + cv.frame_number()); for (const auto &c : cv) { result.push_back(reduce_to_2x2(c)); } @@ -211,7 +212,8 @@ template > reduce_to_3x3( const ClusterVector> &cv) { - ClusterVector> result; + ClusterVector> result(cv.size(), + cv.frame_number()); for (const auto &c : cv) { result.push_back(reduce_to_3x3(c)); } diff --git a/python/src/bind_ClusterVector.hpp b/python/src/bind_ClusterVector.hpp index a3d59f57..e6494dbf 100644 --- a/python/src/bind_ClusterVector.hpp +++ b/python/src/bind_ClusterVector.hpp @@ -37,10 +37,14 @@ void define_ClusterVector(py::module &m, const std::string &typestr) { .def( "__call__", - [](ClusterVector &self, py::array_t mask) { + [](ClusterVector &self, + py::array_t mask) { + if (mask.ndim() != 1) { + throw py::value_error("Mask must be one-dimensional"); + } return self(make_view_1d(mask)); }, - py::arg("mask"), R"( + py::arg("mask").noconvert(), R"( Create a copy of the clustervector and apply a boolean mask to the ClusterVector. Parameters diff --git a/python/tests/test_ClusterVector.py b/python/tests/test_ClusterVector.py index 30f934fa..aa7113bc 100644 --- a/python/tests/test_ClusterVector.py +++ b/python/tests/test_ClusterVector.py @@ -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) diff --git a/src/ClusterVector.test.cpp b/src/ClusterVector.test.cpp index 98f0c2b3..3905efd4 100644 --- a/src/ClusterVector.test.cpp +++ b/src/ClusterVector.test.cpp @@ -271,6 +271,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> source(1, 135); + source.push_back(Cluster{}); + + auto reduced = aare::reduce_to_2x2(source); + + CHECK(reduced.size() == source.size()); + CHECK(reduced.frame_number() == source.frame_number()); + } + + SECTION("Reduce to 3x3") { + ClusterVector> source(1, 246); + source.push_back(Cluster{}); + + 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;