From a2adc4e0214d1b4c9b4dbc9ef835357703e7f4de Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Fri, 31 Jul 2026 11:51:08 +0200 Subject: [PATCH] Stills scaling: an image whose scale collapsed is dropped, not merged unscaled Leaving it at G = 1 looked like the conservative choice and is the more damaging of the two errors. The per-image scale enters as rlp/(partiality*G) and multiplies intensity and sigma alike, so substituting 1 for a scale that was really 1/200 of the run median puts the intensities in 200x too low with sigmas 200x too low too - 1/G^2 times the weight they deserve. The merge cannot defend itself against that, because the number that is wrong is the number the weight is built from. And if the collapsed value was instead a failed fit, G = 1 merges the image mis-scaled by an unknown factor. Per-crystal scales on serial stills genuinely span orders of magnitude, unlike frames of one rotation sweep, so both readings are live. An image whose scale is not believable has no usable scale. Write NaN into its image_scale_corr, which every merge path already skips on, so it drops out of the merged intensities, the error model and the statistics consistently. Co-Authored-By: Claude Opus 5 (1M context) --- image_analysis/scale_merge/ScaleOnTheFly.cpp | 22 +++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/image_analysis/scale_merge/ScaleOnTheFly.cpp b/image_analysis/scale_merge/ScaleOnTheFly.cpp index 7c719436..de9db0be 100644 --- a/image_analysis/scale_merge/ScaleOnTheFly.cpp +++ b/image_analysis/scale_merge/ScaleOnTheFly.cpp @@ -148,9 +148,15 @@ void ScaleOnTheFly::Scale(IntegrationOutcome &integration_outcome) const { // A per-image scale that has collapsed toward zero multiplies that image's intensities by 1/G - and its // sigmas by the same factor, so nothing downstream can recognise it: the merge's n-sigma outlier test // scales with the very number that is wrong. The rotation path already refuses a per-frame scale this -// far below its neighbours; the stills path had no such guard. An image whose fit collapsed is left -// UNSCALED (G = 1, the same state as an image with too few reflections to fit) rather than merged with -// its intensities blown up. +// far below its neighbours; the stills path had no such guard. +// +// Such an image is DROPPED from the merge, not merged unscaled. Substituting G = 1 looks conservative +// but is the more damaging of the two errors: if the collapsed value was a failed fit, the image goes in +// mis-scaled by an unknown factor, and if it was a real scale (per-crystal scales on serial stills +// genuinely span orders of magnitude, unlike frames of one rotation sweep) then G = 1 divides both its +// intensities AND its sigmas by 1/G, so it enters at 1/G^2 times the weight it deserves - the merge +// cannot down-weight it, because the number that is wrong is the same number the weight is built from. +// An image whose scale is not believable has no usable scale, so it contributes nothing instead. void ScaleOnTheFly::RejectCollapsedScales(std::vector &integration) { std::vector fitted; fitted.reserve(integration.size()); @@ -168,10 +174,10 @@ void ScaleOnTheFly::RejectCollapsedScales(std::vector &integ for (auto &i: integration) { if (!i.image_scale_g || !std::isfinite(*i.image_scale_g) || *i.image_scale_g >= g_floor) continue; + // A non-finite correction is what every merge path already skips on (Merge.cpp), so this drops + // the image consistently from the merged intensities, the error model and the statistics. for (auto &r: i.reflections) - r.image_scale_corr = (std::isfinite(r.rlp) && r.partiality > 0.0f) - ? static_cast(r.rlp / r.partiality) - : NAN; + r.image_scale_corr = NAN; i.image_scale_cc.reset(); i.image_scale_cc_n.reset(); i.image_scale_g.reset(); @@ -179,8 +185,8 @@ void ScaleOnTheFly::RejectCollapsedScales(std::vector &integ } if (n_rejected > 0) Logger("ScaleOnTheFly").Warning( - "Left {} image(s) unscaled: their per-image scale collapsed more than {:.0f}x below the " - "run median, which would have amplified their intensities by the same factor", + "Dropped {} image(s) from the merge: their per-image scale collapsed more than {:.0f}x below " + "the run median, so it is not a scale the intensities can be put on", n_rejected, 1.0 / MIN_CREDIBLE_SCALE_RATIO); }