Bragg integration: separate the three things a bandwidth used to switch

Setting a bandwidth flipped three unrelated switches at once: it changed the profile's
radial capture term, it moved the width measurement from the signal disk to the whole
fit grid, and it silently overrode the background clip and trim, so --background-clip
under --bandwidth was ignored - the two runs were bit-identical.

The width measurement was the damaging one. The fit grid is an azimuthally averaged
stack, so its second moment is sigma_r^2 + sigma_t^2 and the radial smear of a
bandwidth leaked into the tangential model - a tangential width of 3.04 px against a
1.06 px truth, inflating the effective background pixel count where the weak signal is.
The result was a step rather than a slope: on genuinely monochromatic data, declaring a
0.2% bandwidth cost ISa 28.4 -> 22.2.

Measure the two widths separately, accumulated in each spot's own radial/tangential
frame over the signal disk, from the signed profile cells - away from the peak a
learned cell is background noise centred on zero, so the signed sum is unbiased, while
clamping it at zero turns that noise into a pedestal the r^2 weight reads as width. The
radial term is then the measured excess or the analytic floor, whichever is larger.

With the two widths separated there is nothing left for the broadband switch to select,
so it is gone - which is the proof the three were independent. The background clip and
trim now come from the settings in every case; the tuned 3-sigma broadband default
moves to the rugnux front end, which is the only place that knows whether the user gave
a value.

Monochromatic data: declaring a 0.2% bandwidth now costs ISa 28.4 -> 27.9 rather than
22.2, and forcing the old 3-sigma clip in the new build reproduces the good result, so
none of the step came from the clip. On large-bandwidth data CC1/2 improves in 8 of 10
shells. Across 12 monochromatic crystals the space groups are unchanged and CC1/2 moves
by at most 0.2 points.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-09 21:08:29 +02:00
co-authored by Claude Opus 5
parent 3d3fb0e58b
commit 1239c49731
9 changed files with 174 additions and 124 deletions
@@ -57,26 +57,25 @@ BraggIntegrationEngine::BraggIntegrationEngine(const DiffractionExperiment &expe
G = 2 * R + 1;
GG = G * G;
// A set bandwidth (broadband / stills) vs monochromatic (rotation) splits the treatment: the
// background sigma-clip and radial-elongation terms are path-dependent (see ProfileIntegrate2D).
// The X-ray bandwidth enters ONE place: it smears a reflection radially by bw_sigma * Rpx, which
// the per-reflection Gaussian carries as part of its radial variance. It is not a mode switch -
// the background estimator and the parallax/capture term below are the same whatever the beam is.
bw_sigma = experiment.GetBandwidthFWHM().value_or(0.0f) / 2.3548f;
broadband = bw_sigma > 0.0;
const double c_par = parallax_var_px2(det.GetSensorMaterial(), det.GetSensorThickness_um(),
geom.GetWavelength_A(), geom.GetPixelSize_mm() * 1000.0);
c_radial = c_par + (broadband ? 0.0 : bragg_engine::C_CAPTURE);
c_radial = c_par + bragg_engine::C_CAPTURE;
F_px = geom.GetDetectorDistance_mm() / std::max(1e-6f, geom.GetPixelSize_mm());
beam_x = geom.GetBeamX_pxl();
beam_y = geom.GetBeamY_pxl();
use_ellipse = !empirical && (bw_sigma > 0.0 || c_radial > 0.0);
use_ellipse = !empirical;
// Robust background ring, one estimator or the other (see BraggIntegrationSettings). Broadband
// (non-zero bandwidth: pink-beam / DMM) data keep their tuned 3 sigma high-side clip whatever the
// settings say; monochromatic data - rotation AND stills, the discriminator is the beam, not the
// acquisition mode - take the clip multiplier from settings, and fall back to the symmetric trim
// only when the clip is switched off (rugnux --background-trim).
bkg_clip_nsigma = broadband ? 3.0f : settings.GetBackgroundClipNSigma();
bkg_trim = (broadband || bkg_clip_nsigma > 0.0f) ? 0.0f : settings.GetBackgroundTrimFraction();
// Robust background ring, one estimator or the other (see BraggIntegrationSettings): a high-side
// sigma-clip (rugnux --background-clip, the default) or, when the clip is switched off, a
// symmetric trimmed mean (rugnux --background-trim). The caller owns the choice - the engine no
// longer overrides it for broadband data.
bkg_clip_nsigma = settings.GetBackgroundClipNSigma();
bkg_trim = bkg_clip_nsigma > 0.0f ? 0.0f : settings.GetBackgroundTrimFraction();
// Radial-offset kernels for the background curvature correction. A stencil pixel at (dx, dy)
// sits at radial offset dx*cos(phi) + dy*sin(phi) from the reflection, where phi is the