bragg_integration: quantization-noise variance floor + summation fallback
Build Packages / build:viewer-tgz:cpu (push) Successful in 6m41s
Build Packages / build:viewer-tgz:cuda (push) Successful in 7m58s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 13m22s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m21s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 13m31s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 13m54s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m7s
Build Packages / build:windows:nocuda (push) Successful in 16m13s
Build Packages / build:windows:cuda (push) Successful in 17m40s
Build Packages / build:rpm (rocky8) (push) Successful in 10m58s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 12m37s
Build Packages / XDS test (durin plugin) (push) Successful in 7m17s
Build Packages / Generate python client (push) Successful in 28s
Build Packages / Build documentation (push) Successful in 1m9s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m52s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 12m44s
Build Packages / build:rpm (rocky9) (push) Successful in 13m44s
Build Packages / DIALS test (push) Successful in 13m38s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m29s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 8m54s
Build Packages / Unit tests (push) Successful in 1h51m52s
Build Packages / Unit tests (pull_request) Successful in 1h38m57s
Build Packages / build:viewer-tgz:cpu (pull_request) Successful in 6m10s
Build Packages / build:viewer-tgz:cuda (pull_request) Successful in 6m44s
Build Packages / build:rpm (rocky8_nocuda) (pull_request) Successful in 10m9s
Build Packages / build:rpm (rocky9_nocuda) (pull_request) Successful in 11m10s
Build Packages / build:rpm (ubuntu2204_nocuda) (pull_request) Successful in 10m30s
Build Packages / build:rpm (ubuntu2404_nocuda) (pull_request) Successful in 9m52s
Build Packages / build:rpm (rocky8_sls9) (pull_request) Successful in 10m48s
Build Packages / build:rpm (rocky9_sls9) (pull_request) Successful in 11m9s
Build Packages / build:rpm (rocky8) (pull_request) Successful in 10m46s
Build Packages / build:rpm (rocky9) (pull_request) Successful in 12m2s
Build Packages / build:rpm (ubuntu2204) (pull_request) Successful in 11m16s
Build Packages / build:rpm (ubuntu2404) (pull_request) Successful in 10m47s
Build Packages / DIALS test (pull_request) Successful in 14m23s
Build Packages / XDS test (durin plugin) (pull_request) Successful in 7m59s
Build Packages / XDS test (JFJoch plugin) (pull_request) Successful in 7m43s
Build Packages / XDS test (neggia plugin) (pull_request) Successful in 7m47s
Build Packages / Generate python client (pull_request) Successful in 12s
Build Packages / Build documentation (pull_request) Successful in 1m11s
Build Packages / Create release (pull_request) Skipped
Build Packages / build:windows:nocuda (pull_request) Successful in 15m54s
Build Packages / build:windows:cuda (pull_request) Successful in 18m24s

The Kabsch profile-fit weights floored the per-pixel variance at 1.0, far
above the real detector noise floor (electronic noise is well below 1 for
both EIGER and JUNGFRAU). The true floor is the quantization noise from
rounding the charge-spread deposited energy to an integer: Var = 1/12. The
1.0 floor silently over-regularized — it inflated weak-reflection sigma ~6x
at high resolution and pinned the scaling error model's `a` term at its 0.25
floor, suppressing ISa.

Use PIXEL_VARIANCE_FLOOR = 1/12, and guard against the weak-reflection
runaways it exposes: fall back to the box-sum (summation) intensity when the
profile fit diverges from the summation seed by more than
PROFILE_SUMMATION_MAX_NSIGMA box-sum sigmas. Applied identically in the CPU
and GPU engines (GPU-vs-CPU equivalence test passes).

