From dd75c6eb2b8a694a3f6b889f7029e6ed1111dfbf Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Mon, 19 Oct 2026 14:21:22 +0200 Subject: [PATCH] RotationScaleMerge: the fulls' own deviation is handed back however the loop stopped (RESULT-CHANGING) ShrinkToRestrained ran only off a fulls loop that reported itself converged, so whether each frame kept what its own fulls say beyond the smooth curve depended on whether the last step happened to cross the tolerance: one sweep's ISa read 35.09 or 34.46 by how its loop stopped. The hand-back is a continuous decision already - each frame keeps the fraction its observations support, read off the lag-1 covariance of the sweep's deviations - so it is now applied after every fulls loop. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4 --- .../scale_merge/RotationScaleMerge.cpp | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/image_analysis/scale_merge/RotationScaleMerge.cpp b/image_analysis/scale_merge/RotationScaleMerge.cpp index be3b9aa95..f8e574d64 100644 --- a/image_analysis/scale_merge/RotationScaleMerge.cpp +++ b/image_analysis/scale_merge/RotationScaleMerge.cpp @@ -2098,25 +2098,24 @@ RotationScaleMerge::ScalingLoopOutcome RotationScaleMerge::RunFullsScalingLoop( if (stop) break; } // The curve is the part of the scale the frames share over a rocking curve and more; what a - // frame's own fulls say beyond it is handed back by how much of it is real (ShrinkToRestrained) - - // only off a loop that settled, since a loop still walking has measured nothing to hand back. - if (out.converged) { - fit(); - std::vector fitted(n_frames); - for (int f = 0; f < n_frames; ++f) - fitted[f] = frame_scaled_scratch[f] && frame_info_scratch[f] > 0.0 ? 1 : 0; - g_new = g; - ShrinkToRestrained("fulls", g, fitted, frame_obs_count, g_fit); - for (int f = 0; f < n_frames; ++f) - if (fitted[f] && std::isfinite(g_fit[f]) && g_fit[f] > 0.0) g_new[f] = g_fit[f]; - const double g_ref = TypicalFrameScale(g_new, n_obs); - for (int f = 0; f < n_frames; ++f) { - g_new[f] /= g_ref; - ratio[f] = g[f] / g_new[f]; - } - rescale(has_fulls, ratio); - g = g_new; + // frame's own fulls say beyond it is handed back by how much of it is real (ShrinkToRestrained), + // however the loop stopped: what is real is read off the frames themselves, and a hand-back that + // depended on whether the last step crossed the tolerance moved ISa by 0.6 on one sweep. + fit(); + std::vector fitted(n_frames); + for (int f = 0; f < n_frames; ++f) + fitted[f] = frame_scaled_scratch[f] && frame_info_scratch[f] > 0.0 ? 1 : 0; + g_new = g; + ShrinkToRestrained("fulls", g, fitted, frame_obs_count, g_fit); + for (int f = 0; f < n_frames; ++f) + if (fitted[f] && std::isfinite(g_fit[f]) && g_fit[f] > 0.0) g_new[f] = g_fit[f]; + const double g_ref = TypicalFrameScale(g_new, n_obs); + for (int f = 0; f < n_frames; ++f) { + g_new[f] /= g_ref; + ratio[f] = g[f] / g_new[f]; } + rescale(has_fulls, ratio); + g = g_new; frame_scaled_scratch = has_fulls; out.best_step = best_step; std::vector positive;