AdaptiveSpotFinderGPU does the per-resolution-ring reduction once on the GPU and drives both products from it: the azimuthal-integration profile (corrected space) and the self-calibrating adaptive spot-detection threshold (raw counts). This replaces the separate GPU azint pass and the host-side adaptive spot finder that runs on the GPU path today. On a ~4.5 MP detector it does both jobs in ~1 ms/frame versus ~40 ms for the CPU adaptive finder (~42x), with an identical spot list and azimuthal profile. The per-ring threshold math (Poisson tail + read-floored Gaussian, operating point from the false-pixels-per-frame knob) is factored into AdaptiveThreshold.h so the CPU and GPU finders share one source of truth and cannot drift. Wired opt-in via a MXAnalysisWithoutFPGA constructor flag, default on for the rugnux offline path and the interactive viewer, off for the online receiver (so the broker path is unchanged). When on, Analyze() skips the separate azint pass and lifts the profile from the fused engine. The viewer gains an "Adaptive threshold" checkbox that greys out the signal/noise and photon-count sliders (the adaptive finder uses neither). Dedicated tests exercise both products (spot-finding parity vs the CPU finder, azimuthal profile vs a standalone GPU azint) plus a speed benchmark. Validated end-to-end on lysozyme serial stills: fused == CPU-adaptive index rate and merge stats. Docs: new section 3.2 in docs/CPU_DATA_ANALYSIS.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
3.7 KiB
C++
83 lines
3.7 KiB
C++
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <mutex>
|
|
|
|
#include "../common/JFJochMessages.h"
|
|
#include "../common/DiffractionExperiment.h"
|
|
#include "../common/AzimuthalIntegrationMapping.h"
|
|
#include "../common/PixelMask.h"
|
|
#include "../common/AzimuthalIntegrationProfile.h"
|
|
#include "bragg_prediction/BraggPrediction.h"
|
|
#include "bragg_integration/BraggIntegrationEngine.h"
|
|
#include "spot_finding/ImageSpotFinder.h"
|
|
#include "spot_finding/AdaptiveSpotFinderCPU.h"
|
|
#include "indexing/IndexerThreadPool.h"
|
|
#include "azint/AzIntEngine.h"
|
|
#include "roi/ROIIntegration.h"
|
|
#include "IndexAndRefine.h"
|
|
#include "image_preprocessing/ImagePreprocessor.h"
|
|
#include "image_preprocessing/ImagePreprocessorBuffer.h"
|
|
|
|
class CudaStream;
|
|
class AdaptiveSpotFinderGPU;
|
|
|
|
// MXAnalysisWithoutFPGA is not thread safe - it has to owned by a single thread
|
|
class MXAnalysisWithoutFPGA {
|
|
const DiffractionExperiment &experiment;
|
|
const AzimuthalIntegrationMapping &integration;
|
|
|
|
std::vector<uint8_t> decompression_buffer;
|
|
|
|
std::unique_ptr<ImagePreprocessor> preprocessor;
|
|
|
|
size_t npixels;
|
|
size_t xpixels;
|
|
|
|
std::unique_ptr<AzIntEngine> azint;
|
|
std::unique_ptr<ROIIntegration> roi;
|
|
std::unique_ptr<ImageSpotFinder> spotFinder;
|
|
// Self-calibrating finder, used when spot settings request adaptive detection. Kept alongside the
|
|
// default finder because the choice arrives with the per-image settings, not at construction. It is
|
|
// an AdaptiveSpotFinderCPU by default; on the GPU path, when the fused engine is enabled (rugnux
|
|
// offline only), it is instead an AdaptiveSpotFinderGPU that also computes the azimuthal profile,
|
|
// aliased through fused_adaptive so Analyze() can take that profile and skip the separate azint pass.
|
|
std::unique_ptr<ImageSpotFinder> adaptiveSpotFinder;
|
|
AdaptiveSpotFinderGPU *fused_adaptive = nullptr;
|
|
const bool enable_fused_adaptive_gpu;
|
|
IndexAndRefine &indexer;
|
|
std::unique_ptr<BraggPrediction> prediction;
|
|
std::unique_ptr<BraggIntegrationEngine> bragg_engine;
|
|
std::unique_ptr<ImagePreprocessorBuffer> preprocessor_buffer;
|
|
const PixelMask &mask;
|
|
|
|
std::vector<bool> mask_resolution;
|
|
float mask_high_res;
|
|
float mask_low_res;
|
|
void UpdateMaskResolution(const SpotFindingSettings& settings);
|
|
#ifdef JFJOCH_USE_CUDA
|
|
std::shared_ptr<CudaStream> stream; // kept so RebuildROI() can recreate the GPU ROI engine
|
|
#endif
|
|
public:
|
|
// enable_fused_adaptive_gpu turns on the fused GPU azint+adaptive spot finder (only takes effect on
|
|
// the GPU path with adaptive detection). The rugnux offline path and the interactive viewer enable
|
|
// it by default; the online receiver leaves it off and keeps the CPU adaptive finder + separate
|
|
// azint. It only changes performance - the fused engine reproduces the CPU finder's spots.
|
|
MXAnalysisWithoutFPGA(const DiffractionExperiment &experiment, const AzimuthalIntegrationMapping &integration,
|
|
const PixelMask &mask, IndexAndRefine &indexer, bool enable_fused_adaptive_gpu = false);
|
|
void Analyze(DataMessage &output, AzimuthalIntegrationProfile &profile, const SpotFindingSettings &spot_finding_settings);
|
|
|
|
// Surgical ROI-only paths used when a full re-analysis is not wanted: rebuild the
|
|
// ROI engine after the ROI set changes, recompute ROIs after preprocessing a new
|
|
// image (reanalyze off), or just rerun ROIs on the current preprocessed image (an
|
|
// interactive ROI move). A full Analyze() already computes ROIs, so needs nothing.
|
|
void RebuildROI();
|
|
void AnalyzeROIOnly(DataMessage &output);
|
|
void RunROIOnly(DataMessage &output);
|
|
};
|
|
|
|
|
|
|