Skip to content

List-mode per-shot spectra: rotation + gap correction directly on droplet sparse arrays (no full-image reconstruction) #96

Description

@lg345

Summary

Add a registered XSpect step that produces per-shot 1D spectra directly from the droplet2photon sparse photon list, without reconstructing the full detector image. Rotation, ROI selection, dispersion/energy projection, and detector-gap correction are all done as geometry operations on the photon coordinates ("list-mode" / "event-mode" processing).

The full-image droplet_reconstruction step (XSpect analysis/droplet.py) is currently the throughput bottleneck. But dense reconstruction is only needed as an intermediate because rotation and gap-patching were written as image operations. Both are point/geometry operations that act more naturally — and more accurately — on the sparse list. Since the per-shot end product is a 1D spectrum (sum rows in an ROI, histogram along dispersion), the ~540k-pixel array is never actually required.

Motivation

  • Speed: per-shot cost drops from "allocate + scatter + spline-rotate a 540k-pixel image" to "transform + histogram the handful of photons in that shot" (~100–1000× fewer ops/shot; no image allocation, no interpolation).
  • Correctness / artifact removal: the polynomial gap-patch injects dead columns as linear combinations of their neighbors, which spook cannot assign independent incident-energy weight to — this produces the vertical RIXS-map stripes documented in lg345/polarization_dependent_stochastic#28. Geometric exposure normalization injects no linearly-dependent data and removes that artifact.

Method

For a shot's photons (r_i, c_i) with weights w_i (droplet photon count):

1. Rotation = exact linear transform on coordinates (O(N_photons), no interpolation):

d_i = (c_i - c0)*cosθ - (r_i - r0)*sinθ      # dispersion axis
s_i = (c_i - c0)*sinθ + (r_i - r0)*cosθ      # cross-dispersion axis

(θ sign chosen to match the existing scipy.ndimage.rotate convention.)

2. ROI + projection = filter then histogram:

m        = (s_lo <= s_i <= s_hi)
spec_raw = histogram(d_i[m], bins=dispersion_bins, weights=w_i[m])

3. Gaps = static coverage/exposure normalization (computed ONCE):

rr, cc   = nonzero(live_pixel_map within ROI)      # dead columns excluded
d_live   = (cc-c0)*cosθ - (rr-r0)*sinθ
s_live   = (cc-c0)*sinθ + (rr-r0)*cosθ
coverage = histogram(d_live[s_lo<=s_live<=s_hi], bins=dispersion_bins)
# per shot:
spec = spec_raw / coverage * coverage.mean()

Because rotation maps a dead column to a diagonal set of output bins, each output bin loses only ~1/N_rows of coverage per crossing — coverage never hits zero away from θ=0, so the correction is smooth and well-conditioned. Guard the division with a coverage floor (mask bins below ~20% of median coverage).

Optional: fold the pixel→eV map into the histogram bin edges to merge reconstruction + rotation + gap correction + energy calibration into a single pass over the photon list.

What still needs a dense image (one-time only, not the hot loop)

  • Rotation-angle finding (mfx101609126_rotation_diagnostic) and gap-column / live-pixel identification (mfx101609126_pixel_patch_diagnostic) — run on the sum image over all shots, built once.

Proposed API

New registered step, e.g. sparse_project_spectrum:

  • inputs: droplet_droplet2phot_sparse_* arrays, rotation angle, pivot (r0,c0), ROI cross-dispersion band [s_lo,s_hi], dispersion bins (or energy bins + calibration), live-pixel map (or explicit dead-column list)
  • output: per-shot 1D spectrum (N_shots, N_bins), already gap-corrected — a drop-in replacement for droplet_reconstruction → patch_pixels → rotate_detector → reduce_detector_spatial.

Acceptance / validation

  • Benchmark vs the current droplet_reconstruction pipeline on mfx101609126 run 79 (Fe foil); report speedup and peak memory.
  • Compare mean per-shot spectra list-mode vs dense path (expect close agreement, NOT bit-identical — coordinate rotation ≠ image-spline rotation, exposure norm ≠ polynomial patch).
  • Confirm the SEER RIXS-map gap stripes (polarization_dependent_stochastic#28) are gone / reduced with exposure normalization.
  • Compare spook reconstruction residuals list-mode vs dense on Fe foil (78-82) and ferricyanide (91-95).

Caveats

  • Carry the droplet photon count as w_i (a droplet may be >1 photon).
  • This is a new, arguably more correct method, not a bit-match to the current path — revalidate rather than expecting corr=1.0.
  • Coverage floor needed only near θ=0 or with a very narrow ROI.

Cross-references

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions