From ee7503082d0de65fc917426b4585acd58fd28ff1 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Thu, 11 Jun 2026 15:04:20 +0200 Subject: [PATCH] Dev/matterhorn decoder (#324) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reshape image directly in decoder such that first dimension is num counters - take into account chip artefact in decoder. --------- Co-authored-by: Erik Fröjdh --- RELEASE.md | 3 +++ python/aare/transform.py | 12 ++++++++---- python/tests/test_Transformation.py | 17 ++++++++--------- src/PixelMap.cpp | 6 +++++- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 5982634..e07e9a1 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -10,6 +10,8 @@ - mask opeartor for ClusterVector ``masked_clustervector = aare.ClusterVector()(mask)`` - passing pre computed eta values to ``aare.Interpolator.interpolate`` alongside clusters - Added ``PixelHistogram`` and ``PedestalTrackingPixelHistogram`` +- ``aare.transfrom.Matterhorn10Transform`` handles counter artefact in chip. Mind that enabling only one counter or three counters or enabling the wromg two counters e.g. 0,1 will still lead to erreneous data. +- ``aare.transfrom.Matterhorn10Transform`` reshapes data such that first dimension is number of counters ### Bugfixes: @@ -18,6 +20,7 @@ - Fixed libfmt failures due to consteval when building with C++20 + ## 2026.3.17 ### New Features: diff --git a/python/aare/transform.py b/python/aare/transform.py index 50435d0..45cc780 100644 --- a/python/aare/transform.py +++ b/python/aare/transform.py @@ -81,6 +81,10 @@ class Matterhorn10Transform: A matterhorn chip has 256 columns and 256 rows. A matterhornchip with dynamic range 16 and 2 counters thus requires 256*256*16*2/(2*64) = 1024 transceiver samples. (Per default 2 channels are enabled per transceiver sample, each channel storing 64 bits) + + .. note:: + Due to an artefact in the chip, the transformation only fully supports 2 or 4 counters. Also if you enable 2 counters you can only select counter 1 and 2 or 0, 3 to get reasonable results. + Otherwise only the first half of the image is correct. """ def __init__(self, dynamic_range : int, num_counters : int): self.pixel_map = _aare.GenerateMatterhorn10PixelMap(dynamic_range, num_counters) @@ -105,7 +109,7 @@ class Matterhorn10Transform: checks if data is compatible for transformation :param data: data to be transformed, expected to be a 1D numpy array of uint8 - :type data: np.ndarray + :type data: np.ndarray(n_counters, n_rows, n_cols) :raises ValueError: if not compatible """ expected_size = (Matterhorn10.nRows*Matterhorn10.nCols*self.num_counters*self.dynamic_range)//8 # read_frame returns data in uint8_t @@ -118,11 +122,11 @@ class Matterhorn10Transform: def __call__(self, data): self.data_compatibility(data) if self.dynamic_range == 16: - return np.take(data.view(np.uint16), self.pixel_map) + return np.take(data.view(np.uint16), self.pixel_map).reshape(self.num_counters, Matterhorn10.nRows, Matterhorn10.nCols) elif self.dynamic_range == 8: - return np.take(data.view(np.uint8), self.pixel_map) + return np.take(data.view(np.uint8), self.pixel_map).reshape(self.num_counters, Matterhorn10.nRows, Matterhorn10.nCols) else: #dynamic range 4 - return np.take(_aare.expand4to8bit(data.view(np.uint8)), self.pixel_map) + return np.take(_aare.expand4to8bit(data.view(np.uint8)), self.pixel_map).reshape(self.num_counters, Matterhorn10.nRows, Matterhorn10.nCols) class Mythen302Transform: """ diff --git a/python/tests/test_Transformation.py b/python/tests/test_Transformation.py index 192083e..2b38108 100644 --- a/python/tests/test_Transformation.py +++ b/python/tests/test_Transformation.py @@ -8,10 +8,10 @@ def test_matterhorn10_16bit(test_data_path): with CtbRawFile(test_data_path / "raw/Matterhorn10/16bit_master_0.json", transform = transform.Matterhorn10Transform(dynamic_range=16, num_counters=1)) as f: headers, frames = f.read_frame() - assert frames.shape == (256, 256) + assert frames.shape == (1, 256, 256) assert frames.dtype == np.uint16 - expected_data = np.tile(np.arange(255, -1, -1,dtype=np.uint16), (256, 1)) # TODO: endianess issue ? + expected_data = np.tile(np.arange(255, -1, -1,dtype=np.uint16), (1, 256, 1)) # TODO: endianess issue ? assert np.all(frames == expected_data) @@ -22,24 +22,23 @@ def test_matterhorn10_8bit(test_data_path): with CtbRawFile(test_data_path / "raw/Matterhorn10/8bit_master_1.json", transform = transform.Matterhorn10Transform(dynamic_range=8, num_counters=1)) as f: headers, frames = f.read_frame() - assert frames.shape == (256, 256) + assert frames.shape == (1, 256, 256) assert frames.dtype == np.uint8 - expected_data = np.tile(np.arange(255, -1, -1,dtype=np.uint8), (256, 1)) # TODO: endianess issue ? + expected_data = np.tile(np.arange(255, -1, -1,dtype=np.uint8), (1, 256, 1)) # TODO: endianess issue ? assert np.all(frames == expected_data) - @pytest.mark.withdata def test_matterhorn10_4bit(test_data_path): """ Matterhorn10Transform 1 counter 4 bit dynamic range """ with CtbRawFile(test_data_path / "raw/Matterhorn10/newnewrun_4bit_1counter_master_0.json", transform = transform.Matterhorn10Transform(dynamic_range=4, num_counters=1)) as f: headers, frames = f.read_frame() - assert frames.shape == (256, 256) + assert frames.shape == (1, 256, 256) assert frames.dtype == np.uint8 - expected_data = np.tile(np.tile(np.arange(15, -1, -1, dtype=np.uint8), 16), (256, 1)) # TODO: endianess issue ? + expected_data = np.tile(np.tile(np.arange(15, -1, -1, dtype=np.uint8), 16), (1, 256, 1)) # TODO: endianess issue ? assert np.all(frames == expected_data) @@ -50,9 +49,9 @@ def test_matterhorn10_16bit_4counters(test_data_path): with CtbRawFile(test_data_path / "raw/Matterhorn10/4counter_16bit_master_4.json", transform = transform.Matterhorn10Transform(dynamic_range=16, num_counters=4)) as f: headers, frames = f.read_frame() - assert frames.shape == (4*256, 256) + assert frames.shape == (4, 256, 256) assert frames.dtype == np.uint16 - expected_data = np.tile(np.arange(255, -1, -1,dtype=np.uint16), (4*256, 1)) # TODO: endianess issue ? + expected_data = np.tile(np.arange(255, -1, -1,dtype=np.uint16), (4, 256, 1)) # TODO: endianess issue ? assert np.all(frames == expected_data) diff --git a/src/PixelMap.cpp b/src/PixelMap.cpp index 6713cca..ab8d3ee 100644 --- a/src/PixelMap.cpp +++ b/src/PixelMap.cpp @@ -195,12 +195,16 @@ NDArray GenerateMatterhorn10PixelMap(const size_t dynamic_range, constexpr size_t num_64_bit_packages = 4; // n_cols/64 + constexpr size_t half_rows = n_rows / 2; // 128 + const size_t num_consecutive_pixels_in_package = packet_size / num_consecutive_pixels; for (size_t row = 0; row < n_rows; ++row) { for (size_t counter = 0; counter < n_counters; ++counter) { size_t col = 0; + size_t actual_counter = + row < half_rows ? counter : (n_counters - counter - 1); for (size_t package = 0; package < num_64_bit_packages; ++package) { for (size_t consecutive_pixel_group = 0; consecutive_pixel_group < @@ -213,7 +217,7 @@ NDArray GenerateMatterhorn10PixelMap(const size_t dynamic_range, consecutive_pixel_group * num_64_bit_packages * num_consecutive_pixels + pixel + row * n_cols * n_counters + - n_cols * counter; + n_cols * actual_counter; ++col; } }