An image integrated in pyFAI through our .poni came out with every chi 180 degrees from where it belongs. pyFAI's in-plane axes are the negatives of ours, so Rot3 needs a half turn on top of the sign flip. Being a rotation about the beam it leaves 2theta alone - which is why radial integration was right all along and only the azimuth was wrong, and why a powder-ring check could never have caught it. The half turn is needed for the orientation-3 form written before rc.162 as well, so it is not an artefact of declaring the orientation - the file has been 180 degrees out for as long as it has been written. Verified against pyFAI 2026.5.0 on a tilted detector with an off-centre beam, against the lab positions of the NXmx chain: 2theta to 3.6e-15 deg and chi to 2.8e-14 deg. Then end to end, by integrating an image in jfjoch's own layout through a .poni the code actually writes: chi lands within 0.15 deg of physical truth on a 0.5 deg cake bin. Withdraws two changelog claims. The .poni does negate Rot3, and declaring orientation did not fix the azimuth: pyFAI's orientation is numerically inert here, so the file was relabelled and not corrected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VfYvJT5Nb71suJCowRBn5z
54 lines
3.8 KiB
Markdown
54 lines
3.8 KiB
Markdown
# 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](https://pyfai.readthedocs.io/en/stable/) 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 (`Poni2`, fast axis), DIALS/dxtbx | (948.0 + 0.5) × pixel size, because the centre of pixel *i* is at (*i* + 0.5) × pixel size from the edge |
|
||
| pyFAI `Poni1` (slow axis) | (height − 1 − *y* + 0.5) × pixel size — pyFAI measures the slow axis from the opposite edge, and the `.poni` declares `orientation: 2` to say so |
|
||
|
||
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. `Rot3` in that file is our rot3
|
||
negated and turned by 180°: the half turn sets the azimuthal reference, because pyFAI's in-plane axes
|
||
are the negatives of ours. It leaves 2θ untouched, so it moves only the azimuth.
|
||
|
||
## 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. |