rugnux: measure the ice in the first pass, and always find its own spots
Ice handling was gated on a measurement the run only made AFTER the images had been processed, so the per-image pass could not use it. The flagging therefore ran unconditionally: ice-band spots were ordered last in the --max-spots budget and held out of the indexer seed and the geometry refinement on every crystal, iced or not. The eleven bands are fixed geometry holding 16-26 % of the unique reflections whether or not there is ice, so on a clean crystal that discards a fifth of the spots - the strongest first - for nothing. Measured on a crystal whose gate never fires, that moved the merged data by a mean of 0.85 sigma against a run-to-run floor of 9.3e-5. Measure it in the first pass instead. That pass already looks at ~100 images spread over the sweep, and it already stops at the spot finder, so it sees the azimuthal profile for the smooth channel and the unfiltered connected components for the spot channel. Both counts SpotAnalyze takes are pre-filter, so pooling them there is the run's own verdict, reached before anything has been discarded and in time for the pass that acts on it. Where the sample sees no ice, the run indexes on the ice-band spots too. It has to be the whole sample: the spot channel is a ratio pooled over images, because one frame carries a handful of control spots. A per-image gate is not an alternative - two of the crystals whose indexing this rescues fire on that channel alone, at profile scores of 1.12 and 1.22, so gating per image on the profile score would drop exactly the cases that matter. This also removes the first-pass spot reuse, and with it --redo-rotation-spots and the reuse path. Finding the ~100 first-pass spots costs little, and reusing was actively wrong here: the stored spots were found online at the acquisition's threshold and have already had their ice-band entries ordered last and dropped by its spot budget, so counting ice from them under-reads it by construction, and the lattice search never saw the spot-finding settings at all. It also removes the need for the machinery that re-found spots whenever a spot-finding option was named, which made those options impossible to A/B. IndexAndRefine cached index_ice_rings at construction, which happens before the first pass; it holds a reference to the experiment, so it now reads the setting where it uses it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,8 +64,7 @@ namespace {
|
||||
|
||||
IndexAndRefine::IndexAndRefine(const DiffractionExperiment &x, IndexerThreadPool *indexer,
|
||||
bool retain_outcomes, bool real_time)
|
||||
: index_ice_rings(x.GetIndexingSettings().GetIndexIceRings()),
|
||||
retain_outcomes_(retain_outcomes),
|
||||
: retain_outcomes_(retain_outcomes),
|
||||
real_time(real_time),
|
||||
experiment(x),
|
||||
geom_(x.GetDiffractionGeometry()),
|
||||
@@ -158,6 +157,9 @@ IndexAndRefine::IndexingOutcome IndexAndRefine::DetermineLatticeAndSymmetry(Data
|
||||
IndexerResult indexer_result;
|
||||
bool any_executed = false;
|
||||
float best_frac = -1.0f;
|
||||
// Read per call rather than cached at construction: the run measures for itself whether the
|
||||
// crystal has ice, and that verdict lands after this object is built.
|
||||
const bool index_ice_rings = experiment.GetIndexingSettings().GetIndexIceRings();
|
||||
for (size_t seed_cap : {size_t{30}, size_t{80}, std::numeric_limits<size_t>::max()}) {
|
||||
std::vector<Coord> recip;
|
||||
recip.reserve(std::min<size_t>(seed_cap, msg.spots.size()));
|
||||
|
||||
Reference in New Issue
Block a user