rugnux: scale self-referenced stills in a single pass (fix weak-data collapse)

The stills self-scaling loop rebuilt the merge reference FROM the just-scaled reflections
every iteration, so on weak data each pass re-fit its own noise and the merged CC1/2
collapsed (PfluDING combine: 0.3% at the default 3 iters). It is provably pointless too:
ScaleOnTheFly::Scale re-solves the per-image G from raw I and never reads the prior
correction, so against a fixed reference passes 2..N are bit-identical - the single G is the
exact one-pass solution and iteration only ever does the harmful reference rebuild (N=1 is
best on every dataset, including strong lyso 82% vs 76% at N=3; leave-one-out and external
anchors were tested and do not beat it). Do one self-reference pass; rotation (RotationScaleMerge)
and the external-reference branch are untouched.

Validated: PfluDING-combined CC1/2 0.3% -> 42.0% by default (pinned 2.5 A); strong data unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 12:05:08 +02:00
co-authored by Claude Opus 4.8
parent 4a5028d98d
commit 9527fa6e2c
2 changed files with 12 additions and 7 deletions
+6 -6
View File
@@ -594,12 +594,12 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
// running merge with ScaleOnTheFly (fixed partiality), then merge directly. With an external
// reference each image is already scaled against it during the per-image pass, so skip.
if (config_.reference_data.empty()) {
for (int i = 0; i < config_.scaling_iter; i++) {
phase("Scaling images (" + label + ", iteration " + std::to_string(i + 1) + "/"
+ std::to_string(config_.scaling_iter) + ")");
auto merge_result = MergeAll(experiment_, indexer->GetIntegrationOutcome(), false);
indexer->ScaleAllImages(merge_result);
}
// One pass: the per-image scale G is the exact one-pass solution, so iterating only
// rebuilds the reference from the freshly-scaled (noisy) data - degrading weak stills
// instead of converging (measured CC1/2 collapse at the default 3 iters on weak data).
phase("Scaling images (" + label + ")");
auto merge_result = MergeAll(experiment_, indexer->GetIntegrationOutcome(), false);
indexer->ScaleAllImages(merge_result);
}
const std::vector<IntegrationOutcome> &merge_input = indexer->GetIntegrationOutcome();
+6 -1
View File
@@ -1035,7 +1035,12 @@ int main(int argc, char **argv) {
merged_statistics = std::move(r.statistics);
error_model_isa = r.isa;
} else {
for (int i = 0; i < scaling_iter; i++) {
// The per-image scale G is the exact one-pass solution (Scale re-solves from raw I and never
// reads the prior correction), so with a self-rebuilt reference iterating only re-fits the
// freshly-scaled noise and degrades weak stills (measured CC1/2 collapse at the default 3
// iters). Scale the self-reference merge once; a fixed external reference keeps scaling_iter.
const int n_self = reference_data.empty() ? 1 : scaling_iter;
for (int i = 0; i < n_self; i++) {
if (reference_data.empty())
ScaleOnTheFly(experiment, MergeAll(experiment, reflections)).Scale(reflections, nthreads);
else