Added parsing of exptime and period from master files (#256)
All checks were successful
Build on RHEL9 / build (push) Successful in 3m26s
Build on RHEL8 / build (push) Successful in 3m33s

- New aare:to_string/string_to similar to what we have in
slsDetectorPackage
- Added members period and exptime to RawMasterFile
- Parsing exposure time and period for json and raw master file formats
- Parsing of RawMasterFile from string stream to enable test without
files

Comments:

- to_string is at the moment not a public header. Can make it later if
needed. This gives us full freedom with the API
- FileConfig should probably be deprecated need to look into it.
Meanwhile removed python bindings and string conv
This commit is contained in:
Erik Fröjdh
2025-12-18 17:04:12 +01:00
committed by GitHub
parent fb95e518b4
commit 7f3123d68f
15 changed files with 1185 additions and 437 deletions

View File

@@ -4,6 +4,7 @@
#include "test_config.hpp"
#include <catch2/catch_test_macros.hpp>
#include <iostream>
#include <sstream>
using namespace aare;
@@ -164,7 +165,7 @@ TEST_CASE("Parse a master file in .raw format", "[.integration]") {
auto fpath =
test_data_path() /
"moench/"
"raw/moench04/"
"moench04_noise_200V_sto_both_100us_no_light_thresh_900_master_0.raw";
REQUIRE(std::filesystem::exists(fpath));
RawMasterFile f(fpath);
@@ -194,7 +195,9 @@ TEST_CASE("Parse a master file in .raw format", "[.integration]") {
// Total Frames : 100
REQUIRE(f.total_frames_expected() == 100);
// Exptime : 100us
REQUIRE(f.exptime() == std::chrono::microseconds(100));
// Period : 4ms
REQUIRE(f.period() == std::chrono::milliseconds(4));
// Ten Giga : 1
// ADC Mask : 0xffffffff
// Analog Flag : 1
@@ -255,13 +258,13 @@ TEST_CASE("Parse a master file in new .json format",
auto roi = f.roi().value();
REQUIRE(roi.xmin == 0);
REQUIRE(roi.xmax == 2559);
REQUIRE(roi.ymin == -1);
REQUIRE(roi.ymax == -1);
REQUIRE(roi.xmax == 2560);
REQUIRE(roi.ymin == 0);
REQUIRE(roi.ymax == 1);
}
TEST_CASE("Read eiger master file", "[.integration]") {
auto fpath = test_data_path() / "eiger" / "eiger_500k_32bit_master_0.json";
auto fpath = test_data_path() / "raw/eiger/eiger_500k_32bit_master_0.json";
REQUIRE(std::filesystem::exists(fpath));
RawMasterFile f(fpath);
@@ -302,8 +305,10 @@ TEST_CASE("Read eiger master file", "[.integration]") {
// },
// "Dynamic Range": 32,
// "Ten Giga": 0,
// "Exptime": "5s",
// "Exptime": "5s",
REQUIRE(f.exptime() == std::chrono::seconds(5));
// "Period": "1s",
REQUIRE(f.period() == std::chrono::seconds(1));
// "Threshold Energy": -1,
// "Sub Exptime": "2.62144ms",
// "Sub Period": "2.62144ms",
@@ -329,3 +334,392 @@ TEST_CASE("Read eiger master file", "[.integration]") {
// }
// }
}
TEST_CASE("Parse EIGER 7.2 master from string stream") {
std::string master_content = R"({
"Version": 7.2,
"Timestamp": "Tue Mar 26 17:24:34 2024",
"Detector Type": "Eiger",
"Timing Mode": "auto",
"Geometry": {
"x": 2,
"y": 2
},
"Image Size in bytes": 524288,
"Pixels": {
"x": 512,
"y": 256
},
"Max Frames Per File": 10000,
"Frame Discard Policy": "nodiscard",
"Frame Padding": 1,
"Scan Parameters": "[disabled]",
"Total Frames": 3,
"Receiver Roi": {
"xmin": 4294967295,
"xmax": 4294967295,
"ymin": 4294967295,
"ymax": 4294967295
},
"Dynamic Range": 32,
"Ten Giga": 0,
"Exptime": "5s",
"Period": "1s",
"Threshold Energy": -1,
"Sub Exptime": "2.62144ms",
"Sub Period": "2.62144ms",
"Quad": 0,
"Number of rows": 256,
"Rate Corrections": "[0, 0]",
"Frames in File": 3,
"Frame Header Format": {
"Frame Number": "8 bytes",
"SubFrame Number/ExpLength": "4 bytes",
"Packet Number": "4 bytes",
"Bunch ID": "8 bytes",
"Timestamp": "8 bytes",
"Module Id": "2 bytes",
"Row": "2 bytes",
"Column": "2 bytes",
"Reserved": "2 bytes",
"Debug": "4 bytes",
"Round Robin Number": "2 bytes",
"Detector Type": "1 byte",
"Header Version": "1 byte",
"Packets Caught Mask": "64 bytes"
}
})";
std::istringstream iss(master_content);
RawMasterFile f(iss, "test_master_0.json");
REQUIRE(f.version() == "7.2");
REQUIRE(f.detector_type() == DetectorType::Eiger);
REQUIRE(f.timing_mode() == TimingMode::Auto);
REQUIRE(f.geometry().col == 2);
REQUIRE(f.geometry().row == 2);
REQUIRE(f.image_size_in_bytes() == 524288);
REQUIRE(f.pixels_x() == 512);
REQUIRE(f.pixels_y() == 256);
REQUIRE(f.max_frames_per_file() == 10000);
REQUIRE(f.frame_discard_policy() == FrameDiscardPolicy::NoDiscard);
REQUIRE(f.frame_padding() == 1);
REQUIRE(f.total_frames_expected() == 3);
REQUIRE(f.exptime() == std::chrono::seconds(5));
REQUIRE(f.period() == std::chrono::seconds(1));
}
TEST_CASE("Parse JUNGFRAU 7.2 master from string stream") {
std::string master_content = R"({
"Version": 7.2,
"Timestamp": "Tue Feb 20 08:29:19 2024",
"Detector Type": "Jungfrau",
"Timing Mode": "auto",
"Geometry": {
"x": 1,
"y": 2
},
"Image Size in bytes": 524288,
"Pixels": {
"x": 1024,
"y": 256
},
"Max Frames Per File": 3,
"Frame Discard Policy": "nodiscard",
"Frame Padding": 1,
"Scan Parameters": "[disabled]",
"Total Frames": 10,
"Receiver Roi": {
"xmin": 4294967295,
"xmax": 4294967295,
"ymin": 4294967295,
"ymax": 4294967295
},
"Exptime": "10us",
"Period": "1ms",
"Number of UDP Interfaces": 2,
"Number of rows": 512,
"Frames in File": 10,
"Frame Header Format": {
"Frame Number": "8 bytes",
"SubFrame Number/ExpLength": "4 bytes",
"Packet Number": "4 bytes",
"Bunch ID": "8 bytes",
"Timestamp": "8 bytes",
"Module Id": "2 bytes",
"Row": "2 bytes",
"Column": "2 bytes",
"Reserved": "2 bytes",
"Debug": "4 bytes",
"Round Robin Number": "2 bytes",
"Detector Type": "1 byte",
"Header Version": "1 byte",
"Packets Caught Mask": "64 bytes"
}
})";
std::istringstream iss(master_content);
RawMasterFile f(iss, "test_master_0.json");
REQUIRE(f.version() == "7.2");
REQUIRE(f.detector_type() == DetectorType::Jungfrau);
REQUIRE(f.timing_mode() == TimingMode::Auto);
REQUIRE(f.geometry().col == 1);
REQUIRE(f.geometry().row == 2);
REQUIRE(f.n_modules() == 2);
REQUIRE(f.image_size_in_bytes() == 524288);
REQUIRE(f.pixels_x() == 1024);
REQUIRE(f.pixels_y() == 256);
REQUIRE(f.max_frames_per_file() == 3);
REQUIRE(f.frame_discard_policy() == FrameDiscardPolicy::NoDiscard);
REQUIRE(f.frame_padding() == 1);
REQUIRE(f.total_frames_expected() == 10);
REQUIRE(f.exptime() == std::chrono::microseconds(10));
REQUIRE(f.period() == std::chrono::milliseconds(1));
REQUIRE(f.number_of_rows() == 512);
REQUIRE(f.frames_in_file() == 10);
REQUIRE(f.udp_interfaces_per_module() == xy{2, 1});
}
TEST_CASE("Parse a CTB file from stream"){
std::string master_content = R"({
"Version": 8.0,
"Timestamp": "Mon Dec 15 10:57:27 2025",
"Detector Type": "ChipTestBoard",
"Timing Mode": "auto",
"Geometry": {
"x": 1,
"y": 1
},
"Image Size": 18432,
"Pixels": {
"x": 2,
"y": 1
},
"Max Frames Per File": 20000,
"Frame Discard Policy": "nodiscard",
"Frame Padding": 1,
"Scan Parameters": {
"enable": 0,
"dacInd": 0,
"start offset": 0,
"stop offset": 0,
"step size": 0,
"dac settle time ns": 0
},
"Total Frames": 1,
"Exposure Time": "0.25s",
"Acquisition Period": "10ms",
"Ten Giga": 1,
"ADC Mask": 4294967295,
"Analog Flag": 0,
"Analog Samples": 1,
"Digital Flag": 0,
"Digital Samples": 1,
"Dbit Offset": 0,
"Dbit Reorder": 1,
"Dbit Bitset": 0,
"Transceiver Mask": 3,
"Transceiver Flag": 1,
"Transceiver Samples": 1152,
"Frames in File": 40,
"Additional JSON Header": {}
})";
std::istringstream iss(master_content);
RawMasterFile f(iss, "test_master_0.json");
REQUIRE(f.version() == "8.0");
REQUIRE(f.detector_type() == DetectorType::ChipTestBoard);
REQUIRE(f.timing_mode() == TimingMode::Auto);
REQUIRE(f.geometry().col == 1);
REQUIRE(f.geometry().row == 1);
REQUIRE(f.image_size_in_bytes() == 18432);
REQUIRE(f.pixels_x() == 2);
REQUIRE(f.pixels_y() == 1);
REQUIRE(f.max_frames_per_file() == 20000);
// CTB does not have bitdepth in master file, but for the moment we write 16
// TODO! refactor using std::optional
// REQUIRE(f.bitdepth() == std::nullopt);
REQUIRE(f.n_modules() == 1);
REQUIRE(f.quad() == 0);
REQUIRE(f.frame_discard_policy() == FrameDiscardPolicy::NoDiscard);
REQUIRE(f.frame_padding() == 1);
REQUIRE(f.total_frames_expected() == 1); //This is Total Frames in the master file
REQUIRE(f.exptime() == std::chrono::milliseconds(250));
REQUIRE(f.period() == std::chrono::milliseconds(10));
REQUIRE(f.analog_samples() == std::nullopt); //Analog Flag is 0
REQUIRE(f.digital_samples() == std::nullopt); //Digital Flag is 0
REQUIRE(f.transceiver_samples() == 1152);
REQUIRE(f.frames_in_file() == 40);
}
TEST_CASE("Parse v8.0 MYTHEN3 from stream"){
std::string master_content = R"({
"Version": 8.0,
"Timestamp": "Wed Oct 1 14:37:26 2025",
"Detector Type": "Mythen3",
"Timing Mode": "auto",
"Geometry": {
"x": 2,
"y": 1
},
"Image Size": 5120,
"Pixels": {
"x": 1280,
"y": 1
},
"Max Frames Per File": 10000,
"Frame Discard Policy": "nodiscard",
"Frame Padding": 1,
"Scan Parameters": {
"enable": 0,
"dacInd": 0,
"start offset": 0,
"stop offset": 0,
"step size": 0,
"dac settle time ns": 0
},
"Total Frames": 1,
"Receiver Rois": [
{
"xmin": 0,
"xmax": 2559,
"ymin": -1,
"ymax": -1
}
],
"Dynamic Range": 32,
"Ten Giga": 1,
"Acquisition Period": "0ns",
"Counter Mask": 4,
"Exposure Times": [
"5s",
"5s",
"5s"
],
"Gate Delays": [
"0ns",
"0ns",
"0ns"
],
"Gates": 1,
"Threshold Energies": [
-1,
-1,
-1
],
"Readout Speed": "half_speed",
"Frames in File": 1,
"Additional JSON Header": {}
})";
std::istringstream iss(master_content);
RawMasterFile f(iss, "test_master_0.json");
REQUIRE(f.version() == "8.0");
REQUIRE(f.detector_type() == DetectorType::Mythen3);
REQUIRE(f.timing_mode() == TimingMode::Auto);
REQUIRE(f.geometry().col == 2);
REQUIRE(f.geometry().row == 1);
REQUIRE(f.image_size_in_bytes() == 5120);
REQUIRE(f.pixels_x() == 1280);
REQUIRE(f.pixels_y() == 1);
REQUIRE(f.max_frames_per_file() == 10000);
REQUIRE(f.n_modules() == 2);
REQUIRE(f.quad() == 0);
REQUIRE(f.frame_discard_policy() == FrameDiscardPolicy::NoDiscard);
REQUIRE(f.frame_padding() == 1);
REQUIRE(f.total_frames_expected() == 1); //This is Total Frames in the master file
REQUIRE(f.counter_mask() == 4);
// Mythen3 has three exposure times, but for the moment we don't handle them
REQUIRE(f.exptime() == std::nullopt);
// Period is ok though
REQUIRE(f.period() == std::chrono::nanoseconds(0));
}
TEST_CASE("Parse a v7.1 Mythen3 from stream"){
std::string master_content = R"({
"Version": 7.1,
"Timestamp": "Wed Sep 21 13:48:10 2022",
"Detector Type": "Mythen3",
"Timing Mode": "auto",
"Geometry": {
"x": 1,
"y": 1
},
"Image Size in bytes": 15360,
"Pixels": {
"x": 3840,
"y": 1
},
"Max Frames Per File": 10000,
"Frame Discard Policy": "nodiscard",
"Frame Padding": 1,
"Scan Parameters": "[disabled]",
"Total Frames": 1,
"Receiver Roi": {
"xmin": 4294967295,
"xmax": 4294967295,
"ymin": 4294967295,
"ymax": 4294967295
},
"Dynamic Range": 32,
"Ten Giga": 1,
"Period": "2ms",
"Counter Mask": "0x7",
"Exptime1": "0.1s",
"Exptime2": "0.1s",
"Exptime3": "0.1s",
"GateDelay1": "0ns",
"GateDelay2": "0ns",
"GateDelay3": "0ns",
"Gates": 1,
"Threshold Energies": "[-1, -1, -1]",
"Frames in File": 1,
"Frame Header Format": {
"Frame Number": "8 bytes",
"SubFrame Number/ExpLength": "4 bytes",
"Packet Number": "4 bytes",
"Bunch ID": "8 bytes",
"Timestamp": "8 bytes",
"Module Id": "2 bytes",
"Row": "2 bytes",
"Column": "2 bytes",
"Reserved": "2 bytes",
"Debug": "4 bytes",
"Round Robin Number": "2 bytes",
"Detector Type": "1 byte",
"Header Version": "1 byte",
"Packets Caught Mask": "64 bytes"
}
})";
std::istringstream iss(master_content);
RawMasterFile f(iss, "test_master_0.json");
REQUIRE(f.version() == "7.1");
REQUIRE(f.detector_type() == DetectorType::Mythen3);
REQUIRE(f.timing_mode() == TimingMode::Auto);
REQUIRE(f.geometry().col == 1);
REQUIRE(f.geometry().row == 1);
REQUIRE(f.image_size_in_bytes() == 15360);
REQUIRE(f.pixels_x() == 3840);
REQUIRE(f.pixels_y() == 1);
REQUIRE(f.max_frames_per_file() == 10000);
REQUIRE(f.n_modules() == 1);
REQUIRE(f.quad() == 0);
REQUIRE(f.frame_discard_policy() == FrameDiscardPolicy::NoDiscard);
REQUIRE(f.frame_padding() == 1);
REQUIRE(f.total_frames_expected() == 1); //This is Total Frames in the master file
REQUIRE(f.counter_mask() == 0x7);
// Mythen3 has three exposure times, but for the moment we don't handle them
REQUIRE(f.exptime() == std::nullopt);
// Period is ok though
REQUIRE(f.period() == std::chrono::milliseconds(2));
}