The gain of an input in detector units (SMV, marCCD, XTC, the XFEL formats) is measured once, before anything reads the frames (RugnuxMeasureGain: photon-peak spacing and background variance per unit mean), or given with --gain, and handed to the integration and the spot finding by one function (ApplyRugnuxPixelGain). The rotation pre-scan no longer measures it, and the stills reader no longer converts frames of a detector whose photons are not resolved: SACLA MPCCD, Rayonix and CCD frames are integrated in ADU at the gain, rotation and stills alike. Where a photon count was assumed, it is now taken at the gain (bit-identical at gain 1): - integration: the background clip's one-photon floor, the sigma floor (one photon), the profile fit's background floor (0.01 photon), and a still's overload kept as a lower bound, which was left in ADU while every other reflection left the integrator in photons - the cause of the earlier loss of the engine route on clipped CCD stills (merged low resolution inflated by the gain, the rocking-width fit's bandwidth collapsing); CPU and CUDA, Metal through the shared CPU code; - the adaptive spot threshold (Poisson arm and its one-photon floor), CPU and GPU; - the pre-scan spot-width measurement (strong-spot SNR, the ring clip) behind the stills bandwidth and the rotation signal radius; - the hot/dead pixel tests, CPU and GPU; the empty-aperture ring clip. BackgroundPixelGain is gone: one pixel_gain. A detector whose background resolves single photons (photon peaks: JUNGFRAU in keV, AGIPD) is a photon counter and its frames are still counted in whole photons as read: analysed in keV at the gain instead, a photon-counting still set lost 0.008 R-free (fewer spots, a noisier beam-centre trial) - the rounding is the detector's own discrimination. --gain sets the value, the background still says which kind. Validation, against rc178: photon-counting and g<=1 rotation p.hkl md5-identical (lyso, thau, cytc, myob, insu, a small-molecule set, 5reo, 6cee, 6i3j, 2xfw, 6s1u, 8v4j); CCD g>1 at or within noise of rc178 (5uth R_meas 0.113 -> 0.108). Stills R-free (0-cycle REFMAC): MPCCD sets 0.184 -> 0.185 and 0.199 -> 0.196, Rayonix identical, photon-resolving sets back on their rc178 numbers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu (cherry picked from commit da8dbfc8147354b63e375f2d9521dffb80938b6d)
165 lines
13 KiB
Markdown
165 lines
13 KiB
Markdown
# What Rugnux reads
|
|
|
|
Which data `rugnux` opens, before anything is typed. The short answer: an HDF5 master (NXmx or
|
|
DECTRIS, from any facility), a PILATUS miniCBF sweep, a marCCD sweep or an SMV sweep (ADSC or Rigaku d\*TREK) — nothing else
|
|
is read, so any other format has to be converted to one of these first.
|
|
|
|
**Input** is files, never a directory: an HDF5 master file, or one frame of a PILATUS miniCBF, marCCD
|
|
or SMV sweep. For rotation data one input is **one sweep of one crystal** — Rugnux does not combine sweeps or crystals in a run; process each
|
|
sweep to its own `_unmerged.mtz` and merge them downstream
|
|
(see [Taking the data onward](RUGNUX_INTEGRATION.md#taking-the-data-onward)).
|
|
|
|
Stills are given as all the files of the dataset, which the shell expands from a glob and which are
|
|
read in the order given: `rugnux run1_master.h5 run2_master.h5` (or `rugnux *_master.h5`) for
|
|
several Jungfraujoch/NXmx collections of one setup, `rugnux run*.h5` for SACLA, `rugnux *.cxi` for a
|
|
Cheetah CXI set and `rugnux *.xtc` for LCLS streams. Several files must all be of one format; a
|
|
directory is refused.
|
|
|
|
* **HDF5 master (NXmx-based)** — a file written by [`jfjoch_writer`](JFJOCH_WRITER.md), a DECTRIS
|
|
EIGER master, or an NXmx master written by another facility's toolchain. Lengths are taken in the
|
|
unit the file declares, the image size from the image array's own shape, and each data file's
|
|
images at the path the master's own link names, so masters that state single values as one-element
|
|
arrays, compose their images as a virtual dataset over the master itself, or keep them somewhere
|
|
other than `/entry/data/data` all open. Where the NXmx spellings are absent the pre-NXmx ones are
|
|
tried, so a DECTRIS firmware 1.x master opens too — including its goniometer, which used to be
|
|
missed and the sweep read as stills. Images may be `bitshuffle`+LZ4/Zstd or the HDF Group's plain
|
|
LZ4 (filter 32004); any other filter is named in the error.
|
|
* **PILATUS miniCBF sweep** — one frame per file, read natively with no conversion and no libcbf.
|
|
Name **any frame of the sweep** and the whole sweep is processed: the frames are the ones matching
|
|
that frame's template (prefix plus digit count), so a directory holding two sweeps is not spliced
|
|
into one crystal. The geometry, the rotation axis and the detector mounting come from the
|
|
header, including the imgCIF axis table where the header carries one (see
|
|
[Detector geometry](DETECTOR_GEOMETRY.md)). A raw CBF carries no analysis results, so
|
|
`--mode scale` — which re-scales the reflections stored in a `_process.h5` — does not accept one.
|
|
**Gzipped frames (`.cbf.gz`) are read directly**, with no decompression step: EMBL Hamburg's
|
|
beamlines write that form by default, and expanding a sweep first would double the disk it needs.
|
|
The two forms count as separate sweeps.
|
|
* **marCCD sweep** — what Rayonix MX-series and mar Mosaic detectors write, and what a decade of
|
|
deposited CCD data is archived as: an uncompressed TIFF with the instrument header in the gap
|
|
before the pixels. One frame per file, read natively. Naming a frame selects the
|
|
sweep exactly as for miniCBF, under either naming scheme these detectors use — a numbered stem
|
|
(`xtal_1_00042.mccd`) or the frame number as the file extension (`D1.042`). The distance, beam
|
|
centre, pixel size, wavelength and the circle that turned come from the header; the rotation
|
|
axis' direction does not, because the format has nowhere to state it, so the run settles its sign
|
|
from the data as it does for a miniCBF that carries no axis table. A CCD frame marks no untrusted
|
|
pixels, so the sweep starts with nothing masked. Like a raw CBF, it carries no analysis results
|
|
and `--mode scale` does not accept one.
|
|
* **SMV sweep** — what ADSC Quantum detectors wrote and what Rayonix and others still write, so most
|
|
archived CCD data from the 2000s is in this form: an ASCII `KEY=value;` block between braces, then
|
|
the pixels. One frame per file, read natively, the sweep selected exactly as above. The beam centre
|
|
it states is in **millimetres** and is converted here; its convention varies between writers, and a
|
|
file that states it transposed indexes nothing until the run's own beam-centre measurement replaces
|
|
it, which it does automatically. The saturation value is read where the header states one
|
|
(`CCD_IMAGE_SATURATION` or `SATURATED_VALUE`); where it does not, overloads are judged on the 16-bit
|
|
container alone and the reader says so once per sweep.
|
|
**Rigaku d\*TREK headers** — what Saturn CCDs on rotating-anode home sources write, recognised by
|
|
their `DETECTOR_NAMES` key — are read in their own vocabulary: the pixel size and the point of
|
|
normal incidence from the spatial-distortion record, the image directions from the detector and
|
|
distortion vectors (so a mirrored or turned image keeps its hand), the distance and the detector
|
|
circles, including a swung **2θ arm**, from the detector goniometer, the spindle and its angles from
|
|
the rotation record, and the wavelength from the scan record. Pixel values above 32767 are the
|
|
format's encoded overflows and are expanded with the header's compression ratio.
|
|
|
|
Support for the two CCD formats — marCCD and SMV — is **very limited and provided as-is**; they
|
|
are read so that a CCD sweep does not need a conversion step to be processed. Only the header
|
|
fields named above are read, and the headers vary by site and vintage, so no promise is made that
|
|
every writer's dialect opens. The HDF5 and miniCBF paths are the maintained ones.
|
|
|
|
### Serial XFEL inputs
|
|
|
|
Stills from the XFEL serial-crystallography pipelines are read straight from the files those
|
|
pipelines write, **without a geometry file**. Each reader takes from the file what it states; what
|
|
the file does not state is either measured by rugnux or given on the command line. Every run is read
|
|
at **one wavelength** (the mean of the per-shot photon energies where the file states them), the
|
|
**beam centre is measured** (the file's centre, the background-isotropy measurement and the
|
|
point-symmetry capture are indexed on a sample of frames and the best is kept), and a SACLA or CXI
|
|
file that states **no gain** is read at the gain its background shows. Where the pixel values resolve
|
|
single photons (a JUNGFRAU file in keV, an AGIPD file) the gain is the spacing of their photon peaks, so a
|
|
baseline the file left in does not hide it, and the detector is a photon counter: its frames are counted in
|
|
whole photons as they are read. Otherwise (a CCD, an MPCCD, a Rayonix) the gain is the background's
|
|
variance per unit mean and the frames are analysed in the units they are stored in: the gain enters only
|
|
where a photon count is assumed (the spot finder's threshold, the background clip, the integrated
|
|
intensities' variances, which come out in photons). `--gain` gives the value; the background still says
|
|
which of the two the detector is. Float frames analysed in their own units are rounded to whole ones.
|
|
The saturation level is read off the frames where the file states none.
|
|
|
|
| Format | Read from the file | Measured by rugnux | Given on the command line |
|
|
|---|---|---|---|
|
|
| **Cheetah HDF5 with per-shot groups and a panel table** (`/tag-N/data`, sensors stacked in one frame, their placement in `/metadata` - as SACLA writes for the MPCCD) | the sensors' positions and in-plane angles, pixel size, nominal camera length and pixel mask (`/metadata`); each shot's photon energy | beam centre, gain, saturation; with a cell, the camera length (the stated one is nominal) and each sensor's placement | nothing; `--detector-distance` to give the camera length |
|
|
| **Cheetah CXI, single-panel detector** (`/entry_1/data_1/data`, square frames, e.g. a Rayonix at PAL-XFEL) | pixel size, photon energy per shot, camera length, `mask_shared` | beam centre, gain, saturation; with a cell, the camera length | `--detector-distance` to hold a camera length |
|
|
| **LCLS-I XTC, Rayonix** (Camera::FrameV1 events; one or more streams) | photon energy per event (electron-beam record), pixel size (Rayonix model and binning), the camera's pixel offset | beam centre, saturation; with a cell, the distance | `--detector-distance` without a cell (not recorded in XTC); `--pixel-size`/`--wavelength` only where the XTC lacks them |
|
|
|
|
The SACLA sensor positions in `/metadata` are the detector's nominal metrology, a few pixels from a
|
|
per-beamtime refined geometry; the stills geometry refinement places each sensor as a module (see
|
|
[Module geometry](#module-geometry)).
|
|
|
|
What is **refused**, with a message rather than a wrong answer:
|
|
|
|
* a Cheetah CXI file of a **tiled detector** (CSPAD, AGIPD, ePix, JUNGFRAU, MPCCD stored as a slab of
|
|
panels): the file does not say where the panels lie;
|
|
* **multi-panel XTC** (CSPAD, ePix, JUNGFRAU at LCLS), stored raw with per-module calibration, and an XTC
|
|
stream holding images of the same size from two cameras;
|
|
* **unconverted detector data** in general (raw ADC values with gain stages and pedestals);
|
|
* **per-shot wavelengths**: they are averaged, never followed shot by shot;
|
|
* files of one run that store their frames differently (size, type, compression) or place the sensors
|
|
differently.
|
|
|
|
Damaged or compressed XTC images are left out, and so is a last event that a partly downloaded stream
|
|
ends inside. An XFEL dataset that has already been assembled into single images in an NXmx master
|
|
opens like any other HDF5 input.
|
|
|
|
`--crystfel-geometry <file>` places the panels of an HDF5/CXI input by a CrystFEL geometry file. Taken
|
|
from it are the module geometry (panel pixel ranges, fast/slow directions, corners, pixel size,
|
|
per-panel `coffset`, the frames' data path), optionally the camera length, and the pixels to leave out:
|
|
the mask arrays (`mask`, `mask_file`, `mask_good`, `mask_bad`, or up to eight as `maskN_data`,
|
|
`maskN_file`, `maskN_goodbits`, `maskN_badbits`; a panel may state its own), the bad regions (`bad*`,
|
|
a box in the laboratory plane or a pixel range on a panel), `mask_edge_pixels` and `no_index`. A mask
|
|
dataset with one plane per frame, or with `%` in its path, is applied frame by frame. A relative
|
|
`mask_file` is looked for in the working directory, as CrystFEL does, and then beside the geometry file.
|
|
Where the geometry names mask arrays, the data file's own (the CXI `mask_shared` and per-shot mask, a
|
|
panel table's `/metadata/pixelmask`) are not taken: the geometry says which bits matter. Everything else
|
|
it may hold (gain, saturation, photon energy, value flags) is ignored, with a note in the log. DIALS
|
|
`.expt` files are not read.
|
|
|
|
A mask array the data files do not hold themselves - e.g. a per-run pixel mask beside the frames - is
|
|
given with `--mask-good [<file>:]<dataset>[@<bits>]` (a pixel is usable only where the array is non-zero,
|
|
or has all of `<bits>`) or `--mask-bad [<file>:]<dataset>[@<bits>]` (unusable where non-zero, or where it
|
|
has any of `<bits>`, e.g. `@0xfeff`), in the frames' own layout, as stored. Without `<file>` the dataset is
|
|
read from each data file - a per-frame mask stored beside the frames. Both may be given more than once,
|
|
and add to the masks the data files or the geometry file state (the CXI `mask_shared` and per-shot mask,
|
|
the `/metadata/pixelmask` of a panel-table file, a `--crystfel-geometry` file's masks). A gain the background shows at or below one detector unit
|
|
per photon is taken as one.
|
|
|
|
### Module geometry
|
|
|
|
A detector of several modules is described module by module: where each module's pixels are in the image
|
|
rugnux reads, and where they really are on the detector plane. The modules come from an explicit
|
|
geometry, never from the image:
|
|
|
|
* an NXmx file with one `NXdetector_module` group per module (`/entry/instrument/detector/module_0`,
|
|
`module_1`, ...: what a run writes as `<prefix>_modules.h5`; the masters Jungfraujoch writes still state
|
|
the image as one module);
|
|
* the panels the XFEL readers know (the SACLA sensors from `/metadata`, the panels of a
|
|
`--crystfel-geometry` file); each panel is put on the image whole, at a whole pixel, and its fraction of
|
|
a pixel and small turn are its placement;
|
|
* `--module-geometry <file>`: an NXmx file with such groups (what a run writes as `<prefix>_modules.h5`)
|
|
or a CrystFEL geometry file whose panels are laid on the image (`fs` = column, `ss` = row, corners
|
|
relative to the beam). It replaces the input's own modules - the way to give the module layout of a
|
|
master written before the modules were recorded.
|
|
|
|
Spots are mapped from the image onto the plane, and predictions from the plane back onto the image for
|
|
integration, so the image itself is never re-assembled. With a cell given, the stills geometry refinement
|
|
places each module with enough spots on its frames' lattices as a rigid block (shift and in-plane turn,
|
|
`--refine-modules`, on by default) where that predicts held-out spots better; modules move only relative
|
|
to one another, the beam centre, distance and crystal orientations carrying what all of them share. The
|
|
placement is used from there on for the panels of a foreign multi-panel detector (the sensors of a
|
|
panel-table file, the panels of a `--crystfel-geometry` file); for a JUNGFRAU (an NXmx master) it is
|
|
measured and reported only. Either way it is written to `<prefix>_modules.h5`, which a later run reads
|
|
back with `--module-geometry`.
|
|
|
|
Spots are always found by `rugnux` itself, including for the two-pass rotation first pass — the
|
|
spot lists a dataset may already carry were found online, at the acquisition's threshold and with
|
|
its ice-band spots already discarded, so reusing them would hide the spot-finding settings from
|
|
the lattice search.
|
|
|