Dev/stuff from pyctbgui (#273)
Build on RHEL8 / build (push) Successful in 2m23s
Build on RHEL9 / build (push) Successful in 2m35s
Run tests using data on local RHEL8 / build (push) Failing after 3m19s

Matterhorn10 Transform 
some other Transformations from pyctbGUI 
added method get_reading_mode for easier error handling in decoders 


## TODO: 

- proper error handling for all other decoders 
- proper documentation for all other decoders 
- refactoring all other decoders to store hard coded values in a Struct
ChipSpecification
This commit is contained in:
2026-02-19 16:12:44 +01:00
committed by GitHub
parent 5dbc746462
commit 2139e5843c
19 changed files with 411 additions and 51 deletions
+84 -10
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
#include "aare/PixelMap.hpp"
#include "aare/defs.hpp"
#include <array>
@@ -31,6 +32,29 @@ NDArray<ssize_t, 2> GenerateMoench03PixelMap() {
return order_map;
}
NDArray<ssize_t, 2> GenerateMoench04AnalogPixelMap() {
std::array<int, 32> const adc_nr = Moench04::adcNumbers;
int const nadc = adc_nr.size();
NDArray<ssize_t, 2> order_map({Moench04::nRows, Moench04::nCols});
int pixel = 0;
for (size_t i = 0; i != Moench04::nPixelsPerSuperColumn; ++i) {
for (size_t i_adc = 0; i_adc != nadc; ++i_adc) {
int const col =
(adc_nr[i_adc] % 16) * 25 + (i % Moench04::superColumnWidth);
int row = 0;
if (i_adc < 16)
row = 199 - (i / Moench04::superColumnWidth);
else
row = 200 + (i / Moench04::superColumnWidth);
order_map(row, col) = pixel;
pixel++;
}
}
return order_map;
}
NDArray<ssize_t, 2> GenerateMoench05PixelMap() {
std::array<int, 3> adc_numbers = {5, 9, 1};
NDArray<ssize_t, 2> order_map({160, 150});
@@ -104,16 +128,18 @@ NDArray<ssize_t, 2> GenerateEigerFlipRowsPixelMap() {
return order_map;
}
// transceiver pixel map for Matterhorn02
NDArray<ssize_t, 2> GenerateMH02SingleCounterPixelMap() {
// This is the pixel map for a single counter Matterhorn02, i.e. 48x48
// pixels. Data is read from two transceivers in blocks of 4 pixels.
NDArray<ssize_t, 2> order_map({48, 48});
NDArray<ssize_t, 2> order_map({Matterhorn02::nRows, Matterhorn02::nCols});
size_t offset = 0;
size_t nSamples = 4;
for (int row = 0; row < 48; row++) {
for (int col = 0; col < 24; col++) {
for (int iTrans = 0; iTrans < 2; iTrans++) {
order_map(row, iTrans * 24 + col) = offset + nSamples * iTrans;
for (size_t row = 0; row < Matterhorn02::nRows; row++) {
for (size_t col = 0; col < Matterhorn02::nHalfCols; col++) {
for (size_t iTrans = 0; iTrans < 2; iTrans++) {
order_map(row, iTrans * Matterhorn02::nHalfCols + col) =
offset + nSamples * iTrans;
}
offset += 1;
if ((col + 1) % nSamples == 0) {
@@ -126,16 +152,64 @@ NDArray<ssize_t, 2> GenerateMH02SingleCounterPixelMap() {
NDArray<ssize_t, 3> GenerateMH02FourCounterPixelMap() {
auto single_counter_map = GenerateMH02SingleCounterPixelMap();
NDArray<ssize_t, 3> order_map({4, 48, 48});
for (int counter = 0; counter < 4; counter++) {
for (int row = 0; row < 48; row++) {
for (int col = 0; col < 48; col++) {
NDArray<ssize_t, 3> order_map(
{4, Matterhorn02::nRows, Matterhorn02::nCols});
for (size_t counter = 0; counter < 4; counter++) {
for (size_t row = 0; row < Matterhorn02::nRows; row++) {
for (size_t col = 0; col < Matterhorn02::nCols; col++) {
order_map(counter, row, col) =
single_counter_map(row, col) + counter * 48 * 48;
single_counter_map(row, col) +
counter * Matterhorn02::nRows * Matterhorn02::nCols;
}
}
}
return order_map;
}
NDArray<ssize_t, 2> GenerateMatterhorn10PixelMap(const size_t dynamic_range,
const size_t n_counters) {
// Matterhorn10 uses transceiver samples (each transceiver sample has 1-4
// channels storing 8 bytes each)
constexpr size_t n_cols = Matterhorn10::nCols;
constexpr size_t n_rows = Matterhorn10::nRows;
NDArray<ssize_t, 2> pixel_map(
{static_cast<ssize_t>(n_rows * n_counters), n_cols});
size_t num_consecutive_pixels{};
switch (dynamic_range) {
case 16:
num_consecutive_pixels = 4;
break;
case 8:
num_consecutive_pixels = 8;
break;
case 4:
num_consecutive_pixels = 16;
break;
default:
throw std::runtime_error("Unsupported dynamic range for Matterhorn02");
}
for (size_t row = 0; row < n_rows; ++row) {
for (size_t counter = 0; counter < n_counters; ++counter) {
size_t col = 0;
for (size_t offset = 0; offset < 64;
offset += num_consecutive_pixels) {
for (size_t pkg = offset; pkg < Matterhorn10::nCols;
pkg += 64) {
for (size_t pixel = 0; pixel < num_consecutive_pixels;
++pixel) {
pixel_map(row + counter * n_rows, col) =
pkg + pixel + row * n_cols * n_counters +
n_cols * counter;
++col;
}
}
}
}
}
return pixel_map;
}
} // namespace aare
+36 -18
View File
@@ -206,6 +206,30 @@ std::optional<ROI> RawMasterFile::roi() const {
std::optional<std::vector<ROI>> RawMasterFile::rois() const { return m_rois; }
ReadoutMode RawMasterFile::get_reading_mode() const {
if (m_type != DetectorType::ChipTestBoard &&
m_type != DetectorType::Xilinx_ChipTestBoard) {
LOG(TLogLevel::logINFO)
<< "reading mode is only available for CTB detectors.";
return ReadoutMode::UNKNOWN;
}
if (m_analog_flag && m_digital_flag) {
return ReadoutMode::ANALOG_AND_DIGITAL;
} else if (m_analog_flag) {
return ReadoutMode::ANALOG_ONLY;
} else if (m_digital_flag && m_transceiver_flag) {
return ReadoutMode::DIGITAL_AND_TRANSCEIVER;
} else if (m_digital_flag) {
return ReadoutMode::DIGITAL_ONLY;
} else if (m_transceiver_flag) {
return ReadoutMode::TRANSCEIVER_ONLY;
} else {
return ReadoutMode::UNKNOWN;
}
}
void RawMasterFile::parse_json(std::istream &is) {
json j;
is >> j;
@@ -216,10 +240,10 @@ void RawMasterFile::parse_json(std::istream &is) {
m_type = string_to<DetectorType>(j["Detector Type"].get<std::string>());
m_timing_mode = string_to<TimingMode>(j["Timing Mode"].get<std::string>());
m_geometry = {
j["Geometry"]["y"],
j["Geometry"]["x"]}; // TODO: isnt it only available for version > 7.1?
// - try block default should be 1x1
m_geometry = {j["Geometry"]["y"],
j["Geometry"]["x"]}; // TODO: isnt it only available for
// version > 7.1?
// - try block default should be 1x1
m_image_size_in_bytes =
v < 8.0 ? j["Image Size in bytes"] : j["Image Size"];
@@ -272,22 +296,15 @@ void RawMasterFile::parse_json(std::istream &is) {
// ----------------------------------------------------------------
// Special treatment of analog flag because of Moench03
m_analog_flag = v < 8.0 && (m_type == DetectorType::Moench);
try {
m_analog_flag = j.at("Analog Flag");
} catch (const json::out_of_range &e) {
// if it doesn't work still set it to one
// to try to decode analog samples (Old Moench03)
m_analog_flag = 1;
}
try {
m_analog_flag = static_cast<bool>(j.at("Analog Flag").get<int>());
if (m_analog_flag) {
m_analog_samples = j.at("Analog Samples");
}
} catch (const json::out_of_range &e) {
// keep the optional empty
// and set analog flag to 0
m_analog_flag = 0;
}
//-----------------------------------------------------------------
try {
@@ -302,7 +319,7 @@ void RawMasterFile::parse_json(std::istream &is) {
// m_adc_mask = 0;
// }
try {
int digital_flag = j.at("Digital Flag");
bool digital_flag = static_cast<bool>(j.at("Digital Flag").get<int>());
if (digital_flag) {
m_digital_samples = j.at("Digital Samples");
}
@@ -310,7 +327,8 @@ void RawMasterFile::parse_json(std::istream &is) {
// keep the optional empty
}
try {
m_transceiver_flag = j.at("Transceiver Flag");
m_transceiver_flag =
static_cast<bool>(j.at("Transceiver Flag").get<int>());
if (m_transceiver_flag) {
m_transceiver_samples = j.at("Transceiver Samples");
}
@@ -444,9 +462,9 @@ void RawMasterFile::parse_raw(std::istream &is) {
// } else if (key == "Number of rows"){
// m_number_of_rows = std::stoi(value);
} else if (key == "Analog Flag") {
m_analog_flag = std::stoi(value);
m_analog_flag = static_cast<bool>(std::stoi(value));
} else if (key == "Digital Flag") {
m_digital_flag = std::stoi(value);
m_digital_flag = static_cast<bool>(std::stoi(value));
} else if (key == "Analog Samples") {
if (m_analog_flag == 1) {
+5
View File
@@ -146,6 +146,8 @@ TEST_CASE("Parse a master file in .json format", "[.integration]") {
REQUIRE_FALSE(f.analog_samples());
REQUIRE_FALSE(f.digital_samples());
REQUIRE(f.get_reading_mode() == ReadoutMode::UNKNOWN);
}
TEST_CASE("Parse a master file in old .raw format",
@@ -211,6 +213,8 @@ TEST_CASE("Parse a master file in .raw format", "[.integration]") {
// Frames in File : 100
REQUIRE(f.frames_in_file() == 100);
REQUIRE(f.get_reading_mode() == ReadoutMode::ANALOG_AND_DIGITAL);
// #Frame Header
// Frame Number : 8 bytes
// SubFrame Number/ExpLength : 4 bytes
@@ -560,6 +564,7 @@ TEST_CASE("Parse a CTB file from stream") {
REQUIRE(f.digital_samples() == std::nullopt); // Digital Flag is 0
REQUIRE(f.transceiver_samples() == 1152);
REQUIRE(f.frames_in_file() == 40);
REQUIRE(f.get_reading_mode() == ReadoutMode::TRANSCEIVER_ONLY);
}
TEST_CASE("Parse v8.0 MYTHEN3 from stream") {
+18
View File
@@ -144,6 +144,24 @@ uint32_t mask32to24bits(uint32_t input, BitOffset offset) {
return (input >> offset.value()) & mask24bits;
}
void expand4to8bit(NDView<uint8_t, 1> input, NDView<uint8_t, 1> output) {
if (2 * input.size() != output.size())
throw std::runtime_error(
fmt::format("Mismatch between input and output size. Input "
"size of {} requires an output of at least {} "
"bytes. Called with input size: {} output size: {}",
LOCATION, input.size(), 2 * input.size(), input.size(),
output.size()));
// assumes little-endian
for (ssize_t i = 0; i < input.size(); ++i) {
uint8_t val = input(i);
output[2 * i] = (val & 0x0F);
output[2 * i + 1] = (val & 0xF0) >> 4;
}
}
void expand24to32bit(NDView<uint8_t, 1> input, NDView<uint32_t, 1> output,
BitOffset bit_offset) {
+20
View File
@@ -152,4 +152,24 @@ TEST_CASE("Expand container with 24 bit data to 32") {
CHECK(out(1) == 0xFFF);
CHECK(out(2) == 0xFF0);
}
}
TEST_CASE("Expand 4 bit values packed into 8 bit to 8 bit values") {
{
uint8_t buffer[] = {
0x00, 0xF0, 0xFF, 0x00, 0xF0, 0xFF,
};
aare::NDView<uint8_t, 1> input(&buffer[0], {6});
aare::NDArray<uint8_t, 1> out({12});
aare::expand4to8bit(input, out.view());
uint8_t expected_output[] = {
0x0, 0x0, 0x0, 0xF, 0xF, 0xF,
0x0, 0x0, 0x0, 0xF, 0xF, 0xF}; // assuming little endian
for (size_t i = 0; i < 12; ++i) {
CHECK(out(i) == expected_output[i]);
}
}
}