Battery of 24 rotation crystals: broad ISa gains (lysoC_14 7.8->10.4, plus
cytC/Ins/Myo), EcwtAL500 completeness 51->92% and CC1/2 35->79%, pding4_001
space group now correct; no space-group regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 14:30:15 +02:00
co-authored by Claude Opus 4.8
parent 04bce94f4e
commit f6eca6c69e
3 changed files with 36 additions and 7 deletions
@@ -48,6 +48,18 @@ constexpr int N_SHELL = 6; // resolution shells for per-shell
constexpr double STRONG_I_OVER_SIGMA = 5.0; // strong-spot threshold that seeds the profile
constexpr int MIN_STRONG_PER_SHELL = 30; // below this a shell falls back to the global profile
constexpr double C_CAPTURE = 2.5; // weak-spot radial capture term (monochromatic only)
// Per-pixel variance floor for the Kabsch fit weights (v = floor + signal). The detector noise floor is
// the quantization noise from rounding the charge-spread deposited energy to an integer: a uniform
// rounding error has variance 1/12. Electronic noise is far below this for both EIGER and JUNGFRAU. A
// larger floor (the previous 1.0) silently over-regularizes — it inflates weak-reflection sigma and
// pins the scaling error model's `a` term at its floor.
constexpr double PIXEL_VARIANCE_FLOOR = 1.0 / 12.0;
// Guard against profile-fit runaways: on a weak / near-zero reflection the reweighted Kabsch iteration
// has no real peak to lock onto and can manufacture intensity the box sum never sees. Fall back to the
// summation (box-sum) intensity when the profile result disagrees with the summation seed by more than
// this many box-sum sigmas (a real fit agrees within counting noise, so the margin is generous).
constexpr double PROFILE_SUMMATION_MAX_NSIGMA = 10.0;
} // namespace bragg_engine
// One reflection's extracted intensity, produced by the derived engine and turned into a
@@ -293,7 +293,7 @@ std::vector<Reflection> BraggIntegrationEngineCPU::RunImpl(const Sampler &img,
}
const int Gf = 2 * Rf + 1;
const double B = std::max(rh.bkg, 1.0);
const double B = std::max(rh.bkg, PIXEL_VARIANCE_FLOOR);
double I = rh.I, den = 0.0;
for (int iter = 0; iter < 4; ++iter) {
double num = 0.0;
@@ -314,7 +314,16 @@ std::vector<Reflection> BraggIntegrationEngineCPU::RunImpl(const Sampler &img,
}
if (!(den > 0.0)) continue;
results[i] = {static_cast<float>(I), static_cast<float>(std::sqrt(1.0 / den)),
// Guard against profile-fit runaways: on a weak / near-zero reflection the reweighted Kabsch
// iteration has no real peak to lock onto and can manufacture intensity the box sum never sees.
// Keep the profile intensity only if it agrees with the summation seed within the margin;
// otherwise fall back to the summation, which is robust there.
double sigma = std::sqrt(1.0 / den);
if (std::abs(I - rh.I) > PROFILE_SUMMATION_MAX_NSIGMA * rh.sigma) {
I = rh.I;
sigma = rh.sigma;
}
results[i] = {static_cast<float>(I), static_cast<float>(sigma),
static_cast<float>(rh.bkg), 0.0f, 0.0f, true, false};
}
@@ -253,7 +253,7 @@ __global__ void build_profiles(const float *shell_grid, const float *global_grid
// One block per reflection; the (possibly elongated) profile is built in shared memory. ---
__global__ void fit(const int32_t *img, const float *px_x, const float *px_y,
const int *cx_a, const int *cy_a, const float *dd, const unsigned long long *invd2mm,
const float *I_seed, const float *bkg_a, const uint8_t *ok_a,
const float *I_seed, const float *sigma_seed, const float *bkg_a, const uint8_t *ok_a,
const float *shell_P, const float *global_P,
const float *shell_sigma2, const float *global_sigma2, const int *shell_n,
float *I_o, float *sigma_o, uint8_t *ok_o, BraggGpuParams p, int n) {
@@ -308,7 +308,7 @@ __global__ void fit(const int32_t *img, const float *px_x, const float *px_y,
}
const int Rf = s_Rf, Gf = s_Gf, GfGf = Gf * Gf;
const float B = fmaxf(bkg, 1.0f);
const float B = fmaxf(bkg, (float) PIXEL_VARIANCE_FLOOR);
if (threadIdx.x == 0) s_I = I_seed[i];
__syncthreads();
@@ -335,8 +335,16 @@ __global__ void fit(const int32_t *img, const float *px_x, const float *px_y,
}
if (threadIdx.x == 0) {
if (s_den > 0.0f) { I_o[i] = s_I; sigma_o[i] = sqrtf(1.0f / s_den); ok_o[i] = 1; }
else ok_o[i] = 0;
if (s_den > 0.0f) {
float I = s_I, sigma = sqrtf(1.0f / s_den);
// Guard against profile-fit runaways (see the CPU engine): fall back to the summation seed
// when the profile result diverges from it.
if (fabsf(I - I_seed[i]) > (float) PROFILE_SUMMATION_MAX_NSIGMA * sigma_seed[i]) {
I = I_seed[i];
sigma = sigma_seed[i];
}
I_o[i] = I; sigma_o[i] = sigma; ok_o[i] = 1;
} else ok_o[i] = 0;
}
}
@@ -447,7 +455,7 @@ std::vector<Reflection> BraggIntegrationEngineGPU::Run(const ImagePreprocessorBu
d_shell_grid, d_global_grid, d_shell_n, d_global_n,
d_shell_P, d_global_P, d_shell_sigma2, d_global_sigma2, p);
fit<<<n, threads, fit_shared_bytes, *stream>>>(img, d_px_x, d_px_y, d_cx, d_cy, d_d, d_invd2,
d_I, d_bkg, d_ok, d_shell_P, d_global_P,
d_I, d_sigma, d_bkg, d_ok, d_shell_P, d_global_P,
d_shell_sigma2, d_global_sigma2, d_shell_n,
d_I, d_sigma, d_ok, p, n);
}