From f2db33365eb1a48197052fed8b6cefb973e069d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Sun, 4 Oct 2026 09:54:55 +0200 Subject: [PATCH] bindings for xy --- RELEASE.md | 8 ++++++++ python/aare/__init__.py | 1 + python/src/bind_Defs.hpp | 16 ++++++++++++++++ python/src/bind_RawFile.hpp | 9 ++++++++- python/src/raw_master_file.hpp | 18 ++++++++++++++++-- python/tests/test_RawFile.py | 16 +++++++++++++++- python/tests/test_xy.py | 26 ++++++++++++++++++++++++++ 7 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 python/tests/test_xy.py diff --git a/RELEASE.md b/RELEASE.md index 88686f53..16f2322b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,13 @@ # Release notes +## Next + +### New Features: + +- Added the Python ``xy`` type with ``row`` and ``col`` fields, enabling access + to ``RawFile.geometry``, ``RawMasterFile.detector_layout``, and + ``RawMasterFile.udp_interfaces_per_module``. It unpacks as ``row, col``. + ## 2026.9.30 ### New Features: diff --git a/python/aare/__init__.py b/python/aare/__init__.py index 570176f9..ea3f4c39 100644 --- a/python/aare/__init__.py +++ b/python/aare/__init__.py @@ -25,6 +25,7 @@ from ._aare import Pedestal_d, Pedestal_f, ClusterFinder_Cluster3x3i, VarCluster from ._aare import DetectorType, FrameDiscardPolicy, ReadoutMode from ._aare import hitmap from ._aare import ROI +from ._aare import xy from ._aare import corner from ._aare import UDPPortPosition diff --git a/python/src/bind_Defs.hpp b/python/src/bind_Defs.hpp index c6592f24..e5f3cc3d 100644 --- a/python/src/bind_Defs.hpp +++ b/python/src/bind_Defs.hpp @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 +#include #include #include @@ -74,6 +75,21 @@ void define_defs_bindings(py::module &m) { self.ymin == other.ymin && self.ymax == other.ymax; }); + py::class_(m, "xy") + .def(py::init(), py::arg("row"), py::arg("col")) + .def_readwrite("row", &xy::row) + .def_readwrite("col", &xy::col) + .def("__repr__", + [](const xy &self) { + return fmt::format("xy(row={}, col={})", self.row, self.col); + }) + .def("__iter__", + [](const xy &self) { + return py::iter(py::make_tuple(self.row, self.col)); + }) + .def(py::self == py::self) + .def(py::self != py::self); + py::enum_(m, "DetectorType") .value("Jungfrau", DetectorType::Jungfrau) .value("Eiger", DetectorType::Eiger) diff --git a/python/src/bind_RawFile.hpp b/python/src/bind_RawFile.hpp index 2c4e20c8..11d1d31a 100644 --- a/python/src/bind_RawFile.hpp +++ b/python/src/bind_RawFile.hpp @@ -318,7 +318,14 @@ void define_raw_file_io_bindings(py::module &m) { Cols for the given ROI. )") .def_property_readonly("bitdepth", &RawFile::bitdepth) - .def_property_readonly("geometry", &RawFile::geometry) + .def_property_readonly("geometry", &RawFile::geometry, R"( + Number of modules along each axis of the detector. + + Returns + ------- + xy + Module rows and columns. + )") .def_property_readonly("detector_type", &RawFile::detector_type) .def_property_readonly("master", &RawFile::master) .def_property_readonly("n_modules", &RawFile::n_modules) diff --git a/python/src/raw_master_file.hpp b/python/src/raw_master_file.hpp index 40e942a9..d30762a4 100644 --- a/python/src/raw_master_file.hpp +++ b/python/src/raw_master_file.hpp @@ -67,9 +67,23 @@ void define_raw_master_file_bindings(py::module &m) { .def_property_readonly("total_frames_expected", &RawMasterFile::total_frames_expected) .def_property_readonly("detector_layout", - &RawMasterFile::detector_layout) + &RawMasterFile::detector_layout, R"( + Number of modules along each axis of the detector + + Returns + ---------- + xy + Module rows and columns + )") .def_property_readonly("udp_interfaces_per_module", - &RawMasterFile::udp_interfaces_per_module) + &RawMasterFile::udp_interfaces_per_module, R"( + Number of UDP interfaces along each axis of a module + + Returns + ---------- + xy + UDP interface rows and columns + )") .def_property_readonly("analog_samples", &RawMasterFile::analog_samples, R"( Number of analog samples diff --git a/python/tests/test_RawFile.py b/python/tests/test_RawFile.py index 1fb6c3f7..76988db9 100644 --- a/python/tests/test_RawFile.py +++ b/python/tests/test_RawFile.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MPL-2.0 import pytest import json -from aare import File, RawFile, RawSubFile, DetectorType, ROI, UDPPortPosition +from aare import File, RawFile, RawSubFile, DetectorType, ROI, UDPPortPosition, xy import numpy as np @@ -237,6 +237,20 @@ def test_raw_frame_count_across_rois(small_raw_file, short_module): reader.read_roi(roi_index) +def test_raw_file_geometry(small_raw_file): + metadata = json.loads(small_raw_file.read_text()) + metadata["Geometry"] = {"x": 1, "y": 2} + small_raw_file.write_text(json.dumps(metadata)) + for index in range(2): + data = (small_raw_file.parent / f"run_d0_f{index}_0.raw").read_bytes() + (small_raw_file.parent / f"run_d1_f{index}_0.raw").write_bytes(data) + + reader = RawFile(small_raw_file) + assert reader.geometry == xy(row=2, col=1) + assert reader.master.detector_layout == xy(row=2, col=1) + assert reader.master.udp_interfaces_per_module == xy(row=1, col=1) + + @pytest.mark.parametrize("disabled_port", [0, 1]) def test_raw_frame_count_with_disabled_udp_port(small_raw_file, disabled_port): metadata = json.loads(small_raw_file.read_text()) diff --git a/python/tests/test_xy.py b/python/tests/test_xy.py new file mode 100644 index 00000000..efc9c7d2 --- /dev/null +++ b/python/tests/test_xy.py @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: MPL-2.0 +from aare import xy + + +def test_xy_fields(): + layout = xy(row=2, col=3) + assert layout.row == 2 + assert layout.col == 3 + + layout.row = 4 + assert layout.row == 4 + + +def test_xy_unpacks_as_row_col(): + row, col = xy(2, 3) + assert (row, col) == (2, 3) + + +def test_xy_comparison(): + assert xy(2, 3) == xy(2, 3) + assert xy(2, 3) != xy(3, 2) + assert xy(2, 3) != (2, 3) + + +def test_xy_repr(): + assert repr(xy(2, 3)) == "xy(row=2, col=3)"