diff --git a/RELEASE.md b/RELEASE.md index e07e9a1..1689a9d 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,6 +12,8 @@ - Added ``PixelHistogram`` and ``PedestalTrackingPixelHistogram`` - ``aare.transfrom.Matterhorn10Transform`` handles counter artefact in chip. Mind that enabling only one counter or three counters or enabling the wromg two counters e.g. 0,1 will still lead to erreneous data. - ``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 ### Bugfixes: diff --git a/include/aare/CtbRawFile.hpp b/include/aare/CtbRawFile.hpp index ebcfa46..f1c992e 100644 --- a/include/aare/CtbRawFile.hpp +++ b/include/aare/CtbRawFile.hpp @@ -28,6 +28,7 @@ class CtbRawFile { size_t image_size_in_bytes() const; size_t frames_in_file() const; + size_t total_frames() const; RawMasterFile master() const; diff --git a/include/aare/NDView.hpp b/include/aare/NDView.hpp index 403d203..8c2432d 100644 --- a/include/aare/NDView.hpp +++ b/include/aare/NDView.hpp @@ -90,8 +90,7 @@ class NDView : public ArrayExpr, Ndim> { NDView(T *buffer, std::array shape) : buffer_(buffer), strides_(c_strides(shape)), shape_(shape), - size_(std::accumulate(std::begin(shape), std::end(shape), 1, - std::multiplies<>())) {} + size_(num_elements(shape)) {} template std::enable_if_t operator()(Ix... index) { diff --git a/python/src/bind_RawFile.hpp b/python/src/bind_RawFile.hpp index c31a8c3..85ef155 100644 --- a/python/src/bind_RawFile.hpp +++ b/python/src/bind_RawFile.hpp @@ -280,6 +280,7 @@ void define_raw_file_io_bindings(py::module &m) { .def("tell", &RawFile::tell, R"( Return the current frame number.)") .def_property_readonly("total_frames", &RawFile::total_frames) + .def("__len__", &RawFile::total_frames) .def("rows", static_cast(&RawFile::rows)) .def( "rows", diff --git a/python/src/ctb_raw_file.hpp b/python/src/ctb_raw_file.hpp index 6deff4d..5570254 100644 --- a/python/src/ctb_raw_file.hpp +++ b/python/src/ctb_raw_file.hpp @@ -226,5 +226,7 @@ void define_ctb_raw_file_io_bindings(py::module &m) { .def_property_readonly("image_size_in_bytes", &CtbRawFile::image_size_in_bytes) - .def_property_readonly("frames_in_file", &CtbRawFile::frames_in_file); + .def_property_readonly("frames_in_file", &CtbRawFile::frames_in_file) + .def_property_readonly("total_frames", &CtbRawFile::total_frames) + .def("__len__", &CtbRawFile::total_frames); } diff --git a/python/src/file.hpp b/python/src/file.hpp index 3be4ae2..b602673 100644 --- a/python/src/file.hpp +++ b/python/src/file.hpp @@ -60,6 +60,7 @@ void define_file_io_bindings(py::module &m) { .def("seek", &File::seek) .def("tell", &File::tell) .def_property_readonly("total_frames", &File::total_frames) + .def("__len__", &File::total_frames) .def_property_readonly("rows", &File::rows) .def_property_readonly("cols", &File::cols) .def_property_readonly("bitdepth", &File::bitdepth) diff --git a/python/src/jungfrau_data_file.hpp b/python/src/jungfrau_data_file.hpp index 9b0f188..44ecd90 100644 --- a/python/src/jungfrau_data_file.hpp +++ b/python/src/jungfrau_data_file.hpp @@ -72,6 +72,7 @@ void define_jungfrau_data_file_io_bindings(py::module &m) { .def_property_readonly("bitdepth", &JungfrauDataFile::bitdepth) .def_property_readonly("current_file", &JungfrauDataFile::current_file) .def_property_readonly("total_frames", &JungfrauDataFile::total_frames) + .def("__len__", &JungfrauDataFile::total_frames) .def_property_readonly("n_files", &JungfrauDataFile::n_files) .def("read_frame", &read_dat_frame, R"( diff --git a/python/src/pedestal.hpp b/python/src/pedestal.hpp index 6f8fc06..238848b 100644 --- a/python/src/pedestal.hpp +++ b/python/src/pedestal.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -12,7 +13,8 @@ namespace py = pybind11; template void define_pedestal_bindings(py::module &m, const std::string &name) { - py::class_>(m, name.c_str()) + + py::class_>(m, name.c_str(), py::buffer_protocol()) .def(py::init()) .def(py::init()) .def("mean", @@ -50,6 +52,23 @@ void define_pedestal_bindings(py::module &m, const std::string &name) { *std = self.std(); return return_image_data(std); }) + .def( + "__array_ufunc__", + [](py::object self, py::object ufunc, const std::string &method, + py::args inputs, py::kwargs kwargs) -> py::object { + if (method != "__call__" || inputs.size() != 2 || + inputs[1].ptr() != self.ptr() || + py::cast(ufunc.attr("__name__")) != + "subtract") { + return py::reinterpret_borrow( + Py_NotImplemented); + } + + auto mean = + py::module_::import("builtins").attr("memoryview")(self); + return ufunc(inputs[0], mean, **kwargs); + }, + "Support subtracting a Pedestal from a NumPy array.") .def("clear", py::overload_cast<>(&Pedestal::clear)) .def_property_readonly("rows", &Pedestal::rows) .def_property_readonly("cols", &Pedestal::cols) @@ -84,5 +103,17 @@ void define_pedestal_bindings(py::module &m, const std::string &name) { pedestal.push_no_update(v); }, py::arg().noconvert()) - .def("update_mean", &Pedestal::update_mean); -} \ No newline at end of file + .def("update_mean", &Pedestal::update_mean) + .def_buffer([](Pedestal &self) { + auto mean = self.view(); + return py::buffer_info( + const_cast(mean.data()), sizeof(SUM_TYPE), + py::format_descriptor::format(), 2, + {static_cast(mean.shape(0)), + static_cast(mean.shape(1))}, + {static_cast(mean.strides()[0] * sizeof(SUM_TYPE)), + static_cast(mean.strides()[1] * + sizeof(SUM_TYPE))}, + true); + }); +} diff --git a/python/tests/test_Pedestal.py b/python/tests/test_Pedestal.py new file mode 100644 index 0000000..2c94811 --- /dev/null +++ b/python/tests/test_Pedestal.py @@ -0,0 +1,40 @@ +import numpy as np +import pytest + +from aare import Pedestal_d, Pedestal_f + + +@pytest.mark.parametrize( + ("pedestal_type", "expected_dtype"), + [(Pedestal_d, np.float64), (Pedestal_f, np.float32)], +) +def test_numpy_array_minus_pedestal(pedestal_type, expected_dtype): + pedestal = pedestal_type(2, 3) + pedestal.push(np.array([[2, 4, 6], [8, 10, 12]], dtype=np.uint16)) + array = np.array([[12, 14, 16], [18, 20, 22]], dtype=np.uint16) + + result = array - pedestal + + np.testing.assert_array_equal( + result, np.array([[10, 10, 10], [10, 10, 10]], dtype=expected_dtype) + ) + assert result.dtype == expected_dtype + + +def test_numpy_array_minus_pedestal_rejects_incompatible_shape(): + pedestal = Pedestal_d(2, 3) + array = np.zeros((2, 2), dtype=np.float64) + + with pytest.raises(ValueError): + array - pedestal + + +def test_pedestal_exposes_mean_as_read_only_buffer(): + pedestal = Pedestal_d(2, 3) + pedestal.push(np.array([[2, 4, 6], [8, 10, 12]], dtype=np.uint16)) + + mean = np.asarray(pedestal) + + np.testing.assert_array_equal(mean, pedestal.view()) + assert np.shares_memory(mean, pedestal.view()) + assert not mean.flags.writeable diff --git a/src/CtbRawFile.cpp b/src/CtbRawFile.cpp index f22b2f3..260ab66 100644 --- a/src/CtbRawFile.cpp +++ b/src/CtbRawFile.cpp @@ -57,6 +57,8 @@ size_t CtbRawFile::image_size_in_bytes() const { size_t CtbRawFile::frames_in_file() const { return m_master.frames_in_file(); } +size_t CtbRawFile::total_frames() const { return m_master.frames_in_file(); } + RawMasterFile CtbRawFile::master() const { return m_master; } void CtbRawFile::find_subfiles() {