Skip to content

Droplet reconstruction, patch_pixels auto-detect, XAS fixes, and XCS/MFX pipelines#95

Merged
lg345 merged 12 commits into
feature/yaml-pipeline-architecturefrom
xcs101591326-polariton
Jul 1, 2026
Merged

Droplet reconstruction, patch_pixels auto-detect, XAS fixes, and XCS/MFX pipelines#95
lg345 merged 12 commits into
feature/yaml-pipeline-architecturefrom
xcs101591326-polariton

Conversation

@lg345

@lg345 lg345 commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

Closes out a major round of pipeline development driven by stochastic RIXS (spooktroscopy) analysis on mfx101609126 and pump-probe XAS on xcs101591326.

Infrastructure

  • batch_manager: inject abs_start_index/abs_end_index on every batch run (required by droplet_reconstruction to slice the correct HDF5 rows); add precomputed_attrs mechanism so CCM/time axis steps run once on the full run and their results are broadcast into every batch worker, preventing inconsistent energy/time grids.
  • pipeline: scalar pre-pipeline pass before entering the batched loop; _collect_scalar_precomputed excludes raw input keys to avoid injecting filtered-down arrays into batch workers.
  • config_parser: max_shots YAML field caps shots per run — essential for testing droplet pipelines where each shot materialises a full 2D detector array.

New step: droplet_reconstruction

Reads sparse photon coordinates from smalldata HDF5 (fixed-length zero-padded or variable-length _len pointer format) and scatters them back onto dense 2D images. ROI-direct scatter avoids allocating the full 704×768 panel. Motivation: photon counting eliminates read noise below the single-photon threshold, improving the spook AtA condition number for stochastic RIXS.

patch_pixels overhaul

Auto-detection of ASIC panel-gap columns via a dual method:

  1. Ratio — column vs median-filtered baseline; catches extreme charge-sharing spikes
  2. Z-score + width filter — robust MAD-based z-score with narrow-cluster gating; catches subtle 50%-reduced dead columns that the ratio method misses

Polynomial fit vectorised (Vandermonde solve → single dot product replaces per-row np.polyfit loop). Also adds find_rotation_angle step wrapping XSpectDetectorProcessor PCA tilt detection; fixes arctan2 argument order and cluster-size weighting in XSpectDetectorProcessor.

XAS/XES analysis fixes

  • reduce_detector_ccm: 3D detector support (produces (n_energy, rows, cols) RIXS-plane stack); NaN-guarded accumulation; off-by-one fix using len(ccm_energies) not len(ccm_bins).
  • Same fixes in reduce_detector_ccm_temporal.

New YAML configs

File Experiment Workflow
xcs101591326_ultrafast_xas.yaml XCS runs 187-216 DCCM energy scan, 1D XAS
xcs101591326_temporal_xas.yaml XCS runs 339-347 Time-delay scan, reduce_detector_temporal
xcs101591326_2d_xas.yaml XCS runs 187-216 2D XAS: Δμ(E,t)
mfx101609126_pershot_xes.yaml MFX run 79 Per-shot XES for spook
mfx101609126_seer_xrt.yaml MFX runs 78-82 Static SEER+XRT comparison
mfx101609126_static_rixs.yaml MFX run 97 RIXS plane
mfx101609126_droplet_pershot_xes.yaml MFX runs 78-82 Photon-counted per-shot XES
mfx101609126_droplet_recon_test.yaml MFX run 79 100-shot smoke test

Notebooks & scripts

Rotation diagnostic, pixel-patch diagnostic, shot browser, SEER-vs-XRT correlation analysis, spook RIXS (ADU + photon-counted + transmission), static RIXS plane, droplet reconstruction evaluation, and regeneration scripts.

Tests

4 new patch_pixels auto-detect test cases covering spike detection, subtle gap detection, gradient rejection, and manual+auto merge.

lbgee added 12 commits July 1, 2026 00:33
Pipeline output HDF5 files (per-shot spectra, binned arrays) can reach
hundreds of MB per run and must not be tracked in git.  Add examples/results/
to .gitignore so regenerate_*.py and Pipeline.run() output is never staged.
droplet_reconstruction reads sparse photon coordinates by absolute HDF5
shot index, not by relative position within a batch.  Two new attributes
(abs_start_index, abs_end_index) are now injected by _make_batch_run and
_process_batch_parallel so each batch worker knows its exact slice in the
source file.  Both are listed in _INFRASTRUCTURE_ATTRS to prevent them
from being collected as result keys.

