diff --git a/CMakeLists.txt b/CMakeLists.txt index 24a3926f..6f8528e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -387,6 +387,7 @@ set(PUBLICHEADERS include/aare/RawMasterFile.hpp include/aare/RawSubFile.hpp include/aare/VarClusterFinder.hpp + include/aare/to_string.hpp include/aare/utils/task.hpp include/aare/utils/ifstream_helpers.hpp include/aare/utils/math_helpers.hpp) diff --git a/RELEASE.md b/RELEASE.md index 5436d958..9a8c2060 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -16,6 +16,9 @@ - Added the ``AARE_TUNE_LOCAL`` CMake option to build with ``-march=native`` and ``-mtune=native`` when supported. Binaries built with this option are specific to the local CPU and may not be portable. +- Added string representator in python for Cluster and Eta +- Added roi slice method in python for easy slicing of numpy arrays ``array[roi.slice()]``. +- added context manager for ``aare.RawMasterFile`` ### API Changes: diff --git a/src/to_string.hpp b/include/aare/to_string.hpp similarity index 92% rename from src/to_string.hpp rename to include/aare/to_string.hpp index 690d82fa..dbb0c09f 100644 --- a/src/to_string.hpp +++ b/include/aare/to_string.hpp @@ -87,4 +87,12 @@ template <> FrameDiscardPolicy string_to(const std::string &arg); */ template <> DACIndex string_to(const std::string &arg); +/** + * @brief Convert a corner enum to string + * @param arg corner enum value + * @return string representation of the corner enum + * @throw runtime_error if the corner value is invalid + */ +std::string to_string(const corner &arg); + } // namespace aare \ No newline at end of file diff --git a/python/src/bind_Cluster.hpp b/python/src/bind_Cluster.hpp index 14384125..5a749bfc 100644 --- a/python/src/bind_Cluster.hpp +++ b/python/src/bind_Cluster.hpp @@ -37,7 +37,6 @@ void define_Cluster(py::module &m, const std::string &typestr) { return cluster; })) - // TODO! Review if to keep or not .def_property_readonly( "data", [](Cluster &c) @@ -60,6 +59,26 @@ void define_Cluster(py::module &m, const std::string &typestr) { .def_readonly("y", &Cluster::y) + .def("__repr__", + [class_name](const Cluster &self) { + fmt::memory_buffer data; + fmt::format_to(std::back_inserter(data), "[["); + for (size_t i = 0; i < self.data.size(); ++i) { + if (i != 0) { + fmt::format_to(std::back_inserter(data), + i % ClusterSizeX == 0 ? "], [" : ", "); + } + fmt::format_to(std::back_inserter(data), "{}", + self.data[i]); + } + fmt::format_to(std::back_inserter(data), "]]"); + + return fmt::format("{}(x={}, y={}, data={})", class_name, + self.x, self.y, + fmt::string_view(data.data(), data.size())); + }) + .def( "max_sum_2x2", [](Cluster &self) { diff --git a/python/src/bind_Defs.hpp b/python/src/bind_Defs.hpp index c6886def..2b25669b 100644 --- a/python/src/bind_Defs.hpp +++ b/python/src/bind_Defs.hpp @@ -36,6 +36,22 @@ void define_defs_bindings(py::module &m) { .def_readwrite("xmax", &ROI::xmax) .def_readwrite("ymin", &ROI::ymin) .def_readwrite("ymax", &ROI::ymax) + + .def( + "slice", + [](const ROI &self) { + return std::make_tuple(py::slice(self.ymin, self.ymax, 1), + py::slice(self.xmin, self.xmax, 1)); + }, + R"doc( + The slice can be used to index into a 2D array to extract the region of interest. + + Returns + ------- + tuple of slice + A tuple containing two slice objects for the x and y dimensions. + )doc") + .def("__str__", [](const ROI &self) { return fmt::format("ROI: xmin: {} xmax: {} ymin: {} ymax: {}", diff --git a/python/src/bind_Eta.hpp b/python/src/bind_Eta.hpp index c27012f7..716b0e72 100644 --- a/python/src/bind_Eta.hpp +++ b/python/src/bind_Eta.hpp @@ -1,6 +1,8 @@ #include "aare/CalculateEta.hpp" +#include "aare/to_string.hpp" #include +#include // #include only for version 3 #include @@ -18,7 +20,11 @@ void define_eta(py::module &m, const std::string &typestr) { .def_readwrite("c", &Eta2::c, "eta corner value cTopLeft, cTopRight, " "cBottomLeft, cBottomRight") - .def_readwrite("sum", &Eta2::sum, "photon energy of cluster"); + .def_readwrite("sum", &Eta2::sum, "photon energy of cluster") + .def("__repr__", [class_name](const Eta2 &self) { + return fmt::format("{}(x={}, y={}, c={}, sum={})", class_name, + self.x, self.y, to_string(self.c), self.sum); + }); } void define_corner_enum(py::module &m) { @@ -101,4 +107,4 @@ void register_calculate_3x3eta(py::module &m) { }, R"(calculates eta3x3 taking into account cross pixels in cluster)", py::arg("cluster")); -} \ No newline at end of file +} diff --git a/python/src/raw_master_file.hpp b/python/src/raw_master_file.hpp index f7d3e8e4..40e942a9 100644 --- a/python/src/raw_master_file.hpp +++ b/python/src/raw_master_file.hpp @@ -135,9 +135,19 @@ void define_raw_master_file_bindings(py::module &m) { 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(); - return seconds; - }); + .def_property_readonly( + "period", + [](RawMasterFile &self) { + double seconds = + std::chrono::duration(self.period()).count(); + return seconds; + }) + + .def("__enter__", + [](RawMasterFile &self) -> RawMasterFile & { return self; }) + + .def("__exit__", + []([[maybe_unused]] RawMasterFile &self, + [[maybe_unused]] py::object, [[maybe_unused]] py::object, + [[maybe_unused]] py::object) { return; }); } diff --git a/python/tests/test_Cluster.py b/python/tests/test_Cluster.py index fece7273..027cc203 100644 --- a/python/tests/test_Cluster.py +++ b/python/tests/test_Cluster.py @@ -3,7 +3,7 @@ import pytest import numpy as np from aare import _aare #import the C++ module -from aare import corner +from aare import ROI, corner from conftest import test_data_path @@ -91,6 +91,22 @@ def test_calculate_eta(): assert eta2.sum == 4 +def test_cluster_representations(): + """Test Cluster representation""" + + cluster = _aare.Cluster2x2i(5, 6, np.array([1, 2, 3, 4], dtype=np.int32)) + assert repr(cluster) == "Cluster2x2i(x=5, y=6, data=[[1, 2], [3, 4]])" + +def test_eta_representations(): + """Test Eta representation""" + eta = _aare.Etai() + eta.x = 0.25 + eta.y = 0.75 + eta.c = corner.cBottomRight + eta.sum = 42 + assert repr(eta) == "Etai(x=0.25, y=0.75, c=BottomRight, sum=42)" + + def test_max_sum(): """Max 2x2 Sum""" cluster = _aare.Cluster3x3i(5,5,np.array([1, 1, 1, 2, 3, 1, 2, 2, 1], dtype=np.int32)) @@ -146,4 +162,3 @@ def test_3x3_reduction(): - diff --git a/python/tests/test_ROI.py b/python/tests/test_ROI.py new file mode 100644 index 00000000..f0b98de0 --- /dev/null +++ b/python/tests/test_ROI.py @@ -0,0 +1,21 @@ +import pytest + +import numpy as np + +from aare import ROI + + +def test_roi_slice(): + """test the ROI slice method""" + roi = ROI(0, 4, 2, 4) + + array = np.array([[1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16]]) + + sliced_array = array[roi.slice()] + + assert sliced_array.shape == (2, 4) + assert np.array_equal(sliced_array, np.array([[9, 10, 11, 12], + [13, 14, 15, 16]])) \ No newline at end of file diff --git a/python/tests/test_RawMasterFile.py b/python/tests/test_RawMasterFile.py index 6ea9ff74..8ca8aee1 100644 --- a/python/tests/test_RawMasterFile.py +++ b/python/tests/test_RawMasterFile.py @@ -1,5 +1,7 @@ +import json + import pytest from aare import RawMasterFile, ReadoutMode, DetectorType @@ -11,4 +13,26 @@ def test_read_rawfile_quad_eiger_and_compare_to_numpy(test_data_path): f = RawMasterFile(file_name) assert(f.reading_mode == ReadoutMode.UNKNOWN) - assert(f.detector_type == DetectorType.Jungfrau) \ No newline at end of file + assert(f.detector_type == DetectorType.Jungfrau) + + +def test_raw_master_file_context_manager(tmp_path): + file_name = tmp_path / "run_master_0.json" + file_name.write_text(json.dumps({ + "Version": 7.2, + "Detector Type": "Jungfrau", + "Timing Mode": "auto", + "Geometry": {"x": 1, "y": 1}, + "Image Size in bytes": 12, + "Pixels": {"x": 3, "y": 2}, + "Max Frames Per File": 1, + "Total Frames": 2, + "Frames in File": 2, + "Frame Padding": 1, + "Frame Discard Policy": "nodiscard", + })) + + + with RawMasterFile(file_name) as context_file: + assert context_file.reading_mode == ReadoutMode.UNKNOWN + assert context_file.detector_type == DetectorType.Jungfrau diff --git a/src/RawMasterFile.cpp b/src/RawMasterFile.cpp index 9c4d3752..61999438 100644 --- a/src/RawMasterFile.cpp +++ b/src/RawMasterFile.cpp @@ -4,7 +4,7 @@ #include "aare/logger.hpp" #include -#include "to_string.hpp" +#include "aare/to_string.hpp" namespace aare { @@ -624,6 +624,10 @@ void RawMasterFile::parse_raw(std::istream &is) { << m_detector_layout.row << " x " << m_detector_layout.col << "\n "; } + m_rois.push_back( + {0, m_detector_layout.col * static_cast(m_pixels_x), 0, + m_detector_layout.row * static_cast(m_pixels_y)}); + // TODO! Read files and find actual frames if (m_frames_in_file == 0) m_frames_in_file = m_total_frames_expected; diff --git a/src/to_string.cpp b/src/to_string.cpp index e8265324..8776c501 100644 --- a/src/to_string.cpp +++ b/src/to_string.cpp @@ -1,4 +1,4 @@ -#include "to_string.hpp" +#include "aare/to_string.hpp" namespace aare { @@ -254,6 +254,21 @@ template <> UDPPortPosition string_to(const std::string &arg) { "\""); } +std::string to_string(const corner &arg) { + switch (arg) { + case corner::cTopLeft: + return "TopLeft"; + case corner::cTopRight: + return "TopRight"; + case corner::cBottomLeft: + return "BottomLeft"; + case corner::cBottomRight: + return "BottomRight"; + default: + throw std::runtime_error("Invalid corner value"); + } +} + std::string remove_unit(std::string &str) { auto it = str.begin(); while (it != str.end()) { diff --git a/src/to_string.test.cpp b/src/to_string.test.cpp index 4acd7813..1489632f 100644 --- a/src/to_string.test.cpp +++ b/src/to_string.test.cpp @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MPL-2.0 +#include "aare/to_string.hpp" #include "aare/defs.hpp" #include #include -#include "to_string.hpp" using aare::string_to; TEST_CASE("DetectorType string to enum") {