From b4d3d40cd8339e5b998c9a59ce6d8513fca91ba3 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Fri, 3 Jul 2026 11:58:34 +0200 Subject: [PATCH] Replace the dead PartialityModel with a force_still_processing flag With ScaleOnTheFly now fixed-only and rotation routed through RotationScaleMerge, the PartialityModel enum carried no information (it was always Rotation for rotation processing, Fixed otherwise, and mirrored combine_3d). Drop it: - ScalingSettings: `partiality_mode` (PartialityModel) -> `force_still_processing` (bool); SetPartialityModel/GetPartialityModel -> ForceStillProcessing/GetForceStillProcessing. Remove the enum. - DiffractionExperiment: drop GetPartialityModel(); the rotation-vs-stills decision is now just GetCombine3D() (set by the tool = rotation && !force_still). The wedge getters no longer key off the model (dead since wedge refinement was removed). - jfjoch_process: `-P/--partiality fixed|rot3d` -> `--force-still-processing` (a rotation dataset scaled as independent stills). Auto-detect sets combine_3d for rotation data unless the flag is given. - jfjoch_scale: `-P fixed|rot3d` now toggles combine_3d directly (no PartialityModel). - jfjoch_viewer: the "process as stills" toggle sets ForceStillProcessing(!rotation_mode) - UI unchanged, just wired to the new field. PartialityModel was never in the OpenAPI, so no generated clients change. Rotation path behaviour is unchanged (lyso 16.4/99.6%/87.3%); --force-still-processing correctly routes to ScaleOnTheFly. CUDA + non-CUDA + viewer all build. Co-Authored-By: Claude Opus 4.8 --- common/DiffractionExperiment.cpp | 24 ++----------- common/DiffractionExperiment.h | 1 - common/ScalingSettings.cpp | 8 ++--- common/ScalingSettings.h | 15 +++++---- process/JFJochProcess.cpp | 3 +- process/JFJochProcessCommandLine.cpp | 4 +-- tools/jfjoch_process.cpp | 37 +++++++-------------- tools/jfjoch_scale.cpp | 14 +++----- viewer/widgets/JFJochViewerSettingsDock.cpp | 2 +- 9 files changed, 35 insertions(+), 73 deletions(-) diff --git a/common/DiffractionExperiment.cpp b/common/DiffractionExperiment.cpp index 818abb4c..7e16f1c7 100644 --- a/common/DiffractionExperiment.cpp +++ b/common/DiffractionExperiment.cpp @@ -1696,30 +1696,10 @@ DiffractionExperiment &DiffractionExperiment::RunNumber(uint64_t input) { return *this; } -PartialityModel DiffractionExperiment::GetPartialityModel() const { - if (const auto model = scaling_settings.GetPartialityModel()) - return model.value(); - if (GetGoniometer().has_value()) - return PartialityModel::Rotation; - return PartialityModel::Unity; -} - std::optional DiffractionExperiment::GetRotationWedgeForScaling() const { - // Only makes sense for rotation partiality model - // If ScalingSettings set one, it is takes priority - // otherwise if this is really rotation scan, than actual rotation wedge is given - // otherwise return std::nullopt - if (GetPartialityModel() != PartialityModel::Rotation) - return std::nullopt; - if (scaling_settings.GetRotationWedgeForScaling().has_value()) - return scaling_settings.GetRotationWedgeForScaling(); - if (GetGoniometer().has_value()) - return GetGoniometer()->GetWedge_deg(); - return std::nullopt; + return scaling_settings.GetRotationWedgeForScaling(); } bool DiffractionExperiment::GetRefineRotationWedgeInScaling() const { - if (GetRotationWedgeForScaling().has_value()) - return scaling_settings.GetRefineWedge(); - return false; + return scaling_settings.GetRotationWedgeForScaling().has_value() && scaling_settings.GetRefineWedge(); } diff --git a/common/DiffractionExperiment.h b/common/DiffractionExperiment.h index a542ad03..01ce9dd4 100644 --- a/common/DiffractionExperiment.h +++ b/common/DiffractionExperiment.h @@ -417,7 +417,6 @@ public: bool IsRotationIndexing() const; - PartialityModel GetPartialityModel() const; std::optional GetRotationWedgeForScaling() const; bool GetRefineRotationWedgeInScaling() const; }; diff --git a/common/ScalingSettings.cpp b/common/ScalingSettings.cpp index b22fa8f5..1cc0e88b 100644 --- a/common/ScalingSettings.cpp +++ b/common/ScalingSettings.cpp @@ -5,8 +5,8 @@ #include "ScalingSettings.h" -ScalingSettings& ScalingSettings::SetPartialityModel(PartialityModel mode) { - partiality_mode = mode; +ScalingSettings& ScalingSettings::ForceStillProcessing(bool input) { + force_still_processing = input; return *this; } @@ -51,8 +51,8 @@ bool ScalingSettings::GetRefineWedge() const { return refine_wedge; } -std::optional ScalingSettings::GetPartialityModel() const { - return partiality_mode; +bool ScalingSettings::GetForceStillProcessing() const { + return force_still_processing; } std::optional ScalingSettings::GetHighResolutionLimit_A() const { diff --git a/common/ScalingSettings.h b/common/ScalingSettings.h index 121dc4e1..183ca7c2 100644 --- a/common/ScalingSettings.h +++ b/common/ScalingSettings.h @@ -6,11 +6,12 @@ #include #include "JFJochException.h" -enum class PartialityModel { Fixed, Rotation, Unity }; enum class IntensityFormat { Text, mmCIF, MTZ}; class ScalingSettings { - std::optional partiality_mode; + // Force per-image stills scaling (ScaleOnTheFly, fixed partiality) even for a rotation dataset, + // instead of the rotation self-scale + 3D combine + merge (RotationScaleMerge). Off by default. + bool force_still_processing = false; bool refine_b = false; double max_b = 200.0; @@ -30,9 +31,9 @@ class ScalingSettings { double min_cc_for_image = 0.0; double outlier_reject_nsigma = 0.0; // per-observation merge outlier rejection (XDS/DIALS-style); 0 = off, e.g. 6 enables - // The "-P rot3d" 3D combine: scale per-frame as Rotation, then weight-sum each reflection's - // per-frame partials into one counting-limited full before merging (orthogonal to the partiality - // model, which stays Rotation - see Combine3D). + // Rotation processing (RotationScaleMerge): recompute partiality, weight-sum each reflection's + // per-frame partials into one counting-limited full, scale the fulls and merge. Set by the tool for + // rotation data unless force_still_processing. bool combine_3d = false; // Scale fulls: after the rot3d combine, refit a per-frame scale on the combined fulls (XDS @@ -55,7 +56,7 @@ class ScalingSettings { bool scaling_regularize = false; public: - ScalingSettings& SetPartialityModel(PartialityModel mode); + ScalingSettings& ForceStillProcessing(bool input); ScalingSettings& RefineB(bool input); ScalingSettings& RefineRotationWedge(bool input); ScalingSettings& RotationWedgeForScaling(std::optional input); @@ -92,7 +93,7 @@ public: [[nodiscard]] bool GetMergeFriedel() const; - [[nodiscard]] std::optional GetPartialityModel() const; + [[nodiscard]] bool GetForceStillProcessing() const; [[nodiscard]] std::optional GetHighResolutionLimit_A() const; [[nodiscard]] double GetMinPartiality() const; diff --git a/process/JFJochProcess.cpp b/process/JFJochProcess.cpp index c333b0d0..604d3198 100644 --- a/process/JFJochProcess.cpp +++ b/process/JFJochProcess.cpp @@ -440,8 +440,7 @@ ProcessResult JFJochProcess::Run(JFJochProcessObserver *observer) { // (forced) mosaicity is handled by the recompute. It does NOT support external-reference scaling, // B-factor refinement, an absorption surface, or wedge refinement - reject those combinations. const auto &rot_ss = experiment_.GetScalingSettings(); - const bool is_rotation = rot_ss.GetCombine3D() - && experiment_.GetPartialityModel() == PartialityModel::Rotation; + const bool is_rotation = rot_ss.GetCombine3D(); // set for rotation data unless force-still std::optional rsm; if (is_rotation) { if (!config_.reference_data.empty() || rot_ss.GetRefineB() || rot_ss.GetAbsorptionIter() > 0 diff --git a/process/JFJochProcessCommandLine.cpp b/process/JFJochProcessCommandLine.cpp index 847a4992..ac398eff 100644 --- a/process/JFJochProcessCommandLine.cpp +++ b/process/JFJochProcessCommandLine.cpp @@ -116,8 +116,8 @@ std::string JFJochProcessCommandLine(const ProcessConfig &config, if (config.run_scaling) { args.emplace_back("-M"); const auto sc = experiment.GetScalingSettings(); - if (const auto pm = sc.GetPartialityModel()) - add("-P", *pm == PartialityModel::Rotation ? "rot3d" : "fixed"); + if (sc.GetForceStillProcessing()) + args.emplace_back("--force-still-processing"); if (!sc.GetMergeFriedel()) args.emplace_back("-A"); if (sc.GetRefineB()) diff --git a/tools/jfjoch_process.cpp b/tools/jfjoch_process.cpp index 43c76ba9..d07f6c1a 100644 --- a/tools/jfjoch_process.cpp +++ b/tools/jfjoch_process.cpp @@ -63,7 +63,7 @@ void print_usage() { std::cout << " --no-scale-fulls Disable the rot3d scale-fulls refit (it is on by default for rot3d)" << std::endl; std::cout << " --write-process-h5 Also write the (large) _process.h5 when merging (default: only .mtz/.cif when merging)" << 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 Scaling/merge model fixed|rot3d (default: rot3d for rotation data, fixed for stills). rot3d = RotationScaleMerge (recompute partiality, 3D combine, scale-fulls, merge); fixed = per-image ScaleOnTheFly with the stored partiality" << std::endl; + std::cout << " --force-still-processing Scale a rotation dataset as independent stills (per-image ScaleOnTheFly, fixed partiality) instead of the default RotationScaleMerge (recompute partiality, 3D combine, scale-fulls, merge)" << 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 << " --scaling-high-resolution High resolution limit for spot finding (default: no limit)" << std::endl; @@ -117,6 +117,7 @@ enum { OPT_NO_SCALE_FULLS, OPT_WRITE_PROCESS_H5, OPT_PROCESS_AS_STILLS, + OPT_FORCE_STILL_PROCESSING, OPT_AZIM_MIN_Q, OPT_AZIM_MAX_Q, OPT_AZIM_PHI_BINS @@ -135,7 +136,7 @@ static option long_options[] = { {"reference-column", required_argument, nullptr, OPT_REFERENCE_COLUMN}, {"dump-observations", required_argument, nullptr, OPT_DUMP_OBSERVATIONS}, {"space-group", required_argument, nullptr, 'S'}, - {"partiality", required_argument, nullptr, 'P'}, + {"force-still-processing", no_argument, nullptr, OPT_FORCE_STILL_PROCESSING}, {"anomalous", no_argument, nullptr, 'A'}, {"refine-bfactor", no_argument, nullptr, 'B'}, {"scale-merge", no_argument, nullptr, 'M'}, @@ -339,8 +340,7 @@ int main(int argc, char **argv) { GeomRefinementAlgorithmEnum refinement_algorithm = GeomRefinementAlgorithmEnum::BeamCenter; std::optional 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 force_still_processing = false; // --force-still-processing: scale a rotation dataset as stills bool combine_3d = false; // -P rot3d: weight-sum per-frame partials into fulls before merging float d_min_spot_finding = 1.5; @@ -357,7 +357,7 @@ int main(int argc, char **argv) { int opt; int option_index = 0; - const char *short_opts = "vo:N:s:e:t:R::X:C:z:FABS:MP:r:q:"; + const char *short_opts = "vo:N:s:e:t:R::X:C:z:FABS:Mr:q:"; while ((opt = getopt_long(argc, argv, short_opts, long_options, &option_index)) != -1) { switch (opt) { @@ -501,19 +501,8 @@ int main(int argc, char **argv) { case 'S': space_group_number = atoi(optarg); break; - case 'P': - if (strcmp(optarg, "fixed") == 0) - partiality_model = PartialityModel::Fixed; - else if (strcmp(optarg, "rot3d") == 0) { - partiality_model = PartialityModel::Rotation; - combine_3d = true; - } - else { - logger.Error("Invalid partiality mode: {}", optarg); - print_usage(); - exit(EXIT_FAILURE); - } - partiality_explicit = true; + case OPT_FORCE_STILL_PROCESSING: + force_still_processing = true; break; case OPT_SPOT_SIGMA: sigma_spot_finding = atof(optarg); @@ -789,12 +778,10 @@ int main(int argc, char **argv) { "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 - } + // Rotation data goes through RotationScaleMerge (3D combine of the per-frame partials into fulls) + // unless --force-still-processing asks for per-image stills scaling instead. + if (rotation_indexing && !force_still_processing) + combine_3d = true; // Configure Indexing IndexingSettings indexing_settings; @@ -815,7 +802,7 @@ int main(int argc, char **argv) { const bool scale_fulls = scale_fulls_arg.value_or(combine_3d); ScalingSettings scaling_settings; - scaling_settings.SetPartialityModel(partiality_model); + scaling_settings.ForceStillProcessing(force_still_processing); scaling_settings.Combine3D(combine_3d); scaling_settings.ScaleFulls(scale_fulls); scaling_settings.SmoothGDegrees(smooth_g_deg_arg.value_or(combine_3d ? SMOOTH_G_DEFAULT_DEG : 0.0)); diff --git a/tools/jfjoch_scale.cpp b/tools/jfjoch_scale.cpp index 159c1662..73501796 100644 --- a/tools/jfjoch_scale.cpp +++ b/tools/jfjoch_scale.cpp @@ -107,7 +107,6 @@ int main(int argc, char **argv) { int64_t scaling_iter = 3; std::optional intensity_format; // --scaling-output override; default lives in ScalingSettings (mmCIF) - PartialityModel partiality_model = PartialityModel::Fixed; bool combine_3d = false; // -P rot3d: weight-sum per-frame partials into fulls before merging std::optional d_min_scale_merge; @@ -149,13 +148,11 @@ int main(int argc, char **argv) { break; case 'P': if (strcmp(optarg, "fixed") == 0) - partiality_model = PartialityModel::Fixed; - else if (strcmp(optarg, "rot3d") == 0) { - partiality_model = PartialityModel::Rotation; - combine_3d = true; - } + combine_3d = false; // stills: per-image ScaleOnTheFly + else if (strcmp(optarg, "rot3d") == 0) + combine_3d = true; // rotation: RotationScaleMerge else { - logger.Error("Invalid partiality mode: {}", optarg); + logger.Error("Invalid scaling model (use fixed|rot3d): {}", optarg); print_usage(); exit(EXIT_FAILURE); } @@ -304,7 +301,6 @@ int main(int argc, char **argv) { experiment.NumTriggers(1); ScalingSettings scaling_settings; - scaling_settings.SetPartialityModel(partiality_model); scaling_settings.Combine3D(combine_3d); if (d_min_scale_merge) scaling_settings.HighResolutionLimit_A(d_min_scale_merge.value()); @@ -337,7 +333,7 @@ int main(int argc, char **argv) { // Rotation (rot3d): the dedicated RotationScaleMerge does the whole self-scale -> 3D combine -> merge // (on the GPU when present). It does not support external-reference scaling, B-factor refinement or // wedge refinement. Everything else (stills, reference scaling) uses ScaleOnTheFly + MergeOnTheFly. - const bool is_rotation = combine_3d && experiment.GetPartialityModel() == PartialityModel::Rotation; + const bool is_rotation = combine_3d; // -P rot3d if (is_rotation) { if (!reference_data.empty() || experiment.GetScalingSettings().GetRefineB() || experiment.GetRefineRotationWedgeInScaling() diff --git a/viewer/widgets/JFJochViewerSettingsDock.cpp b/viewer/widgets/JFJochViewerSettingsDock.cpp index 3c8a5c9f..a52840fd 100644 --- a/viewer/widgets/JFJochViewerSettingsDock.cpp +++ b/viewer/widgets/JFJochViewerSettingsDock.cpp @@ -585,7 +585,7 @@ void JFJochViewerSettingsDock::ApplyProcessingMode() { const bool rotation_data = experiment_.GetGoniometer().has_value(); const bool rotation_mode = rotation_data && stills_ && !stills_->isChecked(); indexing_.RotationIndexing(rotation_mode); - scaling_.SetPartialityModel(rotation_mode ? PartialityModel::Rotation : PartialityModel::Fixed); + scaling_.ForceStillProcessing(!rotation_mode); scaling_.Combine3D(rotation_mode); scaling_.ScaleFulls(rotation_mode); EmitSpotFinding(); // carries indexing_ (incl. RotationIndexing) to the worker