Files
Jungfraujoch/image_analysis/geom_refinement/BeamCenterFFT.h
T
leonarski_fandFilip Leonarski 19b135186d
Build Packages / Create release (push) Successful in 7s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 4m56s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 9m10s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 13m51s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 16m56s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 20m59s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 21m25s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 21m17s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 14m14s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m59s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 25m48s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 15m52s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m33s
Build Packages / Generate python client (push) Successful in 29s
Build Packages / Build documentation (push) Successful in 1m19s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m49s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 25m31s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 25m24s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 26m5s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 22m19s
Build Packages / Unit tests (push) Failing after 2h28m24s
Build Packages / Validation Battery (push) Successful in 40m33s
v1.0.0-rc.175 (#85)
* Rugnux: Processes a collection live while jfjoch_broker writes it (`rugnux <master.h5> <broker URL>`).
* Rugnux: Processing starts on the first images and confirms its choices once all are in, so analysis can begin during collection.
* Rugnux: Faster processing, especially on servers with several GPUs and CPU sockets and on computers without a GPU (`--no-gpu`, macOS).
* Rugnux: Uses much less GPU memory, so large data sets stay on the GPU of smaller cards, and a data set too large for the card is processed on the CPU instead.
* Rugnux: On macOS, Bragg integration runs on the Apple GPU.
* Rugnux: Better handling of twinning and pseudo-symmetry, with the twin hypothesis written as `<prefix>_twin_<SG>.mtz`, and a wrong `--model` no longer confirms the data or decides the hand.
* Rugnux: `--model` accepts predicted (AlphaFold) models and falls back to molecular replacement when the model is not placed as a rigid body (experimental `--mr` forces it), settling space group, hand and twinning and writing a different answer as `<prefix>_mr.mtz`.
* Rugnux: Molecular replacement is faster and settles the space group in more cases with several copies in the asymmetric unit.
* Rugnux: Deposition-ready mmCIF, a richer report, and numbered steps on the terminal with the full log in `<prefix>_log.txt`; `_P1.mtz`, `--estimate-beam-center` and `--beam-center-search` are removed.
* Rugnux: The beam-stop shadow is found correctly on tilted detectors.
* Rugnux: Stills files that record a stationary goniometer axis get the default stills geometry refinement.
* jfjoch_viewer: "Analyze dataset" uses the same defaults as a bare `rugnux <file>`, and shows the results of processed CBF, marCCD and SMV sweeps.
* jfjoch_viewer: The Files panel refreshes on F5 and every few seconds, and the plot type and other selections stay put during live collection.
* jfjoch_writer: Memory stays bounded and old data files are closed efficiently on long runs, any failure ends the run in an error state the broker is told about, and the master file appears only after its data files; the ZeroMQ pusher logs when a stalled writer makes it drop images.
* Fixes: The TCP image pusher no longer blocks the broker's receiver, GPU out-of-memory failures stop with a clear message, and the XDS plugin masks module and chip edges.

---------

Co-authored-by: Filip Leonarski <jungfrau@filips-mac-mini.home>
Reviewed-on: #85
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-10-16 15:34:43 +02:00

71 lines
3.6 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <cstdint>
#include <vector>
#include "../../common/DiffractionExperiment.h"
#include "../../common/PixelMask.h"
// Whole-detector beam-centre capture from the symmetry of the scattered image, via FFT.
//
// The centrosymmetry score of the image about a candidate centre c, sum_x I(x) I(2c - x), is the
// self-convolution (I * I)(2c), so one FFT pair scores EVERY candidate centre on the detector at
// half-pixel spacing in a single transform set - the cost is O(N log N) and independent of how far
// the true centre is from the value in the file. The score is a 2D point inversion - a
// measurement of the isotropic background (air and solvent scatter), point-symmetric about the beam
// whatever the crystal does. It shares its physics with FindBeamCenterFromBackground, so their
// agreement is not two votes: the strongest candidate seeds that walk.
//
// The score is a CAPTURE device, not a decision device: measured on 17 datasets its shortlist
// holds the true centre in the top candidates every time (three gross header errors of 71-351 px
// captured at rank 1 within 1 px), but the surface can be locally flat, so the result is a
// shortlist plus a margin - never a single centre. The margin (best minus runner-up, as a
// fraction of best, after suppression) is the self-diagnostic: measured, ambiguous surfaces sit
// below ~3 % and clean ones above ~5 %. Callers must verify a candidate (index at it) before
// believing it, and must not average candidates.
//
// The input is the mean-projection the pre-scan already accumulates for the beam-stop search
// (ShadowFinder::GetMeanProjection), NAN where nothing was counted - so this costs no new I/O.
//
// The transforms run on the GPU where there is one with room for them and on fftw3f otherwise
// (BeamCenterFFTGPU / BeamCenterFFTCPU); everything that decides anything is shared between the
// two. Whole-detector reach is what this buys, and on a 16 Mpixel detector the transforms are the
// whole of its cost, so which engine runs them is the difference between seconds and milliseconds.
struct BeamCenterFFTCandidate {
float beam_x_pxl = 0.0f;
float beam_y_pxl = 0.0f;
float score = 0.0f; // masked Pearson r of the image with its mirror about this centre
};
struct BeamCenterFFTResult {
// Non-maximum-suppressed shortlist of the 2D point-inversion score, strongest first.
std::vector<BeamCenterFFTCandidate> point;
// (best - runner_up) / best; 0 when fewer than two peaks survive.
float margin_point = 0.0f;
};
struct BeamCenterFFTSettings {
float nms_radius_pxl = 25.0f; // peak separation; tracks the score's plateau width
int candidates_point = 8;
// Fraction of the maximum mirror-pair count below which a centre is not scored - centres so
// far out that almost no pixel has a valid mirror partner correlate noise with noise.
float min_pair_fraction = 0.25f;
};
// Core entry: `mean` is a width x height row-major per-pixel projection, NAN (or any non-finite
// value) where no valid pixel exists. Kept free of DiffractionExperiment so it can be tested
// against stored arrays.
[[nodiscard]] BeamCenterFFTResult
BeamCenterFFTScore(int64_t width, int64_t height, const std::vector<float> &mean,
const BeamCenterFFTSettings &settings = {});
// Convenience wrapper over the pre-scan projection.
[[nodiscard]] BeamCenterFFTResult
FindBeamCenterFFT(const DiffractionExperiment &experiment, const std::vector<float> &mean,
const BeamCenterFFTSettings &settings = {});