Merge branch 'main' into dev/reduce
All checks were successful
Build on RHEL8 / build (push) Successful in 3m16s
Build on RHEL9 / build (push) Successful in 3m35s

This commit is contained in:
2025-09-08 15:39:27 +02:00
23 changed files with 658 additions and 55 deletions

View File

@@ -46,14 +46,13 @@ def ClusterFinderMT(image_size, cluster_size = (3,3), dtype=np.int32, n_sigma=5,
return cls(image_size, n_sigma=n_sigma, capacity=capacity, n_threads=n_threads)
def ClusterCollector(clusterfindermt, cluster_size = (3,3), dtype=np.int32):
def ClusterCollector(clusterfindermt, dtype=np.int32):
"""
Factory function to create a ClusterCollector object. Provides a cleaner syntax for
the templated ClusterCollector in C++.
"""
cls = _get_class("ClusterCollector", cluster_size, dtype)
cls = _get_class("ClusterCollector", clusterfindermt.cluster_size, dtype)
return cls(clusterfindermt)
def ClusterFileSink(clusterfindermt, cluster_file, dtype=np.int32):

View File

@@ -32,6 +32,7 @@ from .utils import random_pixels, random_pixel, flat_list, add_colorbar
from .func import *
from .calibration import *
from ._aare import apply_calibration
from ._aare import apply_calibration, count_switching_pixels
from ._aare import calculate_pedestal, calculate_pedestal_float, calculate_pedestal_g0, calculate_pedestal_g0_float
from ._aare import VarClusterFinder

View File

@@ -17,27 +17,137 @@ py::array_t<DataType> pybind_apply_calibration(
calibration,
int n_threads = 4) {
auto data_span = make_view_3d(data);
auto ped = make_view_3d(pedestal);
auto cal = make_view_3d(calibration);
auto data_span = make_view_3d(data); // data is always 3D
/* No pointer is passed, so NumPy will allocate the buffer */
auto result = py::array_t<DataType>(data_span.shape());
auto res = make_view_3d(result);
aare::apply_calibration<DataType>(res, data_span, ped, cal, n_threads);
if (data.ndim() == 3 && pedestal.ndim() == 3 && calibration.ndim() == 3) {
auto ped = make_view_3d(pedestal);
auto cal = make_view_3d(calibration);
aare::apply_calibration<DataType, 3>(res, data_span, ped, cal,
n_threads);
} else if (data.ndim() == 3 && pedestal.ndim() == 2 &&
calibration.ndim() == 2) {
auto ped = make_view_2d(pedestal);
auto cal = make_view_2d(calibration);
aare::apply_calibration<DataType, 2>(res, data_span, ped, cal,
n_threads);
} else {
throw std::runtime_error(
"Invalid number of dimensions for data, pedestal or calibration");
}
return result;
}
py::array_t<int> pybind_count_switching_pixels(
py::array_t<uint16_t, py::array::c_style | py::array::forcecast> data,
ssize_t n_threads = 4) {
auto data_span = make_view_3d(data);
auto arr = new NDArray<int, 2>{};
*arr = aare::count_switching_pixels(data_span, n_threads);
return return_image_data(arr);
}
template <typename T>
py::array_t<T> pybind_calculate_pedestal(
py::array_t<uint16_t, py::array::c_style | py::array::forcecast> data,
ssize_t n_threads) {
auto data_span = make_view_3d(data);
auto arr = new NDArray<T, 3>{};
*arr = aare::calculate_pedestal<T, false>(data_span, n_threads);
return return_image_data(arr);
}
template <typename T>
py::array_t<T> pybind_calculate_pedestal_g0(
py::array_t<uint16_t, py::array::c_style | py::array::forcecast> data,
ssize_t n_threads) {
auto data_span = make_view_3d(data);
auto arr = new NDArray<T, 2>{};
*arr = aare::calculate_pedestal<T, true>(data_span, n_threads);
return return_image_data(arr);
}
void bind_calibration(py::module &m) {
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);
m.def("apply_calibration", &pybind_apply_calibration<float>,
py::arg("raw_data").noconvert(), py::kw_only(),
py::arg("pd").noconvert(), py::arg("cal").noconvert(),
py::arg("n_threads") = 4);
m.def("apply_calibration", &pybind_apply_calibration<double>,
m.def("count_switching_pixels", &pybind_count_switching_pixels,
R"(
Count the number of time each pixel switches to G1 or G2.
Parameters
----------
raw_data : array_like
3D array of shape (frames, rows, cols) to count the switching pixels from.
n_threads : int
The number of threads to use for the calculation.
)",
py::arg("raw_data").noconvert(), py::kw_only(),
py::arg("pd").noconvert(), py::arg("cal").noconvert(),
py::arg("n_threads") = 4);
m.def("calculate_pedestal", &pybind_calculate_pedestal<double>,
R"(
Calculate the pedestal for all three gains and return the result as a 3D array of doubles.
Parameters
----------
raw_data : array_like
3D array of shape (frames, rows, cols) to calculate the pedestal from.
Needs to contain data for all three gains (G0, G1, G2).
n_threads : int
The number of threads to use for the calculation.
)",
py::arg("raw_data").noconvert(), py::arg("n_threads") = 4);
m.def("calculate_pedestal_float", &pybind_calculate_pedestal<float>,
R"(
Same as `calculate_pedestal` but returns a 3D array of floats.
Parameters
----------
raw_data : array_like
3D array of shape (frames, rows, cols) to calculate the pedestal from.
Needs to contain data for all three gains (G0, G1, G2).
n_threads : int
The number of threads to use for the calculation.
)",
py::arg("raw_data").noconvert(), py::arg("n_threads") = 4);
m.def("calculate_pedestal_g0", &pybind_calculate_pedestal_g0<double>,
R"(
Calculate the pedestal for G0 and return the result as a 2D array of doubles.
Pixels in G1 and G2 are ignored.
Parameters
----------
raw_data : array_like
3D array of shape (frames, rows, cols) to calculate the pedestal from.
n_threads : int
The number of threads to use for the calculation.
)",
py::arg("raw_data").noconvert(), py::arg("n_threads") = 4);
m.def("calculate_pedestal_g0_float", &pybind_calculate_pedestal_g0<float>,
R"(
Same as `calculate_pedestal_g0` but returns a 2D array of floats.
Parameters
----------
raw_data : array_like
3D array of shape (frames, rows, cols) to calculate the pedestal from.
n_threads : int
The number of threads to use for the calculation.
)",
py::arg("raw_data").noconvert(), py::arg("n_threads") = 4);
}

