// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once #include #include #include #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 }; 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; };