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
* `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>
95 lines
6.2 KiB
Markdown
95 lines
6.2 KiB
Markdown
# 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.
|
||
|