From dfc6703f4a0b72064dc1d785c56c9c8f978cbbdc Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 1 Jul 2026 14:24:05 +0200 Subject: [PATCH] jfjoch_process: don't double-smooth per-frame G on the reference path SmoothImageScaleG rewrites the partials in place (image_scale_corr and image_scale_g). On the no-reference path that is harmless: each scaling pass recomputes G from scratch via ScaleAllImages, so smoothing always runs on freshly-refined G. On the reference path the scaling loop is skipped, so G is computed once and stays; running scale_and_merge twice (P1 then the adopted space group) smoothed the already-smoothed G a second time, compounding into a ~2x wider effective kernel than the configured --smooth-g and biasing the merged intensities. Smooth only on the first pass of the reference path (G is unchanged afterwards, and the smoothed partials persist into the second pass's combine3D). The no-reference path is unchanged. Verified on lyso (600 frames, -P rot3d -z ref.mtz -M): the reference run now logs the smoothing once instead of twice, and the merged MTZ changes. Co-Authored-By: Claude Opus 4.8 (1M context) --- process/JFJochProcess.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/process/JFJochProcess.cpp b/process/JFJochProcess.cpp index 9a6f424d..c9ada9e8 100644 --- a/process/JFJochProcess.cpp +++ b/process/JFJochProcess.cpp @@ -560,6 +560,11 @@ ProcessResult JFJochProcess::Run(JFJochProcessObserver *observer) { std::vector merged; MergeStatistics statistics; }; + // The reference path computes each image's G once (per-image scaling against the + // reference); the scaling loop below is skipped, so G is stable across the two passes. + // Smoothing it more than once would compound the correction, so do it only on the first + // pass. The no-reference path recomputes G from scratch each pass and re-smooths correctly. + bool reference_g_smoothed = false; auto scale_and_merge = [&](const std::string &label) -> ScaleMergeResult { // ScaleOnTheFly self-scaling is only for the no-reference path; with a reference each // image is already scaled against it during the per-image pass, so we merge directly. @@ -580,7 +585,8 @@ ProcessResult JFJochProcess::Run(JFJochProcessObserver *observer) { const double smooth_g_deg = experiment_.GetScalingSettings().GetSmoothGDegrees(); const auto gonio = experiment_.GetGoniometer(); const double osc_deg = gonio ? std::fabs(gonio->GetIncrement_deg()) : 0.0; - if (rot3d && smooth_g_deg > 0.0 && osc_deg > 1e-6) { + const bool reference_path = !config_.reference_data.empty(); + if (rot3d && smooth_g_deg > 0.0 && osc_deg > 1e-6 && !(reference_path && reference_g_smoothed)) { int window = std::max(1, static_cast(std::lround(smooth_g_deg / osc_deg))); if (window % 2 == 0) ++window; // odd window for a centered average @@ -588,6 +594,7 @@ ProcessResult JFJochProcess::Run(JFJochProcessObserver *observer) { logger.Info("Smoothing per-frame scale G over {:.2f} deg ({} frames at {:.3f} deg/frame)", smooth_g_deg, window, osc_deg); SmoothImageScaleG(indexer->GetIntegrationOutcome(), window, logger); + reference_g_smoothed = true; } std::vector combined; if (rot3d) {