Files
Jungfraujoch/image_analysis/spot_finding/AdaptiveSpotFinderCPU.h
T
leonarski_fandClaude Opus 4.8 86f71ed95d Remove --soft-weight and --local-snr spot-finder options
Both were opt-in adaptive-spot refinements that did not help. Soft per-spot
weighting was index-rate neutral across the battery (re-ranking only bites when
spots exceed the max-spot cap, which weak serial data does not reach). The
local-SNR gate was neutral on index rate and degraded merged CC1/2 on flooded
XFEL data. Drops the flags, ApplyWeights/FilterByLocalSNR, the per-spot weight
field, and the by-weight FilterSpotsByCount branch (now strongest-first only).
--adaptive-spots itself is unchanged.

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

45 lines
2.1 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 (KR2 ~12 photons, weak OCP ~5).
//
// 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 ~12 photons on KR2 and ~5
// on OCP 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
std::vector<double> ring_sum;
std::vector<double> 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);
std::vector<DiffractionSpot> Run(const ImagePreprocessorBuffer &image,
const SpotFindingSettings &settings,
const std::vector<bool> &res_mask) override;
};