MLXIDDemo

A walkthrough of the MLXID pipeline: Monte Carlo generation of single-photon detector clusters -> assembling multi-photon pile-up samples -> trained deep learning models that reconstructs sub-pixel photon position -> for single photon, comparison against the classical eta-interpolation baseline, scored directly against known MC ground truth -> optional training and inference on real measurement data.

See the sibling repos for the full picture this is distilled from: DataProcess, DeepLearning, EtaInterpolation, McGeneration. The charge-transport simulation that produced data/mc_raw_simulation/ (the basis for data/mc_params/) is open-sourced at https://github.com/slsdetectorgroup/ChargeTransportSimulation.

12 keV working point

12 keV is the MaxIV 2603 beamtime working point: it has knife-edge measurements (for a quantitative MTF benchmark) with flat-field measurements, and already has trained 1/2/3-photon models -- which is what this demo's pretrained checkpoints are.

What's in here vs. what's left out

Everything in data/ and models/ is chosen or generated specifically to be shippable:

Folder Contents Size Provenance
data/mc_raw_simulation/ raw per-depth-bin charge-transport simulation output for this one sensor/voltage/energy point (256 files) ~34 MB pre-generated
data/mc_params/ charge-cloud shape parameters alpha(t)/beta(t), fit fresh from the above each time 01 runs (+ a plot of the fit) ~200 B generated by scripts/01_...
data/mc_samples/ ~40k single-photon MC clusters + exact ground-truth (x,y,z,E) ~18 MB generated by scripts/01_...
data/pileup_samples/ ~40k 2-photon + ~40k 3-photon MC pile-up clusters, ground truth ~40 MB generated by scripts/02_...
data/measurement_samples/ small fallback slice of real measurement clusters (both targets, 1/2/3-photon), for 05 when group storage isn't reachable ~27 MB generated by scripts/prepare_measurement_fallback.py
models/ one pretrained checkpoint per photon count (1/2/3), the same ones DeepLearning/Configs/infer_*photon.yaml point at for 12 keV ~13 MB copied from DeepLearning/Results/**/Models/

Left out on purpose: the full real measurement data isn't shipped -- it's tens of GB per target (see DataProcess/Samples/), so 05 reads a small slice of it directly from group storage at run time by default. Only a much smaller (~30k-65k event) fallback slice lives in data/measurement_samples/, for when that storage isn't reachable (see "What 05 does"). The full production MC/pile-up training sets (tens of GB) are also not shipped. The charge-transport simulation code that produced data/mc_raw_simulation/ is a separate, open-sourced repo: ChargeTransportSimulation.

Layout

data/            small datasets (see table above)
models/          three pretrained 12 keV checkpoints (1/2/3-photon)
src/             shared code: CNN architectures, datasets, eta-interpolation
                 functions, and a trimmed MC generator (copied/adapted from
                 the four source repos, kept here so this folder is self-
                 contained)
scripts/         01-05, run in order (see below); plus
                 prepare_measurement_fallback.py, a separate one-time
                 utility (needs group storage access) that (re)builds
                 data/measurement_samples/ -- not part of the numbered
                 sequence

Environment

All five scripts run in one conda env, mlxid_demo, which has everything (numpy, scipy, ROOT, h5py, omegaconf, torch+CUDA, and aare) together -- unlike the four source repos, which switch between several partial envs per pipeline stage. It doesn't depend on any machine-specific build: aare comes straight from PyPI, so a colleague can build the same env on their own machine (with a CUDA GPU) with:

conda create -n mlxid_demo -c conda-forge python=3.12 numpy scipy h5py omegaconf root -y
conda activate mlxid_demo
pip install aare
pip install torch --index-url https://download.pytorch.org/whl/cu124   # match your CUDA version

ROOT has to come from conda (conda-forge; no usable pip wheel); everything else here is pip-installable, matching how the scripts actually import it. environment.yml in this folder is the exact conda env export of the env these scripts were tested against, for when you want a precise reproduction rather than the recipe above (conda env create -f environment.yml). If disk space is tight, conda clean --all is safe to run first -- it only clears the package cache, not any env.

Running it

