Merge branch 'main' into testing/cuda_cf_api

Three files conflicted:

- ClusterFinderMT::collect(): both sides rewrote it. Keep this branch's
  version (f4bbbc9), which reads the stop flags before sweeping, over
  main's (afce91b), which still samples queue emptiness mid-condition.
  The staged splice had also lost a brace, swallowing popFront().
- python/aare/ClusterFinder.py: restore the CUDA API deleted by the
  resolution (_cuda_available, _CUDA_ALGORITHMS, ClusterFinderCUDA,
  find_cluster_views_batched_iter, ClusterFinderCUDAGraph), and main's
  min_pedestal_samples forwarding, dropped as an adjacent hunk.
- bind_ClusterVector.hpp: keep py::module_local() (133cedf) alongside
  main's new docstrings; without it _aare and _aare_cuda cannot both
  register Cluster/ClusterVector under pybind11 3.

Does not build on its own: ClusterFinderFrozen and the CUDA drivers
still call Pedestal APIs main removed. Fixed in the follow-up.
This commit is contained in:
kferjaoui committed 2026-09-23 15:39:54 +02:00
commit 7a8f918a56
91 files changed
+5740 -1303

No files matched your search

+44 -14
View File
@@ -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<Type, ClusterSizeX, ClusterSizeY, CoordType> &c)
@@ -60,6 +59,26 @@ void define_Cluster(py::module &m, const std::string &typestr) {
.def_readonly("y",
&Cluster<Type, ClusterSizeX, ClusterSizeY, CoordType>::y)
.def("__repr__",
[class_name](const Cluster<Type, ClusterSizeX, ClusterSizeY,
CoordType> &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<Type, ClusterSizeX, ClusterSizeY, CoordType> &self) {
@@ -67,8 +86,16 @@ void define_Cluster(py::module &m, const std::string &typestr) {
return py::make_tuple(max_sum.sum,
static_cast<int>(max_sum.index));
},
R"(calculates sum of 2x2 subcluster with highest energy and index relative to cluster center 0: top_left, 1: top_right, 2: bottom_left, 3: bottom_right
)");
R"doc(
Return the highest-sum center-adjacent 2x2 subcluster.
Returns
-------
tuple
``(sum, index)``, where index is 0 for top-left, 1 for
top-right, 2 for bottom-left, or 3 for bottom-right relative to
the cluster center.
)doc");
}
template <typename T, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
@@ -80,7 +107,14 @@ void reduce_to_3x3(py::module &m) {
[](const Cluster<T, ClusterSizeX, ClusterSizeY, CoordType> &cl) {
return reduce_to_3x3(cl);
},
py::return_value_policy::move, R"(Reduce cluster to 3x3 subcluster)");
py::return_value_policy::move, py::arg("cluster"), R"doc(
Return the 3x3 block around the cluster's center index.
Both input dimensions must be at least 3, and at least one must be
greater than 3.
The input coordinates are preserved and output data is stored in
row-major order.
)doc");
}
template <typename T, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
@@ -92,16 +126,12 @@ void reduce_to_2x2(py::module &m) {
[](const Cluster<T, ClusterSizeX, ClusterSizeY, CoordType> &cl) {
return reduce_to_2x2(cl);
},
py::return_value_policy::move,
R"(
Reduce cluster to 2x2 subcluster by taking the 2x2 subcluster with
the highest photon energy.
py::return_value_policy::move, py::arg("cluster"), R"doc(
Return the highest-sum center-adjacent 2x2 block.
RETURN:
reduced cluster (cluster is filled in row major ordering starting at the top left. Thus for a max subcluster in the top left corner the photon hit is at the fourth position.)
)");
The input coordinates are preserved and output data is stored in
row-major order.
)doc");
}
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop
+123 -28
View File
@@ -11,6 +11,7 @@
#include <pybind11/stl.h>
#include <pybind11/stl/filesystem.h>
#include <string>
#include <utility>
// Disable warnings for unused parameters, as we ignore some
// in the __exit__ method
@@ -20,18 +21,90 @@
namespace py = pybind11;
using namespace ::aare;
template <typename Range> class ClusterFileIterator {
Range m_range;
std::optional<typename Range::Iterator> m_iterator;
bool m_done{false};
public:
explicit ClusterFileIterator(Range range) : m_range(std::move(range)) {}
typename Range::Iterator::value_type next() {
if (m_done) {
throw py::stop_iteration();
}
// Advance before returning the next result, never after yielding it.
if (m_iterator) {
++*m_iterator;
} else {
m_iterator.emplace(m_range.begin());
}
if (*m_iterator == m_range.end()) {
m_done = true;
throw py::stop_iteration();
}
return std::move(**m_iterator);
}
};
template <typename Iterator>
void define_cluster_file_iterator(py::module &m, const std::string &name) {
py::class_<Iterator>(m, name.c_str())
.def(
"__iter__", [](Iterator &self) -> Iterator & { return self; },
py::return_value_policy::reference_internal)
.def("__next__", &Iterator::next);
}
template <typename Type, uint8_t CoordSizeX, uint8_t CoordSizeY,
typename CoordType = uint16_t>
void define_ClusterFile(py::module &m, const std::string &typestr) {
using ClusterType = Cluster<Type, CoordSizeX, CoordSizeY, CoordType>;
using File = ClusterFile<ClusterType>;
using FrameIterator = ClusterFileIterator<typename File::FrameRange>;
using ChunkIterator = ClusterFileIterator<typename File::ChunkRange>;
auto class_name = fmt::format("ClusterFile_{}", typestr);
define_cluster_file_iterator<FrameIterator>(m,
class_name + "_FrameIterator");
define_cluster_file_iterator<ChunkIterator>(m,
class_name + "_ChunkIterator");
py::class_<ClusterFile<ClusterType>>(m, class_name.c_str())
py::class_<ClusterFile<ClusterType>>(
m, class_name.c_str(),
"Read and write legacy binary cluster files. The format contains no "
"cluster type or shape metadata, so this class must match the file.")
.def(py::init<const std::filesystem::path &, size_t,
const std::string &>(),
py::arg(), py::arg("chunk_size") = 1000, py::arg("mode") = "r")
py::arg("fname"), py::arg("chunk_size") = 1000,
py::arg("mode") = "r",
"Open a cluster file. Mode must be 'r' to read, 'w' to truncate "
"and write, or 'a' to append.")
.def(
"frames", [](File &self) { return FrameIterator(self.frames()); },
py::keep_alive<0, 1>(),
"Iterate over complete frames from the current position, including "
"empty and fully filtered frames with their stored frame numbers. "
"Results own their storage. The iterator keeps the file alive but "
"shares its cursor; use only one traversal at a time.")
.def(
"chunks", [](File &self) { return ChunkIterator(self.chunks()); },
py::keep_alive<0, 1>(),
"Iterate from the current position using the constructor's chunk "
"size, which must be positive. Results own their storage. The "
"iterator keeps the file alive but shares its cursor; use only "
"one traversal at a time.")
.def(
"chunks",
[](File &self, size_t chunk_size) {
return ChunkIterator(self.chunks(chunk_size));
},
py::arg("chunk_size"), py::keep_alive<0, 1>(),
"Iterate over chunks of up to chunk_size selected clusters. The "
"size must be positive and does not change the constructor's "
"default. Chunks may span frames; their frame numbers are not "
"per-cluster metadata. Only the final chunk can be short.")
.def(
"read_clusters",
[](ClusterFile<ClusterType> &self, size_t n_clusters) {
@@ -39,32 +112,60 @@ void define_ClusterFile(py::module &m, const std::string &typestr) {
self.read_clusters(n_clusters));
return v;
},
py::return_value_policy::take_ownership, py::arg("n_clusters"))
.def("read_frame",
[](ClusterFile<ClusterType> &self) {
auto v = new ClusterVector<ClusterType>(self.read_frame());
return v;
})
.def("set_roi", &ClusterFile<ClusterType>::set_roi, py::arg("roi"))
.def("tell", &ClusterFile<ClusterType>::tell)
py::return_value_policy::take_ownership, py::arg("n_clusters"),
"Read up to n_clusters without preserving frame boundaries. The "
"result may combine frames, so its frame number is not reliable "
"per-cluster metadata.")
.def(
"read_frame",
[](ClusterFile<ClusterType> &self) -> py::object {
auto clusters = self.read_frame();
if (!clusters) {
return py::none();
}
return py::cast(
new ClusterVector<ClusterType>(std::move(*clusters)),
py::return_value_policy::take_ownership);
},
"Read and return the next complete frame with its frame number, "
"or None at end of file.")
.def("set_roi", &ClusterFile<ClusterType>::set_roi, py::arg("roi"),
"Select clusters whose centers lie within the half-open ROI.")
.def("tell", &ClusterFile<ClusterType>::tell,
"Return the current byte position in the file.")
.def("estimate_n_clusters",
&ClusterFile<ClusterType>::estimate_n_clusters)
&ClusterFile<ClusterType>::estimate_n_clusters,
"Estimate the number of clusters from the file size. Frame "
"headers can make this larger than the actual count.")
.def(
"set_noise_map",
[](ClusterFile<ClusterType> &self, py::array_t<int32_t> noise_map) {
[](ClusterFile<ClusterType> &self,
py::array_t<int32_t, py::array::c_style> noise_map) {
auto view = make_view_2d(noise_map);
self.set_noise_map(view);
},
py::arg("noise_map"))
py::arg("noise_map"),
"Set a two-dimensional, C-contiguous int32 noise map indexed as "
"[y, x]. The map must cover every cluster center coordinate.")
.def("set_gain_map",
[](ClusterFile<ClusterType> &self, py::array_t<double> gain_map) {
auto view = make_view_2d(gain_map);
self.set_gain_map(view);
})
.def(
"set_gain_map",
[](ClusterFile<ClusterType> &self,
py::array_t<double, py::array::c_style> gain_map) {
auto view = make_view_2d(gain_map);
self.set_gain_map(view);
},
py::arg("gain_map"),
"Set a two-dimensional, C-contiguous float64 gain map in "
"ADU/energy, indexed as [y, x]. Clusters whose complete footprint "
"extends beyond the map are retained with all data values set to "
"zero.")
.def("close", &ClusterFile<ClusterType>::close)
.def("write_frame", &ClusterFile<ClusterType>::write_frame)
.def("close", &ClusterFile<ClusterType>::close,
"Close the file. Calling close more than once is safe.")
.def("write_frame", &ClusterFile<ClusterType>::write_frame,
py::arg("clusters"),
"Write one ClusterVector, including its frame number.")
.def("__enter__", [](ClusterFile<ClusterType> &self) { return &self; })
.def("__exit__",
[](ClusterFile<ClusterType> &self,
@@ -74,14 +175,8 @@ void define_ClusterFile(py::module &m, const std::string &typestr) {
self.close();
})
.def("__iter__", [](ClusterFile<ClusterType> &self) { return &self; })
.def("__next__", [](ClusterFile<ClusterType> &self) {
auto v = new ClusterVector<ClusterType>(
self.read_clusters(self.chunk_size()));
if (v->size() == 0) {
throw py::stop_iteration();
}
return v;
});
.def("__next__",
[](File &self) { return ChunkIterator(self.chunks()).next(); });
}
#pragma GCC diagnostic pop
+4 -2
View File
@@ -32,8 +32,10 @@ void define_ClusterFinder(py::module &m, const std::string &typestr) {
py::class_<ClusterFinder<ClusterType, uint16_t, pd_type>>(
m, class_name.c_str())
.def(py::init<Shape<2>, pd_type, size_t>(), py::arg("image_size"),
py::arg("n_sigma") = 5.0, py::arg("capacity") = 1'000'000)
.def(py::init<Shape<2>, pd_type, size_t, size_t>(),
py::arg("image_size"), py::arg("n_sigma") = 5.0,
py::arg("capacity") = 1'000'000,
py::arg("min_pedestal_samples") = 1000)
.def_property(
"nSigma",
+3 -2
View File
@@ -32,10 +32,11 @@ void define_ClusterFinderMT(py::module &m, const std::string &typestr) {
py::class_<ClusterFinderMT<ClusterType, uint16_t, pd_type>>(
m, class_name.c_str())
.def(py::init<Shape<2>, pd_type, size_t, size_t, size_t>(),
.def(py::init<Shape<2>, pd_type, size_t, size_t, size_t, size_t>(),
py::arg("image_size"), py::arg("n_sigma") = 5.0,
py::arg("capacity") = 2048, py::arg("n_threads") = 3,
py::arg("queue_depth") = 16)
py::arg("queue_depth") = 16,
py::arg("min_pedestal_samples") = 1000)
.def("push_pedestal_frame",
[](ClusterFinderMT<ClusterType, uint16_t, pd_type> &self,
py::array_t<uint16_t> frame) {
+130 -69
View File
@@ -29,37 +29,73 @@ void define_ClusterVector(py::module &m, const std::string &typestr) {
py::class_<ClusterVector<
Cluster<Type, ClusterSizeX, ClusterSizeY, CoordType>, void>>(
m, class_name.c_str(), py::buffer_protocol(),
py::module_local())
m, class_name.c_str(), R"doc(
A contiguous, move-only container of fixed-size clusters.
.def(py::init()) // TODO change!!!
The class supports the Python buffer protocol, so ``numpy.array`` can
either copy its data or create a zero-copy view. A zero-copy view is
valid only while the ClusterVector's underlying allocation and size
remain unchanged.
)doc",
py::buffer_protocol(), py::module_local())
.def(py::init(), R"doc(
Create an empty ClusterVector with frame number 0 and space reserved
for at least 1024 clusters.
)doc")
.def(
"__call__",
[](ClusterVector<ClusterType> &self, py::array_t<bool> mask) {
[](ClusterVector<ClusterType> &self,
py::array_t<bool, py::array::c_style> mask) {
return self(make_view_1d(mask));
},
py::arg("mask"), R"(
Create a copy of the clustervector and apply a boolean mask to the ClusterVector.
py::arg("mask").noconvert(), R"doc(
Return a filtered copy of this ClusterVector.
Parameters
----------
mask : numpy.ndarray
One-dimensional, writable, C-contiguous array with dtype
``numpy.bool_`` and one element per cluster.
mask : 1d boolean numpy array
Mask to apply to the ClusterVector. Must be the same length as the number of clusters in the ClusterVector.
Returns
-------
ClusterVector
Selected clusters in their original order. The frame number is
preserved.
)doc")
)")
.def(
"push_back",
[](ClusterVector<ClusterType> &self, const ClusterType &cluster) {
self.push_back(cluster);
},
py::arg("cluster"), R"doc(
Append one cluster.
.def("push_back",
[](ClusterVector<ClusterType> &self, const ClusterType &cluster) {
self.push_back(cluster);
})
Notes
-----
Do not call this method while a zero-copy NumPy view of the
ClusterVector exists. Reallocation invalidates the view, while an
append without reallocation leaves its shape unchanged.
)doc")
.def("sum",
[](ClusterVector<ClusterType> &self) {
auto *vec = new std::vector<Type>(self.sum());
return return_vector(vec);
})
.def(
"sum",
[](ClusterVector<ClusterType> &self) {
auto *vec = new std::vector<Type>(self.sum());
return return_vector(vec);
},
R"doc(
Return the sum of all pixels in each cluster.
Returns
-------
numpy.ndarray
One value per cluster, in container order and with the cluster
pixel dtype.
)doc")
.def(
"sum_2x2",
[](ClusterVector<ClusterType> &self) {
@@ -68,24 +104,45 @@ void define_ClusterVector(py::module &m, const std::string &typestr) {
return return_vector(vec);
},
R"(calculates sum of 2x2 subcluster with highest energy and index relative to cluster center 0: top_left, 1: top_right, 2: bottom_left, 3: bottom_right
)")
.def_property_readonly("size", &ClusterVector<ClusterType>::size)
.def("empty", &ClusterVector<ClusterType>::empty)
.def("item_size", &ClusterVector<ClusterType>::item_size)
.def_property_readonly("fmt",
[typestr](ClusterVector<ClusterType> &self) {
return fmt_format<ClusterType>;
})
R"doc(
Return the highest-sum center-adjacent 2x2 subcluster for each
cluster.
Returns
-------
numpy.ndarray
Structured array with ``sum`` and ``index`` fields. Indices are
0 for top-left, 1 for top-right, 2 for bottom-left, and 3 for
bottom-right, relative to the cluster center.
)doc")
.def_property_readonly("size", &ClusterVector<ClusterType>::size,
"Number of stored clusters.")
.def("empty", &ClusterVector<ClusterType>::empty,
"Return True when no clusters are stored.")
.def("item_size", &ClusterVector<ClusterType>::item_size,
"Return the size in bytes of one stored cluster, including "
"padding.")
.def_property_readonly(
"fmt",
[typestr](ClusterVector<ClusterType> &self) {
return fmt_format<ClusterType>;
},
"PEP 3118 format string for one stored cluster.")
.def_property_readonly("cluster_size_x",
&ClusterVector<ClusterType>::cluster_size_x)
&ClusterVector<ClusterType>::cluster_size_x,
"Cluster size in the x dimension.")
.def_property_readonly("cluster_size_y",
&ClusterVector<ClusterType>::cluster_size_y)
&ClusterVector<ClusterType>::cluster_size_y,
"Cluster size in the y dimension.")
.def_property_readonly("capacity",
&ClusterVector<ClusterType>::capacity)
&ClusterVector<ClusterType>::capacity,
"Number of clusters that fit without "
"reallocation.")
.def_property("frame_number", &ClusterVector<ClusterType>::frame_number,
&ClusterVector<ClusterType>::set_frame_number)
&ClusterVector<ClusterType>::set_frame_number,
"Signed 32-bit frame number; 0 can indicate clusters "
"from multiple frames.")
.def_buffer(
[typestr](ClusterVector<ClusterType> &self) -> py::buffer_info {
return py::buffer_info(
@@ -99,31 +156,38 @@ void define_ClusterVector(py::module &m, const std::string &typestr) {
});
// Free functions using ClusterVector
m.def("hitmap",
[](std::array<size_t, 2> image_size, ClusterVector<ClusterType> &cv) {
// Create a numpy array to hold the hitmap
// The shape of the array is (image_size[0], image_size[1])
// note that the python array is passed as [row, col] which
// is the opposite of the clusters [x,y]
py::array_t<int32_t> hitmap(image_size);
auto r = hitmap.mutable_unchecked<2>();
m.def(
"hitmap",
[](std::array<size_t, 2> image_size, ClusterVector<ClusterType> &cv) {
// Create a numpy array to hold the hitmap
// The shape of the array is (image_size[0], image_size[1])
// note that the python array is passed as [row, col] which
// is the opposite of the clusters [x,y]
py::array_t<int32_t> hitmap(image_size);
auto r = hitmap.mutable_unchecked<2>();
// Initialize hitmap to 0
for (py::ssize_t i = 0; i < r.shape(0); i++)
for (py::ssize_t j = 0; j < r.shape(1); j++)
r(i, j) = 0;
// Initialize hitmap to 0
for (py::ssize_t i = 0; i < r.shape(0); i++)
for (py::ssize_t j = 0; j < r.shape(1); j++)
r(i, j) = 0;
// Loop over the clusters and increment the hitmap
// Skip out of bound clusters
for (const auto &cluster : cv) {
auto x = cluster.x;
auto y = cluster.y;
if (x < image_size[1] && y < image_size[0])
r(cluster.y, cluster.x) += 1;
}
// Loop over the clusters and increment the hitmap
// Skip out of bound clusters
for (const auto &cluster : cv) {
auto x = cluster.x;
auto y = cluster.y;
if (x < image_size[1] && y < image_size[0])
r(cluster.y, cluster.x) += 1;
}
return hitmap;
});
return hitmap;
},
py::arg("image_size"), py::arg("clusters"), R"doc(
Count cluster centers into an ``int32`` image whose shape is given by
``image_size`` as ``(rows, columns)``. Element ``[y, x]`` contains the
number of cluster centers at that coordinate. Out-of-bounds centers are
ignored.
)doc");
}
template <typename Type, uint8_t ClusterSizeX, uint8_t ClusterSizeY,
@@ -136,15 +200,12 @@ void define_2x2_reduction(py::module &m) {
return new ClusterVector<Cluster<Type, 2, 2, CoordType>>(
reduce_to_2x2(cv));
},
R"(
Reduce cluster to 2x2 subcluster by taking the 2x2 subcluster with
the highest photon energy.
Parameters
cv : ClusterVector (clusters are filled in row-major ordering starting at the top left. Thus for a max subcluster in the top left corner the photon hit is at the fourth position.)
)",
R"doc(
Reduce every cluster to its highest-sum center-adjacent 2x2 block.
Returns a new ClusterVector; cluster order, coordinates, and frame
number are preserved. Input pixel data is interpreted in row-major
order.
)doc",
py::arg("clustervector"));
}
@@ -159,13 +220,13 @@ void define_3x3_reduction(py::module &m) {
return new ClusterVector<Cluster<Type, 3, 3, CoordType>>(
reduce_to_3x3(cv));
},
R"(
Reduce cluster to 3x3 subcluster
Parameters
cv : ClusterVector
)",
R"doc(
Reduce every cluster to the 3x3 block around its center index.
Both input dimensions must be at least 3, and at least one must be
greater than 3.
Returns a new ClusterVector; cluster order, coordinates, and frame
number are preserved.
)doc",
py::arg("clustervector"));
}
+73
View File
@@ -27,4 +27,77 @@ void define_defs_bindings(py::module &m) {
moench05.attr("nRows") = Moench05::nRows;
moench05.attr("nCols") = Moench05::nCols;
moench05.attr("adcNumbers") = Moench05::adcNumbers;
py::class_<ROI>(m, "ROI")
.def(py::init<>())
.def(py::init<ssize_t, ssize_t, ssize_t, ssize_t>(), py::arg("xmin"),
py::arg("xmax"), py::arg("ymin"), py::arg("ymax"))
.def_readwrite("xmin", &ROI::xmin)
.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: {}",
self.xmin, self.xmax, self.ymin, self.ymax);
})
.def("__repr__",
[](const ROI &self) {
return fmt::format(
"<ROI: xmin: {} xmax: {} ymin: {} ymax: {}>", self.xmin,
self.xmax, self.ymin, self.ymax);
})
.def("__iter__",
[](const ROI &self) {
return py::make_iterator(&self.xmin, &self.ymax + 1); // NOLINT
})
.def("__eq__", [](const ROI &self, const ROI &other) {
return self.xmin == other.xmin && self.xmax == other.xmax &&
self.ymin == other.ymin && self.ymax == other.ymax;
});
py::enum_<DetectorType>(m, "DetectorType")
.value("Jungfrau", DetectorType::Jungfrau)
.value("Eiger", DetectorType::Eiger)
.value("Mythen3", DetectorType::Mythen3)
.value("Moench", DetectorType::Moench)
.value("Moench03", DetectorType::Moench03)
.value("Moench03_old", DetectorType::Moench03_old)
.value("ChipTestBoard", DetectorType::ChipTestBoard)
.value("Unknown", DetectorType::Unknown);
py::enum_<UDPPortPosition>(m, "UDPPortPosition")
.value("LEFT", UDPPortPosition::LEFT)
.value("RIGHT", UDPPortPosition::RIGHT)
.value("TOP", UDPPortPosition::TOP)
.value("BOTTOM", UDPPortPosition::BOTTOM);
py::enum_<TimingMode>(m, "TimingMode")
.value("AUTO_TIMING", TimingMode::AUTO_TIMING)
.value("TRIGGER_EXPOSURE", TimingMode::TRIGGER_EXPOSURE)
.value("GATED", TimingMode::GATED)
.value("BURST_TRIGGER", TimingMode::BURST_TRIGGER)
.value("TRIGGER_GATED", TimingMode::TRIGGER_GATED);
py::enum_<FrameDiscardPolicy>(m, "FrameDiscardPolicy")
.value("NoDiscard", FrameDiscardPolicy::NoDiscard)
.value("Discard", FrameDiscardPolicy::Discard)
.value("DiscardPartial", FrameDiscardPolicy::DiscardPartial);
}
+8 -2
View File
@@ -1,6 +1,8 @@
#include "aare/CalculateEta.hpp"
#include "aare/to_string.hpp"
#include <cstdint>
#include <fmt/format.h>
// #include <pybind11/native_enum.h> only for version 3
#include <pybind11/pybind11.h>
@@ -18,7 +20,11 @@ void define_eta(py::module &m, const std::string &typestr) {
.def_readwrite("c", &Eta2<T>::c,
"eta corner value cTopLeft, cTopRight, "
"cBottomLeft, cBottomRight")
.def_readwrite("sum", &Eta2<T>::sum, "photon energy of cluster");
.def_readwrite("sum", &Eta2<T>::sum, "photon energy of cluster")
.def("__repr__", [class_name](const Eta2<T> &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"));
}
}
+1 -11
View File
@@ -15,8 +15,7 @@ void define_fast_pedestal_bindings(py::module &m, const std::string &name) {
py::class_<FastPedestal<SUM_TYPE>>(
m, name.c_str(),
"Maintain a per-pixel running mean, population variance, and "
"standard deviation.",
"Maintain a per-pixel running mean and population standard deviation.",
py::buffer_protocol())
.def(py::init<uint32_t, uint32_t, uint32_t>(), py::arg("rows"),
py::arg("cols"), py::arg("n_samples"),
@@ -33,15 +32,6 @@ void define_fast_pedestal_bindings(py::module &m, const std::string &name) {
return return_image_data(mean);
},
"Return a copy of the cached mean. The pedestal must be ready.")
.def(
"var",
[](FastPedestal<SUM_TYPE> &self) {
auto variance = new NDArray<SUM_TYPE, 2>{};
*variance = self.variance();
return return_image_data(variance);
},
"Return the population variance, normalized by n_samples, as a "
"NumPy array. The pedestal must be ready.")
.def(
"std",
[](FastPedestal<SUM_TYPE> &self) {
+132
View File
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: MPL-2.0
#include "aare/Pedestal.hpp"
#include "np_helper.hpp"
#include <cstdint>
#include <filesystem>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
template <typename SUM_TYPE>
void define_pedestal_bindings(py::module &m, const std::string &name) {
py::class_<Pedestal<SUM_TYPE>>(
m, name.c_str(),
"Maintain a per-pixel running mean and population standard deviation. "
"Statistics are available during initialization and are zero for empty "
"pixels.",
py::buffer_protocol())
.def(py::init<uint32_t, uint32_t, uint32_t>(), py::arg("rows"),
py::arg("cols"), py::arg("n_samples"),
"Construct an empty pedestal. Each pixel accumulates n_samples "
"values before switching to exponential updates.")
.def(py::init<uint32_t, uint32_t>(), py::arg("rows"), py::arg("cols"),
"Construct an empty pedestal with n_samples=1000.")
.def(
"mean",
[](Pedestal<SUM_TYPE> &self) {
auto mea = new NDArray<SUM_TYPE, 2>{};
*mea = self.mean();
return return_image_data(mea);
},
"Return a copy of the cached mean. Empty pixels return zero.")
.def(
"view",
[](py::object self_py) {
return py::module_::import("numpy").attr("asarray")(self_py);
},
"Return a non-owning, non-writable NumPy view of the cached mean.")
.def(
"std",
[](Pedestal<SUM_TYPE> &self) {
auto std = new NDArray<SUM_TYPE, 2>{};
*std = self.std();
return return_image_data(std);
},
"Return the population standard deviation as a NumPy array. "
"Empty pixels return zero.")
.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<std::string>(ufunc.attr("__name__")) !=
"subtract") {
return py::reinterpret_borrow<py::object>(
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<SUM_TYPE>::clear),
"Reset all statistics and per-pixel sample counts to zero.")
.def_property_readonly("rows", &Pedestal<SUM_TYPE>::rows,
"Number of image rows.")
.def_property_readonly("cols", &Pedestal<SUM_TYPE>::cols,
"Number of image columns.")
.def_property_readonly(
"n_samples", &Pedestal<SUM_TYPE>::n_samples,
"Initialization sample count per pixel and steady-state "
"update-weight denominator.")
.def(
"clone",
[&](Pedestal<SUM_TYPE> &pedestal) {
return Pedestal<SUM_TYPE>(pedestal);
},
"Return an independent copy of the pedestal and its state.")
// TODO! add push for other data types
.def(
"push",
[](Pedestal<SUM_TYPE> &pedestal,
py::array_t<uint16_t, py::array::c_style> &f) {
if (f.ndim() != 2) {
throw py::value_error("Frame must be 2-dimensional");
}
auto v = make_view_2d(f);
pedestal.push(v);
},
py::arg("frame").noconvert(),
"Accumulate or exponentially update every pixel from a "
"C-contiguous uint16 frame matching the pedestal shape. After "
"n_samples values per pixel, new values have weight 1 / n_samples.")
.def(
"push_with_threshold",
[](Pedestal<SUM_TYPE> &pedestal,
py::array_t<uint16_t, py::array::c_style> &f,
py::array_t<SUM_TYPE, py::array::c_style> &threshold) {
if (f.ndim() != 2) {
throw py::value_error("Frame must be 2-dimensional");
}
if (threshold.ndim() != 2) {
throw py::value_error("Threshold must be 2-dimensional");
}
auto frame_view = make_view_2d(f);
auto threshold_view = make_view_2d(threshold);
pedestal.push_with_threshold(frame_view, threshold_view);
},
py::arg("frame").noconvert(), py::arg("threshold").noconvert(),
"Push only pixels where abs(frame - mean) is strictly less than "
"threshold. Both arrays must be C-contiguous with the pedestal "
"shape; frame must be uint16 and threshold must use the output "
"dtype. Rejected pixels keep their statistics and sample counts.")
.def_buffer([](Pedestal<SUM_TYPE> &self) {
auto mean = self.view();
return py::buffer_info(
const_cast<SUM_TYPE *>(mean.data()), sizeof(SUM_TYPE),
py::format_descriptor<SUM_TYPE>::format(), 2,
{static_cast<py::ssize_t>(mean.shape(0)),
static_cast<py::ssize_t>(mean.shape(1))},
{static_cast<py::ssize_t>(mean.strides()[0] * sizeof(SUM_TYPE)),
static_cast<py::ssize_t>(mean.strides()[1] *
sizeof(SUM_TYPE))},
true);
});
}
+33 -15
View File
@@ -11,6 +11,7 @@
#include <cstdint>
#include <filesystem>
#include <fmt/format.h>
#include <pybind11/iostream.h>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
@@ -21,14 +22,23 @@
namespace py = pybind11;
using namespace ::aare;
namespace {
std::runtime_error raw_file_read_error(RawFile &file,
const std::string &message) {
return std::runtime_error(fmt::format(
"Error reading frame index {} from file '{}': {}", file.tell(),
file.master().master_fname().string(), message));
}
} // namespace
void define_raw_file_io_bindings(py::module &m) {
py::class_<RawFile>(m, "RawFile")
.def(py::init<const std::filesystem::path &>())
.def("read_frame",
[](RawFile &self) {
if (self.n_modules_in_roi().size() > 1) {
throw std::runtime_error(
"File contains multiple ROIs - use read_ROIs()");
throw raw_file_read_error(
self, "File contains multiple ROIs - use read_ROIs()");
}
std::vector<size_t> shape;
@@ -51,16 +61,19 @@ void define_raw_file_io_bindings(py::module &m) {
"read_n",
[](RawFile &self, size_t n_frames) {
if (self.n_modules_in_roi().size() > 1) {
throw std::runtime_error(
throw raw_file_read_error(
self,
"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());
std::min(n_frames, self.tell() < self.total_frames()
? self.total_frames() - self.tell()
: size_t{0});
if (n_frames == 0) {
throw std::runtime_error("No frames left in file");
throw raw_file_read_error(self, "No frames left in file");
}
std::vector<size_t> shape{n_frames, self.rows(), self.cols()};
@@ -89,12 +102,13 @@ void define_raw_file_io_bindings(py::module &m) {
"read_roi",
[](RawFile &self, const size_t roi_index) {
if (self.num_rois() == 0) {
throw std::runtime_error(LOCATION + "No ROIs defined.");
throw raw_file_read_error(self,
LOCATION + "No ROIs defined.");
}
if (roi_index >= self.num_rois()) {
throw std::runtime_error(LOCATION +
"ROI index out of range.");
throw raw_file_read_error(
self, LOCATION + "ROI index out of range.");
}
// return headers from all subfiles
@@ -141,7 +155,8 @@ void define_raw_file_io_bindings(py::module &m) {
"read_rois",
[](RawFile &self) {
if (self.num_rois() == 0) {
throw std::runtime_error(LOCATION + "No ROIs defined.");
throw raw_file_read_error(self,
LOCATION + "No ROIs defined.");
}
size_t number_of_ROIs = self.num_rois();
@@ -196,19 +211,22 @@ void define_raw_file_io_bindings(py::module &m) {
"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.");
throw raw_file_read_error(self,
LOCATION + "No ROIs defined.");
}
if (roi_index >= self.num_rois()) {
throw std::runtime_error(LOCATION +
"ROI index out of range.");
throw raw_file_read_error(
self, 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());
std::min(num_frames, self.tell() < self.total_frames()
? self.total_frames() - self.tell()
: size_t{0});
if (n_frames == 0) {
throw std::runtime_error("No frames left in file");
throw raw_file_read_error(self, "No frames left in file");
}
std::vector<size_t> shape{
n_frames, self.roi_geometries(roi_index).pixels_y(),
@@ -306,4 +324,4 @@ void define_raw_file_io_bindings(py::module &m) {
.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);
}
}
+18 -1
View File
@@ -73,10 +73,27 @@ py::array_t<T> pybind_calculate_pedestal_g0(
}
void bind_calibration(py::module &m) {
py::options options;
options.disable_function_signatures();
m.def("apply_calibration", &pybind_apply_calibration<double>,
py::arg("raw_data").noconvert(), py::kw_only(),
py::arg("pd").noconvert(), py::arg("cal").noconvert(),
py::arg("n_threads") = 4);
py::arg("n_threads") = 4,
R"(
Apply the calibration to the raw data and return the result as a 3D array of doubles.
Parameters
----------
raw_data : array_like
3D array of shape ``(frames, rows, cols)``.
pd : array_like
Pedestal array.
cal : array_like
Calibration array.
n_threads : int, optional
Number of threads to use. Defaults to 4.
)");
m.def("apply_calibration", &pybind_apply_calibration<float>,
py::arg("raw_data").noconvert(), py::kw_only(),
+65
View File
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MPL-2.0
// Probes used by the python test-suite to exercise the helpers in
// np_helper.hpp. Internal, no API guarantees.
#pragma once
#include "aare/NDArray.hpp"
#include "aare/NDView.hpp"
#include "np_helper.hpp"
#include <algorithm>
#include <cstdint>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <type_traits>
namespace py = pybind11;
using namespace ::aare;
// Arrays are taken with the default flags (no c_style) so that nothing is
// made contiguous by pybind11 before it reaches make_view
template <typename T, ssize_t Ndim>
void define_make_view_probes(py::module &m, const std::string &suffix) {
// Copy what C++ sees through the view back to python
m.def(("view_roundtrip_" + suffix).c_str(),
[](py::array_t<T> arr) {
auto view = make_view<Ndim>(arr);
return return_image_data(new NDArray<T, Ndim>(view));
},
py::arg("arr").noconvert());
// Same as above but through a read only view
m.def(("const_view_roundtrip_" + suffix).c_str(),
[](py::array_t<T> arr) {
auto view = make_const_view<Ndim>(arr);
static_assert(
std::is_same_v<decltype(view), NDView<const T, Ndim>>);
auto out = new NDArray<T, Ndim>(view.shape());
std::copy(view.begin(), view.end(), out->begin());
return return_image_data(out);
},
py::arg("arr").noconvert());
// Write through the view
m.def(("view_fill_" + suffix).c_str(),
[](py::array_t<T> arr, T value) {
auto view = make_view<Ndim>(arr);
view = value;
},
py::arg("arr").noconvert(), py::arg("value"));
}
void define_testing_bindings(py::module &m) {
auto testing = m.def_submodule(
"testing", "Internal helpers for the test-suite, no API guarantees");
// noconvert + overloads gives dispatch on dtype
define_make_view_probes<double, 1>(testing, "1d");
define_make_view_probes<double, 2>(testing, "2d");
define_make_view_probes<double, 3>(testing, "3d");
define_make_view_probes<uint16_t, 1>(testing, "1d");
define_make_view_probes<uint16_t, 2>(testing, "2d");
define_make_view_probes<uint16_t, 3>(testing, "3d");
}
+1 -33
View File
@@ -2,6 +2,7 @@
#include "aare/CtbRawFile.hpp"
#include "aare/File.hpp"
#include "aare/Frame.hpp"
#include "aare/ROI.hpp"
#include "aare/RawFile.hpp"
#include "aare/RawMasterFile.hpp"
#include "aare/RawSubFile.hpp"
@@ -27,16 +28,6 @@ using namespace ::aare;
void define_file_io_bindings(py::module &m) {
py::enum_<DetectorType>(m, "DetectorType")
.value("Jungfrau", DetectorType::Jungfrau)
.value("Eiger", DetectorType::Eiger)
.value("Mythen3", DetectorType::Mythen3)
.value("Moench", DetectorType::Moench)
.value("Moench03", DetectorType::Moench03)
.value("Moench03_old", DetectorType::Moench03_old)
.value("ChipTestBoard", DetectorType::ChipTestBoard)
.value("Unknown", DetectorType::Unknown);
PYBIND11_NUMPY_DTYPE(DetectorHeader, frameNumber, expLength, packetNumber,
bunchId, timestamp, modId, row, column, reserved,
debug, roundRNumber, detType, version, packetMask);
@@ -170,28 +161,5 @@ void define_file_io_bindings(py::module &m) {
.def_property_readonly("stop", &ScanParameters::stop)
.def_property_readonly("step", &ScanParameters::step);
py::class_<ROI>(m, "ROI")
.def(py::init<>())
.def(py::init<ssize_t, ssize_t, ssize_t, ssize_t>(), py::arg("xmin"),
py::arg("xmax"), py::arg("ymin"), py::arg("ymax"))
.def_readwrite("xmin", &ROI::xmin)
.def_readwrite("xmax", &ROI::xmax)
.def_readwrite("ymin", &ROI::ymin)
.def_readwrite("ymax", &ROI::ymax)
.def("__str__",
[](const ROI &self) {
return fmt::format("ROI: xmin: {} xmax: {} ymin: {} ymax: {}",
self.xmin, self.xmax, self.ymin, self.ymax);
})
.def("__repr__",
[](const ROI &self) {
return fmt::format(
"<ROI: xmin: {} xmax: {} ymin: {} ymax: {}>", self.xmin,
self.xmax, self.ymin, self.ymax);
})
.def("__iter__", [](const ROI &self) {
return py::make_iterator(&self.xmin, &self.ymax + 1); // NOLINT
});
#pragma GCC diagnostic pop
}
+3 -7
View File
@@ -17,18 +17,19 @@
#include "bind_FastPedestal.hpp"
#include "bind_Interpolator.hpp"
#include "bind_MultiThreadedFileReader.hpp"
#include "bind_Pedestal.hpp"
#include "bind_PedestalTrackingPixelHistogram.hpp"
#include "bind_PixelHistogram.hpp"
#include "bind_PixelMap.hpp"
#include "bind_RawFile.hpp"
#include "bind_calibration.hpp"
#include "bind_testing.hpp"
// TODO! migrate the other names
#include "ctb_raw_file.hpp"
#include "file.hpp"
#include "fit.hpp"
#include "jungfrau_data_file.hpp"
#include "pedestal.hpp"
#include "raw_master_file.hpp"
#include "raw_sub_file.hpp"
#include "var_cluster.hpp"
@@ -90,6 +91,7 @@ PYBIND11_MODULE(_aare, m) {
define_jungfrau_data_file_io_bindings(m);
bind_calibration(m);
define_testing_bindings(m);
DEFINE_CLUSTER_BINDINGS(int, 3, 3, uint16_t, i);
DEFINE_CLUSTER_BINDINGS(double, 3, 3, uint16_t, d);
@@ -130,9 +132,6 @@ PYBIND11_MODULE(_aare, m) {
DEFINE_BINDINGS_CLUSTERFINDER(double, 9, 9, uint16_t, d);
DEFINE_BINDINGS_CLUSTERFINDER(float, 9, 9, uint16_t, f);
define_3x3_reduction<int, 3, 3, uint16_t>(m);
define_3x3_reduction<double, 3, 3, uint16_t>(m);
define_3x3_reduction<float, 3, 3, uint16_t>(m);
define_3x3_reduction<int, 5, 5, uint16_t>(m);
define_3x3_reduction<double, 5, 5, uint16_t>(m);
define_3x3_reduction<float, 5, 5, uint16_t>(m);
@@ -143,9 +142,6 @@ PYBIND11_MODULE(_aare, m) {
define_3x3_reduction<double, 9, 9, uint16_t>(m);
define_3x3_reduction<float, 9, 9, uint16_t>(m);
reduce_to_3x3<int, 3, 3, uint16_t>(m);
reduce_to_3x3<double, 3, 3, uint16_t>(m);
reduce_to_3x3<float, 3, 3, uint16_t>(m);
reduce_to_3x3<int, 5, 5, uint16_t>(m);
reduce_to_3x3<double, 5, 5, uint16_t>(m);
reduce_to_3x3<float, 5, 5, uint16_t>(m);
+47 -18
View File
@@ -41,31 +41,60 @@ template <typename T> py::array return_vector(std::vector<T> *vec) {
free_when_done); // numpy array references this parent
}
// todo rewrite generic
template <class T, int Flags>
auto get_shape_3d(const py::array_t<T, Flags> &arr) {
return aare::Shape<3>{arr.shape(0), arr.shape(1), arr.shape(2)};
// Create a NDView of a numpy array. NDView assumes C-order strides so we
// reject anything that is not Ndim dimensional and C-contiguous instead of
// silently reading the data in the wrong order.
template <ssize_t Ndim, class T, int Flags>
auto get_checked_shape(const py::array_t<T, Flags> &arr) {
if (arr.ndim() != Ndim) {
throw py::value_error(
fmt::format("Expected {}D array, got {}D", Ndim, arr.ndim()));
}
if (!(arr.flags() & py::array::c_style)) {
throw py::value_error(
"Array is not C-contiguous. Use np.ascontiguousarray(arr)");
}
aare::Shape<Ndim> shape{};
for (ssize_t i = 0; i < Ndim; ++i) {
shape[i] = arr.shape(i);
}
return shape;
}
template <ssize_t Ndim, class T, int Flags>
auto make_view(py::array_t<T, Flags> &arr) {
auto shape = get_checked_shape<Ndim>(arr);
return aare::NDView<T, Ndim>(arr.mutable_data(), shape);
}
// Read only view, also works for numpy arrays that are not writeable
template <ssize_t Ndim, class T, int Flags>
auto make_const_view(const py::array_t<T, Flags> &arr) {
auto shape = get_checked_shape<Ndim>(arr);
return aare::NDView<const T, Ndim>(arr.data(), shape);
}
template <class T, int Flags> auto make_view_3d(py::array_t<T, Flags> &arr) {
return aare::NDView<T, 3>(arr.mutable_data(), get_shape_3d<T, Flags>(arr));
return make_view<3>(arr);
}
template <class T, int Flags>
auto get_shape_2d(const py::array_t<T, Flags> &arr) {
return aare::Shape<2>{arr.shape(0), arr.shape(1)};
}
template <class T, int Flags>
auto get_shape_1d(const py::array_t<T, Flags> &arr) {
return aare::Shape<1>{arr.shape(0)};
}
template <class T, int Flags> auto make_view_2d(py::array_t<T, Flags> &arr) {
return aare::NDView<T, 2>(arr.mutable_data(), get_shape_2d<T, Flags>(arr));
return make_view<2>(arr);
}
template <class T, int Flags> auto make_view_1d(py::array_t<T, Flags> &arr) {
return aare::NDView<T, 1>(arr.mutable_data(), get_shape_1d<T, Flags>(arr));
return make_view<1>(arr);
}
template <class T, int Flags>
auto make_const_view_3d(const py::array_t<T, Flags> &arr) {
return make_const_view<3>(arr);
}
template <class T, int Flags>
auto make_const_view_2d(const py::array_t<T, Flags> &arr) {
return make_const_view<2>(arr);
}
template <class T, int Flags>
auto make_const_view_1d(const py::array_t<T, Flags> &arr) {
return make_const_view<1>(arr);
}
template <typename ClusterType> struct fmt_format_trait; // forward declaration
-125
View File
@@ -1,125 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
#include "aare/Pedestal.hpp"
#include "np_helper.hpp"
#include <cstdint>
#include <filesystem>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
template <typename SUM_TYPE>
void define_pedestal_bindings(py::module &m, const std::string &name) {
py::class_<Pedestal<SUM_TYPE>>(m, name.c_str(), py::buffer_protocol())
.def(py::init<int, int, int>())
.def(py::init<int, int>())
.def("mean",
[](Pedestal<SUM_TYPE> &self) {
auto mea = new NDArray<SUM_TYPE, 2>{};
*mea = self.mean();
return return_image_data(mea);
})
.def("view",
[](py::object self_py) {
auto &self = self_py.cast<Pedestal<SUM_TYPE> &>();
auto v = self.view();
std::array<py::ssize_t, 2> shape{
static_cast<py::ssize_t>(v.shape(0)),
static_cast<py::ssize_t>(v.shape(1))};
std::array<py::ssize_t, 2> byte_strides{
static_cast<py::ssize_t>(v.strides()[0]) *
static_cast<py::ssize_t>(sizeof(SUM_TYPE)),
static_cast<py::ssize_t>(v.strides()[1]) *
static_cast<py::ssize_t>(sizeof(SUM_TYPE))};
auto arr = py::array_t<SUM_TYPE>(shape, byte_strides, v.data(),
self_py);
arr.attr("setflags")(py::arg("write") = false);
return arr;
})
.def("variance",
[](Pedestal<SUM_TYPE> &self) {
auto var = new NDArray<SUM_TYPE, 2>{};
*var = self.variance();
return return_image_data(var);
})
.def("std",
[](Pedestal<SUM_TYPE> &self) {
auto std = new NDArray<SUM_TYPE, 2>{};
*std = self.std();
return return_image_data(std);
})
.def("cached_std",
[](Pedestal<SUM_TYPE> &self) {
auto standard_deviation = new NDArray<SUM_TYPE, 2>{};
*standard_deviation = self.cached_std();
return return_image_data(standard_deviation);
})
.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<std::string>(ufunc.attr("__name__")) !=
"subtract") {
return py::reinterpret_borrow<py::object>(
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<SUM_TYPE>::clear))
.def_property_readonly("rows", &Pedestal<SUM_TYPE>::rows)
.def_property_readonly("cols", &Pedestal<SUM_TYPE>::cols)
.def_property_readonly("n_samples", &Pedestal<SUM_TYPE>::n_samples)
.def_property_readonly("sum", &Pedestal<SUM_TYPE>::get_sum)
.def_property_readonly("sum2", &Pedestal<SUM_TYPE>::get_sum2)
.def("clone",
[&](Pedestal<SUM_TYPE> &pedestal) {
return Pedestal<SUM_TYPE>(pedestal);
})
// TODO! add push for other data types
.def("push",
[](Pedestal<SUM_TYPE> &pedestal, py::array_t<uint16_t> &f) {
auto v = make_view_2d(f);
pedestal.push(v);
})
.def(
"push_with_threshold",
[](Pedestal<SUM_TYPE> &pedestal,
py::array_t<uint16_t, py::array::c_style> &f,
py::array_t<SUM_TYPE, py::array::c_style> &threshold) {
auto frame_view = make_view_2d(f);
auto threshold_view = make_view_2d(threshold);
pedestal.push_with_threshold(frame_view, threshold_view);
},
py::arg("frame").noconvert(), py::arg("threshold").noconvert())
.def(
"push_no_update",
[](Pedestal<SUM_TYPE> &pedestal,
py::array_t<uint16_t, py::array::c_style> &f) {
auto v = make_view_2d(f);
pedestal.push_no_update(v);
},
py::arg().noconvert())
.def("update_mean", &Pedestal<SUM_TYPE>::update_mean)
.def_buffer([](Pedestal<SUM_TYPE> &self) {
auto mean = self.view();
return py::buffer_info(
const_cast<SUM_TYPE *>(mean.data()), sizeof(SUM_TYPE),
py::format_descriptor<SUM_TYPE>::format(), 2,
{static_cast<py::ssize_t>(mean.shape(0)),
static_cast<py::ssize_t>(mean.shape(1))},
{static_cast<py::ssize_t>(mean.strides()[0] * sizeof(SUM_TYPE)),
static_cast<py::ssize_t>(mean.strides()[1] *
sizeof(SUM_TYPE))},
true);
});
}
+44 -6
View File
@@ -66,7 +66,8 @@ void define_raw_master_file_bindings(py::module &m) {
.def_property_readonly("total_frames_expected",
&RawMasterFile::total_frames_expected)
.def_property_readonly("geometry", &RawMasterFile::geometry)
.def_property_readonly("detector_layout",
&RawMasterFile::detector_layout)
.def_property_readonly("udp_interfaces_per_module",
&RawMasterFile::udp_interfaces_per_module)
.def_property_readonly("analog_samples", &RawMasterFile::analog_samples,
@@ -107,9 +108,46 @@ void define_raw_master_file_bindings(py::module &m) {
return std::nullopt;
}
})
.def_property_readonly("period", [](RawMasterFile &self) {
double seconds =
std::chrono::duration<double>(self.period()).count();
return seconds;
});
.def_property_readonly("rois", &RawMasterFile::rois, R"(
Get the ROIs defined in the master file
Returns
----------
List[ROI]
List of ROIs (default complete ROI)
)")
.def_property_readonly("udp_port_types", &RawMasterFile::udp_port_types,
R"(
Get the types of UDP ports
Returns
----------
Optional[List[UDPPortPosition]]
Optional vector of UDP port types as strings (only present for
masterfile version >= 8.1)
)")
.def_property_readonly("disabled_udp_ports",
&RawMasterFile::disabled_udp_ports, R"(
Get the indices of disabled UDP ports
Returns
----------
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<double>(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; });
}