From 7e3ad84a5be93affb9f82610fbfc3c193b1b99fc Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 2 Jul 2026 08:34:50 +0200 Subject: [PATCH] jfjoch_process/scale: default to mmCIF, auto-detect rotation, rot3d partiality - Default reflection format is now mmCIF (ScalingSettings.h, the single source of truth). jfjoch_process and jfjoch_scale no longer hard-code a local MTZ default; they override only when --scaling-output is given. jfjoch_viewer inherits this. - A dataset with a rotation goniometer axis is processed as rotation data (two-pass indexing) by default; add --process-as-stills to force per-frame stills. -R still tunes the first-pass image count. - rot3d is the default partiality model for rotation processing (fixed for stills) when no explicit -P is given. - Update docs/JFJOCH_PROCESS.md: new de-novo rotation/stills examples, corrected -R/-P/--scaling-output defaults, --process-as-stills, and a real Integration table. Co-Authored-By: Claude Fable 5 --- common/ScalingSettings.h | 2 +- docs/JFJOCH_PROCESS.md | 68 +++++++++++++++++++++++++--------------- tools/jfjoch_process.cpp | 49 +++++++++++++++++++++++++---- tools/jfjoch_scale.cpp | 7 +++-- 4 files changed, 90 insertions(+), 36 deletions(-) diff --git a/common/ScalingSettings.h b/common/ScalingSettings.h index b55a511d..121dc4e1 100644 --- a/common/ScalingSettings.h +++ b/common/ScalingSettings.h @@ -51,7 +51,7 @@ class ScalingSettings { double smooth_g_deg = 0.0; double rfree_fraction = 0.05; - IntensityFormat intensity_format = IntensityFormat::MTZ; + IntensityFormat intensity_format = IntensityFormat::mmCIF; bool scaling_regularize = false; public: diff --git a/docs/JFJOCH_PROCESS.md b/docs/JFJOCH_PROCESS.md index ac8886a7..0ad4bc07 100644 --- a/docs/JFJOCH_PROCESS.md +++ b/docs/JFJOCH_PROCESS.md @@ -43,8 +43,10 @@ the first pass. integration, azimuthal integration, per-image statistics). See [HDF5 / NeXus data format](HDF5.md) for the layout. - When merging (`-M`, or whenever a `--reference-mtz` is supplied), the merged reflections are - written as `.mtz` (default), or `.cif` / `.hkl` depending on - `--scaling-output`. No-reference scaling additionally emits per-iteration `_iterN_scale.dat`. + written as `.cif` (mmCIF — the default), or `.mtz` / `.hkl` depending on + `--scaling-output`. Both the mmCIF and the MTZ carry the **refined unit cell** (from rotation + indexing) and the **space group determined from systematic absences** (constrained to the indexed + lattice symmetry). No-reference scaling additionally emits per-iteration `_iterN_scale.dat`. Merged statistics (⟨I/σ⟩, CC1/2, completeness, …), the error model and timing are printed to the console. @@ -60,38 +62,45 @@ or to combine several processed runs into one set of merged intensities. ### Rotation data -Two-pass rotation indexing, rotation partiality, scale and merge in space group 96: +Index, integrate, scale and merge a rotation sweep, fully de novo: ``` jfjoch_process rotation_master.h5 \ - -o lyso_rot -N 16 \ - -R -S 96 \ - -M -P rot + -o lyso_rot -N 32 \ + -M --scaling-high-resolution 1.4 ``` -`-R` runs the two-pass rotation indexer (index the sweep once, then process every frame against -that lattice); `-P rot` selects the rotation partiality model; `-M` scales and merges. For strong -rotation data the de-novo FFT indexer often indexes more frames — add `-X fft` (and drop `-C` to -let it find the cell from scratch). +Because the dataset carries a rotation goniometer axis, it is processed as **rotation data by +default**: two-pass rotation indexing (index the sweep once, then process every frame against that +lattice) with the **`rot3d`** partiality model (rotation partials combined into 3D fulls). `-M` +scales and merges; the unit cell is taken from the rotation indexer and the space group is +determined from systematic absences, and both are written into the merged `.cif`. + +Run **fully de novo** (no `-C`/`-S`) for the best result — supplying a cell or space group up front +tends to *degrade* low-symmetry cases. `--scaling-high-resolution` (set it to your expected +resolution) sharpens both the space-group search and the error model. To tune the first pass use +`--two-pass-rotation=100` (or `-R100` — the first-pass image count); to force the sweep to be +treated as independent stills use `--process-as-stills`. ### Still / serial data -Known-cell indexing of independent stills with the GPU fast-feedback indexer, then merge against a -reference structure: +A dataset with **no goniometer axis** (e.g. a serial grid scan) is processed as **independent +stills automatically** — no flag needed. Known-cell indexing with the GPU fast-feedback indexer, +then merge against a reference structure: ``` jfjoch_process serial_master.h5 \ - -o lyso_serial -N 16 \ + -o lyso_serial -N 32 \ -X ffbidx -C 79,79,38,90,90,90 -S 96 \ --spot-sigma 4 \ - -M -z reference.mtz -r pixelrefine \ + -M -z reference.mtz \ --scaling-high-resolution 1.8 ``` -`ffbidx` requires a known cell (`-C`) and is the indexer of choice for sparse serial stills. -`-r pixelrefine` selects the experimental reference-driven still integrator (needs -`--reference-mtz`). For weak serial data, tightening spot finding with `--spot-sigma 4` typically -raises the indexing rate substantially. +`ffbidx` requires a known cell (`-C`) and is the indexer of choice for sparse serial stills. For +weak serial data, tightening spot finding with `--spot-sigma 4` typically raises the indexing rate +substantially. If a dataset *does* carry a goniometer axis but you want per-frame stills processing +anyway, add `--process-as-stills`. ## Command-line options @@ -117,13 +126,19 @@ Spot finding: Indexing: +A dataset with a **rotation goniometer axis** is processed as rotation data (two-pass rotation +indexing) by default; a dataset without one is processed as independent stills. `--process-as-stills` +overrides the former; the `-R` / `--single-pass-rotation` / `--force-rotation-lattice` flags request +rotation explicitly and pick the pass or lattice. + | Option | Description | | --- | --- | +| `--process-as-stills` | Treat a rotation (goniometer) dataset as independent stills instead of rotation | | `-X, --indexing-algorithm ` | `FFBIDX` \| `FFT` \| `FFTW` \| `Auto` \| `None` | | `-C, --unit-cell ` | Reference unit cell `"a,b,c,alpha,beta,gamma"` (required by `ffbidx`) | | `-S, --space-group ` | Space group number (used for indexing and scaling) | -| `-r, --refine ` | Geometry refinement: `none` \| `orientation` \| `beam_and_lattice` (default) \| `pixelrefine` | -| `-R, --two-pass-rotation[=num]` | Two-pass offline rotation indexing (optional image count, default 30) | +| `-r, --refine ` | Geometry refinement: `none` \| `orientation` \| `beam_and_lattice` (default) | +| `-R, --two-pass-rotation[=num]` | Two-pass offline rotation indexing (default for goniometer data; optional first-pass image count, default 100) | | `--single-pass-rotation[=num]` | Online-like single-pass rotation indexing (optional min angular range, deg) | | `--redo-rotation-spots` | Redo spot finding for the two-pass rotation first pass | | `--force-rotation-lattice ` | Force rotation lattice (9 floats, Å), skipping the first pass | @@ -137,23 +152,24 @@ Scaling and merging: | Option | Description | | --- | --- | | `-M, --scale-merge` | Scale and merge | -| `-P, --partiality ` | Partiality model: `fixed` (default) \| `rot` \| `unity` | +| `-P, --partiality ` | Partiality model: `fixed` \| `rot` \| `rot3d` \| `unity` (default: `rot3d` for rotation data, `fixed` for stills). `rot3d` = `rot` + 3D combine of the per-frame partials into fulls | | `-A, --anomalous` | Anomalous mode (keep Friedel pairs separate) | | `-B, --refine-bfactor` | Refine a per-image B-factor | | `-w, --wedge[=num]` | Refine the per-image rotation wedge (optional starting value) | | `--scaling-high-resolution ` | High-resolution limit for scaling, Å (default: no limit) | | `--min-partiality ` | Minimum partiality to accept a reflection (default: 0.02) | -| `--reject-outliers ` | Per-observation outlier rejection, N σ from the per-reflection median (default: off) | +| `--reject-outliers ` | Per-observation outlier rejection, N σ from the per-reflection median (default: 6 for `rot3d`, off otherwise) | | `--reject-delta-cchalf ` | Drop images with ΔCC1/2 below mean − N·stddev (default: off) | | `--min-image-cc ` | Per-image CC limit, percent (default: no limit) | | `--scaling-iterations ` | Scaling iterations with no reference data (default: 3) | -| `--scaling-output ` | Reflection output format: `mtz` (default) \| `cif` \| `txt` | +| `--scaling-output ` | Reflection output format: `cif` (mmCIF, default) \| `mtz` \| `txt` | | `-z, --reference-mtz ` | Reference MTZ (enables reference-driven scaling) | -Pixel refinement (experimental; select with `-r pixelrefine`, requires `--reference-mtz`): +Integration: | Option | Description | | --- | --- | -| `--bandwidth ` | Relative X-ray bandwidth FWHM (e.g. `0.01` for a 1% DMM); default from file or 0 (monochromatic) | +| `--integrator ` | Spot integrator: `gaussian` (profile-fit, default) \| `empirical` \| `boxsum` (classical fallback) | | `--integration-radius ` | Signal-box radius `r1`, or `r1,r2,r3` (px). One value ⇒ `r2=r1+2`, `r3=r1+4` | -| `--profile-multiplier ` | Scale the measured tangential profile width (default: 6; XDS-style generous aperture) | +| `--reciprocal-profile` | Learn one global reciprocal-space profile width (`A+B·|q|+C·|q|²`) instead of per-shell; helps mosaic/sparse data | +| `--bandwidth ` | Relative X-ray bandwidth FWHM (e.g. `0.01` for a 1% DMM); default from file or 0 (monochromatic) | diff --git a/tools/jfjoch_process.cpp b/tools/jfjoch_process.cpp index 6b0dea09..d5659a9b 100644 --- a/tools/jfjoch_process.cpp +++ b/tools/jfjoch_process.cpp @@ -45,7 +45,9 @@ void print_usage() { std::cout << std::endl; std::cout << " Indexing" << std::endl; - std::cout << " -R, --two-pass-rotation[=num] Two-pass offline rotation indexing (optional: number of images, default: 100)" << std::endl; + std::cout << " (A dataset with a rotation goniometer axis is processed as rotation data by default; use --process-as-stills to override)" << std::endl; + std::cout << " --process-as-stills Process a rotation (goniometer) dataset as independent stills instead of rotation" << std::endl; + std::cout << " -R, --two-pass-rotation[=num] Two-pass offline rotation indexing (default for goniometer data; optional first-pass image count, default: 100)" << std::endl; std::cout << " --single-pass-rotation[=num] Use online-like single-pass rotation indexing (optional: min angular range deg)" << std::endl; std::cout << " --redo-rotation-spots Redo spot finding for two-pass rotation indexing" << std::endl; std::cout << " --force-rotation-lattice Force rotation indexer with external lattice (in Angstrom) : \"a0x,a0y,a0z,a1x,a1y,a1z,a2x,a2y,a2z\" (9 floats, skips first pass)" << std::endl; @@ -62,7 +64,7 @@ void print_usage() { std::cout << " --write-process-h5 Also write the (large) _process.h5 when merging (default: only .mtz/.cif when merging)" << std::endl; std::cout << " --absorption[=num] rot3d: fit a smooth absorption surface on the fulls over num iterations (default 3; off by default; matters at low energy)" << std::endl; std::cout << " --smooth-g[=deg] rot3d: smooth per-frame scale G over a deg-degree rotation range (XDS DELPHI-like) before the combine (default: 5 for rot3d; 0 = off)" << std::endl; - std::cout << " -P, --partiality Partiality model fixed|rot|rot3d|unity (default: fixed). rot3d = rot + 3D combine of per-frame partials" << std::endl; + std::cout << " -P, --partiality Partiality model fixed|rot|rot3d|unity (default: rot3d for rotation data, fixed for stills). rot3d = rot + 3D combine of per-frame partials" << std::endl; std::cout << " -A, --anomalous Anomalous mode (don't merge Friedel pairs)" << std::endl; std::cout << " -B, --refine-bfactor Refine per image B-factor" << std::endl; std::cout << " -w, --wedge[=num] Refine image wedge during scaling with starting wedge value" << std::endl; @@ -74,7 +76,7 @@ void print_usage() { std::cout << " --reject-delta-cchalf Per-crystal CC1/2-delta rejection: drop images with deltaCChalf below mean - N*stddev (default: off; e.g. 2.5)" << std::endl; std::cout << " --min-image-cc Per-image CC limit in percent (default: no limit)" << std::endl; std::cout << " --scaling-iterations Number of scaling iterations with no reference data (default: 3)" << std::endl; - std::cout << " --scaling-output Output format for scaling results mtz|cif|txt (default: mtz)" << std::endl; + std::cout << " --scaling-output Output format for scaling results mtz|cif|txt (default: cif)" << std::endl; std::cout << " -z, --reference-mtz Reference MTZ file" << std::endl; std::cout << " --reference-column