Files
Jungfraujoch/docs/RUGNUX_CALIBRATION.md
T
leonarski_fandClaude Opus 5 b8fa8e67d5 docs: the pages catch up with the last day on rc166
An audit of docs/ against the code at HEAD, concentrating on what landed after
the previous documentation audit: the balanced half-set split and CCanom, the
declared-range completeness denominator, the one rule for a quantity nobody
measured, and the PONI the calibration now refuses to write.

Five statements the code had made false:

- The rotation merge's CCref column shows a dash, not "nan".
- Two pages still promised unweighted 2Fo-Fc / Fo-Fc maps; they have been
  sigma_A-weighted since the rigid-body work, and one of the two sat six lines
  from a paragraph that said so.
- Inserting the CCanom prose into the SigAno paragraph left the sentence about
  the PDBx items and the SigAno column with CCanom as its subject, so it read as
  a claim about a quantity that is in neither. CCanom is also rotation-only and
  is not in the mmCIF, which nothing said.
- Two cross-references did not resolve - a heading that was renumbered, and a
  slug spelled without the hyphen MyST puts in "TCP/IP".

Added where the behaviour is new and a user meets it: the report's contract for
a quantity a run did not measure - no key, and a dash in the table, which is not
the same claim as a measured zero; the half-set rule behind CC1/2, which is why
a CUDA and a non-CUDA build now agree on it and on CCanom; and the second reason
a calibration writes no .poni, a detector whose stored image is mirrored or
quarter-turned, which the PONI format cannot state.

The rc.166 changelog stays a release note. One entry is widened from the shell
table to the rule it is a case of, and two are added for output that was wrong
rather than merely undocumented: FITTED_RESOLUTION was the P1 cross-check's, and
the unmerged MTZ carried the reference setting where the merged file carried the
adopted one.

Sphinx builds clean with -W on the pinned docs/requirements.txt, and every
intra-doc anchor resolves against the generated HTML.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-09-02 17:44:20 +02:00

6.2 KiB
Raw Blame History

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 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 P63/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.