diff --git a/CMakeLists.txt b/CMakeLists.txt index 97fdb4a..1de16ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -387,6 +387,7 @@ set(SourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/src/RawFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RawMasterFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RawSubFile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/to_string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/task.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/ifstream_helpers.cpp ) @@ -460,6 +461,7 @@ if(AARE_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/src/RawFile.test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RawSubFile.test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/task.test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/to_string.test.cpp ) target_sources(tests PRIVATE ${TestSources} ) diff --git a/RELEASE.md b/RELEASE.md index 495d824..e067c82 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -7,6 +7,8 @@ - Expanding 24 to 32 bit data - Decoding digital data from Mythen 302 - added ``transform_eta_values``. Function transforms :math:`\eta` to uniform spatial coordinates. Should only be used for easier debugging. +- New to_string, string_to for aare +- Added exptime and period members to RawMasterFile including decoding ### 2025.11.21 diff --git a/include/aare/FileInterface.hpp b/include/aare/FileInterface.hpp index 654ad0b..971ca4f 100644 --- a/include/aare/FileInterface.hpp +++ b/include/aare/FileInterface.hpp @@ -34,15 +34,15 @@ struct FileConfig { DetectorType detector_type{DetectorType::Unknown}; int max_frames_per_file{}; size_t total_frames{}; - std::string to_string() const { - return "{ dtype: " + dtype.to_string() + - ", rows: " + std::to_string(rows) + - ", cols: " + std::to_string(cols) + - ", geometry: " + geometry.to_string() + - ", detector_type: " + ToString(detector_type) + - ", max_frames_per_file: " + std::to_string(max_frames_per_file) + - ", total_frames: " + std::to_string(total_frames) + " }"; - } + // std::string to_string() const { + // return "{ dtype: " + dtype.to_string() + + // ", rows: " + std::to_string(rows) + + // ", cols: " + std::to_string(cols) + + // ", geometry: " + geometry.to_string() + + // ", detector_type: " + ToString(detector_type) + + // ", max_frames_per_file: " + std::to_string(max_frames_per_file) + + // ", total_frames: " + std::to_string(total_frames) + " }"; + // } }; /** diff --git a/include/aare/RawMasterFile.hpp b/include/aare/RawMasterFile.hpp index 16eb1ca..c672a2e 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::optional m_exptime; + std::chrono::nanoseconds m_period{0}; + xy m_geometry{}; xy m_udp_interfaces_per_module{1, 1}; @@ -109,6 +113,7 @@ class RawMasterFile { public: RawMasterFile(const std::filesystem::path &fpath); + RawMasterFile(std::istream &is, const std::string &fname); // for testing std::filesystem::path data_fname(size_t mod_id, size_t file_id) const; @@ -140,9 +145,12 @@ class RawMasterFile { ScanParameters scan_parameters() const; + std::optional 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); + void parse_json(std::istream &is); + void parse_raw(std::istream &is); void retrieve_geometry(); }; diff --git a/include/aare/defs.hpp b/include/aare/defs.hpp index c92d247..433b3aa 100644 --- a/include/aare/defs.hpp +++ b/include/aare/defs.hpp @@ -348,29 +348,11 @@ enum class corner : int { enum class TimingMode { Auto, Trigger }; enum class FrameDiscardPolicy { NoDiscard, Discard, DiscardPartial }; -template T StringTo(const std::string &arg) { return T(arg); } - -template std::string ToString(T arg) { return T(arg); } - -template <> DetectorType StringTo(const std::string & /*name*/); -template <> std::string ToString(DetectorType arg); - -template <> TimingMode StringTo(const std::string & /*mode*/); - -template <> FrameDiscardPolicy StringTo(const std::string & /*mode*/); - using DataTypeVariants = std::variant; constexpr uint16_t ADC_MASK = 0x3FFF; // used to mask out the gain bits in Jungfrau -/** - * @brief Convert a string to a DACIndex - * @param arg string representation of the dacIndex - * @return DACIndex - * @throw invalid argument error if the string does not match any DACIndex - */ -template <> DACIndex StringTo(const std::string &arg); class BitOffset{ uint8_t m_offset{}; diff --git a/include/aare/logger.hpp b/include/aare/logger.hpp index 382ebd4..728bdda 100644 --- a/include/aare/logger.hpp +++ b/include/aare/logger.hpp @@ -106,7 +106,7 @@ class Logger { } std::ostringstream &Get() { - os << Color(m_level) << "- " << Timestamp() << " " << ToString(m_level) + os << Color(m_level) << "- " << Timestamp() << " " << Logger::ToString(m_level) << ": "; return os; } diff --git a/python/src/file.hpp b/python/src/file.hpp index 64ba74d..ff7365b 100644 --- a/python/src/file.hpp +++ b/python/src/file.hpp @@ -66,7 +66,7 @@ void define_file_io_bindings(py::module &m) { .def_property_readonly("bytes_per_pixel", &File::bytes_per_pixel) .def_property_readonly( "detector_type", - [](File &self) { return ToString(self.detector_type()); }) + [](File &self) { return self.detector_type(); }) .def("read_frame", [](File &self) { const uint8_t item_size = self.bytes_per_pixel(); @@ -161,21 +161,6 @@ void define_file_io_bindings(py::module &m) { } }); - py::class_(m, "FileConfig") - .def(py::init<>()) - .def_readwrite("rows", &FileConfig::rows) - .def_readwrite("cols", &FileConfig::cols) - .def_readwrite("version", &FileConfig::version) - .def_readwrite("geometry", &FileConfig::geometry) - .def_readwrite("detector_type", &FileConfig::detector_type) - .def_readwrite("max_frames_per_file", &FileConfig::max_frames_per_file) - .def_readwrite("total_frames", &FileConfig::total_frames) - .def_readwrite("dtype", &FileConfig::dtype) - .def("__eq__", &FileConfig::operator==) - .def("__ne__", &FileConfig::operator!=) - .def("__repr__", [](const FileConfig &a) { - return ""; - }); py::class_(m, "ScanParameters") .def(py::init()) diff --git a/python/src/raw_master_file.hpp b/python/src/raw_master_file.hpp index bcd440c..f8730d9 100644 --- a/python/src/raw_master_file.hpp +++ b/python/src/raw_master_file.hpp @@ -85,5 +85,21 @@ 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) -> std::optional { + if (self.exptime()) { + double seconds = + std::chrono::duration(*self.exptime()).count(); + return seconds; + } else { + return std::nullopt; + } + }) + .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 0449d59..04334c9 100644 --- a/src/RawMasterFile.cpp +++ b/src/RawMasterFile.cpp @@ -4,6 +4,8 @@ #include "aare/logger.hpp" #include +#include "to_string.hpp" + namespace aare { RawFileNameComponents::RawFileNameComponents( @@ -69,7 +71,7 @@ ScanParameters::ScanParameters(const bool enabled, const DACIndex dac, const int start, const int stop, const int step, const int64_t settleTime) : m_enabled(enabled), m_dac(dac), m_start(start), m_stop(stop), - m_step(step), m_settleTime(settleTime){}; + m_step(step), m_settleTime(settleTime) {}; // "[enabled\ndac dac 4\nstart 500\nstop 2200\nstep 5\nsettleTime 100us\n]" ScanParameters::ScanParameters(const std::string &par) { @@ -79,7 +81,7 @@ ScanParameters::ScanParameters(const std::string &par) { if (line == "enabled") { m_enabled = true; } else if (line.find("dac") != std::string::npos) { - m_dac = StringTo(line.substr(4)); + m_dac = string_to(line.substr(4)); } else if (line.find("start") != std::string::npos) { m_start = std::stoi(line.substr(6)); } else if (line.find("stop") != std::string::npos) { @@ -104,10 +106,24 @@ RawMasterFile::RawMasterFile(const std::filesystem::path &fpath) throw std::runtime_error(fmt::format("{} File does not exist: {}", LOCATION, fpath.string())); } + + std::ifstream ifs(fpath); if (m_fnc.ext() == ".json") { - parse_json(fpath); + parse_json(ifs); } else if (m_fnc.ext() == ".raw") { - parse_raw(fpath); + parse_raw(ifs); + } else { + throw std::runtime_error(LOCATION + "Unsupported file type"); + } +} + +RawMasterFile::RawMasterFile(std::istream &is, const std::string &fname) + : m_fnc(fname) { + + if (m_fnc.ext() == ".json") { + parse_json(is); + } else if (m_fnc.ext() == ".raw") { + parse_raw(is); } else { throw std::runtime_error(LOCATION + "Unsupported file type"); } @@ -179,16 +195,15 @@ ScanParameters RawMasterFile::scan_parameters() const { std::optional RawMasterFile::roi() const { return m_roi; } -void RawMasterFile::parse_json(const std::filesystem::path &fpath) { - std::ifstream ifs(fpath); +void RawMasterFile::parse_json(std::istream &is) { json j; - ifs >> j; + is >> j; double v = j["Version"]; m_version = fmt::format("{:.1f}", v); - m_type = StringTo(j["Detector Type"].get()); - m_timing_mode = StringTo(j["Timing Mode"].get()); + m_type = string_to(j["Detector Type"].get()); + m_timing_mode = string_to(j["Timing Mode"].get()); m_geometry = { j["Geometry"]["y"], @@ -204,24 +219,46 @@ void RawMasterFile::parse_json(const std::filesystem::path &fpath) { m_max_frames_per_file = j["Max Frames Per File"]; + // Before v8.0 we had Exptime instead of Exposure Time + // Mythen3 uses 3 exposure times and is not handled at the moment + if (j.contains("Exptime") && j["Exptime"].is_string()) { + m_exptime = string_to( + j["Exptime"].get()); + } + if (j.contains("Exposure Time") && j["Exposure Time"].is_string()) { + m_exptime = string_to( + j["Exposure Time"].get()); + } + + // Before v8.0 we had Period instead of Acquisition Period + if (j.contains("Period") && j["Period"].is_string()) { + m_period = + string_to(j["Period"].get()); + } + if (j.contains("Acquisition Period") && + j["Acquisition Period"].is_string()) { + m_period = string_to( + j["Acquisition Period"].get()); + } + + // TODO! Not valid for CTB but not changing api right now! // Not all detectors write the bitdepth but in case // its not there it is 16 - try { - m_bitdepth = j.at("Dynamic Range"); - } catch (const json::out_of_range &e) { + if(j.contains("Bit Depth") && j["Bit Depth"].is_number()){ + m_bitdepth = j["Bit Depth"]; + } else { m_bitdepth = 16; } m_total_frames_expected = j["Total Frames"]; m_frame_padding = j["Frame Padding"]; - m_frame_discard_policy = StringTo( + m_frame_discard_policy = string_to( j["Frame Discard Policy"].get()); - try { - m_number_of_rows = j.at("Number of rows"); - } catch (const json::out_of_range &e) { - // keep the optional empty - } + if(j.contains("Number of rows") && j["Number of rows"].is_number()){ + m_number_of_rows = j["Number of rows"]; + } + // ---------------------------------------------------------------- // Special treatment of analog flag because of Moench03 try { @@ -334,19 +371,18 @@ void RawMasterFile::parse_json(const std::filesystem::path &fpath) { } catch (const json::out_of_range &e) { // leave the optional empty } - try { - // TODO: what is the best format to handle - m_counter_mask = j.at("Counter Mask"); - } catch (const json::out_of_range &e) { - // leave the optional empty - } -// Update detector type for Moench -// TODO! How does this work with old .raw master files? -#ifdef AARE_VERBOSE - fmt::print("Detecting Moench03: m_pixels_y: {}, m_analog_samples: {}\n", - m_pixels_y, m_analog_samples.value_or(0)); -#endif + if (j.contains("Counter Mask")) { + if (j["Counter Mask"].is_number()) + m_counter_mask = j["Counter Mask"]; + else if (j["Counter Mask"].is_string()) + m_counter_mask = + std::stoi(j["Counter Mask"].get(), nullptr, 16); + } + + + // Update detector type for Moench + // TODO! How does this work with old .raw master files? if (m_type == DetectorType::Moench && !m_analog_samples && m_pixels_y == 400) { m_type = DetectorType::Moench03; @@ -355,10 +391,8 @@ void RawMasterFile::parse_json(const std::filesystem::path &fpath) { m_type = DetectorType::Moench03_old; } } -void RawMasterFile::parse_raw(const std::filesystem::path &fpath) { - - std::ifstream ifs(fpath); - for (std::string line; std::getline(ifs, line);) { +void RawMasterFile::parse_raw(std::istream &is) { + for (std::string line; std::getline(is, line);) { if (line == "#Frame Header") break; auto pos = line.find(':'); @@ -383,12 +417,12 @@ void RawMasterFile::parse_raw(const std::filesystem::path &fpath) { } else if (key == "TimeStamp") { } else if (key == "Detector Type") { - m_type = StringTo(value); + m_type = string_to(value); if (m_type == DetectorType::Moench) { m_type = DetectorType::Moench03_old; } } else if (key == "Timing Mode") { - m_timing_mode = StringTo(value); + m_timing_mode = string_to(value); } else if (key == "Image Size") { m_image_size_in_bytes = std::stoi(value); } else if (key == "Frame Padding") { @@ -429,6 +463,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..640f0de 100644 --- a/src/RawMasterFile.test.cpp +++ b/src/RawMasterFile.test.cpp @@ -4,6 +4,7 @@ #include "test_config.hpp" #include #include +#include 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)); +} \ No newline at end of file diff --git a/src/defs.cpp b/src/defs.cpp index 6eabbc1..18425d9 100644 --- a/src/defs.cpp +++ b/src/defs.cpp @@ -11,292 +11,46 @@ void assert_failed(const std::string &msg) { exit(1); } -/** - * @brief Convert a DetectorType to a string - * @param type DetectorType - * @return string representation of the DetectorType - */ -template <> std::string ToString(DetectorType arg) { - switch (arg) { - case DetectorType::Generic: - return "Generic"; - case DetectorType::Eiger: - return "Eiger"; - case DetectorType::Gotthard: - return "Gotthard"; - case DetectorType::Jungfrau: - return "Jungfrau"; - case DetectorType::ChipTestBoard: - return "ChipTestBoard"; - case DetectorType::Moench: - return "Moench"; - case DetectorType::Mythen3: - return "Mythen3"; - case DetectorType::Gotthard2: - return "Gotthard2"; - case DetectorType::Xilinx_ChipTestBoard: - return "Xilinx_ChipTestBoard"; +// /** +// * @brief Convert a DetectorType to a string +// * @param type DetectorType +// * @return string representation of the DetectorType +// */ +// template <> std::string ToString(DetectorType arg) { +// switch (arg) { +// case DetectorType::Generic: +// return "Generic"; +// case DetectorType::Eiger: +// return "Eiger"; +// case DetectorType::Gotthard: +// return "Gotthard"; +// case DetectorType::Jungfrau: +// return "Jungfrau"; +// case DetectorType::ChipTestBoard: +// return "ChipTestBoard"; +// case DetectorType::Moench: +// return "Moench"; +// case DetectorType::Mythen3: +// return "Mythen3"; +// case DetectorType::Gotthard2: +// return "Gotthard2"; +// case DetectorType::Xilinx_ChipTestBoard: +// return "Xilinx_ChipTestBoard"; - // Custom ones - case DetectorType::Moench03: - return "Moench03"; - case DetectorType::Moench03_old: - return "Moench03_old"; - case DetectorType::Unknown: - return "Unknown"; +// // Custom ones +// case DetectorType::Moench03: +// return "Moench03"; +// case DetectorType::Moench03_old: +// return "Moench03_old"; +// case DetectorType::Unknown: +// return "Unknown"; - // no default case to trigger compiler warning if not all - // enum values are handled - } - throw std::runtime_error("Could not decode detector to string"); -} +// // no default case to trigger compiler warning if not all +// // enum values are handled +// } +// throw std::runtime_error("Could not decode detector to string"); +// } -/** - * @brief Convert a string to a DetectorType - * @param name string representation of the DetectorType - * @return DetectorType - * @throw runtime_error if the string does not match any DetectorType - */ -template <> DetectorType StringTo(const std::string &arg) { - if (arg == "Generic") - return DetectorType::Generic; - if (arg == "Eiger") - return DetectorType::Eiger; - if (arg == "Gotthard") - return DetectorType::Gotthard; - if (arg == "Jungfrau") - return DetectorType::Jungfrau; - if (arg == "ChipTestBoard") - return DetectorType::ChipTestBoard; - if (arg == "Moench") - return DetectorType::Moench; - if (arg == "Mythen3") - return DetectorType::Mythen3; - if (arg == "Gotthard2") - return DetectorType::Gotthard2; - if (arg == "Xilinx_ChipTestBoard") - return DetectorType::Xilinx_ChipTestBoard; - - // Custom ones - if (arg == "Moench03") - return DetectorType::Moench03; - if (arg == "Moench03_old") - return DetectorType::Moench03_old; - if (arg == "Unknown") - return DetectorType::Unknown; - - throw std::runtime_error("Could not decode detector from: \"" + arg + "\""); -} - -/** - * @brief Convert a string to a TimingMode - * @param mode string representation of the TimingMode - * @return TimingMode - * @throw runtime_error if the string does not match any TimingMode - */ -template <> TimingMode StringTo(const std::string &arg) { - if (arg == "auto") - return TimingMode::Auto; - if (arg == "trigger") - return TimingMode::Trigger; - throw std::runtime_error("Could not decode timing mode from: \"" + arg + - "\""); -} - -template <> FrameDiscardPolicy StringTo(const std::string &arg) { - if (arg == "nodiscard") - return FrameDiscardPolicy::NoDiscard; - if (arg == "discard") - return FrameDiscardPolicy::Discard; - if (arg == "discardpartial") - return FrameDiscardPolicy::DiscardPartial; - throw std::runtime_error("Could not decode frame discard policy from: \"" + - arg + "\""); -} - -// template <> TimingMode StringTo(std::string mode); - -template <> DACIndex StringTo(const std::string &arg) { - if (arg == "dac 0") - return DACIndex::DAC_0; - else if (arg == "dac 1") - return DACIndex::DAC_1; - else if (arg == "dac 2") - return DACIndex::DAC_2; - else if (arg == "dac 3") - return DACIndex::DAC_3; - else if (arg == "dac 4") - return DACIndex::DAC_4; - else if (arg == "dac 5") - return DACIndex::DAC_5; - else if (arg == "dac 6") - return DACIndex::DAC_6; - else if (arg == "dac 7") - return DACIndex::DAC_7; - else if (arg == "dac 8") - return DACIndex::DAC_8; - else if (arg == "dac 9") - return DACIndex::DAC_9; - else if (arg == "dac 10") - return DACIndex::DAC_10; - else if (arg == "dac 11") - return DACIndex::DAC_11; - else if (arg == "dac 12") - return DACIndex::DAC_12; - else if (arg == "dac 13") - return DACIndex::DAC_13; - else if (arg == "dac 14") - return DACIndex::DAC_14; - else if (arg == "dac 15") - return DACIndex::DAC_15; - else if (arg == "dac 16") - return DACIndex::DAC_16; - else if (arg == "dac 17") - return DACIndex::DAC_17; - else if (arg == "vsvp") - return DACIndex::VSVP; - else if (arg == "vtrim") - return DACIndex::VTRIM; - else if (arg == "vrpreamp") - return DACIndex::VRPREAMP; - else if (arg == "vrshaper") - return DACIndex::VRSHAPER; - else if (arg == "vsvn") - return DACIndex::VSVN; - else if (arg == "vtgstv") - return DACIndex::VTGSTV; - else if (arg == "vcmp_ll") - return DACIndex::VCMP_LL; - else if (arg == "vcmp_lr") - return DACIndex::VCMP_LR; - else if (arg == "vcal") - return DACIndex::VCAL; - else if (arg == "vcmp_rl") - return DACIndex::VCMP_RL; - else if (arg == "rxb_rb") - return DACIndex::RXB_RB; - else if (arg == "rxb_lb") - return DACIndex::RXB_LB; - else if (arg == "vcmp_rr") - return DACIndex::VCMP_RR; - else if (arg == "vcp") - return DACIndex::VCP; - else if (arg == "vcn") - return DACIndex::VCN; - else if (arg == "vishaper") - return DACIndex::VISHAPER; - else if (arg == "vthreshold") - return DACIndex::VTHRESHOLD; - else if (arg == "vref_ds") - return DACIndex::VREF_DS; - else if (arg == "vout_cm") - return DACIndex::VOUT_CM; - else if (arg == "vin_cm") - return DACIndex::VIN_CM; - else if (arg == "vref_comp") - return DACIndex::VREF_COMP; - else if (arg == "vb_comp") - return DACIndex::VB_COMP; - else if (arg == "vdd_prot") - return DACIndex::VDD_PROT; - else if (arg == "vin_com") - return DACIndex::VIN_COM; - else if (arg == "vref_prech") - return DACIndex::VREF_PRECH; - else if (arg == "vb_pixbuf") - return DACIndex::VB_PIXBUF; - else if (arg == "vb_ds") - return DACIndex::VB_DS; - else if (arg == "vref_h_adc") - return DACIndex::VREF_H_ADC; - else if (arg == "vb_comp_fe") - return DACIndex::VB_COMP_FE; - else if (arg == "vb_comp_adc") - return DACIndex::VB_COMP_ADC; - else if (arg == "vcom_cds") - return DACIndex::VCOM_CDS; - else if (arg == "vref_rstore") - return DACIndex::VREF_RSTORE; - else if (arg == "vb_opa_1st") - return DACIndex::VB_OPA_1ST; - else if (arg == "vref_comp_fe") - return DACIndex::VREF_COMP_FE; - else if (arg == "vcom_adc1") - return DACIndex::VCOM_ADC1; - else if (arg == "vref_l_adc") - return DACIndex::VREF_L_ADC; - else if (arg == "vref_cds") - return DACIndex::VREF_CDS; - else if (arg == "vb_cs") - return DACIndex::VB_CS; - else if (arg == "vb_opa_fd") - return DACIndex::VB_OPA_FD; - else if (arg == "vcom_adc2") - return DACIndex::VCOM_ADC2; - else if (arg == "vcassh") - return DACIndex::VCASSH; - else if (arg == "vth2") - return DACIndex::VTH2; - else if (arg == "vrshaper_n") - return DACIndex::VRSHAPER_N; - else if (arg == "vipre_out") - return DACIndex::VIPRE_OUT; - else if (arg == "vth3") - return DACIndex::VTH3; - else if (arg == "vth1") - return DACIndex::VTH1; - else if (arg == "vicin") - return DACIndex::VICIN; - else if (arg == "vcas") - return DACIndex::VCAS; - else if (arg == "vcal_n") - return DACIndex::VCAL_N; - else if (arg == "vipre") - return DACIndex::VIPRE; - else if (arg == "vcal_p") - return DACIndex::VCAL_P; - else if (arg == "vdcsh") - return DACIndex::VDCSH; - else if (arg == "vbp_colbuf") - return DACIndex::VBP_COLBUF; - else if (arg == "vb_sda") - return DACIndex::VB_SDA; - else if (arg == "vcasc_sfp") - return DACIndex::VCASC_SFP; - else if (arg == "vipre_cds") - return DACIndex::VIPRE_CDS; - else if (arg == "ibias_sfp") - return DACIndex::IBIAS_SFP; - else if (arg == "trimbits") - return DACIndex::TRIMBIT_SCAN; - else if (arg == "highvoltage") - return DACIndex::HIGH_VOLTAGE; - else if (arg == "iodelay") - return DACIndex::IO_DELAY; - else if (arg == "temp_adc") - return DACIndex::TEMPERATURE_ADC; - else if (arg == "temp_fpga") - return DACIndex::TEMPERATURE_FPGA; - else if (arg == "temp_fpgaext") - return DACIndex::TEMPERATURE_FPGAEXT; - else if (arg == "temp_10ge") - return DACIndex::TEMPERATURE_10GE; - else if (arg == "temp_dcdc") - return DACIndex::TEMPERATURE_DCDC; - else if (arg == "temp_sodl") - return DACIndex::TEMPERATURE_SODL; - else if (arg == "temp_sodr") - return DACIndex::TEMPERATURE_SODR; - else if (arg == "temp_fpgafl") - return DACIndex::TEMPERATURE_FPGA2; - else if (arg == "temp_fpgafr") - return DACIndex::TEMPERATURE_FPGA3; - else if (arg == "temp_slowadc") - return DACIndex::SLOW_ADC_TEMP; - else - throw std::invalid_argument("Could not decode DACIndex from: \"" + arg + - "\""); -} BitOffset::BitOffset(uint32_t offset){ if (offset>7) diff --git a/src/defs.test.cpp b/src/defs.test.cpp index 40fb168..b42ef51 100644 --- a/src/defs.test.cpp +++ b/src/defs.test.cpp @@ -4,52 +4,7 @@ #include #include -using aare::StringTo; -using aare::ToString; -TEST_CASE("Enum to string conversion") { - // TODO! By the way I don't think the enum string conversions should be in - // the defs.hpp file but let's use this to show a test - REQUIRE(ToString(aare::DetectorType::Generic) == "Generic"); - REQUIRE(ToString(aare::DetectorType::Eiger) == "Eiger"); - REQUIRE(ToString(aare::DetectorType::Gotthard) == "Gotthard"); - REQUIRE(ToString(aare::DetectorType::Jungfrau) == "Jungfrau"); - REQUIRE(ToString(aare::DetectorType::ChipTestBoard) == "ChipTestBoard"); - REQUIRE(ToString(aare::DetectorType::Moench) == "Moench"); - REQUIRE(ToString(aare::DetectorType::Mythen3) == "Mythen3"); - REQUIRE(ToString(aare::DetectorType::Gotthard2) == "Gotthard2"); - REQUIRE(ToString(aare::DetectorType::Xilinx_ChipTestBoard) == - "Xilinx_ChipTestBoard"); - REQUIRE(ToString(aare::DetectorType::Moench03) == "Moench03"); - REQUIRE(ToString(aare::DetectorType::Moench03_old) == "Moench03_old"); - REQUIRE(ToString(aare::DetectorType::Unknown) == "Unknown"); -} - -TEST_CASE("String to enum") { - REQUIRE(StringTo("Generic") == - aare::DetectorType::Generic); - REQUIRE(StringTo("Eiger") == aare::DetectorType::Eiger); - REQUIRE(StringTo("Gotthard") == - aare::DetectorType::Gotthard); - REQUIRE(StringTo("Jungfrau") == - aare::DetectorType::Jungfrau); - REQUIRE(StringTo("ChipTestBoard") == - aare::DetectorType::ChipTestBoard); - REQUIRE(StringTo("Moench") == - aare::DetectorType::Moench); - REQUIRE(StringTo("Mythen3") == - aare::DetectorType::Mythen3); - REQUIRE(StringTo("Gotthard2") == - aare::DetectorType::Gotthard2); - REQUIRE(StringTo("Xilinx_ChipTestBoard") == - aare::DetectorType::Xilinx_ChipTestBoard); - REQUIRE(StringTo("Moench03") == - aare::DetectorType::Moench03); - REQUIRE(StringTo("Moench03_old") == - aare::DetectorType::Moench03_old); - REQUIRE(StringTo("Unknown") == - aare::DetectorType::Unknown); -} TEST_CASE("Enum values") { // Since some of the enums are written to file we need to make sure @@ -103,23 +58,3 @@ TEST_CASE("Basic ops on BitOffset"){ REQUIRE(offset2==offset); } - - -// TEST_CASE("cluster set and get data") { - -// aare::DynamicCluster c2(33, 44, aare::Dtype(typeid(double))); -// REQUIRE(c2.cluster_sizeX == 33); -// REQUIRE(c2.cluster_sizeY == 44); -// REQUIRE(c2.dt == aare::Dtype::DOUBLE); -// double v = 3.14; -// c2.set(0, v); -// double v2 = c2.get(0); -// REQUIRE(aare::compare_floats(v, v2)); - -// c2.set(33 * 44 - 1, 123.11); -// double v3 = c2.get(33 * 44 - 1); -// REQUIRE(aare::compare_floats(123.11, v3)); - -// REQUIRE_THROWS_AS(c2.set(0, 1), std::invalid_argument); // set int to -// double -// } \ No newline at end of file diff --git a/src/to_string.cpp b/src/to_string.cpp new file mode 100644 index 0000000..9a32eae --- /dev/null +++ b/src/to_string.cpp @@ -0,0 +1,276 @@ +#include "to_string.hpp" + +namespace aare { + +template <> DetectorType string_to(const std::string &arg) { + if (arg == "Generic") + return DetectorType::Generic; + if (arg == "Eiger") + return DetectorType::Eiger; + if (arg == "Gotthard") + return DetectorType::Gotthard; + if (arg == "Jungfrau") + return DetectorType::Jungfrau; + if (arg == "ChipTestBoard") + return DetectorType::ChipTestBoard; + if (arg == "Moench") + return DetectorType::Moench; + if (arg == "Mythen3") + return DetectorType::Mythen3; + if (arg == "Gotthard2") + return DetectorType::Gotthard2; + if (arg == "Xilinx_ChipTestBoard") + return DetectorType::Xilinx_ChipTestBoard; + + // Custom ones + if (arg == "Moench03") + return DetectorType::Moench03; + if (arg == "Moench03_old") + return DetectorType::Moench03_old; + if (arg == "Unknown") + return DetectorType::Unknown; + + throw std::runtime_error("Could not decode detector from: \"" + arg + "\""); +} + +template <> TimingMode string_to(const std::string &arg) { + if (arg == "auto") + return TimingMode::Auto; + if (arg == "trigger") + return TimingMode::Trigger; + throw std::runtime_error("Could not decode timing mode from: \"" + arg + + "\""); +} + +template <> FrameDiscardPolicy string_to(const std::string &arg) { + if (arg == "nodiscard") + return FrameDiscardPolicy::NoDiscard; + if (arg == "discard") + return FrameDiscardPolicy::Discard; + if (arg == "discardpartial") + return FrameDiscardPolicy::DiscardPartial; + throw std::runtime_error("Could not decode frame discard policy from: \"" + + arg + "\""); +} + + +template <> DACIndex string_to(const std::string &arg) { + if (arg == "dac 0") + return DACIndex::DAC_0; + else if (arg == "dac 1") + return DACIndex::DAC_1; + else if (arg == "dac 2") + return DACIndex::DAC_2; + else if (arg == "dac 3") + return DACIndex::DAC_3; + else if (arg == "dac 4") + return DACIndex::DAC_4; + else if (arg == "dac 5") + return DACIndex::DAC_5; + else if (arg == "dac 6") + return DACIndex::DAC_6; + else if (arg == "dac 7") + return DACIndex::DAC_7; + else if (arg == "dac 8") + return DACIndex::DAC_8; + else if (arg == "dac 9") + return DACIndex::DAC_9; + else if (arg == "dac 10") + return DACIndex::DAC_10; + else if (arg == "dac 11") + return DACIndex::DAC_11; + else if (arg == "dac 12") + return DACIndex::DAC_12; + else if (arg == "dac 13") + return DACIndex::DAC_13; + else if (arg == "dac 14") + return DACIndex::DAC_14; + else if (arg == "dac 15") + return DACIndex::DAC_15; + else if (arg == "dac 16") + return DACIndex::DAC_16; + else if (arg == "dac 17") + return DACIndex::DAC_17; + else if (arg == "vsvp") + return DACIndex::VSVP; + else if (arg == "vtrim") + return DACIndex::VTRIM; + else if (arg == "vrpreamp") + return DACIndex::VRPREAMP; + else if (arg == "vrshaper") + return DACIndex::VRSHAPER; + else if (arg == "vsvn") + return DACIndex::VSVN; + else if (arg == "vtgstv") + return DACIndex::VTGSTV; + else if (arg == "vcmp_ll") + return DACIndex::VCMP_LL; + else if (arg == "vcmp_lr") + return DACIndex::VCMP_LR; + else if (arg == "vcal") + return DACIndex::VCAL; + else if (arg == "vcmp_rl") + return DACIndex::VCMP_RL; + else if (arg == "rxb_rb") + return DACIndex::RXB_RB; + else if (arg == "rxb_lb") + return DACIndex::RXB_LB; + else if (arg == "vcmp_rr") + return DACIndex::VCMP_RR; + else if (arg == "vcp") + return DACIndex::VCP; + else if (arg == "vcn") + return DACIndex::VCN; + else if (arg == "vishaper") + return DACIndex::VISHAPER; + else if (arg == "vthreshold") + return DACIndex::VTHRESHOLD; + else if (arg == "vref_ds") + return DACIndex::VREF_DS; + else if (arg == "vout_cm") + return DACIndex::VOUT_CM; + else if (arg == "vin_cm") + return DACIndex::VIN_CM; + else if (arg == "vref_comp") + return DACIndex::VREF_COMP; + else if (arg == "vb_comp") + return DACIndex::VB_COMP; + else if (arg == "vdd_prot") + return DACIndex::VDD_PROT; + else if (arg == "vin_com") + return DACIndex::VIN_COM; + else if (arg == "vref_prech") + return DACIndex::VREF_PRECH; + else if (arg == "vb_pixbuf") + return DACIndex::VB_PIXBUF; + else if (arg == "vb_ds") + return DACIndex::VB_DS; + else if (arg == "vref_h_adc") + return DACIndex::VREF_H_ADC; + else if (arg == "vb_comp_fe") + return DACIndex::VB_COMP_FE; + else if (arg == "vb_comp_adc") + return DACIndex::VB_COMP_ADC; + else if (arg == "vcom_cds") + return DACIndex::VCOM_CDS; + else if (arg == "vref_rstore") + return DACIndex::VREF_RSTORE; + else if (arg == "vb_opa_1st") + return DACIndex::VB_OPA_1ST; + else if (arg == "vref_comp_fe") + return DACIndex::VREF_COMP_FE; + else if (arg == "vcom_adc1") + return DACIndex::VCOM_ADC1; + else if (arg == "vref_l_adc") + return DACIndex::VREF_L_ADC; + else if (arg == "vref_cds") + return DACIndex::VREF_CDS; + else if (arg == "vb_cs") + return DACIndex::VB_CS; + else if (arg == "vb_opa_fd") + return DACIndex::VB_OPA_FD; + else if (arg == "vcom_adc2") + return DACIndex::VCOM_ADC2; + else if (arg == "vcassh") + return DACIndex::VCASSH; + else if (arg == "vth2") + return DACIndex::VTH2; + else if (arg == "vrshaper_n") + return DACIndex::VRSHAPER_N; + else if (arg == "vipre_out") + return DACIndex::VIPRE_OUT; + else if (arg == "vth3") + return DACIndex::VTH3; + else if (arg == "vth1") + return DACIndex::VTH1; + else if (arg == "vicin") + return DACIndex::VICIN; + else if (arg == "vcas") + return DACIndex::VCAS; + else if (arg == "vcal_n") + return DACIndex::VCAL_N; + else if (arg == "vipre") + return DACIndex::VIPRE; + else if (arg == "vcal_p") + return DACIndex::VCAL_P; + else if (arg == "vdcsh") + return DACIndex::VDCSH; + else if (arg == "vbp_colbuf") + return DACIndex::VBP_COLBUF; + else if (arg == "vb_sda") + return DACIndex::VB_SDA; + else if (arg == "vcasc_sfp") + return DACIndex::VCASC_SFP; + else if (arg == "vipre_cds") + return DACIndex::VIPRE_CDS; + else if (arg == "ibias_sfp") + return DACIndex::IBIAS_SFP; + else if (arg == "trimbits") + return DACIndex::TRIMBIT_SCAN; + else if (arg == "highvoltage") + return DACIndex::HIGH_VOLTAGE; + else if (arg == "iodelay") + return DACIndex::IO_DELAY; + else if (arg == "temp_adc") + return DACIndex::TEMPERATURE_ADC; + else if (arg == "temp_fpga") + return DACIndex::TEMPERATURE_FPGA; + else if (arg == "temp_fpgaext") + return DACIndex::TEMPERATURE_FPGAEXT; + else if (arg == "temp_10ge") + return DACIndex::TEMPERATURE_10GE; + else if (arg == "temp_dcdc") + return DACIndex::TEMPERATURE_DCDC; + else if (arg == "temp_sodl") + return DACIndex::TEMPERATURE_SODL; + else if (arg == "temp_sodr") + return DACIndex::TEMPERATURE_SODR; + else if (arg == "temp_fpgafl") + return DACIndex::TEMPERATURE_FPGA2; + else if (arg == "temp_fpgafr") + return DACIndex::TEMPERATURE_FPGA3; + else if (arg == "temp_slowadc") + return DACIndex::SLOW_ADC_TEMP; + else + throw std::invalid_argument("Could not decode DACIndex from: \"" + 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 new file mode 100644 index 0000000..6d13b54 --- /dev/null +++ b/src/to_string.hpp @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MPL-2.0 + +/* + *The file to_string.hpp contains conversion to and from string for various aare + * types. It is intentionally not a part of the public API. + * Only include the conversion that are actually needed!!! + */ + +#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) { + std::string tmp{arg}; + auto unit = remove_unit(tmp); + return string_to(tmp, unit); +} + +/** + * @brief Convert a string to DetectorType + * @param name string representation of the DetectorType + * @return DetectorType + * @throw runtime_error if the string does not match any DetectorType + */ +template <> DetectorType string_to(const std::string &arg); + +/** + * @brief Convert a string to TimingMode + * @param mode string representation of the TimingMode + * @return TimingMode + * @throw runtime_error if the string does not match any TimingMode + */ +template <> TimingMode string_to(const std::string &arg); + +/** + * @brief Convert a string to FrameDiscardPolicy + * @param mode string representation of the FrameDiscardPolicy + * @return FrameDiscardPolicy + * @throw runtime_error if the string does not match any FrameDiscardPolicy + */ +template <> FrameDiscardPolicy string_to(const std::string &arg); + +/** + * @brief Convert a string to a DACIndex + * @param arg string representation of the dacIndex + * @return DACIndex + * @throw invalid argument error if the string does not match any DACIndex + */ +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 new file mode 100644 index 0000000..351acf7 --- /dev/null +++ b/src/to_string.test.cpp @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: MPL-2.0 +#include "aare/defs.hpp" + +#include +#include + +#include "to_string.hpp" +using aare::string_to; + +TEST_CASE("DetectorType string to enum") { + REQUIRE(string_to("Generic") == + aare::DetectorType::Generic); + REQUIRE(string_to("Eiger") == aare::DetectorType::Eiger); + REQUIRE(string_to("Gotthard") == + aare::DetectorType::Gotthard); + REQUIRE(string_to("Jungfrau") == + aare::DetectorType::Jungfrau); + REQUIRE(string_to("ChipTestBoard") == + aare::DetectorType::ChipTestBoard); + REQUIRE(string_to("Moench") == + aare::DetectorType::Moench); + REQUIRE(string_to("Mythen3") == + aare::DetectorType::Mythen3); + REQUIRE(string_to("Gotthard2") == + aare::DetectorType::Gotthard2); + REQUIRE(string_to("Xilinx_ChipTestBoard") == + aare::DetectorType::Xilinx_ChipTestBoard); + REQUIRE(string_to("Moench03") == + aare::DetectorType::Moench03); + REQUIRE(string_to("Moench03_old") == + aare::DetectorType::Moench03_old); + REQUIRE(string_to("Unknown") == + aare::DetectorType::Unknown); + REQUIRE_THROWS(string_to("invalid_detector")); +} + +TEST_CASE("TimingMode string to enum") { + REQUIRE(string_to("auto") == aare::TimingMode::Auto); + REQUIRE(string_to("trigger") == aare::TimingMode::Trigger); + REQUIRE_THROWS(string_to("invalid_mode")); +} + +TEST_CASE("FrameDiscardPolicy string to enum") { + REQUIRE(string_to("nodiscard") == + aare::FrameDiscardPolicy::NoDiscard); + REQUIRE(string_to("discard") == + aare::FrameDiscardPolicy::Discard); + REQUIRE(string_to("discardpartial") == + aare::FrameDiscardPolicy::DiscardPartial); + REQUIRE_THROWS(string_to("invalid_policy")); +} + +TEST_CASE("DACIndex string to enum") { + // Numeric DACs + REQUIRE(string_to("dac 0") == aare::DACIndex::DAC_0); + REQUIRE(string_to("dac 1") == aare::DACIndex::DAC_1); + REQUIRE(string_to("dac 2") == aare::DACIndex::DAC_2); + REQUIRE(string_to("dac 3") == aare::DACIndex::DAC_3); + REQUIRE(string_to("dac 4") == aare::DACIndex::DAC_4); + REQUIRE(string_to("dac 5") == aare::DACIndex::DAC_5); + REQUIRE(string_to("dac 6") == aare::DACIndex::DAC_6); + REQUIRE(string_to("dac 7") == aare::DACIndex::DAC_7); + REQUIRE(string_to("dac 8") == aare::DACIndex::DAC_8); + REQUIRE(string_to("dac 9") == aare::DACIndex::DAC_9); + REQUIRE(string_to("dac 10") == aare::DACIndex::DAC_10); + REQUIRE(string_to("dac 11") == aare::DACIndex::DAC_11); + REQUIRE(string_to("dac 12") == aare::DACIndex::DAC_12); + REQUIRE(string_to("dac 13") == aare::DACIndex::DAC_13); + REQUIRE(string_to("dac 14") == aare::DACIndex::DAC_14); + REQUIRE(string_to("dac 15") == aare::DACIndex::DAC_15); + REQUIRE(string_to("dac 16") == aare::DACIndex::DAC_16); + REQUIRE(string_to("dac 17") == aare::DACIndex::DAC_17); + + // Named DACs + REQUIRE(string_to("vsvp") == aare::DACIndex::VSVP); + REQUIRE(string_to("vtrim") == aare::DACIndex::VTRIM); + REQUIRE(string_to("vrpreamp") == aare::DACIndex::VRPREAMP); + REQUIRE(string_to("vrshaper") == aare::DACIndex::VRSHAPER); + REQUIRE(string_to("vsvn") == aare::DACIndex::VSVN); + REQUIRE(string_to("vtgstv") == aare::DACIndex::VTGSTV); + REQUIRE(string_to("vcmp_ll") == aare::DACIndex::VCMP_LL); + REQUIRE(string_to("vcmp_lr") == aare::DACIndex::VCMP_LR); + REQUIRE(string_to("vcal") == aare::DACIndex::VCAL); + REQUIRE(string_to("vcmp_rl") == aare::DACIndex::VCMP_RL); + REQUIRE(string_to("rxb_rb") == aare::DACIndex::RXB_RB); + REQUIRE(string_to("rxb_lb") == aare::DACIndex::RXB_LB); + REQUIRE(string_to("vcmp_rr") == aare::DACIndex::VCMP_RR); + REQUIRE(string_to("vcp") == aare::DACIndex::VCP); + REQUIRE(string_to("vcn") == aare::DACIndex::VCN); + REQUIRE(string_to("vishaper") == aare::DACIndex::VISHAPER); + REQUIRE(string_to("vthreshold") == aare::DACIndex::VTHRESHOLD); + REQUIRE(string_to("vref_ds") == aare::DACIndex::VREF_DS); + REQUIRE(string_to("vout_cm") == aare::DACIndex::VOUT_CM); + REQUIRE(string_to("vin_cm") == aare::DACIndex::VIN_CM); + REQUIRE(string_to("vref_comp") == aare::DACIndex::VREF_COMP); + REQUIRE(string_to("vb_comp") == aare::DACIndex::VB_COMP); + REQUIRE(string_to("vdd_prot") == aare::DACIndex::VDD_PROT); + REQUIRE(string_to("vin_com") == aare::DACIndex::VIN_COM); + REQUIRE(string_to("vref_prech") == aare::DACIndex::VREF_PRECH); + REQUIRE(string_to("vb_pixbuf") == aare::DACIndex::VB_PIXBUF); + REQUIRE(string_to("vb_ds") == aare::DACIndex::VB_DS); + REQUIRE(string_to("vref_h_adc") == aare::DACIndex::VREF_H_ADC); + REQUIRE(string_to("vb_comp_fe") == aare::DACIndex::VB_COMP_FE); + REQUIRE(string_to("vb_comp_adc") == aare::DACIndex::VB_COMP_ADC); + REQUIRE(string_to("vcom_cds") == aare::DACIndex::VCOM_CDS); + REQUIRE(string_to("vref_rstore") == aare::DACIndex::VREF_RSTORE); + REQUIRE(string_to("vb_opa_1st") == aare::DACIndex::VB_OPA_1ST); + REQUIRE(string_to("vref_comp_fe") == aare::DACIndex::VREF_COMP_FE); + REQUIRE(string_to("vcom_adc1") == aare::DACIndex::VCOM_ADC1); + REQUIRE(string_to("vref_l_adc") == aare::DACIndex::VREF_L_ADC); + REQUIRE(string_to("vref_cds") == aare::DACIndex::VREF_CDS); + REQUIRE(string_to("vb_cs") == aare::DACIndex::VB_CS); + REQUIRE(string_to("vb_opa_fd") == aare::DACIndex::VB_OPA_FD); + REQUIRE(string_to("vcom_adc2") == aare::DACIndex::VCOM_ADC2); + REQUIRE(string_to("vcassh") == aare::DACIndex::VCASSH); + REQUIRE(string_to("vth2") == aare::DACIndex::VTH2); + REQUIRE(string_to("vrshaper_n") == aare::DACIndex::VRSHAPER_N); + REQUIRE(string_to("vipre_out") == aare::DACIndex::VIPRE_OUT); + REQUIRE(string_to("vth3") == aare::DACIndex::VTH3); + REQUIRE(string_to("vth1") == aare::DACIndex::VTH1); + REQUIRE(string_to("vicin") == aare::DACIndex::VICIN); + REQUIRE(string_to("vcas") == aare::DACIndex::VCAS); + REQUIRE(string_to("vcal_n") == aare::DACIndex::VCAL_N); + REQUIRE(string_to("vipre") == aare::DACIndex::VIPRE); + REQUIRE(string_to("vcal_p") == aare::DACIndex::VCAL_P); + REQUIRE(string_to("vdcsh") == aare::DACIndex::VDCSH); + REQUIRE(string_to("vbp_colbuf") == aare::DACIndex::VBP_COLBUF); + REQUIRE(string_to("vb_sda") == aare::DACIndex::VB_SDA); + REQUIRE(string_to("vcasc_sfp") == aare::DACIndex::VCASC_SFP); + REQUIRE(string_to("vipre_cds") == aare::DACIndex::VIPRE_CDS); + REQUIRE(string_to("ibias_sfp") == aare::DACIndex::IBIAS_SFP); + REQUIRE(string_to("trimbits") == aare::DACIndex::TRIMBIT_SCAN); + REQUIRE(string_to("highvoltage") == aare::DACIndex::HIGH_VOLTAGE); + REQUIRE(string_to("iodelay") == aare::DACIndex::IO_DELAY); + REQUIRE(string_to("temp_adc") == aare::DACIndex::TEMPERATURE_ADC); + REQUIRE(string_to("temp_fpga") == aare::DACIndex::TEMPERATURE_FPGA); + REQUIRE(string_to("temp_fpgaext") == aare::DACIndex::TEMPERATURE_FPGAEXT); + REQUIRE(string_to("temp_10ge") == aare::DACIndex::TEMPERATURE_10GE); + REQUIRE(string_to("temp_dcdc") == aare::DACIndex::TEMPERATURE_DCDC); + REQUIRE(string_to("temp_sodl") == aare::DACIndex::TEMPERATURE_SODL); + REQUIRE(string_to("temp_sodr") == aare::DACIndex::TEMPERATURE_SODR); + REQUIRE(string_to("temp_fpgafl") == aare::DACIndex::TEMPERATURE_FPGA2); + REQUIRE(string_to("temp_fpgafr") == aare::DACIndex::TEMPERATURE_FPGA3); + 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