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) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 14:24:05 +02:00
co-authored by Claude Opus 4.8
parent ab2176960f
commit dfc6703f4a
+8 -1
View File
@@ -560,6 +560,11 @@ ProcessResult JFJochProcess::Run(JFJochProcessObserver *observer) {
std::vector<MergedReflection> 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<int>(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<IntegrationOutcome> combined;
if (rot3d) {