Files
Jungfraujoch/image_analysis/spot_finding/SpotUtils.h
T
leonarski_fandClaude Opus 5 1388b16d7a Resolution estimate: predict past the edge of the detector
The spot-finding resolution estimate was clamped so it could never beat the
detector corner. On a crystal that diffracts past the corner that reports where
the DETECTOR stops, which is the one thing this number is not for - it is meant
to say how far a merge of data like these would reach, a property of the
crystal and the exposure. The clamp also hid the interesting case: an estimate
finer than what the run actually merged is the statement "this run was
detector-limited", and there was no way to make it.

The statistic already extrapolates. Its quantile sits in the middle of the
fall-off, well inside what the detector records, so it goes on measuring the
crystal's own decay when the detector cuts that decay short. Measured by
truncating the spot lists of 31 battery crystals at an artificial detector edge
and scoring the unclamped answer against each crystal's own measured CC1/2 =
0.30 crossing, it holds its 8-9% floor out to about 1.7x past the cut and only
then drifts pessimistic, which is the safe direction. Every genuinely
detector-limited crystal in the battery needs between 1.10x and 1.63x.

Against a truth corrected for censoring - the six crystals whose merge is cut
off by their own detector cannot have a measured crossing, so theirs is
extrapolated from multiplicity-corrected <I/sigma> and anchored on the 25 where
both exist: symmetric-log RMS 13.5 -> 9.4% over 37 crystals, 25 -> 28 within
0.2 A. On the six detector-limited ones 26.1 -> 9.8% and the bias goes +19 ->
-3%; on the 31 that are not, 9.23 -> 9.32%, i.e. it costs them nothing. The
0.30 tail fraction and the 2.25 reach were refit by leave-one-out against that
truth and did not move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FBumeJVx4oeXxiBRpkrE5H
2026-08-28 10:41:34 +02:00

52 lines
2.6 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include "../../common/DiffractionSpot.h"
void GenerateSpotPlot(DataMessage &msg, const std::vector<SpotToSave> &spots, float d_min_A);
void CountSpots(DataMessage &msg,
const DiffractionExperiment& experiment,
const std::vector<DiffractionSpot> &spots,
float d_min_A);
void CountSpots(DataMessage &msg,
const std::vector<SpotToSave> &spots,
float d_min_A);
float CountIceRingControlSpots(const std::vector<SpotToSave> &spots, float half_width_q_recipA);
void MarkIceRings(std::vector<SpotToSave> &spots, float tolerance_q_recipA);
// Keep the strongest `count` spots. With deprioritise_ice, spots on the hexagonal ice bands are ordered
// last and so are the first to go; pass false where the run has no measurable ice, in which case the
// flag marks ordinary reflections and ordering on it would discard good data.
void FilterSpotsByCount(std::vector<SpotToSave> &input, int64_t count, bool deprioritise_ice);
void FilterSpuriousHighResolutionSpots(std::vector<SpotToSave> &spots, float threshold);
// How far the SCALED AND MERGED data from a run of images like this one are expected to reach, in A,
// from the spots of this one image alone. Nothing else - no lattice, no integration, no merge.
//
// The spots' intensity-weighted resolution distribution falls off at the crystal's own rate, and a
// quantile taken near the middle of that fall-off measures that rate. The far end of the distribution
// does not: the highest-resolution spot found says where DETECTION stops, which moves with the
// exposure and with how many reflections the unit cell puts on the frame. Merging then reaches a fixed
// factor further in 1/d than the quantile, because averaging many observations goes on measuring
// intensities that one image cannot detect.
//
// The answer is deliberately NOT limited to what this detector records. The quantile sits in the
// middle of the fall-off, well inside the recorded range, so it goes on measuring the crystal when
// the detector stops before the diffraction does - which is the case the number is most wanted for.
// Returns nothing when the image has too few spots to have a fall-off at all.
std::optional<float> GetResolution(const std::vector<SpotToSave> &spots);
void SpotAnalyze(const DiffractionExperiment &experiment,
const SpotFindingSettings &settings,
const std::vector<DiffractionSpot> &spots,
DataMessage &message);