// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include #include #include "../image_analysis/bragg_integration/BackgroundAperture.h" #include "../common/JFJochMath.h" namespace { constexpr int W = 600, H = 600; // A background of `level` counts a pixel with Poisson noise, modulated by `ripple` (relative) with a // period of 20 px along x - the structure a ring further from the disk reads wrong. ImagePreprocessorBuffer Frame(double level, double ripple, uint32_t seed) { std::mt19937 rng(seed); ImagePreprocessorBuffer img(static_cast(W) * H); for (int y = 0; y < H; y++) for (int x = 0; x < W; x++) { std::poisson_distribution p(level * (1.0 + ripple * std::sin(2.0 * PI * x / 20.0))); img[static_cast(y) * W + x] = p(rng); } return img; } int BestR3(double level, double ripple) { const auto r3 = background_aperture::CandidateR3(8.0f, 14.0f); background_aperture::Tally tally; for (uint32_t seed = 1; seed <= 4; seed++) { auto img = Frame(level, ripple, seed); background_aperture::MeasureFrame(img, W, H, {}, 6.0f, 8.0f, r3, 4.0f, 1.0f, tally); } REQUIRE(tally.apertures > 100); return static_cast(r3[background_aperture::Best(tally)]); } } // namespace TEST_CASE("BackgroundAperture_CandidateR3", "[Integration]") { const auto r3 = background_aperture::CandidateR3(8.0f, 14.0f); REQUIRE(r3.size() == 5); CHECK(r3.front() == 10.0f); CHECK(r3.back() == 14.0f); } // A few counts a pixel on a flat background: the noise wins, and the widest ring is kept. TEST_CASE("BackgroundAperture_FlatNoisyKeepsWidestRing", "[Integration]") { CHECK(BestR3(0.5, 0.0) == 14); } // Thousands of counts a pixel on a background that varies over the stencil's own scale: the structure // wins, and the narrowest ring is kept. TEST_CASE("BackgroundAperture_StructuredBrightTakesNarrowestRing", "[Integration]") { CHECK(BestR3(2000.0, 0.05) == 10); } // An aperture over an unreadable pixel is not used. TEST_CASE("BackgroundAperture_SkipsUnreadablePixels", "[Integration]") { const auto r3 = background_aperture::CandidateR3(8.0f, 14.0f); auto img = Frame(1.0, 0.0, 7); background_aperture::Tally all; background_aperture::MeasureFrame(img, W, H, {}, 6.0f, 8.0f, r3, 4.0f, 1.0f, all); for (size_t i = 0; i < static_cast(W) * H; i += 97) img[i] = INT32_MIN; background_aperture::Tally masked; background_aperture::MeasureFrame(img, W, H, {}, 6.0f, 8.0f, r3, 4.0f, 1.0f, masked); CHECK(masked.apertures == 0); CHECK(all.apertures > 0); }