Files
Jungfraujoch/image_analysis/spot_finding/SpotFindingSettings.h
T
leonarski_fandClaude Opus 4.8 427f4b3fff Choose min-pix-per-spot adaptively per image for serial-stills indexing
For stills indexing the minimum-pixels-per-spot filter is now chosen per image
instead of being fixed: the frame is indexed at min-pix 3/2/1 and the setting that
maximises indexed-spot count weighted by indexed fraction (n_indexed^2 / n_total)
is kept, then integrated once at that min-pix. The fraction factor keeps a smaller
min-pix's extra spots only when the lattice actually explains them, so strong frames
retain their real weak spots (extending resolution) while noise-flooded frames stay
strict.

The mode is selected by the presence of --min-pix-per-spot, now optional
(SpotFindingSettings::min_pix_per_spot is std::optional<int64_t>): omit it for the
adaptive per-image path, give a value to force a fixed min-pix. It applies only to
the stills indexing path -- rotation indexing builds one global lattice and keeps a
fixed min-pix, and the online receiver and the FPGA host path always carry a concrete
value, so neither changes. IndexAndRefine::ProcessImage now returns whether the frame
indexed, to drive the per-image selection.

Exposed in the jfjoch_viewer spot-finding settings (adaptive-threshold and
adaptive-min-pix checkboxes, each greying out the control it overrides); the broker
uses neither.

Validated on the full rotation regression battery (no regression) and the whole
serial-stills target battery at full image count.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 18:31:35 +02:00

39 lines
1.9 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <optional>
#include <cstdint>
struct SpotFindingSettings {
bool enable = true;
float signal_to_noise_threshold = 4.0; // STRONG_PIXEL in XDS
int64_t photon_count_threshold = 10; // Threshold in photon counts
// Minimum connected pixels per spot. std::nullopt = choose it per image: on the stills indexing
// path the frame is indexed at min-pix 3/2/1 and the one maximising indexed count x indexed fraction
// is kept (see MXAnalysisWithoutFPGA::Analyze); a value fixes it. Defaults to a concrete value, so
// the online receiver and the FPGA path keep the single-pass fixed behaviour unless set otherwise.
std::optional<int64_t> min_pix_per_spot = 2;
int64_t max_pix_per_spot = 50; // Maximum pixels per spot
float high_resolution_limit = 2.0;
float low_resolution_limit = 50.0;
float cutoff_spot_count_low_res = 5.0;
std::optional<float> high_res_gap_Q_recipA = 1.5; // 0.25 * 2 * pi
// Half-width of the ice-ring exclusion band in q (2*pi/d). Measured hexagonal-ice ring FWHM on the
// JUNGFRAU is ~0.06 q, so the band half-width is ~0.03; 0.02 under-covered the strong low-res rings.
float ice_ring_width_Q_recipA = 0.03;
bool indexing = true;
bool quick_integration = true;
// Self-calibrating detection (offline/rugnux path): when true, the fixed photon_count_threshold is
// replaced by a per-resolution-ring threshold set from the image's own noise (see
// AdaptiveSpotFinderCPU), so the same setting adapts across datasets with no per-dataset tuning.
// false_pixels_per_frame is the one portable knob: the expected number of noise pixels tolerated
// per frame (the threshold's operating point), ~100 for a multi-megapixel detector.
bool adaptive_threshold = false;
float false_pixels_per_frame = 100.0f;
};