Add XDS-order scaling of the rot3d fulls: --scale-fulls

The rot3d combine emits fulls with partiality == 1 and image_scale_corr == 1,
so the fulls are only ever scaled as per-frame partials upstream - their
per-frame scale G is fit through the rocking-curve/partiality model
(G*partiality*B*lp*Itrue - Iobs) and so absorbs any model error. XDS/DIALS
instead scale the 3D-integrated fulls directly.

--scale-fulls inserts a second scaling pass on the combined fulls with the
Unity model (G*Itrue - I_full, no partiality term), between Combine3D and
MergeOnTheFly, reusing ScaleOnTheFly on a Unity-configured experiment copy. It
is a pure post-correction (updates the fulls' image_scale_corr 1 -> 1/G, no
re-combine).

HEWL crystal 2, anomalous S-peak height vs XDS: 0.53x -> 0.57x and ISa
9.4 -> 10.5 - improving precision and accuracy together (not the CC1/2-up /
anomalous-down trade-off of outlier rejection).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 20:43:04 +02:00
co-authored by Claude Opus 4.8
parent 50ebf1694e
commit 8d0cd19e48
3 changed files with 38 additions and 1 deletions
+24
View File
@@ -28,6 +28,7 @@
#include "../image_analysis/image_preprocessing/ImagePreprocessorBuffer.h"
#include "../image_analysis/scale_merge/Merge.h"
#include "../image_analysis/scale_merge/GlobalScale.h"
#include "../image_analysis/scale_merge/ScaleOnTheFly.h"
#include "../image_analysis/scale_merge/SearchSpaceGroup.h"
#include "../image_analysis/scale_merge/Combine3D.h"
#include "../image_analysis/WriteReflections.h"
@@ -118,6 +119,27 @@ namespace {
}
logger.Info("Global scaling complete ({} images)", outcomes.size());
}
// XDS-order scaling. The rot3d combine emits fulls with partiality == 1 (image_scale_corr == 1),
// so they were only ever scaled as per-frame *partials* upstream - their per-frame scale is
// entangled with the rocking-curve/partiality model. This refits a per-frame scale directly on
// the complete reflections with the Unity model (no partiality term, G*Itrue - I_full), the way
// XDS/DIALS scale 3D-integrated fulls. A pure post-correction: it updates image_scale_corr on the
// fulls (1 -> 1/G) without re-combining.
void ScaleFulls(const DiffractionExperiment &experiment,
std::vector<IntegrationOutcome> &fulls, int scaling_iter,
size_t nthreads, Logger &logger) {
DiffractionExperiment unity = experiment;
ScalingSettings ss = unity.GetScalingSettings();
ss.SetPartialityModel(PartialityModel::Unity);
unity.ImportScalingSettings(ss);
for (int i = 0; i < scaling_iter; i++) {
const auto reference = MergeAll(unity, fulls, false);
ScaleOnTheFly(unity, reference).Scale(fulls, nthreads);
}
logger.Info("Scaled fulls (XDS order, Unity model)");
}
}
JFJochProcess::JFJochProcess(JFJochHDF5Reader &reader, DiffractionExperiment experiment,
@@ -464,6 +486,8 @@ ProcessResult JFJochProcess::Run(JFJochProcessObserver *observer) {
if (rot3d)
combined = CombineRotationObservations(indexer->GetIntegrationOutcome(), experiment_, &logger,
config_.observation_dump_path);
if (rot3d && config_.scale_fulls)
ScaleFulls(experiment_, combined, static_cast<int>(config_.scaling_iter), config_.nthreads, logger);
const std::vector<IntegrationOutcome> &merge_input =
rot3d ? combined : indexer->GetIntegrationOutcome();