Files
Jungfraujoch/image_analysis/bragg_integration/ProfileIntegrate2D.h
T
leonarski_fandClaude Opus 4.8 5ba5fe8ad1
Build Packages / Unit tests (push) Successful in 58m42s
Build Packages / build:windows:cuda (push) Successful in 18m14s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 13m7s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m4s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 13m30s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 12m44s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 13m26s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 14m25s
Build Packages / build:rpm (rocky8) (push) Successful in 21m46s
Build Packages / build:rpm (rocky9) (push) Successful in 13m20s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 12m15s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m20s
Build Packages / DIALS test (push) Successful in 12m40s
Build Packages / XDS test (durin plugin) (push) Successful in 8m12s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m37s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m23s
Build Packages / Generate python client (push) Successful in 30s
Build Packages / Build documentation (push) Successful in 1m11s
Build Packages / Create release (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 9m59s
integration: centre the profile on the predicted sub-pixel position
The profile was learned and applied on the integer pixel round(predicted), so a
shared profile sits up to 0.5 px off the true spot (and stacking spots with random
sub-pixel offsets broadens the learned profile). Build the Gaussian per reflection
instead, centred on the predicted sub-pixel offset -- noise-free geometry, unlike the
observed centroid, which hurt -- and elongated radially as before.

HEWL rotation @1.0A: ISa 15.7->16.2, CCref band 89.9->90.0, CCxds 94.8->95.0 (high-res
1.00A CCref 66.0->66.9); sharp serial stills 1.68A CC1/2 61.6->62.5; anomalous S peak
0.92x XDS (no accuracy traded). De-broadening the learned width by the 1/12 px^2/axis
integer-binning floor was tested and rejected (it over-narrows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:34:06 +02:00

57 lines
3.6 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, Combine3D (-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);