ice rings: add --detect-ice-rings and exclude ice-ring reflections from scaling

jfjoch_process gains --detect-ice-rings, which (a) activates the existing spot-finder
ice flagging (ice spots de-prioritised in indexing) and (b) drops reflections sitting
on a hexagonal-ice powder ring from scaling/combine/merge/stats, via a new shared
IsOnIceRing() helper over ICE_RING_RES_A using the spot-finder's q half-width. Their
integrated intensity is contaminated by the strong, variable ice background, so leaving
them in mis-scales the whole frame and inflates the error model.

On EP0117 (ice-ring crystal): de-novo space-group determination recovers from P1 to P2
and CC1/2 improves (31->37%). Off by default; a no-op without the flag. This is the
first, non-controversial step - the residual gap needs ice-aware background/integration
(follow-up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 20:40:51 +02:00
co-authored by Claude Opus 4.8
parent 7ade6d9906
commit a74b8767de
3 changed files with 45 additions and 0 deletions
+20
View File
@@ -34,6 +34,7 @@
#include "../image_analysis/scale_merge/Combine3D.h"
#include "../image_analysis/scale_merge/HKLKey.h"
#include "../image_analysis/WriteReflections.h"
#include "../common/Definitions.h"
#include <array>
#include <map>
#include <Eigen/Dense>
@@ -532,6 +533,25 @@ ProcessResult JFJochProcess::Run(JFJochProcessObserver *observer) {
};
phase("Scaling and merging");
// Ice-ring handling (--detect-ice-rings): drop reflections sitting on a hexagonal-ice powder
// ring before scaling. Their integrated intensity is contaminated by the strong, variable ice
// background, so leaving them in mis-scales the whole frame (the per-image fit is dragged) and
// inflates the error model. Removed once here, so every scaling pass, the combine, the merge
// and the statistics all skip them.
if (experiment_.IsDetectIceRings()) {
const float ice_width = config_.spot_finding.ice_ring_width_Q_recipA;
size_t total = 0, dropped = 0;
for (auto &outcome : indexer->GetIntegrationOutcome()) {
total += outcome.reflections.size();
const size_t before = outcome.reflections.size();
std::erase_if(outcome.reflections,
[&](const Reflection &r) { return IsOnIceRing(r.d, ice_width); });
dropped += before - outcome.reflections.size();
}
logger.Info("Ice-ring exclusion: dropped {} of {} reflections on ice rings (half-width {:.3f} A^-1)",
dropped, total, ice_width);
}
// Scale the images and merge. Factored so it can run twice: first in P1 to give the
// space-group search a merged dataset, then again in the determined space group so the
// scaling sees symmetry equivalents and the final statistics are in the right symmetry.