rugnux: the gain of a stills file that states none is read off its single-photon peak where the background resolves photons

A pedestal-subtracted JUNGFRAU background is mostly empty pixels; the variance per unit mean leaves
out the zeros and read 6.7 keV per photon on a 12 keV run in keV, 2.4 on a 4.6 keV one, so such
files needed --gain. MeasureGain now also histograms the sampled pixel values and takes the gain from
the single-photon peak (background_gain::PhotonPeak): the highest bin past the zero peak's descent,
standing 5 sigma above the valley before it, narrow (below half height a quarter of its value away),
and repeated by a two-photon peak near twice its value. Where there is no such pair the variance per
unit mean decides as before. Float frames are read for the measurement at a sixteenth of their unit,
so a photon of a few keV is resolved rather than rounded to whole keV.

Measured: float32 keV JUNGFRAU files 11.96 (photon energy 12.04 keV) and 4.53 (4.57 keV); the int16
photon copies show no peak past the first bin and keep the 0.55 variance answer, i.e. stay in
photons; the SACLA MPCCD and PAL Rayonix files show humps, not photon peaks, and measure as before.
A gain the file states or --gain gives still wins. Rotation is untouched (the pre-scan's CCD gain
uses only the variance estimator).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-10-22 20:22:15 +02:00
1 parent f72950612a
commit 87560311bc
6 files changed
+142 -11

No files matched your search

+42
View File
@@ -73,3 +73,45 @@ TEST_CASE("BackgroundGain_settings") {
CHECK(s.GetBackgroundPixelGain() == 4.5f);
CHECK_THROWS_AS(s.BackgroundPixelGain(0.5f), JFJochException);
}
namespace {
// A background of mean photons per pixel, each worth gain units, with Gaussian noise of sigma units, the
// pedestal subtracted.
std::vector<uint64_t> PhotonHistogram(double gain, double sigma, uint32_t seed, double mean = 0.2) {
std::mt19937 rng(seed);
std::poisson_distribution<int> photons(mean);
std::normal_distribution<double> noise(0.0, sigma);
std::vector<int32_t> img(W * H);
for (auto &v: img)
v = static_cast<int32_t>(std::lround(gain * photons(rng) + noise(rng)));
std::vector<uint64_t> histogram(background_gain::PEAK_HISTOGRAM_BINS, 0);
background_gain::AddToHistogram(img, std::vector<uint32_t>(W * H, 0), 1 << 30, histogram);
return histogram;
}
}
TEST_CASE("BackgroundGain_PhotonPeak") {
// A photon of 4.57 keV in a file in keV, read at a sixteenth of a keV, 0.3 keV of noise.
const auto g = background_gain::PhotonPeak(PhotonHistogram(4.57 * 16, 0.3 * 16, 1));
REQUIRE(g.has_value());
CHECK_THAT(*g / 16.0, Catch::Matchers::WithinAbs(4.57, 0.05));
// Whole detector units, 12 per photon.
const auto g12 = background_gain::PhotonPeak(PhotonHistogram(12.0, 1.0, 2));
REQUIRE(g12.has_value());
CHECK_THAT(*g12, Catch::Matchers::WithinAbs(12.0, 0.2));
// Integer photon counts: the single photon is the first bin, no peak past the zero peak.
CHECK_FALSE(background_gain::PhotonPeak(PhotonHistogram(1.0, 0.0, 3)).has_value());
// 30 photons per pixel at 4 units each: one hump, with no second peak at twice its value.
CHECK_FALSE(background_gain::PhotonPeak(PhotonHistogram(4.0, 3.0, 4, 30.0)).has_value());
// A CCD's background (bias + gain * photons + read noise): a hump, not a pair of photon peaks.
std::vector<uint32_t> mask;
std::vector<uint64_t> ccd(background_gain::PEAK_HISTOGRAM_BINS, 0);
background_gain::AddToHistogram(Frame(4.0, 5, mask), mask, 1 << 30, ccd);
CHECK_FALSE(background_gain::PhotonPeak(ccd).has_value());
}