Files
Jungfraujoch/image_analysis/bragg_integration/ProfileIntegrate2D.h
T
leonarski_fandClaude Opus 4.8 9f45935fd3 Scale/merge: rotation -> RotationScaleMerge, stills -> ScaleOnTheFly (fixed)
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>
2026-07-03 11:41:49 +02:00

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);