Files
Jungfraujoch/docs/RUGNUX_CALIBRATION.md
T
leonarski_f 680c36c20d
Build Packages / Unit tests (push) Successful in 1h22m15s
Build Packages / build:windows:nocuda (push) Successful in 18m0s
Build Packages / build:windows:cuda (push) Successful in 20m30s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m39s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m55s
Build Packages / build:rugnux:windows (push) Successful in 11m25s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 20m6s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m27s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 15m34s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m25s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8) (push) Successful in 17m43s
Build Packages / build:rpm (rocky9) (push) Successful in 13m34s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 21m28s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m19s
Build Packages / DIALS test (push) Successful in 12m36s
Build Packages / XDS test (durin plugin) (push) Successful in 6m56s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m48s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m7s
Build Packages / Generate python client (push) Successful in 11s
Build Packages / Build documentation (push) Successful in 36s
Build Packages / Create release (push) Skipped
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m11s
v1.0.0-rc.166 (#76)
* `rugnux --mode calibration` writes `<prefix>.json` beside the `.poni`, whose `dataset_settings` member is a `jfjoch_broker` `dataset_settings` body as it stands.
* `rugnux` and `jfjoch_viewer` read PILATUS miniCBF sweeps natively, without conversion.
* Masters written by other facilities open, including Eiger 1.x and third-party NXmx variants.
* `rugnux` measures the beam centre on every run, and indexes with it when the file's value indexes nothing.
* A detector swung out on a 2theta arm is placed where the file says it stands, and the calibration can hold the tilt fixed.
* `rugnux` writes the unmerged MTZ by default, and a P1 merge beside it, so a wrong space group can be re-merged without reprocessing.
* Significant improvements to symmetry handling in `rugnux`: the lattice, the point group, the setting and the systematic absences.
* The `rugnux` report gives the resolution the CC1/2 fit reached, beside the range the reflections were written to.
* The `rugnux` report gives the twinning statistics measured before the space group was decided, beside the ones measured after.
* The `rugnux` report gives the strong-direction diffraction limit, and warns when CC1/2 is not monotone with resolution.
* `rugnux` ranks screw axes on the evidence their absences carry, rather than on how many control reflections a candidate happens to have.
* Twinning is no longer reported when the L-test contradicts it.
* The `rugnux` report gives the detector tilt, the measured tilt and the direct beam beside the beam centre, and a post-refined beam centre is judged against the run's own measurement rather than the file's.
* `--no-refine-tilt` holds the detector tilt at the value in the file, instead of zeroing it, when the calibration starts from the spots.
* The `jfjoch_viewer` grid scan view draws the cells in the proportion of the scan steps, so the map has the shape of the scanned area.

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

95 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Detector calibration from powder rings (`rugnux --mode calibration`)
The `calibration` mode determines the detector geometry — PONI *x*/*y*, the two tilts
rot1/rot2 and the distance — from the powder rings of a calibrant, and writes it as a
pyFAI **`<prefix>.poni`** file and a machine-readable **`<prefix>.json`**, alongside a printed
report of how far each parameter moved from the header. Bragg data pin the beam centre worst (it is gauge-coupled to the crystal orientation);
a powder ring has no orientation to be coupled to, so this is the measurement that fixes it.
```
rugnux --mode calibration --calibrant lab6 -o det LaB6_master.h5
```
## What it writes
`<prefix>.poni` is for pyFAI and the tools that read its format. `<prefix>.json` is for everything
else, and has two members:
* **`dataset_settings`** — the geometry under the property names
[`dataset_settings`](https://jungfraujoch.readthedocs.io) gives them in `jfjoch_broker`'s OpenAPI
schema, and nothing else. It is a valid `dataset_settings` body as it stands, so it can be POSTed
or merged into one without translating a field:
```
curl -X POST -H 'Content-Type: application/json' \
-d "$(jq -c .dataset_settings det.json)" http://broker:5232/start
```
`beam_x_pxl`/`beam_y_pxl` is the **PONI**, as everywhere in this system — on a tilted detector it
is not where the direct beam lands. The three `poni_rot*_rad` are written whenever any of them is
non-zero, and left out when all are zero: a body that omits them states a *flat* detector, so they
travel together or not at all.
* **`calibration`** — what the run knows about that geometry: the residual, the fit's own sigmas and
the correlation between the tilt and the beam centre, whether the tilt cleared its significance
test or was declined and pinned, where the direct beam lands, and where the spots independently
put the beam. A calibration that has gone wrong looks exactly like one that has not until those
are read.
`--calibrant` takes `lab6`, `agbh` (silver behenate), `ceo2`, `si` or `ice`, case-insensitively.
**`ice`** calibrates a real experiment against its own ice rings — no calibrant exposure needed —
and is the reason a calibrant is a list of ring positions rather than a unit cell: hexagonal ice
is P6<sub>3</sub>/mmc, so rings enumerated from its cell would include systematically absent ones.
`--calibration` picks how the rings are measured, and **both use the whole dataset** — `-s`/`-e`/`-t`
select which images:
- **`rings`** (default) sums the (*q* × azimuth) azimuthal profile over every processed image into
one map and fits the ring arcs in it. A powder ring is an arc, not a set of spots, and the summed
profile measures it at every azimuth with all the run's counts behind it. It needs the profile to
be binned in azimuth, so this mode defaults `--azim-phi-bins` to 32.
- **`spots`** pools the found spots of every processed image and fits those. It determines the
centre from scratch (a Hough circle vote, which quantises it to a whole pixel) and then refines.
Both routes read the ring position out of a binned profile or a spot centroid, so the radial
sampling matters: at a long detector distance the default 0.01 Å⁻¹ *q* bin is several pixels wide
and quantises the `rings` route accordingly — pass a finer `--azim-q-spacing` there (the total
*q* × azimuth bin count must stay under 65534).
The report prints the fitted geometry, the scatter of the ring points about the fitted rings and the
standard error that implies on the centre. That error is *formal*: it measures the scatter of the
points, not whether the rings themselves are trustworthy, so it stays small when a fit goes wrong
for a structural reason — one visible ring, or ice that is textured rather than smooth.
A calibration is run because the header is in doubt, so a fit that quietly hands part of that header
back has answered nothing — and it looks exactly like a fit that worked, down to the residual and the
sigmas around it. **`calibration.converged`** in the JSON says which of the two a file is. It is false
when the tilt was declined and pinned at a *non-zero* header value — the rings said they could not tell
a tilt from a shift of the beam centre, and the angle written in its place has no more support than the
one refused — or when the fit's covariance never conditioned, so it cannot say what it determined. In
either case rugnux prints the reason, writes the `.json` with `converged` false and
`not_converged_reason` beside it, exits non-zero, and writes **no `.poni`**: a PONI file states where
the detector is and has no field in which to say that it does not know. A declined tilt over a header
that states *no* tilt is not this — reporting no tilt is then exactly what the fit measured.
A `.poni` is refused for a second reason, whatever the fit found: a detector whose **image
orientation** is not the identity — the stored image mirrored in Y, or turned by a multiple of 90°
about the beam. That comes either from `--detector-mirror-y` / `--detector-quarter-turns` or from the
file itself (a PILATUS miniCBF axis table, an NXmx module's pixel directions). A PONI states the
detector in five numbers — two offsets, a distance and three rotations — and has no field for how the
image is stored, so one written here would describe a different geometry from the one that was
measured. The run says so and exits non-zero.
`--no-refine-tilt` holds rot1/rot2 where the header put them and fits only the centre and the
distance. The tilt is real and worth measuring, but a program that has nowhere to put one — XDS
takes a detector normal to the beam — is better given a geometry that was measured with the tilt
pinned than one that was measured tilted and then flattened, because in the tilted fit the centre
and the distance have already absorbed the tilt.
Both the PONI (the point of normal incidence, which is what a `.poni` file stores) and the direct
beam (where the beam lands, which is what most other programs call the beam centre) are printed.
They differ by *distance* × tan(rot) once the detector is tilted, which on a 0.3° tilt at 300 mm is
several pixels — enough to look like a disagreement with another program when there is none.