From cdbd0bed5de8a5af816b205559031f079df20b55 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 2 Jul 2026 14:57:16 +0200 Subject: [PATCH] partiality: fix the rotation-prediction wedge, use the stored value in scaling BraggPredictionRot halves settings.wedge_deg to the +/- half-wedge of the partiality erf pair, but IndexAndRefine already passed GetWedge_deg()/2, so the two /2 compounded to a half-wedge of increment/4 - half the correct Kabsch value (increment/2, which ScaleOnTheFly's RotationPartiality already used). Pass the full increment so prediction's partiality matches scaling. With prediction correct, ScaleOnTheFly now uses the stored r.partiality directly (the value the reflection was integrated with) rather than recomputing the erf pair per reflection - recomputing only when scaling overrides the geometry (-w wedge refinement, --mosaicity, or a scaling wedge override). Output-neutral on the /data/rotation_test battery (SG/cell/completeness identical, ISa/CC1/2 within run noise on the stable crystals). Co-Authored-By: Claude Fable 5 --- image_analysis/IndexAndRefine.cpp | 5 ++++- image_analysis/scale_merge/ScaleOnTheFly.cpp | 13 +++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/image_analysis/IndexAndRefine.cpp b/image_analysis/IndexAndRefine.cpp index c1b0ac7e..89c938d7 100644 --- a/image_analysis/IndexAndRefine.cpp +++ b/image_analysis/IndexAndRefine.cpp @@ -292,7 +292,10 @@ void IndexAndRefine::QuickPredictAndIntegrate(DataMessage &msg, float mos_deg = 0.1f; if (experiment.GetGoniometer().has_value()) { - wedge_deg = experiment.GetGoniometer()->GetWedge_deg() / 2.0; + // Full oscillation wedge of one frame; BraggPredictionRot halves it to the +/- half-wedge of the + // partiality erf pair (Kabsch). Passing the full increment gives a half-wedge of increment/2 - + // matching ScaleOnTheFly's RotationPartiality, so the predicted partiality is used directly there. + wedge_deg = experiment.GetGoniometer()->GetWedge_deg(); if (msg.mosaicity_deg) { mos_deg = msg.mosaicity_deg.value(); diff --git a/image_analysis/scale_merge/ScaleOnTheFly.cpp b/image_analysis/scale_merge/ScaleOnTheFly.cpp index fe909feb..086fc928 100644 --- a/image_analysis/scale_merge/ScaleOnTheFly.cpp +++ b/image_analysis/scale_merge/ScaleOnTheFly.cpp @@ -269,6 +269,12 @@ void ScaleOnTheFly::Scale(IntegrationOutcome &integration_outcome) const { result.wedge = NAN; } + // The partiality stored on each reflection was computed at prediction with this image's mosaicity and + // the (now correct) rotation wedge, so it is used directly. Recompute it only when scaling overrides + // that geometry: wedge refinement, a forced mosaicity, or a scaling wedge override. + const bool recompute_partiality = model == PartialityModel::Rotation + && (refine_rot_wedge || s.GetForcedMosaicity().has_value() || s.GetRotationWedgeForScaling().has_value()); + auto clear_scale = [&]() { integration_outcome.image_scale_cc.reset(); integration_outcome.image_scale_cc_n.reset(); @@ -298,7 +304,9 @@ void ScaleOnTheFly::Scale(IntegrationOutcome &integration_outcome) const { partiality = 1.0; break; case PartialityModel::Rotation: - partiality = RotationPartiality(r.delta_phi_deg, r.zeta, result.mos, result.wedge); + partiality = recompute_partiality + ? RotationPartiality(r.delta_phi_deg, r.zeta, result.mos, result.wedge) + : r.partiality; break; default: partiality = r.partiality; @@ -399,7 +407,8 @@ void ScaleOnTheFly::Scale(IntegrationOutcome &integration_outcome) const { r.partiality = 1.0; break; case PartialityModel::Rotation: { - if (std::isfinite(r.delta_phi_deg) && std::isfinite(r.zeta) && result.mos > 1e-6) + if (recompute_partiality && std::isfinite(r.delta_phi_deg) && std::isfinite(r.zeta) + && result.mos > 1e-6) r.partiality = RotationPartiality(r.delta_phi_deg, r.zeta, result.mos, result.wedge); break; }