// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include #include #include #include "../image_analysis/bragg_integration/BackgroundGain.h" #include "../common/BraggIntegrationSettings.h" #include "../common/JFJochException.h" namespace { constexpr size_t W = 512, H = 512; // A smooth background of 100..2000 photons per pixel, read out as bias + gain * photons + read noise, // with a grid of bright 5x5 "Bragg" spots and a masked strip. std::vector Frame(double gain, uint32_t seed, std::vector &mask) { std::mt19937 rng(seed); std::normal_distribution read_noise(0.0, 3.0); std::vector img(W * H); mask.assign(W * H, 0); for (size_t y = 0; y < H; y++) for (size_t x = 0; x < W; x++) { const double photons = 100.0 + 1900.0 * (x + y) / static_cast(W + H); std::poisson_distribution p(photons); double v = (gain > 1.0 ? 20.0 : 0.0) + gain * p(rng) + (gain > 1.0 ? read_noise(rng) : 0.0); if (x % 32 >= 14 && x % 32 < 19 && y % 32 >= 14 && y % 32 < 19) v += 50.0 * gain * photons; img[y * W + x] = static_cast(std::lround(v)); if (y >= 200 && y < 210) mask[y * W + x] = 1; } return img; } } TEST_CASE("BackgroundGain_CCD_gain4") { std::vector frames; std::vector mask; for (uint32_t seed = 1; seed <= 3; seed++) { const auto img = Frame(4.0, seed, mask); frames.push_back(background_gain::MeasureFrame(img, W, H, mask, 1 << 30)); } const auto g = background_gain::Gain(frames); REQUIRE(g.has_value()); CHECK_THAT(*g, Catch::Matchers::WithinAbs(4.0, 0.25)); } TEST_CASE("BackgroundGain_photon_counting") { std::vector mask; const auto img = Frame(1.0, 7, mask); const auto g = background_gain::Gain({background_gain::MeasureFrame(img, W, H, mask, 1 << 30)}); REQUIRE(g.has_value()); CHECK_THAT(*g, Catch::Matchers::WithinAbs(1.0, 0.1)); } TEST_CASE("BackgroundGain_saturated_and_empty") { std::vector mask; const auto img = Frame(4.0, 11, mask); // Everything at or above the saturation limit: nothing to measure. CHECK_FALSE(background_gain::Gain({background_gain::MeasureFrame(img, W, H, mask, 1)}).has_value()); CHECK_FALSE(background_gain::Gain({}).has_value()); } TEST_CASE("BackgroundGain_settings") { BraggIntegrationSettings s; CHECK(s.GetBackgroundPixelGain() == 1.0f); s.BackgroundPixelGain(4.5f); CHECK(s.GetBackgroundPixelGain() == 4.5f); CHECK_THROWS_AS(s.BackgroundPixelGain(0.5f), JFJochException); }