Add soft per-spot quality weighting for adaptive spot detection
Add --soft-weight (implies --adaptive-spots): give every detected spot a
continuous quality weight in (0,1] and keep the highest-weight spots rather than
the brightest, so a deliberately loose detector self-cleans -- bright ice / salt
/ jet blobs and single-pixel noise no longer evict faint clean Bragg spots from
the max-spots cut.
The weight is a product of dimensionless gates (AdaptiveSpotFinderCPU::ApplyWeights,
computed against the per-ring background the adaptive finder already builds): a
logistic ramp in the spot's SNR and a soft size band (rises from one pixel,
plateaus, falls for oversized ice/salt/streak blobs). It carries on
DiffractionSpot -> SpotToSave and is consumed by FilterSpotsByCount, which ranks
by {non-ice, weight, intensity} when requested and by intensity otherwise, so the
classic and FPGA paths are unchanged.
Honest result: on the serial-stills battery this is index-rate-NEUTRAL. The
weighted ranking only changes the outcome when the spot count exceeds the
max-spots cap and the weight disagrees with intensity in a way that affects
indexing; the adaptive detectors already produce clean spot lists and the weak
sets sit under the cap, so re-ranking is a wash there (and a wash, not a
regression, on the one set that floods). Its intended benefit -- robustness to
ice/jet-contaminated frames and to a loosened detector -- is not exercised by
this battery; kept opt-in as the substrate for that.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,15 +37,19 @@ void MarkIceRings(std::vector<SpotToSave> &spots, float tolerance_q_recipA) {
|
||||
}
|
||||
}
|
||||
|
||||
void FilterSpotsByCount(std::vector<SpotToSave> &input, int64_t count) {
|
||||
void FilterSpotsByCount(std::vector<SpotToSave> &input, int64_t count, bool by_weight) {
|
||||
size_t output_size = std::min<size_t>(input.size(), count);
|
||||
|
||||
std::ranges::partial_sort(input, input.begin() + output_size,
|
||||
std::ranges::less{}, // comparator on the projected key
|
||||
[](const SpotToSave &s) {
|
||||
// projection: key to compare by
|
||||
return std::tuple{s.ice_ring, -s.intensity};
|
||||
// false < true → non-ice first; negate intensity → higher first
|
||||
[by_weight](const SpotToSave &s) {
|
||||
// projection: key to compare by. non-ice first (false < true), then
|
||||
// by soft quality weight (higher first) when requested -- so a loose
|
||||
// detector's bright junk cannot evict faint clean Bragg -- else by
|
||||
// raw intensity. Intensity is the tie-breaker under the weight.
|
||||
if (by_weight)
|
||||
return std::tuple{s.ice_ring, -s.weight, -s.intensity};
|
||||
return std::tuple{s.ice_ring, 0.0f, -s.intensity};
|
||||
});
|
||||
input.resize(output_size);
|
||||
}
|
||||
@@ -152,7 +156,7 @@ void SpotAnalyze(const DiffractionExperiment &experiment,
|
||||
|
||||
output.resolution_estimate = GetResolution(spots_out);
|
||||
|
||||
FilterSpotsByCount(spots_out, experiment.GetMaxSpotCount());
|
||||
FilterSpotsByCount(spots_out, experiment.GetMaxSpotCount(), spot_finding_settings.soft_weight);
|
||||
|
||||
output.spots = spots_out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user