From f2f95c44f6e1a64b2fc71c63c312c159841d187d Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Mon, 15 Jun 2026 23:00:37 +0200 Subject: [PATCH] FFTIndexer: name the histogram axis one_over_d (1/d), not q/Q The FFT histogram extent was written as maxQ = 2*pi/HighRes (the powder Q = 2*pi/d convention) while the bin width and spot coordinates use the internal 1/d convention - a unit conflation. It is benign: len_coeff = 2*max_length/histogram_size carries the same factor, so recovered cell lengths are correct; the 2*pi only acts as a ~6.28x zero-padding of the histogram. Rewrite it transparently: one_over_d_max = 1/HighRes (1/d), with the 2*pi kept as an explicitly-named oversampling factor. In this codebase Q always denotes 2*pi/d, so 1/d is named one_over_d (never q). histogram_size is numerically identical to before, so behaviour is unchanged (FFT de-novo crystal 2 95.44%, bit-identical merge). Documented that the exact padding amount is load-bearing at the marginal-frame level (rounding 2*pi to a nearby integer shifts indexing ~0.5-1%). Co-Authored-By: Claude Opus 4.8 --- image_analysis/indexing/FFTIndexer.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/image_analysis/indexing/FFTIndexer.cpp b/image_analysis/indexing/FFTIndexer.cpp index a0f224d3..2578d589 100644 --- a/image_analysis/indexing/FFTIndexer.cpp +++ b/image_analysis/indexing/FFTIndexer.cpp @@ -13,10 +13,21 @@ FFTIndexer::FFTIndexer(const IndexingSettings &settings) nDirections(settings.GetFFT_NumVectors()), result_fft(nDirections) { - float maxQ = 2.0f * static_cast(M_PI) / settings.GetFFT_HighResolution_A(); + // Reciprocal-magnitude histogram in one_over_d = 1/d units (the internal convention - + // the spot coordinates and histogram_spacing are 1/d too; the resolution limit converts + // as 1/HighRes). NB in this code Q always means the powder 2*pi/d, so 1/d is named + // one_over_d, never q. The histogram covers the data range [0, one_over_d_max] and is + // zero-padded by OVERSAMPLING for sub-bin peak localisation (finer cell lengths than the + // raw bin width gives). len_coeff (= 2*max_length/histogram_size) cancels the factor, so + // recovered lengths are independent of it. The padding factor is 2*pi: a historical value + // from when the extent was mistakenly written as 2*pi/d (the Q convention); it is kept + // because the exact amount sets which marginal frames index - rounding it to a nearby + // integer shifts the indexing rate ~0.5-1% (validated on the lyso datasets). + const float oversampling = 2.0f * static_cast(M_PI); + const float one_over_d_max = 1.0f / settings.GetFFT_HighResolution_A(); histogram_spacing = 1.0f / (2.0f * max_length_A); - histogram_size = std::ceil(maxQ / histogram_spacing); + histogram_size = std::ceil(oversampling * one_over_d_max / histogram_spacing); input_size = histogram_size * nDirections; output_size = (histogram_size / 2 + 1) * nDirections;