diff --git a/CMakeLists.txt b/CMakeLists.txt index d6a22d7..3cfc6a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -368,6 +368,7 @@ set(PUBLICHEADERS set(SourceFiles + ${CMAKE_CURRENT_SOURCE_DIR}/src/calibration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/CtbRawFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/decode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/defs.cpp diff --git a/include/aare/NDView.hpp b/include/aare/NDView.hpp index 28f5371..4c16742 100644 --- a/include/aare/NDView.hpp +++ b/include/aare/NDView.hpp @@ -66,17 +66,28 @@ class NDView : public ArrayExpr, Ndim> { : buffer_(buffer), strides_(c_strides(shape)), shape_(shape), size_(std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<>())) {} - + template std::enable_if_t operator()(Ix... index) { return buffer_[element_offset(strides_, index...)]; } template - const std::enable_if_t operator()(Ix... index) const { + std::enable_if_t 1), NDView> operator()(Ix... index) { + // return a view of the next dimension + std::array new_shape{}; + std::copy_n(shape_.begin() + 1, Ndim - 1, new_shape.begin()); + return NDView(&buffer_[element_offset(strides_, index...)], + new_shape); + + } + + template + std::enable_if_t operator()(Ix... index) const { return buffer_[element_offset(strides_, index...)]; } + ssize_t size() const { return static_cast(size_); } size_t total_bytes() const { return size_ * sizeof(T); } std::array strides() const noexcept { return strides_; } @@ -85,9 +96,19 @@ class NDView : public ArrayExpr, Ndim> { T *end() { return buffer_ + size_; } T const *begin() const { return buffer_; } T const *end() const { return buffer_ + size_; } - T &operator()(ssize_t i) { return buffer_[i]; } + + + + + + /** + * @brief Access element at index i. + */ T &operator[](ssize_t i) { return buffer_[i]; } - const T &operator()(ssize_t i) const { return buffer_[i]; } + + /** + * @brief Access element at index i. + */ const T &operator[](ssize_t i) const { return buffer_[i]; } bool operator==(const NDView &other) const { diff --git a/include/aare/VarClusterFinder.hpp b/include/aare/VarClusterFinder.hpp index 3679d39..a002f9b 100644 --- a/include/aare/VarClusterFinder.hpp +++ b/include/aare/VarClusterFinder.hpp @@ -240,14 +240,14 @@ template void VarClusterFinder::first_pass() { for (ssize_t i = 0; i < original_.size(); ++i) { if (use_noise_map) - threshold_ = 5 * noiseMap(i); - binary_(i) = (original_(i) > threshold_); + threshold_ = 5 * noiseMap[i]; + binary_[i] = (original_[i] > threshold_); } for (int i = 0; i < shape_[0]; ++i) { for (int j = 0; j < shape_[1]; ++j) { - // do we have someting to process? + // do we have something to process? if (binary_(i, j)) { auto tmp = check_neighbours(i, j); if (tmp != 0) { diff --git a/include/aare/calibration.hpp b/include/aare/calibration.hpp index aab1f89..8d672b4 100644 --- a/include/aare/calibration.hpp +++ b/include/aare/calibration.hpp @@ -1,5 +1,7 @@ #pragma once +#include "aare/NDArray.hpp" +#include "aare/NDView.hpp" #include "aare/defs.hpp" #include "aare/utils/task.hpp" #include @@ -55,32 +57,227 @@ ALWAYS_INLINE std::pair get_value_and_gain(uint16_t raw) { template void apply_calibration_impl(NDView res, NDView raw_data, - NDView ped, NDView cal, int start, - int stop) { + NDView ped, NDView cal, int start, + int stop) { for (int frame_nr = start; frame_nr != stop; ++frame_nr) { for (int row = 0; row != raw_data.shape(1); ++row) { for (int col = 0; col != raw_data.shape(2); ++col) { - auto [value, gain] = get_value_and_gain(raw_data(frame_nr, row, col)); + auto [value, gain] = + get_value_and_gain(raw_data(frame_nr, row, col)); + + // Using multiplication does not seem to speed up the code here + // ADU/keV is the standard unit for the calibration which + // means rewriting the formula is not worth it. res(frame_nr, row, col) = - (value - ped(gain, row, col)) / cal(gain, row, col); //TODO! use multiplication + (value - ped(gain, row, col)) / cal(gain, row, col); } } } } template +void apply_calibration_impl(NDView res, NDView raw_data, + NDView ped, NDView cal, int start, + int stop) { + + for (int frame_nr = start; frame_nr != stop; ++frame_nr) { + for (int row = 0; row != raw_data.shape(1); ++row) { + for (int col = 0; col != raw_data.shape(2); ++col) { + auto [value, gain] = + get_value_and_gain(raw_data(frame_nr, row, col)); + + // Using multiplication does not seem to speed up the code here + // ADU/keV is the standard unit for the calibration which + // means rewriting the formula is not worth it. + + // ignore anything that is not gain 0 + // TODO! deal with fixed gain? + if (gain == 0) + res(frame_nr, row, col) = + (value - ped(row, col)) / cal(row, col); + } + } + } +} + +template void apply_calibration(NDView res, NDView raw_data, - NDView ped, NDView cal, + NDView ped, NDView cal, ssize_t n_threads = 4) { std::vector> futures; futures.reserve(n_threads); auto limits = split_task(0, raw_data.shape(0), n_threads); for (const auto &lim : limits) - futures.push_back(std::async(&apply_calibration_impl, res, raw_data, ped, cal, - lim.first, lim.second)); + futures.push_back(std::async( + static_cast, NDView, + NDView, NDView, int, int)>( + apply_calibration_impl), + res, raw_data, ped, cal, lim.first, lim.second)); for (auto &f : futures) f.get(); } +std::pair, NDArray> +sum_and_count_per_gain(NDView raw_data); + +std::pair, NDArray> +sum_and_count_g0(NDView raw_data); + +template +NDArray calculate_pedestal(NDView raw_data) { + auto [accumulator, count] = sum_and_count_per_gain(raw_data); + + NDArray pedestal( + std::array{3, raw_data.shape(1), raw_data.shape(2)}, 0); + for (int gain = 0; gain < 3; ++gain) { + for (int row = 0; row < raw_data.shape(1); ++row) { + for (int col = 0; col < raw_data.shape(2); ++col) { + if (count(gain, row, col) != 0) { + pedestal(gain, row, col) = + static_cast(accumulator(gain, row, col)) / + static_cast(count(gain, row, col)); + } + } + } + } + return pedestal; +} + +template +NDArray calculate_pedestal_g0(NDView raw_data) { + auto [accumulator, count] = sum_and_count_g0(raw_data); + + NDArray pedestal( + std::array{raw_data.shape(1), raw_data.shape(2)}, 0); + for (int row = 0; row < raw_data.shape(1); ++row) { + for (int col = 0; col < raw_data.shape(2); ++col) { + if (count(row, col) != 0) { + pedestal(row, col) = + static_cast(accumulator(row, col)) / + static_cast(count(row, col)); + } + } + } + return pedestal; +} +template +NDArray calculate_pedestal_g0(NDView raw_data, + ssize_t n_threads) { + std::vector, NDArray>>> + futures; + futures.reserve(n_threads); + auto limits = split_task(0, raw_data.shape(0), n_threads); + // make subviews for each thread + std::vector> subviews; + for (const auto &lim : limits) { + subviews.emplace_back( + raw_data.data() + lim.first * raw_data.strides()[0], + std::array{lim.second - lim.first, raw_data.shape(1), + raw_data.shape(2)}); + } + for (auto view : subviews) { + futures.push_back(std::async( + static_cast, NDArray> (*)( + NDView)>(&sum_and_count_g0), + view)); + } + NDArray accumulator( + std::array{raw_data.shape(1), raw_data.shape(2)}, 0); + NDArray count( + std::array{raw_data.shape(1), raw_data.shape(2)}, 0); + for (auto &f : futures) { + auto [acc, cnt] = f.get(); + accumulator += acc; + count += cnt; + } + NDArray pedestal( + std::array{raw_data.shape(1), raw_data.shape(2)}, 0); + for (int gain = 0; gain < 3; ++gain) { + for (int row = 0; row < raw_data.shape(1); ++row) { + for (int col = 0; col < raw_data.shape(2); ++col) { + if (count(row, col) != 0) { + pedestal(row, col) = + static_cast(accumulator(row, col)) / + static_cast(count(row, col)); + } + } + } + } + return pedestal; + +} + + +template +NDArray calculate_pedestal(NDView raw_data, + ssize_t n_threads) { + NDArray switched( + std::array{raw_data.shape(1), raw_data.shape(2)}, 0); + std::vector, NDArray>>> + futures; + futures.reserve(n_threads); + auto limits = split_task(0, raw_data.shape(0), n_threads); + + // make subviews for each thread + std::vector> subviews; + for (const auto &lim : limits) { + subviews.emplace_back( + raw_data.data() + lim.first * raw_data.strides()[0], + std::array{lim.second - lim.first, raw_data.shape(1), + raw_data.shape(2)}); + } + for (auto view : subviews) { + futures.push_back(std::async( + static_cast, NDArray> (*)( + NDView)>(&sum_and_count_per_gain), + view)); + } + + NDArray accumulator( + std::array{3, raw_data.shape(1), raw_data.shape(2)}, 0); + NDArray count( + std::array{3, raw_data.shape(1), raw_data.shape(2)}, 0); + for (auto &f : futures) { + auto [acc, cnt] = f.get(); + accumulator += acc; + count += cnt; + } + + NDArray pedestal( + std::array{3, raw_data.shape(1), raw_data.shape(2)}, 0); + for (int gain = 0; gain < 3; ++gain) { + for (int row = 0; row < raw_data.shape(1); ++row) { + for (int col = 0; col < raw_data.shape(2); ++col) { + if (count(gain, row, col) != 0) { + pedestal(gain, row, col) = + static_cast(accumulator(gain, row, col)) / + static_cast(count(gain, row, col)); + } + } + } + } + return pedestal; +} + +/** + * @brief Count the number of switching pixels in the raw data. + * This function counts the number of pixels that switch between G1 and G2 gain. + * It returns an NDArray with the number of switching pixels per pixel. + * @param raw_data The NDView containing the raw data + * @return An NDArray with the number of switching pixels per pixel + */ +NDArray count_switching_pixels(NDView raw_data); + +/** + * @brief Count the number of switching pixels in the raw data. + * This function counts the number of pixels that switch between G1 and G2 gain. + * It returns an NDArray with the number of switching pixels per pixel. + * @param raw_data The NDView containing the raw data + * @param n_threads The number of threads to use for parallel processing + * @return An NDArray with the number of switching pixels per pixel + */ +NDArray count_switching_pixels(NDView raw_data, + ssize_t n_threads); + } // namespace aare \ No newline at end of file diff --git a/python/aare/__init__.py b/python/aare/__init__.py index c48da49..1d34a85 100644 --- a/python/aare/__init__.py +++ b/python/aare/__init__.py @@ -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 diff --git a/python/src/bind_calibration.hpp b/python/src/bind_calibration.hpp index df4b207..d2e43f1 100644 --- a/python/src/bind_calibration.hpp +++ b/python/src/bind_calibration.hpp @@ -17,19 +17,60 @@ py::array_t 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(data_span.shape()); auto res = make_view_3d(result); - - aare::apply_calibration(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(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(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 pybind_count_switching_pixels( + py::array_t data, + ssize_t n_threads = 4) { + + auto data_span = make_view_3d(data); + auto arr = new NDArray{}; + *arr = aare::count_switching_pixels(data_span, n_threads); + return return_image_data(arr); +} + +template +py::array_t pybind_calculate_pedestal( + py::array_t data, + ssize_t n_threads) { + + auto data_span = make_view_3d(data); + auto arr = new NDArray{}; + *arr = aare::calculate_pedestal(data_span, n_threads); + return return_image_data(arr); +} + +template +py::array_t pybind_calculate_pedestal_g0( + py::array_t data, + ssize_t n_threads) { + + auto data_span = make_view_3d(data); + auto arr = new NDArray{}; + *arr = aare::calculate_pedestal_g0(data_span, n_threads); + return return_image_data(arr); +} + void bind_calibration(py::module &m) { m.def("apply_calibration", &pybind_apply_calibration, py::arg("raw_data").noconvert(), py::kw_only(), @@ -40,4 +81,20 @@ void bind_calibration(py::module &m) { py::arg("raw_data").noconvert(), py::kw_only(), py::arg("pd").noconvert(), py::arg("cal").noconvert(), py::arg("n_threads") = 4); + + m.def("count_switching_pixels", &pybind_count_switching_pixels, + py::arg("raw_data").noconvert(), py::kw_only(), + py::arg("n_threads") = 4); + + m.def("calculate_pedestal_float", &pybind_calculate_pedestal, + py::arg("raw_data").noconvert(), py::arg("n_threads") = 4); + + m.def("calculate_pedestal", &pybind_calculate_pedestal, + py::arg("raw_data").noconvert(), py::arg("n_threads") = 4); + + m.def("calculate_pedestal_g0", &pybind_calculate_pedestal_g0, + py::arg("raw_data").noconvert(), py::arg("n_threads") = 4); + + m.def("calculate_pedestal_g0_float", &pybind_calculate_pedestal_g0, + py::arg("raw_data").noconvert(), py::arg("n_threads") = 4); } \ No newline at end of file diff --git a/src/NDArray.test.cpp b/src/NDArray.test.cpp index 91b5933..9af8379 100644 --- a/src/NDArray.test.cpp +++ b/src/NDArray.test.cpp @@ -25,13 +25,13 @@ TEST_CASE("Construct from an NDView") { REQUIRE(image.data() != view.data()); for (uint32_t i = 0; i < image.size(); ++i) { - REQUIRE(image(i) == view(i)); + REQUIRE(image[i] == view[i]); } // Changing the image doesn't change the view image = 43; for (uint32_t i = 0; i < image.size(); ++i) { - REQUIRE(image(i) != view(i)); + REQUIRE(image[i] != view[i]); } } diff --git a/src/calibration.cpp b/src/calibration.cpp new file mode 100644 index 0000000..c5578e3 --- /dev/null +++ b/src/calibration.cpp @@ -0,0 +1,78 @@ +#include "aare/calibration.hpp" + +namespace aare { + +std::pair, NDArray> sum_and_count_per_gain(NDView raw_data){ + NDArray accumulator(std::array{3, raw_data.shape(1), raw_data.shape(2)}, 0); + NDArray count(std::array{3, raw_data.shape(1), raw_data.shape(2)}, 0); + for (int frame_nr = 0; frame_nr != raw_data.shape(0); ++frame_nr) { + for (int row = 0; row != raw_data.shape(1); ++row) { + for (int col = 0; col != raw_data.shape(2); ++col) { + auto [value, gain] = get_value_and_gain(raw_data(frame_nr, row, col)); + accumulator(gain, row, col) += value; + count(gain, row, col) += 1; + } + } + } + + return {std::move(accumulator), std::move(count)}; +} + +std::pair, NDArray> sum_and_count_g0(NDView raw_data){ + NDArray accumulator(std::array{raw_data.shape(1), raw_data.shape(2)}, 0); + NDArray count(std::array{raw_data.shape(1), raw_data.shape(2)}, 0); + for (int frame_nr = 0; frame_nr != raw_data.shape(0); ++frame_nr) { + for (int row = 0; row != raw_data.shape(1); ++row) { + for (int col = 0; col != raw_data.shape(2); ++col) { + auto [value, gain] = get_value_and_gain(raw_data(frame_nr, row, col)); + if (gain != 0) + continue; // we only care about gain 0 + accumulator(row, col) += value; + count(row, col) += 1; + } + } + } + + return {std::move(accumulator), std::move(count)}; +} + +NDArray count_switching_pixels(NDView raw_data) { + NDArray switched(std::array{raw_data.shape(1), raw_data.shape(2)},0); + for (int frame_nr = 0; frame_nr != raw_data.shape(0); ++frame_nr) { + for (int row = 0; row != raw_data.shape(1); ++row) { + for (int col = 0; col != raw_data.shape(2); ++col) { + auto [value, gain] = get_value_and_gain(raw_data(frame_nr, row, col)); + if (gain != 0) { + switched(row, col) += 1; + } + } + } + } + return switched; +} + + +NDArray count_switching_pixels(NDView raw_data, ssize_t n_threads){ + NDArray switched(std::array{raw_data.shape(1), raw_data.shape(2)},0); + std::vector>> futures; + futures.reserve(n_threads); + auto limits = split_task(0, raw_data.shape(0), n_threads); + + // make subviews for each thread + std::vector> subviews; + for (const auto &lim : limits) { + subviews.emplace_back(raw_data.data() + lim.first * raw_data.strides()[0], + std::array{lim.second-lim.first, raw_data.shape(1), raw_data.shape(2)}); + } + + for (auto view : subviews) { + futures.push_back(std::async(static_cast(*)(NDView)>(&count_switching_pixels), view)); + } + + for (auto &f : futures) { + switched += f.get(); + } + return switched; +} + +}// namespace aare \ No newline at end of file