Background estimate: use the mean of the local ring, not the median. For a right-skewed (Poisson) background the median sits below the mean, so subtracting it under-subtracts and biases every weak intensity positive; over multiplicity this becomes fake <I/sig> of a few in no-signal high-resolution shells. Fixed in both PixelRefine and BraggIntegrate2D (the classical route had the same bug). <I/sig> now tracks CC honestly and the true resolution limit is visible. Error model: fit a global a, b (XDS form sigma'^2 = a*sigma^2 + (b*I)^2) from the scatter of symmetry equivalents at the merge level (so both integrators benefit), and print it with ISa = 1/b in jfjoch_process. The (b*I)^2 term uses the reflection mean (not the per-observation I_i, which biases the weights and collapses CC); a,b come from a relative-weighted bin regression. Replaces the earlier per-resolution-shell variant, which was partly masking the background bias. METHODS.md: document both (Sections 6-7), integrator-agnostic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
14 KiB
PixelRefine — methods and improvements
This note documents the changes made to the still-image pixel-refinement integrator
(PixelRefine) and, more importantly, why each one was needed. It is written from a
methods point of view; the equations are the load-bearing part.
PixelRefine integrates Bragg reflections on a still image by fitting a per-pixel
forward model against a known reference intensity set (e.g. F_calc from a deposited
model). Unlike a rotation experiment, a still samples only a thin slice of each
reflection, so the integrator must (a) model the partiality of that slice, (b) refine the
per-image geometry well enough that the high-resolution shoeboxes land on signal, and
(c) scale each image onto the reference. Each of those three is where the original code
went wrong.
Throughout, a reflection's shoebox is a small box of raw detector pixels I_p with a
local flat background B; the (area-normalised) model spot profile at pixel p is
P_p, and v_p is the variance used to weight pixel p.
0. The forward model
The recorded amplitude of a still reflection is estimated by profile fitting:
J \;=\; \frac{\sum_p w_p\,P_p\,(I_p - B)}{\sum_p w_p\,P_p^{2}},
\qquad w_p = \frac{1}{v_p},
\qquad \operatorname{var}(J) = \frac{1}{\sum_p P_p^{2}/v_p}.
The full (rotation-equivalent) intensity is recovered by dividing out the factors a still does not record,
I \;=\; \frac{J}{p\,B_\mathrm{DW}\,\mathrm{pol}},\qquad
p = \exp\!\left(-\frac{\epsilon_r^{2}}{R_0^{2}}\right),\quad
B_\mathrm{DW}=\exp\!\left(-\frac{B_\mathrm{fac}}{4 d^{2}}\right),
where the partiality p is the fraction of the mosaic block crossing the Ewald
sphere, \epsilon_r = 1/\lambda - |S_{hkl}| is the excitation error, R_0 the radial
(rocking) width, and \mathrm{pol} the polarisation correction. The per-image model
intensity for a pixel is
I_p^\mathrm{model} = G\,I^\mathrm{ref}\,B_\mathrm{DW}\,p\,P^\mathrm{tang}_p\,\mathrm{pol} + B,
with G the per-image scale. The per-image least squares minimises
\chi^2 = \sum_p w_p\,(I_p^\mathrm{model}-I_p)^2 over geometry, orientation, G, R.
1. De-biased variance (the load-bearing fix)
Symptom. Mean intensities went negative in the high-resolution shells
(\langle I/\sigma\rangle down to -12), which a box-sum integrator never does. The
per-image scale G also collapsed to 0 on most images, dropping ~80 % of observations.
Cause. Both the extraction and the fit weighted each pixel by its observed count,
v_p = I_p. For a background pixel that fluctuated down (I_p < B), v_p is small, so
w_p = 1/v_p is large, and its contribution P_p (I_p-B)/v_p < 0 is large in
magnitude. Summed over the many (mostly empty) shoebox pixels, this drags J below zero —
the classic inverse-observed-count (Poisson-on-data) bias. It bites hardest where the
true signal is weakest, i.e. at high resolution. In the fit it manifests differently but
identically in origin: the weighted empty pixels make "no signal" (G=0) the cheapest
solution, so G\to 0.
Fix. For background-limited (weak) reflections the correct variance is the local background, constant over the shoebox:
v_p = \max(B,\,1)\quad\Longrightarrow\quad
J = \frac{\sum_p P_p\,(I_p-B)}{\sum_p P_p^{2}},
the unbiased uniform-variance estimator. This single change turned \langle I/\sigma\rangle
positive at all resolutions and stopped the scale collapse. It is applied to both the
extraction weight and the fit weight.
2. Prediction band and multiplicity
Symptom. PixelRefine recorded ~4× fewer observations per unique reflection than the classical integrator — completeness was fine, redundancy was not.
Cause. A reflection is given a shoebox only when it lies within a radial band of the Ewald sphere,
\bigl|\,|S_{hkl}| - 1/\lambda\,\bigr| \le \delta .
For randomly oriented stills the number of images on which a given hkl satisfies this is
\propto \delta. The default \delta = 5\times10^{-4}\,\text{Å}^{-1} was 4–6× tighter
than the classical integrator's \delta = 2\text{–}3\times r_\mathrm{profile}, so each
reflection was recorded on 4–6× fewer images.
Fix. Widen to \delta = 2\times10^{-3}\,\text{Å}^{-1}. Multiplicity rose from
~240 k to ~950 k observations and CC$_\mathrm{ref}$ from 49.7 % to 55.9 %. Widening is
only safe once the fit is well-behaved (Sections 1, 3, 4); with the original
unconstrained fit it caused divergence.
3. Regularising the per-image fit
Symptom. Freeing any per-image parameter (orientation, R, even the scalar scale
G) collapsed the merged data (CC$_{1/2}$ from 90 % to a few %). The predict↔refine loop
with all parameters frozen was, by contrast, byte-identical to extraction-only — proving
the fit, not the loop, was at fault.
Cause. The per-image problem regularised nothing: orientation had no prior, R
and G only a lower bound. Three orientation DOF (plus R, G) against a handful of
signal pixels per still overfit the noise, and an unconstrained 1/G then scrambled the
cross-image merge.
Fix. Anchor each refined parameter to its prior with a data-scaled weight, as
ScaleOnTheFly already does for rotation data. The data term has one residual per
pixel, so the prior weight must scale with the pixel count:
w_\theta = \sqrt{\frac{N_\mathrm{pix}}{\sigma_\theta^{2}}}\,,
\qquad \chi^2_\mathrm{reg} = w_\theta^{2}\,(\theta-\theta_0)^2 .
Using \sqrt{N_\mathrm{refl}} instead (as in the rotation scaler) is a factor
\sqrt{N_\mathrm{pix}/N_\mathrm{refl}}\approx\sqrt{49}\approx 7 too weak and is simply not
felt. Applied to:
- Scale:
\theta=G,\ \theta_0=1. Prevents1/Gfrom wandering; restored CC$_{1/2}$ from 4 % back to 87 %. - Orientation:
\theta= Rodrigues vector,\theta_0= spot-centroid orientation,\sigma_\thetain radians. At\sigma_\theta\!\sim\!1^\circthis gives the best CC$_\mathrm{ref}$; beyond\sim 2^\circthe fit overfits and the merge collapses, so\sigma_\thetais the safety knob.
4. Signal-weighting the fit
Cause. Even de-biased, a shoebox is ~80 % empty pixels (≈ 40 of 49 for a radius-3
box). They carry no information on G, R or orientation but add noise and, near the
overfitting edge, destabilise the fit.
Fix. Multiply the fit weight by a detector-space Gaussian centred on the predicted spot,
w_p \;\to\; w_p \cdot \exp\!\left(-\frac{r_p^{2}}{2\sigma_s^{2}}\right),
\qquad r_p = \lVert (x_p,y_p) - (x_\mathrm{pred},y_\mathrm{pred})\rVert,
so the signal-bearing core drives the refined parameters (\sigma_s\approx 1.5 px). With
the regularised orientation this lifted CC$_\mathrm{ref}$ from 60.5 % to 62.6 %.
5. Global orientation + cell-scale sweep
Motivation. The classical refinement (XtalOptimizer) works on spot centroids from
spot-finding, which are poor value at high resolution; the local LSQ above is a heavy
gradient step that cannot make the ~degree-scale global moves needed to pull a
mis-indexed crystal's high-resolution reflections onto their shoeboxes. A small,
global sweep that simply asks "at which orientation does the most high-resolution
signal appear where the reference says it should?" is structurally better suited.
Score. Pearson CC of the box-summed intensities against the reference, over all matched reflections (not just the strong ones):
\mathrm{CC}_\mathrm{ref}(L) = \operatorname{corr}_{hkl}\bigl(\,I^\mathrm{box}_{hkl}(L),\ I^\mathrm{ref}_{hkl}\,\bigr).
The strong low-resolution reflections anchor the CC (moving them off their boxes collapses it), so the change in CC across the sweep is driven almost entirely by weak high-resolution reflections falling onto — or off — real signal. This is the "appearing out of the void" behaviour.
Geometry-derived bounds (parameter-free). A spot at resolution d sits at detector
radius
r(d) = \frac{L}{p}\,\frac{\lambda}{d}\quad[\text{px}],
(L = detector distance, p = pixel size). Both a crystal rotation \delta\theta and a
fractional cell-scale \epsilon displace that spot by an amount proportional to its
radius:
\Delta_\mathrm{px} = r(d)\,\delta\theta \quad(\text{rotation}),\qquad
\Delta_\mathrm{px} = r(d)\,\epsilon \quad(\text{cell scale}).
So high-resolution spots (large r) move most. The two natural constraints fix the grid:
- Step = 1 px at the highest resolution:
\;\delta\theta_\mathrm{step} = 1/r_\mathrm{max}(finer is below the detector's resolving power). - Range = the orientation uncertainty
\Delta\theta_u(a few px at high res). The number of steps per axis is then
n = \frac{\Delta\theta_u}{\delta\theta_\mathrm{step}} = \Delta\theta_u\, r_\mathrm{max},
a handful of steps, and the lowest-resolution spots move only
n\,\delta\theta_\mathrm{step}\,r_\mathrm{min} = \Delta\theta_u\, r_\mathrm{min} \ll 2 px
— i.e. the strong anchors stay put by construction.
Lesson learned. Setting the range from "2 px at low resolution" instead gives
n = 2\,r_\mathrm{max}/r_\mathrm{min} = 2\,d_\mathrm{low}/d_\mathrm{high}steps — tens of pixels of high-resolution freedom — which lets the per-image CC overfit and degrades the merge. The range must be tied to the (small) orientation uncertainty, not the low-resolution cap.
The sweep is a coordinate descent over the three Rodrigues axes and the cell scale, run
before the LSQ. It improves the high-resolution shells (CC$\mathrm{ref}$ +1 to +5
per shell) while preserving CC${1/2}\approx 90$ %. It is an alternative to the
loose-\sigma orientation LSQ, not a complement — stacking the two double-moves the
orientation and overfits. It is therefore available but off by default.
6. Background-estimator bias (the largest σ error; both integrators)
Symptom. Pushed past the true resolution limit (e.g. a 1.8 Å crystal merged to 1.3 Å),
the no-signal shells still reported \langle I/\sigma\rangle \approx 4\text{–}6 with
\mathrm{CC}_{1/2}\approx 0 — i.e. confident "data" where there is none. Present in both
PixelRefine and the classical BraggIntegrate2D.
Cause. Both estimated the local background as the median of the surrounding ring. For a Poisson / right-skewed background the median sits below the mean,
\operatorname{median}(B) < \mathbb{E}[B],
so subtracting it under-subtracts on every pixel. The leftover positive offset is tiny per
pixel but coherent, so over an $n_\mathrm{pix}$-pixel peak and a multiplicity-m merge it
grows to a fake signal
\langle I\rangle_\mathrm{bias} \;\approx\; n_\mathrm{pix}\,\bigl(\mathbb{E}[B]-\operatorname{median}(B)\bigr),
\qquad
\Bigl(\tfrac{I}{\sigma}\Bigr)_\mathrm{merged,\,bias} \propto \sqrt{m}.
It is worst where the real signal is weakest (high resolution), because there the offset is all that remains — which is exactly the observed signature. Nothing leaks into the high-resolution shells; the background is simply under-estimated.
Fix. Use the mean of the ring (outliers excluded by the existing spot-core mask and
saturation sentinels). \langle I/\sigma\rangle then collapses to ~0 wherever
\mathrm{CC}\approx0, tracks CC down the shells, and the honest resolution limit becomes
visible. This was the single largest contributor to untrustworthy σ — a one-line change in
each integrator, not a variance-model problem.
7. Error model (global a, b; XDS form)
Counting statistics under-estimate the variance of strong reflections, which carry
systematic errors (scaling, partiality, detector) proportional to intensity, not to
\sqrt{I}. After merging, this leaves CC$_{1/2}$ and \langle I/\sigma\rangle
inconsistent. The standard correction (XDS / DIALS / AIMLESS) inflates the variance with a
global two-parameter model:
\sigma'^{\,2} \;=\; a\,\sigma^{2} + \bigl(b\,\langle I\rangle\bigr)^{2},
\qquad
\mathrm{ISa} \;=\; \frac{1}{b}\;=\;\lim_{I\to\infty}\frac{I}{\sigma'} .
Two points matter for an unbiased fit:
- The
I^2term uses the reflection mean\langle I\rangle(constant over its observations), not the per-observationI_i. UsingI_igives a down-fluctuated point a small\sigma'and hence a large1/\sigma'^2weight, biasing the merged mean — which collapses CC. (This was the decisive bug in the first attempt.) aandbare fit from the spread of symmetry equivalents: for an observation in a group ofn,\mathbb{E}[(I_i-\langle I\rangle)^2] = \sigma_i^2(1-h_i)with leverageh_i = w_i/\sum w. Binning by intensity and regressing the bin medians of(I_i-\langle I\rangle)^2/(1-h_i)on(\sigma^2, \langle I\rangle^2)— weighted by1/\mathrm{dev}^4so the fit is relative — gives(a, b^2); the relative weight stops the strong bins (which fixb) from swamping the weak bins (which fixa).
It is applied at the merge level (MergeOnTheFly), so both integrators benefit, and
jfjoch_process prints the model and ISa. Earlier per-resolution-shell variants were
dropped: the standard tools use a single global a, b, and the per-shell version was
partly masking the background bias of Section 6.
Results (lysozyme jet, 1.8 Å, identical input)
| Configuration | N_obs | Compl. | CC$_{1/2}$ | CC$_\mathrm{ref}$ |
|---|---|---|---|---|
| Classical integrator (box-sum) | 421 k | 100 % | 81.4 % | 60.9 % |
| PixelRefine — original | 61 k | 95 % | 0.0 % | −0.4 % |
| PixelRefine — consolidated (this work) | 951 k | 100 % | 80.0 % | 62.6 % |
The consolidated integrator beats the classical one on the accuracy metric (CC$\mathrm{ref}$) with > 2× the multiplicity, and turns a previously unusable result (CC${1/2}=0$) into a competitive one.
Default recipe
Sections 1–5 are PixelRefine-specific; Sections 6–7 act at the integration/merge level
and apply to the classical route too.
| Field / behaviour | Default | Section |
|---|---|---|
| fit/extraction variance | local background B |
1 |
ewald_dist_cutoff |
2\times10^{-3}\,\text{Å}^{-1} |
2 |
scale_reg_sigma |
2.0 | 3 |
orient_reg_sigma_deg |
1.0 | 3 |
refine_R |
false |
3 |
fit_signal_sigma_pix |
1.5 | 4 |
sweep_orientation |
false (available) |
5 |
| local background estimator | mean of the ring | 6 |
| merge error model | global a,b (ISa printed) |
7 |
orient_reg_sigma_deg (accuracy vs. precision) and ewald_dist_cutoff (multiplicity vs.
cost) are the two knobs worth tuning per dataset.