diff --git a/docs/CPU_DATA_ANALYSIS.md b/docs/CPU_DATA_ANALYSIS.md index 234dfff2..0bae209b 100644 --- a/docs/CPU_DATA_ANALYSIS.md +++ b/docs/CPU_DATA_ANALYSIS.md @@ -187,7 +187,7 @@ Special cases: ### 3.2 Adaptive (self-calibrating) detection -The local-statistics test above still needs a fixed photon/count threshold, and the right value depends on the background level, which varies between datasets — so it has to be tuned per dataset. The **adaptive** mode (`--adaptive-spots`; the default for stills in `rugnux` and in the viewer, `--no-adaptive-spots` reverts) removes that tuning by deriving the threshold from each image's own noise, per resolution ring. Rotation data keeps the fixed-threshold finder by default: across a 33-crystal rotation battery adaptive detection helped four hard crystals but deterministically broke three (a lost space group, a halved indexing rate, a collapsed merge). +The local-statistics test above still needs a fixed photon/count threshold, and the right value depends on the background level, which varies between datasets — so it has to be tuned per dataset. The **adaptive** mode (`--adaptive-spots`; the default in `rugnux` and in the viewer, `--no-adaptive-spots` reverts) removes that tuning by deriving the threshold from each image's own noise, per resolution ring. It is the default for rotation data as well: it was held back while a 33-crystal battery showed it breaking three crystals (a lost space group, a halved indexing rate, a collapsed merge), but none of those causes lay in detection. Adaptive finds roughly twice as many spots and they are real — 64 % recur at the same position on the adjacent frame against a 0.5 % chance rate, and none would fail a conventional local-SNR test — but they include genuine peaks that belong to no indexed lattice, and the damage they did scaled with their absolute *count*. Guarding the per-frame scale against collapse and weighting each spot by how strong it is for its resolution (§ per-image refinement) removed all three failures; adaptive now matches the exact space group on 26 of 33 crystals against 25 for the fixed-threshold finder, with better ISa on six. Pixels are binned into the same resolution rings as the azimuthal integrator (§2). For each ring a robust background is estimated in three passes: one plain pass over all valid pixels, then two $\sigma$-clipping passes that keep only pixels within $\pm 3\sigma$ of the current ring mean (removing the Bragg peaks from the background estimate). This yields a per-ring background mean $\mu_b$ and scatter $\sigma_b$. diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index d1e1e66f..cde3d25e 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -66,7 +66,7 @@ void print_usage() { std::cout << " --spot-sigma Noise sigma level for spot finding (default: 3.0)" << std::endl; std::cout << " --spot-threshold Photon count threshold for spot finding (default: 10)" << std::endl; std::cout << " --min-pix-per-spot Minimum connected strong pixels per spot. If omitted, min-pix is chosen PER IMAGE (stills indexing): the frame is indexed at min-pix 3/2/1 and the one maximising indexed count x indexed fraction is kept. Give an explicit value to force a fixed min-pix instead." << std::endl; - std::cout << " --adaptive-spots Self-calibrating detection (DEFAULT for stills): the strong-pixel threshold comes from each image's own per-resolution-ring noise instead of the fixed --spot-threshold, so one setting adapts across datasets (no per-dataset --spot-threshold/--spot-sigma tuning). Rotation data keeps the fixed-threshold finder unless this is given." << std::endl; + std::cout << " --adaptive-spots Self-calibrating detection (DEFAULT): the strong-pixel threshold comes from each image's own per-resolution-ring noise instead of the fixed --spot-threshold, so one setting adapts across datasets (no per-dataset --spot-threshold/--spot-sigma tuning)." << std::endl; std::cout << " --no-adaptive-spots Turn adaptive detection off and use the fixed --spot-threshold / --spot-sigma finder instead" << std::endl; std::cout << " --spot-false-pixels Adaptive detection operating point: expected noise pixels tolerated per frame (default: 100; implies --adaptive-spots)" << std::endl; std::cout << " --spot-high-resolution High resolution limit for spot finding. If omitted, stills extend as far as the detector reaches (no resolution clipping) and rotation data keeps a 1.5 A limit." << std::endl; @@ -1495,7 +1495,7 @@ int main(int argc, char **argv) { spot_settings.min_pix_per_spot = 2; if (rotation_indexing && !d_min_spot_finding.has_value()) d_min_spot_finding = 1.5f; - spot_settings.adaptive_threshold = adaptive_spots.value_or(!rotation_indexing); + spot_settings.adaptive_threshold = adaptive_spots.value_or(true); spot_settings.high_resolution_limit = d_min_spot_finding; spot_settings.false_pixels_per_frame = false_pixels_per_frame; if (d_max_spot_finding > 0.0f)