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); }