Files
Jungfraujoch/image_analysis/bragg_integration/ProfileIntegrate2D.h
leonarski_fandClaude Opus 4.8 38ea0ec237 Remove the experimental PixelRefine integrator
On the LysozymeJet5 serial stills the default Gaussian profile-fit
integrator (ProfileIntegrate2D) + reference scaling matched or beat
whole-PixelRefine on every per-shell CC1/2 (overall 95.7% vs 91.9%), ISa
(1.6 vs 1.2) and R-meas (98.5% vs 175%), with CCref a tie -- so PixelRefine
has no remaining advantage. Reference-based per-image scaling is
integrator-agnostic (IndexAndRefine::ReferenceIntensities builds a
ScaleOnTheFly(experiment, reference) applied to any integrator's output),
so the reference-dataset feature (CCref + reference scaling) is kept.

Delete image_analysis/pixel_refinement/, GeomRefinementAlgorithmEnum::
PixelRefine and its gates, BraggIntegrationSettings::ProfileMultiplier
(PixelRefine-only; R1 is shared and kept), and the -r pixelrefine /
--profile-multiplier CLI. The inherited lessons (mean background, de-biased
variance, tight-profile-loses / centroid floor, R-refinement futile) are
folded into NEXTGEN_INTEGRATOR.md.

NOTE: this transiently breaks the viewer build -- the committed viewer
still references the removed enum and ProfileMultiplier. It is fixed in the
next commit (the viewer feature work), held separate while the viewer UI is
being tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:43:04 +02:00

52 lines
3.1 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).
// NOTE (measured 2026-06): radial/tangential ANISOTROPY and per-detector-region profiles were
// tried and add nothing - the 2D detector-plane spots are ~round, the real anisotropy is in the
// discarded rocking direction - so an isotropic per-shell width is kept. The empirical grid
// under-performs the Gaussian as built (per-frame, integer-binned -> sub-pixel-smeared + noisy).
// 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.
//
// 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);