diff --git a/image_analysis/scale_merge/ScaleOnTheFly.cpp b/image_analysis/scale_merge/ScaleOnTheFly.cpp index 3d431bd2..fda12d97 100644 --- a/image_analysis/scale_merge/ScaleOnTheFly.cpp +++ b/image_analysis/scale_merge/ScaleOnTheFly.cpp @@ -8,6 +8,12 @@ #include namespace { + // Robust loss scale (in sigma units) for the per-image scale fit: a few outlier reflections + // (zingers, overlaps, a mis-predicted spot) must not drag a frame's G/B into a bad optimum - + // that is the stochastic per-frame mis-scaling that elevates R-meas and collapses CC1/2 at low + // symmetry. Cauchy down-weights residuals beyond ~this many sigma without a hard cut. + constexpr double SCALE_ROBUST_K = 3.0; + double SafeInv(double x, double fallback) { if (!std::isfinite(x) || x == 0.0) return fallback; @@ -248,19 +254,19 @@ void ScaleOnTheFly::Scale(IntegrationOutcome &integration_outcome) const { case PartialityModel::Fixed: { auto *cost = new ceres::AutoDiffCostFunction( new IntensityFixedResidual(r, Itrue, sigma, r.partiality)); - problem.AddResidualBlock(cost, nullptr, &result.G, &result.B); + problem.AddResidualBlock(cost, new ceres::CauchyLoss(SCALE_ROBUST_K), &result.G, &result.B); } break; case PartialityModel::Unity: { auto *cost = new ceres::AutoDiffCostFunction( new IntensityFixedResidual(r, Itrue, sigma, 1.0)); - problem.AddResidualBlock(cost, nullptr, &result.G, &result.B); + problem.AddResidualBlock(cost, new ceres::CauchyLoss(SCALE_ROBUST_K), &result.G, &result.B); } break; case PartialityModel::Rotation: { auto *cost = new ceres::AutoDiffCostFunction( new ScalingRotationResidual(r, Itrue, sigma)); - problem.AddResidualBlock(cost, nullptr, &result.G, &result.B, &result.mos, + problem.AddResidualBlock(cost, new ceres::CauchyLoss(SCALE_ROBUST_K), &result.G, &result.B, &result.mos, &result.wedge); } break;