Bragg integration: a CCD rotation sweep's sigmas at the measured gain, intensities in photons

The pre-scan measures the gain of an SMV/marCCD detector (background variance per unit mean) but the
integrator used it only for the background clip: sigma, var_bkg, the strong-spot flag (I/sigma >= 5)
and the profile-vs-box-sum guard (10 box sigmas) all treated one ADU as one photon, so on a Saturn
(g 4.7-5.8) the guard sat at ~4.5 true sigma. The new BraggIntegrationSettings::PixelGain (set by
rugnux from the same measurement, rotation only, at max(1, g)) takes every variance the engine forms
at the gain, and Finalize converts I, sigma, bkg and var_bkg to photons, which is what the merge's
variance model (var_bkg + I) assumes. CPU and CUDA engines; Metal runs the CPU code for this. Gain 1
(every photon-counting input, ADSC/marCCD whose measured ratio is below 1, and stills) is bit-identical.

Battery --only, 12 open-arm CCD/background-dominated sets vs the base build (987e812e2), GPU:
the six Saturn sets (g 4.7-5.8) all improve - guard fallbacks 4.1-17.3% -> 1.2-6.1%,
anomalous peak height 3mc4 x1.10, 3p85 x1.02, 3r6o x1.06, 5cc8 x1.03, 5uth x1.05, 5vml x1.04;
R_meas 0.880-0.993x on all six; R_model shell-scaled better on 5/6 (5cc8 +0.0005); ISa 5uth +1.5%,
5vml +15%, 3p85 +3%, the twinned 3mc4/3r6o -5%/-1%. 6cee, 6i3j, 6v2r, 7kcn, 8v4j, 9jzo unchanged.

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-23 01:26:44 +02:00
1 parent b6f4973908
commit 46da7fb359
9 files changed
+114 -19

No files matched your search

+43
View File
@@ -351,3 +351,46 @@ TEST_CASE("BraggIntegrationEngineCPU_StillKeepsOnlySaturatedOverloads", "[Integr
CHECK(out[1].overloaded);
CHECK(still.Counts().overloads_kept == 1);
}
// A detector that reports gain ADU per photon: the same spot read at gain 4 leaves the integrator in
// photons - a quarter of the ADU intensity, at the Poisson sigma of that many photons, i.e. half the
// sigma the ADU counted as photons gave.
TEST_CASE("BraggIntegrationEngineCPU_PixelGainGivesPhotons", "[Integration][portable]") {
DiffractionExperiment experiment(DetJF(2));
experiment.DetectorDistance_mm(100.0f).IncidentEnergy_keV(WVL_1A_IN_KEV).BeamX_pxl(400.0f).BeamY_pxl(400.0f);
const size_t width = experiment.GetXPixelsNum(), npixel = experiment.GetPixelsNum();
const int cx = 600, cy = 300;
ImagePreprocessorBuffer image(npixel);
for (size_t i = 0; i < npixel; ++i)
image[i] = 40;
for (int dy = -6; dy <= 6; ++dy)
for (int dx = -6; dx <= 6; ++dx)
image[(cy + dy) * width + cx + dx] += static_cast<int32_t>(std::lround(800.0 * std::exp(-(dx * dx + dy * dy) / (2 * 1.3 * 1.3))));
Reflection r{};
r.h = 1; r.k = 2; r.l = 3;
r.predicted_x = cx; r.predicted_y = cy;
r.d = 2.0f;
r.prescaling_corr = 1.0f;
r.partiality = 1.0f;
const std::vector<Reflection> predicted{r};
for (const auto mode : {IntegratorMode::BoxSum, IntegratorMode::ProfileGaussian}) {
BraggIntegrationSettings settings;
settings.Integrator(mode);
experiment.ImportBraggIntegrationSettings(settings);
BraggIntegrationEngineCPU adu(experiment, PixelMask(experiment));
const auto out_adu = adu.Run(image, predicted, 1, 0);
settings.PixelGain(4.0f);
experiment.ImportBraggIntegrationSettings(settings);
BraggIntegrationEngineCPU photons(experiment, PixelMask(experiment));
const auto out_ph = photons.Run(image, predicted, 1, 0);
REQUIRE(out_adu.size() == 1);
REQUIRE(out_ph.size() == 1);
CHECK(out_ph[0].I == Catch::Approx(out_adu[0].I / 4.0f));
CHECK(out_ph[0].sigma == Catch::Approx(out_adu[0].sigma / 2.0f));
CHECK(out_ph[0].bkg == Catch::Approx(out_adu[0].bkg / 4.0f));
CHECK(out_ph[0].var_bkg == Catch::Approx(out_adu[0].var_bkg / 4.0f));
}
}