mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-11-01 09:11:29 +01:00
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:
commit
7a8f918a56
91 files changed
+5740
-1303
No files matched your search
+44
-14
@@ -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
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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; });
|
||||
}
|
||||
Reference in new issue
Block a user