View File

@@ -1,6 +1,7 @@
import pytest
import numpy as np
from aare import apply_calibration
import aare
def test_apply_calibration_small_data():
# The raw data consists of 10 4x5 images
@@ -27,7 +28,7 @@ def test_apply_calibration_small_data():
data = apply_calibration(raw, pd = pedestal, cal = calibration)
data = aare.apply_calibration(raw, pd = pedestal, cal = calibration)
# The formula that is applied is:
@@ -41,3 +42,94 @@ def test_apply_calibration_small_data():
assert data[2,2,2] == 0
assert data[0,1,1] == 0
assert data[1,3,0] == 0
@pytest.fixture
def raw_data_3x2x2():
raw = np.zeros((3, 2, 2), dtype=np.uint16)
raw[0, 0, 0] = 100
raw[1,0, 0] = 200
raw[2, 0, 0] = 300
raw[0, 0, 1] = (1<<14) + 100
raw[1, 0, 1] = (1<<14) + 200
raw[2, 0, 1] = (1<<14) + 300
raw[0, 1, 0] = (1<<14) + 37
raw[1, 1, 0] = 38
raw[2, 1, 0] = (3<<14) + 39
raw[0, 1, 1] = (3<<14) + 100
raw[1, 1, 1] = (3<<14) + 200
raw[2, 1, 1] = (3<<14) + 300
return raw
def test_calculate_pedestal(raw_data_3x2x2):
# Calculate the pedestal
pd = aare.calculate_pedestal(raw_data_3x2x2)
assert pd.shape == (3, 2, 2)
assert pd.dtype == np.float64
assert pd[0, 0, 0] == 200
assert pd[1, 0, 0] == 0
assert pd[2, 0, 0] == 0
assert pd[0, 0, 1] == 0
assert pd[1, 0, 1] == 200
assert pd[2, 0, 1] == 0
assert pd[0, 1, 0] == 38
assert pd[1, 1, 0] == 37
assert pd[2, 1, 0] == 39
assert pd[0, 1, 1] == 0
assert pd[1, 1, 1] == 0
assert pd[2, 1, 1] == 200
def test_calculate_pedestal_float(raw_data_3x2x2):
#results should be the same for float
pd2 = aare.calculate_pedestal_float(raw_data_3x2x2)
assert pd2.shape == (3, 2, 2)
assert pd2.dtype == np.float32
assert pd2[0, 0, 0] == 200
assert pd2[1, 0, 0] == 0
assert pd2[2, 0, 0] == 0
assert pd2[0, 0, 1] == 0
assert pd2[1, 0, 1] == 200
assert pd2[2, 0, 1] == 0
assert pd2[0, 1, 0] == 38
assert pd2[1, 1, 0] == 37
assert pd2[2, 1, 0] == 39
assert pd2[0, 1, 1] == 0
assert pd2[1, 1, 1] == 0
assert pd2[2, 1, 1] == 200
def test_calculate_pedestal_g0(raw_data_3x2x2):
pd = aare.calculate_pedestal_g0(raw_data_3x2x2)
assert pd.shape == (2, 2)
assert pd.dtype == np.float64
assert pd[0, 0] == 200
assert pd[1, 0] == 38
assert pd[0, 1] == 0
assert pd[1, 1] == 0
def test_calculate_pedestal_g0_float(raw_data_3x2x2):
pd = aare.calculate_pedestal_g0_float(raw_data_3x2x2)
assert pd.shape == (2, 2)
assert pd.dtype == np.float32
assert pd[0, 0] == 200
assert pd[1, 0] == 38
assert pd[0, 1] == 0
assert pd[1, 1] == 0
def test_count_switching_pixels(raw_data_3x2x2):
# Count the number of pixels that switched gain
count = aare.count_switching_pixels(raw_data_3x2x2)
assert count.shape == (2, 2)
assert count.sum() == 8
assert count[0, 0] == 0
assert count[1, 0] == 2
assert count[0, 1] == 3
assert count[1, 1] == 3