diff --git a/include/aare/hist/PedestalTrackingPixelHistogram.hpp b/include/aare/hist/PedestalTrackingPixelHistogram.hpp index 3e6c89c..c42b597 100644 --- a/include/aare/hist/PedestalTrackingPixelHistogram.hpp +++ b/include/aare/hist/PedestalTrackingPixelHistogram.hpp @@ -107,6 +107,7 @@ class PedestalTrackingPixelHistogram { void push_pedestal_no_update(const NDView &frame); void update_mean(); NDArray pedestal_mean() const; + NDArray pedestal_std() const; void fill_async(NDArray &&image); diff --git a/python/src/bind_PedestalTrackingPixelHistogram.hpp b/python/src/bind_PedestalTrackingPixelHistogram.hpp index 13a7047..eedcbf3 100644 --- a/python/src/bind_PedestalTrackingPixelHistogram.hpp +++ b/python/src/bind_PedestalTrackingPixelHistogram.hpp @@ -119,6 +119,30 @@ void define_pedestal_tracking_pixel_histogram_bindings(py::module &m) { containing the current cached pedestal mean. )") + .def( + "pedestal_std", + [](const PedestalTrackingPixelHistogram &self) { + // pedestal_std() flushes + locks + memcpys; do all of + // that without the GIL, only reacquire to wrap into a + // numpy array. + NDArray *ptr = + nullptr; + { + py::gil_scoped_release release; + ptr = new NDArray(self.pedestal_std()); + } + return return_image_data(ptr); + }, + R"( + Snapshot the per-pixel pedestal std stitched together + from all shards. + + Returns: + A 2D numpy array (rows x cols, dtype: float64) + containing the current cached pedestal std. + )") + .def( "fill_async", [](PedestalTrackingPixelHistogram &self, diff --git a/src/hist/PedestalTrackingPixelHistogram.cpp b/src/hist/PedestalTrackingPixelHistogram.cpp index f913e71..932b480 100644 --- a/src/hist/PedestalTrackingPixelHistogram.cpp +++ b/src/hist/PedestalTrackingPixelHistogram.cpp @@ -386,6 +386,37 @@ PedestalTrackingPixelHistogram::pedestal_mean() const { return data; } +NDArray +PedestalTrackingPixelHistogram::pedestal_std() const { + // Drain in-flight async fills and serialise with all other fan-outs + // (Fill / PushPedestal / UpdateMean). m_std is overwritten wholesale + // by Pedestal::update_mean, so without the lock we could read torn + // rows mid-update. + flush(); + std::lock_guard lock(fill_mutex_); + + NDArray data( + {static_cast(rows_), static_cast(cols_)}); + + // Each partial pedestal stores its slice of m_std in C-order + // [local_rows x cols], identical in layout to the corresponding + // [first_row .. first_row + local_rows)[col] slice of `data`, so + // we can copy each shard with a single memcpy. + const size_t row_stride = static_cast(cols_); + for (int t = 0; t < n_threads_; ++t) { + const auto first_row = static_cast(row_start(t)); + const auto local_rows = static_cast(row_count(t)); + if (local_rows == 0) + continue; + + const auto view = partial_std_[t].view(); + std::memcpy(data.data() + first_row * row_stride, view.data(), + local_rows * row_stride * sizeof(AxisType)); + } + + return data; +} + void PedestalTrackingPixelHistogram::fill_with_threshold_batch_( std::vector> &batch) { // Called only by the coordinator thread on images already shape-checked