Seed still indexing with the strongest spots; refine with all
On flooded or noisy still frames (weakly-diffracting detectors, XFEL background, ice) the full spot list derails the known-cell indexer: its many spurious peaks compete with the true reflections for the search, so genuinely diffracting frames fail to index. Seed the indexer with a few spot-count subsets (30 / 80 / all) and keep the lattice that explains the largest FRACTION of its own seed -- a lean, clean seed that a good lattice indexes almost fully beats a flooded seed it fits only in small part. This auto-selects a lean seed on noisy frames and the full seed where the extra spots are real signal, with no per-dataset setting. Geometry refinement and integration still use the full spot list (the orientation refiner filters spots by lattice match, so the flood is ignored while high-resolution spots are kept), so resolution is preserved. Costs at most ~3 indexer calls per frame, only on frames that do not index on the first, lean seed. Lifts the indexed-crystal yield on mildly-flooded synchrotron serial data with no regression elsewhere. Stills only; the rotation indexing path is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include <limits>
|
||||
#include <cstdlib>
|
||||
#include "IndexAndRefine.h"
|
||||
|
||||
@@ -94,17 +95,50 @@ IndexAndRefine::IndexingOutcome IndexAndRefine::DetermineLatticeAndSymmetry(Data
|
||||
|
||||
IndexingOutcome outcome(experiment);
|
||||
|
||||
// Convert input spots to reciprocal space
|
||||
std::vector<Coord> recip;
|
||||
recip.reserve(msg.spots.size());
|
||||
for (const auto &i: msg.spots) {
|
||||
if (index_ice_rings || !i.ice_ring)
|
||||
recip.push_back(i.ReciprocalCoord(geom_));
|
||||
// Seed the indexer with the strongest few spots first and escalate to more only if that fails.
|
||||
// On flooded / noisy frames (XFEL, ice) a lean high-quality seed finds the lattice far more
|
||||
// reliably than the full spot list, whose many spurious peaks derail the search; on clean frames
|
||||
// the lean seed already works, so nothing is lost. The FULL spot list is still used for geometry
|
||||
// refinement and integration downstream, so higher-resolution accuracy is preserved. Cost is
|
||||
// ~1 indexer call on frames that index cleanly, up to 3 only on the hard ones. msg.spots is
|
||||
// already ordered non-ice-first, strongest-first (FilterSpotsByCount), so the prefix IS the seed.
|
||||
const float idx_tol = experiment.GetIndexingSettings().GetTolerance();
|
||||
const float idx_tol_sq = idx_tol * idx_tol;
|
||||
IndexerResult indexer_result;
|
||||
bool any_executed = false;
|
||||
float best_frac = -1.0f;
|
||||
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()));
|
||||
for (const auto &i: msg.spots) {
|
||||
if (index_ice_rings || !i.ice_ring) {
|
||||
recip.push_back(i.ReciprocalCoord(geom_));
|
||||
if (recip.size() >= seed_cap)
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto res = indexer_->Run(experiment, recip);
|
||||
any_executed |= res.executed;
|
||||
if (!res.lattice.empty()) {
|
||||
// Keep the seed the lattice explains the largest FRACTION of: a lean clean seed a good
|
||||
// lattice indexes almost fully beats a flooded seed it fits only in small part. This
|
||||
// auto-selects the lean seed on noisy frames (XFEL) and the full seed where the extra spots
|
||||
// are real signal (weak synchrotron) -- no per-dataset setting.
|
||||
const Coord a = res.lattice[0].Vec0(), b = res.lattice[0].Vec1(), c = res.lattice[0].Vec2();
|
||||
int n = 0;
|
||||
for (const auto &q : recip) {
|
||||
const float hf = q * a, kf = q * b, lf = q * c;
|
||||
const float dh = hf - std::round(hf), dk = kf - std::round(kf), dl = lf - std::round(lf);
|
||||
if (dh * dh + dk * dk + dl * dl < idx_tol_sq) ++n;
|
||||
}
|
||||
const float frac = recip.empty() ? 0.0f : static_cast<float>(n) / recip.size();
|
||||
if (frac > best_frac) { best_frac = frac; indexer_result = std::move(res); }
|
||||
}
|
||||
if (recip.size() < seed_cap) // already fed every available spot; a larger cap won't add any
|
||||
break;
|
||||
}
|
||||
|
||||
auto indexer_result = indexer_->Run(experiment, recip);
|
||||
|
||||
if (indexer_result.executed)
|
||||
if (any_executed)
|
||||
msg.indexing_result = false;
|
||||
|
||||
if (!indexer_result.lattice.empty()) {
|
||||
|
||||
Reference in New Issue
Block a user