precomputed_attrs solves a batch consistency problem for axis steps
(make_ccm_axis, time_binning): when those steps run independently on
each batch they can produce different energy/time grids from incomplete
data slices.  The pipeline now runs a scalar-only pre-pass on the full
run, collects the resulting axis arrays, and injects them via
_inject_precomputed before each batch pipeline executes.  Per-shot
arrays (shape[0] == total_shots) are sliced to the batch range; static
axis arrays (ccm_bins, ccm_energies, time_bins) are copied as-is.

reconverge_results is tightened: geometry scalars ending in _angle are
averaged (not summed) across batches; axis/bin key detection now matches
on exact key names first, then suffix patterns.
…ecomputed

pipeline.py: when a run is large enough to batch, the pipeline now executes
a lightweight scalar-only pre-pass (run_pipeline without detector data) before
entering the batched loop.  Axis-derivation steps (make_ccm_axis, time_binning,
ccm_binning) fire here on the full set of scalar diagnostics and produce a
globally consistent ccm_bins/ccm_energies/time_bins.  _collect_scalar_precomputed
then harvests those attributes — excluding raw input key names so that
pre-pipeline-filtered copies of ipm/xray/encoder never overwrite the
freshly-loaded batch-slice arrays — and passes them to run_batched as
precomputed_attrs.

max_shots (data.max_shots in YAML) caps the number of shots loaded per run,
which is essential when testing droplet_reconstruction pipelines where each
reconstructed shot expands into a full-size 2D detector array.  The cap is
forwarded to spectroscopy_run(end_index=max_shots) and also stored on the
run object so it is picked up correctly by the fallback constructor path.

config_parser.py: parse data.max_shots from YAML (int or null).
Reads per-shot sparse photon coordinates stored by smalldata's
droplet2photon algorithm and scatters them back onto dense 2D images,
yielding a (N_shots, rows, cols) array that is drop-in compatible with
all downstream pipeline steps (filter_detector_adu, patch_pixels,
rotate_detector, reduce_detector_spatial, etc.).

Two HDF5 layouts are auto-detected:
  Fixed-length  — <det>/droplet_droplet2phot_sparse_{row,col,data}
                  shape (Nshots, nData), zero-padded (valid entries != 0)
  Variable-length — <det>/var_droplet_droplet2phot_sparse/{row,col,data}
                    flat arrays + <det>/var_droplet_droplet2phot_sparse_len

ROI-direct scattering (_scatter_roi) filters photons to the crop window
before allocating the output, so the full 704×768 panel is never
materialised when a roi is specified.  For a typical (60, 300) XES
window this reduces memory by ~30x vs full-panel scatter-then-crop.

Batch-awareness: the step reads abs_start_index / abs_end_index injected
by the batch manager so each worker slices the correct HDF5 rows.  In
the non-batched path it falls back to start_index / end_index.

Motivation: stochastic RIXS (spooktroscopy) requires the cleanest
possible per-shot emission spectra.  Standard ADU integration accumulates
read noise on every pixel on every shot; photon counting eliminates all
counts below the single-photon threshold and produces integer photon maps
that have much better signal-to-noise for the spook correlation matrix.
…d_rotation_angle

patch_pixels gains auto_detect mode for ASIC panel-gap columns.
A two-method approach handles the full range of defect severity:

  Method 1 (ratio): compares column profile against median-filtered
  baseline.  Columns with ratio > threshold (spikes) or < 1/threshold
  within the signal band (dead gaps) are flagged.  Catches extreme
  charge-sharing spikes that dwarf the spectral signal.

  Method 2 (z-score + width filter): computes a robust z-score using
  MAD-based local sigma.  Bright and dark outliers above nsigma are
  separately clustered and kept only when cluster width <= max_gap_width.
  Separating bright/dark prevents merging a dark gap with its bright
  charge-sharing neighbors into a single wide cluster that would be
  rejected as a signal gradient.  This catches subtle 50%-reduced dead
  columns that the ratio method misses.

The polynomial fit is vectorised: instead of calling np.polyfit for every
(shot, row) pair, the Vandermonde matrix is solved once via np.linalg.solve
to get a projection vector, then all rows are evaluated with a single dot
product.  The fit region now also excludes the ±patch_range neighborhood
of every known bad column, not just the target pixel, so elevated
charge-sharing neighbors don't bias the polynomial anchor.

