Files
Jungfraujoch/image_analysis/spot_finding/AdaptiveSpotFinderCPU.h
T
leonarski_fandClaude Opus 5 abb94ca450 spot_finding: accumulate the adaptive ring statistics in integers
The per-ring sums were floats reduced by atomics, so the ring sigma - and with it the
detection threshold - depended on the order the blocks happened to arrive in. Detection
compares an INTEGER pixel value against that threshold, so a threshold that drifts
across an integer flips every pixel of that value in the ring at once, which is how a
last-bit difference turned into a different spot list.

A preprocessed pixel is an exact int32 and the masked and saturated sentinels are
skipped, so v and v*v are exact in 64 bits, and integer addition is associative: the
sums no longer care about arrival order. Both engines now accumulate the same way, so
they agree exactly rather than approximately, and the GPU spot list is bit-identical
across runs. The corrected sums that feed the reported azimuthal profile stay float -
a pixel value times a float correction has no exact integer form - but they do not
enter the detection decision.

Cost: the ring reduction needs 28 bytes per bin instead of 20 in the plain pass, which
drops it from eight co-resident blocks per SM to seven and costs about 11% of that
kernel (0.582 -> 0.650 ms/frame on a 4.5 Mpx frame). End to end it does not show:
alternating runs on three rotation crystals came out the same or slightly faster, and
the battery is unchanged in every number. The CPU engine got 30% faster (32.2 -> 22.6
ms/frame), integers being cheaper than doubles.

Tests: exact CPU/GPU agreement on the spot list, and 50 repeats of bit-identical output
where there were four.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 15:19:11 +02:00

47 lines
2.3 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <vector>
#include "ImageSpotFinder.h"
#include "SpotFindingSettings.h"
#include "../../common/AzimuthalIntegrationMapping.h"
// Self-calibrating strong-pixel detector for the offline (rugnux/viewer) path.
//
// The classic finder (ImageSpotFinderCPU) marks a pixel strong when it clears a *fixed* photon
// count AND a local-box SNR. The fixed photon floor is what forces per-dataset tuning: it must sit
// above the background (wants high) yet not bury weak spots (wants low), and the background level
// differs per dataset, so the sweet spot is narrow (~12 photons on one serial-stills set, ~5 on a
// weaker one).
//
// Here the floor is replaced by a per-resolution-ring threshold derived from a single portable
// number: E = the expected count of noise pixels tolerated per frame (default ~100). For a ring
// whose (peak-excluded) background mean is mu, the threshold is the smallest count whose Poisson
// upper tail is <= p = E / N_pixels, max'd with a Gaussian arm mu + z*sigma to absorb read/flat-field
// excess. Because it is set from the image's own noise, the SAME E lands at ~12 photons on the first
// set and ~5 on the weaker one with no user input. Detection then is simply value > ring_threshold,
// fed to the same connected-component builder as the classic finder.
class AdaptiveSpotFinderCPU : public ImageSpotFinder {
const AzimuthalIntegrationMapping &mapping;
// per-ring scratch, sized to the mapping's bin count
// Exact integers: a preprocessed pixel is an int32 and the sentinels are skipped, so v and v*v
// are exact in 64 bits. That is what lets the GPU engine reproduce these bit for bit - integer
// addition is associative, so its block atomics can arrive in any order.
std::vector<int64_t> ring_sum;
std::vector<uint64_t> ring_sum2;
std::vector<int64_t> ring_cnt;
std::vector<float> ring_mean;
std::vector<float> ring_sigma;
std::vector<float> ring_thr;
void AccumulateRings(const ImagePreprocessorBuffer &image, float clip_k);
public:
explicit AdaptiveSpotFinderCPU(const AzimuthalIntegrationMapping &mapping);
void Detect(const ImagePreprocessorBuffer &image, const SpotFindingSettings &settings) override;
};