From 218f31ce6017fdfd381d5d978958a23efc9e9cc2 Mon Sep 17 00:00:00 2001 From: AliceMazzoleni99 Date: Wed, 18 Feb 2026 10:57:56 +0100 Subject: [PATCH] Dev/multiple rois in aare (#263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reading multiple ROI's for aare - read_frame, read_n etc throws for multiple ROIs - new functions read_ROIs, read_n_ROIs - read_roi_into (used for python bindings - to not copy) all these functions use get_frame or get_frame_into where one passes the roi_index ## Refactoring: - each roi keeps track of its subfiles that one has to open e.g. subfiles can be opened several times - refactored class DetectorGeometry - keep track of the updated module geometries in new class ROIGeometry. - ModuleGeometry updates based on ROI ## ROIGeometry: - stores number of modules overlapping with ROI and its indices - size of ROI Note: only tested size of the resulting frames not the actual values --------- Co-authored-by: Erik Fröjdh Co-authored-by: Erik Fröjdh --- CMakeLists.txt | 2 + RELEASE.md | 4 + include/aare/DetectorGeometry.hpp | 77 ++++++- include/aare/ROIGeometry.hpp | 48 +++++ include/aare/RawFile.hpp | 95 ++++++++- include/aare/RawMasterFile.hpp | 10 +- python/src/bind_RawFile.hpp | 311 ++++++++++++++++++++++++++++ python/src/module.cpp | 2 +- python/src/np_helper.hpp | 19 +- python/src/raw_file.hpp | 109 ---------- python/tests/test_RawFile.py | 44 +++- src/DetectorGeometry.cpp | 88 +------- src/DetectorGeometry.test.cpp | 312 ++++++++++++++++++++-------- src/ROIGeometry.cpp | 42 ++++ src/RawFile.cpp | 330 ++++++++++++++++++++++++------ src/RawFile.test.cpp | 99 +++++++-- src/RawMasterFile.cpp | 71 ++++--- 17 files changed, 1242 insertions(+), 421 deletions(-) create mode 100644 include/aare/ROIGeometry.hpp create mode 100644 python/src/bind_RawFile.hpp delete mode 100644 python/src/raw_file.hpp create mode 100644 src/ROIGeometry.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dfd7357..1ad94b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -352,6 +352,7 @@ set(PUBLICHEADERS include/aare/FilePtr.hpp include/aare/Frame.hpp include/aare/GainMap.hpp + include/aare/ROIGeometry.hpp include/aare/DetectorGeometry.hpp include/aare/JungfrauDataFile.hpp include/aare/logger.hpp @@ -374,6 +375,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/ROIGeometry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/DetectorGeometry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Dtype.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/File.cpp diff --git a/RELEASE.md b/RELEASE.md index 6ca6f44..72a2c72 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,6 +12,10 @@ - Removed redundant arr.value(ix,iy...) on NDArray use arr(ix,iy...) - Removed Print/Print_some/Print_all form NDArray (operator << still works) - Added const* version of .data() +- reading multiple ROI's supported for aare. + - Use ``read_roi/rois`` to read multiple ROIs for one frame, the index of a specific ROI to only read that ROI + - Use ``read_n_with_roi`` to read multiple frames for a specific ROI. + - Note ``read_frame`` and ``read_n`` is not supported for multiple ROI's. ### Bugfixes: diff --git a/include/aare/DetectorGeometry.hpp b/include/aare/DetectorGeometry.hpp index 31bba94..8aa6078 100644 --- a/include/aare/DetectorGeometry.hpp +++ b/include/aare/DetectorGeometry.hpp @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MPL-2.0 #pragma once +#include "aare/ROIGeometry.hpp" #include "aare/RawMasterFile.hpp" //ROI refactor away #include "aare/defs.hpp" +#include + namespace aare { struct ModuleConfig { @@ -18,16 +21,69 @@ struct ModuleConfig { }; /** - * @brief Class to hold the geometry of a module. Where pixel 0 is located and - * the size of the module + * @brief Class to hold the geometry of a module. Where pixel 0 is located + * relative to full Detector/ROI and the size of the module e.g. size of ROI + * occupied by the module */ struct ModuleGeometry { + /// @brief start x coordinate of the module in the full detector/ROI int origin_x{}; + /// @brief start y coordinate of the module in the full detector/ROI int origin_y{}; + /// @brief height of the module in pixels int height{}; + /// @brief width of the module in pixels int width{}; + /// @brief module index of the module in the detector int row_index{}; + /// @brief module index of the module in the detector int col_index{}; + + /** + * @brief checks if module is in the region of interest + */ + bool module_in_roi(const ROI &roi) { + // module is to the left of the roi + bool to_the_left = origin_x + width - 1 < roi.xmin; + bool to_the_right = origin_x >= roi.xmax; + bool above = origin_y + height - 1 < roi.ymin; + bool below = origin_y >= roi.ymax; + + return !(to_the_left || to_the_right || below || above); + } + + /** + * @brief Update the module geometry given a region of interest + */ + void update_geometry_with_roi(const ROI &roi) { + + if (!(module_in_roi(roi))) { + return; // do nothing + } + + int new_width = std::min(origin_x + width, static_cast(roi.xmax)) - + std::max(origin_x, static_cast(roi.xmin)); + + // ROI starts inside module + if (roi.xmin >= origin_x && roi.xmin < origin_x + width) { + origin_x = 0; + } else { + origin_x -= roi.xmin; + } + + int new_height = + std::min(origin_y + height, static_cast(roi.ymax)) - + std::max(origin_y, static_cast(roi.ymin)); + + if (roi.ymin >= origin_y && roi.ymin < origin_y + height) { + origin_y = 0; + } else { + origin_y -= roi.ymin; + } + + height = new_height; + width = new_width; + } }; /** @@ -49,34 +105,33 @@ class DetectorGeometry { * @param roi * @return DetectorGeometry */ - void update_geometry_with_roi(ROI roi); size_t n_modules() const; - size_t n_modules_in_roi() const; - size_t pixels_x() const; size_t pixels_y() const; size_t modules_x() const; size_t modules_y() const; - const std::vector &get_modules_in_roi() const; - - ssize_t get_modules_in_roi(const size_t index) const; - const std::vector &get_module_geometries() const; const ModuleGeometry &get_module_geometries(const size_t index) const; + ModuleGeometry &get_module_geometries(const size_t index); + private: size_t m_modules_x{}; size_t m_modules_y{}; size_t m_pixels_x{}; size_t m_pixels_y{}; static constexpr ModuleConfig cfg{0, 0}; - std::vector module_geometries{}; - std::vector modules_in_roi{}; + + // TODO: maybe remove - should be a member in ROIGeometry - in particular + // only works as no overlap between ROIs allowed? maybe just have another + // additional vector of ModuleGeometry for each ROIGeometry - some + // duplication but nicer design? + std::vector module_geometries; }; } // namespace aare \ No newline at end of file diff --git a/include/aare/ROIGeometry.hpp b/include/aare/ROIGeometry.hpp new file mode 100644 index 0000000..05450df --- /dev/null +++ b/include/aare/ROIGeometry.hpp @@ -0,0 +1,48 @@ +#pragma once +#include "aare/DetectorGeometry.hpp" +#include "aare/defs.hpp" + +namespace aare { + +class DetectorGeometry; // forward declaration to avoid circular dependency + +/** + * @brief Class to hold the geometry of a region of interest (ROI) + */ +class ROIGeometry { + public: + /** @brief Constructor + * @param roi + * @param geometry general detector geometry + */ + 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; + + /// @brief Get the indices of modules in the ROI + const std::vector &module_indices_in_roi() const; + + size_t module_indices_in_roi(const size_t module_index) const; + + size_t pixels_x() const; + size_t pixels_y() const; + + private: + /// @brief Size of the ROI in pixels in x direction + size_t m_pixels_x{}; + /// @brief Size of the ROI in pixels in y direction + size_t m_pixels_y{}; + + DetectorGeometry &m_geometry; + + /// @brief Indices of modules included in the ROI + std::vector m_module_indices_in_roi; +}; + +} // namespace aare \ No newline at end of file diff --git a/include/aare/RawFile.hpp b/include/aare/RawFile.hpp index d0b164d..2bb7c90 100644 --- a/include/aare/RawFile.hpp +++ b/include/aare/RawFile.hpp @@ -4,6 +4,7 @@ #include "aare/FileInterface.hpp" #include "aare/Frame.hpp" #include "aare/NDArray.hpp" //for pixel map +#include "aare/ROIGeometry.hpp" #include "aare/RawMasterFile.hpp" #include "aare/RawSubFile.hpp" @@ -23,13 +24,17 @@ namespace aare { */ class RawFile : public FileInterface { - std::vector> m_subfiles; + std::vector>> + m_subfiles; // [ROI][modules_per_ROI] 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; + public: /** * @brief RawFile constructor @@ -43,26 +48,94 @@ class RawFile : public FileInterface { Frame read_frame() override; Frame read_frame(size_t frame_number) override; std::vector read_n(size_t n_frames) override; + + + /** + * @brief Read one ROI defined in the master file + * @param roi_index index of the ROI to read + * @return Frame + * @note the frame index is incremented after calling this function so + * reading rois one after the other wont work. + */ + Frame read_roi(const size_t roi_index); + + /** + * @brief Read all ROIs defined in the master file + * @return vector of Frames (one Frame per ROI) + */ + std::vectorread_rois(); + + /** + * @brief Read n frames for the given ROI index + * @param n_frames number of frames to read + * @param roi_index index of the ROI to read + * @return vector of Frames + */ + std::vector read_n_with_roi(const size_t n_frames, + const size_t roi_index); + void read_into(std::byte *image_buf) override; void read_into(std::byte *image_buf, size_t n_frames) override; // TODO! do we need to adapt the API? - void read_into(std::byte *image_buf, DetectorHeader *header); + void read_into(std::byte *image_buf, DetectorHeader *header = nullptr); void read_into(std::byte *image_buf, size_t n_frames, DetectorHeader *header); + void read_roi_into(std::byte *image_buf, const size_t roi_index, + const size_t frame_number, + DetectorHeader *header = + nullptr); // maybe just make get_frame_into public + size_t frame_number(size_t frame_index) override; size_t bytes_per_frame() override; + // TODO: mmh maybe also pass roi_index in Base class File. Leave it unused + // for NumpyFile and JungfrauDataFile + /** + * @brief bytes per frame for the given ROI + * @param roi_index index of the ROI + */ + size_t bytes_per_frame(const size_t roi_index); size_t pixels_per_frame() override; + /** + * @brief pixels per frame for the given ROI + * @param roi_index index of the ROI + */ + size_t pixels_per_frame(const size_t roi_index); size_t bytes_per_pixel() const; void seek(size_t frame_index) override; size_t tell() override; size_t total_frames() const override; size_t rows() const override; + /** + * @brief rows for the given ROI + * @param roi_index index of the ROI + */ + size_t rows(const size_t roi_index) const; size_t cols() const override; + /** + * @brief cols for the given ROI + * @param roi_index index of the ROI + */ + size_t cols(const size_t roi_index) const; size_t bitdepth() const override; size_t n_modules() const; - size_t n_modules_in_roi() const; + + /** + * @brief number of ROIs defined + */ + size_t num_rois() const; + + /** + * @brief get the ROI geometry for the given ROI index + * @param roi_index index of the ROI + */ + const ROIGeometry &roi_geometries(size_t roi_index) const; + + /** + * @brief number of modules in each ROI + */ + std::vector n_modules_in_roi() const; xy geometry() const; RawMasterFile master() const; @@ -79,21 +152,23 @@ class RawFile : public FileInterface { private: /** * @brief read the frame at the given frame index into the image buffer - * @param frame_number frame number to read - * @param image_buf buffer to store the frame + * @param frame_index frame number to read + * @param frame_buffer buffer to store the frame + * @param roi_index index of the ROI to read (default is 0 e.g. full frame) */ - - void get_frame_into(size_t frame_index, std::byte *frame_buffer, - DetectorHeader *header = nullptr); + void get_frame_into( + size_t frame_index, std::byte *frame_buffer, const size_t roi_index = 0, + DetectorHeader *header = nullptr); // TODO read_into updates it!!!! /** * @brief get the frame at the given frame index * @param frame_number frame number to read + * @param roi_index index of the ROI to read (default is 0 e.g. full frame) * @return Frame */ - Frame get_frame(size_t frame_index); + Frame get_frame(size_t frame_index, const size_t roi_index = 0); - void open_subfiles(); + void open_subfiles(const size_t roi_index); }; } // namespace aare \ No newline at end of file diff --git a/include/aare/RawMasterFile.hpp b/include/aare/RawMasterFile.hpp index c672a2e..37a5a53 100644 --- a/include/aare/RawMasterFile.hpp +++ b/include/aare/RawMasterFile.hpp @@ -2,11 +2,11 @@ #pragma once #include "aare/defs.hpp" #include +#include #include #include #include #include -#include #include using json = nlohmann::json; @@ -109,7 +109,7 @@ class RawMasterFile { std::optional m_number_of_rows; std::optional m_counter_mask; - std::optional m_roi; + std::optional> m_rois; public: RawMasterFile(const std::filesystem::path &fpath); @@ -141,11 +141,15 @@ class RawMasterFile { std::optional number_of_rows() const; std::optional counter_mask() const; + std::optional> rois() const; + std::optional roi() const; ScanParameters scan_parameters() const; - std::optional exptime() const { return m_exptime; } + std::optional exptime() const { + return m_exptime; + } std::chrono::nanoseconds period() const { return m_period; } private: diff --git a/python/src/bind_RawFile.hpp b/python/src/bind_RawFile.hpp new file mode 100644 index 0000000..991a737 --- /dev/null +++ b/python/src/bind_RawFile.hpp @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: MPL-2.0 +#include "aare/CtbRawFile.hpp" +#include "aare/File.hpp" +#include "aare/Frame.hpp" +#include "aare/RawFile.hpp" +#include "aare/RawMasterFile.hpp" +#include "aare/RawSubFile.hpp" + +#include "aare/defs.hpp" +#include "np_helper.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace py = pybind11; +using namespace ::aare; + +void define_raw_file_io_bindings(py::module &m) { + py::class_(m, "RawFile") + .def(py::init()) + .def("read_frame", + [](RawFile &self) { + if (self.n_modules_in_roi().size() > 1) { + throw std::runtime_error( + "File contains multiple ROIs - use read_ROIs()"); + } + + std::vector shape; + shape.reserve(2); + shape.push_back(self.rows()); + shape.push_back(self.cols()); + + // return headers from all subfiles + py::array_t header(self.n_modules()); + + py::array image = allocate_image_data(self.bytes_per_pixel(), shape); + self.read_into( + reinterpret_cast(image.mutable_data()), + header.mutable_data()); + + return py::make_tuple(header, image); + }) + .def( + "read_n", + [](RawFile &self, size_t n_frames) { + if (self.n_modules_in_roi().size() > 1) { + throw std::runtime_error( + "File contains multiple ROIs - use read_n_ROIs() to " + "read a specific ROI or use read_ROIs and " + "read one frame at a time."); + } + // adjust for actual frames left in the file + n_frames = + std::min(n_frames, self.total_frames() - self.tell()); + if (n_frames == 0) { + throw std::runtime_error("No frames left in file"); + } + std::vector shape{n_frames, self.rows(), self.cols()}; + + // return headers from all subfiles + py::array_t header; + if (self.n_modules() == 1) { + header = py::array_t(n_frames); + } else { + header = py::array_t( + {self.n_modules_in_roi()[0], n_frames}); + } + + py::array images = allocate_image_data(self.bytes_per_pixel(), shape); + self.read_into( + reinterpret_cast(images.mutable_data()), + n_frames, header.mutable_data()); + + return py::make_tuple(header, images); + }, + R"( + Read n frames from the file. + )") + + .def( + "read_roi", + [](RawFile &self, + const size_t roi_index) { + if (self.num_rois() == 0) { + throw std::runtime_error(LOCATION + "No ROIs defined."); + } + + if ( roi_index >= self.num_rois()) { + throw std::runtime_error(LOCATION + + "ROI index out of range."); + } + + // return headers from all subfiles + py::array_t header(self.n_modules_in_roi()[roi_index]); + + + std::vector shape; + shape.reserve(2); + shape.push_back(self.roi_geometries(roi_index).pixels_y()); + shape.push_back(self.roi_geometries(roi_index).pixels_x()); + + py::array image = allocate_image_data(self.bytes_per_pixel(), shape); + + + self.read_roi_into( + reinterpret_cast(image.mutable_data()), + roi_index, self.tell(), header.mutable_data()); + + self.seek(self.tell() + 1); // advance frame number so the + return py::make_tuple(header, image); + }, + R"( + Read one ROI from the current frame. + + Parameters + ---------- + + roi_index : int + Index of the ROI to read. + + Notes + ----- + + The method advances the frame number, so reading ROIs one after the other won't work. + + Returns + ------- + + tuple (header, image) + )", + py::arg("roi_index")) + + .def( + "read_rois", + [](RawFile &self) { + if (self.num_rois() == 0) { + throw std::runtime_error(LOCATION + "No ROIs defined."); + } + + + + size_t number_of_ROIs = self.num_rois(); + + // const uint8_t item_size = self.bytes_per_pixel(); + + std::vector images(number_of_ROIs); + + // return headers from all subfiles + std::vector> headers(number_of_ROIs); + for (size_t r = 0; r < number_of_ROIs; r++) { + headers[r] = + py::array_t(self.n_modules_in_roi()[r]); + } + + + + for (size_t r = 0; r < number_of_ROIs; r++) { + std::vector shape; + shape.reserve(2); + shape.push_back(self.roi_geometries(r).pixels_y()); + shape.push_back(self.roi_geometries(r).pixels_x()); + + images[r] = allocate_image_data(self.bytes_per_pixel(), shape); + + self.read_roi_into( + reinterpret_cast(images[r].mutable_data()), + r, self.tell(),headers[r].mutable_data()); + } + self.seek(self.tell() + 1); // advance frame number so the + return py::make_tuple(headers, images); + }, + R"( + Read all ROIs for specific frame. + + Parameters + ---------- + + frame_number : int + Frame number to read. + + roi_index : Optional[int] + Index of the ROI to read. If not provided, all ROIs are read. + + Returns + ------- + + list of numpy.ndarray + One array per ROI.)") + + .def( + "read_n_with_roi", + [](RawFile &self, const size_t num_frames, const size_t roi_index) { + if (self.num_rois() == 0) { + throw std::runtime_error(LOCATION + "No ROIs defined."); + } + + if (roi_index >= self.num_rois()) { + throw std::runtime_error(LOCATION + + "ROI index out of range."); + } + + // adjust for actual frames left in the file + size_t n_frames = + std::min(num_frames, self.total_frames() - self.tell()); + if (n_frames == 0) { + throw std::runtime_error("No frames left in file"); + } + std::vector shape{ + n_frames, self.roi_geometries(roi_index).pixels_y(), + self.roi_geometries(roi_index).pixels_x()}; + + // return headers from all subfiles + auto n_mod = self.n_modules_in_roi()[roi_index]; + py::array_t header({ + n_frames, n_mod} + ); + + + py::array images = allocate_image_data(self.bytes_per_pixel(), shape); + + auto image_buffer = + reinterpret_cast(images.mutable_data()); + auto h = header.mutable_data(); + + for (size_t i = 0; i < n_frames; i++) { + self.read_roi_into(image_buffer, roi_index, self.tell(), h); + + self.seek(self.tell() + 1); // advance frame number + image_buffer += self.bytes_per_frame(roi_index); + h += n_mod; + } + + return py::make_tuple(header, images); + }, + R"( + Read n frames for a specific ROI + + Parameters + ---------- + + num_frames : int + Number of frames to read. + roi_index : int + Index of the ROI to read. + + Returns + ------- + + three dimensional numpy.ndarray.)", + py::arg("num_frames"), py::kw_only(), py::arg("roi_index")) + + .def("frame_number", &RawFile::frame_number) + .def("bytes_per_frame", + static_cast(&RawFile::bytes_per_frame)) + .def( + "bytes_per_frame", + [](RawFile &self, const size_t roi_index) { + return self.bytes_per_frame(roi_index); + }, + R"( + Bytes per frame for the given ROI. + )") + .def("pixels_per_frame", + static_cast(&RawFile::pixels_per_frame)) + .def( + "pixels_per_frame", + [](RawFile &self, const size_t roi_index) { + return self.pixels_per_frame(roi_index); + }, + R"( + Pixels per frame for the given ROI. + )") + .def_property_readonly("bytes_per_pixel", &RawFile::bytes_per_pixel) + .def("seek", &RawFile::seek, R"( + Seek to a frame index in file. + )") + .def("tell", &RawFile::tell, R"( + Return the current frame number.)") + .def_property_readonly("total_frames", &RawFile::total_frames) + .def("rows", static_cast(&RawFile::rows)) + .def( + "rows", + [](RawFile &self, const size_t roi_index) { + return self.rows(roi_index); + }, + R"( + Rows for the given ROI. + )") + .def("cols", static_cast(&RawFile::cols)) + .def( + "cols", + [](RawFile &self, const size_t roi_index) { + return self.cols(roi_index); + }, + R"( + Cols for the given ROI. + )") + .def_property_readonly("bitdepth", &RawFile::bitdepth) + .def_property_readonly("geometry", &RawFile::geometry) + .def_property_readonly("detector_type", &RawFile::detector_type) + .def_property_readonly("master", &RawFile::master) + .def_property_readonly("n_modules", &RawFile::n_modules) + .def_property_readonly("n_modules_in_roi", &RawFile::n_modules_in_roi) + .def_property_readonly("num_rois", &RawFile::num_rois); +} \ No newline at end of file diff --git a/python/src/module.cpp b/python/src/module.cpp index f78d2d4..dfd25dc 100644 --- a/python/src/module.cpp +++ b/python/src/module.cpp @@ -11,6 +11,7 @@ #include "bind_ClusterVector.hpp" #include "bind_Eta.hpp" #include "bind_Interpolator.hpp" +#include "bind_RawFile.hpp" #include "bind_calibration.hpp" // TODO! migrate the other names @@ -20,7 +21,6 @@ #include "jungfrau_data_file.hpp" #include "pedestal.hpp" #include "pixel_map.hpp" -#include "raw_file.hpp" #include "raw_master_file.hpp" #include "raw_sub_file.hpp" #include "var_cluster.hpp" diff --git a/python/src/np_helper.hpp b/python/src/np_helper.hpp index 9283be2..cdaa03e 100644 --- a/python/src/np_helper.hpp +++ b/python/src/np_helper.hpp @@ -84,4 +84,21 @@ struct fmt_format_trait> { }; template -auto fmt_format = fmt_format_trait::value(); \ No newline at end of file +auto fmt_format = fmt_format_trait::value(); + +/** +* Helper function to allocate image data given item size and shape +* used when we want to fill a numpy array and return to python +*/ +py::array allocate_image_data(size_t item_size, + const std::vector &shape) { + py::array image_data; + if (item_size == 1) { + image_data = py::array_t(shape); + } else if (item_size == 2) { + image_data = py::array_t(shape); + } else if (item_size == 4) { + image_data = py::array_t(shape); + } + return image_data; +} \ No newline at end of file diff --git a/python/src/raw_file.hpp b/python/src/raw_file.hpp deleted file mode 100644 index 64cc9aa..0000000 --- a/python/src/raw_file.hpp +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -#include "aare/CtbRawFile.hpp" -#include "aare/File.hpp" -#include "aare/Frame.hpp" -#include "aare/RawFile.hpp" -#include "aare/RawMasterFile.hpp" -#include "aare/RawSubFile.hpp" - -#include "aare/defs.hpp" -// #include "aare/fClusterFileV2.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace py = pybind11; -using namespace ::aare; - -void define_raw_file_io_bindings(py::module &m) { - py::class_(m, "RawFile") - .def(py::init()) - .def("read_frame", - [](RawFile &self) { - py::array image; - std::vector shape; - shape.reserve(2); - shape.push_back(self.rows()); - shape.push_back(self.cols()); - - // return headers from all subfiles - py::array_t header(self.n_modules()); - - const uint8_t item_size = self.bytes_per_pixel(); - if (item_size == 1) { - image = py::array_t(shape); - } else if (item_size == 2) { - image = py::array_t(shape); - } else if (item_size == 4) { - image = py::array_t(shape); - } - self.read_into( - reinterpret_cast(image.mutable_data()), - header.mutable_data()); - - return py::make_tuple(header, image); - }) - .def( - "read_n", - [](RawFile &self, size_t n_frames) { - // adjust for actual frames left in the file - n_frames = - std::min(n_frames, self.total_frames() - self.tell()); - if (n_frames == 0) { - throw std::runtime_error("No frames left in file"); - } - std::vector shape{n_frames, self.rows(), self.cols()}; - - // return headers from all subfiles - py::array_t header; - if (self.n_modules() == 1) { - header = py::array_t(n_frames); - } else { - header = py::array_t( - {self.n_modules_in_roi(), n_frames}); - } - // py::array_t header({self.n_mod(), n_frames}); - - py::array image; - const uint8_t item_size = self.bytes_per_pixel(); - if (item_size == 1) { - image = py::array_t(shape); - } else if (item_size == 2) { - image = py::array_t(shape); - } else if (item_size == 4) { - image = py::array_t(shape); - } - self.read_into( - reinterpret_cast(image.mutable_data()), - n_frames, header.mutable_data()); - - return py::make_tuple(header, image); - }, - R"( - Read n frames from the file. - )") - .def("frame_number", &RawFile::frame_number) - .def_property_readonly("bytes_per_frame", &RawFile::bytes_per_frame) - .def_property_readonly("pixels_per_frame", &RawFile::pixels_per_frame) - .def_property_readonly("bytes_per_pixel", &RawFile::bytes_per_pixel) - .def("seek", &RawFile::seek, R"( - Seek to a frame index in file. - )") - .def("tell", &RawFile::tell, R"( - Return the current frame number.)") - .def_property_readonly("total_frames", &RawFile::total_frames) - .def_property_readonly("rows", &RawFile::rows) - .def_property_readonly("cols", &RawFile::cols) - .def_property_readonly("bitdepth", &RawFile::bitdepth) - .def_property_readonly("geometry", &RawFile::geometry) - .def_property_readonly("detector_type", &RawFile::detector_type) - .def_property_readonly("master", &RawFile::master) - .def_property_readonly("n_modules", &RawFile::n_modules) - .def_property_readonly("n_modules_in_roi", &RawFile::n_modules_in_roi); -} \ No newline at end of file diff --git a/python/tests/test_RawFile.py b/python/tests/test_RawFile.py index a30941a..b9cad77 100644 --- a/python/tests/test_RawFile.py +++ b/python/tests/test_RawFile.py @@ -4,14 +4,46 @@ from aare import RawFile import numpy as np @pytest.mark.withdata -def test_read_rawfile_with_roi(test_data_path): +def test_read_rawfile_with_roi_spanning_over_one_module(test_data_path): - with RawFile(test_data_path / "raw/ROITestData/SingleChipROI/Data_master_0.json") as f: - headers, frames = f.read() + with RawFile(test_data_path / "raw/ROITestData/SingleChipROI/Data_master_0.json") as f: + headers, frames = f.read() - assert headers.size == 10100 - assert frames.shape == (10100, 256, 256) - + num_rois = f.num_rois + assert num_rois == 1 + assert headers.size == 10100 + assert frames.shape == (10100, 256, 256) + +@pytest.mark.withdata +def test_read_rawfile_with_multiple_rois(test_data_path): + with RawFile(test_data_path / "raw/ROITestData/MultipleROIs/run_master_0.json") as f: + num_rois = f.num_rois + + #cannot read_frame for multiple ROIs + with pytest.raises(RuntimeError): + f.read_frame() + + assert f.tell() == 0 + frames = f.read_ROIs() + assert num_rois == 2 + assert len(frames) == 2 + assert frames[0].shape == (301, 101) + assert frames[1].shape == (101, 101) + + assert f.tell() == 1 + + # read multiple ROIs at once + frames = f.read_n_ROIs(2, 1) + assert frames.shape == (2, 101, 101) + + assert f.tell() == 3 + + # read specific ROI + frame = f.read_ROIs(1, 0) + assert len(frame) == 1 + assert frame[0].shape == (301, 101) + assert f.tell() == 2 + @pytest.mark.withdata def test_read_rawfile_quad_eiger_and_compare_to_numpy(test_data_path): diff --git a/src/DetectorGeometry.cpp b/src/DetectorGeometry.cpp index 4e3b367..9a35195 100644 --- a/src/DetectorGeometry.cpp +++ b/src/DetectorGeometry.cpp @@ -41,31 +41,16 @@ DetectorGeometry::DetectorGeometry(const xy &geometry, m_modules_x = geometry.col; m_modules_y = geometry.row; m_pixels_y += static_cast((geometry.row - 1) * cfg.module_gap_row); - - modules_in_roi.resize(num_modules); - std::iota(modules_in_roi.begin(), modules_in_roi.end(), 0); } size_t DetectorGeometry::n_modules() const { return m_modules_x * m_modules_y; } -size_t DetectorGeometry::n_modules_in_roi() const { - return modules_in_roi.size(); -}; - size_t DetectorGeometry::pixels_x() const { return m_pixels_x; } size_t DetectorGeometry::pixels_y() const { return m_pixels_y; } size_t DetectorGeometry::modules_x() const { return m_modules_x; }; size_t DetectorGeometry::modules_y() const { return m_modules_y; }; -const std::vector &DetectorGeometry::get_modules_in_roi() const { - return modules_in_roi; -} - -ssize_t DetectorGeometry::get_modules_in_roi(const size_t index) const { - return modules_in_roi[index]; -} - const std::vector & DetectorGeometry::get_module_geometries() const { return module_geometries; @@ -76,77 +61,8 @@ DetectorGeometry::get_module_geometries(const size_t index) const { return module_geometries[index]; } -void DetectorGeometry::update_geometry_with_roi(ROI roi) { -#ifdef AARE_VERBOSE - fmt::println("update_geometry_with_roi() called with ROI: {} {} {} {}", - roi.xmin, roi.xmax, roi.ymin, roi.ymax); - fmt::println("Geometry: {} {} {} {} {} {}", m_modules_x, m_modules_y, - m_pixels_x, m_pixels_y, cfg.module_gap_row, - cfg.module_gap_col); - -#endif - - modules_in_roi.clear(); - modules_in_roi.reserve(m_modules_x * m_modules_y); - int pos_y = 0; - int pos_y_increment = 0; - for (size_t row = 0; row < m_modules_y; row++) { - int pos_x = 0; - for (size_t col = 0; col < m_modules_x; col++) { - auto &m = module_geometries[row * m_modules_x + col]; - - auto original_height = m.height; - auto original_width = m.width; - - // module is to the left of the roi - if (m.origin_x + m.width < roi.xmin) { - m.width = 0; - - // roi is in module - } else { - // here we only arrive when the roi is in or to the left of - // the module - if (roi.xmin > m.origin_x) { - m.width -= roi.xmin - m.origin_x; - } - if (roi.xmax < m.origin_x + original_width) { - m.width -= m.origin_x + original_width - roi.xmax; - } - m.origin_x = pos_x; - pos_x += m.width; - } - - if (m.origin_y + m.height < roi.ymin) { - m.height = 0; - } else { - if ((roi.ymin > m.origin_y) && - (roi.ymin < m.origin_y + m.height)) { - m.height -= roi.ymin - m.origin_y; - } - if (roi.ymax < m.origin_y + original_height) { - m.height -= m.origin_y + original_height - roi.ymax; - } - m.origin_y = pos_y; - pos_y_increment = m.height; - } - - if (m.height != 0 && m.width != 0) { - modules_in_roi.push_back(row * m_modules_x + col); - } - -#ifdef AARE_VERBOSE - fmt::println("Module {} {} {} {}", m.origin_x, m.origin_y, m.width, - m.height); -#endif - } - // increment pos_y - pos_y += pos_y_increment; - } - - // m_rows = roi.height(); - // m_cols = roi.width(); - m_pixels_x = roi.width(); - m_pixels_y = roi.height(); +ModuleGeometry &DetectorGeometry::get_module_geometries(const size_t index) { + return module_geometries[index]; } } // namespace aare \ No newline at end of file diff --git a/src/DetectorGeometry.test.cpp b/src/DetectorGeometry.test.cpp index ba8b620..12eab99 100644 --- a/src/DetectorGeometry.test.cpp +++ b/src/DetectorGeometry.test.cpp @@ -7,17 +7,20 @@ #include #include "aare/DetectorGeometry.hpp" +#include "aare/ROIGeometry.hpp" #include "test_config.hpp" -TEST_CASE("Simple ROIs on one module") { - // DetectorGeometry update_geometry_with_roi(DetectorGeometry geo, aare::ROI - // roi) +TEST_CASE("Simple ROIs on one module", "[DetectorGeometry]") { aare::DetectorGeometry geo(aare::xy{1, 1}, 1024, 512); REQUIRE(geo.get_module_geometries(0).origin_x == 0); REQUIRE(geo.get_module_geometries(0).origin_y == 0); REQUIRE(geo.get_module_geometries(0).width == 1024); REQUIRE(geo.get_module_geometries(0).height == 512); + REQUIRE(geo.modules_x() == 1); + REQUIRE(geo.modules_y() == 1); + REQUIRE(geo.pixels_x() == 1024); + REQUIRE(geo.pixels_y() == 512); SECTION("ROI is the whole module") { aare::ROI roi; @@ -25,14 +28,18 @@ TEST_CASE("Simple ROIs on one module") { roi.xmax = 1024; roi.ymin = 0; roi.ymax = 512; - geo.update_geometry_with_roi(roi); - REQUIRE(geo.pixels_x() == 1024); - REQUIRE(geo.pixels_y() == 512); - REQUIRE(geo.modules_x() == 1); - REQUIRE(geo.modules_y() == 1); - REQUIRE(geo.get_module_geometries(0).height == 512); - REQUIRE(geo.get_module_geometries(0).width == 1024); + aare::ROIGeometry roi_geometry(roi, geo); + + REQUIRE(roi_geometry.pixels_x() == 1024); + REQUIRE(roi_geometry.pixels_y() == 512); + REQUIRE(roi_geometry.num_modules_in_roi() == 1); + auto module_geometry = + geo.get_module_geometries(roi_geometry.module_indices_in_roi(0)); + REQUIRE(module_geometry.height == 512); + REQUIRE(module_geometry.width == 1024); + REQUIRE(module_geometry.origin_x == 0); + REQUIRE(module_geometry.origin_y == 0); } SECTION("ROI is the top left corner of the module") { aare::ROI roi; @@ -40,51 +47,22 @@ TEST_CASE("Simple ROIs on one module") { roi.xmax = 200; roi.ymin = 150; roi.ymax = 200; - geo.update_geometry_with_roi(roi); - REQUIRE(geo.pixels_x() == 100); - REQUIRE(geo.pixels_y() == 50); - REQUIRE(geo.modules_x() == 1); - REQUIRE(geo.modules_y() == 1); - REQUIRE(geo.get_module_geometries(0).height == 50); - REQUIRE(geo.get_module_geometries(0).width == 100); - } + aare::ROIGeometry roi_geometry(roi, geo); - SECTION("ROI is a small square") { - aare::ROI roi; - roi.xmin = 1000; - roi.xmax = 1010; - roi.ymin = 500; - roi.ymax = 510; - geo.update_geometry_with_roi(roi); - - REQUIRE(geo.pixels_x() == 10); - REQUIRE(geo.pixels_y() == 10); - REQUIRE(geo.modules_x() == 1); - REQUIRE(geo.modules_y() == 1); - REQUIRE(geo.get_module_geometries(0).height == 10); - REQUIRE(geo.get_module_geometries(0).width == 10); - } - SECTION("ROI is a few columns") { - aare::ROI roi; - roi.xmin = 750; - roi.xmax = 800; - roi.ymin = 0; - roi.ymax = 512; - geo.update_geometry_with_roi(roi); - - REQUIRE(geo.pixels_x() == 50); - REQUIRE(geo.pixels_y() == 512); - REQUIRE(geo.modules_x() == 1); - REQUIRE(geo.modules_y() == 1); - REQUIRE(geo.get_module_geometries(0).height == 512); - REQUIRE(geo.get_module_geometries(0).width == 50); + REQUIRE(roi_geometry.pixels_x() == 100); + REQUIRE(roi_geometry.pixels_y() == 50); + REQUIRE(roi_geometry.num_modules_in_roi() == 1); + auto module_geometry = + geo.get_module_geometries(roi_geometry.module_indices_in_roi(0)); + REQUIRE(module_geometry.height == 50); + REQUIRE(module_geometry.width == 100); + REQUIRE(module_geometry.origin_x == 0); + REQUIRE(module_geometry.origin_y == 0); } } -TEST_CASE("Two modules side by side") { - // DetectorGeometry update_geometry_with_roi(DetectorGeometry geo, aare::ROI - // roi) +TEST_CASE("Two modules side by side", "[DetectorGeometry]") { aare::DetectorGeometry geo(aare::xy{1, 2}, 1024, 512); REQUIRE(geo.get_module_geometries(0).origin_x == 0); @@ -93,6 +71,10 @@ TEST_CASE("Two modules side by side") { REQUIRE(geo.get_module_geometries(0).height == 512); REQUIRE(geo.get_module_geometries(1).origin_x == 1024); REQUIRE(geo.get_module_geometries(1).origin_y == 0); + REQUIRE(geo.modules_x() == 2); + REQUIRE(geo.modules_y() == 1); + REQUIRE(geo.pixels_x() == 2048); + REQUIRE(geo.pixels_y() == 512); SECTION("ROI is the whole image") { aare::ROI roi; @@ -100,12 +82,24 @@ TEST_CASE("Two modules side by side") { roi.xmax = 2048; roi.ymin = 0; roi.ymax = 512; - geo.update_geometry_with_roi(roi); - REQUIRE(geo.pixels_x() == 2048); - REQUIRE(geo.pixels_y() == 512); - REQUIRE(geo.modules_x() == 2); - REQUIRE(geo.modules_y() == 1); + aare::ROIGeometry roi_geometry(roi, geo); + + REQUIRE(roi_geometry.pixels_x() == 2048); + REQUIRE(roi_geometry.pixels_y() == 512); + REQUIRE(roi_geometry.num_modules_in_roi() == 2); + + auto module_geometry_0 = geo.get_module_geometries(0); + auto module_geometry_1 = geo.get_module_geometries(1); + + REQUIRE(module_geometry_0.height == 512); + REQUIRE(module_geometry_0.width == 1024); + REQUIRE(module_geometry_1.height == 512); + REQUIRE(module_geometry_1.width == 1024); + REQUIRE(module_geometry_0.origin_x == 0); + REQUIRE(module_geometry_1.origin_x == 1024); + REQUIRE(module_geometry_0.origin_y == 0); + REQUIRE(module_geometry_1.origin_y == 0); } SECTION("rectangle on both modules") { aare::ROI roi; @@ -113,28 +107,30 @@ TEST_CASE("Two modules side by side") { roi.xmax = 1300; roi.ymin = 200; roi.ymax = 499; - geo.update_geometry_with_roi(roi); - REQUIRE(geo.pixels_x() == 500); - REQUIRE(geo.pixels_y() == 299); - REQUIRE(geo.modules_x() == 2); - REQUIRE(geo.modules_y() == 1); - REQUIRE(geo.get_module_geometries(0).height == 299); - REQUIRE(geo.get_module_geometries(0).width == 224); - REQUIRE(geo.get_module_geometries(1).height == 299); - REQUIRE(geo.get_module_geometries(1).width == 276); + aare::ROIGeometry roi_geometry(roi, geo); + + REQUIRE(roi_geometry.pixels_x() == 500); + REQUIRE(roi_geometry.pixels_y() == 299); + REQUIRE(roi_geometry.num_modules_in_roi() == 2); + + auto module_geometry_0 = geo.get_module_geometries(0); + auto module_geometry_1 = geo.get_module_geometries(1); + + REQUIRE(module_geometry_0.height == 299); + REQUIRE(module_geometry_0.width == 224); + REQUIRE(module_geometry_1.height == 299); + REQUIRE(module_geometry_1.width == 276); + REQUIRE(module_geometry_0.origin_x == 0); + REQUIRE(module_geometry_1.origin_x == 224); + REQUIRE(module_geometry_0.origin_y == 0); + REQUIRE(module_geometry_1.origin_y == 0); } } -TEST_CASE("Three modules side by side") { - // DetectorGeometry update_geometry_with_roi(DetectorGeometry geo, aare::ROI - // roi) +TEST_CASE("Three modules side by side", "[DetectorGeometry]") { + aare::DetectorGeometry geo(aare::xy{1, 3}, 1024, 512); - aare::ROI roi; - roi.xmin = 700; - roi.xmax = 2500; - roi.ymin = 0; - roi.ymax = 123; REQUIRE(geo.get_module_geometries(0).origin_x == 0); REQUIRE(geo.get_module_geometries(0).origin_y == 0); @@ -142,24 +138,78 @@ TEST_CASE("Three modules side by side") { REQUIRE(geo.get_module_geometries(0).height == 512); REQUIRE(geo.get_module_geometries(1).origin_x == 1024); REQUIRE(geo.get_module_geometries(2).origin_x == 2048); - - geo.update_geometry_with_roi(roi); - - REQUIRE(geo.pixels_x() == 1800); - REQUIRE(geo.pixels_y() == 123); REQUIRE(geo.modules_x() == 3); REQUIRE(geo.modules_y() == 1); - REQUIRE(geo.get_module_geometries(0).height == 123); - REQUIRE(geo.get_module_geometries(0).width == 324); - REQUIRE(geo.get_module_geometries(1).height == 123); - REQUIRE(geo.get_module_geometries(1).width == 1024); - REQUIRE(geo.get_module_geometries(2).height == 123); - REQUIRE(geo.get_module_geometries(2).width == 452); + REQUIRE(geo.pixels_x() == 3072); + REQUIRE(geo.pixels_y() == 512); + + SECTION("ROI overlapping all three modules") { + aare::ROI roi; + roi.xmin = 500; + roi.xmax = 2500; + roi.ymin = 0; + roi.ymax = 123; + + aare::ROIGeometry roi_geometry(roi, geo); + REQUIRE(roi_geometry.pixels_x() == 2000); + REQUIRE(roi_geometry.pixels_y() == 123); + REQUIRE(roi_geometry.num_modules_in_roi() == 3); + + auto module_geometry_0 = geo.get_module_geometries(0); + auto module_geometry_1 = geo.get_module_geometries(1); + auto module_geometry_2 = geo.get_module_geometries(2); + + REQUIRE(module_geometry_0.height == 123); + REQUIRE(module_geometry_0.width == 524); + REQUIRE(module_geometry_0.origin_x == 0); + REQUIRE(module_geometry_0.origin_y == 0); + + REQUIRE(module_geometry_1.height == 123); + REQUIRE(module_geometry_1.width == 1024); + REQUIRE(module_geometry_1.origin_x == 524); + REQUIRE(module_geometry_1.origin_y == 0); + + REQUIRE(module_geometry_2.height == 123); + REQUIRE(module_geometry_2.width == 452); + REQUIRE(module_geometry_2.origin_x == 1548); + REQUIRE(module_geometry_2.origin_y == 0); + } + + SECTION("ROI overlapping last two modules") { + aare::ROI roi; + roi.xmin = 1050; + roi.xmax = 2500; + roi.ymin = 0; + roi.ymax = 123; + + aare::ROIGeometry roi_geometry(roi, geo); + + REQUIRE(roi_geometry.pixels_x() == 1450); + REQUIRE(roi_geometry.pixels_y() == 123); + REQUIRE(roi_geometry.num_modules_in_roi() == 2); + + auto module_geometry_0 = geo.get_module_geometries(0); + auto module_geometry_1 = geo.get_module_geometries(1); + auto module_geometry_2 = geo.get_module_geometries(2); + + REQUIRE(module_geometry_0.height == 512); + REQUIRE(module_geometry_0.width == 1024); + REQUIRE(module_geometry_0.origin_x == 0); + REQUIRE(module_geometry_0.origin_y == 0); + + REQUIRE(module_geometry_1.height == 123); + REQUIRE(module_geometry_1.width == 998); + REQUIRE(module_geometry_1.origin_x == 0); + REQUIRE(module_geometry_1.origin_y == 0); + + REQUIRE(module_geometry_2.height == 123); + REQUIRE(module_geometry_2.width == 452); + REQUIRE(module_geometry_2.origin_x == 998); + REQUIRE(module_geometry_2.origin_y == 0); + } } -TEST_CASE("Four modules as a square") { - // DetectorGeometry update_geometry_with_roi(DetectorGeometry geo, aare::ROI - // roi) +TEST_CASE("Four modules as a square", "[DetectorGeometry]") { aare::DetectorGeometry geo(aare::xy{2, 2}, 1024, 512, aare::xy{1, 2}); aare::ROI roi; roi.xmin = 500; @@ -177,19 +227,99 @@ TEST_CASE("Four modules as a square") { REQUIRE(geo.get_module_geometries(2).origin_y == 512); REQUIRE(geo.get_module_geometries(3).origin_x == 1024); REQUIRE(geo.get_module_geometries(3).origin_y == 512); - - geo.update_geometry_with_roi(roi); - - REQUIRE(geo.pixels_x() == 1500); - REQUIRE(geo.pixels_y() == 100); REQUIRE(geo.modules_x() == 2); REQUIRE(geo.modules_y() == 2); + REQUIRE(geo.pixels_x() == 2048); + REQUIRE(geo.pixels_y() == 1024); + + aare::ROIGeometry roi_geometry(roi, geo); + + REQUIRE(roi_geometry.pixels_x() == 1500); + REQUIRE(roi_geometry.pixels_y() == 100); + REQUIRE(roi_geometry.num_modules_in_roi() == 4); + REQUIRE(geo.get_module_geometries(0).height == 12); REQUIRE(geo.get_module_geometries(0).width == 524); + REQUIRE(geo.get_module_geometries(0).origin_x == 0); + REQUIRE(geo.get_module_geometries(0).origin_y == 0); + REQUIRE(geo.get_module_geometries(1).height == 12); REQUIRE(geo.get_module_geometries(1).width == 976); + REQUIRE(geo.get_module_geometries(1).origin_x == 524); + REQUIRE(geo.get_module_geometries(1).origin_y == 0); + REQUIRE(geo.get_module_geometries(2).height == 88); REQUIRE(geo.get_module_geometries(2).width == 524); + REQUIRE(geo.get_module_geometries(2).origin_x == 0); + REQUIRE(geo.get_module_geometries(2).origin_y == 12); + REQUIRE(geo.get_module_geometries(3).height == 88); REQUIRE(geo.get_module_geometries(3).width == 976); + REQUIRE(geo.get_module_geometries(3).origin_x == 524); + REQUIRE(geo.get_module_geometries(3).origin_y == 12); +} + +TEST_CASE("check if module in ROI", "[DetectorGeometry]") { + aare::DetectorGeometry geo(aare::xy{2, 2}, 1024, 512, aare::xy{1, 2}); + + SECTION("all modules in ROI") { + aare::ROI roi; + roi.xmin = 500; + roi.xmax = 2000; + roi.ymin = 500; + roi.ymax = 600; + + REQUIRE(geo.get_module_geometries(0).module_in_roi(roi) == true); + REQUIRE(geo.get_module_geometries(1).module_in_roi(roi) == true); + REQUIRE(geo.get_module_geometries(2).module_in_roi(roi) == true); + REQUIRE(geo.get_module_geometries(3).module_in_roi(roi) == true); + } + SECTION("some modules on border of ROI") { + aare::ROI roi; + roi.xmin = 1024; + roi.xmax = 2000; + roi.ymin = 500; + roi.ymax = 600; + + REQUIRE(geo.get_module_geometries(0).module_in_roi(roi) == false); + REQUIRE(geo.get_module_geometries(1).module_in_roi(roi) == true); + REQUIRE(geo.get_module_geometries(2).module_in_roi(roi) == false); + REQUIRE(geo.get_module_geometries(3).module_in_roi(roi) == true); + } + SECTION("one module on border of ROI") { + aare::ROI roi; + roi.xmin = 1025; + roi.xmax = 2000; + roi.ymin = 513; + roi.ymax = 600; + + REQUIRE(geo.get_module_geometries(0).module_in_roi(roi) == false); + REQUIRE(geo.get_module_geometries(1).module_in_roi(roi) == false); + REQUIRE(geo.get_module_geometries(2).module_in_roi(roi) == false); + REQUIRE(geo.get_module_geometries(3).module_in_roi(roi) == true); + } + SECTION("some modules to the left of ROI") { + aare::ROI roi; + roi.xmin = 500; + roi.xmax = 1024; + roi.ymin = 500; + roi.ymax = 600; + + REQUIRE(geo.get_module_geometries(0).module_in_roi(roi) == true); + REQUIRE(geo.get_module_geometries(1).module_in_roi(roi) == false); + REQUIRE(geo.get_module_geometries(2).module_in_roi(roi) == true); + REQUIRE(geo.get_module_geometries(3).module_in_roi(roi) == false); + } + SECTION("all modules in ROI") { + aare::ROI roi; + roi.xmin = 500; + roi.xmax = 2000; + roi.ymin = 10; + roi.ymax = 513; + + REQUIRE(geo.get_module_geometries(0).module_in_roi(roi) == true); + REQUIRE(geo.get_module_geometries(1).module_in_roi(roi) == true); + REQUIRE(geo.get_module_geometries(2).module_in_roi(roi) == true); + REQUIRE(geo.get_module_geometries(3).module_in_roi(roi) == true); + } } \ No newline at end of file diff --git a/src/ROIGeometry.cpp b/src/ROIGeometry.cpp new file mode 100644 index 0000000..56611d1 --- /dev/null +++ b/src/ROIGeometry.cpp @@ -0,0 +1,42 @@ +#include "aare/ROIGeometry.hpp" +#include + +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); + } + } +}; + +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(); +} + +const std::vector &ROIGeometry::module_indices_in_roi() const { + return m_module_indices_in_roi; +} + +size_t ROIGeometry::module_indices_in_roi(const size_t module_index) const { + return m_module_indices_in_roi.at(module_index); +} + +size_t ROIGeometry::pixels_x() const { return m_pixels_x; } +size_t ROIGeometry::pixels_y() const { return m_pixels_y; } + +} // namespace aare \ No newline at end of file diff --git a/src/RawFile.cpp b/src/RawFile.cpp index 89bb74b..e8f3057 100644 --- a/src/RawFile.cpp +++ b/src/RawFile.cpp @@ -2,6 +2,7 @@ #include "aare/RawFile.hpp" #include "aare/DetectorGeometry.hpp" #include "aare/PixelMap.hpp" +#include "aare/ROIGeometry.hpp" #include "aare/algorithm.hpp" #include "aare/defs.hpp" #include "aare/logger.hpp" @@ -17,28 +18,98 @@ 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_mode = mode; + m_subfiles.resize(m_master.rois().has_value() ? m_master.rois()->size() + : 1); + if (mode == "r") { - if (m_master.roi()) { - m_geometry.update_geometry_with_roi(m_master.roi().value()); + 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; + } + + } else { + // no ROI use full detector + m_ROI_geometries.reserve(1); + m_ROI_geometries.push_back(ROIGeometry(m_geometry)); + open_subfiles(0); } - open_subfiles(); } else { throw std::runtime_error(LOCATION + " Unsupported mode. Can only read RawFiles."); } } -Frame RawFile::read_frame() { return get_frame(m_current_frame++); } +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()) { + 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(); + + std::vector frames; + frames.reserve(num_rois); + + for (size_t roi_idx = 0; roi_idx < num_rois; ++roi_idx) { + frames.push_back(get_frame(m_current_frame, roi_idx)); + } + ++m_current_frame; + + return frames; +} + + + +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."); + } + 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) { + throw std::runtime_error( + LOCATION + "Multiple ROIs defined in the master file. " + "Use read_ROIs(const size_t frame_number) instead."); + } seek(frame_number); return read_frame(); } 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) { + throw std::runtime_error(LOCATION + + "Cannot use read_into for multiple ROIs."); + } for (size_t i = 0; i < n_frames; i++) { this->get_frame_into(m_current_frame++, image_buf); @@ -47,33 +118,83 @@ 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) { + throw std::runtime_error(LOCATION + + "Cannot use read_into for multiple ROIs. Use " + "read_roi_into() for a single ROI instead."); + } return get_frame_into(m_current_frame++, image_buf); } -void RawFile::read_into(std::byte *image_buf, DetectorHeader *header) { +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."); + } + return get_frame_into(frame_number, image_buf, roi_index, header); +} - return get_frame_into(m_current_frame++, image_buf, header); +void RawFile::read_into(std::byte *image_buf, DetectorHeader *header) { + if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + throw std::runtime_error(LOCATION + + "Cannot use read_into for multiple ROIs. Use " + "read_roi_into() for a single ROI instead."); + } + return get_frame_into(m_current_frame++, image_buf, 0, header); } 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) { + 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 + } + for (size_t i = 0; i < n_frames; i++) { - this->get_frame_into(m_current_frame++, image_buf, header); + this->get_frame_into(m_current_frame++, image_buf, 0, header); image_buf += bytes_per_frame(); if (header) - header += m_geometry.n_modules_in_roi(); + header += m_ROI_geometries[0].num_modules_in_roi(); } } size_t RawFile::bytes_per_frame() { - return m_geometry.pixels_x() * m_geometry.pixels_y() * m_master.bitdepth() / + if (m_master.rois().has_value() && m_master.rois()->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. "); + } + return bytes_per_frame(0); +} + +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; } + size_t RawFile::pixels_per_frame() { - // return m_rows * m_cols; - return m_geometry.pixels_x() * m_geometry.pixels_y(); + if (m_master.rois().has_value() && m_master.rois()->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. "); + } + return pixels_per_frame(0); +} + +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(); } DetectorType RawFile::detector_type() const { return m_master.detector_type(); } @@ -93,8 +214,29 @@ 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::rows() const { return m_geometry.pixels_y(); } -size_t RawFile::cols() const { return m_geometry.pixels_x(); } + +size_t RawFile::rows() const { + if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + throw std::runtime_error(LOCATION + + "Pass the desired roi_index to rows to get " + "rows for the specific ROI. "); + } + return rows(0); +} +size_t RawFile::rows(const size_t roi_index) const { + return m_ROI_geometries.at(roi_index).pixels_y(); +} +size_t RawFile::cols() const { + if (m_master.rois().has_value() && m_master.rois()->size() > 1) { + throw std::runtime_error(LOCATION + + "Pass the desired roi_index to cols to get " + "cols for the specific ROI. "); + } + return cols(0); +} +size_t RawFile::cols(const size_t roi_index) const { + return m_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()), @@ -102,19 +244,46 @@ xy RawFile::geometry() const { } size_t RawFile::n_modules() const { return m_geometry.n_modules(); }; -size_t RawFile::n_modules_in_roi() const { - return m_geometry.n_modules_in_roi(); + +size_t RawFile::num_rois() const { + if (m_master.rois().has_value()) { + return m_master.rois()->size(); + } else { + return 0; + } }; -void RawFile::open_subfiles() { - if (m_mode == "r") - for (size_t i : m_geometry.get_modules_in_roi()) { - auto pos = m_geometry.get_module_geometries(i); - m_subfiles.emplace_back(std::make_unique( +const ROIGeometry &RawFile::roi_geometries(size_t roi_index) const { + return m_ROI_geometries[roi_index]; +} + +std::vector RawFile::n_modules_in_roi() const { + + std::vector results(m_ROI_geometries.size()); + std::transform( + m_ROI_geometries.begin(), m_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()); + + auto module_indices = + m_ROI_geometries[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); + 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)); } - else { + } else { throw std::runtime_error(LOCATION + "Unsupported mode. Can only read RawFiles."); } @@ -139,33 +308,37 @@ DetectorHeader RawFile::read_header(const std::filesystem::path &fname) { RawMasterFile RawFile::master() const { return m_master; } -Frame RawFile::get_frame(size_t frame_index) { - auto f = Frame(m_geometry.pixels_y(), m_geometry.pixels_x(), +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(), Dtype::from_bitdepth(m_master.bitdepth())); std::byte *frame_buffer = f.data(); - get_frame_into(frame_index, frame_buffer); + get_frame_into(frame_index, frame_buffer, roi_index); return f; } size_t RawFile::bytes_per_pixel() const { return m_master.bitdepth() / 8; } void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, - DetectorHeader *header) { + const size_t roi_index, DetectorHeader *header) { LOG(logDEBUG) << "RawFile::get_frame_into(" << frame_index << ")"; if (frame_index >= total_frames()) { throw std::runtime_error(LOCATION + "Frame number out of range"); } - std::vector frame_numbers(m_geometry.n_modules_in_roi()); - std::vector frame_indices(m_geometry.n_modules_in_roi(), - frame_index); + std::vector frame_numbers( + m_ROI_geometries[roi_index].num_modules_in_roi()); + std::vector frame_indices( + m_ROI_geometries[roi_index].num_modules_in_roi(), frame_index); // sync the frame numbers - if (m_geometry.n_modules() != 1) { // if we have more than one module - for (size_t part_idx = 0; part_idx != m_geometry.n_modules_in_roi(); + if (m_ROI_geometries[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) { frame_numbers[part_idx] = - m_subfiles[part_idx]->frame_number(frame_index); + m_subfiles[roi_index][part_idx]->frame_number(frame_index); } // 1. if frame number vector is the same break @@ -176,7 +349,8 @@ void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, frame_numbers.begin(), std::min_element(frame_numbers.begin(), frame_numbers.end())); - // 3. increase its index and update its respective frame number + // 3. increase its index and update its respective frame + // number frame_indices[min_frame_idx]++; // 4. if we can't increase its index => throw error @@ -186,42 +360,50 @@ void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, } frame_numbers[min_frame_idx] = - m_subfiles[min_frame_idx]->frame_number( + m_subfiles[roi_index][min_frame_idx]->frame_number( frame_indices[min_frame_idx]); } } if (m_master.geometry().col == 1) { // get the part from each subfile and copy it to the frame - for (size_t part_idx = 0; part_idx != m_geometry.n_modules_in_roi(); + for (size_t part_idx = 0; + part_idx != m_ROI_geometries[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 - .get_module_geometries( - m_geometry.get_modules_in_roi(part_idx)) - .origin_y * - m_geometry.pixels_x() + - m_geometry - .get_module_geometries( - m_geometry.get_modules_in_roi(part_idx)) - .origin_x) * - m_master.bitdepth() / 8; + auto offset = + (m_geometry + .get_module_geometries( + m_ROI_geometries[roi_index].module_indices_in_roi( + part_idx)) + .origin_y * + m_ROI_geometries[roi_index].pixels_x() + + m_geometry + .get_module_geometries( + m_ROI_geometries[roi_index].module_indices_in_roi( + part_idx)) + .origin_x) * + m_master.bitdepth() / 8; if (m_geometry .get_module_geometries( - m_geometry.get_modules_in_roi(part_idx)) + m_ROI_geometries[roi_index].module_indices_in_roi( + part_idx)) .origin_x != 0) throw std::runtime_error( LOCATION + - " Implementation error. x pos not 0."); // TODO: origin + " Implementation error. x pos not 0."); // TODO: + // origin // can still - // change if roi + // change if + // roi // changes // TODO! What if the files don't match? - m_subfiles[part_idx]->seek(corrected_idx); - m_subfiles[part_idx]->read_into(frame_buffer + offset, header); + m_subfiles[roi_index][part_idx]->seek(corrected_idx); + m_subfiles[roi_index][part_idx]->read_into(frame_buffer + offset, + header); if (header) ++header; } @@ -230,23 +412,25 @@ void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, // TODO! should we read row by row? // create a buffer large enough to hold a full module - auto bytes_per_part = m_master.pixels_y() * m_master.pixels_x() * - m_master.bitdepth() / - 8; // TODO! replace with image_size_in_bytes + auto bytes_per_part = + m_master.pixels_y() * m_master.pixels_x() * m_master.bitdepth() / + 8; // TODO! replace with image_size_in_bytes // TODO + // shouldnt it only be the module size? - check auto *part_buffer = new std::byte[bytes_per_part]; - // TODO! if we have many submodules we should reorder them on the - // module level + // TODO! if we have many submodules we should reorder them on + // the module level - for (size_t part_idx = 0; part_idx != m_geometry.n_modules_in_roi(); + for (size_t part_idx = 0; + part_idx != m_ROI_geometries[roi_index].num_modules_in_roi(); ++part_idx) { auto pos = m_geometry.get_module_geometries( - m_geometry.get_modules_in_roi(part_idx)); + m_ROI_geometries[roi_index].module_indices_in_roi(part_idx)); auto corrected_idx = frame_indices[part_idx]; - m_subfiles[part_idx]->seek(corrected_idx); - m_subfiles[part_idx]->read_into(part_buffer, header); + m_subfiles[roi_index][part_idx]->seek(corrected_idx); + m_subfiles[roi_index][part_idx]->read_into(part_buffer, header); if (header) ++header; @@ -255,7 +439,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 * this->m_geometry.pixels_x() + icol); + auto dest = + (irow * m_ROI_geometries[roi_index].pixels_x() + icol); dest = dest * m_master.bitdepth() / 8; memcpy(frame_buffer + dest, part_buffer + @@ -269,7 +454,17 @@ void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, std::vector RawFile::read_n(size_t n_frames) { // TODO: implement this in a more efficient way + if (num_rois() > 1) { + throw std::runtime_error(LOCATION + + "Multiple ROIs defined in the master " + "file. Use " + "read_num_rois for a specific ROI or use " + "read_ROIs to read one frame after " + "the other."); + } + std::vector frames; + frames.reserve(n_frames); for (size_t i = 0; i < n_frames; i++) { frames.push_back(this->get_frame(m_current_frame)); m_current_frame++; @@ -277,11 +472,26 @@ std::vector RawFile::read_n(size_t n_frames) { return frames; } +std::vector RawFile::read_n_with_roi(const size_t n_frames, + const size_t roi_index) { + if (roi_index >= num_rois()) { + throw std::runtime_error(LOCATION + "ROI index out of range."); + } + + std::vector frames; + frames.reserve(n_frames); + for (size_t i = 0; i < n_frames; i++) { + frames.push_back(this->get_frame(m_current_frame, roi_index)); + m_current_frame++; + } + return frames; +} + size_t RawFile::frame_number(size_t frame_index) { if (frame_index >= m_master.frames_in_file()) { throw std::runtime_error(LOCATION + " Frame number out of range"); } - return m_subfiles[0]->frame_number(frame_index); + return m_subfiles[0][0]->frame_number(frame_index); } } // namespace aare diff --git a/src/RawFile.test.cpp b/src/RawFile.test.cpp index 26f2a75..88efc02 100644 --- a/src/RawFile.test.cpp +++ b/src/RawFile.test.cpp @@ -14,7 +14,8 @@ using aare::File; using aare::RawFile; using namespace aare; -TEST_CASE("Read number of frames from a jungfrau raw file", "[.with-data]") { +TEST_CASE("Read number of frames from a jungfrau raw file", + "[.with-data][RawFile]") { auto fpath = test_data_path() / "raw/jungfrau/jungfrau_single_master_0.json"; @@ -24,7 +25,8 @@ TEST_CASE("Read number of frames from a jungfrau raw file", "[.with-data]") { REQUIRE(f.total_frames() == 10); } -TEST_CASE("Read frame numbers from a jungfrau raw file", "[.with-data]") { +TEST_CASE("Read frame numbers from a jungfrau raw file", + "[.with-data][RawFile]") { auto fpath = test_data_path() / "raw/jungfrau/jungfrau_single_master_0.json"; REQUIRE(std::filesystem::exists(fpath)); @@ -41,7 +43,7 @@ TEST_CASE("Read frame numbers from a jungfrau raw file", "[.with-data]") { } } -TEST_CASE("Read a frame number too high throws", "[.with-data]") { +TEST_CASE("Read a frame number too high throws", "[.with-data][RawFile]") { auto fpath = test_data_path() / "raw/jungfrau/jungfrau_single_master_0.json"; REQUIRE(std::filesystem::exists(fpath)); @@ -57,7 +59,7 @@ TEST_CASE("Read a frame number too high throws", "[.with-data]") { } TEST_CASE("Read a frame numbers where the subfile is missing throws", - "[.with-data]") { + "[.with-data][RawFile]") { auto fpath = test_data_path() / "raw/jungfrau" / "jungfrau_missing_subfile_master_0.json"; @@ -80,7 +82,7 @@ TEST_CASE("Read a frame numbers where the subfile is missing throws", } TEST_CASE("Read data from a jungfrau 500k single port raw file", - "[.with-data]") { + "[.with-data][RawFile]") { auto fpath = test_data_path() / "raw/jungfrau/jungfrau_single_master_0.json"; REQUIRE(std::filesystem::exists(fpath)); @@ -99,7 +101,7 @@ TEST_CASE("Read data from a jungfrau 500k single port raw file", } } -TEST_CASE("Read frame numbers from a raw file", "[.with-data]") { +TEST_CASE("Read frame numbers from a raw file", "[.with-data][RawFile]") { auto fpath = test_data_path() / "raw/eiger" / "eiger_500k_16bit_master_0.json"; REQUIRE(std::filesystem::exists(fpath)); @@ -113,7 +115,8 @@ TEST_CASE("Read frame numbers from a raw file", "[.with-data]") { } } -TEST_CASE("Compare reading from a numpy file with a raw file", "[.with-data]") { +TEST_CASE("Compare reading from a numpy file with a raw file", + "[.with-data][RawFile]") { SECTION("jungfrau data") { auto fpath_raw = @@ -155,6 +158,11 @@ TEST_CASE("Compare reading from a numpy file with a raw file", "[.with-data]") { auto raw_frame = raw.read_frame(); auto npy_frame = npy.read_frame(); + CHECK(raw_frame.rows() == 512); + CHECK(raw_frame.cols() == 512); + CHECK(npy_frame.rows() == 512); + CHECK(npy_frame.cols() == 512); + CHECK((raw_frame.view() == npy_frame.view())); } SECTION("eiger data") { @@ -176,7 +184,7 @@ TEST_CASE("Compare reading from a numpy file with a raw file", "[.with-data]") { } } -TEST_CASE("Read multipart files", "[.with-data]") { +TEST_CASE("Read multipart files", "[.with-data][RawFile]") { auto fpath = test_data_path() / "raw/jungfrau" / "jungfrau_double_master_0.json"; REQUIRE(std::filesystem::exists(fpath)); @@ -217,7 +225,7 @@ struct TestParameters { std::vector module_geometries{}; }; -TEST_CASE("check find_geometry", "[.with-data]") { +TEST_CASE("check find_geometry", "[.with-data][RawFile]") { auto test_parameters = GENERATE( TestParameters{"raw/jungfrau_2modules_version6.1.2/run_master_0.raw", 2, @@ -290,7 +298,8 @@ TEST_CASE("check find_geometry", "[.with-data]") { } } -TEST_CASE("Open multi module file with ROI", "[.with-data]") { +TEST_CASE("Open multi module file with ROI", + "[.with-data][read_rois][RawFile]") { auto fpath = test_data_path() / "raw/ROITestData/SingleChipROI/Data_master_0.json"; @@ -304,7 +313,8 @@ TEST_CASE("Open multi module file with ROI", "[.with-data]") { CHECK(f.n_modules() == 2); - CHECK(f.n_modules_in_roi() == 1); + REQUIRE(f.n_modules_in_roi().size() == 1); + CHECK(f.n_modules_in_roi()[0] == 1); auto frames = f.read_n(2); @@ -315,7 +325,70 @@ TEST_CASE("Open multi module file with ROI", "[.with-data]") { } } -TEST_CASE("Read file with unordered frames", "[.with-data]") { +TEST_CASE("Open multi module file with ROI spanning over multiple modules", + "[.width-data][read_rois][RawFile]") { + + auto fpath = + test_data_path() / "raw/ROITestData/MultipleChipROI/run_master_2.json"; + REQUIRE(std::filesystem::exists(fpath)); + + RawFile f(fpath, "r"); + + CHECK(f.n_modules() == 2); + CHECK(f.n_modules_in_roi().size() == 1); + CHECK(f.n_modules_in_roi()[0] == 2); + + auto frame = f.read_frame(); + + CHECK(frame.rows() == 301); + CHECK(frame.cols() == 101); + + CHECK(f.rows() == 301); + CHECK(f.cols() == 101); +} + +TEST_CASE("Open multi module file with two ROIs", + "[.width-data][read_rois][RawFile]") { + + auto fpath = + test_data_path() / "raw/ROITestData/MultipleROIs/run_master_0.json"; + REQUIRE(std::filesystem::exists(fpath)); + + RawFile f(fpath, "r"); + + CHECK(f.n_modules() == 2); + REQUIRE(f.n_modules_in_roi().size() == 2); + CHECK(f.n_modules_in_roi()[0] == 1); + CHECK(f.n_modules_in_roi()[1] == 1); + + CHECK_THROWS(f.read_frame()); + + CHECK(f.tell() == 0); + + auto frame = f.read_rois(); + + CHECK(f.tell() == 1); + + REQUIRE(frame.size() == 2); + + CHECK(frame[0].rows() == 301); + CHECK(frame[0].cols() == 101); + CHECK(frame[1].rows() == 101); + CHECK(frame[1].cols() == 101); + + CHECK_THROWS(f.rows()); + CHECK(f.rows(0) == 301); + + auto frames = f.read_n_with_roi(2, 1); + REQUIRE(frames.size() == 2); + CHECK(f.tell() == 3); + for (const auto &frm : frames) { + CHECK(frm.rows() == 101); + CHECK(frm.cols() == 101); + } +} + +TEST_CASE("Read file with unordered frames", "[.with-data][RawFile]") { // TODO! Better explanation and error message auto fpath = test_data_path() / "raw/mythen/scan242_master_3.raw"; REQUIRE(std::filesystem::exists(fpath)); @@ -323,7 +396,7 @@ TEST_CASE("Read file with unordered frames", "[.with-data]") { REQUIRE_THROWS((f.read_frame())); } -TEST_CASE("Read Mythenframe", "[.with-data]") { +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); diff --git a/src/RawMasterFile.cpp b/src/RawMasterFile.cpp index afc9b09..6e561cb 100644 --- a/src/RawMasterFile.cpp +++ b/src/RawMasterFile.cpp @@ -71,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) { @@ -193,7 +193,18 @@ ScanParameters RawMasterFile::scan_parameters() const { return m_scan_parameters; } -std::optional RawMasterFile::roi() const { return m_roi; } +std::optional RawMasterFile::roi() const { + if (m_rois.value().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 + } +} + +std::optional> RawMasterFile::rois() const { return m_rois; } void RawMasterFile::parse_json(std::istream &is) { json j; @@ -255,9 +266,9 @@ void RawMasterFile::parse_json(std::istream &is) { m_frame_discard_policy = string_to( j["Frame Discard Policy"].get()); - if(j.contains("Number of rows") && j["Number of rows"].is_number()){ + 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 @@ -338,34 +349,35 @@ void RawMasterFile::parse_json(std::istream &is) { } } try { - ROI tmp_roi; if (v < 8.0) { auto obj = j.at("Receiver Roi"); - tmp_roi.xmin = obj.at("xmin"); - tmp_roi.xmax = obj.at("xmax"); - tmp_roi.ymin = obj.at("ymin"); - tmp_roi.ymax = obj.at("ymax"); - } else { - // TODO: for now only handle single ROI - auto obj = j.at("Receiver Rois"); - tmp_roi.xmin = obj[0].at("xmin"); - tmp_roi.xmax = obj[0].at("xmax"); - tmp_roi.ymin = obj[0].at("ymin"); - tmp_roi.ymax = obj[0].at("ymax"); - } - - // if any of the values are set update the roi - // TODO: doesnt it write garbage if one of them is not set - if (tmp_roi.xmin != 4294967295 || tmp_roi.xmax != 4294967295 || - tmp_roi.ymin != 4294967295 || tmp_roi.ymax != 4294967295) { - tmp_roi.xmax++; - // Handle Mythen - if (tmp_roi.ymin == -1 && tmp_roi.ymax == -1) { - tmp_roi.ymin = 0; - tmp_roi.ymax = 0; + if (obj.at("xmin") != 4294967295 || obj.at("xmax") != 4294967295 || + obj.at("ymin") != 4294967295 || obj.at("ymax") != 4294967295) { + // Handle Mythen + if (obj.at("ymin") == -1 && obj.at("ymax") == -1) { + 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}); + } + } 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) { + elem.at("ymin") = 0; + 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}); } - tmp_roi.ymax++; - m_roi = tmp_roi; } } catch (const json::out_of_range &e) { @@ -379,7 +391,6 @@ void RawMasterFile::parse_json(std::istream &is) { 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?