Consolidate the offline scaling/merging on two engines and delete the old classic rotation chain: - ScaleOnTheFly now implements ONLY the fixed-partiality model: it uses each reflection's stored partiality as-is (1 for stills, the zeta/erf rocking-curve value already set at prediction for rotation). Dropped the Rotation partiality recompute, the unity override, the ceres rotation residual, and all wedge/mosaicity refinement. - Remove Combine3D (CombineRotationObservations): its 3D-combine lives in RotationScaleMerge. - JFJochProcess / jfjoch_scale: rotation (-P rot3d) goes through RotationScaleMerge for the whole self-scale -> 3D combine -> scale-fulls -> merge; stills self-scale with ScaleOnTheFly + MergeOnTheFly. RotationScaleMerge does not support external-reference scaling, B-factor refinement, an absorption surface or wedge refinement, so those combinations now throw instead of silently falling back. Deleted the classic ScaleFulls / AbsorptionSurface / SmoothImageScaleG helpers. - CLI cleanup: drop -w/--wedge (wedge refinement) and --absorption (both tools), and the now dead -P values rot and unity (keep fixed | rot3d). Fix the -P round-trip in the reproduced command line. Behaviour on the rotation path is unchanged: the full 18-crystal battery matches the pre-cleanup metrics (SG/ISa/CC1.2/completeness) exactly on all 15 deterministic crystals. Non-CUDA build unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
3.7 KiB
C++
57 lines
3.7 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
// =============================================================================
|
|
// ProfileIntegrate2D — profile-fitting 2D integrator (the DEFAULT integrator)
|
|
// =============================================================================
|
|
//
|
|
// A drop-in alternative to BraggIntegrate2D that replaces uniform box summation with
|
|
// profile-fitted extraction (no reference intensities needed). See NEXTGEN_INTEGRATOR.md for
|
|
// the rationale: the residual ~4x R-meas/ISa gap to XDS is the box-sum method (a fixed disk
|
|
// captures a width-dependent fraction of each spot -> ~18% multiplicative per-observation floor
|
|
// on strong reflections), and for the lineage (this is the extraction half of the former,
|
|
// now-removed PixelRefine, which beat whole-PixelRefine on the serial test).
|
|
//
|
|
// Output is a vector<Reflection> with I, sigma, partiality, d - IDENTICAL in shape to
|
|
// BraggIntegrate2D - so ScaleOnTheFly, RotationScaleMerge (-P rot3d) and the merge consume it
|
|
// unchanged, and it works for both stills and rotation.
|
|
//
|
|
// Algorithm (per frame):
|
|
// A. Box-sum every reflection (rough I + observed centroid); pick strong spots (signif>=5).
|
|
// B. Build the profile per resolution shell from the strong spots: a Gaussian of the measured
|
|
// second moment (ProfileGaussian, the keeper) or the empirical average grid (ProfileEmpirical).
|
|
// The width 2nd-moment is taken over the signal disk r1 on the monochromatic path (the wide grid
|
|
// corners only carry neighbour leakage + rectified noise, which inflate the learned width several-
|
|
// fold) and over the full grid on the broadband path (sparse spots, generous width wanted).
|
|
// NOTE (2026-06-30): an earlier test found radial/tangential ANISOTROPY "adds nothing", but that
|
|
// was confounded by the then-contaminated (over-wide) width; with the de-contaminated width, a
|
|
// RADIAL elongation does help at high resolution - see step C and NEXTGEN_INTEGRATOR.md round 3.
|
|
// C. Profile-fit each reflection (Kabsch): I = sum P(c-B)/v over sum P^2/v, de-biased
|
|
// variance v = B + max(I,0)*P (iterate), sigma = sqrt(1/sum P^2/v). Carry the rotation
|
|
// partiality exactly as BraggIntegrate2D does. The Gaussian is built per reflection, centred on the
|
|
// predicted SUB-PIXEL position (the integer grid otherwise mis-places it up to 0.5 px) and elongated
|
|
// along the RADIAL direction by sigma^2_radial = sigma^2_intrinsic + (parallax + bandwidth +
|
|
// capture)*tan^2(2theta) (analytic sensor parallax always; bandwidth streak + capture term per path).
|
|
//
|
|
// Staging: v1 = measured-R1 Gaussian (the keeper); v2 = empirical per-shell; v3 = empirical per
|
|
// detector-region x shell (XDS-grade).
|
|
//
|
|
// Selected by BraggIntegrationSettings::Integrator: ProfileGaussian (the DEFAULT, v1) or
|
|
// ProfileEmpirical (v2); BoxSum (BraggIntegrate2D) is the fallback. jfjoch_process exposes it as
|
|
// `--integrator boxsum|gaussian|empirical`. For A/B vs XDS_ASCII.HKL via --dump-observations.
|
|
// =============================================================================
|
|
|
|
#include <vector>
|
|
|
|
#include "../../common/DiffractionExperiment.h"
|
|
#include "../../common/Reflection.h"
|
|
#include "../../common/CompressedImage.h"
|
|
|
|
std::vector<Reflection> ProfileIntegrate2D(const DiffractionExperiment &experiment,
|
|
const CompressedImage &image,
|
|
const std::vector<Reflection> &predicted,
|
|
size_t npredicted,
|
|
int64_t image_number);
|