Files
Jungfraujoch/image_analysis/geom_refinement/PowderCalibration.h
T
leonarski_fandClaude Opus 5 fba5435c38 calibration: report what the ring fit knows about its own answer
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
2026-08-31 16:11:18 +02:00

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);