The score gated on 60 spots, but the seed escalation stops at the leanest seed that indexes - 30 spots on precisely the clean frames a grid scan produces - so the value was absent exactly where beamline automation most needs it, and absence maps to "engage": the protocol would have fired on every good frame, which degenerates the trigger into "always". The floor itself stays where it was calibrated; what changes is what it gates. When no escalation pass could answer, one severity-only pass runs over the full spot list - the row search alone, no reduction, no refinement - purely to produce the number. The same was true of the indexer choice: only the FFT family computes a row shortlist, so a deployment configured with the known-cell indexer - the ordinary online stills path - never produced the score at all. Where the severity-only pass has no row search to run, the severity is read off the rows of the winning lattice instead, which any indexer produces: the lattice's shortest few distinct directions, as many as the FFT shortlist resolves in practice, fed through the same window and scoring with equal magnitudes. The count parity is load-bearing - a worst case over every enumerable lattice direction fires on 100% of harmless mounts of a generic triclinic cell against 74% for this selection at theta_max = 15 deg, and an always-firing trigger decides nothing - while the diad-detection rate stays 1.00 on the monoclinic classes either way, a dropped axis row being recovered by the pair normals exactly as an invisible one is. A frame that neither indexed nor reached the spot floor still reports nothing, which is the honest answer and maps to the recoverable error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
42 lines
1.7 KiB
C++
42 lines
1.7 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "Indexer.h"
|
|
|
|
void Indexer::Setup(const DiffractionExperiment& experiment) {
|
|
geom = experiment.GetDiffractionGeometry();
|
|
indexing_tolerance = experiment.GetIndexingSettings().GetTolerance();
|
|
dist_tolerance_vs_reference = experiment.GetIndexingSettings().GetUnitCellDistTolerance();
|
|
viable_cell_min_spots = experiment.GetIndexingSettings().GetViableCellMinSpots();
|
|
index_ice_rings = experiment.GetIndexingSettings().GetIndexIceRings();
|
|
wavelength_A = experiment.GetWavelength_A();
|
|
spindle_axis = {};
|
|
if (const auto goniometer = experiment.GetGoniometer())
|
|
spindle_axis = goniometer->GetAxis();
|
|
spindle_theta_max_deg = SpindleThetaMax_deg(wavelength_A, experiment.GetDetectorMaxResolution_A());
|
|
SetupUnitCell(experiment.GetUnitCell());
|
|
}
|
|
|
|
IndexerResult Indexer::Run(const std::vector<Coord> &coord, bool severity_only) {
|
|
IndexerResult ret;
|
|
auto start = std::chrono::steady_clock::now();
|
|
std::optional<SpindleSeverity> severity;
|
|
if (severity_only) {
|
|
severity = RunSeverityOnly(coord);
|
|
} else {
|
|
ret.lattice = RunInternal(coord, coord.size());
|
|
ret.executed = true;
|
|
severity = GetSpindleSeverity();
|
|
}
|
|
|
|
auto end = std::chrono::steady_clock::now();
|
|
std::chrono::duration<float> duration = end - start;
|
|
ret.indexing_time_s = duration.count();
|
|
if (severity) {
|
|
ret.spindle_blind_fraction = severity->score;
|
|
ret.spindle_row_length_A = severity->row_length_A;
|
|
ret.spindle_miss_angle_deg = severity->miss_angle_deg;
|
|
}
|
|
return ret;
|
|
}
|