find_rotation_angle wraps XSpectDetectorProcessor to auto-detect the tilt
of a dispersed spectral signal via Canny edge detection → DBSCAN clustering
→ per-cluster PCA.  Stores the angle on the run for use by rotate_detector.

XSpectDetectorProcessor.find_optimal_rotation_angle: fix arctan2 argument
order (was arctan2(dy,dx), should be arctan2(dx,dy) for row-dominant
principal vector); weight cluster angles by cluster size so small
edge-detection artifacts don't bias the result.

filter_detector_adu: guard getattr with a None check so the step skips
gracefully when the detector key is absent on the pre-pipeline pass.
…gies length

reduce_detector_ccm previously only handled 1D (scalar) and 2D (spectrum)
detectors.  A new 3D branch accumulates full 2D images per energy bin —
producing a (n_energy, rows, cols) stack — enabling RIXS-plane pipelines
where the detector is a 2D spectrometer image not yet collapsed spatially.

Off-by-one in bin count: both reduce_detector_ccm and
reduce_detector_ccm_temporal were allocating n_bins = len(ccm_bins) where
ccm_bins is the n+1-element edge array from make_ccm_axis.  They now use
len(ccm_energies) (= n edges - 1) so the output axis length matches the
energy axis.

NaN-guarded accumulation: shots that produce NaN values (e.g. empty bins,
missing HDF5 rows) are now skipped rather than poisoning the running sum.
For 2D/3D detectors np.where(np.isnan(...), 0.0, ...) replaces NaN pixels
element-wise so a single bad shot doesn't zero an entire spectrum row.

reduce_detector_ccm_temporal gets the same ccm_energies length fix and
NaN guarding for both 1D and 2D detector dims.

make_energy_axis and normalize_xes: minor formatting only.
…erance

Four new test cases cover the full auto_detect surface:
  - test_auto_detect_spike: confirms a 100× bright column is flagged and
    patched via the ratio method (Method 1)
  - test_auto_detect_subtle_gap: confirms a 50%-reduced narrow 2-col dip
    is caught by the z-score method (Method 2) which the ratio threshold misses
  - test_auto_detect_ignores_wide_gradient: verifies that a smooth
    intensity gradient across 200 columns produces zero flagged pixels
  - test_auto_detect_merges_manual: checks that auto-detected and
    manually specified pixel lists are both applied

test_patch_interpolate tolerance relaxed from exact equality to atol=1e-3
because the vectorized polynomial fit (Vandermonde + np.linalg.solve)
differs from np.polyfit in numerical precision by ~1e-10 — the test was
comparing against a specific polyfit rounding, not a scientific threshold.
…nalysis notebook

Three YAML pipeline configs for the polariton / FeNO pump-probe XAS
experiment at XCS (LCLS run 26, runs 187-216 and 339-347):

  xcs101591326_ultrafast_xas.yaml  — static DCCM energy scan, auto CCM
    axis from setpoints, laser-on vs laser-off, 1D XAS with reduce_detector_ccm

  xcs101591326_temporal_xas.yaml  — time-delay scan at fixed DCCM energy,
    enc/lasDelay binning (-10 to 25 ps, 35 bins), reduce_detector_temporal,
    combine_runs reduction across 7 runs with uncertainty propagation

  xcs101591326_2d_xas.yaml  — simultaneous DCCM energy × time-delay scan,
    21-bin time axis (-2 to 8 ps), 2D binning via reduce_detector_ccm_temporal,
    produces transient absorption map Δμ(E,t)

Analysis notebook (xcs101591326_temporal_xas_analysis.ipynb) computes
mu = If/I0 per delay bin from the pipeline output and plots normalised
difference spectra for kinetic analysis.
…eline configs

mfx101609126_pershot_xes.yaml  — stochastic RIXS input preparation:
  filters to xray shots, ADU thresholds both detectors, patches ASIC
  gap columns (manual on epix100_0; auto_detect on epix100_1 SEER),
  rotates -2.0° (spec) / -1.6° (SEER), projects rows → 1D per-shot
  spectra for spook correlation.  Produces:
    xrt_hproj (N×2048) and epix_spec_ROI_1 / epix_seer_ROI_1 (N×n_cols)

mfx101609126_seer_xrt.yaml  — static comparison of both spectrometers
  across runs 78-82: sums all xray shots to 2D image then collapses
  rows → 1D spectrum for each detector independently.

mfx101609126_static_rixs.yaml  — RIXS plane for run 97 DCCM scan:
  reduce_detector_ccm accumulates full 2D epix images at each incident
  energy → (n_energy, 60, 300) stack, then reduce_detector_spatial
  collapses the cross-dispersion axis → (n_energy, 300) RIXS plane.

