Files
Jungfraujoch/common/Reflection.h
T
leonarski_f a395f358ef
Build Packages / Create release (push) Successful in 17s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m22s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m37s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 9m33s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 10m39s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 11m4s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 13m19s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 17m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 18m49s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 19m10s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m26s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m31s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 18m54s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m45s
Build Packages / Generate python client (push) Successful in 37s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 20m20s
Build Packages / Build documentation (push) Successful in 1m32s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m37s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m6s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 19m49s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 20m29s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 17m2s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 14m27s
Build Packages / Unit tests (push) Successful in 1h18m12s
1.0.0-rc.174 (#84)
* Rugnux: Performance improvements on GPU and CPU (more of the pre-scan and of scaling on the GPU, faster CPU spot finding and crystal refinement), with unchanged results.
* Rugnux: More robust processing - patches of persistently hot pixels are masked, an inconsistent merge triggers a retry at the measured beam centre, and builds targeting different CPU levels give the same results.
* Rugnux: Improved scaling and merging - reflections with an overloaded pixel are dropped, as in XDS, sparse rotation sweeps are scaled more reliably, and French-Wilson amplitudes use an anisotropic Wilson prior.
* Rugnux: Improved space-group determination - glide planes in groups without a centre of symmetry, screw axes from short or weak axial rows kept when a higher group is adopted, and more reliable decisions on twinned and pseudo-symmetric crystals.
* Rugnux: Improved small-molecule processing - spots that grow wider than the integration disk and split spots are integrated over their measured footprint, sparse lattices are integrated on every frame, and the `.hkl` file holds unmerged scaled reflections (SHELX HKLF 4).
* Rugnux: Reads Rigaku d*TREK SMV images (Saturn CCD), including detector 2theta and encoded pixel overflows; home-source (rotating-anode) datasets were added to the validation battery.
* jfjoch_viewer: Fixed processing failing at the end with "Wrong JPEG library version" on Linux; the merge window shows the space group with proper subscripts and a checklist of crystal pathologies.

Reviewed-on: #84
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-10-06 14:03:18 +02:00

116 lines
5.9 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <cstdint>
#include <optional>
#include <cmath>
#include "SpotToSave.h"
struct Reflection {
int32_t h;
int32_t k;
int32_t l;
float image_number; // Can be in-between for 3D integration
float delta_phi_deg; // phi angle from XDS - difference from middle of current frame (NOT an absolute angle)
float predicted_x;
float predicted_y;
float observed_x;
float observed_y;
float d;
float I;
float bkg;
float var_bkg; // non-signal (background) part of sigma^2, carried to the merge
float sigma;
float dist_ewald;
// The reciprocal Lorentz factor (rotation only - a still's Lorentz factor is one) times the
// reciprocal polarization factor, and nothing else. This is what LP means everywhere in the
// field, and it is what the CBOR key "rlp" and the HDF5 dataset "lp" store the reciprocal of.
// What it is not is a scale: the fitted per-image scale and the partiality stay out of it and
// are divided in separately below. (Named after DIALS's prescaling_correction.)
float prescaling_corr;
// The sensor's angle-dependent efficiency, QE(0)/QE(alpha): always <= 1, and exactly 1 where the
// sensor is opaque or its material and thickness are unknown. It is carried BESIDE
// prescaling_corr rather than inside it, because LP and detector response are two different
// things and every file the field reads keeps them apart. It is one of the three factors whose
// product is the total deterministic correction, with prescaling_corr above and flight_corr
// below. Defaulted to 1 so a reflection read from a file written before this existed is a no-op
// rather than a zero.
float qe_corr = 1.0f;
// The medium in the sample-to-pixel flight path, exp(D/L * (1/cos(alpha) - 1)) with D the
// normal-incidence distance, L the medium's attenuation length and alpha the angle of incidence
// on the detector: always >= 1, because an oblique reflection crossed more of the medium than
// one arriving head-on. Exactly 1 under --flight-path vacuum. Carried beside the two above for the same
// reason they are carried apart - it is neither beam geometry nor detector response but the
// medium in between, and unlike either of them it is set by the flight distance. The total
// deterministic correction on a reflection is prescaling_corr * qe_corr * flight_corr, and every
// site that corrects an intensity multiplies all three. Defaulted to 1 so a reflection read
// from a file written before this existed is a no-op rather than a zero.
float flight_corr = 1.0f;
float partiality; // fraction of the reflection recorded in the sampled (rocking) slice
float zeta;
float image_scale_corr; // I_true = image_scale_corr * I; = prescaling_corr * qe_corr * flight_corr / (partiality * image_scale)
bool observed = false;
bool on_ice_ring = false; // sits on a hexagonal-ice powder ring: excluded from scaling, kept for merging
// The signal disk lost pixels to the mask or to saturation, so the intensity is the profile's
// estimate over what remained. Such a measurement may be low, and the merge does not let it
// testify against a larger observation of the same reflection (WilsonOutliers.h).
bool clipped = false;
// A pixel of the signal disk was saturated. The reflection is then not a measurement of its
// intensity at all - its brightest pixels are missing - and a rotation merge drops the whole
// rocking event it belongs to, as XDS does with an overloaded reflection.
bool overloaded = false;
};
// One full reflection of a rotation sweep as the merge used it - its partials summed into one
// measurement, every correction and the per-frame scale applied, its sigma under the error model the
// merge weighted it with - but NOT averaged with its symmetry mates. h k l are the index it was
// measured at, not its ASU representative. Written as the SHELX HKLF 4 file, so that the program
// reading it sees the equivalents and computes Rint itself.
struct ScaledFull {
int32_t h = 0;
int32_t k = 0;
int32_t l = 0;
float I = NAN;
float sigma = NAN;
};
struct MergedReflection {
int32_t h = 0;
int32_t k = 0;
int32_t l = 0;
float I = NAN;
float sigma = NAN;
float I_half[2] = {NAN, NAN};
float sigma_half[2] = {NAN, NAN};
// Weight of this reflection in a CC1/2: the precision its half-sets would have had with every
// observation at the run's typical frame scale, over the precision they have. 1 on a sweep
// without a weak stretch; small for a reflection measured only where the crystal barely
// diffracted, whose scaled-up noise would otherwise count as much as a well-measured pair.
float cc_weight = 1.0f;
float d = 0.0;
// Any observation of this reflection sat on an ice ring. The intensity is still merged and
// written - the ring contaminates it, it does not make it absent - and this only marks it so
// a consumer that must not read the ring as crystal signal can leave it out.
bool on_ice_ring = false;
bool rfree_flag = false;
float F = NAN; // French-Wilson amplitude |F| (filled by ApplyFrenchWilson at end of merge)
float sigmaF = NAN; // its sigma
// Anomalous (Bijvoet) split of this reflection's own observations, kept even when the merge is
// Friedel-averaged (I above is the Friedel mean). Lets I(+)/I(-) be written and CCano reported by
// default without scaling anomalously; NaN when a hand was not measured or for centrics.
float I_plus = NAN;
float sigma_plus = NAN;
float I_minus = NAN;
float sigma_minus = NAN;
// French-Wilson amplitudes of the two hands (filled by ApplyFrenchWilson from I_plus/I_minus).
float F_plus = NAN;
float sigmaF_plus = NAN;
float F_minus = NAN;
float sigmaF_minus = NAN;
};