Scripts 01-02 regenerate data/; skip them and just use the shipped files unless you want to change the sample sizes.

conda activate mlxid_demo

# regenerate demo data (optional -- already shipped in data/)
python scripts/01_generate_mc_singlephoton.py
python scripts/02_assemble_pileup_samples.py

# score the shipped pretrained models (and, for 1-photon, classical
# eta-interpolation) against MC ground truth
python scripts/03_eval_mc_truth.py

# train a model from scratch on the small demo dataset (sanity check, not
# a reproduction of the shipped checkpoint's accuracy)
python scripts/04_train_demo.py --photons 1
python scripts/04_train_demo.py --photons 2
python scripts/04_train_demo.py --photons 3

# qualitative inference on real measurement data (needs group storage access,
# see "What 05 does" below) -- defaults to a small, quick slice
python scripts/05_infer_measurement.py --photons 1 --target Edge2Filters_12keV
python scripts/05_infer_measurement.py --photons 2 --target Flat2Filters_12keV

The double/triple-photon model classes (src/models.py) hard-code .to('cuda') for their coordinate grids, inherited from the source repo -- a CUDA device is required for --photons 2 and --photons 3 (and for 03 and 05 where they load those checkpoints).

What 03's eta-vs-CNN comparison shows

Classical eta interpolation (the Rosenblatt-LUT method in EtaInterpolation/EtaInterpolationFunctions.py) only reconstructs one photon per cluster by construction, so this comparison is single-photon only (2/3-photon pile-up has no classical baseline to compare against -- that's the reason a CNN is used there at all). It's deliberately not the same computation as DeepLearning/Infer_1Photon.py's interpolate_eta_from_points(predictions): that applies eta interpolation as a post-hoc smoothing step on top of the CNN's own output. Here, the two methods run independently on the same raw clusters, both scored against the same MC truth.

The LUT is fit on the same full dataset it then interpolates (no train/test split) -- that matches real usage: there's no separate "training set" for eta interpolation, a measurement interpolates itself from its own charge-sharing statistics.

eval_single_photon() reports RMS vs. truth three ways: the raw eta value used directly as a position (before interpolation), the LUT-interpolated position (after), and the CNN. The before/after split is the point of including it at all -- it shows why eta interpolation exists, not just how it compares to the CNN. Raw eta values are pulled toward pixel centers by the charge-sharing response (the classic detector "S-curve" bias); the LUT interpolation removes most of that.

Reference numbers from a normal run (regenerating data/ via 01/02 first): eta interpolation ~0.048 px RMS, CNN ~0.033 px; pile-up per-photon RMS ~0.051 px (2ph) / ~0.067 px (3ph).

What 05 does

Runs the pretrained models on real measurement data that normally lives on sls group storage (/mnt/sls_det_storage/moench_data/MLXID/Samples/Measurement, not part of this repo -- each target is tens of GB across 16 chunk files per photon count). By default it reads just one chunk, capped at 50,000 events, so it finishes in a few seconds; widen with --n-chunks / --max-events-per-chunk for a fuller (slower) reconstruction.

If that group-storage folder isn't reachable, 05 automatically falls back to the small sample shipped in data/measurement_samples/ instead -- same code path, just a much smaller input (one pseudo-chunk of 30k-65k events instead of up to 16 chunks of millions). --n-chunks is capped to 1 in that case. To (re)build that fallback sample, run scripts/prepare_measurement_fallback.py (needs group-storage access itself).

There's no ground truth for real data, so this is qualitative, and the output format follows DeepLearning/Infer_{1,2,3}Photon.py's accumulate_hits() / save_results(): a super-resolution reconstructed frame, a per-pixel hit-count frame, and a sub-pixel position histogram, each saved as .npy and .png under InferenceResults/<target>/<n>ph/. The knife-edge target's super-resolution frame should show a sharp edge; the flat-field target's sub-pixel histogram should look uniform (though with the fallback sample's much smaller statistic). Unlike the Infer scripts, 05 does not also apply the post-hoc eta-interpolation smoothing step to the predictions -- just the CNN's own output.

S
Description
No description provided
Readme
108 MiB
Languages
Python 100%