From b6b83b2a22fa10349ba0b742cee60898cb60fb2d Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sat, 31 Oct 2026 07:48:00 +0100 Subject: [PATCH] Stills: --stills-window-sigmas sets the prediction window's half-width The stills prediction window was a fixed two run rocking widths. --stills-window-sigmas K predicts, integrates and (with --export-observations) exports the rocking curve's tails out to K widths, which is what a partiality model fitted from the data needs to see; --min-partiality decides separately what the merge takes. The default stays 2 and the processing is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQqN9qRD57vnShzTC37 --- rugnux/Rugnux.h | 5 ++++- rugnux/RugnuxImagePass.cpp | 6 +++--- rugnux/rugnux_cli.cpp | 8 ++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index 1ee456478..f18016ad7 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -268,6 +268,10 @@ struct ProcessConfig { // (ObservationMtzTable; --export-observations). Off by default; it does not change the processing. bool export_observations = false; + // Stills: the prediction window's half-width, in run rocking widths (--stills-window-sigmas). Wider + // predicts, integrates and exports more of the rocking curve's tails. + double stills_window_sigmas = 2.0; + // If set, after merging, validate the merged intensities against this atomic model: compute // R-free with an optimized bulk solvent and write 2Fo-Fc / Fo-Fc maps (--model). The caller parses // it (ParseRugnuxModel, writer/RugnuxModelFile.h). @@ -1612,7 +1616,6 @@ class Rugnux { // --export-observations: the written stills merge's verdict on every observation, and its resolution cut. std::vector> observation_verdicts_; std::optional observation_d_min_; - static constexpr double STILLS_WINDOW_SIGMAS = 2.0; // the window's half-width, in rocking widths static constexpr int STILLS_WINDOW_MIN_FRAMES = 2000; // the sample: a tenth of the run, at least this static constexpr int STILLS_WINDOW_FRACTION = 10; // Stills: where the frames' pixel values pile up, against the file's saturation limit (see there). diff --git a/rugnux/RugnuxImagePass.cpp b/rugnux/RugnuxImagePass.cpp index 92edbe15f..f9509795e 100644 --- a/rugnux/RugnuxImagePass.cpp +++ b/rugnux/RugnuxImagePass.cpp @@ -260,7 +260,7 @@ void Rugnux::ImagePass(PipelineLocals &p) { // predicted five to eight widths at low resolution, mostly empty, and cut the curve at one or two at // high resolution, so the partials there were never integrated. So the run's width is measured first, // on a sample of the frames integrated the old way (MeasureRunWidth, as the merge measures it), and - // every frame is then predicted through STILLS_WINDOW_SIGMAS of it. + // every frame is then predicted through config_.stills_window_sigmas of it. std::vector window_frames; const bool stills_window = full && per_image_analysis && !calibration && !geometry_prepass && indexer && !experiment_.IsRotationIndexing() && config_.spot_finding.indexing @@ -452,13 +452,13 @@ void Rugnux::ImagePass(PipelineLocals &p) { for (const int o: window_frames) outcomes[o] = IntegrationOutcome{}; if (stills_run_width_) { - const auto k = static_cast(STILLS_WINDOW_SIGMAS); + const auto k = static_cast(config_.stills_window_sigmas); indexer->SetStillsWindow(StillsWindow{.c0 = k * static_cast(stills_run_width_->gamma0), .ang = k * static_cast(stills_run_width_->gamma_e), .bw = k * static_cast(stills_run_width_->bandwidth_sigma), .q_scale = static_cast(stills_run_width_->radial_scale)}); Note(fmt::format("prediction window {:.1f} x the rocking width measured on {} frames, centred at " - "radial scale {:+.2e}", STILLS_WINDOW_SIGMAS, window_frames.size(), radial_scale - 1.0)); + "radial scale {:+.2e}", config_.stills_window_sigmas, window_frames.size(), radial_scale - 1.0)); } } for (int i = 0; i < image_workers; ++i) diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index 4af7c6ede..7cec8bc94 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -235,6 +235,7 @@ enum { OPT_NO_EXPORT_UNMERGED, OPT_EXPORT_UNMERGED_PARTIALS, OPT_EXPORT_OBSERVATIONS, + OPT_STILLS_WINDOW_SIGMAS, OPT_DUMP_OBSERVATIONS, OPT_INTEGRATOR, OPT_SIMPLE_STILLS, @@ -319,6 +320,7 @@ static option long_options[] = { {"no-export-unmerged", no_argument, nullptr, OPT_NO_EXPORT_UNMERGED}, {"export-unmerged-partials", no_argument, nullptr, OPT_EXPORT_UNMERGED_PARTIALS}, {"export-observations", no_argument, nullptr, OPT_EXPORT_OBSERVATIONS}, + {"stills-window-sigmas", required_argument, nullptr, OPT_STILLS_WINDOW_SIGMAS}, {"dump-observations", required_argument, nullptr, OPT_DUMP_OBSERVATIONS}, {"space-group", required_argument, nullptr, 'S'}, {"anomalous", no_argument, nullptr, 'A'}, @@ -914,6 +916,7 @@ struct RugnuxCli { bool export_unmerged = true; // on by default; --no-export-unmerged turns it off bool export_unmerged_partials = false; // --export-unmerged-partials: the same, one row per image bool export_observations = false; // --export-observations: _obs.mtz, stills diagnostic + std::optional stills_window_sigmas_arg; // --stills-window-sigmas std::string dump_observations; // diagnostic: dump unmerged -P rot3d fulls to this path std::optional min_partiality_arg; // --min-partiality; default per mode (RugnuxDefaultScalingSettings) std::optional min_captured_fraction_arg; // explicit --min-captured-fraction; default depends on rotation @@ -1267,6 +1270,9 @@ int RugnuxCli::Parse(int argc, char **argv) { case OPT_EXPORT_OBSERVATIONS: export_observations = true; break; + case OPT_STILLS_WINDOW_SIGMAS: + stills_window_sigmas_arg = parse_double_arg(optarg, "--stills-window-sigmas", logger); + break; case OPT_DUMP_OBSERVATIONS: dump_observations = optarg; break; @@ -3612,6 +3618,8 @@ int RugnuxInputRun::PrepareRugnuxInput() { }; config.export_unmerged = export_unmerged; config.export_observations = export_observations; + if (stills_window_sigmas_arg) + config.stills_window_sigmas = *stills_window_sigmas_arg; config.export_unmerged_partials = export_unmerged_partials; config.model = model; config.force_mr = !mr_model.empty();