jfjoch_process/scale: default to mmCIF, auto-detect rotation, rot3d partiality
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m31s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m1s
Build Packages / build:rpm (rocky8) (push) Successful in 14m56s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m10s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m16s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 15m14s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 15m49s
Build Packages / build:windows:nocuda (push) Successful in 15m5s
Build Packages / build:windows:cuda (push) Successful in 17m15s
Build Packages / XDS test (durin plugin) (push) Successful in 8m51s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m12s
Build Packages / Generate python client (push) Successful in 24s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m14s
Build Packages / Build documentation (push) Successful in 1m5s
Build Packages / build:windows:nocuda (pull_request) Successful in 9m29s
Build Packages / build:rpm (rocky9) (push) Successful in 13m8s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m44s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 12m56s
Build Packages / DIALS test (push) Successful in 13m57s
Build Packages / build:windows:cuda (pull_request) Successful in 16m22s
Build Packages / build:rpm (rocky8_nocuda) (pull_request) Successful in 10m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (pull_request) Successful in 10m45s
Build Packages / build:rpm (rocky9_nocuda) (pull_request) Successful in 13m23s
Build Packages / build:rpm (ubuntu2204_nocuda) (pull_request) Successful in 11m55s
Build Packages / build:rpm (rocky8_sls9) (pull_request) Successful in 11m55s
Build Packages / build:rpm (rocky9_sls9) (pull_request) Successful in 12m40s
Build Packages / build:rpm (rocky8) (pull_request) Successful in 10m46s
Build Packages / build:rpm (ubuntu2404) (pull_request) Successful in 11m3s
Build Packages / build:rpm (ubuntu2204) (pull_request) Successful in 11m48s
Build Packages / build:rpm (rocky9) (pull_request) Successful in 12m57s
Build Packages / Generate python client (pull_request) Successful in 17s
Build Packages / Create release (pull_request) Skipped
Build Packages / XDS test (durin plugin) (pull_request) Successful in 9m11s
Build Packages / Build documentation (pull_request) Successful in 46s
Build Packages / DIALS test (pull_request) Successful in 12m41s
Build Packages / XDS test (JFJoch plugin) (pull_request) Successful in 7m36s
Build Packages / XDS test (neggia plugin) (pull_request) Successful in 5m42s
Build Packages / Unit tests (push) Successful in 1h11m43s
Build Packages / Unit tests (pull_request) Successful in 57m54s

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 08:34:50 +02:00
co-authored by Claude Fable 5
parent 4b393082d4
commit 7e3ad84a5b
4 changed files with 90 additions and 36 deletions
+43 -6
View File
@@ -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 <vec> 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 <txt> Partiality model fixed|rot|rot3d|unity (default: fixed). rot3d = rot + 3D combine of per-frame partials" << std::endl;
std::cout << " -P, --partiality <txt> 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 <num> 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 <num> Per-image CC limit in percent (default: no limit)" << std::endl;
std::cout << " --scaling-iterations <num> Number of scaling iterations with no reference data (default: 3)" << std::endl;
std::cout << " --scaling-output <txt> Output format for scaling results mtz|cif|txt (default: mtz)" << std::endl;
std::cout << " --scaling-output <txt> Output format for scaling results mtz|cif|txt (default: cif)" << std::endl;
std::cout << " -z, --reference-mtz <file> Reference MTZ file" << std::endl;
std::cout << " --reference-column <label> Reference MTZ column to use (default: auto - F-model, else IMEAN/I/...)" << std::endl;
std::cout << std::endl;
@@ -114,7 +116,8 @@ enum {
OPT_DETECT_ICE_RINGS,
OPT_NO_SCALE_FULLS,
OPT_WRITE_PROCESS_H5,
OPT_ABSORPTION
OPT_ABSORPTION,
OPT_PROCESS_AS_STILLS
};
static option long_options[] = {
@@ -144,6 +147,7 @@ static option long_options[] = {
{"two-pass-rotation", optional_argument, nullptr, 'R'},
{"single-pass-rotation", optional_argument, nullptr, OPT_SINGLE_PASS_ROTATION},
{"process-as-stills", no_argument, nullptr, OPT_PROCESS_AS_STILLS},
{"redo-rotation-spots", no_argument, nullptr, OPT_REDO_ROTATION_SPOTS},
{"force-rotation-lattice", required_argument, nullptr, OPT_FORCE_ROTATION_LATTICE},
@@ -297,6 +301,7 @@ int main(int argc, char **argv) {
bool verbose = false;
bool rotation_indexing = false;
bool process_as_stills = false; // --process-as-stills: force per-frame stills on a rotation dataset
bool two_pass_rotation = true;
bool reuse_rotation_spots = true;
int rotation_indexing_image_count = 100;
@@ -331,8 +336,9 @@ int main(int argc, char **argv) {
IndexingAlgorithmEnum indexing_algorithm = IndexingAlgorithmEnum::Auto;
GeomRefinementAlgorithmEnum refinement_algorithm = GeomRefinementAlgorithmEnum::BeamCenter;
IntensityFormat intensity_format = IntensityFormat::MTZ;
std::optional<IntensityFormat> intensity_format; // --scaling-output override; default lives in ScalingSettings (mmCIF)
PartialityModel partiality_model = PartialityModel::Fixed;
bool partiality_explicit = false; // true once -P is given, so the rotation/stills default does not override it
bool combine_3d = false; // -P rot3d: weight-sum per-frame partials into fulls before merging
float d_min_spot_finding = 1.5;
@@ -515,6 +521,7 @@ int main(int argc, char **argv) {
print_usage();
exit(EXIT_FAILURE);
}
partiality_explicit = true;
break;
case OPT_SPOT_SIGMA:
sigma_spot_finding = atof(optarg);
@@ -601,6 +608,9 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE);
}
break;
case OPT_PROCESS_AS_STILLS:
process_as_stills = true;
break;
case OPT_SCALING_ITERATIONS:
scaling_iter = atoi(optarg);
if (scaling_iter <= 0) {
@@ -753,6 +763,32 @@ int main(int argc, char **argv) {
if (experiment.GetBandwidthFWHM())
logger.Info("X-ray bandwidth FWHM set to {:.4f}", experiment.GetBandwidthFWHM().value());
// Rotation vs stills. A dataset collected on a rotation goniometer is processed as rotation data
// (two-pass indexing) by default; --process-as-stills forces per-frame stills. The rotation flags
// (-R / --single-pass-rotation / --force-rotation-lattice) still request rotation explicitly and
// choose the pass/lattice; at this point they show up as rotation_indexing already being set.
const bool has_goniometer = experiment.GetGoniometer().has_value();
if (process_as_stills) {
if (rotation_indexing) {
logger.Error("--process-as-stills conflicts with -R / --single-pass-rotation / --force-rotation-lattice");
exit(EXIT_FAILURE);
}
if (has_goniometer)
logger.Info("--process-as-stills: treating the rotation dataset as independent stills");
} else if (!rotation_indexing && has_goniometer) {
rotation_indexing = true;
two_pass_rotation = true;
logger.Info("Dataset has a rotation goniometer axis: processing as rotation data (two-pass "
"indexing). Use --process-as-stills to treat it as stills.");
}
// Default partiality model: rot3d for rotation processing (Rotation partiality + 3D combine of the
// per-frame partials into fulls), fixed for stills. An explicit -P always wins.
if (!partiality_explicit && rotation_indexing) {
partiality_model = PartialityModel::Rotation;
combine_3d = true; // rot3d
}
// Configure Indexing
IndexingSettings indexing_settings;
indexing_settings.Algorithm(indexing_algorithm);
@@ -791,7 +827,8 @@ int main(int argc, char **argv) {
scaling_settings.MinCCForImage(min_image_cc / 100.0); // --min-image-cc is in percent; the setting is a fraction
scaling_settings.OutlierRejectNsigma(
outlier_reject_nsigma.value_or(combine_3d ? REJECT_OUTLIERS_DEFAULT_NSIGMA : 0.0));
scaling_settings.FileFormat(intensity_format);
if (intensity_format)
scaling_settings.FileFormat(intensity_format.value());
experiment.ImportScalingSettings(scaling_settings);