Files
Jungfraujoch/common/ScalingSettings.h
T
leonarski_fandClaude Opus 4.8 74d7589abe stills: optional detector-plane modulation (flat-field) correction
The stills scaling/merging path (ScaleOnTheFly + MergeOnTheFly) had no
correction surfaces, whereas rotation fits decay/absorption/modulation inside
RotationScaleMerge. Serial stills hammer the same detector regions shot after
shot, so a detector-plane (flat-field) systematic is at least as well
determined there as in rotation - it just had no home.

Add MergeOnTheFly::RefineModulation: a 16x16 multiplicative surface over the
predicted detector position, fit against the merged reference and folded into
each reflection's image_scale_corr before the error model and the merge. It
mirrors RotationScaleMerge::ApplyCellSurface - alternating per-cell fit with a
den-weighted geometric-mean gauge (never drifts the overall scale) and
Tikhonov pull to 1 - and is cross-validated the same way: fit the surface on
even images, score the held-out odd equivalents by a sigma-independent
R-meas-like agreement (a fractional metric a sigma-reshaping surface cannot
game), and apply the full-data surface only if the held-out gain clears a
margin. A no-op when the systematic is absent or the data too sparse (< 8 obs
per cell), so it is safe to leave off by default and opt in.

Wired through ScalingSettings::StillsModulation and rugnux --stills-modulation
(default off), in both the full-analysis and --scale paths. On a 17 MP
JUNGFRAU serial set the surface cross-validates with a large held-out gain,
confirming a real detector-plane systematic; whether it improves the merged
data quality is under evaluation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 20:45:44 +02:00

135 lines
6.8 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <optional>
#include "JFJochException.h"
// How the high-resolution cutoff for the written reflections and the reported shell table is chosen
// when no explicit --scaling-high-resolution is given. Off = keep the full (detector-edge) range;
// CCHalfLogistic = fit the CC1/2 fall-off and cut one shell past cc_target (DIALS-style, generous).
enum class ResolutionCutoffMethod { Off, CCHalfLogistic };
class ScalingSettings {
bool refine_b = false;
double max_b = 200.0;
double min_b = -50.0;
bool refine_wedge = false;
bool merge_friedel = true;
std::optional<double> high_resolution_limit_A;
std::optional<double> wedge_for_scaling;
std::optional<double> forced_mosaicity; // diagnostic: fix the scaling mosaicity (deg) instead of the per-image seed
double min_partiality = 0.02;
// Capture-aware systematic uncertainty for the rot3d combine: a full reconstructed from only
// 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).
double min_captured_fraction = 0.0;
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
// Scale fulls: after the rotation 3D combine, refit a per-frame scale on the combined fulls (XDS
// order). Only used by the rotation path (RotationScaleMerge).
bool scale_fulls = false;
// Correction surfaces fitted on the rot3d fulls after scale-fulls: a decay (per-run Debye-Waller B)
// and an absorption surface (over the diffracted-beam direction in the goniometer frame). Both are
// cross-validated, so they no-op when their systematic is absent - hence ON by default (they only ever
// help or do nothing). No-op without rot3d. Set false to disable both.
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;
// Smooth the per-frame scale G across frames (centered moving average of log G) before the rot3d
// combine, so a rocking event's partials share a consistent scale. Given as a ROTATION RANGE in
// degrees (like XDS DELPHI), converted to an odd frame window from the oscillation step; this keeps
// the smoothing physical (independent of frame slicing). 0 = off. A no-op without rot3d.
double smooth_g_deg = 0.0;
double rfree_fraction = 0.05;
// Automatic high-resolution cutoff for the written reflections + reported shells (not the scaling
// or the error model, and not the per-image _process.h5). Applied only when no explicit
// high_resolution_limit_A is set - that manual limit always wins and disables the auto-cut.
ResolutionCutoffMethod resolution_cutoff = ResolutionCutoffMethod::CCHalfLogistic;
double resolution_cc_target = 0.30; // CC1/2 value defining the fall-off limit before the +1 shell
int report_shell_count = 10; // number of resolution shells in the reported statistics table
bool scaling_regularize = false;
public:
ScalingSettings& RefineB(bool input);
ScalingSettings& RefineRotationWedge(bool input);
ScalingSettings& RotationWedgeForScaling(std::optional<double> input);
ScalingSettings& MergeFriedel(bool input);
ScalingSettings& HighResolutionLimit_A(double limit);
ScalingSettings& HighResolutionLimit_A(std::optional<double> limit); // nullopt clears the limit
ScalingSettings& MinPartiality(double min_partiality);
ScalingSettings& ForcedMosaicity(std::optional<double> 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& SmoothGDegrees(double input);
ScalingSettings& RfreeFraction(double input);
ScalingSettings& ScalingRegularize(bool input);
ScalingSettings& ResolutionCutoff(ResolutionCutoffMethod input);
ScalingSettings& ResolutionCCTarget(double input);
ScalingSettings& ReportShellCount(int input);
[[nodiscard]] bool GetRefineB() const;
[[nodiscard]] bool GetRefineWedge() const;
[[nodiscard]] double GetMinB() const;
[[nodiscard]] double GetMaxB() const;
[[nodiscard]] double GetMinMosaicity() const;
[[nodiscard]] double GetDefaultMosaicity() const;
[[nodiscard]] double GetMaxMosaicity() const;
[[nodiscard]] double GetMinWedge() const;
[[nodiscard]] std::optional<double> GetRotationWedgeForScaling() const;
[[nodiscard]] double GetMaxWedge() const;
[[nodiscard]] bool GetMergeFriedel() const;
[[nodiscard]] std::optional<double> GetHighResolutionLimit_A() const;
[[nodiscard]] double GetMinPartiality() const;
[[nodiscard]] std::optional<double> 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]] double GetSmoothGDegrees() const;
[[nodiscard]] double GetRfreeFraction() const;
[[nodiscard]] bool GetScalingRegularize() const;
[[nodiscard]] ResolutionCutoffMethod GetResolutionCutoff() const;
[[nodiscard]] double GetResolutionCCTarget() const;
[[nodiscard]] int GetReportShellCount() const;
};