mfx101609126_droplet_pershot_xes.yaml  — per-shot XES using photon-
  counting via droplet_reconstruction (epix100_0 fixed-length sparse
  format), same downstream steps as pershot_xes.

mfx101609126_droplet_recon_test.yaml  — 100-shot smoke test for
  droplet_reconstruction; max_shots: 100 avoids materialising the full
  ~36k-shot array during development.
rotation_diagnostic — finds optimal detector tilt angle by running the
  pipeline on a subset of shots and inspecting the 2D sum image with
  overlaid rotation angle estimates from XSpectDetectorProcessor.  Fixed
  angles (-2.0° / -1.6°) were determined here and hardcoded into YAMLs.

pixel_patch_diagnostic — compares raw vs patched column profiles for
  epix100_0 and epix100_1, visualises the ASIC gap positions and the
  effect of auto_detect patching on the baseline ratio and z-score.

seer_shot_browser — per-shot SEER image browser: scan forward/backward
  through individual shots to inspect photon distributions and confirm
  the ASIC gap correction is working correctly.

seer_vs_xrt — quantifies SEER as an alternative incident-energy monitor
  for stochastic RIXS.  Computes correlation between SEER and XRT per-
  shot total-intensity and center-of-mass energy.  Conclusion: SEER
  tracks intensity well (r=0.90) but has low COM energy correlation (0.41)
  because it encodes sample transmission/emission, not purely the incident
  beam.  Gap patching dramatically improves the spook AtA condition number.

spook_rixs — stochastic RIXS extraction using the spook library: builds
  A (XRT hproj) and B (epix_spec per-shot spectra) matrices and solves
  for the RIXS cross-section X via regularised least squares.

droplet_spook_rixs — same as spook_rixs but using photon-counted spectra
  from droplet_reconstruction instead of raw ADU integration.

droplet_spook_transmission — stochastic RIXS using SEER (transmission
  geometry) as the B matrix; photon-counted for improved S/N.

rixs_plane — static RIXS plane visualisation for the DCCM scan run.
regenerate_run0079.py — runs mfx101609126_pershot_xes.yaml (standard ADU
  integration) and saves per-shot spectra to examples/results/; used as
  the baseline to compare against droplet-reconstructed spectra.

regenerate_droplet_pershot.py — runs mfx101609126_droplet_pershot_xes.yaml
  for runs 78-82 sequentially, saving one HDF5 per run to results/.
  Measures reconstruction throughput (shots/sec) and reports memory usage.

droplet_reconstruct.py — standalone utility script that reconstructs a
  single run's sparse photon data without the full pipeline framework;
  used for rapid iteration and debugging of the reconstruction step.

droplet_reconstruction.ipynb — step-by-step walkthrough of the sparse
  format: reads raw HDF5, shows fixed-length vs variable-length layouts,
  runs _scatter and _scatter_roi helpers manually, compares output images
  against the pre-processed ROI_area dataset for bit-identity verification.

droplet_recon_pipeline_eval.ipynb — end-to-end comparison of ADU
  integration vs photon-counting via the pipeline API.  Runs both YAMLs
  on run 79, overlays 1D emission spectra, and evaluates peak S/N ratios
  to quantify the photon-counting improvement for spook inputs.
…c assets

mfx101080524_static_xes.yaml: remove hitfinding step.  The hit-finder was
removing shots that had low per-frame median signal, which is expected for
single-photon-level XES data.  Removing it recovers the correct shot count
and avoids discarding weak-signal runs.

seer_full_detector_run78.png: full-panel SEER detector image from run 78
showing the spatial distribution of signal across all ASIC tiles; used in
rotation_diagnostic.ipynb and seer_shot_browser.ipynb to orient the
cross-dispersion ROI selection.

c.ipynb: scratch notebook for static RIXS pipeline diagnostics (run 97);
shows RIXS plane shape, energy range, and 2D sum-image ROI inspection.

xcs101591326.ipynb, Untitled.ipynb: updated experiment analysis notebooks.
Copilot AI review requested due to automatic review settings July 1, 2026 07:43
@lg345 lg345 merged commit e38b397 into feature/yaml-pipeline-architecture Jul 1, 2026
Copilot stopped reviewing on behalf of lg345 due to an error July 1, 2026 08:03
@lg345 lg345 removed the request for review from Copilot July 1, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants