From 641ef047b563dd04c3137792c1683c5e751d3d6e Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Thu, 3 Sep 2026 14:14:57 +0200 Subject: [PATCH] Dev/filereading for disabled udp (#342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - handles file reading of disabled udp ports - adds members disabled_udp_ports in master file as an optional - adds member diasbled_udp_port_types in master file as an optional - Treats enabled udp ports as ROIs - merges/combines consecutive ROis into one ### To discuss: - right now can only handle either ROI or disabled udp ports (disabled udp ports has higher precedence) - Should Frames in File be removed from Master File as value is nonsense for disabled udp ports --------- Co-authored-by: Erik Fröjdh --- CMakeLists.txt | 3 + RELEASE.md | 5 + include/aare/ClusterFile.hpp | 2 +- include/aare/DetectorGeometry.hpp | 8 +- include/aare/ROI.hpp | 132 +++++++++++++ include/aare/ROIGeometry.hpp | 9 +- include/aare/RawFile.hpp | 10 +- include/aare/RawMasterFile.hpp | 44 ++++- include/aare/defs.hpp | 20 +- python/aare/__init__.py | 2 + python/src/bind_Defs.hpp | 45 +++++ python/src/file.hpp | 34 +--- python/src/raw_master_file.hpp | 30 ++- python/tests/test_RawFile.py | 40 +++- src/DetectorGeometry.cpp | 7 +- src/ROI.cpp | 43 +++++ src/ROI.test.cpp | 57 ++++++ src/ROIGeometry.cpp | 29 ++- src/RawFile.cpp | 217 +++++++++++---------- src/RawFile.test.cpp | 305 +++++++++++++++++++++++++++++- src/RawMasterFile.cpp | 277 ++++++++++++++++++++++++--- src/RawMasterFile.test.cpp | 177 +++++++++++++---- src/algorithm.test.cpp | 2 +- src/to_string.cpp | 13 ++ src/to_string.hpp | 8 + 25 files changed, 1252 insertions(+), 267 deletions(-) create mode 100644 include/aare/ROI.hpp create mode 100644 src/ROI.cpp create mode 100644 src/ROI.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cb45ea8..e0c9b6c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -366,6 +366,7 @@ set(PUBLICHEADERS include/aare/hist/PixelHistogramImpl.hpp include/aare/hist/PedestalTrackingPixelHistogram.hpp include/aare/GainMap.hpp + include/aare/ROI.hpp include/aare/ROIGeometry.hpp include/aare/DetectorGeometry.hpp include/aare/JungfrauDataFile.hpp @@ -389,6 +390,7 @@ set(SourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/src/CtbRawFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/decode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/defs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ROI.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ROIGeometry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/DetectorGeometry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/hist/PedestalTrackingPixelHistogram.cpp @@ -477,6 +479,7 @@ if(AARE_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/src/NumpyHelpers.test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RawFile.test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RawSubFile.test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ROI.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 f4ed7745..aa51f62c 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -37,6 +37,10 @@ - ``NDView`` now converts to ``NDView``; ``expand4to8bit`` and ``expand24to32bit`` accept const input views. +- ``RawMasterFile::geometry()`` is deprecetad and returns full detector geometry information including module geometry. Use +``RawMasterFile::module_layout()`` to get num_modules in x an y +- ``RawMasterFile::rois()`` always returns a list of rois (no optional). Per default it returns a list of one ROI element spawing the entire detector + ### Bugfixes: - Fixed broken reading of old (pre reordering) Moench03 @@ -54,6 +58,7 @@ - ``aare.transfrom.Matterhorn10Transform`` reshapes data such that first dimension is number of counters - Added support for len() for files. Returns the number of frames - Added support for direct subtraction of Pedestal from numpy array +- Added support to read files with disabled udp ports ### Bugfixes: diff --git a/include/aare/ClusterFile.hpp b/include/aare/ClusterFile.hpp index 4e416c23..e464a254 100644 --- a/include/aare/ClusterFile.hpp +++ b/include/aare/ClusterFile.hpp @@ -5,7 +5,7 @@ #include "aare/ClusterVector.hpp" #include "aare/GainMap.hpp" #include "aare/NDArray.hpp" -#include "aare/defs.hpp" +#include "aare/ROI.hpp" #include "aare/logger.hpp" #include diff --git a/include/aare/DetectorGeometry.hpp b/include/aare/DetectorGeometry.hpp index 8aa60783..e6718ffd 100644 --- a/include/aare/DetectorGeometry.hpp +++ b/include/aare/DetectorGeometry.hpp @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 #pragma once +#include "aare/ROI.hpp" #include "aare/ROIGeometry.hpp" -#include "aare/RawMasterFile.hpp" //ROI refactor away -#include "aare/defs.hpp" #include namespace aare { @@ -97,6 +96,8 @@ class DetectorGeometry { const xy udp_interfaces_per_module = xy{1, 1}, const bool quad = false); + DetectorGeometry() = default; + ~DetectorGeometry() = default; /** @@ -114,6 +115,8 @@ class DetectorGeometry { size_t modules_x() const; size_t modules_y() const; + xy udp_interfaces_per_module() const; + const std::vector &get_module_geometries() const; const ModuleGeometry &get_module_geometries(const size_t index) const; @@ -125,6 +128,7 @@ class DetectorGeometry { size_t m_modules_y{}; size_t m_pixels_x{}; size_t m_pixels_y{}; + xy m_udp_interfaces_per_module{}; static constexpr ModuleConfig cfg{0, 0}; // TODO: maybe remove - should be a member in ROIGeometry - in particular diff --git a/include/aare/ROI.hpp b/include/aare/ROI.hpp new file mode 100644 index 00000000..81c2a84e --- /dev/null +++ b/include/aare/ROI.hpp @@ -0,0 +1,132 @@ +#pragma once +#include "aare/defs.hpp" +#include +#include +#include +#include +#include +#include + +namespace aare { + +class ROIGeometry; // forward declaration to avoid circular dependency +class DetectorGeometry; // forward declaration to avoid circular dependency + +struct ROI { + ssize_t xmin{}; + ssize_t xmax{}; + ssize_t ymin{}; + ssize_t ymax{}; + + ssize_t height() const { return ymax - ymin; } + ssize_t width() const { return xmax - xmin; } + bool contains(ssize_t x, ssize_t y) const { + return x >= xmin && x < xmax && y >= ymin && y < ymax; + } + + bool operator==(const ROI &other) const { + return xmin == other.xmin && xmax == other.xmax && ymin == other.ymin && + ymax == other.ymax; + } +}; + +/** + * @brief Merge all consecutive ROIs in a vector into a single ROI. + * @param rois vector of ROIs to merge + * @return vector of merged ROIs + * @tparam horizontally_aligned true if the ROIs are horizontally aligned, false + * otherwise + * @tparam vertically_aligned true if the ROIs are vertically aligned, false + * otherwise + */ +template +std::vector merge_consecutive_rois(std::vector &rois) { + + if constexpr (horizontally_aligned && vertically_aligned) { + throw std::runtime_error( + LOCATION + "Vector of the same ROI? Cannot merge ROIs both " + "horizontally and vertically at the same time."); + } + + if (rois.empty()) { + return {}; + } + if (rois.size() == 1) { + return rois; + } + + auto merge_along_x = [](std::vector in_rois) { + std::sort(in_rois.begin(), in_rois.end(), + [](const ROI &a, const ROI &b) { + return (a.ymin != b.ymin) ? (a.ymin < b.ymin) + : (a.xmin < b.xmin); + }); // N log (N) + + std::vector merged_rois; + merged_rois.reserve(in_rois.size()); + merged_rois.push_back(in_rois[0]); + + for (size_t i = 1; i < in_rois.size(); ++i) { + auto &last = merged_rois.back(); + const auto ¤t = in_rois[i]; + if (last.ymin == current.ymin && last.ymax == current.ymax && + last.xmax == current.xmin) { + // merge + last.xmax = current.xmax; + } else { + merged_rois.push_back(current); + } + } + return merged_rois; + }; + + auto merge_along_y = [](std::vector in_rois) { + std::sort(in_rois.begin(), in_rois.end(), + [](const ROI &a, const ROI &b) { + return (a.xmin != b.xmin) ? (a.xmin < b.xmin) + : (a.ymin < b.ymin); + }); + + std::vector merged_rois; + merged_rois.reserve(in_rois.size()); + merged_rois.push_back(in_rois[0]); + + for (size_t i = 1; i < in_rois.size(); ++i) { + auto &last = merged_rois.back(); + const auto ¤t = in_rois[i]; + if (last.xmin == current.xmin && last.xmax == current.xmax && + last.ymax == current.ymin) { + last.ymax = current.ymax; + } else { + merged_rois.push_back(current); + } + } + return merged_rois; + }; + + if constexpr (horizontally_aligned) { + return merge_along_y(rois); // one sort + one pass + } else if constexpr (vertically_aligned) { + return merge_along_x(rois); // one sort + one pass + } else { + return merge_along_y(merge_along_x(rois)); // generic case: two passes + } +} + +/** + * @brief Check if the ROI covers the entire detector geometry + * @param roi Region of interest + * @param geometry Detector geometry + * @return true if the ROI covers the entire detector geometry, false otherwise + */ +bool complete_ROI(const ROI &roi, const DetectorGeometry &geometry); + +bool complete_ROI(const std::vector &rois, + const DetectorGeometry &geometry); + +bool complete_ROI(const ROIGeometry &roi, const DetectorGeometry &geometry); + +bool complete_ROI(const std::vector &rois, + const DetectorGeometry &geometry); + +} // namespace aare \ No newline at end of file diff --git a/include/aare/ROIGeometry.hpp b/include/aare/ROIGeometry.hpp index 05450df5..739b24c3 100644 --- a/include/aare/ROIGeometry.hpp +++ b/include/aare/ROIGeometry.hpp @@ -1,6 +1,8 @@ #pragma once #include "aare/DetectorGeometry.hpp" -#include "aare/defs.hpp" +#include "aare/ROI.hpp" +#include +#include namespace aare { @@ -17,11 +19,6 @@ class ROIGeometry { */ ROIGeometry(const ROI &roi, DetectorGeometry &geometry); - /** @brief Constructor for ROI geometry expanding over full detector - * @param geometry general detector geometry - */ - ROIGeometry(DetectorGeometry &geometry); - /// @brief Get number of modules in the ROI size_t num_modules_in_roi() const; diff --git a/include/aare/RawFile.hpp b/include/aare/RawFile.hpp index 999b737f..d9cf0d71 100644 --- a/include/aare/RawFile.hpp +++ b/include/aare/RawFile.hpp @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 #pragma once -#include "aare/DetectorGeometry.hpp" #include "aare/FileInterface.hpp" #include "aare/Frame.hpp" #include "aare/NDArray.hpp" //for pixel map @@ -30,17 +29,16 @@ class RawFile : public FileInterface { RawMasterFile m_master; size_t m_current_frame{}; - DetectorGeometry m_geometry; - - /// @brief Geometries e.g. number of modules, size etc. for each ROI std::vector m_ROI_geometries; + /// @brief total number of frames in file + size_t m_frames_in_file{}; + public: /** * @brief RawFile constructor * @param fname path to the master file (.json) * @param mode file mode (only "r" is supported at the moment) - */ RawFile(const std::filesystem::path &fname, const std::string &mode = "r"); virtual ~RawFile() override = default; @@ -122,7 +120,7 @@ class RawFile : public FileInterface { size_t n_modules() const; /** - * @brief number of ROIs defined + * @brief number of ROIs defined (always 1 for complete ROI) */ size_t num_rois() const; diff --git a/include/aare/RawMasterFile.hpp b/include/aare/RawMasterFile.hpp index d7b13a42..827bcc70 100644 --- a/include/aare/RawMasterFile.hpp +++ b/include/aare/RawMasterFile.hpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 #pragma once -#include "aare/defs.hpp" +#include "aare/DetectorGeometry.hpp" +#include "aare/ROI.hpp" #include #include #include @@ -88,7 +89,8 @@ class RawMasterFile { std::optional m_exptime; std::chrono::nanoseconds m_period{0}; - xy m_geometry{}; + /// @brief modules in x and y direction + xy m_detector_layout{}; xy m_udp_interfaces_per_module{1, 1}; size_t m_max_frames_per_file{}; @@ -109,7 +111,20 @@ class RawMasterFile { std::optional m_number_of_rows; std::optional m_counter_mask; - std::optional> m_rois; + /// @brief index of disabled UDP ports - index relative to UDP_port_types + std::vector m_disabled_udp_ports{}; + + /// @brief udp port types + std::optional> m_udp_port_types{}; + + /// @brief ROIs defined in master file or derived from disabled UDP ports + std::vector m_rois; + + /// @brief Detector geometry - geometry for each module + DetectorGeometry m_geometry{}; + + /// @brief ROI geometries + std::vector m_ROI_geometries; public: RawMasterFile(const std::filesystem::path &fpath); @@ -131,10 +146,14 @@ class RawMasterFile { const FrameDiscardPolicy &frame_discard_policy() const; size_t total_frames_expected() const; - xy geometry() const; + xy detector_layout() const; size_t n_modules() const; uint8_t quad() const; + const DetectorGeometry &geometry() const; + + const std::vector &roi_geometries() const; + ReadoutMode get_reading_mode() const; std::optional analog_samples() const; @@ -143,9 +162,21 @@ class RawMasterFile { std::optional number_of_rows() const; std::optional counter_mask() const; - std::optional> rois() const; + /// @brief Get the types of UDP ports + /// @return Optional vector of UDP port types as strings (only present for + /// masterfile version >= 8.1) + std::optional> udp_port_types() const; - std::optional roi() const; + /// @brief Get the indices of disabled UDP ports + /// @return vector of indices of disabled UDP ports (empty if none are + /// disabled) + std::vector disabled_udp_ports() const; + + std::vector rois() const; + + /// @brief get roi for the case of a single ROI + /// @return ROI object (complete ROI if no roi present in master file) + ROI roi() const; ScanParameters scan_parameters() const; @@ -157,6 +188,7 @@ class RawMasterFile { private: void parse_json(std::istream &is); void parse_raw(std::istream &is); + void update_rois_from_disabled_udp_ports(); void retrieve_geometry(); }; diff --git a/include/aare/defs.hpp b/include/aare/defs.hpp index f810f411..19abb032 100644 --- a/include/aare/defs.hpp +++ b/include/aare/defs.hpp @@ -94,19 +94,6 @@ template struct t_xy { }; using xy = t_xy; -struct ROI { - ssize_t xmin{}; - ssize_t xmax{}; - ssize_t ymin{}; - ssize_t ymax{}; - - ssize_t height() const { return ymax - ymin; } - ssize_t width() const { return xmax - xmin; } - bool contains(ssize_t x, ssize_t y) const { - return x >= xmin && x < xmax && y >= ymin && y < ymax; - } -}; - /// @brief Chip specifications for Matterhorn1 struct Matterhorn10 { constexpr static size_t nRows = 256; @@ -315,6 +302,13 @@ enum class corner : int { cBottomRight = 3 }; +enum class UDPPortPosition : uint8_t { + LEFT = 0, + RIGHT = 1, + TOP = 2, + BOTTOM = 3 +}; + enum class TimingMode { Auto, Trigger }; enum class FrameDiscardPolicy { NoDiscard, Discard, DiscardPartial }; diff --git a/python/aare/__init__.py b/python/aare/__init__.py index 72c2573b..0d5d69e0 100644 --- a/python/aare/__init__.py +++ b/python/aare/__init__.py @@ -27,6 +27,8 @@ from ._aare import hitmap from ._aare import ROI from ._aare import corner +from ._aare import UDPPortPosition + # from ._aare import ClusterFinderMT, ClusterCollector, ClusterFileSink, ClusterVector_i from ._version import __version__ diff --git a/python/src/bind_Defs.hpp b/python/src/bind_Defs.hpp index f0917884..1165e5b3 100644 --- a/python/src/bind_Defs.hpp +++ b/python/src/bind_Defs.hpp @@ -27,4 +27,49 @@ void define_defs_bindings(py::module &m) { moench05.attr("nRows") = Moench05::nRows; moench05.attr("nCols") = Moench05::nCols; moench05.attr("adcNumbers") = Moench05::adcNumbers; + + py::class_(m, "ROI") + .def(py::init<>()) + .def(py::init(), py::arg("xmin"), + py::arg("xmax"), py::arg("ymin"), py::arg("ymax")) + .def_readwrite("xmin", &ROI::xmin) + .def_readwrite("xmax", &ROI::xmax) + .def_readwrite("ymin", &ROI::ymin) + .def_readwrite("ymax", &ROI::ymax) + .def("__str__", + [](const ROI &self) { + return fmt::format("ROI: xmin: {} xmax: {} ymin: {} ymax: {}", + self.xmin, self.xmax, self.ymin, self.ymax); + }) + .def("__repr__", + [](const ROI &self) { + return fmt::format( + "", self.xmin, + self.xmax, self.ymin, self.ymax); + }) + .def("__iter__", + [](const ROI &self) { + return py::make_iterator(&self.xmin, &self.ymax + 1); // NOLINT + }) + + .def("__eq__", [](const ROI &self, const ROI &other) { + return self.xmin == other.xmin && self.xmax == other.xmax && + self.ymin == other.ymin && self.ymax == other.ymax; + }); + + py::enum_(m, "DetectorType") + .value("Jungfrau", DetectorType::Jungfrau) + .value("Eiger", DetectorType::Eiger) + .value("Mythen3", DetectorType::Mythen3) + .value("Moench", DetectorType::Moench) + .value("Moench03", DetectorType::Moench03) + .value("Moench03_old", DetectorType::Moench03_old) + .value("ChipTestBoard", DetectorType::ChipTestBoard) + .value("Unknown", DetectorType::Unknown); + + py::enum_(m, "UDPPortPosition") + .value("LEFT", UDPPortPosition::LEFT) + .value("RIGHT", UDPPortPosition::RIGHT) + .value("TOP", UDPPortPosition::TOP) + .value("BOTTOM", UDPPortPosition::BOTTOM); } diff --git a/python/src/file.hpp b/python/src/file.hpp index ccb62d1f..a493c910 100644 --- a/python/src/file.hpp +++ b/python/src/file.hpp @@ -2,6 +2,7 @@ #include "aare/CtbRawFile.hpp" #include "aare/File.hpp" #include "aare/Frame.hpp" +#include "aare/ROI.hpp" #include "aare/RawFile.hpp" #include "aare/RawMasterFile.hpp" #include "aare/RawSubFile.hpp" @@ -27,16 +28,6 @@ using namespace ::aare; void define_file_io_bindings(py::module &m) { - py::enum_(m, "DetectorType") - .value("Jungfrau", DetectorType::Jungfrau) - .value("Eiger", DetectorType::Eiger) - .value("Mythen3", DetectorType::Mythen3) - .value("Moench", DetectorType::Moench) - .value("Moench03", DetectorType::Moench03) - .value("Moench03_old", DetectorType::Moench03_old) - .value("ChipTestBoard", DetectorType::ChipTestBoard) - .value("Unknown", DetectorType::Unknown); - PYBIND11_NUMPY_DTYPE(DetectorHeader, frameNumber, expLength, packetNumber, bunchId, timestamp, modId, row, column, reserved, debug, roundRNumber, detType, version, packetMask); @@ -170,28 +161,5 @@ void define_file_io_bindings(py::module &m) { .def_property_readonly("stop", &ScanParameters::stop) .def_property_readonly("step", &ScanParameters::step); - py::class_(m, "ROI") - .def(py::init<>()) - .def(py::init(), py::arg("xmin"), - py::arg("xmax"), py::arg("ymin"), py::arg("ymax")) - .def_readwrite("xmin", &ROI::xmin) - .def_readwrite("xmax", &ROI::xmax) - .def_readwrite("ymin", &ROI::ymin) - .def_readwrite("ymax", &ROI::ymax) - .def("__str__", - [](const ROI &self) { - return fmt::format("ROI: xmin: {} xmax: {} ymin: {} ymax: {}", - self.xmin, self.xmax, self.ymin, self.ymax); - }) - .def("__repr__", - [](const ROI &self) { - return fmt::format( - "", self.xmin, - self.xmax, self.ymin, self.ymax); - }) - .def("__iter__", [](const ROI &self) { - return py::make_iterator(&self.xmin, &self.ymax + 1); // NOLINT - }); - #pragma GCC diagnostic pop } \ No newline at end of file diff --git a/python/src/raw_master_file.hpp b/python/src/raw_master_file.hpp index 7f1837c0..f7d3e8e4 100644 --- a/python/src/raw_master_file.hpp +++ b/python/src/raw_master_file.hpp @@ -66,7 +66,8 @@ void define_raw_master_file_bindings(py::module &m) { .def_property_readonly("total_frames_expected", &RawMasterFile::total_frames_expected) - .def_property_readonly("geometry", &RawMasterFile::geometry) + .def_property_readonly("detector_layout", + &RawMasterFile::detector_layout) .def_property_readonly("udp_interfaces_per_module", &RawMasterFile::udp_interfaces_per_module) .def_property_readonly("analog_samples", &RawMasterFile::analog_samples, @@ -107,6 +108,33 @@ void define_raw_master_file_bindings(py::module &m) { return std::nullopt; } }) + .def_property_readonly("rois", &RawMasterFile::rois, R"( + Get the ROIs defined in the master file + + Returns + ---------- + List[ROI] + List of ROIs (default complete ROI) + )") + .def_property_readonly("udp_port_types", &RawMasterFile::udp_port_types, + R"( + Get the types of UDP ports + + Returns + ---------- + Optional[List[UDPPortPosition]] + Optional vector of UDP port types as strings (only present for + masterfile version >= 8.1) + )") + .def_property_readonly("disabled_udp_ports", + &RawMasterFile::disabled_udp_ports, R"( + Get the indices of disabled UDP ports + + Returns + ---------- + List[int] + Vector of disabled UDP port indices relative to UDP port types (empty if none are disabled) + )") .def_property_readonly("period", [](RawMasterFile &self) { double seconds = std::chrono::duration(self.period()).count(); diff --git a/python/tests/test_RawFile.py b/python/tests/test_RawFile.py index ca844a0e..5909c04c 100644 --- a/python/tests/test_RawFile.py +++ b/python/tests/test_RawFile.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MPL-2.0 import pytest -from aare import RawFile +from aare import RawFile, ROI, UDPPortPosition import numpy as np @pytest.mark.withdata @@ -110,3 +110,41 @@ def test_read_rawfile_eiger_and_compare_to_numpy(test_data_path): header, image1 = f.read_frame() assert (image == image1).all() + + +@pytest.mark.withdata +def test_read_eiger_udp_port_disabled(test_data_path): + with RawFile(test_data_path / "raw/eiger/one_udp_port_disabled_master_0.json") as f: + _, frame = f.read_rois() + + assert(len(frame) == 2) + assert frame[0].shape == (256, 512) + assert frame[1].shape == (256, 1024) + assert f.master.udp_port_types == [UDPPortPosition.LEFT, UDPPortPosition.RIGHT] + rois = f.master.rois + assert len(rois) == 2 + assert rois[0] == ROI(512, 1024, 0, 256) + assert rois[1] == ROI(0, 1024, 256, 512) + + with RawFile(test_data_path / "raw/eiger/quad_eiger_disabled_bottom_port_master_0.json") as f: + _, frame = f.read_frame() + + assert frame.shape == (256, 512) + assert(f.master.disabled_udp_ports == [1]) + assert f.master.udp_port_types == [UDPPortPosition.TOP, UDPPortPosition.BOTTOM] + rois = f.master.rois + assert len(rois) == 1 + assert rois[0] == ROI(0, 512, 256, 512) + + with RawFile(test_data_path / "raw/eiger/2_modules_eiger_disabled_udp_port_master_0.json") as f: + _, frame = f.read_rois() + + assert(len(frame) == 2) + assert frame[0].shape == (512, 512) + assert frame[1].shape == (512, 512) + assert (f.master.disabled_udp_ports == [1, 3, 5, 7]) + assert f.master.udp_port_types == [UDPPortPosition.LEFT, UDPPortPosition.RIGHT] + rois = f.master.rois + assert len(rois) == 2 + assert rois[0] == ROI(0, 512, 0, 512) + assert rois[1] == ROI(1024, 1536, 0, 512) \ No newline at end of file diff --git a/src/DetectorGeometry.cpp b/src/DetectorGeometry.cpp index ee3525f2..c0ceef9a 100644 --- a/src/DetectorGeometry.cpp +++ b/src/DetectorGeometry.cpp @@ -11,7 +11,8 @@ DetectorGeometry::DetectorGeometry(const xy &geometry, const ssize_t module_pixels_x, const ssize_t module_pixels_y, const xy udp_interfaces_per_module, - const bool quad) { + const bool quad) + : m_udp_interfaces_per_module(udp_interfaces_per_module) { size_t num_modules = geometry.col * geometry.row; module_geometries.reserve(num_modules); @@ -43,6 +44,10 @@ DetectorGeometry::DetectorGeometry(const xy &geometry, m_pixels_y += static_cast((geometry.row - 1) * cfg.module_gap_row); } +xy DetectorGeometry::udp_interfaces_per_module() const { + return m_udp_interfaces_per_module; +} + size_t DetectorGeometry::n_modules() const { return m_modules_x * m_modules_y; } size_t DetectorGeometry::pixels_x() const { return m_pixels_x; } diff --git a/src/ROI.cpp b/src/ROI.cpp new file mode 100644 index 00000000..9cb37961 --- /dev/null +++ b/src/ROI.cpp @@ -0,0 +1,43 @@ +#include "aare/ROI.hpp" +#include "aare/DetectorGeometry.hpp" +#include "aare/ROIGeometry.hpp" + +namespace aare { + +/** + * @brief Check if the ROI covers the entire detector geometry + * @param roi Region of interest + * @param geometry Detector geometry + * @return true if the ROI covers the entire detector geometry, false otherwise + */ +bool complete_ROI(const ROI &roi, const DetectorGeometry &geometry) { + return roi.xmin == 0 && + roi.xmax == static_cast(geometry.pixels_x()) && + roi.ymin == 0 && + roi.ymax == static_cast(geometry.pixels_y()); +} + +bool complete_ROI(const std::vector &rois, + const DetectorGeometry &geometry) { + if (rois.empty() or rois.size() > 1) { + return false; + } else { + return complete_ROI(rois[0], geometry); + } +} + +bool complete_ROI(const ROIGeometry &roi, const DetectorGeometry &geometry) { + return roi.pixels_x() == geometry.pixels_x() && + roi.pixels_y() == geometry.pixels_y(); +} + +bool complete_ROI(const std::vector &rois, + const DetectorGeometry &geometry) { + if (rois.empty() or rois.size() > 1) { + return false; + } else { + return complete_ROI(rois[0], geometry); + } +} + +} // namespace aare \ No newline at end of file diff --git a/src/ROI.test.cpp b/src/ROI.test.cpp new file mode 100644 index 00000000..6cc7bb26 --- /dev/null +++ b/src/ROI.test.cpp @@ -0,0 +1,57 @@ +#include "aare/ROI.hpp" +#include + +namespace aare { + +TEST_CASE("merge ROIs", "[utility_functions]") { + + SECTION("not fully contiguous") { + + std::vector rois = {ROI{20, 30, 50, 60}, ROI{20, 30, 40, 50}, + ROI{10, 20, 40, 50}}; + + auto merged_rois = merge_consecutive_rois(rois); + + REQUIRE(merged_rois.size() == 2); + + REQUIRE(merged_rois[0] == ROI{10, 30, 40, 50}); + REQUIRE(merged_rois[1] == ROI{20, 30, 50, 60}); + } + SECTION("complex merge") { + + std::vector rois = {ROI{40, 50, 20, 30}, ROI{10, 20, 30, 40}, + ROI{10, 20, 50, 60}, ROI{20, 30, 30, 40}, + ROI{60, 70, 30, 40}, ROI{60, 70, 20, 30}, + ROI{20, 30, 20, 30}, ROI{10, 20, 20, 30}}; + + auto merged_rois = merge_consecutive_rois(rois); + + REQUIRE(merged_rois.size() == 4); + + REQUIRE(merged_rois[0] == ROI{10, 30, 20, 40}); + REQUIRE(merged_rois[1] == ROI{10, 20, 50, 60}); + REQUIRE(merged_rois[2] == ROI{40, 50, 20, 30}); + REQUIRE(merged_rois[3] == ROI{60, 70, 20, 40}); + } + SECTION("horizontally aligned") { + std::vector rois = {ROI{10, 20, 30, 40}, ROI{10, 20, 50, 60}, + ROI{10, 20, 20, 30}}; + + auto merged_rois = merge_consecutive_rois(rois); + + REQUIRE(merged_rois.size() == 2); + REQUIRE(merged_rois[0] == ROI{10, 20, 20, 40}); + REQUIRE(merged_rois[1] == ROI{10, 20, 50, 60}); + } + SECTION("vertically aligned") { + std::vector rois = {ROI{10, 20, 30, 40}, ROI{30, 40, 30, 40}, + ROI{20, 30, 30, 40}}; + + auto merged_rois = merge_consecutive_rois(rois); + + REQUIRE(merged_rois.size() == 1); + REQUIRE(merged_rois[0] == ROI{10, 40, 30, 40}); + } +} + +} // namespace aare diff --git a/src/ROIGeometry.cpp b/src/ROIGeometry.cpp index 04cf9187..f61f9079 100644 --- a/src/ROIGeometry.cpp +++ b/src/ROIGeometry.cpp @@ -5,25 +5,24 @@ namespace aare { ROIGeometry::ROIGeometry(const ROI &roi, DetectorGeometry &geometry) : m_pixels_x(roi.width()), m_pixels_y(roi.height()), m_geometry(geometry) { - m_module_indices_in_roi.reserve(m_geometry.n_modules()); - // determine which modules are in the roi - for (size_t i = 0; i < m_geometry.n_modules(); ++i) { - auto &module_geometry = m_geometry.get_module_geometries(i); - if (module_geometry.module_in_roi(roi)) { - module_geometry.update_geometry_with_roi(roi); - m_module_indices_in_roi.push_back(i); + + if (complete_ROI({roi}, geometry)) { + m_module_indices_in_roi.resize(m_geometry.n_modules()); + std::iota(m_module_indices_in_roi.begin(), + m_module_indices_in_roi.end(), 0); + } else { + m_module_indices_in_roi.reserve(m_geometry.n_modules()); + // determine which modules are in the roi + for (size_t i = 0; i < m_geometry.n_modules(); ++i) { + auto &module_geometry = m_geometry.get_module_geometries(i); + if (module_geometry.module_in_roi(roi)) { + module_geometry.update_geometry_with_roi(roi); + m_module_indices_in_roi.push_back(i); + } } } } -ROIGeometry::ROIGeometry(DetectorGeometry &geometry) - : m_pixels_x(geometry.pixels_x()), m_pixels_y(geometry.pixels_y()), - m_geometry(geometry) { - m_module_indices_in_roi.resize(m_geometry.n_modules()); - std::iota(m_module_indices_in_roi.begin(), m_module_indices_in_roi.end(), - 0); -} - size_t ROIGeometry::num_modules_in_roi() const { return m_module_indices_in_roi.size(); } diff --git a/src/RawFile.cpp b/src/RawFile.cpp index cecf3e82..de89929b 100644 --- a/src/RawFile.cpp +++ b/src/RawFile.cpp @@ -2,11 +2,13 @@ #include "aare/RawFile.hpp" #include "aare/DetectorGeometry.hpp" #include "aare/PixelMap.hpp" +#include "aare/ROI.hpp" #include "aare/ROIGeometry.hpp" #include "aare/algorithm.hpp" #include "aare/defs.hpp" #include "aare/logger.hpp" +#include #include #include @@ -15,34 +17,44 @@ using json = nlohmann::json; namespace aare { RawFile::RawFile(const std::filesystem::path &fname, const std::string &mode) - : m_master(fname), - m_geometry(m_master.geometry(), m_master.pixels_x(), m_master.pixels_y(), - m_master.udp_interfaces_per_module(), m_master.quad()) { + : m_master(fname), m_frames_in_file(m_master.frames_in_file()) { m_mode = mode; - m_subfiles.resize(m_master.rois().has_value() ? m_master.rois()->size() - : 1); - if (mode == "r") { - if (m_master.rois().has_value()) { - m_ROI_geometries.reserve(m_master.rois()->size()); - // iterate over all ROIS - size_t roi_index = 0; - const auto rois = m_master.rois().value(); - for (const auto &roi : rois) { - m_ROI_geometries.push_back(ROIGeometry(roi, m_geometry)); - // open subfiles - open_subfiles(roi_index); - ++roi_index; - } + m_subfiles.resize(m_master.roi_geometries().size()); + // iterate over all ROIS + const size_t num_rois = m_master.roi_geometries().size(); - } else { - // no ROI use full detector - m_ROI_geometries.reserve(1); - m_ROI_geometries.push_back(ROIGeometry(m_geometry)); - open_subfiles(0); + for (size_t roi_index = 0; roi_index < num_rois; ++roi_index) { + // open subfiles + open_subfiles(roi_index); + } + + // TODO: work around for now - retrieve num_frames from subfiles + if (!m_master.disabled_udp_ports().empty()) { + // TODO: remove frames_from_file from master file? + // retrieve the frame numbers from subfile as frames per file in + // master file 0 if dataprocessor 0 was disabled + std::vector min_subfiles_per_roi(m_subfiles.size()); + std::transform( + m_subfiles.begin(), m_subfiles.end(), + min_subfiles_per_roi.begin(), [](const auto &subfiles) { + return std::min_element( + subfiles.begin(), subfiles.end(), + [](const auto &raw_subfile1, + const auto &raw_subfile2) { + return raw_subfile1->frames_in_file() < + raw_subfile2->frames_in_file(); + }) + ->get() + ->frames_in_file(); + }); + m_frames_in_file = *std::min_element(min_subfiles_per_roi.begin(), + min_subfiles_per_roi.end()); + + LOG(logDEBUG) << "Frames in file: " << m_frames_in_file; } } else { throw std::runtime_error(LOCATION + @@ -52,24 +64,14 @@ RawFile::RawFile(const std::filesystem::path &fname, const std::string &mode) Frame RawFile::read_roi(const size_t roi_index) { - if (!m_master.rois()) { - throw std::runtime_error(LOCATION + - "No ROIs defined in the master file."); - } - if (roi_index >= m_ROI_geometries.size()) { + if (roi_index >= m_master.roi_geometries().size()) { throw std::runtime_error(LOCATION + "ROI index out of range."); } return get_frame(m_current_frame++, roi_index); } std::vector RawFile::read_rois() { - - if (!m_master.rois()) { - throw std::runtime_error(LOCATION + - "No ROIs defined in the master file."); - } - - const size_t num_rois = m_ROI_geometries.size(); + const size_t num_rois = m_master.roi_geometries().size(); std::vector frames; frames.reserve(num_rois); @@ -83,18 +85,17 @@ std::vector RawFile::read_rois() { } Frame RawFile::read_frame() { - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { - throw std::runtime_error(LOCATION + - "Multiple ROIs defined in the master file. " - "Use read_ROIs() instead."); + if (m_master.roi_geometries().size() > 1) { + throw std::runtime_error(LOCATION + "Multiple ROIs present in file. " + "Use read_ROIs() instead."); } return get_frame(m_current_frame++); } Frame RawFile::read_frame(size_t frame_number) { - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + if (m_master.roi_geometries().size() > 1) { throw std::runtime_error( - LOCATION + "Multiple ROIs defined in the master file. " + LOCATION + "Multiple ROIs present in file. " "Use read_ROIs(const size_t frame_number) instead."); } seek(frame_number); @@ -103,7 +104,7 @@ Frame RawFile::read_frame(size_t frame_number) { void RawFile::read_into(std::byte *image_buf, size_t n_frames) { // TODO: implement this in a more efficient way - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + if (m_master.roi_geometries().size() > 1) { throw std::runtime_error(LOCATION + "Cannot use read_into for multiple ROIs."); } @@ -115,7 +116,7 @@ void RawFile::read_into(std::byte *image_buf, size_t n_frames) { } void RawFile::read_into(std::byte *image_buf) { - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + if (m_master.roi_geometries().size() > 1) { throw std::runtime_error(LOCATION + "Cannot use read_into for multiple ROIs. Use " "read_roi_into() for a single ROI instead."); @@ -125,10 +126,6 @@ void RawFile::read_into(std::byte *image_buf) { void RawFile::read_roi_into(std::byte *image_buf, const size_t roi_index, const size_t frame_number, DetectorHeader *header) { - if (!m_master.rois().has_value()) { - throw std::runtime_error(LOCATION + - "No ROIs defined in the master file."); - } if (roi_index >= num_rois()) { throw std::runtime_error(LOCATION + "ROI index out of range."); } @@ -136,7 +133,7 @@ void RawFile::read_roi_into(std::byte *image_buf, const size_t roi_index, } void RawFile::read_into(std::byte *image_buf, DetectorHeader *header) { - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + if (m_master.roi_geometries().size() > 1) { throw std::runtime_error(LOCATION + "Cannot use read_into for multiple ROIs. Use " "read_roi_into() for a single ROI instead."); @@ -148,25 +145,28 @@ void RawFile::read_into(std::byte *image_buf, size_t n_frames, DetectorHeader *header) { // return get_frame_into(m_current_frame++, image_buf, header); - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + if (m_master.roi_geometries().size() > 1) { throw std::runtime_error( LOCATION + - "Cannot use read_into for multiple ROIs."); // TODO: maybe pass - // roi_index so one can - // use read_into for a - // specific ROI + "Cannot use read_into for multiple ROIs."); // TODO: maybe + // pass + // roi_index so + // one can use + // read_into for + // a specific + // ROI } for (size_t i = 0; i < n_frames; i++) { this->get_frame_into(m_current_frame++, image_buf, 0, header); image_buf += bytes_per_frame(); if (header) - header += m_ROI_geometries[0].num_modules_in_roi(); + header += m_master.roi_geometries()[0].num_modules_in_roi(); } } size_t RawFile::bytes_per_frame() { - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + if (m_master.roi_geometries().size() > 1) { throw std::runtime_error( LOCATION + "Pass the desired roi_index to bytes_per_frame to get " "bytes_per_frame for the specific ROI. "); @@ -175,13 +175,13 @@ size_t RawFile::bytes_per_frame() { } size_t RawFile::bytes_per_frame(const size_t roi_index) { - return m_ROI_geometries.at(roi_index).pixels_x() * - m_ROI_geometries.at(roi_index).pixels_y() * m_master.bitdepth() / - bits_per_byte; + return m_master.roi_geometries().at(roi_index).pixels_x() * + m_master.roi_geometries().at(roi_index).pixels_y() * + m_master.bitdepth() / bits_per_byte; } size_t RawFile::pixels_per_frame() { - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + if (m_master.roi_geometries().size() > 1) { throw std::runtime_error( LOCATION + "Pass the desired roi_index to pixels_per_frame to get " "pixels_per_frame for the specific ROI. "); @@ -190,8 +190,8 @@ size_t RawFile::pixels_per_frame() { } size_t RawFile::pixels_per_frame(const size_t roi_index) { - return m_ROI_geometries.at(roi_index).pixels_x() * - m_ROI_geometries.at(roi_index).pixels_y(); + return m_master.roi_geometries().at(roi_index).pixels_x() * + m_master.roi_geometries().at(roi_index).pixels_y(); } DetectorType RawFile::detector_type() const { return m_master.detector_type(); } @@ -210,10 +210,10 @@ void RawFile::seek(size_t frame_index) { size_t RawFile::tell() { return m_current_frame; } -size_t RawFile::total_frames() const { return m_master.frames_in_file(); } +size_t RawFile::total_frames() const { return m_frames_in_file; } size_t RawFile::rows() const { - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + if (m_master.roi_geometries().size() > 1) { throw std::runtime_error(LOCATION + "Pass the desired roi_index to rows to get " "rows for the specific ROI. "); @@ -221,10 +221,10 @@ size_t RawFile::rows() const { return rows(0); } size_t RawFile::rows(const size_t roi_index) const { - return m_ROI_geometries.at(roi_index).pixels_y(); + return m_master.roi_geometries().at(roi_index).pixels_y(); } size_t RawFile::cols() const { - if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + if (m_master.roi_geometries().size() > 1) { throw std::runtime_error(LOCATION + "Pass the desired roi_index to cols to get " "cols for the specific ROI. "); @@ -232,50 +232,41 @@ size_t RawFile::cols() const { return cols(0); } size_t RawFile::cols(const size_t roi_index) const { - return m_ROI_geometries.at(roi_index).pixels_x(); + return m_master.roi_geometries().at(roi_index).pixels_x(); } size_t RawFile::bitdepth() const { return m_master.bitdepth(); } -xy RawFile::geometry() const { - return xy{static_cast(m_geometry.modules_y()), - static_cast(m_geometry.modules_x())}; -} -size_t RawFile::n_modules() const { return m_geometry.n_modules(); }; +xy RawFile::geometry() const { return m_master.detector_layout(); } -size_t RawFile::num_rois() const { - if (m_master.rois().has_value()) { - return m_master.rois()->size(); - } else { - return 0; - } -} +size_t RawFile::n_modules() const { return m_master.n_modules(); }; + +size_t RawFile::num_rois() const { return m_master.roi_geometries().size(); } const ROIGeometry &RawFile::roi_geometries(size_t roi_index) const { - return m_ROI_geometries[roi_index]; + return m_master.roi_geometries().at(roi_index); } std::vector RawFile::n_modules_in_roi() const { - std::vector results(m_ROI_geometries.size()); + std::vector results(m_master.roi_geometries().size()); std::transform( - m_ROI_geometries.begin(), m_ROI_geometries.end(), results.begin(), + m_master.roi_geometries().begin(), m_master.roi_geometries().end(), + results.begin(), [](const ROIGeometry &roi) { return roi.num_modules_in_roi(); }); return results; } void RawFile::open_subfiles(const size_t roi_index) { - if (m_mode == "r") { m_subfiles[roi_index].reserve( - m_ROI_geometries[roi_index].num_modules_in_roi()); + m_master.roi_geometries().at(roi_index).num_modules_in_roi()); auto module_indices = - m_ROI_geometries[roi_index].module_indices_in_roi(); + m_master.roi_geometries().at(roi_index).module_indices_in_roi(); - for (const size_t i : - m_ROI_geometries[roi_index].module_indices_in_roi()) { - const auto pos = m_geometry.get_module_geometries(i); + for (const size_t i : module_indices) { + const auto pos = m_master.geometry().get_module_geometries(i); m_subfiles[roi_index].emplace_back(std::make_unique( m_master.data_fname(i, 0), m_master.detector_type(), pos.height, pos.width, m_master.bitdepth(), pos.row_index, pos.col_index)); @@ -306,8 +297,8 @@ DetectorHeader RawFile::read_header(const std::filesystem::path &fname) { RawMasterFile RawFile::master() const { return m_master; } Frame RawFile::get_frame(size_t frame_index, const size_t roi_index) { - auto f = Frame(m_ROI_geometries[roi_index].pixels_y(), - m_ROI_geometries[roi_index].pixels_x(), + auto f = Frame(m_master.roi_geometries().at(roi_index).pixels_y(), + m_master.roi_geometries().at(roi_index).pixels_x(), Dtype::from_bitdepth(m_master.bitdepth())); std::byte *frame_buffer = f.data(); get_frame_into(frame_index, frame_buffer, roi_index); @@ -323,16 +314,18 @@ void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, throw std::runtime_error(LOCATION + "Frame number out of range"); } std::vector frame_numbers( - m_ROI_geometries[roi_index].num_modules_in_roi()); + m_master.roi_geometries().at(roi_index).num_modules_in_roi()); std::vector frame_indices( - m_ROI_geometries[roi_index].num_modules_in_roi(), frame_index); + m_master.roi_geometries().at(roi_index).num_modules_in_roi(), + frame_index); // sync the frame numbers - if (m_ROI_geometries[roi_index].num_modules_in_roi() != + if (m_master.roi_geometries().at(roi_index).num_modules_in_roi() != 1) { // if we have more than one module for (size_t part_idx = 0; - part_idx != m_ROI_geometries[roi_index].num_modules_in_roi(); + part_idx != + m_master.roi_geometries().at(roi_index).num_modules_in_roi(); ++part_idx) { frame_numbers[part_idx] = m_subfiles[roi_index][part_idx]->frame_number(frame_index); @@ -362,32 +355,35 @@ void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, } } - if (m_master.geometry().col == 1) { + if (m_master.detector_layout().col == 1) { // get the part from each subfile and copy it to the frame for (size_t part_idx = 0; - part_idx != m_ROI_geometries[roi_index].num_modules_in_roi(); + part_idx != + m_master.roi_geometries().at(roi_index).num_modules_in_roi(); ++part_idx) { auto corrected_idx = frame_indices[part_idx]; // This is where we start writing auto offset = - (m_geometry + (m_master.geometry() .get_module_geometries( - m_ROI_geometries[roi_index].module_indices_in_roi( - part_idx)) + m_master.roi_geometries() + .at(roi_index) + .module_indices_in_roi(part_idx)) .origin_y * - m_ROI_geometries[roi_index].pixels_x() + - m_geometry + m_master.roi_geometries().at(roi_index).pixels_x() + + m_master.geometry() .get_module_geometries( - m_ROI_geometries[roi_index].module_indices_in_roi( - part_idx)) + m_master.roi_geometries() + .at(roi_index) + .module_indices_in_roi(part_idx)) .origin_x) * m_master.bitdepth() / 8; - if (m_geometry - .get_module_geometries( - m_ROI_geometries[roi_index].module_indices_in_roi( - part_idx)) + if (m_master.geometry() + .get_module_geometries(m_master.roi_geometries() + .at(roi_index) + .module_indices_in_roi(part_idx)) .origin_x != 0) throw std::runtime_error( LOCATION + @@ -420,10 +416,12 @@ void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, // the module level for (size_t part_idx = 0; - part_idx != m_ROI_geometries[roi_index].num_modules_in_roi(); + part_idx != + m_master.roi_geometries().at(roi_index).num_modules_in_roi(); ++part_idx) { - auto pos = m_geometry.get_module_geometries( - m_ROI_geometries[roi_index].module_indices_in_roi(part_idx)); + auto pos = m_master.geometry().get_module_geometries( + m_master.roi_geometries().at(roi_index).module_indices_in_roi( + part_idx)); auto corrected_idx = frame_indices[part_idx]; m_subfiles[roi_index][part_idx]->seek(corrected_idx); @@ -437,7 +435,8 @@ void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, auto irow = (pos.origin_y + cur_row); auto icol = pos.origin_x; auto dest = - (irow * m_ROI_geometries[roi_index].pixels_x() + icol); + (irow * m_master.roi_geometries().at(roi_index).pixels_x() + + icol); dest = dest * m_master.bitdepth() / 8; memcpy(frame_buffer + dest, part_buffer + diff --git a/src/RawFile.test.cpp b/src/RawFile.test.cpp index 88efc027..da316d52 100644 --- a/src/RawFile.test.cpp +++ b/src/RawFile.test.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "test_config.hpp" @@ -263,8 +264,9 @@ TEST_CASE("check find_geometry", "[.with-data][RawFile]") { RawMasterFile master_file(fpath); auto geometry = DetectorGeometry( - master_file.geometry(), master_file.pixels_x(), master_file.pixels_y(), - master_file.udp_interfaces_per_module(), master_file.quad()); + master_file.detector_layout(), master_file.pixels_x(), + master_file.pixels_y(), master_file.udp_interfaces_per_module(), + master_file.quad()); CHECK(geometry.modules_x() == test_parameters.modules_x); CHECK(geometry.modules_y() == test_parameters.modules_y); @@ -308,8 +310,8 @@ TEST_CASE("Open multi module file with ROI", RawFile f(fpath, "r"); SECTION("read 2 frames") { - REQUIRE(f.master().roi().value().width() == 256); - REQUIRE(f.master().roi().value().height() == 256); + REQUIRE(f.master().roi().width() == 256); + REQUIRE(f.master().roi().height() == 256); CHECK(f.n_modules() == 2); @@ -400,8 +402,299 @@ TEST_CASE("Read Mythenframe", "[.with-data][RawFile]") { auto fpath = test_data_path() / "raw/newmythen03/run_2_master_1.json"; REQUIRE(std::filesystem::exists(fpath)); RawFile f(fpath); - REQUIRE(f.master().roi().value().width() == 2560); - REQUIRE(f.master().roi().value().height() == 1); + REQUIRE(f.master().roi().width() == 2560); + REQUIRE(f.master().roi().height() == 1); auto frame = f.read_frame(); REQUIRE(frame.cols() == 2560); +} + +TEST_CASE("Read Jungfrau frame with disabled UDP ports", + "[.with-data][RawFile][disabled_udp_ports]") { + SECTION("disabled top port") { + auto fpath = test_data_path() / "raw/jungfrau" / + "2_interfaces_top_disabled_master_6.json"; + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{1}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::BOTTOM, + UDPPortPosition::TOP}); + auto frame = f.read_frame(); + REQUIRE(frame.cols() == 1024); + REQUIRE(frame.rows() == 256); + + auto rois = f.master().rois(); + + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{0, 1024, 0, 256}); + } + + SECTION("disabled bottom port") { + auto fpath = test_data_path() / "raw/jungfrau" / + "2_interfaces_bottom_port_disabled_master_0.json"; + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{0}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::BOTTOM, + UDPPortPosition::TOP}); + auto frame = f.read_frame(); + REQUIRE(frame.cols() == 1024); + REQUIRE(frame.rows() == 256); + auto rois = f.master().rois(); + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{0, 1024, 256, 512}); + } + SECTION("2 modules - top ports disabled") { + auto fpath = test_data_path() / "raw/jungfrau" / + "2_modules_2_interfaces_top_ports_disabled_master_2.json"; + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{1, 3}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::BOTTOM, + UDPPortPosition::TOP}); + REQUIRE_THROWS_WITH( + f.read_frame(), + Catch::Matchers::ContainsSubstring( + "Multiple ROIs present in file. Use read_ROIs() " + "instead")); // cannot read frame + // because multiple rois + + auto frame = f.read_rois(); + + REQUIRE(frame.size() == 2); + REQUIRE(frame[0].cols() == 1024); + REQUIRE(frame[0].rows() == 256); + REQUIRE(frame[1].cols() == 1024); + REQUIRE(frame[1].rows() == 256); + auto rois = f.master().rois(); + REQUIRE(rois.size() == 2); + REQUIRE(rois[0] == ROI{0, 1024, 0, 256}); + REQUIRE(rois[1] == ROI{0, 1024, 512, 768}); + } + SECTION("2 modules - top ports disabled - bottom port disabled") { + auto fpath = test_data_path() / "raw/jungfrau" / + "2_modules_2_interfaces_disabled_ports_master_1.json"; + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{0, 3}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::BOTTOM, + UDPPortPosition::TOP}); + auto frame = f.read_frame(); + REQUIRE(frame.cols() == 1024); + REQUIRE(frame.rows() == 512); + auto rois = f.master().rois(); + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{0, 1024, 256, 768}); + } + SECTION("4 modules- mixed ports disabled") { + auto fpath = test_data_path() / "raw/jungfrau" / + "4_modules_udp_disabled_master_0.json"; + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == + std::vector{1, 3, 4, 6}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::BOTTOM, + UDPPortPosition::TOP}); + REQUIRE_THROWS_WITH( + f.read_frame(), + Catch::Matchers::ContainsSubstring( + "Multiple ROIs present in file. Use read_ROIs() " + "instead")); + auto frames = f.read_rois(); + REQUIRE(frames.size() == 4); + REQUIRE(frames[0].cols() == 1024); + REQUIRE(frames[0].rows() == 256); + REQUIRE(frames[1].cols() == 1024); + REQUIRE(frames[1].rows() == 256); + REQUIRE(frames[2].cols() == 1024); + REQUIRE(frames[2].rows() == 256); + REQUIRE(frames[3].cols() == 1024); + REQUIRE(frames[3].rows() == 256); + auto rois = f.master().rois(); + REQUIRE(rois.size() == 4); + REQUIRE(rois[0] == ROI{0, 1024, 0, 256}); + REQUIRE(rois[1] == ROI{0, 1024, 512, 768}); + REQUIRE(rois[2] == ROI{1024, 2048, 256, 512}); + REQUIRE(rois[3] == ROI{1024, 2048, 768, 1024}); + } +} + +TEST_CASE("Read Moench frame with disabled UDP ports", + "[.with-data][RawFile][disabled_udp_ports]") { + SECTION("disabled top port") { + auto fpath = test_data_path() / "raw/moench" / + "2_interfaces_top_port_disabled_master_0.json"; + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{1}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::BOTTOM, + UDPPortPosition::TOP}); + auto frame = f.read_frame(); + REQUIRE(frame.cols() == 400); + REQUIRE(frame.rows() == 200); + auto rois = f.master().rois(); + + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{0, 400, 0, 200}); + } + + SECTION("disabled bottom port") { + auto fpath = test_data_path() / "raw/moench" / + "2_interfaces_bottom_port_disabled_master_6.json"; + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{0}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::BOTTOM, + UDPPortPosition::TOP}); + auto frame = f.read_frame(); + REQUIRE(frame.cols() == 400); + REQUIRE(frame.rows() == 200); + auto rois = f.master().rois(); + + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{0, 400, 200, 400}); + } +} + +TEST_CASE("Read Eiger frame with disabled UDP ports", + "[.with-data][RawFile][disabled_udp_ports]") { + SECTION("disabled left port (bottom and top half module)") { + auto fpath = test_data_path() / "raw/eiger" / + "left_udp_port_disabled_master_2.json"; + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{0, 2}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::LEFT, + UDPPortPosition::RIGHT}); + + auto frame = f.read_frame(); + + REQUIRE(frame.cols() == 512); + REQUIRE(frame.rows() == 512); + + auto rois = f.master().rois(); + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{512, 1024, 0, 512}); + } + SECTION("disabled right port") { + auto fpath = test_data_path() / "raw/eiger" / + "right_udp_port_disabled_master_3.json"; + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{1, 3}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::LEFT, + UDPPortPosition::RIGHT}); + auto frame = f.read_frame(); + REQUIRE(frame.cols() == 512); + REQUIRE(frame.rows() == 512); + auto rois = f.master().rois(); + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{0, 512, 0, 512}); + } + SECTION("2 full modules stacked vertically - right ports disabled") { + auto fpath = test_data_path() / "raw/eiger" / + "2_modules_eiger_disabled_udp_port_master_0.json"; + + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == + std::vector{1, 3, 5, 7}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::LEFT, + UDPPortPosition::RIGHT}); + REQUIRE_THROWS_WITH( + f.read_frame(), + Catch::Matchers::ContainsSubstring( + "Multiple ROIs present in file. Use read_ROIs() " + "instead")); + auto frames = f.read_rois(); + REQUIRE(frames.size() == 2); + REQUIRE(frames[0].cols() == 512); + REQUIRE(frames[0].rows() == 512); + REQUIRE(frames[1].cols() == 512); + REQUIRE(frames[1].rows() == 512); + auto rois = f.master().rois(); + REQUIRE(rois.size() == 2); + REQUIRE(rois[0] == ROI{0, 512, 0, 512}); + REQUIRE(rois[1] == ROI{1024, 1536, 0, 512}); + } + SECTION("quad module - bottom port disabled") { + auto fpath = test_data_path() / "raw/eiger" / + "quad_eiger_disabled_bottom_port_master_0.json"; + + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{1}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::TOP, + UDPPortPosition::BOTTOM}); + auto frame = f.read_frame(); + REQUIRE(frame.cols() == 512); + REQUIRE(frame.rows() == 256); + auto rois = f.master().rois(); + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{0, 512, 256, 512}); + } + SECTION("only bottom left port disabled") { + auto fpath = test_data_path() / "raw/eiger" / + "one_udp_port_disabled_master_0.json"; + + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + REQUIRE(f.master().disabled_udp_ports() == std::vector{0}); + REQUIRE(f.master().udp_port_types().value() == + std::vector{UDPPortPosition::LEFT, + UDPPortPosition::RIGHT}); + + REQUIRE_THROWS_WITH( + f.read_frame(), + Catch::Matchers::ContainsSubstring( + "Multiple ROIs present in file. Use read_ROIs() instead")); + + auto frame = f.read_rois(); + REQUIRE(frame.size() == 2); + REQUIRE(frame[0].cols() == 512); + REQUIRE(frame[0].rows() == 256); + REQUIRE(frame[1].cols() == 1024); + REQUIRE(frame[1].rows() == 256); + auto rois = f.master().rois(); + REQUIRE(rois.size() == 2); + + REQUIRE(rois[0] == ROI{512, 1024, 0, 256}); + REQUIRE(rois[1] == ROI{0, 1024, 256, 512}); + } + SECTION("No udp ports disabled") { + auto fpath = test_data_path() / + "raw/eiger_virtual_500k_disabled_ports" / + "all_active_master_0.json"; + + REQUIRE(std::filesystem::exists(fpath)); + RawFile f(fpath); + + auto disabled_udp_ports = f.master().disabled_udp_ports(); + REQUIRE(disabled_udp_ports.empty()); + + auto udp_port_types = f.master().udp_port_types(); + REQUIRE(udp_port_types.has_value()); + REQUIRE(udp_port_types.value() == + std::vector{UDPPortPosition::LEFT, + UDPPortPosition::RIGHT}); + REQUIRE(f.total_frames() == 5); + + auto frame = f.read_frame(); + REQUIRE(frame.cols() == 1024); + REQUIRE(frame.rows() == 512); + + auto rois = f.master().rois(); + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{0, 1024, 0, 512}); + } } \ No newline at end of file diff --git a/src/RawMasterFile.cpp b/src/RawMasterFile.cpp index 9eff0d26..9c4d3752 100644 --- a/src/RawMasterFile.cpp +++ b/src/RawMasterFile.cpp @@ -115,6 +115,32 @@ RawMasterFile::RawMasterFile(const std::filesystem::path &fpath) } else { throw std::runtime_error(LOCATION + "Unsupported file type"); } + + m_geometry = DetectorGeometry(m_detector_layout, m_pixels_x, m_pixels_y, + m_udp_interfaces_per_module, m_quad); + + if (m_quad == 1 && m_udp_port_types.has_value()) { + m_udp_port_types.value() = {UDPPortPosition::TOP, + UDPPortPosition::BOTTOM}; + } + + if (!m_disabled_udp_ports.empty()) { + // ROI takes precedence over disabled UDP ports, if both are defined in + // the master file + if (!complete_ROI(m_rois, m_geometry)) { + LOG(logWARNING) + << "ROI and disabled UDP ports defined in master file. ROI " + "will be used and disabled UDP ports will be ignored."; + } else { + update_rois_from_disabled_udp_ports(); + } + } + + m_ROI_geometries.reserve(m_rois.size()); + + for (const auto &roi : m_rois) { + m_ROI_geometries.push_back(ROIGeometry(roi, m_geometry)); + } } RawMasterFile::RawMasterFile(std::istream &is, const std::string &fname) @@ -164,10 +190,16 @@ std::optional RawMasterFile::counter_mask() const { return m_counter_mask; } -xy RawMasterFile::geometry() const { return m_geometry; } +xy RawMasterFile::detector_layout() const { return m_detector_layout; } + +const DetectorGeometry &RawMasterFile::geometry() const { return m_geometry; } + +const std::vector &RawMasterFile::roi_geometries() const { + return m_ROI_geometries; +} size_t RawMasterFile::n_modules() const { - return m_geometry.row * m_geometry.col; + return m_detector_layout.row * m_detector_layout.col; } xy RawMasterFile::udp_interfaces_per_module() const { @@ -193,26 +225,30 @@ ScanParameters RawMasterFile::scan_parameters() const { return m_scan_parameters; } -std::optional RawMasterFile::roi() const { - if (!m_rois) { - return std::nullopt; - } +std::optional> +RawMasterFile::udp_port_types() const { + return m_udp_port_types; +} - if (m_rois->empty()) { +std::vector RawMasterFile::disabled_udp_ports() const { + return m_disabled_udp_ports; +} + +ROI RawMasterFile::roi() const { + + if (m_rois.empty()) { throw std::runtime_error(LOCATION + "Zero ROIs in metadata."); } - if (m_rois.value().size() > 1) { + if (m_rois.size() > 1) { throw std::runtime_error(LOCATION + "Multiple ROIs present, use rois() method."); } else { - return m_rois.has_value() - ? std::optional(m_rois.value().at(0)) - : std::nullopt; // TODO: maybe throw if no roi exists + return m_rois.at(0); } } -std::optional> RawMasterFile::rois() const { return m_rois; } +std::vector RawMasterFile::rois() const { return m_rois; } ReadoutMode RawMasterFile::get_reading_mode() const { @@ -248,10 +284,11 @@ void RawMasterFile::parse_json(std::istream &is) { m_type = string_to(j["Detector Type"].get()); m_timing_mode = string_to(j["Timing Mode"].get()); - m_geometry = {j["Geometry"]["y"], - j["Geometry"]["x"]}; // TODO: isnt it only available for - // version > 7.1? - // - try block default should be 1x1 + m_detector_layout = { + 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"]; @@ -301,6 +338,9 @@ void RawMasterFile::parse_json(std::istream &is) { if (j.contains("Number of rows") && j["Number of rows"].is_number()) { m_number_of_rows = j["Number of rows"]; } + if (j.contains("Number of Rows") && j["Number of Rows"].is_number()) { + m_number_of_rows = j["Number of Rows"]; // changed at some point + } // ---------------------------------------------------------------- // Special treatment of analog flag because of Moench03. @@ -377,7 +417,26 @@ void RawMasterFile::parse_json(std::istream &is) { } catch (const json::out_of_range &e) { // not a scan } - + try { + auto json_list_obj = j.at("UDP Ports Type"); + m_udp_port_types.emplace(); + for (auto &elem : json_list_obj) { + m_udp_port_types.value().push_back( + string_to(elem)); + } + } catch (const json::out_of_range &e) { + // leave the optional empty + } + try { + auto json_list_obj = j.at("UDP Ports Disabled"); + m_disabled_udp_ports.clear(); + for (auto &elem : json_list_obj) { + m_disabled_udp_ports.push_back(static_cast(elem)); + } + } catch (const json::out_of_range &e) { + m_disabled_udp_ports + .clear(); // empty list if not present in master file + } try { m_udp_interfaces_per_module = {j.at("Number of UDP Interfaces"), 1}; } catch (const json::out_of_range &e) { @@ -397,14 +456,21 @@ void RawMasterFile::parse_json(std::istream &is) { obj.at("ymin") = 0; obj.at("ymax") = 0; } - m_rois.emplace(); - m_rois.value().push_back(ROI{ - obj.at("xmin"), static_cast(obj.at("xmax")) + 1, - obj.at("ymin"), static_cast(obj.at("ymax")) + 1}); + m_rois.push_back({static_cast(obj.at("xmin")), + static_cast(obj.at("xmax")) + 1, + static_cast(obj.at("ymin")), + static_cast(obj.at("ymax")) + 1}); + } else { + // fill ROI with full detector size if not present in master + // file + m_rois.push_back( + {0, + m_detector_layout.col * static_cast(m_pixels_x), + 0, + m_detector_layout.row * static_cast(m_pixels_y)}); } } else { auto obj = j.at("Receiver Rois"); - m_rois.emplace(); for (auto &elem : obj) { // handle Mythen if (elem.at("ymin") == -1 && elem.at("ymax") == -1) { @@ -412,15 +478,18 @@ void RawMasterFile::parse_json(std::istream &is) { elem.at("ymax") = 0; } - m_rois.value().push_back(ROI{ - elem.at("xmin"), static_cast(elem.at("xmax")) + 1, - elem.at("ymin"), - static_cast(elem.at("ymax")) + 1}); + m_rois.push_back({static_cast(elem.at("xmin")), + static_cast(elem.at("xmax")) + 1, + static_cast(elem.at("ymin")), + static_cast(elem.at("ymax")) + 1}); } } } catch (const json::out_of_range &e) { - // leave the optional empty + // fill ROI with full detector size if not present in master file + m_rois.push_back( + {0, m_detector_layout.col * static_cast(m_pixels_x), 0, + m_detector_layout.row * static_cast(m_pixels_y)}); } if (j.contains("Counter Mask")) { @@ -528,7 +597,7 @@ void RawMasterFile::parse_raw(std::istream &is) { m_max_frames_per_file = std::stoi(value); } else if (key == "Geometry") { pos = value.find(','); - m_geometry = { + m_detector_layout = { static_cast(std::stoi(value.substr(1, pos))), static_cast(std::stoi(value.substr(pos + 1)))}; } else if (key == "Number of UDP Interfaces") { @@ -548,11 +617,11 @@ void RawMasterFile::parse_raw(std::istream &is) { m_type = DetectorType::Moench03_old; } - if (m_geometry.col == 0 && m_geometry.row == 0) { + if (m_detector_layout.col == 0 && m_detector_layout.row == 0) { retrieve_geometry(); LOG(TLogLevel::logWARNING) << "No geometry found in master file. Retrieved geometry of " - << m_geometry.row << " x " << m_geometry.col << "\n "; + << m_detector_layout.row << " x " << m_detector_layout.col << "\n "; } // TODO! Read files and find actual frames @@ -578,7 +647,153 @@ void RawMasterFile::retrieve_geometry() { ++rows; ++cols; - m_geometry = {rows, cols}; + m_detector_layout = {rows, cols}; +} + +/** + * @brief Update ROIs from disabled UDP ports + */ +void RawMasterFile::update_rois_from_disabled_udp_ports() { + + const size_t num_udp_port_types = m_udp_port_types.value().size(); + + size_t first_port = m_disabled_udp_ports[0] % num_udp_port_types; + + bool all_ports_equal = + std::all_of(m_disabled_udp_ports.begin(), m_disabled_udp_ports.end(), + [&num_udp_port_types, first_port](size_t &port) { + return port % num_udp_port_types == first_port; + }); + + m_rois.clear(); // remove global roi + + const ssize_t udp_ports_per_module = + m_geometry.udp_interfaces_per_module().col * + m_geometry.udp_interfaces_per_module().row; + + bool port_disabled_for_all_modules = + m_disabled_udp_ports.size() == + m_geometry.modules_x() * m_geometry.modules_y() / udp_ports_per_module; + + if (all_ports_equal && port_disabled_for_all_modules) { + + if (m_udp_port_types.value()[first_port] == UDPPortPosition::LEFT) { + const size_t num_rois = + m_geometry.modules_x() / udp_ports_per_module; + m_rois.resize(num_rois); + + const ssize_t pixels_per_module_x = + m_geometry.pixels_x() / m_geometry.modules_x(); + std::generate( + m_rois.begin(), m_rois.end(), + [n = 0, this, pixels_per_module_x, + udp_ports_per_module]() mutable { + ssize_t idx = n++; + return ROI{ + idx * udp_ports_per_module * pixels_per_module_x + + pixels_per_module_x, + idx * udp_ports_per_module * pixels_per_module_x + + 2 * pixels_per_module_x, + 0, static_cast(m_geometry.pixels_y())}; + }); + } + if (m_udp_port_types.value()[first_port] == UDPPortPosition::RIGHT) { + const size_t num_rois = + m_geometry.modules_x() / udp_ports_per_module; + m_rois.resize(num_rois); + + const ssize_t pixels_per_module_x = + m_geometry.pixels_x() / m_geometry.modules_x(); + std::generate( + m_rois.begin(), m_rois.end(), + [n = 0, this, pixels_per_module_x, + udp_ports_per_module]() mutable { + ssize_t idx = n++; + return ROI{idx * udp_ports_per_module * pixels_per_module_x, + idx * udp_ports_per_module * + pixels_per_module_x + + pixels_per_module_x, + 0, static_cast(m_geometry.pixels_y())}; + }); + } + if (m_udp_port_types.value()[first_port] == UDPPortPosition::TOP) { + size_t num_rois = m_geometry.modules_y() / udp_ports_per_module; + m_rois.resize(num_rois); + // assumes euclidean coordinate system with origin at bottom + // left corner of the detector + const ssize_t pixels_per_module_y = + m_geometry.pixels_y() / m_geometry.modules_y(); + std::generate( + m_rois.begin(), m_rois.end(), + [n = 0, this, pixels_per_module_y, + udp_ports_per_module]() mutable { + ssize_t idx = n++; + return ROI{0, static_cast(m_geometry.pixels_x()), + idx * udp_ports_per_module * pixels_per_module_y, + idx * udp_ports_per_module * + pixels_per_module_y + + pixels_per_module_y}; + }); + } + if (m_udp_port_types.value()[first_port] == UDPPortPosition::BOTTOM) { + size_t num_rois = m_geometry.modules_y() / udp_ports_per_module; + m_rois.resize(num_rois); + const ssize_t pixels_per_module_y = + m_geometry.pixels_y() / m_geometry.modules_y(); + std::generate( + m_rois.begin(), m_rois.end(), + [n = 0, this, pixels_per_module_y, + udp_ports_per_module]() mutable { + ssize_t idx = n++; + return ROI{ + 0, static_cast(m_geometry.pixels_x()), + idx * udp_ports_per_module * pixels_per_module_y + + pixels_per_module_y, + idx * udp_ports_per_module * pixels_per_module_y + + 2 * pixels_per_module_y}; + }); + } + } else { + // iterate over all ports and create ROIs for each disabled port + LOG(logDEBUG) << "Creating ROIs from disabled UDP ports"; + // get the enabled ones: + std::vector enabled_ports(m_geometry.n_modules()); + std::iota(enabled_ports.begin(), enabled_ports.end(), 0); + std::for_each(m_disabled_udp_ports.begin(), m_disabled_udp_ports.end(), + [&enabled_ports](size_t &port) { + enabled_ports.erase(std::remove(enabled_ports.begin(), + enabled_ports.end(), + port), + enabled_ports.end()); + }); + + m_rois.reserve(enabled_ports.size()); + for (const auto enabled_port : enabled_ports) { + + auto module_geometry = + m_geometry.get_module_geometries(enabled_port); + m_rois.push_back( + ROI{module_geometry.origin_x, + module_geometry.origin_x + module_geometry.width, + module_geometry.origin_y, + module_geometry.origin_y + module_geometry.height}); + } + + if (m_udp_port_types == + std::vector{UDPPortPosition::LEFT, + UDPPortPosition::RIGHT}) { + m_rois = merge_consecutive_rois(m_rois); + } else if (m_udp_port_types == + std::vector{UDPPortPosition::BOTTOM, + UDPPortPosition::TOP} || + m_udp_port_types == + std::vector{UDPPortPosition::TOP, + UDPPortPosition::BOTTOM}) { + m_rois = merge_consecutive_rois(m_rois); + } else { + throw std::runtime_error(LOCATION + "Unsupported UDP port types"); + } + } } } // namespace aare diff --git a/src/RawMasterFile.test.cpp b/src/RawMasterFile.test.cpp index 59efed71..41857a7f 100644 --- a/src/RawMasterFile.test.cpp +++ b/src/RawMasterFile.test.cpp @@ -68,7 +68,7 @@ TEST_CASE("A disabled scan") { REQUIRE(s.step() == 0); } -TEST_CASE("Parse a master file in .json format", "[.integration]") { +TEST_CASE("Parse a master file in .json format", "[.with-data]") { auto fpath = test_data_path() / "raw" / "jungfrau" / "jungfrau_single_master_0.json"; REQUIRE(std::filesystem::exists(fpath)); @@ -86,8 +86,8 @@ TEST_CASE("Parse a master file in .json format", "[.integration]") { // "x": 1, // "y": 1 // }, - REQUIRE(f.geometry().col == 1); - REQUIRE(f.geometry().row == 1); + REQUIRE(f.detector_layout().col == 1); + REQUIRE(f.detector_layout().row == 1); // "Image Size in bytes": 1048576, REQUIRE(f.image_size_in_bytes() == 1048576); @@ -151,7 +151,7 @@ TEST_CASE("Parse a master file in .json format", "[.integration]") { } TEST_CASE("Parse a master file in old .raw format", - "[.integration][.with-data][.rawmasterfile]") { + "[.with-data][rawmasterfile]") { auto fpath = test_data_path() / "raw/jungfrau_2modules_version6.1.2/run_master_0.raw"; REQUIRE(std::filesystem::exists(fpath)); @@ -159,11 +159,11 @@ TEST_CASE("Parse a master file in old .raw format", CHECK(f.udp_interfaces_per_module() == xy{1, 1}); CHECK(f.n_modules() == 2); - CHECK(f.geometry().row == 2); - CHECK(f.geometry().col == 1); + CHECK(f.detector_layout().row == 2); + CHECK(f.detector_layout().col == 1); } -TEST_CASE("Parse a master file in .raw format", "[.integration]") { +TEST_CASE("Parse a master file in .raw format", "[.with-data]") { auto fpath = test_data_path() / @@ -180,9 +180,9 @@ TEST_CASE("Parse a master file in .raw format", "[.integration]") { REQUIRE(f.detector_type() == DetectorType::ChipTestBoard); // Timing Mode : auto REQUIRE(f.timing_mode() == TimingMode::Auto); - // Geometry : [1, 1] - REQUIRE(f.geometry().col == 1); - REQUIRE(f.geometry().row == 1); + // Detector Layout : [1, 1] + REQUIRE(f.detector_layout().col == 1); + REQUIRE(f.detector_layout().row == 1); // Image Size : 360000 bytes REQUIRE(f.image_size_in_bytes() == 360000); // Pixels : [96, 1] @@ -232,8 +232,7 @@ TEST_CASE("Parse a master file in .raw format", "[.integration]") { // Packets Caught Mask : 64 bytes } -TEST_CASE("Parse a master file in new .json format", - "[.integration][.with-data]") { +TEST_CASE("Parse a master file in new .json format", "[.with-data]") { auto file_path = test_data_path() / "raw" / "newmythen03" / "run_87_master_0.json"; @@ -247,9 +246,9 @@ TEST_CASE("Parse a master file in new .json format", REQUIRE(f.detector_type() == DetectorType::Mythen3); // Timing Mode : auto REQUIRE(f.timing_mode() == TimingMode::Auto); - // Geometry : [2, 1] - REQUIRE(f.geometry().col == 2); - REQUIRE(f.geometry().row == 1); + // Detector Layout : [2, 1] + REQUIRE(f.detector_layout().col == 2); + REQUIRE(f.detector_layout().row == 1); // Image Size : 5120 bytes REQUIRE(f.image_size_in_bytes() == 5120); @@ -260,14 +259,14 @@ TEST_CASE("Parse a master file in new .json format", REQUIRE(f.scan_parameters().step() == 0); REQUIRE(f.scan_parameters().settleTime() == 0); - auto roi = f.roi().value(); + auto roi = f.roi(); REQUIRE(roi.xmin == 0); REQUIRE(roi.xmax == 2560); REQUIRE(roi.ymin == 0); REQUIRE(roi.ymax == 1); } -TEST_CASE("Read eiger master file", "[.integration]") { +TEST_CASE("Read eiger master file", "[.with-data]") { auto fpath = test_data_path() / "raw/eiger/eiger_500k_32bit_master_0.json"; REQUIRE(std::filesystem::exists(fpath)); RawMasterFile f(fpath); @@ -400,8 +399,8 @@ TEST_CASE("Parse EIGER 7.2 master from string stream") { 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.detector_layout().col == 2); + REQUIRE(f.detector_layout().row == 2); REQUIRE(f.image_size_in_bytes() == 524288); REQUIRE(f.pixels_x() == 512); @@ -477,8 +476,8 @@ TEST_CASE("Parse JUNGFRAU 7.2 master from string stream") { 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.detector_layout().col == 1); + REQUIRE(f.detector_layout().row == 2); REQUIRE(f.n_modules() == 2); REQUIRE(f.image_size_in_bytes() == 524288); REQUIRE(f.pixels_x() == 1024); @@ -560,7 +559,7 @@ TEST_CASE( REQUIRE(f.version() == "7.2"); REQUIRE(f.detector_type() == DetectorType::ChipTestBoard); REQUIRE(f.timing_mode() == TimingMode::Auto); - REQUIRE(f.geometry() == xy{1, 1}); + REQUIRE(f.detector_layout() == xy{1, 1}); REQUIRE(f.image_size_in_bytes() == 192000); REQUIRE(f.pixels_x() == 32); REQUIRE(f.pixels_y() == 1); @@ -638,7 +637,7 @@ TEST_CASE( REQUIRE(f.version() == "7.2"); REQUIRE(f.detector_type() == DetectorType::ChipTestBoard); REQUIRE(f.timing_mode() == TimingMode::Auto); - REQUIRE(f.geometry() == xy{1, 1}); + REQUIRE(f.detector_layout() == xy{1, 1}); REQUIRE(f.image_size_in_bytes() == 16000); REQUIRE(f.pixels_x() == 64); REQUIRE(f.pixels_y() == 1); @@ -710,7 +709,7 @@ TEST_CASE("Parse Moench 7.2 master (SW 7.0.3) from string stream") { REQUIRE(f.version() == "7.2"); REQUIRE(f.detector_type() == DetectorType::Moench03_old); REQUIRE(f.timing_mode() == TimingMode::Auto); - REQUIRE(f.geometry() == xy{1, 1}); + REQUIRE(f.detector_layout() == xy{1, 1}); REQUIRE(f.image_size_in_bytes() == 320000); REQUIRE(f.pixels_x() == 400); REQUIRE(f.pixels_y() == 400); @@ -781,7 +780,7 @@ TEST_CASE("Parse Moench 7.2 master (SW 8.0.0) from string stream") { REQUIRE(f.version() == "7.2"); REQUIRE(f.detector_type() == DetectorType::Moench03); REQUIRE(f.timing_mode() == TimingMode::Auto); - REQUIRE(f.geometry() == xy{1, 1}); + REQUIRE(f.detector_layout() == xy{1, 1}); REQUIRE(f.image_size_in_bytes() == 320000); REQUIRE(f.pixels_x() == 400); REQUIRE(f.pixels_y() == 400); @@ -863,7 +862,7 @@ TEST_CASE("Parse CTB 7.2 master (SW 8.0.0) from string stream") { REQUIRE(f.version() == "7.2"); REQUIRE(f.detector_type() == DetectorType::ChipTestBoard); REQUIRE(f.timing_mode() == TimingMode::Auto); - REQUIRE(f.geometry() == xy{1, 1}); + REQUIRE(f.detector_layout() == xy{1, 1}); REQUIRE(f.image_size_in_bytes() == 192000); REQUIRE(f.pixels_x() == 32); REQUIRE(f.pixels_y() == 1); @@ -944,7 +943,7 @@ TEST_CASE( REQUIRE(f.version() == "7.2"); REQUIRE(f.detector_type() == DetectorType::ChipTestBoard); REQUIRE(f.timing_mode() == TimingMode::Auto); - REQUIRE(f.geometry() == xy{1, 1}); + REQUIRE(f.detector_layout() == xy{1, 1}); REQUIRE(f.image_size_in_bytes() == 16000); REQUIRE(f.pixels_x() == 64); REQUIRE(f.pixels_y() == 1); @@ -1011,8 +1010,8 @@ TEST_CASE("Parse a CTB file from stream") { 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.detector_layout().col == 1); + REQUIRE(f.detector_layout().row == 1); REQUIRE(f.image_size_in_bytes() == 18432); REQUIRE(f.pixels_x() == 2); REQUIRE(f.pixels_y() == 1); @@ -1101,8 +1100,8 @@ TEST_CASE("Parse v8.0 MYTHEN3 from stream") { 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.detector_layout().col == 2); + REQUIRE(f.detector_layout().row == 1); REQUIRE(f.image_size_in_bytes() == 5120); REQUIRE(f.pixels_x() == 1280); REQUIRE(f.pixels_y() == 1); @@ -1186,8 +1185,8 @@ TEST_CASE("Parse a v7.1 Mythen3 from stream") { 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.detector_layout().col == 1); + REQUIRE(f.detector_layout().row == 1); REQUIRE(f.image_size_in_bytes() == 15360); REQUIRE(f.pixels_x() == 3840); REQUIRE(f.pixels_y() == 1); @@ -1268,8 +1267,8 @@ TEST_CASE("Parse old Moench03 from stream") { REQUIRE(f.version() == "7.1"); REQUIRE(f.detector_type() == DetectorType::Moench03_old); REQUIRE(f.timing_mode() == TimingMode::Auto); - REQUIRE(f.geometry().col == 1); - REQUIRE(f.geometry().row == 1); + REQUIRE(f.detector_layout().col == 1); + REQUIRE(f.detector_layout().row == 1); REQUIRE(f.image_size_in_bytes() == 320000); REQUIRE(f.pixels_x() == 400); REQUIRE(f.pixels_y() == 400); @@ -1286,3 +1285,111 @@ TEST_CASE("Parse old Moench03 from stream") { REQUIRE(f.period() == std::chrono::microseconds(600)); REQUIRE(f.analog_samples() == 5000); } + +TEST_CASE("Parse Eiger json v8.1 all ports active") { + std::string master_content = R"( + { + "Version": 8.1, + "Timestamp": "Mon Aug 10 17:44:46 2026", + "Detector Type": "Eiger", + "Timing Mode": "auto", + "Geometry": { + "x": 2, + "y": 2 + }, + "Image Size": 262144, + "Pixels": { + "x": 512, + "y": 256 + }, + "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": 5, + "Receiver Rois": [ + { + "xmin": 0, + "xmax": 1023, + "ymin": 0, + "ymax": 511 + } + ], + "Dynamic Range": 16, + "Ten Giga": 0, + "Exposure Time": "1s", + "Acquisition Period": "1s", + "Threshold Energy": -1, + "Sub Exposure Time": "2.62144ms", + "Sub Acquisition Period": "2.62144ms", + "Quad": 0, + "UDP Ports Type": [ + "left", + "right" + ], + "UDP Ports Disabled": [], + "Number of Rows": 256, + "Rate Corrections": [ + 0, + 0 + ], + "Readout Speed": "full_speed", + "Frames in File": 5, + "Additional JSON Header": {} +} + +)"; + + std::istringstream iss(master_content); + RawMasterFile f(iss, "test_master_0.json"); + + REQUIRE(f.version() == "8.1"); + REQUIRE(f.detector_type() == DetectorType::Eiger); + REQUIRE(f.timing_mode() == TimingMode::Auto); + REQUIRE(f.detector_layout() == xy{2, 2}); + REQUIRE(f.n_modules() == 4); + REQUIRE(f.image_size_in_bytes() == 262144); + 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() == 5); + REQUIRE(f.frames_in_file() == 5); + REQUIRE(f.bitdepth() == 16); + REQUIRE(f.exptime() == std::chrono::seconds(1)); + REQUIRE(f.period() == std::chrono::seconds(1)); + REQUIRE(f.quad() == 0); + // rows return a std::optional, so we need to check if it has a value before + // using it + auto rows = f.number_of_rows(); + REQUIRE(rows.has_value()); + REQUIRE(rows.value() == 256); + REQUIRE(f.udp_interfaces_per_module() == xy{1, 2}); + + auto scan_parameters = f.scan_parameters(); + REQUIRE_FALSE(scan_parameters.enabled()); + REQUIRE(scan_parameters.dac() == DACIndex::DAC_0); + REQUIRE(scan_parameters.start() == 0); + REQUIRE(scan_parameters.stop() == 0); + REQUIRE(scan_parameters.step() == 0); + REQUIRE(scan_parameters.settleTime() == 0); + + REQUIRE(f.udp_port_types().has_value()); + REQUIRE(f.udp_port_types().value() == + std::vector{UDPPortPosition::LEFT, + UDPPortPosition::RIGHT}); + + REQUIRE(f.disabled_udp_ports().empty()); + + auto rois = f.rois(); + REQUIRE(rois.size() == 1); + REQUIRE(rois[0] == ROI{0, 1024, 0, 512}); +} diff --git a/src/algorithm.test.cpp b/src/algorithm.test.cpp index daa0616d..e498c3b2 100644 --- a/src/algorithm.test.cpp +++ b/src/algorithm.test.cpp @@ -231,4 +231,4 @@ TEST_CASE("Bilinear interpolation", "[algorithm]") { 0.75); REQUIRE(interpolated_value == 5.25); } -} +} \ No newline at end of file diff --git a/src/to_string.cpp b/src/to_string.cpp index d950b13c..bac7873b 100644 --- a/src/to_string.cpp +++ b/src/to_string.cpp @@ -235,6 +235,19 @@ template <> DACIndex string_to(const std::string &arg) { "\""); } +template <> UDPPortPosition string_to(const std::string &arg) { + if (arg == "left") + return UDPPortPosition::LEFT; + if (arg == "right") + return UDPPortPosition::RIGHT; + if (arg == "top") + return UDPPortPosition::TOP; + if (arg == "bottom") + return UDPPortPosition::BOTTOM; + throw std::runtime_error("Could not decode UDPPortPosition from: \"" + arg + + "\""); +} + std::string remove_unit(std::string &str) { auto it = str.begin(); while (it != str.end()) { diff --git a/src/to_string.hpp b/src/to_string.hpp index 0bb10ada..690d82fa 100644 --- a/src/to_string.hpp +++ b/src/to_string.hpp @@ -55,6 +55,14 @@ template T string_to(const std::string &arg) { */ template <> DetectorType string_to(const std::string &arg); +/** + * @brief Convert a string to UDPPortPosition + * @param name string representation of the UDPPortPosition + * @return UDPPortPosition + * @throw runtime_error if the string does not match any UDPPortPosition + */ +template <> UDPPortPosition string_to(const std::string &arg); + /** * @brief Convert a string to TimingMode * @param mode string representation of the TimingMode