From a8225faa1790527d3022d815427f6067cc862124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Thu, 18 Dec 2025 13:50:25 +0100 Subject: [PATCH] added parsing of period and exptime --- include/aare/RawMasterFile.hpp | 7 ++ python/src/raw_master_file.hpp | 10 ++- src/RawMasterFile.cpp | 10 +++ src/RawMasterFile.test.cpp | 6 +- src/to_string.cpp | 36 ++++++++++ src/to_string.hpp | 44 +++++++++++- src/to_string.test.cpp | 119 +++++++++++++++++++++++++++++++++ 7 files changed, 229 insertions(+), 3 deletions(-) diff --git a/include/aare/RawMasterFile.hpp b/include/aare/RawMasterFile.hpp index 16eb1ca..2cffc13 100644 --- a/include/aare/RawMasterFile.hpp +++ b/include/aare/RawMasterFile.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include using json = nlohmann::json; @@ -84,6 +85,9 @@ class RawMasterFile { size_t m_bitdepth{}; uint8_t m_quad = 0; + std::chrono::nanoseconds m_exptime{0}; + std::chrono::nanoseconds m_period{0}; + xy m_geometry{}; xy m_udp_interfaces_per_module{1, 1}; @@ -140,6 +144,9 @@ class RawMasterFile { ScanParameters scan_parameters() const; + std::chrono::nanoseconds exptime() const { return m_exptime; } + std::chrono::nanoseconds period() const { return m_period; } + private: void parse_json(const std::filesystem::path &fpath); void parse_raw(const std::filesystem::path &fpath); diff --git a/python/src/raw_master_file.hpp b/python/src/raw_master_file.hpp index bcd440c..62653f4 100644 --- a/python/src/raw_master_file.hpp +++ b/python/src/raw_master_file.hpp @@ -85,5 +85,13 @@ void define_raw_master_file_bindings(py::module &m) { .def_property_readonly("quad", &RawMasterFile::quad) .def_property_readonly("scan_parameters", &RawMasterFile::scan_parameters) - .def_property_readonly("roi", &RawMasterFile::roi); + .def_property_readonly("roi", &RawMasterFile::roi) + .def_property_readonly("exptime", [](RawMasterFile &self) { + double seconds = std::chrono::duration(self.exptime()).count(); + return seconds; + }) + .def_property_readonly("period", [](RawMasterFile &self) { + double seconds = std::chrono::duration(self.period()).count(); + return seconds; + }); } diff --git a/src/RawMasterFile.cpp b/src/RawMasterFile.cpp index 18bc51d..d10ee1d 100644 --- a/src/RawMasterFile.cpp +++ b/src/RawMasterFile.cpp @@ -206,6 +206,12 @@ void RawMasterFile::parse_json(const std::filesystem::path &fpath) { m_max_frames_per_file = j["Max Frames Per File"]; + m_exptime = string_to( + j["Exptime"].get()); + + m_period = string_to( + j["Period"].get()); + // Not all detectors write the bitdepth but in case // its not there it is 16 try { @@ -431,6 +437,10 @@ void RawMasterFile::parse_raw(const std::filesystem::path &fpath) { m_pixels_x = std::stoi(value.substr(0, pos)); } else if (key == "Total Frames") { m_total_frames_expected = std::stoi(value); + } else if(key == "Exptime"){ + m_exptime = string_to(value); + } else if(key == "Period"){ + m_period = string_to(value); } else if (key == "Dynamic Range") { m_bitdepth = std::stoi(value); } else if (key == "Quad") { diff --git a/src/RawMasterFile.test.cpp b/src/RawMasterFile.test.cpp index 0398e03..b8b4095 100644 --- a/src/RawMasterFile.test.cpp +++ b/src/RawMasterFile.test.cpp @@ -194,7 +194,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 @@ -302,8 +304,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", diff --git a/src/to_string.cpp b/src/to_string.cpp index dcf6ddc..9a32eae 100644 --- a/src/to_string.cpp +++ b/src/to_string.cpp @@ -237,4 +237,40 @@ template <> DACIndex string_to(const std::string &arg) { } +std::string remove_unit(std::string &str) { + auto it = str.begin(); + while (it != str.end()) { + if (std::isalpha(*it)) { + // Check if this is scientific notation (e or E followed by optional sign and digits) + if (((*it == 'e' || *it == 'E') && (it + 1) != str.end())) { + auto next = it + 1; + // Skip optional sign + if (*next == '+' || *next == '-') { + ++next; + } + // Check if followed by at least one digit + if (next != str.end() && std::isdigit(*next)) { + // This is scientific notation, continue scanning + it = next; + while (it != str.end() && std::isdigit(*it)) { + ++it; + } + continue; + } + } + // Not scientific notation, this is the start of the unit + break; + } + ++it; + } + auto pos = it - str.begin(); + auto unit = str.substr(pos); + str.erase(it, end(str)); + // Strip trailing whitespace + while (!str.empty() && std::isspace(str.back())) { + str.pop_back(); + } + return unit; +} + } // namespace aare \ No newline at end of file diff --git a/src/to_string.hpp b/src/to_string.hpp index 128d9ba..6d13b54 100644 --- a/src/to_string.hpp +++ b/src/to_string.hpp @@ -8,10 +8,46 @@ #include "aare/defs.hpp" //enums +#include //time conversions + namespace aare { + +std::string remove_unit(std::string &str); + +template +T string_to(const std::string &t, const std::string &unit) { + double tval{0}; + try { + tval = std::stod(t); + } catch (const std::invalid_argument &e) { + throw std::runtime_error("Could not convert string to time"); + } + + using std::chrono::duration; + using std::chrono::duration_cast; + if (unit == "ns") { + return duration_cast(duration(tval)); + } else if (unit == "us") { + return duration_cast(duration(tval)); + } else if (unit == "ms") { + return duration_cast(duration(tval)); + } else if (unit == "s" || unit.empty()) { + return duration_cast(std::chrono::duration(tval)); + } else { + throw std::runtime_error( + "Invalid unit in conversion from string to std::chrono::duration"); + } +} + + // if T has a constructor that takes a string, lets use it. -template T string_to(const std::string &arg) { return T{arg}; } +// template T string_to(const std::string &arg) { return T{arg}; } +template T string_to(const std::string &arg) { + std::string tmp{arg}; + auto unit = remove_unit(tmp); + return string_to(tmp, unit); +} /** * @brief Convert a string to DetectorType @@ -45,4 +81,10 @@ template <> FrameDiscardPolicy string_to(const std::string &arg); */ template <> DACIndex string_to(const std::string &arg); + + + + + + } // namespace aare \ No newline at end of file diff --git a/src/to_string.test.cpp b/src/to_string.test.cpp index b245840..351acf7 100644 --- a/src/to_string.test.cpp +++ b/src/to_string.test.cpp @@ -144,4 +144,123 @@ TEST_CASE("DACIndex string to enum") { REQUIRE(string_to("temp_slowadc") == aare::DACIndex::SLOW_ADC_TEMP); REQUIRE_THROWS(string_to("invalid_dac")); +} + +TEST_CASE("Remove unit from string") { + using aare::remove_unit; + + // Test basic numeric value with unit + { + std::string input = "123.45 V"; + std::string unit = remove_unit(input); + REQUIRE(unit == "V"); + REQUIRE(input == "123.45"); + } + + // Test integer value with unit + { + std::string input = "42 Hz"; + std::string unit = remove_unit(input); + REQUIRE(unit == "Hz"); + REQUIRE(input == "42"); + } + + // Test negative value with unit + { + std::string input = "-50.5 mV"; + std::string unit = remove_unit(input); + REQUIRE(unit == "mV"); + REQUIRE(input == "-50.5"); + } + + // Test value with no unit (only numbers) + { + std::string input = "123.45"; + std::string unit = remove_unit(input); + REQUIRE(unit == ""); + REQUIRE(input == "123.45"); + } + + // Test value with only unit (letters at start) + { + std::string input = "kHz"; + std::string unit = remove_unit(input); + REQUIRE(unit == "kHz"); + REQUIRE(input == ""); + } + + // Test with multiple word units + { + std::string input = "100 degrees Celsius"; + std::string unit = remove_unit(input); + REQUIRE(unit == "degrees Celsius"); + REQUIRE(input == "100"); + } + + // Test with scientific notation + { + std::string input = "1.23e-5 A"; + std::string unit = remove_unit(input); + REQUIRE(unit == "A"); + REQUIRE(input == "1.23e-5"); + } + + // Another test with scientific notation + { + std::string input = "-4.56E6 m/s"; + std::string unit = remove_unit(input); + REQUIRE(unit == "m/s"); + REQUIRE(input == "-4.56E6"); + } + + // Test with scientific notation uppercase + { + std::string input = "5.67E+3 Hz"; + std::string unit = remove_unit(input); + REQUIRE(unit == "Hz"); + REQUIRE(input == "5.67E+3"); + } + + // Test with leading zeros + { + std::string input = "00123 ohm"; + std::string unit = remove_unit(input); + REQUIRE(unit == "ohm"); + REQUIRE(input == "00123"); + } + + // Test with leading zeros no space + { + std::string input = "00123ohm"; + std::string unit = remove_unit(input); + REQUIRE(unit == "ohm"); + REQUIRE(input == "00123"); + } + + // Test empty string + { + std::string input = ""; + std::string unit = remove_unit(input); + REQUIRE(unit == ""); + REQUIRE(input == ""); + } +} + +TEST_CASE("Conversions from time string to chrono durations") { + using namespace std::chrono; + using aare::string_to; + + REQUIRE(string_to("100 ns") == nanoseconds(100)); + REQUIRE(string_to("1s") == nanoseconds(1000000000)); + REQUIRE(string_to("200 us") == microseconds(200)); + REQUIRE(string_to("300 ms") == milliseconds(300)); + REQUIRE(string_to("5 s") == seconds(5)); + + REQUIRE(string_to("1.5 us") == nanoseconds(1500)); + REQUIRE(string_to("2.5 ms") == microseconds(2500)); + REQUIRE(string_to("3.5 s") == milliseconds(3500)); + + REQUIRE(string_to("2") == seconds(2)); // No unit defaults to seconds + + REQUIRE_THROWS(string_to("10 min")); // Unsupported unit } \ No newline at end of file