Files
Jungfraujoch/docs/DETECTOR_GEOMETRY.md
leonarski_fandClaude Opus 5 a8d289e7cf Powder calibration: write Poni1/Poni2 in pyFAI's frame, not ours
The same frame mismatch as the rot2/rot3 fix, in the other two fields. Our pixel
coordinates are pixel-centred - 948.0 is the CENTRE of pixel 948 - while pyFAI
measures from the edge of the sensor and puts the centre of pixel i at (i + 0.5) *
pixel size. Poni1/Poni2 went out as beam * pixel size, so anything reading the
file placed the pattern half a pixel (37.5 um at 75 um pixels) off ours. The
previous commit's "Poni1/Poni2 need no such change" was right about the axis
directions and wrong about the origin.

The proof was already in the tree. The pyFAI reference values in
DiffractionGeometryTest were computed for a .poni with Poni2: 0.150 and a 75 um
pixel, which the tests translate to beam_x = 2000 - but pyFAI's numbers are
reproduced only at 1999.5. At 2000 every one of them is out by 2.6e-3 nm^-1, which
the 1e-2 tolerance hid. The tests now use the beam centre those headers actually
mean, and agree with pyFAI to 1e-6 - float precision - across untilted q, azimuth,
rot1, rot1+rot2, rot3, rot1+rot2+rot3 and the solid-angle correction. Tolerances
drop to 1e-4 (1e-5 for solid angle): ~100x the observed float noise, and 26x
tighter than the half pixel they were blind to.

The viewer's calibration window printed "PONI x = ... mm" from the un-offset value
beside the path of the file it disagreed with; it now matches the file.

Also moves the viewer's beam-centre cross half a pixel down and right, where the
spot, prediction, top-pixel and saturation markers already are. Our coordinates
are pixel-centred and the Qt scene's are pixel-cornered, so the map between them
is +0.5, and DrawBeamCenter was the one overlay missing it.

The convention itself is now written down in docs/DETECTOR_GEOMETRY.md, with the
conversions to XDS ORGX/ORGY and to the edge-of-sensor programs, this being the
second bug to come out of it.

Only exported and displayed values change; the fitted geometry, spot positions and
integration were always self-consistent. A .poni written by an earlier build is
half a pixel off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 03:55:42 +02:00

3.4 KiB
Raw Permalink Blame History

Detector geometry

At the moment Jungfraujoch supports solely flat detectors. The default option is to place modules in their actual location vs. detector frame. It is not recommended to place detector modules stacked.

The simplest case is detector perpendicular to the beam. In this case it is enough to provide beam center, detector distance and wavelength.

For more complex case, one can provide tilt of the detector rotation in PyFAI convention. This convention uses Point Of Nominal Interaction (PONI) definition. Beam X and Y would correspond to the location on the detector, where beam from the sample is perpendicular to the detector surface and not to the actual direct beam location. Then tilt of the detector is defined with three rotation angles: rot1 (rotating detector right), rot2 (rotating detector downwards), rot3 (rotating detector clockwise). See PyFAI documentation for more details.

What a pixel coordinate means: (0, 0) is the centre of the first pixel

Pixel coordinates in Jungfraujoch and rugnux are 0-based and pixel-centred: an integer coordinate is the centre of that pixel, so pixel i covers [i 0.5, i + 0.5) and the sensor spans 0.5 … width 0.5. A beam centre of 948.0 × 546.0 sits in the middle of pixel [546][948], not on any of its corners; 948.5 is the boundary between pixel 948 and 949.

This holds throughout the code: spot and reflection centroids are intensity-weighted sums of the integer pixel indices, the resolution and azimuthal-bin maps evaluate pixel (col, row) at exactly (col, row), and a fractional coordinate is turned back into a pixel index by rounding, not by truncation. The same convention applies to every coordinate the system exposes — the beam centre (beam_x_pxl/beam_y_pxl in the API and broker configuration, --beam-x/--beam-y in rugnux, beam_center_x/beam_center_y in NXmx and in the CBOR stream), the spot and predicted-reflection positions written to HDF5, and the PONI reported by --mode calibration.

Other programs place the origin differently, and the difference is worth half a pixel — enough to matter when a geometry is copied between programs and then refined:

Convention Beam centre equivalent to our x = 948.0
Jungfraujoch, rugnux 948.0
XDS (ORGX/ORGY) 949.0 — also pixel-centred, but pixels are numbered from 1
Measured from the edge of the sensor, in length units — pyFAI (Poni1/Poni2), DIALS/dxtbx (948.0 + 0.5) × pixel size, because the centre of pixel i is at (i + 0.5) × pixel size from the edge

The .poni file written by rugnux --mode calibration is in pyFAI's frame and so already carries that half pixel; the pixel values the same run reports are ours.

Macromolecular crystallography convention for the vertical direction

One place of confusion is the convention to have point (0,0) of the detector in the top left corner of the detector, with Y values increasing downwards. This is also consistent with computer image formats.

However, other techniques (as well as internal operation of PSI X-ray detectors) might follow convention, for point (0,0) being in the bottom left corner and Y values increasing upwards. Such a convention is used, for example, by PyFAI.

In general, convention is controlled in Jungfraujoch with a setting in the JSON configuration file, which allows mirroring detector in Y.

Extra care has to be taken by the user to ensure that no errors are made.