The ring fit reported the SCATTER of its measurements (rms, and the beam-centre standard error that follows from it) but nothing about how well the fit pinned each parameter. Those two part company exactly where a calibration is worth doubting: as the rings run out, the tilt and the beam centre stop being separable - both displace a ring's radius as cos(phi) and only the way that amplitude scales with radius tells them apart - so the fit can sit tightly on the few points it has while being free to spend tens of pixels of beam centre on a tilt the data do not support. Take the covariance of the converged problem from Ceres and report it. Measured on a LaB6 distance series, the fitted tilt is 50 sigma at 110 mm and 0.1 sigma at 500 mm, where only two rings reach the detector; at 500 mm the fit quotes its own beam centre to +-180 px and its tilt to +-2.9 deg on a 0.35 deg value, and the correlation between them is 1.000. Nothing acts on this yet - it is printed so the next change can gate on it. Ceres returns the bare (J'J)^-1 of an unweighted problem, so it is scaled by chi2 per degree of freedom; that leaves the sigmas in pixels, mm and radians whatever unit the residual is stated in. Cost is one 5x5 SVD per run, below the noise of the surrounding I/O. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NfuDvf5ipV3Hi8TiCUKD27
59 lines
3.2 KiB
C++
59 lines
3.2 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "../../common/AzimuthalIntegrationMapping.h"
|
|
#include "../../common/DiffractionExperiment.h"
|
|
#include "../../common/DiffractionGeometry.h"
|
|
#include "../../common/SpotToSave.h"
|
|
#include "RingOptimizer.h" // RingFitUncertainty
|
|
|
|
// How the powder rings the detector geometry is fitted to are measured (rugnux --calibration).
|
|
enum class CalibrationMethod {
|
|
Rings, // the run-summed (q x azimuth) azimuthal profile: the ring measured at every azimuth
|
|
Spots // the pooled per-image spot lists: the ring sampled wherever the spot finder bit
|
|
};
|
|
|
|
struct CalibrationResult {
|
|
DiffractionGeometry geometry; // the fitted geometry
|
|
size_t ring_points = 0; // ring measurements the fit used
|
|
// Scatter of those measurements about the fitted rings, and the standard error it implies on the
|
|
// beam centre. Both in pixels - a calibration that has gone wrong (textured ice, one visible ring)
|
|
// says so here, and that is the only warning a user gets.
|
|
double rms_radial_pxl = 0.0;
|
|
double beam_sigma_pxl = 0.0;
|
|
// What the fit itself says about how well each parameter is determined, and how badly the tilt is
|
|
// correlated with the beam centre. rms/beam_sigma above describe the SCATTER of the measurements;
|
|
// this describes the FIT, and the two part company exactly where it matters - a two-ring tilt can
|
|
// leave a small rms while being free to move tens of pixels of beam centre with it.
|
|
RingFitUncertainty uncertainty;
|
|
};
|
|
|
|
// Both fits take the detector tilt as a free parameter unless refine_tilt is false, which holds
|
|
// rot1/rot2 at the value `geom` came in with and fits only the beam centre and the distance. The
|
|
// tilt is real and a PONI carries it, but a program that has nowhere to put it - XDS - is better
|
|
// given a geometry measured with the tilt pinned than one measured tilted and then flattened.
|
|
|
|
// Fit the geometry to the rings found in a run-summed azimuthal profile (CalibrationMethod::Rings).
|
|
CalibrationResult CalibrateFromProfile(const std::vector<float> &profile,
|
|
const AzimuthalIntegrationMapping &mapping,
|
|
const DiffractionGeometry &geom,
|
|
const std::vector<float> &calibrant_ring_q,
|
|
bool refine_tilt = true);
|
|
|
|
// Fit the geometry to a pooled spot list (CalibrationMethod::Spots): the beam centre from scratch off
|
|
// the Hough circle centre, then the same ring fit.
|
|
CalibrationResult CalibrateFromSpots(const std::vector<SpotToSave> &spots,
|
|
const DiffractionGeometry &geom,
|
|
const std::vector<float> &calibrant_ring_q,
|
|
bool refine_tilt = true);
|
|
|
|
// Write the geometry as a pyFAI PONI file, the interchange format every azimuthal-integration tool
|
|
// reads. Throws if the file cannot be written.
|
|
void WritePoniFile(const std::string &path, const DiffractionExperiment &experiment,
|
|
const DiffractionGeometry &geom);
|