Files
Jungfraujoch/image_analysis/geom_refinement/BeamCenterFromBackground.h
T
leonarski_f 9aae0c2ba7
Build Packages / Create release (push) Successful in 21s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m40s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m49s
Build Packages / build:viewer-tgz:cpu (push) Successful in 11m37s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m40s
Build Packages / build:windows:nocuda (push) Successful in 17m44s
Build Packages / build:windows:cuda (push) Successful in 20m13s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m41s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m59s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m5s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m35s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m53s
Build Packages / build:rugnux:windows (push) Successful in 11m29s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 18m51s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 18m43s
Build Packages / Generate python client (push) Successful in 51s
Build Packages / build:rpm (rocky8) (push) Successful in 18m51s
Build Packages / Build documentation (push) Successful in 1m21s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 18m38s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m24s
Build Packages / build:rpm (rocky9) (push) Successful in 19m19s
Build Packages / Unit tests (push) Successful in 1h37m15s
v1.0.0-rc.169 (#79)
* Building Jungfraujoch no longer needs zlib or Eigen installed on the machine, and the dependencies the build fetches are pinned and updated to current releases.
* rugnux: improvements in indexing, lattice selection and geometry post-refinement, which index crystals that previously returned no lattice and keep the better of the two geometries a run measures.
* rugnux: improvements in beam-centre measurement, beam-stop detection and space-group determination.
* rugnux: the unit cell reported with a determined space group now obeys that group - a cell whose symmetry was confirmed from the intensities is re-refined under it, and a cell the group cannot describe is reported with a warning rather than as it stands.
* rugnux drops the stretches of a rotation sweep whose removal measurably improves the merged intensities and reports what became of every frame, and decides the resolution cut on the crystal's own diffraction rather than on its ice rings.
* The rugnux results report is machine-readable - every line that is not `KEY= value` data starts with `#` - and states the build it was written by, its authorship and its terms of use (`REPORT_VERSION= 8`).
* `jfjoch_viewer`: improvements in the file manager (CBF frames beside HDF5 datasets, a remembered root), the dataset plots, the inspector and the image statistics, plus a settable font size, a view of the rugnux results report, usable performance over a remote display (`ssh -X`) and a reset of all settings to defaults; the reciprocal-space window is removed.
* Broker fixes around DECTRIS collections and dark-mask calibration: re-initialising after a run that never started no longer freezes the broker, a cancelled calibration is abandoned instead of reported as done, and a collection whose start message never arrives ends by itself.

Reviewed-on: #79
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-09-15 17:09:31 +02:00

79 lines
4.7 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <optional>
#include <utility>
#include <vector>
#include "BeamCenterFFT.h"
#include "../../common/DiffractionExperiment.h"
#include "../../common/PixelMask.h"
struct BeamCenterEstimate {
float beam_x_pxl = 0.0f;
float beam_y_pxl = 0.0f;
float sigma_pxl = 0.0f; // 1 sigma on the fitted shift, the larger of the two axes
};
// Beam centre from the isotropy of the scattered background, before anything is indexed.
//
// The solvent and air scatter is isotropic in 2-theta about the beam, so a centre that is off
// shifts each azimuthal sector's radial profile by a different amount. Sector k's profile is
// m_k * g(2theta + d_k), with the shift d_k = Jx_k*dx + Jy_k*dy and m_k an amplitude that
// absorbs anything multiplicative and azimuthal - a holder arm, a cryostream shadow, a
// flat-field gradient. Fitting the amplitude alongside the shift is what makes this usable:
// a 50% shadow over one sextant otherwise reads as several tens of pixels of centre error.
//
// The leverage comes from the CURVATURE of the radial profile - the water ring - because for a
// pure exponential decay g' is proportional to g and shift and amplitude are indistinguishable.
// The sigma measures that leverage, so it grows as the curvature weakens, and the caller's gate on
// it is what keeps an ill-determined centre out. It is a precision and not an accuracy: on a
// background with NO curvature at all there is nothing to separate the two parameters, the fit
// follows the noise in g' instead, and it does so confidently.
//
// `mean` is a per-pixel projection over a few tens of frames, NAN where no frame contributed.
// nthreads = 0 asks for all hardware threads. The pixels are split into a fixed number of row
// blocks whatever that count is, so the answer does not depend on it.
//
// `start` is where the walk begins; the centre in the file when it is not given. The walk advances
// by a bounded distance per iteration, so where it starts decides how much of its budget is spent
// travelling and - on a surface with more than one basin - which fixed point it can reach at all.
std::optional<BeamCenterEstimate>
FindBeamCenterFromBackground(const DiffractionExperiment &experiment, const PixelMask &mask,
const std::vector<float> &mean, size_t nthreads = 0,
std::optional<std::pair<float, float>> start = {});
// The precision of a centre that is the FFT capture alone, with no walk behind it. The capture is
// a half-pixel grid position read off a surface, measured over 75 rotation datasets at a median
// 3.2 px and a 90th percentile of 11 px from the truth, so this is what it knows the centre to -
// a capture precision and not a fit precision. It is deliberately far above the ceiling the
// callers adopt a centre on: a capture is evidence about where the beam is, not a measurement of
// where it is.
constexpr float BEAM_CENTER_CAPTURE_SIGMA_PXL = 5.0f;
// The beam centre from the background, captured globally and then refined locally.
//
// The walk in FindBeamCenterFromBackground is a good local refiner and a poor searcher: it moves
// one or two pixels per iteration, it is seeded at the centre in the file, and on a background
// whose isotropy is broken it has a second basin to fall into. The FFT score is the opposite -
// it evaluates EVERY candidate centre on the detector in one transform set, at a cost that does
// not depend on how wrong the file is, but it returns a half-pixel grid position and no sigma.
// Composing them takes the reach from one and the precision from the other: the capture chooses
// the basin, the walk finishes inside it and reports what it knows the answer to.
//
// The shadow of the beam stop is blanked out of the image the capture scores. A one-sided opaque
// region imposes a centrosymmetry of its own that can beat the background's - measured, an umbra
// over 9.6 % of the detector put the capture 48 px out, and masking it put it back to 1.1 px,
// while a random mask of the same area changed nothing.
//
// Where the walk declines at the capture the walk is asked again from the centre in the file, and
// only where neither start gives it something to fit does the capture stand alone, at
// BEAM_CENTER_CAPTURE_SIGMA_PXL - which is what lets a caller that only needs a hypothesis to test
// still get one. Returns nothing only where the capture has no candidate either.
std::optional<BeamCenterEstimate>
FindBeamCenter(const DiffractionExperiment &experiment, const PixelMask &mask,
const std::vector<float> &mean, size_t nthreads = 0,
BeamCenterFFTResult *capture = nullptr);