diff --git a/common/IndexingSettings.cpp b/common/IndexingSettings.cpp index 1400f85c..122bd0e8 100644 --- a/common/IndexingSettings.cpp +++ b/common/IndexingSettings.cpp @@ -28,11 +28,6 @@ int64_t IndexingSettings::GetViableCellMinSpots() const { return viable_cell_min_spots; } -IndexingSettings &IndexingSettings::MinIndexedSpotFraction(float input) { - min_indexed_spot_fraction = input; - return *this; -} - float IndexingSettings::GetMinIndexedSpotFraction() const { return min_indexed_spot_fraction; } diff --git a/common/IndexingSettings.h b/common/IndexingSettings.h index 2872fc6d..7c8ee11d 100644 --- a/common/IndexingSettings.h +++ b/common/IndexingSettings.h @@ -43,7 +43,6 @@ public: IndexingSettings(); IndexingSettings& ViableCellMinSpots(int64_t input); - IndexingSettings& MinIndexedSpotFraction(float input); IndexingSettings& Algorithm(IndexingAlgorithmEnum input); IndexingSettings& FFT_MaxUnitCell_A(float input); IndexingSettings& FFT_MinUnitCell_A(float input); diff --git a/common/ScalingSettings.cpp b/common/ScalingSettings.cpp index 08ff80c6..fe7a8952 100644 --- a/common/ScalingSettings.cpp +++ b/common/ScalingSettings.cpp @@ -151,15 +151,6 @@ bool ScalingSettings::GetCorrectionSurfaces() const { return correction_surfaces; } -ScalingSettings &ScalingSettings::StillsModulation(bool input) { - stills_modulation = input; - return *this; -} - -bool ScalingSettings::GetStillsModulation() const { - return stills_modulation; -} - ScalingSettings &ScalingSettings::StillsPartialityRefine(bool input) { stills_partiality_refine = input; return *this; @@ -229,15 +220,6 @@ double ScalingSettings::GetCaptureUncertaintyCoeff() const { return capture_uncertainty_coeff; } -ScalingSettings &ScalingSettings::PartialityUncertaintyCoeff(double input) { - partiality_uncertainty_coeff = input; - return *this; -} - -double ScalingSettings::GetPartialityUncertaintyCoeff() const { - return partiality_uncertainty_coeff; -} - ScalingSettings &ScalingSettings::MinCapturedFraction(double input) { if (input < 0.0) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, diff --git a/common/ScalingSettings.h b/common/ScalingSettings.h index f215709f..b372e00b 100644 --- a/common/ScalingSettings.h +++ b/common/ScalingSettings.h @@ -27,7 +27,6 @@ class ScalingSettings { // a fraction f<1 of its rocking curve is extrapolated, and the unobserved (1-f) carries a // systematic error ~coeff*(1-f)*I that plain counting sigma misses. 0 = off (baseline). double capture_uncertainty_coeff = 0.0; - double partiality_uncertainty_coeff = 0.0; // Full-level captured-fraction floor for the rot3d combine: drop a reconstructed full whose rocking // curve was only fractionally captured (sum of its partials' partiality < this). Distinct from // min_partiality, which gates individual partials; this gates the assembled full. 0 = off (baseline). @@ -46,10 +45,6 @@ class ScalingSettings { bool correction_surfaces = true; // Absorption-surface refinement iteration count (used when correction_surfaces is on). int absorption_iter = 3; - // Detector-plane modulation (flat-field) correction for the STILLS merge path (ScaleOnTheFly + - // MergeOnTheFly), which otherwise has no correction surfaces. Off by default (experimental; the - // rotation path fits its own modulation via RotationScaleMerge). Enabled by rugnux --stills-modulation. - bool stills_modulation = false; // Physical partiality post-refinement for the STILLS merge (StillsPartialityRefine): refine a per-crystal // orientation tilt against the running merge, recompute each reflection's partiality from the refined @@ -100,14 +95,12 @@ public: ScalingSettings& MinPartiality(double min_partiality); ScalingSettings& ForcedMosaicity(std::optional input); ScalingSettings& CaptureUncertaintyCoeff(double input); - ScalingSettings& PartialityUncertaintyCoeff(double input); ScalingSettings& MinCapturedFraction(double input); ScalingSettings& MinCCForImage(double min_cc_for_image); ScalingSettings& OutlierRejectNsigma(double input); ScalingSettings& ScaleFulls(bool input); ScalingSettings& AbsorptionIter(int input); ScalingSettings& CorrectionSurfaces(bool input); - ScalingSettings& StillsModulation(bool input); ScalingSettings& StillsPartialityRefine(bool input); ScalingSettings& ExpectedVarianceMerge(bool input); ScalingSettings& SmoothGDegrees(double input); @@ -141,14 +134,12 @@ public: [[nodiscard]] double GetMinPartiality() const; [[nodiscard]] std::optional GetForcedMosaicity() const; [[nodiscard]] double GetCaptureUncertaintyCoeff() const; - [[nodiscard]] double GetPartialityUncertaintyCoeff() const; [[nodiscard]] double GetMinCapturedFraction() const; [[nodiscard]] double GetMinCCForImage() const; [[nodiscard]] double GetOutlierRejectNsigma() const; [[nodiscard]] bool GetScaleFulls() const; [[nodiscard]] int GetAbsorptionIter() const; [[nodiscard]] bool GetCorrectionSurfaces() const; - [[nodiscard]] bool GetStillsModulation() const; [[nodiscard]] bool GetStillsPartialityRefine() const; [[nodiscard]] bool GetExpectedVarianceMerge() const; [[nodiscard]] double GetSmoothGDegrees() const; diff --git a/image_analysis/scale_merge/Merge.cpp b/image_analysis/scale_merge/Merge.cpp index 82f91431..fea7100b 100644 --- a/image_analysis/scale_merge/Merge.cpp +++ b/image_analysis/scale_merge/Merge.cpp @@ -93,7 +93,7 @@ void MergeOnTheFly::AddImage(const IntegrationOutcome &outcome, int64_t image_id continue; auto hkl = generator(r); auto hkl_key = hkl.pack(); - sigma_corr = CorrectedSigma(I_corr, sigma_corr, r.image_scale_corr, hkl_key, r.partiality); + sigma_corr = CorrectedSigma(I_corr, sigma_corr, r.image_scale_corr, hkl_key); // Robust outlier rejection: drop this observation if it sits more than // reject_nsigma error-model sigmas from the reflection's median. Needs the active @@ -131,139 +131,8 @@ void MergeOnTheFly::AddImage(const IntegrationOutcome &outcome, int64_t image_id } } -double MergeOnTheFly::RefineModulation(std::vector &outcomes) { - // A minimum held-out generalizing gain (fraction of the held-out scatter) before the surface is - // applied - a margin, so a noise-level "improvement" never engages the correction. Matches the - // rotation ApplyCellSurface gate. - constexpr double CV_MIN_RELATIVE_GAIN = 0.02; - constexpr int NB = 16; - const int ncell = NB * NB; - - // One scaled observation reduced to what the surface fit needs. `parity` (image index & 1) drives the - // even/odd cross-validation split - the stills analogue of the rotation frame parity. - struct MObs { double I, sigma, corr; float px, py; int32_t group; int parity; int cell; }; - std::vector obs; - - // Accept exactly what AddImage merges (systematic absence, scale/resolution/ice/partiality filters), - // and additionally require a finite detector position. Returns the dense ASU-group id or -1. - std::unordered_map group_of; - auto accept = [&](const Reflection &r, MObs &out) -> bool { - if (generator.IsSystematicallyAbsent(r)) return false; - if (r.image_scale_corr <= 0.0f || !std::isfinite(r.image_scale_corr)) return false; - if (!AcceptReflection(r, high_resolution_limit)) return false; - if (exclude_ice_rings && r.on_ice_ring) return false; - if (IsMaskedRing(r)) return false; - if (r.partiality < min_partiality) return false; - if (!std::isfinite(r.predicted_x) || !std::isfinite(r.predicted_y)) return false; - const float I_corr = r.I * r.image_scale_corr, sigma_corr = r.sigma * r.image_scale_corr; - if (!std::isfinite(I_corr) || !std::isfinite(sigma_corr) || sigma_corr <= 0.0f) return false; - const uint64_t key = generator(r).pack(); - auto [it, inserted] = group_of.try_emplace(key, static_cast(group_of.size())); - out.I = r.I; out.sigma = r.sigma; out.corr = r.image_scale_corr; - out.px = r.predicted_x; out.py = r.predicted_y; out.group = it->second; - return true; - }; - - float pxmin = std::numeric_limits::infinity(), pxmax = -pxmin, pymin = pxmin, pymax = -pxmin; - for (size_t i = 0; i < outcomes.size(); ++i) { - const int parity = static_cast(i & 1); - for (const auto &r : outcomes[i].reflections) { - MObs m{}; - if (!accept(r, m)) continue; - m.parity = parity; - pxmin = std::min(pxmin, m.px); pxmax = std::max(pxmax, m.px); - pymin = std::min(pymin, m.py); pymax = std::max(pymax, m.py); - obs.push_back(m); - } - } - const int n_groups = static_cast(group_of.size()); - if (obs.size() < static_cast(8 * ncell) || !(pxmax > pxmin) || !(pymax > pymin)) - return 0.0; // too sparse to over-determine a 16x16 surface, or degenerate detector footprint - - const float sx = NB / (pxmax - pxmin), sy = NB / (pymax - pymin); - for (auto &m : obs) { - const int ix = std::clamp(static_cast((m.px - pxmin) * sx), 0, NB - 1); - const int iy = std::clamp(static_cast((m.py - pymin) * sy), 0, NB - 1); - m.cell = ix * NB + iy; - } - - // Fit the per-cell factor over {parity subset} (parity < 0 = all obs), n_iter alternating rounds against - // that subset's own inverse-variance reference: Tikhonov pull to 1, gauge-fixed to a den-weighted - // geometric mean of 1 so it never drifts the overall scale. Mirrors RotationScaleMerge::ApplyCellSurface. - constexpr int N_ITER = 3; - auto fit_surface = [&](int parity) -> std::vector { - std::vector A(ncell, 1.0); - for (int it = 0; it < N_ITER; ++it) { - std::vector sw(n_groups, 0.0), swI(n_groups, 0.0); - for (const auto &o : obs) { - if (parity >= 0 && o.parity != parity) continue; - const double a = A[o.cell], sc = o.sigma * o.corr * a, w = 1.0 / (sc * sc); - sw[o.group] += w; swI[o.group] += w * o.I * o.corr * a; - } - std::vector num(ncell, 0.0), den(ncell, 0.0); - for (const auto &o : obs) { - if ((parity >= 0 && o.parity != parity) || sw[o.group] <= 0.0) continue; - const double Iref = swI[o.group] / sw[o.group], a = A[o.cell]; - const double Is = o.I * o.corr * a, sc = o.sigma * o.corr * a; - if (!std::isfinite(Iref) || Iref <= 0.0 || !(Is > 0.0) || !(sc > 0.0)) continue; - const double w = 1.0 / (sc * sc); - num[o.cell] += w * Is * Iref; den[o.cell] += w * Is * Is; - } - std::vector dsorted = den; - std::nth_element(dsorted.begin(), dsorted.begin() + dsorted.size() / 2, dsorted.end()); - const double lambda = 0.1 * std::max(1e-30, dsorted[dsorted.size() / 2]); - double logsum = 0.0, wsum = 0.0; - std::vector upd(ncell, 1.0); - for (int c = 0; c < ncell; ++c) upd[c] = (num[c] + lambda) / (den[c] + lambda); - for (int c = 0; c < ncell; ++c) if (den[c] > 0.0) { logsum += den[c] * std::log(upd[c]); wsum += den[c]; } - const double gm = wsum > 0.0 ? std::exp(logsum / wsum) : 1.0; - for (int c = 0; c < ncell; ++c) A[c] = std::clamp(A[c] * upd[c] / gm, 0.25, 4.0); - } - return A; - }; - // Sigma-independent (R-meas-like) agreement of the held-out equivalents: sum|Is - Iref| / sum|Iref|. - // A fractional metric cannot be gamed by a surface that merely reshapes sigma via corr. - auto score = [&](int parity, const std::vector &A) -> double { - std::vector sw(n_groups, 0.0), swI(n_groups, 0.0); - for (const auto &o : obs) { - if (o.parity != parity) continue; - const double a = A[o.cell], Is = o.I * o.corr * a, sc = o.sigma * o.corr * a, w = 1.0 / (sc * sc); - sw[o.group] += w; swI[o.group] += w * Is; - } - double num = 0.0, den = 0.0; - for (const auto &o : obs) { - if (o.parity != parity || sw[o.group] <= 0.0) continue; - const double a = A[o.cell], Is = o.I * o.corr * a, Iref = swI[o.group] / sw[o.group]; - if (!std::isfinite(Iref) || Iref <= 0.0) continue; - num += std::abs(Is - Iref); den += Iref; - } - return den > 0.0 ? num / den : 0.0; - }; - - // Cross-validate: fit on even images, score the held-out odd equivalents (and vice versa). Apply the - // full-data surface only if the held-out agreement improves by a clear margin. - const std::vector ident(ncell, 1.0); - const std::vector A_even = fit_surface(0), A_odd = fit_surface(1); - const double base = score(1, ident) + score(0, ident); - const double gain = base - (score(1, A_even) + score(0, A_odd)); - if (!(gain > CV_MIN_RELATIVE_GAIN * base)) - return 0.0; // not cross-validated: the correction stays a no-op (caller logs) - const std::vector A = fit_surface(-1); - - // Fold the surface into each accepted reflection's image_scale_corr (recompute its cell deterministically). - for (auto &outcome : outcomes) - for (auto &r : outcome.reflections) { - MObs m{}; - if (!accept(r, m)) continue; - const int ix = std::clamp(static_cast((m.px - pxmin) * sx), 0, NB - 1); - const int iy = std::clamp(static_cast((m.py - pymin) * sy), 0, NB - 1); - r.image_scale_corr = static_cast(r.image_scale_corr * A[ix * NB + iy]); - } - return gain / std::max(base, 1e-30); // held-out gain fraction (caller logs) -} - float MergeOnTheFly::CorrectedSigma(float I_corr, float sigma_corr, float image_scale_corr, - uint64_t hkl_key, float partiality) const { + uint64_t hkl_key) const { if (!error_model_active) return sigma_corr; @@ -288,21 +157,8 @@ float MergeOnTheFly::CorrectedSigma(float I_corr, float sigma_corr, float image_ a_var = base; } - double v = error_model_a * a_var - + (error_model_b * I_for_b) * (error_model_b * I_for_b); - - // Partiality-model uncertainty: a reflection recorded at fraction p carries a systematic intensity - // error ~ (dp/p) that is proportional to and grows as p falls - plain counting sigma misses it, - // so strong low-p partials would otherwise be over-trusted. This is the stills-partiality analog of - // the rotation --capture-uncertainty term ((1-captured_fraction)*I in RotationScaleMerge). Inert when - // partiality == 1 (no stills partiality model). Gated on a real systematic (error_model_b > 1, i.e. - // ISa < 1): on weak counting-limited data (small b) it would only over-concentrate the merge and hurt. - const double c = scaling_settings.GetPartialityUncertaintyCoeff(); - if (c > 0.0 && error_model_b > 1.0) { - const double one_minus_p = std::max(0.0, std::min(1.0, 1.0 - static_cast(partiality))); - const double t = c * I_for_b * one_minus_p; - v += t * t; - } + const double v = error_model_a * a_var + + (error_model_b * I_for_b) * (error_model_b * I_for_b); return (v > 0.0) ? static_cast(std::sqrt(v)) : sigma_corr; } diff --git a/image_analysis/scale_merge/Merge.h b/image_analysis/scale_merge/Merge.h index 5b9dc093..cd19b7bd 100644 --- a/image_analysis/scale_merge/Merge.h +++ b/image_analysis/scale_merge/Merge.h @@ -130,7 +130,7 @@ class MergeOnTheFly { // using the per-observation I_i instead would over-weight down-fluctuated points. std::unordered_map error_model_mean_I; [[nodiscard]] float CorrectedSigma(float I_corr, float sigma_corr, float image_scale_corr, - uint64_t hkl_key, float partiality) const; + uint64_t hkl_key) const; // Optional per-observation outlier rejection: drop observations whose corrected // intensity lies more than reject_nsigma error-model sigmas from the reflection's @@ -168,16 +168,6 @@ public: // order (or threading) of AddImage calls - not a draw from a shared RNG in call order. void AddImage(const IntegrationOutcome& outcome, int64_t image_id, bool cc_mask = false); - // Detector-plane modulation (flat-field) correction for the STILLS path: fit a smooth multiplicative - // factor over where a reflection lands on the detector (predicted x,y) against the merged reference and - // fold it into each reflection's image_scale_corr, so the following AddImage merge (and the error model) - // see the corrected scale. A 16x16 grid, cross-validated (fit even images, score the held-out odd - // equivalents by a sigma-independent R-meas-like metric) so it is a no-op when the systematic is absent - // or the data too sparse. The stills analogue of RotationScaleMerge::RefineModulation - the stills - // ScaleOnTheFly/MergeOnTheFly path otherwise has no correction surfaces. Mutates `outcomes`; call after - // scaling and before RefineErrorModel/AddImage. Returns the applied held-out gain fraction (0 = no-op). - double RefineModulation(std::vector &outcomes); - // d_min_override, when set, is the effective high-resolution limit for the shell table (used for // the automatic resolution cutoff computed by the caller); otherwise the manual // ScalingSettings high-resolution limit stands. The number of shells is ScalingSettings::ReportShellCount. diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 3d4c4bce..9dacc5d9 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -991,17 +991,6 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b merge_engine.MaskIceRings(masked_ice_rings, config_.spot_finding.ice_ring_width_Q_recipA); if (result.consensus_cell.has_value()) merge_engine.ReferenceCell(*result.consensus_cell); - // Optional detector-plane modulation (flat-field) correction for stills, folded into each - // reflection's scale before the error model and merge (never for the P1 search pass). The - // rotation path fits its own modulation inside RotationScaleMerge. - if (experiment_.GetScalingSettings().GetStillsModulation() && !for_search) { - const double mod_gain = merge_engine.RefineModulation(indexer->GetIntegrationOutcome()); - if (mod_gain > 0.0) - logger.Info("Stills modulation: detector-frame 16x16 surface applied " - "(cross-validated, held-out gain {:.1f}%)", 100.0 * mod_gain); - else - logger.Info("Stills modulation: no cross-validated gain (skipped)"); - } merge_engine.RefineErrorModel(merge_input); if (merge_engine.ErrorModelActive()) logger.Info("Error model: a={:.3f} b={:.3f} ISa={:.1f} chi2={:.2f}", merge_engine.ErrorModelA(), diff --git a/rugnux/RugnuxCommandLine.cpp b/rugnux/RugnuxCommandLine.cpp index 0b6b6bfa..e2fdab15 100644 --- a/rugnux/RugnuxCommandLine.cpp +++ b/rugnux/RugnuxCommandLine.cpp @@ -149,10 +149,6 @@ std::string RugnuxCommandLine(const ProcessConfig &config, args.emplace_back("-A"); if (sc.GetRefineB()) args.emplace_back("-B"); - if (sc.GetPartialityUncertaintyCoeff() > 0.0) - add("--partiality-uncertainty", num(sc.GetPartialityUncertaintyCoeff())); - if (sc.GetStillsModulation()) - args.emplace_back("--stills-modulation"); if (!sc.GetStillsPartialityRefine()) args.emplace_back("--simple-stills"); if (!sc.GetExpectedVarianceMerge()) diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index 94f66566..17eff6e5 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -67,7 +67,6 @@ void print_usage() { std::cout << " --min-pix-per-spot Minimum connected strong pixels per spot (default: 2; serial data can index better with 1 + a higher --spot-threshold)" << std::endl; std::cout << " --adaptive-spots Self-calibrating detection: replace the fixed --spot-threshold with a per-resolution-ring threshold set from each image's own noise, so one setting adapts across datasets (no per-dataset --spot-threshold/--spot-sigma tuning)" << std::endl; std::cout << " --spot-false-pixels Adaptive detection operating point: expected noise pixels tolerated per frame (default: 100; implies --adaptive-spots)" << std::endl; - std::cout << " --min-indexed-fraction Minimum fraction of in-resolution spots a lattice must index to be accepted (default 0.20); lower to admit weaker/sparser crystals (pair with --min-image-cc to gate the extra spurious ones)" << std::endl; std::cout << " --spot-high-resolution High resolution limit for spot finding (default: 1.5)" << std::endl; std::cout << " --spot-low-resolution Low resolution limit for spot finding, in A (default: 50; lower it, e.g. 24, to exclude the direct-beam halo on weakly-diffracting serial data)" << std::endl; std::cout << " --max-spots Max spot count (default: 250)" << std::endl; @@ -97,7 +96,6 @@ void print_usage() { 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 << " --relative-b[=deg] rot3d: fit a per-batch relative-B (beyond the single decay slope) over deg-degree batches; cross-validated (default: 10 deg when bare; off otherwise)" << std::endl; std::cout << " --no-scaling-corrections rot3d: disable the (default-on) decay + absorption correction surfaces fitted on the fulls after scale-fulls" << std::endl; - std::cout << " --stills-modulation stills: fit a detector-plane modulation (flat-field) surface over the merged reflections (cross-validated; experimental, default off)" << std::endl; std::cout << " --no-expected-variance-merge stills: disable the default expected-variance merge weighting (which rebuilds each weak observation's signal variance at the reflection mean to de-bias the inverse-variance merge); restores observed-sigma weighting" << 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 (stills only)" << std::endl; @@ -107,7 +105,6 @@ void print_usage() { std::cout << " --resolution-shells Number of resolution shells in the reported statistics table (default: 10)" << std::endl; std::cout << " --min-partiality Minimum partiality to accept reflection (default: 0.02)" << std::endl; std::cout << " --capture-uncertainty rot3d: systematic sigma ~num*(1-captured_fraction)*I on under-captured fulls (default: 1.0 for rot3d, 0 otherwise)" << std::endl; - std::cout << " --partiality-uncertainty stills: extra merge sigma ~num*(1-partiality)* on partials (auto-gated to error-model b>1 / ISa<1; default 0, ~2.5 recommended)" << std::endl; std::cout << " --min-captured-fraction rot3d: drop a combined full whose rocking curve was captured below this fraction (edge-of-sweep truncated fulls) (default: 0.7 for rotation, 0 otherwise; 0 = off)" << std::endl; std::cout << " --mosaicity Diagnostic: fix the scaling mosaicity (deg) instead of the per-image seed" << std::endl; std::cout << " --reject-outliers Per-observation merge outlier rejection, N sigma from the per-reflection median (default: 6 for rot3d, XDS/DIALS-style; 0 = off)" << std::endl; @@ -148,7 +145,6 @@ enum { OPT_MIN_PIX_PER_SPOT, OPT_ADAPTIVE_SPOTS, OPT_SPOT_FALSE_PIXELS, - OPT_MIN_INDEXED_FRACTION, OPT_SPOT_RESOLUTION, OPT_SPOT_LOW_RESOLUTION, OPT_MAX_SPOTS, @@ -175,13 +171,11 @@ enum { OPT_SIMPLE_STILLS, OPT_SCALE_FULLS, OPT_CAPTURE_UNCERTAINTY, - OPT_PARTIALITY_UNCERTAINTY, OPT_MIN_CAPTURED_FRACTION, OPT_MOSAICITY, OPT_SMOOTH_G, OPT_RELATIVE_B, OPT_NO_SCALING_CORRECTIONS, - OPT_STILLS_MODULATION, OPT_NO_EXPECTED_VARIANCE_MERGE, OPT_DETECT_ICE_RINGS, OPT_NO_SCALE_FULLS, @@ -229,7 +223,6 @@ static option long_options[] = { {"smooth-g", optional_argument, nullptr, OPT_SMOOTH_G}, {"relative-b", optional_argument, nullptr, OPT_RELATIVE_B}, {"no-scaling-corrections", no_argument, nullptr, OPT_NO_SCALING_CORRECTIONS}, - {"stills-modulation", no_argument, nullptr, OPT_STILLS_MODULATION}, {"no-expected-variance-merge", no_argument, nullptr, OPT_NO_EXPECTED_VARIANCE_MERGE}, {"refine", required_argument, nullptr, 'r'}, @@ -260,13 +253,11 @@ static option long_options[] = { {"min-pix-per-spot", required_argument, nullptr, OPT_MIN_PIX_PER_SPOT}, {"adaptive-spots", no_argument, nullptr, OPT_ADAPTIVE_SPOTS}, {"spot-false-pixels", required_argument, nullptr, OPT_SPOT_FALSE_PIXELS}, - {"min-indexed-fraction", required_argument, nullptr, OPT_MIN_INDEXED_FRACTION}, {"spot-high-resolution", required_argument, nullptr, OPT_SPOT_RESOLUTION}, {"spot-low-resolution", required_argument, nullptr, OPT_SPOT_LOW_RESOLUTION}, {"max-spots", required_argument, nullptr, OPT_MAX_SPOTS}, {"min-partiality", required_argument, nullptr, OPT_MIN_PARTIALITY}, {"capture-uncertainty", required_argument, nullptr, OPT_CAPTURE_UNCERTAINTY}, - {"partiality-uncertainty", required_argument, nullptr, OPT_PARTIALITY_UNCERTAINTY}, {"min-captured-fraction", required_argument, nullptr, OPT_MIN_CAPTURED_FRACTION}, {"mosaicity", required_argument, nullptr, OPT_MOSAICITY}, {"min-image-cc", required_argument, nullptr, OPT_MIN_IMAGE_CC}, @@ -515,7 +506,6 @@ int main(int argc, char **argv) { std::optional smooth_g_deg_arg; // --smooth-g[=deg]; default 5 deg for rot3d, 0 (off) otherwise std::optional relative_b_deg_arg; // --relative-b[=deg]; per-batch relative-B width, 0 (off) unless given bool no_scaling_corrections = false; // --no-scaling-corrections: disable rot3d decay+absorption surfaces - bool stills_modulation_flag = false; // --stills-modulation: detector-plane flat-field surface for stills bool no_expected_variance_merge = false; // --no-expected-variance-merge: restore observed-sigma stills merge weighting bool anomalous_mode = false; std::optional space_group_number; @@ -526,7 +516,6 @@ int main(int argc, char **argv) { int64_t min_pix_per_spot = 2; bool adaptive_spots = false; float false_pixels_per_frame = 100.0f; - std::optional min_indexed_fraction; bool refine_bfactor = false; std::string ref_mtz; std::string ref_column; @@ -535,7 +524,6 @@ int main(int argc, char **argv) { double min_partiality = 0.02; std::optional min_captured_fraction_arg; // explicit --min-captured-fraction; default depends on rotation std::optional capture_uncertainty_arg; // explicit --capture-uncertainty; default depends on rot3d - std::optional partiality_uncertainty_arg; // --partiality-uncertainty (stills partiality merge sigma) std::optional forced_mosaicity_arg; // diagnostic: fix the scaling mosaicity (deg) instead of the per-image seed double min_image_cc = 0.0; int64_t scaling_iter = 3; @@ -765,10 +753,6 @@ int main(int argc, char **argv) { adaptive_spots = true; logger.Info("Adaptive spot detection: expected false pixels/frame set to {:.0f}", false_pixels_per_frame); break; - case OPT_MIN_INDEXED_FRACTION: - min_indexed_fraction = parse_number_arg(optarg, "--min-indexed-fraction", logger, 0.0f); - logger.Info("Minimum indexed-spot fraction for acceptance set to {:.2f}", min_indexed_fraction.value()); - break; case OPT_SPOT_LOW_RESOLUTION: d_max_spot_finding = parse_number_arg(optarg, "--spot-low-resolution", logger, 0.0f); logger.Info("Low resolution limit for spot finding set to {:.1f} A", d_max_spot_finding); @@ -815,9 +799,6 @@ int main(int argc, char **argv) { case OPT_RELATIVE_B: relative_b_deg_arg = optarg ? parse_double_arg(optarg, "--relative-b", logger) : RELATIVE_B_DEFAULT_DEG; break; - case OPT_STILLS_MODULATION: - stills_modulation_flag = true; - break; case OPT_NO_EXPECTED_VARIANCE_MERGE: no_expected_variance_merge = true; break; @@ -830,9 +811,6 @@ int main(int argc, char **argv) { case OPT_CAPTURE_UNCERTAINTY: capture_uncertainty_arg = parse_double_arg(optarg, "--capture-uncertainty", logger); break; - case OPT_PARTIALITY_UNCERTAINTY: - partiality_uncertainty_arg = parse_double_arg(optarg, "--partiality-uncertainty", logger); - break; case OPT_MIN_CAPTURED_FRACTION: min_captured_fraction_arg = parse_double_arg(optarg, "--min-captured-fraction", logger); break; @@ -1099,7 +1077,6 @@ int main(int argc, char **argv) { scaling_settings.MinCapturedFraction(min_captured_fraction_arg.value_or( (experiment.GetGoniometer().has_value() && !force_still) ? 0.7 : 0.0)); scaling_settings.MinCCForImage(min_image_cc / 100.0); // --min-image-cc is percent; the setting is a fraction - scaling_settings.StillsModulation(stills_modulation_flag); scaling_settings.StillsPartialityRefine(!simple_stills_flag); scaling_settings.ExpectedVarianceMerge(!no_expected_variance_merge); scaling_settings.OutlierRejectNsigma( @@ -1167,16 +1144,6 @@ int main(int argc, char **argv) { } MergeOnTheFly merge_engine(experiment); merge_engine.ReferenceCell(experiment.GetUnitCell()); - // Optional detector-plane modulation (flat-field) correction, folded into each reflection's - // scale before the error model + merge (mirrors the full-analysis stills path in Rugnux.cpp). - if (experiment.GetScalingSettings().GetStillsModulation()) { - const double mod_gain = merge_engine.RefineModulation(reflections); - if (mod_gain > 0.0) - logger.Info("Stills modulation: detector-frame 16x16 surface applied " - "(cross-validated, held-out gain {:.1f}%)", 100.0 * mod_gain); - else - logger.Info("Stills modulation: no cross-validated gain (skipped)"); - } // Fit the (a, b) error model from symmetry-mate scatter before merging, exactly as the full // pipeline does (Rugnux.cpp). Without this the offline --scale merge would use the identity // model and produce much worse stills intensities (no (b*I)^2 systematic term, no sigma floor). @@ -1404,8 +1371,6 @@ int main(int argc, char **argv) { if (rotation_indexing_range.has_value()) indexing_settings.RotationIndexingMinAngularRange_deg(rotation_indexing_range.value()); indexing_settings.GeomRefinementAlgorithm(refinement_algorithm); - if (min_indexed_fraction.has_value()) - indexing_settings.MinIndexedSpotFraction(min_indexed_fraction.value()); experiment.ImportIndexingSettings(indexing_settings); // --detect-ice-rings[=on|off] overrides the value carried in from the dataset (HDF5MetadataSource @@ -1423,7 +1388,6 @@ int main(int argc, char **argv) { scaling_settings.RelativeBDegrees(relative_b_deg_arg.value_or(0.0)); // opt-in only; default off if (no_scaling_corrections) scaling_settings.CorrectionSurfaces(false); - scaling_settings.StillsModulation(stills_modulation_flag); scaling_settings.StillsPartialityRefine(!simple_stills_flag); scaling_settings.ExpectedVarianceMerge(!no_expected_variance_merge); if (d_min_scale_merge) @@ -1446,7 +1410,6 @@ int main(int argc, char **argv) { // over-extrapolated under-captured fulls and, with the mosaicity fix, lifts rotation ISa/anomalous // substantially. Off for non-rot3d (no combine). An explicit --capture-uncertainty always wins. scaling_settings.CaptureUncertaintyCoeff(capture_uncertainty_arg.value_or(rotation_indexing ? 1.0 : 0.0)); - scaling_settings.PartialityUncertaintyCoeff(partiality_uncertainty_arg.value_or(0.0)); scaling_settings.ForcedMosaicity(forced_mosaicity_arg); scaling_settings.MinCCForImage(min_image_cc / 100.0); // --min-image-cc is in percent; the setting is a fraction scaling_settings.OutlierRejectNsigma( diff --git a/viewer/widgets/JFJochViewerSettingsDock.cpp b/viewer/widgets/JFJochViewerSettingsDock.cpp index fea318b9..ce86c51a 100644 --- a/viewer/widgets/JFJochViewerSettingsDock.cpp +++ b/viewer/widgets/JFJochViewerSettingsDock.cpp @@ -499,11 +499,6 @@ QWidget *JFJochViewerSettingsDock::BuildScalingSection() { corrections->setToolTip("Rotation only: fit a radiation-damage decay and a goniometer-frame absorption " "surface on the fulls. Cross-validated, so they no-op when their systematic is " "absent. On by default."); - auto *modulation = new QCheckBox("Detector-plane modulation (stills)", this); - modulation->setChecked(scaling_.GetStillsModulation()); - modulation->setToolTip("Stills: fit a detector-plane modulation (flat-field) surface over where each " - "reflection lands, cross-validated so it no-ops when the systematic is absent. " - "For rotation, modulation is part of \"Correction surfaces\" above."); auto *partRefine = new QCheckBox("Partiality post-refinement (stills)", this); partRefine->setChecked(scaling_.GetStillsPartialityRefine()); partRefine->setToolTip("Stills: refine a per-crystal orientation tilt against the running merge and " @@ -515,23 +510,9 @@ QWidget *JFJochViewerSettingsDock::BuildScalingSection() { auto *highRes = new NumberLineEdit(0.3f, 5.0f, scaling_.GetHighResolutionLimit_A().value_or(2.0), 1, "Å", this); highRes->setEnabled(limitRes->isChecked()); - // Stills partiality-uncertainty merge term: adds a systematic sigma ~c*(1-partiality)* on partials, - // so strong low-partiality partials are not over-trusted. Relevant once partials exist (partiality - // post-refinement on, the default); the library auto-gates it to strong/medium data. 0 = off; ~2.5 rec. - const double part_unc = scaling_.GetPartialityUncertaintyCoeff(); - auto *partUncertain = new QCheckBox("Partiality uncertainty", this); - partUncertain->setChecked(part_unc > 0.0); - partUncertain->setToolTip("Stills: add a systematic merge σ ~c·(1−partiality)·⟨I⟩ to partials so strong " - "low-partiality partials are not over-trusted. Relevant with partiality " - "post-refinement (the default); auto-gated to strong/medium data. ~2.5 " - "recommended; unchecked = off."); - auto *partUncertainCoeff = new NumberLineEdit(0.1f, 10.0f, part_unc > 0.0 ? part_unc : 2.5, 1, "", this); - partUncertainCoeff->setEnabled(partUncertain->isChecked()); - form->addRow("", friedel); form->addRow("", refineB); form->addRow("", corrections); - form->addRow("", modulation); form->addRow("", partRefine); // Compact, and aligned with the checkboxes above: the limit checkbox + value sit together in the // field column (not as a row label, which would indent it differently). @@ -539,10 +520,6 @@ QWidget *JFJochViewerSettingsDock::BuildScalingSection() { resRow->addWidget(limitRes); resRow->addWidget(highRes, 1); form->addRow("", resRow); - auto *puRow = new QHBoxLayout(); - puRow->addWidget(partUncertain); - puRow->addWidget(partUncertainCoeff, 1); - form->addRow("", puRow); section->setContentLayout(form); section->setExpanded(false); // folded on start (only geometry + unit cell start open) @@ -550,24 +527,18 @@ QWidget *JFJochViewerSettingsDock::BuildScalingSection() { scaling_.MergeFriedel(friedel->isChecked()); scaling_.RefineB(refineB->isChecked()); scaling_.CorrectionSurfaces(corrections->isChecked()); - scaling_.StillsModulation(modulation->isChecked()); scaling_.StillsPartialityRefine(partRefine->isChecked()); scaling_.HighResolutionLimit_A(limitRes->isChecked() ? std::optional(highRes->value()) : std::nullopt); - scaling_.PartialityUncertaintyCoeff(partUncertain->isChecked() ? partUncertainCoeff->value() : 0.0); emit scalingChanged(scaling_); }; connect(friedel, &QCheckBox::toggled, this, [emitScaling] { emitScaling(); }); connect(refineB, &QCheckBox::toggled, this, [emitScaling] { emitScaling(); }); connect(corrections, &QCheckBox::toggled, this, [emitScaling] { emitScaling(); }); - connect(modulation, &QCheckBox::toggled, this, [emitScaling] { emitScaling(); }); connect(partRefine, &QCheckBox::toggled, this, [emitScaling] { emitScaling(); }); connect(limitRes, &QCheckBox::toggled, this, [emitScaling, highRes](bool on) { highRes->setEnabled(on); emitScaling(); }); connect(highRes, &NumberLineEdit::newValue, this, [emitScaling] { emitScaling(); }); - connect(partUncertain, &QCheckBox::toggled, this, [emitScaling, partUncertainCoeff](bool on) { - partUncertainCoeff->setEnabled(on); emitScaling(); }); - connect(partUncertainCoeff, &NumberLineEdit::newValue, this, [emitScaling] { emitScaling(); }); return section; }