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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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