Initial commit: grid-based multi-photon reconstruction

Code, configs, charge LUTs, best.pth and evaluation records for each
training run, the knife-edge / Siemens-star / XRF notebooks with
outputs, and the overnight run reports. Epoch checkpoints, inference
results and HTML exports stay local (see .gitignore).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xie_x1andClaude Opus 5.5 committed 2026-10-20 07:36:49 +02:00
commit 0f9a3f496f
222 files changed
+53227

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
__pycache__/
*.pyc
*.h5
*.pdf
# symlink to /mnt/sls_det_storage/.../InferenceResults (~19 GB)
InferenceResults
# Notebooks: current versions with outputs; dated backups stay local
Notebooks/Backup_*/
# Results: per run keep config, summaries, history, plots and best.pth;
# epoch checkpoints and last.pth stay local
Results/*_smoke_*/
Results/*/Models/*
!Results/*/Models/best.pth
# energy_response.npz (~4.5 MB each) only where notebooks read it
Results/*/energy_response.npz
!Results/260919_poly3to30keV_Moench014_c96_00/energy_response.npz
!Results/260927_poly2to20keV_Moench040_c96_00/energy_response.npz
# overnight drivers: resume markers, and HTML exports of the tracked notebooks
Results/overnight_*/state/
Results/overnight_*/report/*.html
+23
View File
@@ -0,0 +1,23 @@
# WP1.1 -- alpha(E, t) / beta(E, t) look-up table, MOENCH014 @150 V
#
# The depth grid, and therefore the drift-time grid, depends only on the sensor,
# so (energy, drift time) is a regular grid and needs no fitting in between.
sensor:
code: Moench014
hv: 150 # V
thickness_um: 320
depletion_voltage: 29.6 # V
temperature: 307.8 # K, = 273 + 20.0 + 14.8, as in McGenerator
energies:
first: 5 # keV
last: 30
step: 1
simulation:
folder: /home/xie_x1/MLED/McSimulation/BatchResults/{sensor}_{hv}V_{energy}keV
z_bins: 512
method: simu2 # 'simu1' (cpu) or 'simu2' (gpu)
output: data/charge_lut_Moench014_150V.npz
@@ -0,0 +1,23 @@
# WP1.1 -- alpha(E, t) / beta(E, t) look-up table, MOENCH014 @150 V, 3-30 keV
#
# The depth grid, and therefore the drift-time grid, depends only on the sensor,
# so (energy, drift time) is a regular grid and needs no fitting in between.
sensor:
code: Moench014
hv: 150 # V
thickness_um: 320
depletion_voltage: 29.6 # V
temperature: 307.8 # K, = 273 + 20.0 + 14.8, as in McGenerator
energies:
first: 3 # keV (2 keV exists but only 328/512 depth bins)
last: 30
step: 1
simulation:
folder: /home/xie_x1/MLED/McSimulation/BatchResults/{sensor}_{hv}V_{energy}keV
z_bins: 512
method: simu2 # 'simu1' (cpu) or 'simu2' (gpu)
output: data/charge_lut_Moench014_150V_3to30keV.npz
@@ -0,0 +1,32 @@
# WP1.1 -- alpha(E, t) / beta(E, t) look-up table, MOENCH040 @150 V, 2-20 keV
#
# Same construction as charge_lut_Moench014_3to30keV.yaml; the sensor is the
# 650 um MOENCH040 of the MaxIV knife edge (12 keV) and the SOLEIL Siemens star
# (15 keV). Training range 2-20 keV by the user's choice (2026-09-27).
#
# The copied simulation lacks the deepest depth bins at 6 / 7 / 8 keV
# (bins 266-511 / 475-511 / 492-511, next to the pixel side). At those energies
# 1.4e-5 / 2.9e-6 / 1.3e-4 of the photons convert there; the bins are filled from
# the 5 and 9 keV fits at the same depth (fill_missing: energy) and flagged in
# the table as `filled_from_energy`.
sensor:
code: Moench040
hv: 150 # V
thickness_um: 650
depletion_voltage: 34.7 # V
temperature: 307.8 # K, = 273 + 20.0 + 14.8, as in McGenerator
energies:
first: 2
last: 20
step: 1
simulation:
folder: /home/xie_x1/MLED/McSimulation/BatchResults/{sensor}_{hv}V_{energy}keV
z_bins: 512
method: simu2
fill_missing: energy
output: data/charge_lut_Moench040_150V_2to20keV.npz
+63
View File
@@ -0,0 +1,63 @@
# GridBased frame generation -- 12 keV, MOENCH040 @150 V (WP1.2, stamp-reuse variant)
#
# Frames are composed from the existing noiseless single-photon MC stamps
# (McGeneration output). Charge superposition is linear, so pile-up frames can
# be assembled by summing stamps; detector noise is applied once per frame.
meta:
energy: 12 # keV, beam energy of the stamp library
sensor: Moench040
hv: 150 # V
pixel_size_um: 25
sensor_thickness_um: 650
stamps:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/Simulation/Moench040
pattern: "12keV_Moench040_150V_{}.npz"
train_files: [0, 11] # inclusive index range -> 12 shards
val_files: [12, 13]
test_files: [14, 15]
frame:
size: 32 # training frame side (px). Fully-convolutional
# model => inference runs on full 400x400 frames.
margin: 4 # ring (px) over which photons are also thrown so
# that out-of-frame photons leak charge in
noise:
pixel_keV: 0.13 # per-pixel Gaussian, matches the iWoRiD study
common_mode_row_keV: 0.0 # WP1.3: switch on once measured
common_mode_col_keV: 0.0
edge_augmentation:
fraction: 0.08 # fraction of frames containing a sensor boundary
output:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/12keV_Moench040_150V
splits:
train:
frames_per_shard: 200_000 # 12 shards -> 2.4M frames, ~60M photons
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
val:
frames_per_shard: 80_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
test:
frames_per_shard: 80_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
# fixed-occupancy sets, for scanning performance vs flux
eval_scan:
# 20 % and 50 % are far outside the 0.2-10 % training range on purpose:
# they are where the cluster chains stop working and the grid chain has to
# be shown extrapolating. Appended, never reordered -- the per-point seed
# is base_seed + 9000 + index, so inserting a point would reshuffle the
# existing ones.
occupancies: [0.005, 0.01, 0.02, 0.05, 0.10, 0.20, 0.50]
frames_each: 8_000
stamp_files: [14, 15]
edge_fraction: 0.0
runtime:
n_workers: 16
seed: 20260830
@@ -0,0 +1,65 @@
# GridBased frame generation -- polychromatic 2-20 keV, MOENCH040 @150 V
#
# Same recipe as generate_frames_poly3to30keV_Moench014.yaml, for the 650 um
# MOENCH040 of the 12 keV MaxIV knife edge and the 15 keV SOLEIL Siemens star:
# per-photon energy (flat 2-20 keV), conversion depth from that energy's
# attenuation length, and the charge cloud of alpha(E, t) / beta(E, t).
# Pixel noise 0.13 keV = the 0.129 keV MOENCH040 delivers after the column
# common mode (README, measurement pipeline).
meta:
sensor: Moench040
hv: 150 # V
pixel_size_um: 25
sensor_thickness_um: 650
depletion_voltage: 34.7 # V
temperature: 307.8 # K
generation:
mode: lut
lut: data/charge_lut_Moench040_150V_2to20keV.npz
spectrum:
train: {mode: flat, min: 2.0, max: 20.0}
val: {mode: flat, min: 2.0, max: 20.0}
test: {mode: flat, min: 2.0, max: 20.0}
eval: {mode: flat, min: 2.0, max: 20.0}
frame:
size: 32
margin: 4
noise:
pixel_keV: 0.13
common_mode_row_keV: 0.0
common_mode_col_keV: 0.0
edge_augmentation:
fraction: 0.08
output:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly2to20keV_Moench040_150V
splits:
train:
n_shards: 12
frames_per_shard: 100_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
val:
n_shards: 2
frames_per_shard: 40_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
test:
n_shards: 2
frames_per_shard: 40_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
eval_scan:
occupancies: [0.005, 0.01, 0.02, 0.05, 0.10]
frames_each: 8_000
stamp_files: [0, 1]
edge_fraction: 0.0
runtime:
n_workers: 21
seed: 20260927
@@ -0,0 +1,70 @@
# GridBased frame generation -- polychromatic 3-30 keV, MOENCH014 @150 V (WP1.2)
#
# Same as generate_frames_poly_Moench014.yaml except for the energy range: the
# 5 keV edge biased and compressed Cr Ka (5.41 keV) in the XRF test, so the
# lower edge moves to 3 keV, >= 1 keV below the lowest line of interest.
#
# Every photon gets its own energy, its own conversion depth from that energy's
# attenuation length, and the charge cloud that the alpha(E, t) / beta(E, t)
# table gives for the resulting drift time. The single-energy stamp-reuse path
# is untouched and still lives in generate_frames_12keV.yaml.
meta:
sensor: Moench014
hv: 150 # V
pixel_size_um: 25
sensor_thickness_um: 320
depletion_voltage: 29.6 # V
temperature: 307.8 # K, = 273 + 20.0 + 14.8
generation:
mode: lut
lut: data/charge_lut_Moench014_150V_3to30keV.npz
spectrum:
# flat, so the regression carries no spectral prior of its own; the table
# covers 3-30 keV, and sampling to its edges means the outermost ~1 keV
# regresses with a one-sided prior
train: {mode: flat, min: 3.0, max: 30.0}
val: {mode: flat, min: 3.0, max: 30.0}
test: {mode: flat, min: 3.0, max: 30.0}
eval: {mode: flat, min: 3.0, max: 30.0}
frame:
size: 32
margin: 4
noise:
pixel_keV: 0.13
common_mode_row_keV: 0.0
common_mode_col_keV: 0.0
edge_augmentation:
fraction: 0.08
output:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly3to30keV_Moench014_150V
splits:
train:
n_shards: 12
frames_per_shard: 100_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
val:
n_shards: 2
frames_per_shard: 40_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
test:
n_shards: 2
frames_per_shard: 40_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
eval_scan:
occupancies: [0.005, 0.01, 0.02, 0.05, 0.10]
frames_each: 8_000
stamp_files: [0, 1]
edge_fraction: 0.0
runtime:
n_workers: 14
seed: 20260918
@@ -0,0 +1,66 @@
# GridBased frame generation -- polychromatic 5-30 keV, MOENCH014 @150 V (WP1.2)
#
# Every photon gets its own energy, its own conversion depth from that energy's
# attenuation length, and the charge cloud that the alpha(E, t) / beta(E, t)
# table gives for the resulting drift time. The single-energy stamp-reuse path
# is untouched and still lives in generate_frames_12keV.yaml.
meta:
sensor: Moench014
hv: 150 # V
pixel_size_um: 25
sensor_thickness_um: 320
depletion_voltage: 29.6 # V
temperature: 307.8 # K, = 273 + 20.0 + 14.8
generation:
mode: lut
lut: data/charge_lut_Moench014_150V.npz
spectrum:
# flat, so the regression carries no spectral prior of its own; the table
# covers 5-30 keV, and sampling to its edges means the outermost ~1 keV
# regresses with a one-sided prior
train: {mode: flat, min: 5.0, max: 30.0}
val: {mode: flat, min: 5.0, max: 30.0}
test: {mode: flat, min: 5.0, max: 30.0}
eval: {mode: flat, min: 5.0, max: 30.0}
frame:
size: 32
margin: 4
noise:
pixel_keV: 0.13
common_mode_row_keV: 0.0
common_mode_col_keV: 0.0
edge_augmentation:
fraction: 0.08
output:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly5to30keV_Moench014_150V
splits:
train:
n_shards: 12
frames_per_shard: 100_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
val:
n_shards: 2
frames_per_shard: 40_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
test:
n_shards: 2
frames_per_shard: 40_000
occupancy: {mode: loguniform, min: 0.002, max: 0.10}
eval_scan:
occupancies: [0.005, 0.01, 0.02, 0.05, 0.10]
frames_each: 8_000
stamp_files: [0, 1]
edge_fraction: 0.0
runtime:
n_workers: 14
seed: 20260901
+97
View File
@@ -0,0 +1,97 @@
# GridBased inference on measured data -- 12 keV, MOENCH040 @150 V (WP5.1/5.2)
#
# Raw ADU -> pedestal -> common mode -> 3D-LUT (all on GPU) -> GridNet -> photon
# list -> sub-pixel binned image. No cluster finder, no calibrated frames on
# disk. Select a run with --run <name>.
detector:
n_x: 101
n_y: 101
corner: [70, 170] # StCorner: ROI position inside the 400x400 chip,
# needed to crop the calibration LUT
pixel_size_um: 25.0
header_words: 56
calibration:
file: /home/xie_x1/MLED/data-process/utils/BacksidePulsing_Calibration/Moench040/Moench040_AduToKevMapping_g4_50us_150V_250423.npy
common_mode: column # MOENCH is column-parallel: 6.7 ADU RMS column
# offset. Correcting it takes the per-pixel noise
# from 0.140 to 0.129 keV -- i.e. onto the 0.13 keV
# the model was trained with.
pedestal_source: pedestal_run # 'pedestal_run' or 'per_file' (self-median)
pedestal_frames: 2000
model:
# All eight measurement runs are reconstructed with this one checkpoint so
# that the flux points, and the edge/flat pair within each point, are
# comparable. Override per run with --checkpoint only for tests.
checkpoint: /home/xie_x1/MLXID/GridBased/Results/260831_12keV_Moench040_01/Models/best.pth
threshold: 0.3
merge_distance: 0.1
inference:
n_frames: null # null = the whole run
batch_size: 512
binning_factor: 10 # 25 um / 10 = 2.5 um bins, as in the cluster-based analysis
save_photons: false # 1.9e8 photons for the full run -> ~3 GB
self_flattening: true # Rosenblatt remap of the sub-pixel phase (risk R1)
energy_bins: [0.0, 40.0, 400]
output_folder: /mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased
runs:
Edge_3filters_pos0_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_3filters_pos0_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_3filters_pos0_data_d0_f{}_0.raw
signal_files: [0, 139]
Flat_3filters_pos0_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_3filters_pos0_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_3filters_pos0_data_d0_f{}_0.raw
signal_files: [0, 139]
Edge_0filters_12keV: # 9.42 % occupancy in the illuminated area
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_0filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_0filters_data_d0_f{}_0.raw
signal_files: [0, 34]
Flat_0filters_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_0filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_0filters_data_d0_f{}_0.raw
signal_files: [0, 34]
Edge_1filters_12keV: # 3.90 % occupancy in the illuminated area
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_1filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_1filters_data_d0_f{}_0.raw
signal_files: [0, 69]
Flat_1filters_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_1filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_1filters_data_d0_f{}_0.raw
signal_files: [0, 69]
Edge_2filters_12keV: # 1.58 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_2filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_2filters_data_d0_f{}_0.raw
signal_files: [0, 69]
Flat_2filters_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_2filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_2filters_data_d0_f{}_0.raw
signal_files: [0, 69]
Pedestal_1filters_12keV: # dark run: the model must find (almost) nothing
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_1filters_pedestal_d0_f0_0.raw # a different run's pedestal,
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_1filters_pedestal_d0_f{}_0.raw # so the test is independent
signal_files: [0, 0]
Pedestal_2filters_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_2filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_2filters_pedestal_d0_f{}_0.raw
signal_files: [0, 0]
# dark run: the model must find (almost) nothing here -- the hardest fake-rate test
Pedestal_3filters_pos0_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_3filters_pos0_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_3filters_pos0_pedestal_d0_f{}_0.raw
signal_files: [0, 0]
@@ -0,0 +1,210 @@
# GridBased inference on measured data -- XRF, MOENCH014 @150 V, poly 3-30 keV model
#
# 2603 MaxIV beamtime, process/xrf. Every run is a flood field on the full
# 400x400 chip (fluorescence is isotropic, no imaging optics), so these runs
# test the energy axis and the flux behaviour, not spatial resolution:
# * line positions / widths against tabulated energies (per run, `lines`)
# * Kalpha pairs a few keV apart must come out resolved
# * line positions and pile-up versus flux (factor 10 -> 1)
# * sub-pixel flatness of a uniform illumination (self_flattening)
# * fake rate on a dark run
#
# `factor` is the slit factor of the acquisition script: gap = 1.1 mm / factor,
# so factor 1 is the highest flux. Each data file holds 100 000 frames, each
# pedestal file 1000 frames taken ~3 min before its data run.
#
# Survey of all 58 runs (2000 frames each, 3x3-sum local maxima; see the
# comments per run): pixel noise after column common mode 0.126 keV (0.144 on
# AgCd), matching the 0.13 keV of the training frames; occupancy 0.6-7 %, all
# inside the 0.2-10 % training range. Beam was 12 keV for the Cu/Cr/Co and
# pigment runs (elastic line at 12.0 keV); for the Ag runs it was above the Sb
# K edge (30.5 keV), and their Compton/elastic bump at ~28.5-29 keV sits at the
# top of the training range.
#
# `lines` are Kalpha1/Kalpha2 centroids (2:1) and Kbeta1 in keV, for the
# downstream line fit; 05_infer_measurement.py ignores them.
#
# The Moench014 LUT has 1640 bins, the abs()/sign() convention of
# UsefulFuncs.convertAduFrameToEnergyFrame; GpuCalibrator handles it.
detector:
n_x: 400
n_y: 400
corner: [0, 0] # full chip, the LUT is used uncropped
pixel_size_um: 25.0
header_words: 56
calibration:
file: /home/xie_x1/MLED/data-process/utils/BacksidePulsing_Calibration/Moench014/Moench014_AduToKevMapping_g4_50us_250305.npy
common_mode: column_masked # median over the pixels without charge. The plain
# column median is pulled up by the photons: -4.0 /
# -7.2 meV per pixel on AgMo / CuCrCo factor 10,
# i.e. -36 / -65 meV on a 3x3 sum, growing with flux
pedestal_source: pedestal_run
pedestal_frames: 1000 # all a pedestal file has
model:
# 3-30 keV, [32, 64, 96, 96], chosen 2026-09-19 from the four sizes in
# Results/overnight_poly3to30keV/report. The previous default, 5-30 keV
# [32, 64, 128, 128], is Results/260901_poly5to30keV_Moench014_00; its XRF
# reconstructions carry the tag _poly5to30.
checkpoint: /home/xie_x1/MLXID/GridBased/Results/260919_poly3to30keV_Moench014_c96_00/Models/best.pth
threshold: 0.3
merge_distance: 0.1
inference:
n_frames: null # null = the whole run
precision: fp16 # FP16 autocast + channels-last; offsets and energy of
# the candidate cells recomputed in FP32 (an FP16 head
# rounds E to 7-15 meV steps: a comb in 50 eV spectra).
# Photon list = FP32 within ~20 nm / ~4 meV.
batch_size: 32 # 2.6 GB peak for the model in FP16 (7.9 GB in FP32);
# larger batches are not faster
binning_factor: 10
save_photons: false # ~1.8e8 photons per run at factor 10 -> ~3 GB
self_flattening: true # the 'before' phase histogram of a flood field is
# the direct R1 measurement
energy_bins: [0.0, 40.0, 800] # 0.05 keV, for fitting line positions
output_folder: /mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased/XRF_2603MaxIV
runs:
# --- two elements, Kalpha lines well separated --------------------------
# Mo Ka 17.44 / Ag Ka 22.11: 4.7 keV apart, mid-range, with both Kbeta lines
# resolved as well -- the cleanest multi-line run. Full flux series.
AgMo_1_factor_10: # occupancy 0.75 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgMo_1_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgMo_1_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Mo_Ka: 17.44, Mo_Kb: 19.61, Ag_Ka: 22.11, Ag_Kb: 24.94}
AgMo_1_factor_4: # 1.4 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgMo_1_factor_4_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgMo_1_factor_4_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Mo_Ka: 17.44, Mo_Kb: 19.61, Ag_Ka: 22.11, Ag_Kb: 24.94}
AgMo_1_factor_2: # 2.5 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgMo_1_factor_2_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgMo_1_factor_2_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Mo_Ka: 17.44, Mo_Kb: 19.61, Ag_Ka: 22.11, Ag_Kb: 24.94}
# with the plain column median the 3x3-sum centroids here sat 0.06-0.07 keV
# below factor 10 (median biased by the hits); column_masked removes that
AgMo_1_factor_1: # 4.0 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgMo_1_factor_1_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgMo_1_factor_1_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Mo_Ka: 17.44, Mo_Kb: 19.61, Ag_Ka: 22.11, Ag_Kb: 24.94}
# Ag Ka 22.11 / Sb Ka 26.28: 4.2 keV apart. Sb Kb 29.73 merges with the
# scatter bump at the top edge of the training range.
AgSb_1_factor_10: # 0.70 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgSb_1_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgSb_1_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Ag_Ka: 22.11, Ag_Kb: 24.94, Sb_Ka: 26.28, Sb_Kb: 29.73}
# Cr Ka 5.41 / Cu Ka 8.04: 2.6 keV apart, at the low end. Cr Ka was 0.4 keV
# above the training edge of the 5-30 keV model (+33 meV, width compressed);
# the 3-30 keV model puts it 2.4 keV inside its range. CuCr_1..4 are the same pair
# at different Cr:Cu ratios (3x3-sum peak heights 17 : 4.2 : 1.2 : 0.43).
CuCr_1_factor_10: # 1.0 %, Cr-dominated
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCr_1_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCr_1_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cr_Ka: 5.41, Cr_Kb: 5.95, Cu_Ka: 8.04, Cu_Kb: 8.91, elastic: 12.0}
CuCr_2_factor_10: # 1.0 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCr_2_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCr_2_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cr_Ka: 5.41, Cr_Kb: 5.95, Cu_Ka: 8.04, Cu_Kb: 8.91, elastic: 12.0}
CuCr_3_factor_10: # 1.2 %, Cr and Cu about equal
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCr_3_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCr_3_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cr_Ka: 5.41, Cr_Kb: 5.95, Cu_Ka: 8.04, Cu_Kb: 8.91, elastic: 12.0}
CuCr_4_factor_10: # 1.2 %, Cu-dominated
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCr_4_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCr_4_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cr_Ka: 5.41, Cr_Kb: 5.95, Cu_Ka: 8.04, Cu_Kb: 8.91, elastic: 12.0}
# --- three elements ------------------------------------------------------
# Cr Ka 5.41 / Co Ka 6.93 / Cu Ka 8.04: 1.5 and 1.1 keV apart, and Co Kb 7.65
# sits on the low flank of Cu Ka -- in the 3x3 sums Cu is only a shoulder.
# Pg17 / Pg28 / Cu below are the single-line references for it.
# Full flux series.
CuCrCo_1_factor_10: # 1.2 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cr_Ka: 5.41, Cr_Kb: 5.95, Co_Ka: 6.93, Co_Kb: 7.65, Cu_Ka: 8.04, Cu_Kb: 8.91, elastic: 12.0}
CuCrCo_1_factor_4: # 2.2 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_4_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_4_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cr_Ka: 5.41, Cr_Kb: 5.95, Co_Ka: 6.93, Co_Kb: 7.65, Cu_Ka: 8.04, Cu_Kb: 8.91, elastic: 12.0}
CuCrCo_1_factor_2: # 3.9 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_2_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_2_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cr_Ka: 5.41, Cr_Kb: 5.95, Co_Ka: 6.93, Co_Kb: 7.65, Cu_Ka: 8.04, Cu_Kb: 8.91, elastic: 12.0}
CuCrCo_1_factor_1: # 5.9 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_1_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_1_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cr_Ka: 5.41, Cr_Kb: 5.95, Co_Ka: 6.93, Co_Kb: 7.65, Cu_Ka: 8.04, Cu_Kb: 8.91, elastic: 12.0}
# --- single-line references ---------------------------------------------
# One fluorescence line plus the 12 keV elastic line: energy scale and
# resolution at each of the CuCrCo energies without overlaps. In the 3x3
# sums each also shows a pile-up peak at twice its line (10.8 / 13.8 /
# 15.9 keV); a photon-separating reconstruction should shrink it.
Pg17_factor_10: # 1.6 %, pigment green 17 (Cr2O3)
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/Pg17_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/Pg17_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cr_Ka: 5.41, Cr_Kb: 5.95, elastic: 12.0}
Pg28_factor_10: # 1.1 %, pigment green 28 (cobalt titanate; Ti Ka
# 4.51 is below the training range)
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/Pg28_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/Pg28_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Co_Ka: 6.93, Co_Kb: 7.65, elastic: 12.0}
Cu_factor_10: # 1.5 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/Cu_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/Cu_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Cu_Ka: 8.04, Cu_Kb: 8.91, elastic: 12.0}
# --- resolution limit ------------------------------------------------------
# Ag Ka 22.11 / Cd Ka 23.11: only 1.0 keV apart, merged into one peak in the
# 3x3 sums. Highest noise of the set (0.144 keV).
AgCd_1_factor_10: # 1.4 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgCd_1_factor_10_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/AgCd_1_factor_10_data_d0_f{}_0.raw
signal_files: [0, 0]
lines: {Ag_Ka: 22.11, Cd_Ka: 23.11, Ag_Kb: 24.94, Cd_Kb: 26.10}
# --- dark run ----------------------------------------------------------------
# the shutter is closed during pedestal runs: the model must find (almost)
# nothing. Pedestal from the run taken 3 min earlier, so the test is
# independent of the frames it is applied to.
Pedestal_CuCrCo_1:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_4_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/CuCrCo_1_factor_10_pedestal_d0_f{}_0.raw
signal_files: [0, 0]
@@ -0,0 +1,96 @@
# GridBased inference on measured data -- 12 keV knife edge, MOENCH040 @150 V,
# with the polychromatic 2-20 keV model (overnight 2026-09-27/28)
#
# Identical to infer_measurement_12keV.yaml -- same runs, same ROI readout, same
# pedestal, same column common mode, same self-flattening -- so that the only
# difference from the published 12 keV GridBased images is the model. Only the
# precision differs: FP16 with the energy head in FP32 (predict_fp16), as for
# every poly model. The checkpoint is passed with --checkpoint by
# scripts/overnight_poly2to20keV_Moench040_partB.sh and the outputs carry the tag
# _poly2to20, next to the 12 keV model's.
detector:
n_x: 101
n_y: 101
corner: [70, 170] # StCorner: ROI position inside the 400x400 chip
pixel_size_um: 25.0
header_words: 56
calibration:
file: /home/xie_x1/MLED/data-process/utils/BacksidePulsing_Calibration/Moench040/Moench040_AduToKevMapping_g4_50us_150V_250423.npy
common_mode: column # as for the 12 keV model's runs
pedestal_source: pedestal_run
pedestal_frames: 2000
model:
checkpoint: null # given on the command line (--checkpoint)
threshold: 0.3
merge_distance: 0.1
inference:
n_frames: null # null = the whole run
precision: fp16 # FP16 backbone, FP32 energy/offsets of the candidates
batch_size: 512
binning_factor: 10 # 2.5 um bins
save_photons: false
self_flattening: true
energy_bins: [0.0, 30.0, 600] # 50 eV
output_folder: /mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased
runs:
Edge_3filters_pos0_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_3filters_pos0_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_3filters_pos0_data_d0_f{}_0.raw
signal_files: [0, 139]
Flat_3filters_pos0_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_3filters_pos0_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_3filters_pos0_data_d0_f{}_0.raw
signal_files: [0, 139]
Edge_0filters_12keV: # 9.42 % occupancy in the illuminated area
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_0filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_0filters_data_d0_f{}_0.raw
signal_files: [0, 34]
Flat_0filters_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_0filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_0filters_data_d0_f{}_0.raw
signal_files: [0, 34]
Edge_1filters_12keV: # 3.90 % occupancy in the illuminated area
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_1filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_1filters_data_d0_f{}_0.raw
signal_files: [0, 69]
Flat_1filters_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_1filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_1filters_data_d0_f{}_0.raw
signal_files: [0, 69]
Edge_2filters_12keV: # 1.58 %
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_2filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_2filters_data_d0_f{}_0.raw
signal_files: [0, 69]
Flat_2filters_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_2filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_2filters_data_d0_f{}_0.raw
signal_files: [0, 69]
Pedestal_1filters_12keV: # dark run: the model must find (almost) nothing
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_1filters_pedestal_d0_f0_0.raw # a different run's pedestal,
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_1filters_pedestal_d0_f{}_0.raw # so the test is independent
signal_files: [0, 0]
Pedestal_2filters_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/flat_2filters_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_2filters_pedestal_d0_f{}_0.raw
signal_files: [0, 0]
# dark run: the model must find (almost) nothing here -- the hardest fake-rate test
Pedestal_3filters_pos0_12keV:
pedestal: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_3filters_pos0_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/mtf/edge_3filters_pos0_pedestal_d0_f{}_0.raw
signal_files: [0, 0]
@@ -0,0 +1,80 @@
# GridBased inference on measured data -- 15 keV Siemens star, MOENCH040 @150 V,
# SOLEIL 2504 beamtime, with the polychromatic 2-20 keV model (2026-09-27/28)
#
# The star was recorded on the full 400x400 chip, 2000 files x 10 000 frames.
# DataProcess/Configs.py analyses two regions of it:
# SiemenStarLowerLeft x 130-240, y 110-220
# SiemenStarLowerRight x 235-345, y 110-220
# They share the same rows, so one crop covers both, extended by the halo R = 4
# on every side: x 126-349, y 106-224. Its valid region is exactly x 130-345,
# y 110-220; the notebook cuts the two regions out of it. The frames of each
# file are read as that 118-row band only (RawFrameReader crop, ~3x faster than
# whole frames on the NFS store), so both regions come out of one pass.
#
# The run is split into four parts of 500 files each so that a partial night
# still leaves complete images; the notebook sums the parts it finds.
detector:
n_x: 400
n_y: 400
corner: [0, 0] # full-chip frames on disk
crop: [126, 349, 106, 224] # x0, x1, y0, y1 of the reconstructed window
pixel_size_um: 25.0
header_words: 56
calibration:
file: /home/xie_x1/MLED/data-process/utils/BacksidePulsing_Calibration/Moench040/Moench040_AduToKevMapping_g4_50us_150V_250423.npy
common_mode: column_masked # median over the uncharged pixels of the 118 rows. The
# plain median (the 2026-09-27 run, tag _poly2to20) is pulled
# up by the photons at ~5 % occupancy: 15 keV line at 14.47 keV
pedestal_source: pedestal_run
pedestal_frames: 2000 # of the 10 000 in the pedestal file
model:
checkpoint: null # given on the command line (--checkpoint)
threshold: 0.3
merge_distance: 0.1
inference:
n_frames: null
precision: fp16
batch_size: 512
binning_factor: 10 # 2.5 um bins
save_photons: false
self_flattening: streaming # ~820 photons per frame here: the in-memory
# remap would need ~100 GB per 5 M frames, so the
# Rosenblatt table is built from the first 1e8
# photons and every photon remapped as it comes
# (src/flattening.py; identical to the in-memory
# version when built from the same photons)
flattening_lut_photons: 300000000 # buffered for the tables, ~2.4 GB
# one table per local-occupancy class (photons / px / frame over the 9x9
# receptive field): the sub-pixel phase depends on the flux, and one table for
# the whole crop left a 25 um grid in the dim star of LowerLeft (4.4 % sub-pixel
# rms at ~0.7 % occupancy -> 0.4 % with the classes; 600 k-frame test, 09-28)
flattening_occupancy_edges: [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07]
energy_bins: [0.0, 30.0, 600] # 50 eV
output_folder: /mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased
runs:
SiemensStar_15keV_part0:
pedestal: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_signal_d0_f{}_0.raw
signal_files: [0, 499]
SiemensStar_15keV_part1:
pedestal: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_signal_d0_f{}_0.raw
signal_files: [500, 999]
SiemensStar_15keV_part2:
pedestal: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_signal_d0_f{}_0.raw
signal_files: [1000, 1499]
SiemensStar_15keV_part3:
pedestal: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_signal_d0_f{}_0.raw
signal_files: [1500, 1999]
# smoke test: one file, 10 000 frames
SiemensStar_15keV_smoke:
pedestal: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_pedestal_d0_f0_0.raw
signal_pattern: /mnt/sls_det_storage/moench_data/2504_SoleilBeamtime/Moench040_g4_SimonStars_150V_50us/15keV_signal_d0_f{}_0.raw
signal_files: [1000, 1000]
+49
View File
@@ -0,0 +1,49 @@
# GridBased training -- 12 keV, MOENCH040 @150 V (WP3, monochromatic gate G2)
experiment:
name: "12keV_Moench040"
results_dir: "Results"
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/12keV_Moench040_150V
train_shards: [0, 11] # inclusive
val_shards: [0, 1]
max_train_frames: null # null = all
max_val_frames: 8000
input_scale: 12.0 # keV; frames are divided by this
energy_scale: 12.0 # keV; energy target is E / energy_scale
augment: true # 8-fold D4, a true symmetry of the problem
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels: [32, 64, 64, 64] # 4 x conv3x3 -> receptive-field radius R = 4 px
predict_energy: true
norm: "none" # see models.py: 'group' breaks strict locality
prior_prob: 0.01 # focal-loss bias init
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 0.1
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01 # px = 0.25 um, as for the cluster-based models
smooth_l1_beta_energy: 0.02
training:
epochs: 150
lr: 1.0e-3
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 4
amp: true
seed: 0
checkpoint_epochs: [25, 50, 75, 100, 125, 150]
eval:
threshold: 0.3 # from the ROC of run 260830_00: eff plateaus below 0.3
merge_distance: 0.1 # px; position-space duplicate suppression
match_radius: 1.0 # px; 25 um
pixel_size_um: 25.0
@@ -0,0 +1,59 @@
# GridBased training -- polychromatic 2-20 keV, MOENCH040 @150 V, [32, 64, 96, 96]
#
# The default poly architecture (Results/260919_poly3to30keV_Moench014_c96_00)
# retrained for the 650 um MOENCH040 on flat 2-20 keV frames, to be tested on
# the 12 keV MaxIV knife edge and the 15 keV SOLEIL Siemens star. Everything
# but the data and the number of epochs is identical to the MOENCH014 c96.
#
# Same architecture and the same losses as the 12 keV configuration; what
# changes is that the energy head now carries information, so it gets a real
# weight and a scale that is not tied to a beam energy.
experiment:
name: "poly2to20keV_Moench040_c96"
results_dir: "Results"
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly2to20keV_Moench040_150V
train_shards: [0, 11]
val_shards: [0, 1]
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0 # keV, mid-range; frames are divided by this
energy_scale: 15.0 # keV, the energy target is E / energy_scale
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels: [32, 64, 96, 96] # last two layers 96 wide: -35 % MAC, +23 % speed (FP16)
predict_energy: true
norm: "none"
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0 # 0.1 at a single energy, where the head learned a
# constant; here it carries the spectrum
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03 # ~ the 0.39 keV noise floor of a 3x3 sum, / 15 keV
training:
epochs: 200 # the 3-30 keV c96 run was within 0.004 % of its best val loss at 200
lr: 1.0e-3
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs: [50, 100, 150, 200]
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
@@ -0,0 +1,58 @@
# GridBased training -- polychromatic 3-30 keV, MOENCH014 @150 V, variant c128
#
# One of four models trained on the same 3-30 keV frames to compare network
# sizes (Results/overnight_poly3to30keV). Everything but model.channels is
# identical to train_grid_poly_Moench014.yaml.
#
# Same architecture and the same losses as the 12 keV configuration; what
# changes is that the energy head now carries information, so it gets a real
# weight and a scale that is not tied to a beam energy.
experiment:
name: "poly3to30keV_Moench014_c128"
results_dir: "Results"
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly3to30keV_Moench014_150V
train_shards: [0, 11]
val_shards: [0, 1]
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0 # keV, mid-range; frames are divided by this
energy_scale: 15.0 # keV, the energy target is E / energy_scale
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels: [32, 64, 128, 128] # reference width, as the 5-30 keV model
predict_energy: true
norm: "none"
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0 # 0.1 at a single energy, where the head learned a
# constant; here it carries the spectrum
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03 # ~ the 0.39 keV noise floor of a 3x3 sum, / 15 keV
training:
epochs: 300
lr: 1.0e-3
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs: [50, 100, 150, 200, 250, 300]
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
@@ -0,0 +1,58 @@
# GridBased training -- polychromatic 3-30 keV, MOENCH014 @150 V, variant c64
#
# One of four models trained on the same 3-30 keV frames to compare network
# sizes (Results/overnight_poly3to30keV). Everything but model.channels is
# identical to train_grid_poly_Moench014.yaml.
#
# Same architecture and the same losses as the 12 keV configuration; what
# changes is that the energy head now carries information, so it gets a real
# weight and a scale that is not tied to a beam energy.
experiment:
name: "poly3to30keV_Moench014_c64"
results_dir: "Results"
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly3to30keV_Moench014_150V
train_shards: [0, 11]
val_shards: [0, 1]
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0 # keV, mid-range; frames are divided by this
energy_scale: 15.0 # keV, the energy target is E / energy_scale
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels: [32, 64, 64, 64] # the 12 keV model size: -61 % MAC, +83 % speed (FP16)
predict_energy: true
norm: "none"
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0 # 0.1 at a single energy, where the head learned a
# constant; here it carries the spectrum
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03 # ~ the 0.39 keV noise floor of a 3x3 sum, / 15 keV
training:
epochs: 300
lr: 1.0e-3
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs: [50, 100, 150, 200, 250, 300]
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
@@ -0,0 +1,58 @@
# GridBased training -- polychromatic 3-30 keV, MOENCH014 @150 V, variant c96
#
# One of four models trained on the same 3-30 keV frames to compare network
# sizes (Results/overnight_poly3to30keV). Everything but model.channels is
# identical to train_grid_poly_Moench014.yaml.
#
# Same architecture and the same losses as the 12 keV configuration; what
# changes is that the energy head now carries information, so it gets a real
# weight and a scale that is not tied to a beam energy.
experiment:
name: "poly3to30keV_Moench014_c96"
results_dir: "Results"
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly3to30keV_Moench014_150V
train_shards: [0, 11]
val_shards: [0, 1]
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0 # keV, mid-range; frames are divided by this
energy_scale: 15.0 # keV, the energy target is E / energy_scale
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels: [32, 64, 96, 96] # last two layers 96 wide: -35 % MAC, +23 % speed (FP16)
predict_energy: true
norm: "none"
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0 # 0.1 at a single energy, where the head learned a
# constant; here it carries the spectrum
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03 # ~ the 0.39 keV noise floor of a 3x3 sum, / 15 keV
training:
epochs: 300
lr: 1.0e-3
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs: [50, 100, 150, 200, 250, 300]
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
@@ -0,0 +1,58 @@
# GridBased training -- polychromatic 3-30 keV, MOENCH014 @150 V, variant r3
#
# One of four models trained on the same 3-30 keV frames to compare network
# sizes (Results/overnight_poly3to30keV). Everything but model.channels is
# identical to train_grid_poly_Moench014.yaml.
#
# Same architecture and the same losses as the 12 keV configuration; what
# changes is that the energy head now carries information, so it gets a real
# weight and a scale that is not tied to a beam energy.
experiment:
name: "poly3to30keV_Moench014_r3"
results_dir: "Results"
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly3to30keV_Moench014_150V
train_shards: [0, 11]
val_shards: [0, 1]
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0 # keV, mid-range; frames are divided by this
energy_scale: 15.0 # keV, the energy target is E / energy_scale
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels: [32, 64, 128] # 3 layers, R = 3 px (7x7 field): -61 % MAC, +78 % speed (FP16)
predict_energy: true
norm: "none"
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0 # 0.1 at a single energy, where the head learned a
# constant; here it carries the spectrum
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03 # ~ the 0.39 keV noise floor of a 3x3 sum, / 15 keV
training:
epochs: 300
lr: 1.0e-3
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs: [50, 100, 150, 200, 250, 300]
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
+56
View File
@@ -0,0 +1,56 @@
# GridBased training -- polychromatic 5-30 keV, MOENCH014 @150 V
#
# Same architecture and the same losses as the 12 keV configuration; what
# changes is that the energy head now carries information, so it gets a real
# weight and a scale that is not tied to a beam energy.
experiment:
name: "poly5to30keV_Moench014"
results_dir: "Results"
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly5to30keV_Moench014_150V
train_shards: [0, 11]
val_shards: [0, 1]
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0 # keV, mid-range; frames are divided by this
energy_scale: 15.0 # keV, the energy target is E / energy_scale
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels: [32, 64, 128, 128] # still 4 x conv3x3, so R = 4 px; wider, because
# the cloud size now has to be disentangled from
# the energy rather than mapping straight to depth
predict_energy: true
norm: "none"
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0 # 0.1 at a single energy, where the head learned a
# constant; here it carries the spectrum
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03 # ~ the 0.39 keV noise floor of a 3x3 sum, / 15 keV
training:
epochs: 300
lr: 1.0e-3
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs: [50, 100, 150, 200, 250, 300]
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
+549
View File
@@ -0,0 +1,549 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d50894bb",
"metadata": {},
"source": [
"# Knife edge, 0 filter — GridBased against eta interpolation\n",
"\n",
"The highest flux point of the scan: **10.8 % occupancy** (flat field, inside\n",
"the ESF ROI), 14.4x the 3-filter point and above where the cluster chain was ever\n",
"operated.\n",
"\n",
"No cluster-based reconstruction exists at this flux point: the beamtime\n",
"only wrote cluster files for the 1-, 2- and 3-filter measurements\n",
"(`Samples/Measurement/2603MaxIV_*Filters_12keV`), so neither the CNN\n",
"chain nor eta interpolation has a 0-filter image. Only the GridBased reconstruction\n",
"is analysed here; the classical chains are compared against GridBased in the other three notebooks.\n",
"\n",
"\n",
"**Edge width (revised 2026-09-28).** The ESF is binned and fitted exactly as\n",
"`Plotting/fig_knife_edge.py --fit odr` does it: `generate_presampled_esf()` and\n",
"`get_odr_fitting()` from `DeepLearning/src/funcs_postprocess.py` (bin content\n",
"Σk/Σf), symmetric ±4σ window, **ESF bin width 0.3 µm**. The ODR model is\n",
"`p0/2 (1 + erf((x - p1)/(sqrt(2) sigma))) + p3`, so the fit gives **sigma**, the\n",
"width of the Gaussian LSF, which is what the tables quote, with\n",
"`MTF50 [lp/mm] = 1000 sqrt(ln 2 / 2) / (pi sigma)`; MTF50 and MTF(200 lp/mm) are\n",
"also measured from the LSF (`Plotting/knife_edge_numbers.py`: first crossing,\n",
"central-difference correction), and the relative DQE uses the latter. Only the\n",
"flattened GridBased reconstruction is analysed. Same processing as\n",
"`KnifeEdge_poly2to20keV_Moench040.ipynb`.\n",
"\n",
"**Occupancy** is that of the flat field inside the ESF ROI: the reconstructed\n",
"photons per 25 µm pixel per frame over `ROI // 10` (x 33–46, y 26–49 of the\n",
"99 × 99 frame), the pixels over which `DeepLearning/Notebooks/KnifeEdge_3filters_pos0.ipynb`\n",
"sums the deposited energy (0.705 % at 3 filters from the energy, 0.747 % here\n",
"from the photons of the 12 keV model).\n",
"\n",
"*Until 2026-09-28 this notebook took the fitted sigma for sqrt(2) sigma (so its\n",
"MTF50 was sqrt(2) too high), used 0.5 µm bins, and binned the ESF with an older\n",
"copy of `generate_presampled_esf` (inverse-variance mean of k/f, biased low in\n",
"the toe, sigma ~3 % too small). The previous version is in\n",
"`Backup_260928_pre_sigma_fix/`.*\n",
"\n",
"The ROI, the oversampled ESF and the erf fit are the ones from\n",
"`DeepLearning/Notebooks/KnifeEdge_3filters_pos0.ipynb`; the edge sits at the\n",
"same place at all three flux points, so the same ROI holds throughout and the\n",
"three notebooks are directly comparable.\n",
"\n",
"Run in the `pytorch_nightly` environment (ROOT + scipy + jupyter)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "3975e8f9",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:08.904496Z",
"iopub.status.busy": "2026-09-28T08:23:08.904413Z",
"iopub.status.idle": "2026-09-28T08:23:10.494059Z",
"shell.execute_reply": "2026-09-28T08:23:10.493725Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Welcome to JupyROOT 6.28/10\n"
]
}
],
"source": [
"import json\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"REPO = Path('/home/xie_x1/MLXID/GridBased')\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/DeepLearning/src') # the code Plotting/ uses\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/Plotting')\n",
"from ROOT import TCanvas, gROOT\n",
"gROOT.SetBatch(True)\n",
"from funcs_postprocess import generate_presampled_esf, get_odr_fitting\n",
"from knife_edge_numbers import mtf as mtf_from_lsf # first crossing, sinc-corrected\n",
"\n",
"GRID = Path('/mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased')\n",
"PUBLISHED = Path('/home/xie_x1/MLXID/DeepLearning/InferenceResults')\n",
"ETA = Path('/home/xie_x1/MLXID/EtaInterpolation/Results')\n",
"\n",
"EDGE_RUN, FLAT_RUN = 'Edge_0filters_12keV', 'Flat_0filters_12keV'\n",
"BIN_UM = 2.5 # super-pixel = 25 um / 10\n",
"ROI = (330, 465, 260, 490) # x1, x2, y1, y2, as published\n",
"CROP = slice(10, -10) # 1010 -> 990, as published\n",
"BIN_WIDTH_UM = 0.3 # presampled ESF bin, as Plotting/ and the manuscript\n",
"\n",
"plt.rcParams.update({'figure.dpi': 110, 'image.cmap': 'gray',\n",
" 'image.origin': 'lower', 'image.interpolation': 'nearest'})"
]
},
{
"cell_type": "markdown",
"id": "8c496448",
"metadata": {},
"source": [
"## 1. Load every reconstruction of the same measurement"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4bcf2cfb",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:10.495691Z",
"iopub.status.busy": "2026-09-28T08:23:10.495537Z",
"iopub.status.idle": "2026-09-28T08:23:10.618415Z",
"shell.execute_reply": "2026-09-28T08:23:10.618180Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"GridBased model epoch 135, 3,500,000 frames, occupancy 10.770 % (flat field, ESF ROI)\n",
" sub-pixel excess rms 18.3 % -> 1.6 % after flattening\n",
"\n",
"variant edge photons flat photons relative\n",
"GridBased flat 6.8684e+08 1.1910e+09 1.000\n"
]
}
],
"source": [
"def crop(image):\n",
" \"\"\"The published frames are cropped by 10 super-pixels on each side.\"\"\"\n",
" return image[CROP, CROP] if image.shape[0] == 1010 else image\n",
"\n",
"\n",
"for run in (EDGE_RUN, FLAT_RUN):\n",
" if not (GRID / run / 'summary.json').exists():\n",
" raise SystemExit(\n",
" f'{run} has not been reconstructed yet. Run:\\n'\n",
" f' python scripts/05_infer_measurement.py '\n",
" f'--config Configs/infer_measurement_12keV.yaml --run {run}')\n",
"\n",
"grid_summary = json.load(open(GRID / EDGE_RUN / 'summary.json'))\n",
"flat_summary = json.load(open(GRID / FLAT_RUN / 'summary.json'))\n",
"\n",
"\n",
"def grid_image(run, flattened):\n",
" name = 'binned_image_flattened.npy' if flattened else 'binned_image.npy'\n",
" return np.load(GRID / run / name).astype(float)\n",
"\n",
"\n",
"variants = {\n",
" 'GridBased flat': (grid_image(EDGE_RUN, True), grid_image(FLAT_RUN, True)),\n",
"}\n",
"\n",
"# This flux point was never clustered: only 2603MaxIV_{Edge,Flat}{1,2,3}Filters\n",
"# exist under Samples/Measurement, so there is no CNN and no eta image to load\n",
"# here. (Until 2026-09-17 this cell loaded the *1-filter* eta image, which is a\n",
"# different measurement at 2.4x the flux and was not comparable with anything.)\n",
"\n",
"ORDER = ['GridBased flat']\n",
"variants = {name: (crop(e), crop(f)) for name, (e, f) in variants.items()}\n",
"variants = {name: variants[name] for name in ORDER}\n",
"\n",
"def flat_occupancy(run):\n",
" # reconstructed photons per 25 um pixel per frame in the flat field, inside the\n",
" # ESF ROI: ROI // 10 in pixels of the 99 x 99 frame, both ends included as in\n",
" # DeepLearning/Notebooks; +1 because the 101 x 101 frame is cropped by 1 px\n",
" summary = json.load(open(GRID / run / 'summary.json'))\n",
" native = np.load(GRID / run / 'native_image.npy') / summary['n_frames']\n",
" x0, x1, y0, y1 = (v // 10 for v in ROI)\n",
" return float(native[y0 + 1:y1 + 2, x0 + 1:x1 + 2].mean())\n",
"\n",
"\n",
"occupancy = flat_occupancy(FLAT_RUN)\n",
"print(f\"GridBased model epoch {grid_summary['epoch']}, \"\n",
" f\"{grid_summary['n_frames']:,} frames, \"\n",
" f\"occupancy {occupancy*100:.3f} % (flat field, ESF ROI)\")\n",
"print(f\" sub-pixel excess rms {grid_summary['subpixel_nonuniformity_before']*100:.1f} % -> \"\n",
" f\"{grid_summary['subpixel_nonuniformity_after']*100:.1f} % after flattening\\n\")\n",
"print(f\"{'variant':16s} {'edge photons':>14} {'flat photons':>14} relative\")\n",
"reference = variants['GridBased flat'][0].sum()\n",
"for name, (e, f) in variants.items():\n",
" print(f'{name:16s} {e.sum():14.4e} {f.sum():14.4e} {e.sum()/reference:8.3f}')"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "21d95658",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:10.619670Z",
"iopub.status.busy": "2026-09-28T08:23:10.619469Z",
"iopub.status.idle": "2026-09-28T08:23:10.852109Z",
"shell.execute_reply": "2026-09-28T08:23:10.851858Z"
}
},
"outputs": [
{
"data": {
"image/png": "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1tZWzJkzB6NHj8brr7+OSZMmYcCAAR3G/t///V+cddZZ2HXXXXHFFVfA5/PhwQcfrJkrffLJJ+Pee+/FtGnTcOKJJwIA/vCHP+Doo4/G7bffjjlz5nT+IKqw/fbbY9ddd8WLL75oe/1Pf/oT3G531dd33313DB48uOp4BxxwAG666Sacf/75OProo00IbzQatR33zjvvYPLkyfjBD36AGTNm4O9//zsWLFiAlpYWPPvss53O+ac//SkaGxvxxBNP4KabbsLAgQMB2MOLAWDevHlIJBI4+eSTEY1Gce+99+KCCy7AkCFDcMIJJ5jjeuNzAgDpdBoHHnggxo0bh6uuugorV67E//7v/+Lb3/42Xn/9dey+++5djtEZhx9+OOLxOC699FI0Nzfj17/+NQ4//HA888wzOPTQQ3s01vr163HQQQehUqng9NNPxw477ICWlha89957+POf/4yTTz7Zdvwdd9yB1atX47TTTsOgQYPwhz/8AT/72c8Qi8VsYdN9+e+lMyqVSlVxDwD19fXm+Y0aNQr/+te/sGDBApx77rndLmRXbey6uroOKTDdpVAoYOXKlSYHnCxatAjZbBbf//73EYlEqp7rdrsxe/ZsnH766Xj88cex2267bdQcFEVRFEVRlK8gVg+56667LADWyy+/bOVyOeuEE06w3G639atf/arDsSNGjLAAWA888IDt9WuuucYCYD333HPmtZdfftkCYN11110dxjjwwANtr2WzWWvbbbe1JkyYYOVyOdt7jz76qAXAuvvuu7u8lz322MPaYYcdrPXr19te/9vf/ma53W5r/vz55rXvf//7FgDriSeeqHovI0aMMK+1tLRYkUjE2nHHHa1EImFez+Vy1t57720BsC6//HLz+sKFCy0A1o033thhjt/97neteDxuG6cnnHnmmZbL5bLWrl1rWZZlffrppxYAcz+ffPKJZVmW9cUXX1gul8s699xzzbnVngnPl/OXALBcLpf12muv2V4//fTTLQDWv/71ry7nfPnll1sArE8//bTDe/z8jR071vbsU6mUNWDAAGu//fYzr/XW5+TAAw+0AFhnnnmm7fU33njDcrlc1iGHHGJeq/U5rnVfJ510kgXAOvLII61yuWxe/+yzz6xwOGztvPPOVqVSMa9X+/dw4IEH2j5/f/zjHy0A1kMPPdTpfXGu2267rdXc3GxeL5fL1ujRo60hQ4bYju+rfy+dwf+G1Pp69dVXbfPw+/0WAGv48OHWD37wA+uWW26x/vGPf1Qdm8+12tczzzzT5dz4PJ9++mnryy+/tNauXWstWbLEOvrooy0A1gknnGA7/sILL7QAWI8//nin47755psWAOvYY4/tcC15v4qiKIqiKMrWxUaHnbe0tOCwww7DwoUL8eijj+Kcc86petx2222HmTNn2l77zne+A6A9TLSnvPjii/j8888xa9YsJJNJrFu3znwdeOCBiEQieO655zod4/3338c//vEPnHDCCcZZ49eoUaOw8847mzEqlQoWLlyIMWPGYPr06bZxzjvvvA7O7/PPP490Oo0zzjjDVrgpEAjg/PPP7zCX++67D6FQCDNmzLDNY926dTj66KORSCTw+uuvb9RaHXLIIbAsy4Qw/+lPf4LH48EVV1wBj8eDP/3pTwCAl156CZZl1QzZ7Qn77bcfvvWtb9le29Rn7uSss86yhe1GIhHst99+tvF743MimTdvnu33CRMm4NBDD8VLL72ElpaWTbqfn/zkJ7YokBEjRuDEE0/Ehx9+iHfffbdHY9FtXbx4Mdra2ro8/pRTTkFDQ4P53e1245BDDsGaNWuQSqUA9O2/l67Ybrvt8MILL1T9ampqMsfts88+eOutt3DqqaeiVCrh3nvvxdlnn4099tgD48eP75AiwHutNu7ee+/d7fl997vfxaBBgzB48GBMmDABTz75JP7f//t/HXK2+SwYHVALvt+dZ6coiqIoiqJsPWx02PmsWbOQSCTwwgsvdBoWu+OOO3Z4jWGn69ev36hrL1u2DABwxhln4Iwzzqh6DPOWuxrjmmuuwTXXXFP1GM79iy++QDKZxJgxYzocEwwGMWrUKFtbrE8++QQAsOuuu3Y4fvTo0VXnks1msf3229ecb1f3U4uDDjrIhJifcMIJePHFF7Hnnntihx12wPjx4/Hiiy/itNNOw4svvgiPx4MDDzxwo64j6Ytn3t1ryPF743NC6uvrsd1223V4fcyYMXjhhRfwySefYM899+zWWNWo9tniax999BG++c1vdnusAw44AKeccgp+97vf4cEHH8See+6JiRMn4rjjjsN+++3X4fiunlc0Gu3Tfy9dEQqFuh16v9tuu2HBggUAgFWrVuEvf/kL7r33XixevBhTpkzBBx98YNtocLlcPQ7rd3LTTTdh9913Rzabxeuvv47rr78en3/+eYew9Xg8DqBrUd1dka4oiqIoiqJsXWy0+J4xYwZ+97vf4corr8Q+++xTszWPrPTrxNrI9kiVSgUAcPXVV2PChAlVj5F/YHc2xtlnn42jjjqq6jHdbfVT6z66m3NaqVRQV1eHxx57rOYxG5v7WV9Line truncated
"text/plain": [
"<Figure size 1650x506 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, axes = plt.subplots(1, len(ORDER) if len(ORDER) < 4 else 3, figsize=(15, 4.6))\n",
"extent = [0, 990 * BIN_UM, 0, 990 * BIN_UM]\n",
"shown = ORDER if len(ORDER) < 4 else ['eta', 'cluster <=3ph', 'GridBased flat']\n",
"for ax, name in zip(np.atleast_1d(axes), shown):\n",
" image = variants[name][0]\n",
" im = ax.imshow(image, extent=extent, vmax=np.percentile(image, 99.5))\n",
" ax.add_patch(plt.Rectangle((ROI[0]*BIN_UM, ROI[2]*BIN_UM),\n",
" (ROI[1]-ROI[0])*BIN_UM, (ROI[3]-ROI[2])*BIN_UM,\n",
" edgecolor='red', facecolor='none', lw=1.4))\n",
" ax.set_title(name); ax.set_xlabel('x [um]'); ax.set_ylabel('y [um]')\n",
" plt.colorbar(im, ax=ax, fraction=0.046)\n",
"fig.suptitle('knife edge, with the published ESF ROI')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "782d9a37",
"metadata": {},
"source": [
"## 2. Edge spread function\n",
"\n",
"`generate_presampled_esf` fits an erf to every row of the ROI, fits a straight\n",
"line through the edge positions, and re-samples the ROI onto a common axis;\n",
"`get_odr_fitting` then fits the erf with x and y errors inside |x - p1| <= 4 sigma\n",
"(the fit of `Plotting/fig_knife_edge.py --fit odr`)."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "7a741198",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:10.853265Z",
"iopub.status.busy": "2026-09-28T08:23:10.853179Z",
"iopub.status.idle": "2026-09-28T08:23:11.402245Z",
"shell.execute_reply": "2026-09-28T08:23:11.401964Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Final Edge Midpoint Fit: slope = -0.0858, intercept = 433.2022, chi2/ndf = 0.0302\n",
"variant sigma [um] chi2/ndf MTF50 MTF50 LSF MTF(200) LSF rows\n",
"GridBased flat 1.473 +- 0.017 1.30 127.2 133.0 0.222 100%\n"
]
}
],
"source": [
"def analyse(edge_image, flat_image, label):\n",
" esf = generate_presampled_esf(edge_image, flat_image, ROI,\n",
" pixel_size_um=BIN_UM, bin_width_um=BIN_WIDTH_UM,\n",
" chi2_threshold=15.0, residual_tolerance_px=1.0)\n",
" x, z = esf['numpy']['x'], esf['numpy']['z']\n",
" odr = get_odr_fitting(x, z, esf['numpy']['x_err'], esf['numpy']['z_err'], n_sigma=4.0)\n",
" sigma, sigma_err = odr['sigma_um'], odr['sigma_err_um'] # sigma of the Gaussian LSF\n",
" try:\n",
" mtf50_lsf, mtf200_lsf = mtf_from_lsf(dict(x=x, z=z, pars=odr['beta']))\n",
" except Exception:\n",
" mtf50_lsf = mtf200_lsf = np.nan\n",
" n_fit = int(odr['mask'].sum())\n",
" ndf = max(n_fit - len(odr['beta']), 1)\n",
" x_fit = x[odr['mask']]\n",
" return dict(label=label, x=x, z=z, sigma=sigma, sigma_err=sigma_err, sigma_gauss=sigma,\n",
" chi2_ndf=odr['chi2_ndf'], chi2=odr['chi2_ndf'] * ndf, ndf=ndf,\n",
" mtf50=1000 * np.sqrt(np.log(2) / 2) / (np.pi * sigma),\n",
" mtf10=1000 * np.sqrt(np.log(10) / 2) / (np.pi * sigma),\n",
" mtf50_lsf=mtf50_lsf, mtf200_lsf=mtf200_lsf,\n",
" rows_kept=esf['valid_lines_ratio'],\n",
" edge_slope=esf['edge_slope'], edge_centre=odr['beta'][1],\n",
" fit_points=n_fit, fit_range=(x_fit.min(), x_fit.max()),\n",
" photons=float(edge_image.sum()), odr=odr)\n",
"\n",
"\n",
"results = {name: analyse(edge, flat, name) for name, (edge, flat) in variants.items()}\n",
"\n",
"print(f\"{'variant':16s} {'sigma [um]':>16} {'chi2/ndf':>9} {'MTF50':>7} {'MTF50 LSF':>9} \"\n",
" f\"{'MTF(200) LSF':>12} {'rows':>6}\")\n",
"for name, r in results.items():\n",
" print(f\"{name:16s} {r['sigma']:7.3f} +- {r['sigma_err']:.3f} {r['chi2_ndf']:9.2f} {r['mtf50']:7.1f} \"\n",
" f\"{r['mtf50_lsf']:9.1f} {r['mtf200_lsf']:12.3f} {r['rows_kept']*100:5.0f}%\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "f14649cd",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:11.403441Z",
"iopub.status.busy": "2026-09-28T08:23:11.403279Z",
"iopub.status.idle": "2026-09-28T08:23:11.709352Z",
"shell.execute_reply": "2026-09-28T08:23:11.709131Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1375x528 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"COLOURS = {'eta': '#a0451c', 'cluster 1ph': '#6b6b6b', 'cluster <=2ph': '#7b5aa6',\n",
" 'cluster <=3ph': '#4b2a6b', 'GridBased raw': '#4cbcac',\n",
" 'GridBased flat': '#10695f'}\n",
"frequency = np.linspace(0, 400, 2000)\n",
"fig, ax = plt.subplots(1, 2, figsize=(12.5, 4.8))\n",
"for name, r in results.items():\n",
" ax[0].plot(r['x'], r['z'], lw=1.1, color=COLOURS[name],\n",
" label=f\"{name}: $\\\\sigma$ = {r['sigma']:.3f} $\\\\pm$ {r['sigma_err']:.3f} $\\\\mu$m\")\n",
" sigma_mm = r['sigma_gauss'] / 1000.0\n",
" ax[1].plot(frequency, np.exp(-2 * np.pi**2 * sigma_mm**2 * frequency**2),\n",
" lw=1.6, color=COLOURS[name], label=f\"{name} ({r['mtf50']:.0f} lp/mm)\")\n",
"ax[0].set_xlim(-9, 9); ax[0].set_ylim(-0.05, 1.35)\n",
"ax[0].set_xlabel(r'position across the edge [$\\mu$m]')\n",
"ax[0].set_ylabel('relative intensity')\n",
"ax[0].legend(fontsize=8); ax[0].grid(alpha=.3); ax[0].set_title('oversampled ESF')\n",
"ax[1].axvline(200, color='k', ls=':', lw=.8, label='2.5 um Nyquist')\n",
"ax[1].axhline(0.5, color='grey', lw=.5)\n",
"ax[1].set_xlim(0, 400); ax[1].set_ylim(0, 1.05)\n",
"ax[1].set_xlabel('spatial frequency [lp/mm]'); ax[1].set_ylabel('MTF')\n",
"ax[1].legend(fontsize=8); ax[1].grid(alpha=.3); ax[1].set_title('MTF from the fitted ESF')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "f81b1689",
"metadata": {},
"source": [
"## 3. Where the GridBased sub-pixel phase stands"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "be944928",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:11.710528Z",
"iopub.status.busy": "2026-09-28T08:23:11.710438Z",
"iopub.status.idle": "2026-09-28T08:23:12.378356Z",
"shell.execute_reply": "2026-09-28T08:23:12.378123Z"
}
},
"outputs": [
{
"data": {
"image/png": "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bTjnnHPwwQ9+EFdddRU2bdqESy+9FKOjo/j5z3+Oo446Cg888ADe9KY34bzzzsOHP/xhVUef+9zncN111+Gyyy7D6173Ovzxj3/EZz7zGRSLRVx++eXKjnq9jvPPPx+f+cxncPXVV6O9vR2rVq3C8ccfjyVLluDHP/4xTNPEV77yFZx00kl47rnnkEwmJ5VHQoacf/75WLhwIS6//PItngcAQ0ND6OjowLXXXov29nasWLECV155Jfr7+/GjH/0IAPDAAw/gNa95DT796U/jvPPOA0AskK0dB4C///u/xw9/+EN88YtfxLHHHov7778fn//855FOp3HRRRfF6v69730vLr74Ylx++eW4+uqr8c53vhNr165FZ2fnS7bZttJdeuml+P73v48vf/nLOOKII3DLLbfgn//5n7evU2hoaGho7LfQc5ztw4YNG3DwwQfj/PPPRz6fxxNPPIErrrgCpVIJX/rSl7Y6X6jVarv8fN/SnKYZLzV32pmy/PKXv8Tf/d3f4atf/apir7zUcQB497vfjXvvvRdXXHEFFi1ahDvvvBPvfe970d7ejlNPPVVdv7+/HxdffDEuvfRStLS04NJLL8VZZ52FF154AY7jbLUNtifdBz/4Qfz5z3/G17/+dcyaNQvf//738fjjj293O2toaGhoaGhoaGi8LBFqaOxB3HrrrSGAcPXq1erY6tWrQwDhrbfeusU0vu+HruuGBx98cPiFL3xBHb/iiivCbDa72647Pj4e5nK52DXCMAyvv/76MJ1Oh0NDQ2EYhuHdd98dAggfeeSRMAzDcOnSpaFhGOF//dd/qTSu64YzZ84Md2ZIXXHFFSGAWH6e54WzZ88Ozz333DAMwzAIgnDWrFnqu+COO+4IDcMIn3nmGXUsm82GV1xxhfo+ODgYOo4Tfu5zn4ul/ehHPxpms9mwWCzG7Ljlllti511wwQXhnDlzwkqloo4NDAyE2Ww2vP7661+ybIccckj4/ve/P3bs/e9/f3jIIYdsNY3ruuFPf/rT0LbtsFQqqeMAwn/7t3+bdP6Wjj///POhYRjh97///djxz33uc+GUKVNC3/djZb7jjjvUOStXrgwBhD/5yU/UsRNPPDE87bTT1PftSTc8PBymUqnwy1/+csyGE088cVLf1NDQ0NA48KDnODuGIAhC13XDr3zlK2FfX1/sty3NF3bH8715TrM1NM+dwnDys397yrK19t/a8T//+c8hgPAPf/hD7PjZZ58dHn300er7+9///knzvT/+8Y8hgPCvf/2rOjZr1qzwk5/85A6le/bZZ0MA4Y033qjO8TwvnDt37i63uYaGhoaGhoaGhsaBDB2qS2O/wLJly3DWWWeht7cXlmXBcRw899xzWLFixR675v3334+JiQmcffbZ8DxP/b3pTW9CpVLBM888s8V0Dz/8MMIwVKEyAMC2bZx55pm7ZE9jfpZl4YwzzsCDDz4IAFixYgXWrl2Ld7/73TFbTzzxRBiGgUcffXSr+T700EOK0dKIc889F6VSCX/7299ix9/2trfFvt91110488wzYdu2um57ezsOP/xwPPLII7tUZoBCblx33XVYvHgx0uk0HMfB+eefD8/zsGrVqp3K83//938BAO985ztj9XXSSSehv78f69atU+eapok3v/nN6vv8+fORSCSwfv36l7zGttI9/fTTqFarOOOMM2LpdrWfaGhoaGgcWHglz3Gq1SquuOIKzJ8/H8lkEo7j4Itf/CI2bdqEiYmJHc5vR57vwOQ5za5gd5cFoDlWR0cH3vSmN00qz9/+9jf4vq/OnTp1Kg455BD1XRg425qvbCudzOUa5yuWZcVClGpoaGhoaGhoaGi8EqFDdWnscxSLRbzlLW9Bd3c3rr32WsyaNQupVAof/vCHUa1W99h1h4aGAFCYhi2h+eVbsGnTJjiOMynkQ29v707bsqX8enp6sGnTppitjQsZ22MrAIyOjgIApkyZEjsu30dGRtSxTCaDbDYbO29oaAjXXXfdJE0UAEin01u97vbiuuuuw2c/+1lccskleOMb34j29nY88sgj+OQnP7nT7T80NIQwDNHV1bXF39etW4dZs2YBoDIkEonY747jbPPa20onbdfd3R07p6enZ/sLoqGhoaFxQOOVPsf5/Oc/jx/84Ae44oor8KpXvQptbW24/fbb8a//+q+oVqvI5XI7lN+OPN+3NKfZFezusgBUnpGRka2G2tq0aROmT58OAGhra4v9JnOQbfWjbaWTNm9tbY2dp+crGhoaGhoaGhoar3TojRONfY4HHngA69evx29/+1scfvjh6nihUFAvi3sCHR0dAIBf/vKXmDFjxqTfRcizGX19fXBdF6Ojo7GFhc2bN++0LVvKb2BgQMW/Flu/853v4Nhjj52UfurUqVvNW9Ju3rwZ06ZNU8f7+/tjvwPYoghoR0cHTjvtNHziE5+Y9Fs+n3/Jcm0Pbr31Vpxxxhm4+uqr1bGlS5fuUp4dHR0wDAP33nvvpM0NADj44IN3Kf/tgbTd4OBgrH0GBgb2+LU1NDQ0NPYPvNLnOLfeeis+9rGP4fOf/7w6dscdd+xLine truncated
"text/plain": [
"<Figure size 1650x462 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"before = np.load(GRID / FLAT_RUN / 'subpixel_phase_before.npy')\n",
"after = np.load(GRID / FLAT_RUN / 'subpixel_phase_after.npy')\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(15, 4.2))\n",
"for ax, histogram, title in ((axes[0], before, 'before flattening'),\n",
" (axes[1], after, 'after flattening')):\n",
" im = ax.imshow(histogram.T / histogram.mean(), origin='lower', extent=[0, 1, 0, 1],\n",
" vmin=0.9, vmax=1.1, cmap='viridis')\n",
" ax.set_title(f'flat field, {title}', fontsize=10)\n",
" ax.set_xlabel('sub-pixel x'); ax.set_ylabel('sub-pixel y')\n",
" plt.colorbar(im, ax=ax, fraction=0.046)\n",
"for histogram, title in ((before, 'before'), (after, 'after')):\n",
" profile = histogram.sum(axis=1)\n",
" axes[2].plot(np.linspace(0, 1, len(profile)), profile / profile.mean(), lw=1, label=title)\n",
"axes[2].axhline(1, color='k', lw=.6); axes[2].set_xlabel('sub-pixel x')\n",
"axes[2].set_ylabel('relative density'); axes[2].legend(); axes[2].grid(alpha=.3)\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "1cc3ca80",
"metadata": {},
"source": [
"## 4. Relative DQE"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "87158001",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:12.379977Z",
"iopub.status.busy": "2026-09-28T08:23:12.379792Z",
"iopub.status.idle": "2026-09-28T08:23:12.483482Z",
"shell.execute_reply": "2026-09-28T08:23:12.483250Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"variant QE rel MTF(200) DQE rel (relative to GridBased flat)\n",
"GridBased flat 1.000 0.222 0.049\n"
]
}
],
"source": [
"# DQE(f) is proportional to QE * MTF(f)^2. Every chain sees the same beam, so\n",
"# the ratio of detected photons is the ratio of QE and the relative DQE is well\n",
"# defined even without the absolute incident flux.\n",
"def mtf_at(frequency_lpmm, sigma_gauss_um):\n",
" sigma_mm = sigma_gauss_um / 1000.0\n",
" return float(np.exp(-2 * np.pi**2 * sigma_mm**2 * frequency_lpmm**2))\n",
"\n",
"\n",
"FREQUENCY = 200.0 # lp/mm, the Nyquist of the 2.5 um bins\n",
"REFERENCE = 'GridBased flat'\n",
"reference_photons = results[REFERENCE]['photons']\n",
"print(f\"{'variant':16s} {'QE rel':>8} {'MTF(200)':>9} {'DQE rel':>9} (relative to {REFERENCE})\")\n",
"relative_dqe = {}\n",
"for name, r in results.items():\n",
" qe = r['photons'] / reference_photons\n",
" mtf200 = r['mtf200_lsf'] # measured from the LSF, as in the poly notebook\n",
" relative_dqe[name] = qe * mtf200**2\n",
" print(f'{name:16s} {qe:8.3f} {mtf200:9.3f} {relative_dqe[name]:9.3f}')\n"
]
},
{
"cell_type": "markdown",
"id": "facc1139",
"metadata": {},
"source": [
"## 5. Summary"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "575e8e9c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:12.484541Z",
"iopub.status.busy": "2026-09-28T08:23:12.484461Z",
"iopub.status.idle": "2026-09-28T08:23:12.588463Z",
"shell.execute_reply": "2026-09-28T08:23:12.588198Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"GridBased flat\": {\n",
" \"sigma_um\": 1.473126548845613,\n",
" \"sigma_err_um\": 0.016628349232745622,\n",
" \"mtf50_lsf_lpmm\": 133.0356249682,\n",
" \"mtf200_lsf\": 0.22230766940089752,\n",
" \"chi2_ndf\": 1.2970189194700865,\n",
" \"mtf50_lpmm\": 127.2060606579405,\n",
" \"mtf10_lpmm\": 231.8477669544846,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 686836853.0\n",
" }\n",
"}\n",
"\n",
"written to /mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased/Edge_0filters_12keV/knife_edge_comparison.json\n"
]
}
],
"source": [
"summary = {\n",
" 'measurement': 'Edge/Flat_0filters_12keV',\n",
" 'occupancy_percent': occupancy * 100, # flat field, ESF ROI\n",
" 'roi_superpixel': list(ROI),\n",
" 'bin_um': BIN_UM,\n",
" 'bin_width_um': BIN_WIDTH_UM,\n",
" 'convention': 'sigma = ODR p2 (Gaussian LSF); MTF50 = 1000 sqrt(ln 2 / 2) / (pi sigma); '\n",
" 'ESF from DeepLearning/src, bin width 0.3 um',\n",
" 'grid_model_epoch': grid_summary['epoch'],\n",
" 'grid_frames': grid_summary['n_frames'],\n",
" 'grid_subpixel_before': grid_summary['subpixel_nonuniformity_before'],\n",
" 'grid_subpixel_after': grid_summary['subpixel_nonuniformity_after'],\n",
" 'variants': {name: dict(sigma_um=float(r['sigma']), sigma_err_um=float(r['sigma_err']),\n",
" mtf50_lsf_lpmm=float(r['mtf50_lsf']),\n",
" mtf200_lsf=float(r['mtf200_lsf']),\n",
" chi2_ndf=float(r['chi2_ndf']),\n",
" mtf50_lpmm=float(r['mtf50']), mtf10_lpmm=float(r['mtf10']),\n",
" rows_kept=float(r['rows_kept']), photons=float(r['photons']))\n",
" for name, r in results.items()},\n",
" 'relative_dqe_200lpmm': relative_dqe,\n",
"}\n",
"print(json.dumps(summary['variants'], indent=1))\n",
"out = GRID / EDGE_RUN / 'knife_edge_comparison.json'\n",
"json.dump(summary, open(out, 'w'), indent=1)\n",
"print(f'\\nwritten to {out}')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch_nightly",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+569
View File
@@ -0,0 +1,569 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "116b7266",
"metadata": {},
"source": [
"# Knife edge, 1 filter — GridBased against eta interpolation\n",
"\n",
"The second-highest flux point of the scan: **4.45 % occupancy** (flat field,\n",
"inside the ESF ROI), 6.0x the 3-filter point and above where the cluster chain was ever\n",
"operated.\n",
"\n",
"The cluster-based CNN images for this run exist only for older model\n",
"versions and without the eta-flattening step, so they would not be\n",
"comparable with the other two notebooks and are left out. Eta\n",
"interpolation is available and is kept as the classical reference.\n",
"\n",
"\n",
"**Edge width (revised 2026-09-28).** The ESF is binned and fitted exactly as\n",
"`Plotting/fig_knife_edge.py --fit odr` does it: `generate_presampled_esf()` and\n",
"`get_odr_fitting()` from `DeepLearning/src/funcs_postprocess.py` (bin content\n",
"Σk/Σf), symmetric ±4σ window, **ESF bin width 0.3 µm**. The ODR model is\n",
"`p0/2 (1 + erf((x - p1)/(sqrt(2) sigma))) + p3`, so the fit gives **sigma**, the\n",
"width of the Gaussian LSF, which is what the tables quote, with\n",
"`MTF50 [lp/mm] = 1000 sqrt(ln 2 / 2) / (pi sigma)`; MTF50 and MTF(200 lp/mm) are\n",
"also measured from the LSF (`Plotting/knife_edge_numbers.py`: first crossing,\n",
"central-difference correction), and the relative DQE uses the latter. Only the\n",
"flattened GridBased reconstruction is analysed. Same processing as\n",
"`KnifeEdge_poly2to20keV_Moench040.ipynb`.\n",
"\n",
"**Occupancy** is that of the flat field inside the ESF ROI: the reconstructed\n",
"photons per 25 µm pixel per frame over `ROI // 10` (x 33–46, y 26–49 of the\n",
"99 × 99 frame), the pixels over which `DeepLearning/Notebooks/KnifeEdge_3filters_pos0.ipynb`\n",
"sums the deposited energy (0.705 % at 3 filters from the energy, 0.747 % here\n",
"from the photons of the 12 keV model).\n",
"\n",
"*Until 2026-09-28 this notebook took the fitted sigma for sqrt(2) sigma (so its\n",
"MTF50 was sqrt(2) too high), used 0.5 µm bins, and binned the ESF with an older\n",
"copy of `generate_presampled_esf` (inverse-variance mean of k/f, biased low in\n",
"the toe, sigma ~3 % too small). The previous version is in\n",
"`Backup_260928_pre_sigma_fix/`.*\n",
"\n",
"The ROI, the oversampled ESF and the erf fit are the ones from\n",
"`DeepLearning/Notebooks/KnifeEdge_3filters_pos0.ipynb`; the edge sits at the\n",
"same place at all three flux points, so the same ROI holds throughout and the\n",
"three notebooks are directly comparable.\n",
"\n",
"Run in the `pytorch_nightly` environment (ROOT + scipy + jupyter)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "3975e8f9",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:03.371301Z",
"iopub.status.busy": "2026-09-28T08:23:03.371220Z",
"iopub.status.idle": "2026-09-28T08:23:05.007618Z",
"shell.execute_reply": "2026-09-28T08:23:05.007252Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Welcome to JupyROOT 6.28/10\n"
]
}
],
"source": [
"import json\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"REPO = Path('/home/xie_x1/MLXID/GridBased')\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/DeepLearning/src') # the code Plotting/ uses\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/Plotting')\n",
"from ROOT import TCanvas, gROOT\n",
"gROOT.SetBatch(True)\n",
"from funcs_postprocess import generate_presampled_esf, get_odr_fitting\n",
"from knife_edge_numbers import mtf as mtf_from_lsf # first crossing, sinc-corrected\n",
"\n",
"GRID = Path('/mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased')\n",
"PUBLISHED = Path('/home/xie_x1/MLXID/DeepLearning/InferenceResults')\n",
"ETA = Path('/home/xie_x1/MLXID/EtaInterpolation/Results')\n",
"\n",
"EDGE_RUN, FLAT_RUN = 'Edge_1filters_12keV', 'Flat_1filters_12keV'\n",
"BIN_UM = 2.5 # super-pixel = 25 um / 10\n",
"ROI = (330, 465, 260, 490) # x1, x2, y1, y2, as published\n",
"CROP = slice(10, -10) # 1010 -> 990, as published\n",
"BIN_WIDTH_UM = 0.3 # presampled ESF bin, as Plotting/ and the manuscript\n",
"\n",
"plt.rcParams.update({'figure.dpi': 110, 'image.cmap': 'gray',\n",
" 'image.origin': 'lower', 'image.interpolation': 'nearest'})"
]
},
{
"cell_type": "markdown",
"id": "8c496448",
"metadata": {},
"source": [
"## 1. Load every reconstruction of the same measurement"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4bcf2cfb",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:05.009178Z",
"iopub.status.busy": "2026-09-28T08:23:05.008836Z",
"iopub.status.idle": "2026-09-28T08:23:05.131827Z",
"shell.execute_reply": "2026-09-28T08:23:05.131438Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"GridBased model epoch 135, 7,000,000 frames, occupancy 4.450 % (flat field, ESF ROI)\n",
" sub-pixel excess rms 19.8 % -> 3.0 % after flattening\n",
"\n",
"variant edge photons flat photons relative\n",
"eta 2.3085e+08 3.9229e+08 0.407\n",
"GridBased flat 5.6697e+08 9.8283e+08 1.000\n"
]
}
],
"source": [
"def crop(image):\n",
" \"\"\"The published frames are cropped by 10 super-pixels on each side.\"\"\"\n",
" return image[CROP, CROP] if image.shape[0] == 1010 else image\n",
"\n",
"\n",
"for run in (EDGE_RUN, FLAT_RUN):\n",
" if not (GRID / run / 'summary.json').exists():\n",
" raise SystemExit(\n",
" f'{run} has not been reconstructed yet. Run:\\n'\n",
" f' python scripts/05_infer_measurement.py '\n",
" f'--config Configs/infer_measurement_12keV.yaml --run {run}')\n",
"\n",
"grid_summary = json.load(open(GRID / EDGE_RUN / 'summary.json'))\n",
"flat_summary = json.load(open(GRID / FLAT_RUN / 'summary.json'))\n",
"\n",
"\n",
"def grid_image(run, flattened):\n",
" name = 'binned_image_flattened.npy' if flattened else 'binned_image.npy'\n",
" return np.load(GRID / run / name).astype(float)\n",
"\n",
"\n",
"variants = {\n",
" 'eta': (np.load(ETA / '2603MaxIV_Edge1Filters_12keV_fromClusters.npy'),\n",
" np.load(ETA / '2603MaxIV_Flat1Filters_12keV_fromClusters.npy')),\n",
" 'GridBased flat': (grid_image(EDGE_RUN, True), grid_image(FLAT_RUN, True)),\n",
"}\n",
"\n",
"# The cluster-based CNN images for this flux point exist only for older model\n",
"# versions (folders 260512_1ph.. / 260511_2ph.. / 260529_3ph..) and without the\n",
"# eta-flattening step, so they are not comparable with the other two notebooks\n",
"# and are left out. Eta interpolation is available and is kept.\n",
"\n",
"ORDER = ['eta', 'GridBased flat']\n",
"variants = {name: (crop(e), crop(f)) for name, (e, f) in variants.items()}\n",
"variants = {name: variants[name] for name in ORDER}\n",
"\n",
"def flat_occupancy(run):\n",
" # reconstructed photons per 25 um pixel per frame in the flat field, inside the\n",
" # ESF ROI: ROI // 10 in pixels of the 99 x 99 frame, both ends included as in\n",
" # DeepLearning/Notebooks; +1 because the 101 x 101 frame is cropped by 1 px\n",
" summary = json.load(open(GRID / run / 'summary.json'))\n",
" native = np.load(GRID / run / 'native_image.npy') / summary['n_frames']\n",
" x0, x1, y0, y1 = (v // 10 for v in ROI)\n",
" return float(native[y0 + 1:y1 + 2, x0 + 1:x1 + 2].mean())\n",
"\n",
"\n",
"occupancy = flat_occupancy(FLAT_RUN)\n",
"print(f\"GridBased model epoch {grid_summary['epoch']}, \"\n",
" f\"{grid_summary['n_frames']:,} frames, \"\n",
" f\"occupancy {occupancy*100:.3f} % (flat field, ESF ROI)\")\n",
"print(f\" sub-pixel excess rms {grid_summary['subpixel_nonuniformity_before']*100:.1f} % -> \"\n",
" f\"{grid_summary['subpixel_nonuniformity_after']*100:.1f} % after flattening\\n\")\n",
"print(f\"{'variant':16s} {'edge photons':>14} {'flat photons':>14} relative\")\n",
"reference = variants['GridBased flat'][0].sum()\n",
"for name, (e, f) in variants.items():\n",
" print(f'{name:16s} {e.sum():14.4e} {f.sum():14.4e} {e.sum()/reference:8.3f}')"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "21d95658",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:05.133060Z",
"iopub.status.busy": "2026-09-28T08:23:05.132975Z",
"iopub.status.idle": "2026-09-28T08:23:05.482855Z",
"shell.execute_reply": "2026-09-28T08:23:05.482542Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABUkAAAH1CAYAAADLW38kAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOy9eXxcZdn//5lJMvuWrTu0WMRSKULVsgktuxRkERUqKgUVERUXig8PChR+bsimwuPCIpuyCAqirSBI4RFl+eICYvvIDm1p0zTJZPaZZOb8/ujrc+eaO2cmaZrQNrner1deSc6c5T73Ocm5zuf+XNftcRzHgaIoiqIoiqIoiqIoiqIoygTFu70boCiKoiiKoiiKoiiKoiiKsj1RkVRRFEVRFEVRFEVRFEVRlAmNiqSKoiiKoiiKoiiKoiiKokxoVCRVFEVRFEVRFEVRFEVRFGVCoyKpoiiKoiiKoiiKoiiKoigTGhVJFUVRFEVRFEVRFEVRFEWZ0KhIqiiKoiiKoiiKoiiKoijKhEZFUkVRFEVRFEVRFEVRFEVRJjQqkiqKoiiKoiiKoiiKoiiKMqFRkVRRFEVRFEVRFEVRFEVRlAmNiqSKoig7Kbfccgs8Hg8ee+yxUd3vY489Bo/Hg1tuuaVqeS6Xw7nnnotdd90VDQ0NmDVr1qgedzRYtGjRDtmubaXWNamHx+PB0qVLx6xNY3X/bS+WLl0Kj8cz7PVnzZqFRYsWVS0b6f03kus7GozXvxdFURRFURRFGQkqkiqKoijD4vLLL8e1116LU045Bbfccgt+8IMfbO8mTWiSySSWL18+piLlY489huXLlyOZTI7ZMZSdg1mzZsHj8dT8Wr58edX6r7/+Oj7/+c9jzz33RDgcRiKRwB577IFTTz0Vv/nNb6rWXbRoUc397r777kO2bfny5VXbeL1eJBIJHHzwwbj99tvrbvvQQw/hpJNOwtSpU+Hz+dDe3o6jjz4ad911FxzHqXmsJ554YuhOUxRFURRFUXYqGrd3AxRFUZQdi0MOOQT5fB5NTU1Vyx9++GHMmzcPV1xxxXZq2cTF7Zokk0lceumlADDI0ThaPPbYY7j00kuxdOlSJBKJMTnGeOKPf/yjq7A2Xpg8eTKuvPJK18/23ntv8/Pf//53HHLIISiXyzjttNPwxS9+EQDw8ssvY8WKFchkMvjwhz9ctb3X68Wtt946aL/RaHTY7bvooouwxx57oFwu480338SNN96IT33qU1i/fj0uuOCCqnUrlQrOPvts3HDDDZg9ezbOOusszJw5Ex0dHbjnnnuwZMkS3HHHHbj77rsRDAaH3QZFURRFURRl50VFUkVRFKUKr9eLQCAwaPnGjRux6667bocWKbWuibJj4fP5tncTxpRIJIJPfOITQ663fPlyZLNZ3H///TjhhBOqPrvmmmuwbt26Qdt4PJ5h7bseRx11FD7wgQ+Y388880zsscceuPzyy3H++eejoaHBfPatb30LN9xwA04++WT88pe/hN/vN59dcMEFWLZsGa6++mp88YtfxE033bRN7VIURVEURVF2DjTdXlEUZZzxk5/8BI2NjfjYxz6GYrEIYKB+4osvvogTTjgB8XgckUgExx57LF555ZWq7e36iEwvfe211/D444+7ptf+4x//wEc+8hFMmjQJPp8P73jHO3DBBRcgl8sNu92vvPIKli5dimnTpsHn82HGjBk455xzsHnz5kHrvvzyyzjppJMQj8cRjUZx1FFH4bnnnqu579tvvx177703/H4/ZsyYga9//etYs2aNa5qw4zi44YYbsGDBAoTDYYTDYRx44IG4//77h30uNuvXr4fH48F5551Xtfyss86Cx+PB5z73uarlX/va1+DxeNDR0QFg8DW55ZZbsNtuuwEALr30UnNN3OpLPvPMMzjssMMQiUSQSCSwZMkSdHZ2DtnmRYsWGafqbrvtVjOt2nEc/OAHP8Aee+wBv9+Pd7zjHbjmmmtc97mt9wlraL7xxhs4+eST0dzcjHA4jCOPPBL//Oc/q9atV+eT9/Trr78+6LOuri6ceeaZaG9vRzAYxIEHHohHH310q9onWbNmDZYsWYJddtkFfr8fkyZNwkEHHYSbb77ZdR+8VwOBAKZPn45vfOMbKJfLg9Yby7+XbeXFF18EABx++OGun8+YMWPMji2ZPn069txzTySTyap7fvPmzfje976H6dOn49Zbb60SSIEtgu0VV1yB9773vbj55pvx73//+21pr6IoiqIoirJ9USepoijKOMFxHPz3f/83Lr/8cnz1q1/FVVddVTURzfr163HIIYfg+OOPx+WXX46XXnoJ1157LY4//nj861//gtfrPm724Q9/GLvvvju++tWvoq2tDd/4xjcADKTXPvjggzjxxBOxyy674Etf+hImT56M5557DldffTX+8pe/YNWqVWhsrP+4+ec//4lFixYhFArhzDPPxMyZM/HSSy/hJz/5Cf70pz/hmWeeQTweBwCsXbsWBx54IJLJJM4++2zsueeeePLJJ7Fo0SK0trYO2vf//M//4Itf/CLmzJmDSy+9FE1NTbjzzjtr1vI844wzcNttt+GEE07AaaedBgD4zW9+g5NOOgk/+clPcPbZZ9e/EC5Mnz4dc+bMwSOPPFK1/E9/+hO8Xq/r8r322guTJ0923d8hhxyCa665Bl/96ldx0kknmdTlSCRStd5zzz2HY445Bp/61Kdwyimn4G9/+xtuvPFG9PT04MEHH6zb5m984xtoaWnBfffdh2uuuQZtbW0AqtOqAeDCCy9EKpXCGWecgUgkgttuuw1f+9rXMHXqVJx66qlmvdG4TwAgm81i4cKF2HffffGtb30La9euxf/8z//g4IMPxpNPPom99tpryH3U4+ijj0YsFsNFF12E7u5u/PSnP8XRRx+NP/zhDzjiiCO2al9dXV049NBDUalU8LnPfQ677bYbenp68K9//QuPP/44zjjjjKr1f/azn2H9+vX4zGc+g/b2dvzmN7/Bd77zHUSj0ap08bH8e6lHpVJxFWEBIJFImOs3e/Zs/Oc//8GNN96IL3/5y8OeEMtt3/F4fFDpj+FSKpWwdu1aU6OUrFixAvl8Hp/85CcRDoddt/V6vTjrrLPwuc99Dr/+9a/x7ne/e0RtUBRFURRFUXYiHEVRFGWn5Oabb3YAOKtWrXIKhYJz6qmnOl6v1/nhD384aN2ZM2c6AJw77rijavl3v/tdB4Dz0EMPmWWrVq1yADg333zzoH0sXLiwalk+n3emTJniLFiwwCkUClWf3XPPPQ4A55ZbbhnyXPbZZx9nt912c7q6uqqWP/XUU47X63WWL19uln3yk590ADj33Xef67nMnDnTLOvp6XHC4bDzjne8w0mlUmZ5oVBw3v/+9zsAnEsuucQsv//++x0AztVXXz2ojccdd5wTi8Wq9rM1fOELX3A8Ho/T0dHhOI7jvPbaaw4Acz6vvvqq4ziOs2nTJsfj8Thf/vKXzbZu14Tby/ZLADgej8d54oknqpZ/7nOfcwA4//nPf4Zs8yWXXOIAcF577bVBn/H+23vvvauufSaTcVpbW50DDjjALBut+2ThwoUOAOcLX/hC1fKnn37a8Xg8zuGHH26W1bqPa53X6aef7gBwPvShDznlctksf/31151QKOS8853vdCqVilnu9vewcOHCqvvvt7/9rQPAueuuu+qeF9s6ZcoUp7u72ywvl8vOnnvu6UydOrVq/bH6e6kH/4fU+vrzn/9c1Q6fz+cAcHbddVfnU5/6lHPttdc6//znP133zevq9vWHP/xhyLbxev7+9793Ojs7nY6ODueZZ55xTjrpJAeAc+qpp1atf9555zkAnF//+td19/vss886AJyTTz550LHk+SqKoiiKoijjA023VxRF2cnp6enBUUcdhfvvvx/33HMPzj33XNf1pk2bhiVLllQtO/LIIwEMpMduLY888gg2btyIpUuXIp1OY/PmzeZr4cKFCIfDeOihh+ru44UXXsA///lPnHrqqcapxq/Zs2fjne98p9lHpVLB/fffj7lz5+LEE0+s2s9XvvKVQU7KP/7xj8hmszjnnHOqJoDx+/346le/Oqgtt99+O4LBIE455ZSqdmzevBknnXQSUqkUnnzyyRH11eGHHw7HcUzq9p/+9Cc0NDTg0ksvRUNDA/70pz8BAB599FE4jlMzVXlrOOCAA3DQQQdVLdvWa27zxS9+sSpdORwO44ADDqja/2jcJ5ILL7yw6vcFCxbgiCOOwKOPPoqenp5tOp///u//rnJVz5w5E6eddhpeeuklPP/881u1L7oXV65cid7e3iHXP/PMM9Hc3Gx+93q9OPzww7FhwwZkMhkAY/v3MhTTpk3Dww8/7Po1b948s95+++2Hv//97/j0pz+N/v5+3HbbbfjSl76EffbZB/Pnzx9UGoHn6rbf97///cNu33HHHYf29nZMnjwZCxYswAMPPIDPf/7zg2qK8lrQbVsLfj6ca6coiqIoiqLs/Gi6vaIoyk7O0qVLkUql8PDDD9dNB37HO94xaBnTbbu6ukZ07DVr1gAAzjnnHJxzzjmu67Cu5lD7+O53v4vvfve7ruuw7Zs2bUI6ncbcuXMHrRMIBDB79mwkk0mz7NVXXwUAzJkzZ9D6e+65p2tb8vk8pk+fXrO9Q51PLQ499FCTWn/qqafikUcewXvf+17stttumD9/Ph555BF85jOfwSOPPIKGhgYsXLhwRMeRjMU1H+4x5P5H4z4hiUQC06ZNG7R87ty5ePjhh/Hqq6/ive9977D25YbbvcVlL7/8Mt7znvcMe1+HHHIIzjzzTPz85z/HnXfeife+97048MAD8ZGPfAQHHHDAoPWHul6RSGRM/16GIhgMDrvkwLvf/W7ceOONAIB169bhL3/5C2677TasXLkSixcvxr///e8qQdjj8Wx1OQOba665BnvttRfy+TyefPJJXHnlldi4ceOgdP1Line truncated
"text/plain": [
"<Figure size 1650x506 with 4 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, axes = plt.subplots(1, len(ORDER) if len(ORDER) < 4 else 3, figsize=(15, 4.6))\n",
"extent = [0, 990 * BIN_UM, 0, 990 * BIN_UM]\n",
"shown = ORDER if len(ORDER) < 4 else ['eta', 'cluster <=3ph', 'GridBased flat']\n",
"for ax, name in zip(np.atleast_1d(axes), shown):\n",
" image = variants[name][0]\n",
" im = ax.imshow(image, extent=extent, vmax=np.percentile(image, 99.5))\n",
" ax.add_patch(plt.Rectangle((ROI[0]*BIN_UM, ROI[2]*BIN_UM),\n",
" (ROI[1]-ROI[0])*BIN_UM, (ROI[3]-ROI[2])*BIN_UM,\n",
" edgecolor='red', facecolor='none', lw=1.4))\n",
" ax.set_title(name); ax.set_xlabel('x [um]'); ax.set_ylabel('y [um]')\n",
" plt.colorbar(im, ax=ax, fraction=0.046)\n",
"fig.suptitle('knife edge, with the published ESF ROI')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "782d9a37",
"metadata": {},
"source": [
"## 2. Edge spread function\n",
"\n",
"`generate_presampled_esf` fits an erf to every row of the ROI, fits a straight\n",
"line through the edge positions, and re-samples the ROI onto a common axis;\n",
"`get_odr_fitting` then fits the erf with x and y errors inside |x - p1| <= 4 sigma\n",
"(the fit of `Plotting/fig_knife_edge.py --fit odr`)."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "7a741198",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:05.484058Z",
"iopub.status.busy": "2026-09-28T08:23:05.483960Z",
"iopub.status.idle": "2026-09-28T08:23:06.360573Z",
"shell.execute_reply": "2026-09-28T08:23:06.360259Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Final Edge Midpoint Fit: slope = -0.0859, intercept = 432.5449, chi2/ndf = 0.0615\n",
"Final Edge Midpoint Fit: slope = -0.0852, intercept = 432.6254, chi2/ndf = 0.0434\n",
"variant sigma [um] chi2/ndf MTF50 MTF50 LSF MTF(200) LSF rows\n",
"eta 1.506 +- 0.023 1.83 124.4 130.0 0.218 100%\n",
"GridBased flat 1.388 +- 0.020 1.82 135.0 141.3 0.251 100%\n"
]
}
],
"source": [
"def analyse(edge_image, flat_image, label):\n",
" esf = generate_presampled_esf(edge_image, flat_image, ROI,\n",
" pixel_size_um=BIN_UM, bin_width_um=BIN_WIDTH_UM,\n",
" chi2_threshold=15.0, residual_tolerance_px=1.0)\n",
" x, z = esf['numpy']['x'], esf['numpy']['z']\n",
" odr = get_odr_fitting(x, z, esf['numpy']['x_err'], esf['numpy']['z_err'], n_sigma=4.0)\n",
" sigma, sigma_err = odr['sigma_um'], odr['sigma_err_um'] # sigma of the Gaussian LSF\n",
" try:\n",
" mtf50_lsf, mtf200_lsf = mtf_from_lsf(dict(x=x, z=z, pars=odr['beta']))\n",
" except Exception:\n",
" mtf50_lsf = mtf200_lsf = np.nan\n",
" n_fit = int(odr['mask'].sum())\n",
" ndf = max(n_fit - len(odr['beta']), 1)\n",
" x_fit = x[odr['mask']]\n",
" return dict(label=label, x=x, z=z, sigma=sigma, sigma_err=sigma_err, sigma_gauss=sigma,\n",
" chi2_ndf=odr['chi2_ndf'], chi2=odr['chi2_ndf'] * ndf, ndf=ndf,\n",
" mtf50=1000 * np.sqrt(np.log(2) / 2) / (np.pi * sigma),\n",
" mtf10=1000 * np.sqrt(np.log(10) / 2) / (np.pi * sigma),\n",
" mtf50_lsf=mtf50_lsf, mtf200_lsf=mtf200_lsf,\n",
" rows_kept=esf['valid_lines_ratio'],\n",
" edge_slope=esf['edge_slope'], edge_centre=odr['beta'][1],\n",
" fit_points=n_fit, fit_range=(x_fit.min(), x_fit.max()),\n",
" photons=float(edge_image.sum()), odr=odr)\n",
"\n",
"\n",
"results = {name: analyse(edge, flat, name) for name, (edge, flat) in variants.items()}\n",
"\n",
"print(f\"{'variant':16s} {'sigma [um]':>16} {'chi2/ndf':>9} {'MTF50':>7} {'MTF50 LSF':>9} \"\n",
" f\"{'MTF(200) LSF':>12} {'rows':>6}\")\n",
"for name, r in results.items():\n",
" print(f\"{name:16s} {r['sigma']:7.3f} +- {r['sigma_err']:.3f} {r['chi2_ndf']:9.2f} {r['mtf50']:7.1f} \"\n",
" f\"{r['mtf50_lsf']:9.1f} {r['mtf200_lsf']:12.3f} {r['rows_kept']*100:5.0f}%\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "5b1a1427",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:06.361862Z",
"iopub.status.busy": "2026-09-28T08:23:06.361744Z",
"iopub.status.idle": "2026-09-28T08:23:06.749047Z",
"shell.execute_reply": "2026-09-28T08:23:06.748786Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1375x528 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"COLOURS = {'eta': '#a0451c', 'cluster 1ph': '#6b6b6b', 'cluster <=2ph': '#7b5aa6',\n",
" 'cluster <=3ph': '#4b2a6b', 'GridBased raw': '#4cbcac', 'GridBased flat': '#10695f'}\n",
"frequency = np.linspace(0, 400, 2000)\n",
"fig, ax = plt.subplots(1, 2, figsize=(12.5, 4.8))\n",
"for name, r in results.items():\n",
" ax[0].plot(r['x'], r['z'], lw=1.1, color=COLOURS[name],\n",
" label=f\"{name}: $\\\\sigma$ = {r['sigma']:.3f} $\\\\pm$ {r['sigma_err']:.3f} $\\\\mu$m\")\n",
" sigma_mm = r['sigma_gauss'] / 1000.0\n",
" ax[1].plot(frequency, np.exp(-2 * np.pi**2 * sigma_mm**2 * frequency**2),\n",
" lw=1.6, color=COLOURS[name], label=f\"{name} ({r['mtf50']:.0f} lp/mm)\")\n",
"ax[0].set_xlim(-9, 9); ax[0].set_ylim(-0.05, 1.35)\n",
"ax[0].set_xlabel(r'position across the edge [$\\mu$m]')\n",
"ax[0].set_ylabel('relative intensity')\n",
"ax[0].legend(fontsize=8); ax[0].grid(alpha=.3); ax[0].set_title('oversampled ESF')\n",
"ax[1].axvline(200, color='k', ls=':', lw=.8, label='2.5 um Nyquist')\n",
"ax[1].axhline(0.5, color='grey', lw=.5)\n",
"ax[1].set_xlim(0, 400); ax[1].set_ylim(0, 1.05)\n",
"ax[1].set_xlabel('spatial frequency [lp/mm]'); ax[1].set_ylabel('MTF')\n",
"ax[1].legend(fontsize=8); ax[1].grid(alpha=.3); ax[1].set_title('MTF from the fitted ESF')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "01c38bd2",
"metadata": {},
"source": [
"## 3. Where the GridBased sub-pixel phase stands"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "da7da667",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:06.750216Z",
"iopub.status.busy": "2026-09-28T08:23:06.750108Z",
"iopub.status.idle": "2026-09-28T08:23:07.141786Z",
"shell.execute_reply": "2026-09-28T08:23:07.141541Z"
}
},
"outputs": [
{
"data": {
"image/png": "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V155JcbGxnDppZdidnYWX/3qV/H85z8fP/7xj/Gyl70MF154Id71rnfZOrr44otx7bXX4rLLLsNLXvISfO9738MHPvABLC4u4iMf+YjNR6vVwpvf/GZ84AMfwMc//nEMDAzgsccew+mnn46TTz4Zn//85+G6Lv7qr/4KL3/5y/Hggw+iWCzuUB6GDHnzm9+M448/Hh/5yEd2uh8ATE1NYXBwENdccw0GBgbw0EMP4YorrsD4+Dg++9nPAgB+/OMf4zd+4zfwR3/0R7jwwgsBGBXIk20HgD/+4z/GP//zP+PDH/4wTjvtNNx+++344Ac/iHK5jHe/+925un/LW96C97///fjIRz6Cj3/843jDG96ATZs2YWho6CnbbFfHXXrppfjMZz6Dj33sY3juc5+Lr33ta/iLv/iL3esUCoVCoThooWOc3cPWrVtx3HHH4c1vfjN6enrwi1/8Apdffjmq1So++tGPPul4odls7vXzfWdjmk481dhpT8ryjW98A69//etx1VVXWfXKU20HgN/93d/FD3/4Q1x++eU44YQTcOutt+Itb3kLBgYG8JrXvMaef3x8HO9///tx6aWXore3F5deeinOO+88PProowiC4EnbYHeOe/vb347vf//7+Ou//musXbsWn/nMZ3DXXXftdjsrFAqFQqFQKBSHJVKFYj/ixhtvTAGkjz/+uN32+OOPpwDSG2+8cafHxHGchmGYHnfccemHPvQhu/3yyy9PK5XKPjvvwsJC2t3dnTtHmqbpddddl5bL5XRqaipN0zS97bbbUgDpT3/60zRN0/S+++5LHcdJ/8//+T/2mDAM06OOOirdk0vq8ssvTwHk0ouiKF23bl36pje9KU3TNE2SJF27dq39Ttxyyy2p4zjpPffcY7dVKpX08ssvt98nJyfTIAjSiy++OHfs7//+76eVSiVdXFzM5eNrX/tabr+LLrooXb9+fVqv1+22iYmJtFKppNddd91Tlu2kk05K3/rWt+a2vfWtb01POumkJz0mDMP0y1/+cur7flqtVu12AOnf/M3f7LD/zrY/8sgjqeM46Wc+85nc9osvvjhdtWpVGsdxrsy33HKL3efhhx9OAaRf+tKX7LYzzjgjPfvss+333Tlueno6LZVK6cc+9rFcHs4444wd+qZCoVAoDj3oGOfpIUmSNAzD9K/+6q/S0dHR3G87Gy/si+d755jmydA5dkrTHZ/9u1OWJ2v/J9v+/e9/PwWQfuc738ltP//889NTTjnFfn/rW9+6w3jve9/7Xgog/X//7//ZbWvXrk3f+973Pq3j7r333hRA+sUvftHuE0VRevTRR+91mysUCoVCoVAoFIcyNFSX4qDA/fffj/POOw8rV66E53kIggAPPvggHnroof12zttvvx1LS0s4//zzEUWR/feyl70M9Xod99xzz06Pu+OOO5CmqQ2VAQC+7+Pcc8/dq/y0p+d5Hl73utfhJz/5CQDgoYcewqZNm/C7v/u7ubyeccYZcBwHd95555Om+9///d9W0dKON73pTahWq/j5z3+e2/7a17429/273/0uzj33XPi+b887MDCAX/u1X8NPf/rTvSozYEJuXHvttTjxxBNRLpcRBAHe/OY3I4oiPPbYY3uU5r//+78DAN7whjfk6uvlL385xsfHsXnzZruv67p4xSteYb9v2LABhUIBW7Zsecpz7Oq4u+++G41GA6973etyx+1tP1EoFArFoYVn8hin0Wjg8ssvx4YNG1AsFhEEAT784Q9jbGwMS0tLTzu9p/N8B3Yc0+wN9nVZADPGGhwcxMte9rIdyvPzn/8ccRzbfVevXo2TTjrJfqcCZ1fjlV0dx7Fc+3jF87xciFKFQqFQKBQKheKZCA3VpVh2LC4u4lWvehVGRkZwzTXXYO3atSiVSnjXu96FRqOx3847NTUFwIRp2Bk6X76JsbExBEGwQ8iHlStX7nFedpbeihUrMDY2lstr+0TG7uQVAGZnZwEAq1atym3n95mZGbutq6sLlUolt9/U1BSuvfbaHTxRAKBcLj/peXcX1157Lf78z/8cl1xyCV760pdiYGAAP/3pT/He9753j9t/amoKaZpieHh4p79v3rwZa9euBWDKUCgUcr8HQbDLc+/qOLbdyMhIbp8VK1bsfkEUCoVCcUjjmT7G+eAHP4h/+qd/wuWXX44XvOAF6O/vx80334y//Mu/RKPRQHd399NK7+k833c2ptkb7OuyAKY8MzMzTxpqa2xsDGvWrAEA9Pf3537jGGRX/WhXx7HN+/r6cvvpeEWhUCgUCoVC8UyHLpwolh0//vGPsWXLFnzzm9/Er/3ar9nt8/Pz9mVxf2BwcBAA8I1vfANHHnnkDr/TyLMTo6OjCMMQs7OzuYmF7du373FedpbexMSEjX/NvP7DP/wDTjvttB2OX7169ZOmzWO3b9+OI444wm4fHx/P/Q5gpyagg4ODOPvss/Ge97xnh996enqesly7gxtvvBGve93r8PGPf9xuu++++/YqzcHBQTiOgx/+8Ic7LG4AwHHHHbdX6e8O2HaTk5O59pmYmNjv51YoFArFwYFn+hjnxhtLine truncated
"text/plain": [
"<Figure size 1650x462 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"before = np.load(GRID / FLAT_RUN / 'subpixel_phase_before.npy')\n",
"after = np.load(GRID / FLAT_RUN / 'subpixel_phase_after.npy')\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(15, 4.2))\n",
"for ax, histogram, title in ((axes[0], before, 'before flattening'),\n",
" (axes[1], after, 'after flattening')):\n",
" im = ax.imshow(histogram.T / histogram.mean(), origin='lower', extent=[0, 1, 0, 1],\n",
" vmin=0.9, vmax=1.1, cmap='viridis')\n",
" ax.set_title(f'flat field, {title}', fontsize=10)\n",
" ax.set_xlabel('sub-pixel x'); ax.set_ylabel('sub-pixel y')\n",
" plt.colorbar(im, ax=ax, fraction=0.046)\n",
"for histogram, title in ((before, 'before'), (after, 'after')):\n",
" profile = histogram.sum(axis=1)\n",
" axes[2].plot(np.linspace(0, 1, len(profile)), profile / profile.mean(), lw=1, label=title)\n",
"axes[2].axhline(1, color='k', lw=.6); axes[2].set_xlabel('sub-pixel x')\n",
"axes[2].set_ylabel('relative density'); axes[2].legend(); axes[2].grid(alpha=.3)\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "0d8f058f",
"metadata": {},
"source": [
"## 4. Relative DQE"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "c3b2ff8f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:07.143232Z",
"iopub.status.busy": "2026-09-28T08:23:07.143111Z",
"iopub.status.idle": "2026-09-28T08:23:07.246698Z",
"shell.execute_reply": "2026-09-28T08:23:07.246438Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"variant QE rel MTF(200) DQE rel (relative to GridBased flat)\n",
"eta 0.407 0.218 0.019\n",
"GridBased flat 1.000 0.251 0.063\n",
"\n",
"highest relative DQE at 200 lp/mm: GridBased flat (3.24x eta)\n"
]
}
],
"source": [
"# DQE(f) is proportional to QE * MTF(f)^2. Every chain sees the same beam, so\n",
"# the ratio of detected photons is the ratio of QE and the relative DQE is well\n",
"# defined even without the absolute incident flux.\n",
"def mtf_at(frequency_lpmm, sigma_gauss_um):\n",
" sigma_mm = sigma_gauss_um / 1000.0\n",
" return float(np.exp(-2 * np.pi**2 * sigma_mm**2 * frequency_lpmm**2))\n",
"\n",
"\n",
"FREQUENCY = 200.0 # lp/mm, the Nyquist of the 2.5 um bins\n",
"REFERENCE = 'GridBased flat'\n",
"reference_photons = results[REFERENCE]['photons']\n",
"print(f\"{'variant':16s} {'QE rel':>8} {'MTF(200)':>9} {'DQE rel':>9} (relative to {REFERENCE})\")\n",
"relative_dqe = {}\n",
"for name, r in results.items():\n",
" qe = r['photons'] / reference_photons\n",
" mtf200 = r['mtf200_lsf'] # measured from the LSF, as in the poly notebook\n",
" relative_dqe[name] = qe * mtf200**2\n",
" print(f'{name:16s} {qe:8.3f} {mtf200:9.3f} {relative_dqe[name]:9.3f}')\n",
"best = max(relative_dqe, key=relative_dqe.get)\n",
"print(f\"\\nhighest relative DQE at {FREQUENCY:.0f} lp/mm: {best} \"\n",
" f\"({relative_dqe[best]/relative_dqe['eta']:.2f}x eta)\")"
]
},
{
"cell_type": "markdown",
"id": "28adb593",
"metadata": {},
"source": [
"## 5. Summary"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "ff568b19",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:07.247817Z",
"iopub.status.busy": "2026-09-28T08:23:07.247707Z",
"iopub.status.idle": "2026-09-28T08:23:07.351773Z",
"shell.execute_reply": "2026-09-28T08:23:07.351532Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"eta\": {\n",
" \"sigma_um\": 1.5064047956840743,\n",
" \"sigma_err_um\": 0.02338135508989506,\n",
" \"mtf50_lsf_lpmm\": 130.01783547238728,\n",
" \"mtf200_lsf\": 0.21821247141503744,\n",
" \"chi2_ndf\": 1.831632850088404,\n",
" \"mtf50_lpmm\": 124.39592974355975,\n",
" \"mtf10_lpmm\": 226.7259781499331,\n",
" \"rows_kept\": 0.9956521739130435,\n",
" \"photons\": 230851827.0\n",
" },\n",
" \"GridBased flat\": {\n",
" \"sigma_um\": 1.388201297480562,\n",
" \"sigma_err_um\": 0.020239692729912443,\n",
" \"mtf50_lsf_lpmm\": 141.25277630731097,\n",
" \"mtf200_lsf\": 0.2506173773815928,\n",
" \"chi2_ndf\": 1.8199081902180196,\n",
" \"mtf50_lpmm\": 134.98807807583214,\n",
" \"mtf10_lpmm\": 246.03139430216834,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 566973421.0\n",
" }\n",
"}\n",
"\n",
"written to /mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased/Edge_1filters_12keV/knife_edge_comparison.json\n"
]
}
],
"source": [
"summary = {\n",
" 'measurement': 'Edge/Flat_1filters_12keV',\n",
" 'occupancy_percent': occupancy * 100, # flat field, ESF ROI\n",
" 'roi_superpixel': list(ROI),\n",
" 'bin_um': BIN_UM,\n",
" 'bin_width_um': BIN_WIDTH_UM,\n",
" 'convention': 'sigma = ODR p2 (Gaussian LSF); MTF50 = 1000 sqrt(ln 2 / 2) / (pi sigma); '\n",
" 'ESF from DeepLearning/src, bin width 0.3 um',\n",
" 'grid_model_epoch': grid_summary['epoch'],\n",
" 'grid_frames': grid_summary['n_frames'],\n",
" 'grid_subpixel_before': grid_summary['subpixel_nonuniformity_before'],\n",
" 'grid_subpixel_after': grid_summary['subpixel_nonuniformity_after'],\n",
" 'variants': {name: dict(sigma_um=float(r['sigma']), sigma_err_um=float(r['sigma_err']),\n",
" mtf50_lsf_lpmm=float(r['mtf50_lsf']),\n",
" mtf200_lsf=float(r['mtf200_lsf']),\n",
" chi2_ndf=float(r['chi2_ndf']),\n",
" mtf50_lpmm=float(r['mtf50']), mtf10_lpmm=float(r['mtf10']),\n",
" rows_kept=float(r['rows_kept']), photons=float(r['photons']))\n",
" for name, r in results.items()},\n",
" 'relative_dqe_200lpmm': relative_dqe,\n",
"}\n",
"print(json.dumps(summary['variants'], indent=1))\n",
"out = GRID / EDGE_RUN / 'knife_edge_comparison.json'\n",
"json.dump(summary, open(out, 'w'), indent=1)\n",
"print(f'\\nwritten to {out}')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch_nightly",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+625
View File
@@ -0,0 +1,625 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "06536dc1",
"metadata": {},
"source": [
"# Knife edge, 2 filters — GridBased against the published chains\n",
"\n",
"Same measurement, same ROI, same ESF construction and same erf fit as\n",
"`DeepLearning/Notebooks/KnifeEdge_2filters.ipynb`, so the only thing that\n",
"differs between the rows of the final table is the reconstruction itself.\n",
"The flat field sits at **1.81 % occupancy** inside the ESF ROI, 2.4x the 3-filter point.\n",
"\n",
"\n",
"**Edge width (revised 2026-09-28).** The ESF is binned and fitted exactly as\n",
"`Plotting/fig_knife_edge.py --fit odr` does it: `generate_presampled_esf()` and\n",
"`get_odr_fitting()` from `DeepLearning/src/funcs_postprocess.py` (bin content\n",
"Σk/Σf), symmetric ±4σ window, **ESF bin width 0.3 µm**. The ODR model is\n",
"`p0/2 (1 + erf((x - p1)/(sqrt(2) sigma))) + p3`, so the fit gives **sigma**, the\n",
"width of the Gaussian LSF, which is what the tables quote, with\n",
"`MTF50 [lp/mm] = 1000 sqrt(ln 2 / 2) / (pi sigma)`; MTF50 and MTF(200 lp/mm) are\n",
"also measured from the LSF (`Plotting/knife_edge_numbers.py`: first crossing,\n",
"central-difference correction), and the relative DQE uses the latter. Only the\n",
"flattened GridBased reconstruction is analysed. Same processing as\n",
"`KnifeEdge_poly2to20keV_Moench040.ipynb`.\n",
"\n",
"**Occupancy** is that of the flat field inside the ESF ROI: the reconstructed\n",
"photons per 25 µm pixel per frame over `ROI // 10` (x 33–46, y 26–49 of the\n",
"99 × 99 frame), the pixels over which `DeepLearning/Notebooks/KnifeEdge_3filters_pos0.ipynb`\n",
"sums the deposited energy (0.705 % at 3 filters from the energy, 0.747 % here\n",
"from the photons of the 12 keV model).\n",
"\n",
"*Until 2026-09-28 this notebook took the fitted sigma for sqrt(2) sigma (so its\n",
"MTF50 was sqrt(2) too high), used 0.5 µm bins, and binned the ESF with an older\n",
"copy of `generate_presampled_esf` (inverse-variance mean of k/f, biased low in\n",
"the toe, sigma ~3 % too small). The previous version is in\n",
"`Backup_260928_pre_sigma_fix/`.*\n",
"\n",
"The ROI, the oversampled ESF and the erf fit are the ones from\n",
"`DeepLearning/Notebooks/KnifeEdge_3filters_pos0.ipynb`; the edge sits at the\n",
"same place at all three flux points, so the same ROI holds throughout and the\n",
"three notebooks are directly comparable.\n",
"\n",
"Run in the `pytorch_nightly` environment (ROOT + scipy + jupyter)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "338eb757",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:56.593670Z",
"iopub.status.busy": "2026-09-28T08:22:56.593588Z",
"iopub.status.idle": "2026-09-28T08:22:58.252767Z",
"shell.execute_reply": "2026-09-28T08:22:58.252376Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Welcome to JupyROOT 6.28/10\n"
]
}
],
"source": [
"import json\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"REPO = Path('/home/xie_x1/MLXID/GridBased')\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/DeepLearning/src') # the code Plotting/ uses\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/Plotting')\n",
"from ROOT import TCanvas, gROOT\n",
"gROOT.SetBatch(True)\n",
"from funcs_postprocess import generate_presampled_esf, get_odr_fitting\n",
"from knife_edge_numbers import mtf as mtf_from_lsf # first crossing, sinc-corrected\n",
"\n",
"GRID = Path('/mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased')\n",
"PUBLISHED = Path('/home/xie_x1/MLXID/DeepLearning/InferenceResults')\n",
"ETA = Path('/home/xie_x1/MLXID/EtaInterpolation/Results')\n",
"\n",
"EDGE_RUN, FLAT_RUN = 'Edge_2filters_12keV', 'Flat_2filters_12keV'\n",
"BIN_UM = 2.5 # super-pixel = 25 um / 10\n",
"ROI = (330, 465, 260, 490) # x1, x2, y1, y2, as published\n",
"CROP = slice(10, -10) # 1010 -> 990, as published\n",
"BIN_WIDTH_UM = 0.3 # presampled ESF bin, as Plotting/ and the manuscript\n",
"\n",
"plt.rcParams.update({'figure.dpi': 110, 'image.cmap': 'gray',\n",
" 'image.origin': 'lower', 'image.interpolation': 'nearest'})"
]
},
{
"cell_type": "markdown",
"id": "bdc32295",
"metadata": {},
"source": [
"## 1. Load every reconstruction of the same measurement"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c09dbe26",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:58.254687Z",
"iopub.status.busy": "2026-09-28T08:22:58.254484Z",
"iopub.status.idle": "2026-09-28T08:22:58.475103Z",
"shell.execute_reply": "2026-09-28T08:22:58.474753Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"GridBased model epoch 135, 7,000,000 frames, occupancy 1.808 % (flat field, ESF ROI)\n",
" sub-pixel excess rms 21.6 % -> 3.8 % after flattening\n",
"\n",
"variant edge photons flat photons relative\n",
"eta 1.5670e+08 2.7062e+08 0.710\n",
"cluster 1ph 1.5670e+08 2.7062e+08 0.710\n",
"cluster <=2ph 2.0504e+08 3.5599e+08 0.929\n",
"cluster <=3ph 2.2076e+08 3.8444e+08 1.000\n",
"GridBased flat 2.3053e+08 4.0162e+08 1.044\n"
]
}
],
"source": [
"def crop(image):\n",
" \"\"\"The published frames are cropped by 10 super-pixels on each side.\"\"\"\n",
" return image[CROP, CROP] if image.shape[0] == 1010 else image\n",
"\n",
"\n",
"for run in (EDGE_RUN, FLAT_RUN):\n",
" if not (GRID / run / 'summary.json').exists():\n",
" raise SystemExit(\n",
" f'{run} has not been reconstructed yet. Run:\\n'\n",
" f' python scripts/05_infer_measurement.py '\n",
" f'--config Configs/infer_measurement_12keV.yaml --run {run}')\n",
"\n",
"grid_summary = json.load(open(GRID / EDGE_RUN / 'summary.json'))\n",
"flat_summary = json.load(open(GRID / FLAT_RUN / 'summary.json'))\n",
"\n",
"\n",
"def grid_image(run, flattened):\n",
" name = 'binned_image_flattened.npy' if flattened else 'binned_image.npy'\n",
" return np.load(GRID / run / name).astype(float)\n",
"\n",
"\n",
"variants = {\n",
" 'eta': (np.load(ETA / '2603MaxIV_Edge2Filters_12keV_fromClusters.npy'),\n",
" np.load(ETA / '2603MaxIV_Flat2Filters_12keV_fromClusters.npy')),\n",
" 'GridBased flat': (grid_image(EDGE_RUN, True), grid_image(FLAT_RUN, True)),\n",
"}\n",
"\n",
"EXPERIMENT = {1: '1ph_260512_12keV_v260511_06',\n",
" 2: '2ph_260610_12keV_v260610_01',\n",
" 3: '3ph_260611_12keV_v260611_00'}\n",
"KNIFE, FLAT = 'KnifeEdge_2filters', 'FlatField_2filters'\n",
"\n",
"\n",
"def published_frame(run, n_photons):\n",
" name = f'{n_photons}Photon_ML_superFrame_etaInterpolated.npy'\n",
" return np.load(PUBLISHED / run / EXPERIMENT[n_photons] / 'augX1' / name).astype(float)\n",
"\n",
"\n",
"cluster_edge = {n: published_frame(KNIFE, n) for n in (1, 2, 3)}\n",
"cluster_flat = {n: published_frame(FLAT, n) for n in (1, 2, 3)}\n",
"variants.update({\n",
" 'cluster 1ph': (cluster_edge[1], cluster_flat[1]),\n",
" 'cluster <=2ph': (cluster_edge[1] + cluster_edge[2], cluster_flat[1] + cluster_flat[2]),\n",
" 'cluster <=3ph': (cluster_edge[1] + cluster_edge[2] + cluster_edge[3],\n",
" cluster_flat[1] + cluster_flat[2] + cluster_flat[3]),\n",
"})\n",
"\n",
"ORDER = ['eta', 'cluster 1ph', 'cluster <=2ph', 'cluster <=3ph', 'GridBased flat']\n",
"variants = {name: (crop(e), crop(f)) for name, (e, f) in variants.items()}\n",
"variants = {name: variants[name] for name in ORDER}\n",
"\n",
"def flat_occupancy(run):\n",
" # reconstructed photons per 25 um pixel per frame in the flat field, inside the\n",
" # ESF ROI: ROI // 10 in pixels of the 99 x 99 frame, both ends included as in\n",
" # DeepLearning/Notebooks; +1 because the 101 x 101 frame is cropped by 1 px\n",
" summary = json.load(open(GRID / run / 'summary.json'))\n",
" native = np.load(GRID / run / 'native_image.npy') / summary['n_frames']\n",
" x0, x1, y0, y1 = (v // 10 for v in ROI)\n",
" return float(native[y0 + 1:y1 + 2, x0 + 1:x1 + 2].mean())\n",
"\n",
"\n",
"occupancy = flat_occupancy(FLAT_RUN)\n",
"print(f\"GridBased model epoch {grid_summary['epoch']}, \"\n",
" f\"{grid_summary['n_frames']:,} frames, \"\n",
" f\"occupancy {occupancy*100:.3f} % (flat field, ESF ROI)\")\n",
"print(f\" sub-pixel excess rms {grid_summary['subpixel_nonuniformity_before']*100:.1f} % -> \"\n",
" f\"{grid_summary['subpixel_nonuniformity_after']*100:.1f} % after flattening\\n\")\n",
"print(f\"{'variant':16s} {'edge photons':>14} {'flat photons':>14} relative\")\n",
"reference = variants['cluster <=3ph'][0].sum()\n",
"for name, (e, f) in variants.items():\n",
" print(f'{name:16s} {e.sum():14.4e} {f.sum():14.4e} {e.sum()/reference:8.3f}')"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "8c5580a3",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:58.476333Z",
"iopub.status.busy": "2026-09-28T08:22:58.476173Z",
"iopub.status.idle": "2026-09-28T08:22:58.963183Z",
"shell.execute_reply": "2026-09-28T08:22:58.962866Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABmcAAAH2CAYAAACFuVjoAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOy9eZgU1dn+f3fP1j29zcIwsiijEAXiIOIrKi4gogRQwSUi0ShqgrjGBRNDVDA/jSGuib6uKO6CGiUquIu+cYl+TYxRIa64oQI6W89Mz16/P7juM0+dqe7p2WF4Ptc1FzPV1VWnTjX93PVsx+c4jgNFURRFURRFURRFURRFURRFURSlV/D39QAURVEURVEURVEURVEURVEURVG2JzQ4oyiKoiiKoiiKoiiKoiiKoiiK0otocEZRFEVRFEVRFEVRFEVRFEVRFKUX0eCMoiiKoiiKoiiKoiiKoiiKoihKL6LBGUVRFEVRFEVRFEVRFEVRFEVRlF5EgzOKoiiKoiiKoiiKoiiKoiiKoii9iAZnFEVRFEVRFEVRFEVRFEVRFEVRehENziiKoiiKoiiKoiiKoiiKoiiKovQiGpxRFEVRFEVRFEVRFEVRFEVRFEXpRTQ4oyiKoiiKoiiKoiiKoiiKoiiK0otocEZRFEVRFGU75u6774bP58PLL7/crcd9+eWX4fP5cPfdd7u219bW4txzz8VOO+2EjIwMlJSUdOt5u4NJkyZtlePqKsnuSSp8Ph/mzp3bY2Pqqc9fXzF37lz4fL609y8pKcGkSZNc2zr7+evM/e0O+uv/F0VRFEVRFEXpaTQ4oyiKoiiKovQaS5YswY033ojZs2fj7rvvxg033NDXQ9quqaiowOLFi3s0OPLyyy9j8eLFqKio6LFzKNsGJSUl8Pl8SX8WL17s2v/zzz/HGWecgVGjRiEUCiEvLw+77rorjj/+eDz22GOufSdNmpT0uCNGjGh3bIsXL3a9x+/3Iy8vDwceeCDuu+++lO999tlncdRRR2HQoEHIzs5GUVERpk6diuXLl8NxnKTnevXVV9ufNEVRFEVRFKXfktnXA1AURVEURVH6HwcddBASiQSysrJc259//nmUlpbi6quv7qORbb943ZOKigpcfvnlANCmgqO7ePnll3H55Zdj7ty5yMvL65Fz9Ceee+45T4d+f6G4uBjXXHON52tjxowxv//rX//CQQcdhObmZpxwwgk4++yzAQCffPIJVq1aherqahx99NGu9/v9ftxzzz1tjhuJRNIe36WXXopdd90Vzc3N+PLLL7F06VKcdNJJ2LBhAy6++GLXvi0tLZg/fz7uuOMODB8+HPPmzcOwYcOwceNGPPLII5gzZw4efPBBrFixAsFgMO0xKIqiKIqiKNsHGpxRFEVRFEVRuh2/349AINBm+3fffYeddtqpD0akJLsnytZFdnZ2Xw+hRwmHwzjxxBPb3W/x4sWoqanBypUrMXPmTNdr119/Pb7++us27/H5fGkdOxWHHXYYDjjgAPP3qaeeil133RVLlizBRRddhIyMDPPaFVdcgTvuuAPHHHMMHnjgAeTk5JjXLr74YixYsADXXXcdzj77bNx5551dGpeiKIqiKIrS/9C2ZoqiKIqiKEobbrnlFmRmZuK4445DfX09gNb1MT766CPMnDkTsVgM4XAYM2bMwKeffup6v73+Bdv4rF+/Hq+88opnG6N33nkHxx57LAYOHIjs7GzssssuuPjii1FbW5v2uD/99FPMnTsXgwcPRnZ2NoYOHYozzzwT33//fZt9P/nkExx11FGIxWKIRCI47LDD8O677yY99n333YcxY8YgJycHQ4cOxa9//WusW7fOsx2T4zi44447MH78eIRCIYRCIUyYMAErV65M+1psNmzYAJ/PhwsvvNC1fd68efD5fDj99NNd2y+44AL4fD5s3LgRQNt7cvfdd2PnnXcGAFx++eXmnnitH/LWW29h8uTJCIfDyMvLw5w5c7B58+Z2xzxp0iRTmbPzzjsnbV/lOA5uuOEG7LrrrsjJycEuu+yC66+/3vOYXf2ccI2UL774Ascccwzy8/MRCoVw6KGH4t///rdr31TruPAz/fnnn7d57YcffsCpp56KoqIiBINBTJgwAS+99FKHxidZt24d5syZgx133BE5OTkYOHAg9t9/fyxbtszzGPysBgIBDBkyBL/73e/Q3NzcZr+e/P/SVT766CMAwCGHHOL5+tChQ3vs3JIhQ4Zg1KhRqKiocH3mv//+e/zxj3/EkCFDcM8997gCM8CWQNHVV1+NvfbaC8uWLcMHH3zQK+NVFEVRFEVRth20ckZRFEVRFEUxOI6D3/72t1iyZAnOP/98XHvtta4Fzjds2ICDDjoIRx55JJYsWYKPP/4YN954I4488ki899578Pu9c3+OPvpojBgxAueffz4GDBiA3/3udwBa2xg988wzmDVrFnbccUecc845KC4uxrvvvovrrrsOr732GtasWYPMzNTS9d///jcmTZqE3NxcnHrqqRg2bBg+/vhj3HLLLXjxxRfx1ltvIRaLAQC++uorTJgwARUVFZg/fz5GjRqFN954A5MmTUJhYWGbY//v//4vzj77bIwcORKXX345srKy8NBDDyVdq+WUU07Bvffei5kzZ+KEE04AADz22GM46qijcMstt2D+/Pmpb4QHQ4YMwciRI/HCCy+4tr/44ovw+/2e23fffXcUFxd7Hu+ggw7C9ddfj/PPPx9HHXWUaREVDodd+7377ruYNm0aTjrpJMyePRv//Oc/sXTpUpSXl+OZZ55JOebf/e53KCgowOOPP47rr78eAwYMAOBuXwUACxcuRFVVFU455RSEw2Hce++9uOCCCzBo0CAcf/zxZr/u+JwAQE1NDSZOnIg999wTV1xxBb766iv87//+Lw488EC88cYb2H333ds9RiqmTp2KaDSKSy+9FGVlZbj11lsxdepUPP3005gyZUqHjvXDDz/g4IMPRktLC04//XTsvPPOKC8vx3vvvYdXXnkFp5xyimv/2267DRs2bMAvfvELFBUV4bHHHsMf/vAHRCIRV1uunvz/koqWlhbP4A8A5OXlmfs3fPhwfPjhh1i6dCl+9atfub6HUuF17Fgs1qbFYro0NDTgq6++MmvQkFWrViGRSODnP/85QqGQ53v9fj/mzZuH008/HX/961/x4x//uFNjUBRFURRFUfopjqIoiqIoirLdsmzZMgeAs2bNGqeurs45/vjjHb/f7/z5z39us++wYcMcAM6DDz7o2n7VVVc5AJxnn33WbFuzZo0DwFm2bFmbY0ycONG1LZFIODvssIMzfvx4p66uzvXaI4884gBw7r777navZezYsc7OO+/s/PDDD67t//jHPxy/3+8sXrzYbPv5z3/uAHAef/xxz2sZNmyY2VZeXu6EQiFnl112caqqqsz2uro6Z++993YAOIsWLTLbV65c6QBwrrvuujZjPPzww51oNOo6Tkc466yzHJ/P52zcuNFxHMdZv369A8Bcz2effeY4juNs2rTJ8fl8zq9+9SvzXq97wvfL8UsAOD6fz3n11Vdd208//XQHgPPhhx+2O+ZFixY5AJz169e3eY2fvzFjxrjufXV1tVNYWOjst99+Zlt3fU4mTpzoAHDOOuss1/Y333zT8fl8ziGHHGK2JfscJ7uuk08+2QHgHHHEEU5zc7PZ/vnnnzu5ubnOj370I6elpcVs9/r/MHHiRNfn729/+5sDwFm+fHnK6+JYd9hhB6esrMxsb25udkaNGuUMGjTItX9P/X9JBb9Dkv38/e9/d40jOzvbAeDstNNOzkknneTceOONzr///W/PY/O+ev08/fTT7Y6N9/Opp55yNm/e7GzcuNF56623nKOOOsoB4Bx//PGu/S+88EIHgPPXv/415XHffvttB4BzzDHHtDmXvF5FURRFURRl+0PbmimKoiiKoigoLy/HYYcdhpUrV+KRRx7Bueee67nf4MGDMWfOHNe2Qw89FEBrG6KO8sILL+C7777D3LlzEY/H8f3335ufiRMnIhQK4dlnn015jPfffx///ve/cfzxx5vMfP4MHz4cP/rRj8wxWlpasHLlSowePRqzZs1yHee8885rUzny3HPPoaamBmeeeaZrYfGcnBycf/75bcZy3333IRgMYvbs2a5xfP/99zjqqKNQVVWFN954o1Nzdcghh8BxHNMi68UXX0RGRgYuv/xyZGRk4MUXXwQAvPTSS3AcJ2lLqI6w3377Yf/993dt6+o9tzn77LNdbaFCoRD2228/1/G743MiWbhwoevv8ePHY8qUKXjppZdQXl7epev57W9/66oiGzZsGE444QR8/PHH+M9//tOhY7FaY/Xq1aisrGx3/1NPPRX5+fnmb7/fj0MOOQTffvstqqurAfTs/5f2GDx4MJ5//nnPn9LSUrPfPvvsg3/961847bTT0NTUhHvvvRfnnHMOxo4di3HjxrVpQcdr9Tru3nvvnfb4Dj/8cBQVFaG4uBjjx4/HE088gTPOOKPNmjG8F6wuSgZfT+feKYqiKIqiKNsX2tZMURRFURRFwdy5c1FVVYXnn38+ZdulXXbZpc02tjX64YcfOnXudevWAQDOPPNMnHnmmZ77cN2U9o5x1VVX4aqrrvLch2PftGkT4vE4Ro8e3WafQCCA4cOHo6Kiwmz77LPPAAAjR45ss/+oUaM8x5JIJDBkyJCk423vepJx8MEHmxZmxx9/PF544QXstdde2HnnnTFu3Di88MIL+MUvfoEXXngBGRkZmDhxYqfOI+mJe57uOeTxu+NzQvLy8jB48OA220ePHo3nn38en332Gfbaa6+0juWF12eL2z755BPsscceaR/roIMOwqmnnoq77roLDz30EPbaay9MmDABxx57LPbbb782+7d3v8LhcI/+f2mPYDCYdmu3H//4x1i6dCkA4Ouvv8Zrr72Ge++9F6tXr8b06dPxwQcfuAJLine truncated
"text/plain": [
"<Figure size 1650x506 with 6 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, axes = plt.subplots(1, len(ORDER) if len(ORDER) < 4 else 3, figsize=(15, 4.6))\n",
"extent = [0, 990 * BIN_UM, 0, 990 * BIN_UM]\n",
"shown = ORDER if len(ORDER) < 4 else ['eta', 'cluster <=3ph', 'GridBased flat']\n",
"for ax, name in zip(np.atleast_1d(axes), shown):\n",
" image = variants[name][0]\n",
" im = ax.imshow(image, extent=extent, vmax=np.percentile(image, 99.5))\n",
" ax.add_patch(plt.Rectangle((ROI[0]*BIN_UM, ROI[2]*BIN_UM),\n",
" (ROI[1]-ROI[0])*BIN_UM, (ROI[3]-ROI[2])*BIN_UM,\n",
" edgecolor='red', facecolor='none', lw=1.4))\n",
" ax.set_title(name); ax.set_xlabel('x [um]'); ax.set_ylabel('y [um]')\n",
" plt.colorbar(im, ax=ax, fraction=0.046)\n",
"fig.suptitle('knife edge, with the published ESF ROI')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "1e889d01",
"metadata": {},
"source": [
"## 2. Edge spread function\n",
"\n",
"`generate_presampled_esf` fits an erf to every row of the ROI, fits a straight\n",
"line through the edge positions, and re-samples the ROI onto a common axis;\n",
"`get_odr_fitting` then fits the erf with x and y errors inside |x - p1| <= 4 sigma\n",
"(the fit of `Plotting/fig_knife_edge.py --fit odr`)."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "e36d7502",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:58.964776Z",
"iopub.status.busy": "2026-09-28T08:22:58.964610Z",
"iopub.status.idle": "2026-09-28T08:23:00.677476Z",
"shell.execute_reply": "2026-09-28T08:23:00.677078Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Final Edge Midpoint Fit: slope = -0.0858, intercept = 432.3990, chi2/ndf = 0.0751\n",
"Final Edge Midpoint Fit: slope = -0.0854, intercept = 432.2740, chi2/ndf = 0.0576\n",
"Final Edge Midpoint Fit: slope = -0.0855, intercept = 432.4402, chi2/ndf = 0.0479\n",
"Final Edge Midpoint Fit: slope = -0.0850, intercept = 432.2537, chi2/ndf = 0.0406\n",
"Final Edge Midpoint Fit: slope = -0.0858, intercept = 432.5887, chi2/ndf = 0.0361\n",
"variant sigma [um] chi2/ndf MTF50 MTF50 LSF MTF(200) LSF rows\n",
"eta 1.483 +- 0.024 1.61 126.4 131.7 0.187 100%\n",
"cluster 1ph 1.352 +- 0.022 1.44 138.6 144.8 0.229 100%\n",
"cluster <=2ph 1.372 +- 0.022 1.50 136.5 142.7 0.223 100%\n",
"cluster <=3ph 1.386 +- 0.018 1.02 135.2 139.7 0.244 100%\n",
"GridBased flat 1.346 +- 0.019 1.15 139.2 143.8 0.259 100%\n"
]
}
],
"source": [
"def analyse(edge_image, flat_image, label):\n",
" esf = generate_presampled_esf(edge_image, flat_image, ROI,\n",
" pixel_size_um=BIN_UM, bin_width_um=BIN_WIDTH_UM,\n",
" chi2_threshold=15.0, residual_tolerance_px=1.0)\n",
" x, z = esf['numpy']['x'], esf['numpy']['z']\n",
" odr = get_odr_fitting(x, z, esf['numpy']['x_err'], esf['numpy']['z_err'], n_sigma=4.0)\n",
" sigma, sigma_err = odr['sigma_um'], odr['sigma_err_um'] # sigma of the Gaussian LSF\n",
" try:\n",
" mtf50_lsf, mtf200_lsf = mtf_from_lsf(dict(x=x, z=z, pars=odr['beta']))\n",
" except Exception:\n",
" mtf50_lsf = mtf200_lsf = np.nan\n",
" n_fit = int(odr['mask'].sum())\n",
" ndf = max(n_fit - len(odr['beta']), 1)\n",
" x_fit = x[odr['mask']]\n",
" return dict(label=label, x=x, z=z, sigma=sigma, sigma_err=sigma_err, sigma_gauss=sigma,\n",
" chi2_ndf=odr['chi2_ndf'], chi2=odr['chi2_ndf'] * ndf, ndf=ndf,\n",
" mtf50=1000 * np.sqrt(np.log(2) / 2) / (np.pi * sigma),\n",
" mtf10=1000 * np.sqrt(np.log(10) / 2) / (np.pi * sigma),\n",
" mtf50_lsf=mtf50_lsf, mtf200_lsf=mtf200_lsf,\n",
" rows_kept=esf['valid_lines_ratio'],\n",
" edge_slope=esf['edge_slope'], edge_centre=odr['beta'][1],\n",
" fit_points=n_fit, fit_range=(x_fit.min(), x_fit.max()),\n",
" photons=float(edge_image.sum()), odr=odr)\n",
"\n",
"\n",
"results = {name: analyse(edge, flat, name) for name, (edge, flat) in variants.items()}\n",
"\n",
"print(f\"{'variant':16s} {'sigma [um]':>16} {'chi2/ndf':>9} {'MTF50':>7} {'MTF50 LSF':>9} \"\n",
" f\"{'MTF(200) LSF':>12} {'rows':>6}\")\n",
"for name, r in results.items():\n",
" print(f\"{name:16s} {r['sigma']:7.3f} +- {r['sigma_err']:.3f} {r['chi2_ndf']:9.2f} {r['mtf50']:7.1f} \"\n",
" f\"{r['mtf50_lsf']:9.1f} {r['mtf200_lsf']:12.3f} {r['rows_kept']*100:5.0f}%\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "55ce79f2",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:00.678708Z",
"iopub.status.busy": "2026-09-28T08:23:00.678621Z",
"iopub.status.idle": "2026-09-28T08:23:01.069086Z",
"shell.execute_reply": "2026-09-28T08:23:01.068788Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1375x528 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"COLOURS = {'eta': '#a0451c', 'cluster 1ph': '#6b6b6b', 'cluster <=2ph': '#7b5aa6',\n",
" 'cluster <=3ph': '#4b2a6b', 'GridBased raw': '#4cbcac', 'GridBased flat': '#10695f'}\n",
"frequency = np.linspace(0, 400, 2000)\n",
"fig, ax = plt.subplots(1, 2, figsize=(12.5, 4.8))\n",
"for name, r in results.items():\n",
" ax[0].plot(r['x'], r['z'], lw=1.1, color=COLOURS[name],\n",
" label=f\"{name}: $\\\\sigma$ = {r['sigma']:.3f} $\\\\pm$ {r['sigma_err']:.3f} $\\\\mu$m\")\n",
" sigma_mm = r['sigma_gauss'] / 1000.0\n",
" ax[1].plot(frequency, np.exp(-2 * np.pi**2 * sigma_mm**2 * frequency**2),\n",
" lw=1.6, color=COLOURS[name], label=f\"{name} ({r['mtf50']:.0f} lp/mm)\")\n",
"ax[0].set_xlim(-9, 9); ax[0].set_ylim(-0.05, 1.35)\n",
"ax[0].set_xlabel(r'position across the edge [$\\mu$m]')\n",
"ax[0].set_ylabel('relative intensity')\n",
"ax[0].legend(fontsize=8); ax[0].grid(alpha=.3); ax[0].set_title('oversampled ESF')\n",
"ax[1].axvline(200, color='k', ls=':', lw=.8, label='2.5 um Nyquist')\n",
"ax[1].axhline(0.5, color='grey', lw=.5)\n",
"ax[1].set_xlim(0, 400); ax[1].set_ylim(0, 1.05)\n",
"ax[1].set_xlabel('spatial frequency [lp/mm]'); ax[1].set_ylabel('MTF')\n",
"ax[1].legend(fontsize=8); ax[1].grid(alpha=.3); ax[1].set_title('MTF from the fitted ESF')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "6ff2c3c0",
"metadata": {},
"source": [
"## 3. Where the GridBased sub-pixel phase stands"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "30d4c001",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:01.070347Z",
"iopub.status.busy": "2026-09-28T08:23:01.070263Z",
"iopub.status.idle": "2026-09-28T08:23:01.586719Z",
"shell.execute_reply": "2026-09-28T08:23:01.586384Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABk4AAAHKCAYAAABFSbckAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOy9ebgdZZU1vmo6453nezMPhCQEHAG1VSZxQkT0QxBptNXPtp21GwRsBexu8OlGpAe62x4U9SfKoDafgoqtaKtgI/OQhJA5N7nzvecOZ6zp98fe+z2pJJBwM4e9nifPya1T9dY7VdU+9a61lxXHcQyFQqFQKBQKhUKhUCgUCoVCoVAoFAoF7MNdAYVCoVAoFAqFQqFQKBQKhUKhUCgUiiMFunCiUCgUCoVCoVAoFAqFQqFQKBQKhULB0IUThUKhUCgUCoVCoVAoFAqFQqFQKBQKhi6cKBQKhUKhUCgUCoVCoVAoFAqFQqFQMHThRKFQKBQKhUKhUCgUCoVCoVAoFAqFgqELJwqFQqFQKBQKhUKhUCgUCoVCoVAoFAxdOFEoFAqFQqFQKBQKhUKhUCgUCoVCoWDowolCoVAoFAqFQqFQKBQKhUKhUCgUCgVDF04UCoVCoVAoFAqFQqFQKBQKhUKhUCgYunCiUCgUCoVCoVAoFAqFQqFQKBQKhULB0IUTxUHHV7/6VcyfPx+O4+Ad73gHNm/eDMuycOedd76gcn71q1/huuuuOyzntSwLDz300PPud8MNN8CyrBdUNgBcc801aGhoeMHHPRceeeQRvOpVr0Iul4NlWSgUCges7BeKTZs24ayzzkJjYyMsy8Jjjz2G97///Vi1atULLuuaa67B/fffv8/bDyROP/10vO1tbzuo51AoFArF0QeNcQ4M9hQvHIrnu2BPsdNsnv2FQgHXXHMNVq9evU/bDzQWLlyIj3/84wf1HAqFQqFQKBQKxYsF7uGugOLYxtq1a/HZz34WV1xxBc4991x0dHTMuqxf/epXuOGGG3DVVVfN6ry9vb144IEHsGzZslnX4WjAxz72MYRhiLvvvhvZbBaNjY2HrS5XXXUVNm7ciDvvvBPNzc371ffXXnstGhoa8JrXvGafth9I/PM//zMcxzlo5SsUCoXi6IPGOAcOe4oXDsXzXXCgYqdCoYBrr70Wq1atwsqVK/e6/UDjhz/8IVpbWw9a+QqFQqFQKBQKxYsJunCiOKhYu3YtAOD//t//i8WLFwMANm/efFjOCwCvetWrDvq5DzfWrFmDT37ykzjjjDP2u6xKpYJMJrNfdXnd616HN73pTftdl8OJg/mSQ6FQKBRHJzTGOXA4FPHC88U0BzJ2Opx42ctedriroFAoFAqFQqFQHDPQVF2Kg4b3v//9OP/88wEAS5YsgWVZuOWWW/a477e+9S289rWvRVtbG1pbW3H66afjwQcfNN9fc801uPbaa1EsFmFZFizLwumnn/6CzvtcaSxuueUWnHTSSchkMpgzZw4+//nPIwiC523b1NQULr30UjQ2NqKzsxOXX375Xo/ZG/7whz/glFNOQSaTwYoVK/DjH/94t33uvvtunHrqqchms+js7MSf/dmfoVgsAqin25icnMRf/dVfJfooiiJcd911WLRoEdLpNI477jjcdNNNibIlZdiDDz6IV7/61chkMvjHf/xHAPRC4bzzzkNzczPy+TzOOeccbNiw4TnbIn39+OOP49vf/jYsy8LChQv3uO/AwAA+8IEPYPHixchmszjuuONw1VVXoVqtmn0kPchll11mxl/au6ftABDHMW644QYsW7YM6XQaixcvxle/+tU9tvmJJ57Aa1/7WuRyOaxatQo/+9nPEvvtmq5jX4+r1Wr45Cc/iba2NjQ3N+ODH/wgvvnNb8KyrEPyck2hUCgUBwca4+wb1q5di4suugjz5s1DLpfDypUr8ZWvfAVRFAF47njhQD3f9xTT7Izni51m05ZFixYBAC644AJT7+fbDgDVahVXXXUVFixYgHQ6jRUrVuDWW29NnFvSnP7qV7/Cy172MuTzeZxyyil4+OGHE/vtmqprX4+bnJzEJZdckhjzL3/5ywc1PZtCoVAoFAqFQnGkQxUnioOGL3zhCzj++ONx1VVX4Qc/+AF6e3uxZMkS86J/Z2zevBmXXnoplixZglqthltvvRWvf/3r8cQTT2DZsmX40Ic+hP7+ftx666345S9/CQBoamra7/PeeOONuPzyy/GZz3wGX/nKV7BmzRp8/vOfRxiG+PKXv/ycbfvABz6An/3sZ/jyl7+MRYsW4eabb8bjjz8+y54CfN/HhRdeiD//8z/HokWL8C//8i84//zz8eijjxo/kDvvvBMXXngh/uRP/gTXXnstBgYGcMUVV2BiYgLf+9738PKXvxwPPPAAzjzzTFx88cX40Ic+ZProsssuw0033YSrrroKr3vd6/Dzn/8cn/nMZzA9PY0vfOELph61Wg3vfe978ZnPfAbXX389WltbsXHjRrzmNa/BqlWrcMstt8C2bfzN3/wNzjrrLDzzzDNIp9O7tUdShrz3ve/F8uXL8YUvfGGP+wHA6Ogo2tracOONN6K1tRXr1q3DNddcg8HBQXz9618HADzwwAN49atfjU984hO4+OKLAZAK5Lm2A8CnPvUp/Md//Ac+//nP49RTT8X999+Pz33uc8hms/jIRz6S6PtLLrkEn/zkJ/GFL3wB119/Pd71rndhy5YtaG9vf94x29txV1xxBb72ta/hS1/6El760pfi9ttvx1/+5V/u26RQKBQKxRELjXH2Ddu3b8fxxx+P9773vWhsbMRjjz2Gq6++GsViEV/84hefM16oVqv7/XzfU0yzK54vdppNW37wgx/gne98J6677jqjXnm+7QDw7ne/G7/97W9x9dVXY8WKFbjnnntwySWXoLW1FW95y1vM+QcHB/HJT34SV1xxBZqamnDFFVfg/PPPx4YNG+B53nOOwb4c9yd/8if45S9/ib/927/FggUL8LWvfQ2PPPLIPo+zQqFQKBQKhUJxTCJWKA4i7rjjjhhAvGnTJrNt06ZNMYD4jjvu2OMxYRjGvu/Hxx9/fHzllVea7VdffXWcz+cP2HmnpqbihoaGxDniOI5vvvnmOJvNxqOjo3Ecx/F9990XA4j/8Ic/xHEcx6tXr44ty4r/8z//0xzj+348f/78eDaX1NVXXx0DSJQXBEG8cOHC+D3veU8cx3EcRVG8YMEC87fg7rvvji3Lip966imzLZ/Px1dffbX5e2RkJPY8L77ssssSx374wx+O8/l8PD09najH7bffntjv0ksvjRctWhSXy2WzbXh4OM7n8/HNN9/8vG074YQT4ve9732Jbe973/viE0444TmP8X0//s53vhO7rhsXi0WzHUD8d3/3d7vtv6ft69evjy3Lir/2ta8ltl922WVxT09PHIZhos1333232efZZ5+NAcTf/va3zbbTTjstPuecc8zf+3Lc2NhYnMlk4i996UuJOpx22mm7zU2FQqFQHH3QGOeFIYqi2Pf9+G/+5m/i3t7exHd7ihcOxPN915jmubBr7BTHuz/796UtzzX+z7X9l7/8ZQwg/tnPfpbYfsEFF8Qnn3yy+ft973vfbvHez3/+8xhA/Jvf/MZsW7BgQfyxj33sBR339NNPxwDib33rW2afIAjixYsX7/eYKxQKhUKhUCgURzM0VZfiiMCaNWtw/vnno7u7G47jwPM8PPPMM1i3bt1BO+f999+PmZkZXHDBBQiCwPw788wzUS6X8dRTT+3xuAcffBBxHJtUGQDgui7OO++8/arPzuU5joO3v/3t+P3vfw8AWLduHbZs2YJ3v/vdibqedtppsCwLDz300HOW+7//+79G0bIz3vOe96BYLOLRRx9NbH/rW9+a+Pvee+/FeeedB9d1zXlbW1vxkpe8BH/4wx/2q80Apdy46aabsHLlSmSzWXieh/e+970IggAbN26cVZn//d//DQB417veleivs846C4ODg9i2bZvZ17ZtvOENbzB/L126FKlUCv39/c97jr0d9+STT6JSqeDtb3974rj9nScKhUKhOLrwYo5xKpUKrr76aixduhTpdBqe5+Hzn/88BgYGMDMz84LLeyHPd2D3mGZ/cKDbAlCM1dbWhjPPPHO39jz66KMIw9Ds29fXhxNOOMH8LQqcvcUreztOYrmd4xXHcRIpShUKhUKhUCgUihcjNFWX4rBjenoab3zjG9HZ2Ykbb7wRCxYsQCaTwYc+9CFUKpWDdt7R0VEAlKZhT9j1x7dgYGAAnuftlvKhu7t71nXZU3ldXV0YGBhI1HXnFxn7UlcAmJiYAAD09PQktsvf4+PjZlsul0M+n0/sNzo6iptuumk3TxQAyGazz3nefcVNN92Ev/iLv8Dll1+OM844A62trfjDH/6Aj33sY7Me/9HRUcRxjI6Ojj1+v23bNixYsAAAtSGVSiW+9zxvr+fe23Eydp2dnYl9urq69r0hCoVCoTiq8WKPcT73uc/h3//933H11VfjFa94BVpaWnDXXXfhr//6r1GpVNDQ0PCCynshz/c9xTT7gwPdFoDaMz4+/pyptgYGBjB37lwAQEtLS+I7iUH2No/2dpyMeXNzc2I/jVcUCoVCoVAoFC926MKJ4rDjgQceQH9/P3784x/jJS95idk+OTlpfiweDLS1tQEAfvCDH2DevHm7fS9Gnruit7cXvu9jYmIi8WJhaGho1nXZU3nDw8Mm/7XU9Z/+6Z9w6qmn7nZ8X1/fc5Ytxw4NDWHOnDlm++DgYOJ7AHs0AW1ra8M555yDj370o7t919jY+Lzt2hfccccdePvb347rr7/ebFu9evV+ldnW1gbLsvDb3/52t8UNADj++OP3q/x9gYzdyMhIYnyGh4cP+rkVCoVCcWTgxR7j3HHHHfjTP/1TfO5znzPb7r777lmVBbyw5/uBNjY/0G0BqD2dnZ2455579vj9oVi8kDGLine truncated
"text/plain": [
"<Figure size 1650x462 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"before = np.load(GRID / FLAT_RUN / 'subpixel_phase_before.npy')\n",
"after = np.load(GRID / FLAT_RUN / 'subpixel_phase_after.npy')\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(15, 4.2))\n",
"for ax, histogram, title in ((axes[0], before, 'before flattening'),\n",
" (axes[1], after, 'after flattening')):\n",
" im = ax.imshow(histogram.T / histogram.mean(), origin='lower', extent=[0, 1, 0, 1],\n",
" vmin=0.9, vmax=1.1, cmap='viridis')\n",
" ax.set_title(f'flat field, {title}', fontsize=10)\n",
" ax.set_xlabel('sub-pixel x'); ax.set_ylabel('sub-pixel y')\n",
" plt.colorbar(im, ax=ax, fraction=0.046)\n",
"for histogram, title in ((before, 'before'), (after, 'after')):\n",
" profile = histogram.sum(axis=1)\n",
" axes[2].plot(np.linspace(0, 1, len(profile)), profile / profile.mean(), lw=1, label=title)\n",
"axes[2].axhline(1, color='k', lw=.6); axes[2].set_xlabel('sub-pixel x')\n",
"axes[2].set_ylabel('relative density'); axes[2].legend(); axes[2].grid(alpha=.3)\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "9837906d",
"metadata": {},
"source": [
"## 4. Relative DQE"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "460617ee",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:01.588078Z",
"iopub.status.busy": "2026-09-28T08:23:01.587992Z",
"iopub.status.idle": "2026-09-28T08:23:01.691826Z",
"shell.execute_reply": "2026-09-28T08:23:01.691526Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"variant QE rel MTF(200) DQE rel (relative to cluster <=3ph)\n",
"eta 0.710 0.187 0.025\n",
"cluster 1ph 0.710 0.229 0.037\n",
"cluster <=2ph 0.929 0.223 0.046\n",
"cluster <=3ph 1.000 0.244 0.060\n",
"GridBased flat 1.044 0.259 0.070\n",
"\n",
"highest relative DQE at 200 lp/mm: GridBased flat (2.82x eta)\n"
]
}
],
"source": [
"# DQE(f) is proportional to QE * MTF(f)^2. Every chain sees the same beam, so\n",
"# the ratio of detected photons is the ratio of QE and the relative DQE is well\n",
"# defined even without the absolute incident flux.\n",
"def mtf_at(frequency_lpmm, sigma_gauss_um):\n",
" sigma_mm = sigma_gauss_um / 1000.0\n",
" return float(np.exp(-2 * np.pi**2 * sigma_mm**2 * frequency_lpmm**2))\n",
"\n",
"\n",
"FREQUENCY = 200.0 # lp/mm, the Nyquist of the 2.5 um bins\n",
"REFERENCE = 'cluster <=3ph'\n",
"reference_photons = results[REFERENCE]['photons']\n",
"print(f\"{'variant':16s} {'QE rel':>8} {'MTF(200)':>9} {'DQE rel':>9} (relative to {REFERENCE})\")\n",
"relative_dqe = {}\n",
"for name, r in results.items():\n",
" qe = r['photons'] / reference_photons\n",
" mtf200 = r['mtf200_lsf'] # measured from the LSF, as in the poly notebook\n",
" relative_dqe[name] = qe * mtf200**2\n",
" print(f'{name:16s} {qe:8.3f} {mtf200:9.3f} {relative_dqe[name]:9.3f}')\n",
"best = max(relative_dqe, key=relative_dqe.get)\n",
"print(f\"\\nhighest relative DQE at {FREQUENCY:.0f} lp/mm: {best} \"\n",
" f\"({relative_dqe[best]/relative_dqe['eta']:.2f}x eta)\")"
]
},
{
"cell_type": "markdown",
"id": "9d317fe5",
"metadata": {},
"source": [
"## 5. Summary"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "764dbd8d",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:23:01.692963Z",
"iopub.status.busy": "2026-09-28T08:23:01.692876Z",
"iopub.status.idle": "2026-09-28T08:23:01.799221Z",
"shell.execute_reply": "2026-09-28T08:23:01.798889Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"eta\": {\n",
" \"sigma_um\": 1.482743287676068,\n",
" \"sigma_err_um\": 0.023612628870164847,\n",
" \"mtf50_lsf_lpmm\": 131.67127170307347,\n",
" \"mtf200_lsf\": 0.18708166715602986,\n",
" \"chi2_ndf\": 1.6091412712838244,\n",
" \"mtf50_lpmm\": 126.38103081415967,\n",
" \"mtf10_lpmm\": 230.34405458447614,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 156697901.0\n",
" },\n",
" \"cluster 1ph\": {\n",
" \"sigma_um\": 1.3517498054839803,\n",
" \"sigma_err_um\": 0.02228742348935663,\n",
" \"mtf50_lsf_lpmm\": 144.76471476919644,\n",
" \"mtf200_lsf\": 0.22898527725431547,\n",
" \"chi2_ndf\": 1.4377846589189212,\n",
" \"mtf50_lpmm\": 138.62818723482948,\n",
" \"mtf10_lpmm\": 252.66591450992405,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 156697890.0\n",
" },\n",
" \"cluster <=2ph\": {\n",
" \"sigma_um\": 1.3724427091990845,\n",
" \"sigma_err_um\": 0.021926901151490465,\n",
" \"mtf50_lsf_lpmm\": 142.69021685073372,\n",
" \"mtf200_lsf\": 0.2230447974394754,\n",
" \"chi2_ndf\": 1.5018077194468944,\n",
" \"mtf50_lpmm\": 136.53803096715998,\n",
" \"mtf10_lpmm\": 248.85636282080927,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 205040034.0\n",
" },\n",
" \"cluster <=3ph\": {\n",
" \"sigma_um\": 1.3855387111593214,\n",
" \"sigma_err_um\": 0.017612306263149854,\n",
" \"mtf50_lsf_lpmm\": 139.7355710728802,\n",
" \"mtf200_lsf\": 0.2440890142229673,\n",
" \"chi2_ndf\": 1.0195693691410581,\n",
" \"mtf50_lpmm\": 135.24748433227265,\n",
" \"mtf10_lpmm\": 246.50419222530726,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 220757025.0\n",
" },\n",
" \"GridBased flat\": {\n",
" \"sigma_um\": 1.3461043903325456,\n",
" \"sigma_err_um\": 0.01889890619496615,\n",
" \"mtf50_lsf_lpmm\": 143.7567164975865,\n",
" \"mtf200_lsf\": 0.25887598776406284,\n",
" \"chi2_ndf\": 1.1515016338976902,\n",
" \"mtf50_lpmm\": 139.2095787481861,\n",
" \"mtf10_lpmm\": 253.72556782676153,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 230530601.0\n",
" }\n",
"}\n",
"\n",
"written to /mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased/Edge_2filters_12keV/knife_edge_comparison.json\n"
]
}
],
"source": [
"summary = {\n",
" 'measurement': 'Edge/Flat_2filters_12keV',\n",
" 'occupancy_percent': occupancy * 100, # flat field, ESF ROI\n",
" 'roi_superpixel': list(ROI),\n",
" 'bin_um': BIN_UM,\n",
" 'bin_width_um': BIN_WIDTH_UM,\n",
" 'convention': 'sigma = ODR p2 (Gaussian LSF); MTF50 = 1000 sqrt(ln 2 / 2) / (pi sigma); '\n",
" 'ESF from DeepLearning/src, bin width 0.3 um',\n",
" 'grid_model_epoch': grid_summary['epoch'],\n",
" 'grid_frames': grid_summary['n_frames'],\n",
" 'grid_subpixel_before': grid_summary['subpixel_nonuniformity_before'],\n",
" 'grid_subpixel_after': grid_summary['subpixel_nonuniformity_after'],\n",
" 'variants': {name: dict(sigma_um=float(r['sigma']), sigma_err_um=float(r['sigma_err']),\n",
" mtf50_lsf_lpmm=float(r['mtf50_lsf']),\n",
" mtf200_lsf=float(r['mtf200_lsf']),\n",
" chi2_ndf=float(r['chi2_ndf']),\n",
" mtf50_lpmm=float(r['mtf50']), mtf10_lpmm=float(r['mtf10']),\n",
" rows_kept=float(r['rows_kept']), photons=float(r['photons']))\n",
" for name, r in results.items()},\n",
" 'relative_dqe_200lpmm': relative_dqe,\n",
"}\n",
"print(json.dumps(summary['variants'], indent=1))\n",
"out = GRID / EDGE_RUN / 'knife_edge_comparison.json'\n",
"json.dump(summary, open(out, 'w'), indent=1)\n",
"print(f'\\nwritten to {out}')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch_nightly",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+620
View File
@@ -0,0 +1,620 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d54c3f3e",
"metadata": {},
"source": [
"# Knife edge, 3 filters, pos0 — GridBased against the published chains\n",
"\n",
"Same measurement, same ROI, same ESF construction and same erf fit as\n",
"`DeepLearning/Notebooks/KnifeEdge_3filters_pos0.ipynb`, so the only thing that\n",
"differs between the rows of the final table is the reconstruction itself:\n",
"\n",
"| | |\n",
"|---|---|\n",
"| eta | classical eta interpolation from clusters |\n",
"| cluster 1ph / ≤2ph / ≤3ph | cluster finder + the 1/2/3-photon CNNs, summed |\n",
"| GridBased flattened | this repo, sub-pixel phase remapped (Rosenblatt) |\n",
"\n",
"The ESF code is the published one in `DeepLearning/src/funcs_postprocess.py`,\n",
"the same that `Plotting/` uses.\n",
"\n",
"**Edge width (revised 2026-09-28).** The ESF is binned and fitted exactly as\n",
"`Plotting/fig_knife_edge.py --fit odr` does it: `generate_presampled_esf()` and\n",
"`get_odr_fitting()` from `DeepLearning/src/funcs_postprocess.py` (bin content\n",
"Σk/Σf), symmetric ±4σ window, **ESF bin width 0.3 µm**. The ODR model is\n",
"`p0/2 (1 + erf((x - p1)/(sqrt(2) sigma))) + p3`, so the fit gives **sigma**, the\n",
"width of the Gaussian LSF, which is what the tables quote, with\n",
"`MTF50 [lp/mm] = 1000 sqrt(ln 2 / 2) / (pi sigma)`; MTF50 and MTF(200 lp/mm) are\n",
"also measured from the LSF (`Plotting/knife_edge_numbers.py`: first crossing,\n",
"central-difference correction), and the relative DQE uses the latter. Only the\n",
"flattened GridBased reconstruction is analysed. Same processing as\n",
"`KnifeEdge_poly2to20keV_Moench040.ipynb`.\n",
"\n",
"**Occupancy** is that of the flat field inside the ESF ROI: the reconstructed\n",
"photons per 25 µm pixel per frame over `ROI // 10` (x 33–46, y 26–49 of the\n",
"99 × 99 frame), the pixels over which `DeepLearning/Notebooks/KnifeEdge_3filters_pos0.ipynb`\n",
"sums the deposited energy (0.705 % at 3 filters from the energy, 0.747 % here\n",
"from the photons of the 12 keV model).\n",
"\n",
"*Until 2026-09-28 this notebook took the fitted sigma for sqrt(2) sigma (so its\n",
"MTF50 was sqrt(2) too high), used 0.5 µm bins, and binned the ESF with an older\n",
"copy of `generate_presampled_esf` (inverse-variance mean of k/f, biased low in\n",
"the toe, sigma ~3 % too small). The previous version is in\n",
"`Backup_260928_pre_sigma_fix/`.* The published MTF50\n",
"of 149 lp/mm corresponds to sigma = 1.26 µm.\n",
"\n",
"Run in the `pytorch_nightly` environment (ROOT + scipy + jupyter)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "bbf5c366",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:50.007938Z",
"iopub.status.busy": "2026-09-28T08:22:50.007862Z",
"iopub.status.idle": "2026-09-28T08:22:51.585942Z",
"shell.execute_reply": "2026-09-28T08:22:51.585615Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Welcome to JupyROOT 6.28/10\n"
]
}
],
"source": [
"import json\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"REPO = Path('/home/xie_x1/MLXID/GridBased')\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/DeepLearning/src') # the code Plotting/ uses\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/Plotting')\n",
"from ROOT import TCanvas, gROOT\n",
"gROOT.SetBatch(True)\n",
"from funcs_postprocess import generate_presampled_esf, get_odr_fitting\n",
"from knife_edge_numbers import mtf as mtf_from_lsf # first crossing, sinc-corrected\n",
"\n",
"GRID = Path('/mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased')\n",
"PUBLISHED = Path('/home/xie_x1/MLXID/DeepLearning/InferenceResults')\n",
"ETA = Path('/home/xie_x1/MLXID/EtaInterpolation/Results')\n",
"\n",
"EDGE_RUN, FLAT_RUN = 'Edge_3filters_pos0_12keV', 'Flat_3filters_pos0_12keV'\n",
"BIN_UM = 2.5 # super-pixel = 25 um / 10\n",
"ROI = (330, 465, 260, 490) # x1, x2, y1, y2, as published\n",
"CROP = slice(10, -10) # 1010 -> 990, as published\n",
"BIN_WIDTH_UM = 0.3 # presampled ESF bin, as Plotting/ and the manuscript\n",
"\n",
"plt.rcParams.update({'figure.dpi': 110, 'image.cmap': 'gray',\n",
" 'image.origin': 'lower', 'image.interpolation': 'nearest'})"
]
},
{
"cell_type": "markdown",
"id": "c749780c",
"metadata": {},
"source": [
"## 1. Load every reconstruction of the same measurement"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "3b145751",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:51.587508Z",
"iopub.status.busy": "2026-09-28T08:22:51.587285Z",
"iopub.status.idle": "2026-09-28T08:22:51.804809Z",
"shell.execute_reply": "2026-09-28T08:22:51.804521Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"GridBased model epoch 135, 14,000,000 frames, occupancy 0.747 % (flat field, ESF ROI)\n",
" sub-pixel excess rms 22.63 % -> 4.30 % after flattening\n",
"\n",
"variant edge photons flat photons relative to <=3ph\n",
"eta 1.6099e+08 2.7823e+08 0.855\n",
"cluster 1ph 1.6099e+08 2.7823e+08 0.855\n",
"cluster <=2ph 1.8464e+08 3.1950e+08 0.981\n",
"cluster <=3ph 1.8828e+08 3.2591e+08 1.000\n",
"GridBased flat 1.9083e+08 3.3060e+08 1.014\n"
]
}
],
"source": [
"def crop(image):\n",
" \"\"\"The published frames are cropped by 10 super-pixels on each side.\"\"\"\n",
" return image[CROP, CROP] if image.shape[0] == 1010 else image\n",
"\n",
"\n",
"def published(run, experiment, n_photons):\n",
" name = f'{n_photons}Photon_ML_superFrame_etaInterpolated.npy'\n",
" return np.load(PUBLISHED / run / experiment / 'augX1' / name).astype(float)\n",
"\n",
"\n",
"EXPERIMENT = {1: '1ph_260512_12keV_v260511_06',\n",
" 2: '2ph_260610_12keV_v260610_01',\n",
" 3: '3ph_260611_12keV_v260611_00'}\n",
"KNIFE, FLAT = 'KnifeEdge_3filters_pos0', 'FlatField_3filters_pos0'\n",
"\n",
"cluster_edge = {n: published(KNIFE, EXPERIMENT[n], n) for n in (1, 2, 3)}\n",
"cluster_flat = {n: published(FLAT, EXPERIMENT[n], n) for n in (1, 2, 3)}\n",
"\n",
"grid_summary = json.load(open(GRID / EDGE_RUN / 'summary.json'))\n",
"flat_summary = json.load(open(GRID / FLAT_RUN / 'summary.json'))\n",
"\n",
"\n",
"def grid_image(run, flattened):\n",
" name = 'binned_image_flattened.npy' if flattened else 'binned_image.npy'\n",
" return np.load(GRID / run / name).astype(float)\n",
"\n",
"\n",
"variants = {\n",
" 'eta': (np.load(ETA / '2603MaxIV_Edge3Filters_pos0_12keV_fromClusters.npy'),\n",
" np.load(ETA / '2603MaxIV_FlatField3Filters_pos0_12keV_fromClusters.npy')),\n",
" 'cluster 1ph': (cluster_edge[1], cluster_flat[1]),\n",
" 'cluster <=2ph': (cluster_edge[1] + cluster_edge[2], cluster_flat[1] + cluster_flat[2]),\n",
" 'cluster <=3ph': (cluster_edge[1] + cluster_edge[2] + cluster_edge[3],\n",
" cluster_flat[1] + cluster_flat[2] + cluster_flat[3]),\n",
" 'GridBased flat': (grid_image(EDGE_RUN, True),\n",
" grid_image(FLAT_RUN, True)),\n",
"}\n",
"variants = {name: (crop(edge), crop(flat)) for name, (edge, flat) in variants.items()}\n",
"\n",
"def flat_occupancy(run):\n",
" # reconstructed photons per 25 um pixel per frame in the flat field, inside the\n",
" # ESF ROI: ROI // 10 in pixels of the 99 x 99 frame, both ends included as in\n",
" # DeepLearning/Notebooks; +1 because the 101 x 101 frame is cropped by 1 px\n",
" summary = json.load(open(GRID / run / 'summary.json'))\n",
" native = np.load(GRID / run / 'native_image.npy') / summary['n_frames']\n",
" x0, x1, y0, y1 = (v // 10 for v in ROI)\n",
" return float(native[y0 + 1:y1 + 2, x0 + 1:x1 + 2].mean())\n",
"\n",
"\n",
"occupancy = flat_occupancy(FLAT_RUN)\n",
"print(f\"GridBased model epoch {grid_summary['epoch']}, \"\n",
" f\"{grid_summary['n_frames']:,} frames, \"\n",
" f\"occupancy {occupancy*100:.3f} % (flat field, ESF ROI)\")\n",
"print(f\" sub-pixel excess rms {grid_summary['subpixel_nonuniformity_before']*100:.2f} % -> \"\n",
" f\"{grid_summary['subpixel_nonuniformity_after']*100:.2f} % after flattening\\n\")\n",
"print(f\"{'variant':16s} {'edge photons':>14} {'flat photons':>14} relative to <=3ph\")\n",
"reference = variants['cluster <=3ph'][0].sum()\n",
"for name, (edge, flat) in variants.items():\n",
" print(f'{name:16s} {edge.sum():14.4e} {flat.sum():14.4e} {edge.sum()/reference:8.3f}')"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7e4f857c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:51.805974Z",
"iopub.status.busy": "2026-09-28T08:22:51.805891Z",
"iopub.status.idle": "2026-09-28T08:22:52.219065Z",
"shell.execute_reply": "2026-09-28T08:22:52.218827Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABmcAAAH2CAYAAACFuVjoAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOydeZgU1bn/v92z9d49MwzIoqAQg0RQ8YqKC4ioAVTcIhCN4oa7xu3GGBX0F2OMa6LXFcVdXKJEA24IeqMxek2MUSHuCxJFlll6umef+v3B8z3z1pnqnp4dZt7P88zDTHd11alTRb/ferfjcxzHgaIoiqIoiqIoiqIoiqIoiqIoitIj+Ht7AIqiKIqiKIqiKIqiKIqiKIqiKP0JDc4oiqIoiqIoiqIoiqIoiqIoiqL0IBqcURRFURRFURRFURRFURRFURRF6UE0OKMoiqIoiqIoiqIoiqIoiqIoitKDaHBGURRFURRFURRFURRFURRFURSlB9HgjKIoiqIoiqIoiqIoiqIoiqIoSg+iwRlFURRFURRFURRFURRFURRFUZQeRIMziqIoiqIoiqIoiqIoiqIoiqIoPYgGZxRFURRFURRFURRFURRFURRFUXoQDc4oiqIoiqIoiqIoiqIoiqIoiqL0IBqcURRFURRF6cfcf//98Pl8ePXVV7t0v6+++ip8Ph/uv/9+1+vpdBrnnXcetttuO+Tl5WHEiBFdetyuYPLkyVvkuDpLpmuSDZ/Ph7lz53bbmLrr/ust5s6dC5/Pl/P2I0aMwOTJk12vdfT+68j17Qr66v8XRVEURVEUReluNDijKIqiKIqi9BjXXXcdbr31VsyaNQv3338/brnllt4eUr+moqICCxYs6NbgyKuvvooFCxagoqKi246hbB2MGDECPp8v48+CBQtc23/55Zc488wzsdNOOyEcDiORSGDHHXfE7Nmz8fTTT7u2nTx5csb9jho1qs2xLViwwPUZv9+PRCKB/fbbDw899FDWz7744os48sgjMXjwYBQWFqKsrAyHHHIIFi9eDMdxMh7r9ddfb3vSFEVRFEVRlD5Lfm8PQFEURVEURel77L///qipqUFBQYHr9Zdffhljx47F9ddf30sj6794XZOKigpcddVVANCqgqOrePXVV3HVVVdh7ty5SCQS3XKMvsRLL73k6dDvKwwaNAg33HCD53vjxo0zv//jH//A/vvvj6amJhx33HE455xzAACffvopli5diurqahx11FGuz/v9fjzwwAOt9huNRnMe3xVXXIEdd9wRTU1N+Prrr7Fw4UKccMIJWLt2LS699FLXts3NzTjjjDNwzz33YOTIkZg3bx6GDx+OdevW4cknn8ScOXPw6KOP4vHHH0cwGMx5DIqiKIqiKEr/QIMziqIoiqIoSpfj9/sRCARavf7dd99hu+2264URKZmuibJlUVhY2NtD6FYikQiOP/74NrdbsGABUqkUlixZgpkzZ7reu/nmm/HNN9+0+ozP58tp39k4+OCDse+++5q/Tz75ZOy444647rrrcMkllyAvL8+89+tf/xr33HMPjj76aDzyyCMoKioy71166aW4+OKLcdNNN+Gcc87Bvffe26lxKYqiKIqiKH0PbWumKIqiKIqitOKOO+5Afn4+jj32WNTV1QFoWR/j448/xsyZMxGPxxGJRDBjxgx89tlnrs/b61+wjc8XX3yB1157zbON0bvvvotjjjkGAwcORGFhIXbYYQdceumlSKfTOY/7s88+w9y5czFkyBAUFhZi2LBhOOuss7Bhw4ZW23766ac48sgjEY/HEY1GcfDBB+O9997LuO+HHnoI48aNQ1FREYYNG4b//u//xurVqz3bMTmOg3vuuQcTJkxAOBxGOBzGxIkTsWTJkpzPxWbt2rXw+Xy46KKLXK/PmzcPPp8Pp59+uuv1Cy+8ED6fD+vWrQPQ+prcf//92H777QEAV111lbkmXuuHvP3225gyZQoikQgSiQTmzJmD9evXtznmyZMnm8qc7bffPmP7KsdxcMstt2DHHXdEUVERdthhB9x8882e++zsfcI1Ur766iscffTRKC4uRjgcxkEHHYR//vOfrm2zrePCe/rLL79s9d7GjRtx8skno6ysDMFgEBMnTsSKFSvaNT7J6tWrMWfOHGy77bYoKirCwIEDsc8++2DRokWe++C9GggEMHToUPzqV79CU1NTq+268/9LZ/n4448BAAceeKDn+8OGDeu2Y0uGDh2KnXbaCRUVFa57fsOGDfjtb3+LoUOH4oEHHnAFZoDNgaLrr78eu+++OxYtWoQPP/ywR8arKIqiKIqibD1o5YyiKIqiKIpicBwHv/zlL3HdddfhggsuwI033uha4Hzt2rXYf//9cfjhh+O6667DJ598gltvvRWHH3443n//ffj93rk/Rx11FEaNGoULLrgAAwYMwK9+9SsALW2MXnjhBRxxxBHYdtttce6552LQoEF47733cNNNN+GNN97AypUrkZ+fXbr+85//xOTJkxEKhXDyySdj+PDh+OSTT3DHHXfglVdewdtvv414PA4AWLNmDSZOnIiKigqcccYZ2GmnnfDmm29i8uTJKC0tbbXv//mf/8E555yD0aNH46qrrkJBQQEee+yxjGu1nHTSSXjwwQcxc+ZMHHfccQCAp59+GkceeSTuuOMOnHHGGdkvhAdDhw7F6NGjsXz5ctfrr7zyCvx+v+frO++8MwYNGuS5v/333x8333wzLrjgAhx55JGmRVQkEnFt995772HatGk44YQTMGvWLPz973/HwoULUV5ejhdeeCHrmH/1q1+hpKQEzzzzDG6++WYMGDAAgLt9FQBcdtllqKqqwkknnYRIJIIHH3wQF154IQYPHozZs2eb7briPgGAVCqFSZMmYbfddsOvf/1rrFmzBv/zP/+D/fbbD2+++SZ23nnnNveRjUMOOQSxWAxXXHEFNm3ahDvvvBOHHHIInn/+eUydOrVd+9q4cSMOOOAANDc34/TTT8f222+P8vJyvP/++3jttddw0kknuba/6667sHbtWpx66qkoKyvD008/jd/85jeIRqOutlzd+f8lG83NzZ7BHwBIJBLm+o0cORIfffQRFi5ciPPPP9/1PZQNr33H4/FWLRZzpb6+HmvWrDFr0JClS5eipqYGP/vZzxAOhz0/6/f7MW/ePJx++un44x//iB/96EcdGoOiKIqiKIrSR3EURVEURVGUfsuiRYscAM7KlSud2tpaZ/bs2Y7f73d+//vft9p2+PDhDgDn0Ucfdb1+7bXXOgCcF1980by2cuVKB4CzaNGiVvuYNGmS67Wamhpnm222cSZMmODU1ta63nvyyScdAM7999/f5rnsuuuuzvbbb+9s3LjR9frf/vY3x+/3OwsWLDCv/exnP3MAOM8884znuQwfPty8Vl5e7oTDYWeHHXZwqqqqzOu1tbXOHnvs4QBw5s+fb15fsmSJA8C56aabWo3x0EMPdWKxmGs/7eHss892fD6fs27dOsdxHOeLL75wAJjz+fzzzx3HcZzvv//e8fl8zvnnn28+63VN+Hk5fgkAx+fzOa+//rrr9dNPP90B4Hz00Udtjnn+/PkOAOeLL75o9R7vv3HjxrmufXV1tVNaWursvffe5rWuuk8mTZrkAHDOPvts1+tvvfWW4/P5nAMPPNC8luk+znReJ554ogPAOeyww5ympibz+pdffumEQiHnBz/4gdPc3Gxe9/r/MGnSJNf996c//ckB4CxevDjreXGs22yzjbNp0ybzelNTk7PTTjs5gwcPdm3fXf9fssHvkEw/f/nLX1zjKCwsdAA42223nXPCCSc4t956q/PPf/7Tc9+8rl4/zz//fJtj4/X885//7Kxfv95Zt26d8/bbbztHHnmkA8CZPXu2a/uLLrrIAeD88Y9/zLrfd955xwHgHH300a2OJc9XURRFURRF6X9oWzNFURRFURQF5eXlOPjgg7FkyRI8+eSTOO+88zy3GzJkCObMmeN67aCDDgLQ0oaovSxfvhzfffcd5s6di2QyiQ0bNpifSZMmIRwO48UXX8y6jw8++AD//Oc/MXv2bJOZz5+RI0fiBz/4gdlHc3MzlixZgjFjxuCII45w7efnP/95q8qRl156CalUCmeddZZrYfGioiJccMEFrcby0EMPIRgMYtasWa5xbNiwAUceeSSqqqrw5ptvdmiuDjzwQDiOY1pkvfLKK8jLy8NVV12FvLw8vPLKKwCAFStWwHGcjC2h2sPee++NffbZx/VaZ6+5zTnnnONqCxUOh7H33nu79t8V94nksssuc/09YcIETJ06FStWrEB5eXmnzueXv/ylq4ps+PDhOO644/DJJ5/gX//6V7v2xWqNZcuWobKyss3tTz75ZBQXF5u//X4/DjzwQHz77beorq4G0L3/X9piyJAhePnllz1/xo4da7bbc8898Y9//AOnnHIKGhsb8eCDD+Lcc8/FrrvuivHjx7dqQcdz9drvHnvskfP4Dj30UJSVlWHQoEGYMGECnn32WZx55pmt1ozhtWB1USb4fi7XTlEURVEURelfaFszRVEURVEUBXPnzkVVVRVefvnlrG2Xdthhh1avsa3Rxo0bO3Ts1atXAwDOOussnHXWWZ7bcN2UtvZx7bXX4tprr/XchmP//vvvkUwmMWbMmFbbBAIBjBw5EhUVFea1zz//HAAwevToVtvvtNNOnmOpqanB0KFDM463rfPJxAEHHGBamM2ePRvLly/H7rvvju233x7jx4/H8uXLceqpp2L58uXIy8vDpEmTOnQcSXdc81yPIfffFfcJSSQSGDJkSKvXx4wZg5dffhmff/45dt9995z25YXXvcXXPv30U+yyyy4572v//ffHySefjPvuuw+PPfYYdt99d0ycOBHHHHMM9t5771bbt3W9IpFIt/5/aYtgMJhza7cf/ehHWLhwIQDgm2++wRtvvIEHH3wQy5Ytw/Tp0/Hhhx+Line truncated
"text/plain": [
"<Figure size 1650x506 with 6 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, axes = plt.subplots(1, 3, figsize=(15, 4.6))\n",
"extent = [0, 990 * BIN_UM, 0, 990 * BIN_UM]\n",
"for ax, name in zip(axes, ('eta', 'cluster <=3ph', 'GridBased flat')):\n",
" image = variants[name][0]\n",
" im = ax.imshow(image, extent=extent, vmax=np.percentile(image, 99.5))\n",
" ax.add_patch(plt.Rectangle((ROI[0]*BIN_UM, ROI[2]*BIN_UM),\n",
" (ROI[1]-ROI[0])*BIN_UM, (ROI[3]-ROI[2])*BIN_UM,\n",
" edgecolor='red', facecolor='none', lw=1.4))\n",
" ax.set_title(name); ax.set_xlabel('x [um]'); ax.set_ylabel('y [um]')\n",
" plt.colorbar(im, ax=ax, fraction=0.046)\n",
"fig.suptitle('knife edge, with the published ESF ROI')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "8b8c560d",
"metadata": {},
"source": [
"## 2. Edge spread function\n",
"\n",
"`generate_presampled_esf` fits an erf to every row of the ROI, fits a straight\n",
"line through the edge positions, and re-samples the ROI onto a common axis;\n",
"`get_odr_fitting` then fits the erf with x and y errors inside |x - p1| <= 4 sigma\n",
"(the fit of `Plotting/fig_knife_edge.py --fit odr`)."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "ae7f236f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:52.220504Z",
"iopub.status.busy": "2026-09-28T08:22:52.220382Z",
"iopub.status.idle": "2026-09-28T08:22:53.851025Z",
"shell.execute_reply": "2026-09-28T08:22:53.850763Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Final Edge Midpoint Fit: slope = -0.0866, intercept = 433.2062, chi2/ndf = 0.0458\n",
"Final Edge Midpoint Fit: slope = -0.0859, intercept = 432.9570, chi2/ndf = 0.0451\n",
"Final Edge Midpoint Fit: slope = -0.0858, intercept = 433.0080, chi2/ndf = 0.0419\n",
"Final Edge Midpoint Fit: slope = -0.0859, intercept = 433.0672, chi2/ndf = 0.0473\n",
"Final Edge Midpoint Fit: slope = -0.0860, intercept = 433.1036, chi2/ndf = 0.0451\n",
"variant sigma [um] chi2/ndf MTF50 MTF50 LSF MTF(200) LSF rows\n",
"eta 1.536 +- 0.019 1.06 122.0 127.4 0.170 100%\n",
"cluster 1ph 1.355 +- 0.016 0.69 138.3 140.7 0.225 100%\n",
"cluster <=2ph 1.380 +- 0.016 0.81 135.8 138.1 0.221 100%\n",
"cluster <=3ph 1.381 +- 0.017 0.85 135.7 136.8 0.229 100%\n",
"GridBased flat 1.360 +- 0.019 1.10 137.8 140.3 0.252 100%\n"
]
}
],
"source": [
"def analyse(edge_image, flat_image, label):\n",
" esf = generate_presampled_esf(edge_image, flat_image, ROI,\n",
" pixel_size_um=BIN_UM, bin_width_um=BIN_WIDTH_UM,\n",
" chi2_threshold=15.0, residual_tolerance_px=1.0)\n",
" x, z = esf['numpy']['x'], esf['numpy']['z']\n",
" odr = get_odr_fitting(x, z, esf['numpy']['x_err'], esf['numpy']['z_err'], n_sigma=4.0)\n",
" sigma, sigma_err = odr['sigma_um'], odr['sigma_err_um'] # sigma of the Gaussian LSF\n",
" try:\n",
" mtf50_lsf, mtf200_lsf = mtf_from_lsf(dict(x=x, z=z, pars=odr['beta']))\n",
" except Exception:\n",
" mtf50_lsf = mtf200_lsf = np.nan\n",
" n_fit = int(odr['mask'].sum())\n",
" ndf = max(n_fit - len(odr['beta']), 1)\n",
" x_fit = x[odr['mask']]\n",
" return dict(label=label, x=x, z=z, sigma=sigma, sigma_err=sigma_err, sigma_gauss=sigma,\n",
" chi2_ndf=odr['chi2_ndf'], chi2=odr['chi2_ndf'] * ndf, ndf=ndf,\n",
" mtf50=1000 * np.sqrt(np.log(2) / 2) / (np.pi * sigma),\n",
" mtf10=1000 * np.sqrt(np.log(10) / 2) / (np.pi * sigma),\n",
" mtf50_lsf=mtf50_lsf, mtf200_lsf=mtf200_lsf,\n",
" rows_kept=esf['valid_lines_ratio'],\n",
" edge_slope=esf['edge_slope'], edge_centre=odr['beta'][1],\n",
" fit_points=n_fit, fit_range=(x_fit.min(), x_fit.max()),\n",
" photons=float(edge_image.sum()), odr=odr)\n",
"\n",
"\n",
"results = {name: analyse(edge, flat, name) for name, (edge, flat) in variants.items()}\n",
"\n",
"print(f\"{'variant':16s} {'sigma [um]':>16} {'chi2/ndf':>9} {'MTF50':>7} {'MTF50 LSF':>9} \"\n",
" f\"{'MTF(200) LSF':>12} {'rows':>6}\")\n",
"for name, r in results.items():\n",
" print(f\"{name:16s} {r['sigma']:7.3f} +- {r['sigma_err']:.3f} {r['chi2_ndf']:9.2f} {r['mtf50']:7.1f} \"\n",
" f\"{r['mtf50_lsf']:9.1f} {r['mtf200_lsf']:12.3f} {r['rows_kept']*100:5.0f}%\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "bc41a64f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:53.852183Z",
"iopub.status.busy": "2026-09-28T08:22:53.852031Z",
"iopub.status.idle": "2026-09-28T08:22:54.250652Z",
"shell.execute_reply": "2026-09-28T08:22:54.250431Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1375x528 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots(1, 2, figsize=(12.5, 4.8))\n",
"colours = {'eta': '#a0451c', 'cluster 1ph': '#6b6b6b', 'cluster <=2ph': '#7b5aa6',\n",
" 'cluster <=3ph': '#4b2a6b', 'GridBased raw': '#4cbcac', 'GridBased flat': '#10695f'}\n",
"frequency = np.linspace(0, 400, 2000)\n",
"for name, r in results.items():\n",
" ax[0].plot(r['x'], r['z'], lw=1.1, color=colours[name],\n",
" label=f\"{name}: $\\\\sigma$ = {r['sigma']:.3f} $\\\\pm$ {r['sigma_err']:.3f} $\\\\mu$m\")\n",
" sigma_mm = r['sigma_gauss'] / 1000.0\n",
" ax[1].plot(frequency, np.exp(-2 * np.pi**2 * sigma_mm**2 * frequency**2),\n",
" lw=1.6, color=colours[name], label=f\"{name} ({r['mtf50']:.0f} lp/mm)\")\n",
"ax[0].set_xlim(-9, 9); ax[0].set_ylim(-0.05, 1.35)\n",
"ax[0].set_xlabel(r'position across the edge [$\\mu$m]')\n",
"ax[0].set_ylabel('relative intensity')\n",
"ax[0].legend(fontsize=8); ax[0].grid(alpha=.3); ax[0].set_title('oversampled ESF')\n",
"ax[1].axvline(200, color='k', ls=':', lw=.8, label='2.5 um Nyquist')\n",
"ax[1].axhline(0.5, color='grey', lw=.5)\n",
"ax[1].set_xlim(0, 400); ax[1].set_ylim(0, 1.05)\n",
"ax[1].set_xlabel('spatial frequency [lp/mm]'); ax[1].set_ylabel('MTF')\n",
"ax[1].legend(fontsize=8); ax[1].grid(alpha=.3); ax[1].set_title('MTF from the fitted ESF')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "61548cb0",
"metadata": {},
"source": [
"## 3. Where the GridBased sub-pixel phase stands\n",
"\n",
"Under the uniform illumination of the flat field the reconstructed positions\n",
"should fill the pixel evenly; the Rosenblatt remap in\n",
"`05_infer_measurement.py` makes them do so."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "79ecd95a",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:54.251748Z",
"iopub.status.busy": "2026-09-28T08:22:54.251666Z",
"iopub.status.idle": "2026-09-28T08:22:54.735190Z",
"shell.execute_reply": "2026-09-28T08:22:54.734959Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1650x462 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"before = np.load(GRID / FLAT_RUN / 'subpixel_phase_before.npy')\n",
"after = np.load(GRID / FLAT_RUN / 'subpixel_phase_after.npy')\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(15, 4.2))\n",
"for ax, histogram, title in ((axes[0], before, 'before flattening'),\n",
" (axes[1], after, 'after flattening')):\n",
" im = ax.imshow(histogram.T / histogram.mean(), origin='lower', extent=[0, 1, 0, 1],\n",
" vmin=0.9, vmax=1.1, cmap='viridis')\n",
" ax.set_title(f'flat field, {title}', fontsize=10)\n",
" ax.set_xlabel('sub-pixel x'); ax.set_ylabel('sub-pixel y')\n",
" plt.colorbar(im, ax=ax, fraction=0.046)\n",
"for histogram, title in ((before, 'before'), (after, 'after')):\n",
" profile = histogram.sum(axis=1)\n",
" axes[2].plot(np.linspace(0, 1, len(profile)), profile / profile.mean(), lw=1, label=title)\n",
"axes[2].axhline(1, color='k', lw=.6); axes[2].set_xlabel('sub-pixel x')\n",
"axes[2].set_ylabel('relative density'); axes[2].legend(); axes[2].grid(alpha=.3)\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "5dc0762d",
"metadata": {},
"source": [
"## 4. Relative DQE"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "5e11e1e3",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:54.736713Z",
"iopub.status.busy": "2026-09-28T08:22:54.736582Z",
"iopub.status.idle": "2026-09-28T08:22:54.840950Z",
"shell.execute_reply": "2026-09-28T08:22:54.840626Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"variant QE rel MTF(200) DQE rel (relative to cluster <=3ph)\n",
"eta 0.855 0.170 0.025\n",
"cluster 1ph 0.855 0.225 0.043\n",
"cluster <=2ph 0.981 0.221 0.048\n",
"cluster <=3ph 1.000 0.229 0.052\n",
"GridBased flat 1.014 0.252 0.064\n",
"\n",
"highest relative DQE at 200 lp/mm: GridBased flat (1.23x the cluster <=3ph chain, 2.60x eta)\n"
]
}
],
"source": [
"# DQE(f) is proportional to QE * MTF(f)^2. Every chain sees the same beam, so\n",
"# the ratio of detected photons is the ratio of QE and the relative DQE is well\n",
"# defined even without the absolute incident flux.\n",
"def mtf_at(frequency_lpmm, sigma_gauss_um):\n",
" sigma_mm = sigma_gauss_um / 1000.0\n",
" return float(np.exp(-2 * np.pi**2 * sigma_mm**2 * frequency_lpmm**2))\n",
"\n",
"FREQUENCY = 200.0 # lp/mm, the Nyquist of the 2.5 um bins\n",
"reference_photons = results['cluster <=3ph']['photons']\n",
"print(f\"{'variant':16s} {'QE rel':>8} {'MTF(200)':>9} {'DQE rel':>9} \"\n",
" f\"(relative to cluster <=3ph)\")\n",
"relative_dqe = {}\n",
"for name, r in results.items():\n",
" qe = r['photons'] / reference_photons\n",
" mtf200 = r['mtf200_lsf'] # measured from the LSF, as in the poly notebook\n",
" relative_dqe[name] = qe * mtf200**2\n",
" print(f'{name:16s} {qe:8.3f} {mtf200:9.3f} {relative_dqe[name]:9.3f}')\n",
"best = max(relative_dqe, key=relative_dqe.get)\n",
"print(f\"\\nhighest relative DQE at {FREQUENCY:.0f} lp/mm: {best} \"\n",
" f\"({relative_dqe[best]/relative_dqe['cluster <=3ph']:.2f}x the cluster <=3ph chain, \"\n",
" f\"{relative_dqe[best]/relative_dqe['eta']:.2f}x eta)\")"
]
},
{
"cell_type": "markdown",
"id": "a0cbe193",
"metadata": {},
"source": [
"## 5. Summary"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "72edb58e",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:54.842114Z",
"iopub.status.busy": "2026-09-28T08:22:54.842030Z",
"iopub.status.idle": "2026-09-28T08:22:54.948033Z",
"shell.execute_reply": "2026-09-28T08:22:54.947608Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"eta\": {\n",
" \"sigma_um\": 1.5357788997826887,\n",
" \"sigma_err_um\": 0.018934140987143105,\n",
" \"mtf50_lsf_lpmm\": 127.41391534878392,\n",
" \"mtf200_lsf\": 0.16986274278517122,\n",
" \"chi2_ndf\": 1.0632259342404946,\n",
" \"mtf50_lpmm\": 122.01666864663473,\n",
" \"mtf10_lpmm\": 222.38949945174375,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 160989360.0\n",
" },\n",
" \"cluster 1ph\": {\n",
" \"sigma_um\": 1.3553236105888296,\n",
" \"sigma_err_um\": 0.015582837583920595,\n",
" \"mtf50_lsf_lpmm\": 140.72272920365313,\n",
" \"mtf200_lsf\": 0.22534741293893693,\n",
" \"chi2_ndf\": 0.6948283846340041,\n",
" \"mtf50_lpmm\": 138.26264344931204,\n",
" \"mtf10_lpmm\": 251.999668656873,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 160989340.0\n",
" },\n",
" \"cluster <=2ph\": {\n",
" \"sigma_um\": 1.380244936199657,\n",
" \"sigma_err_um\": 0.016190473494226774,\n",
" \"mtf50_lsf_lpmm\": 138.11611497098744,\n",
" \"mtf200_lsf\": 0.2208935552975036,\n",
" \"chi2_ndf\": 0.8064499904260305,\n",
" \"mtf50_lpmm\": 135.76621091995148,\n",
" \"mtf10_lpmm\": 247.44963146296,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 184639952.0\n",
" },\n",
" \"cluster <=3ph\": {\n",
" \"sigma_um\": 1.381207746488057,\n",
" \"sigma_err_um\": 0.016615366693369205,\n",
" \"mtf50_lsf_lpmm\": 136.79352035267127,\n",
" \"mtf200_lsf\": 0.22868136720959276,\n",
" \"chi2_ndf\": 0.8533928535430366,\n",
" \"mtf50_lpmm\": 135.6715712069733,\n",
" \"mtf10_lpmm\": 247.2771396335164,\n",
" \"rows_kept\": 1.0,\n",
" \"photons\": 188275709.0\n",
" },\n",
" \"GridBased flat\": {\n",
" \"sigma_um\": 1.3596210744597628,\n",
" \"sigma_err_um\": 0.018755935321852032,\n",
" \"mtf50_lsf_lpmm\": 140.32690640620402,\n",
" \"mtf200_lsf\": 0.2518136955393898,\n",
" \"chi2_ndf\": 1.104983605454407,\n",
" \"mtf50_lpmm\": 137.8256255727252,\n",
" \"mtf10_lpmm\": 251.20315300123687,\n",
" \"rows_kept\": 0.9956521739130435,\n",
" \"photons\": 190825531.0\n",
" }\n",
"}\n",
"\n",
"written to /mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased/Edge_3filters_pos0_12keV/knife_edge_comparison.json\n"
]
}
],
"source": [
"summary = {\n",
" 'measurement': 'Edge/Flat_3filters_pos0_12keV',\n",
" 'roi_superpixel': list(ROI),\n",
" 'bin_um': BIN_UM,\n",
" 'bin_width_um': BIN_WIDTH_UM,\n",
" 'convention': 'sigma = ODR p2 (Gaussian LSF); MTF50 = 1000 sqrt(ln 2 / 2) / (pi sigma); '\n",
" 'ESF from DeepLearning/src, bin width 0.3 um',\n",
" 'grid_model_epoch': grid_summary['epoch'],\n",
" 'grid_frames': grid_summary['n_frames'],\n",
" 'occupancy_percent': occupancy * 100, # flat field, ESF ROI\n",
" 'grid_subpixel_before': grid_summary['subpixel_nonuniformity_before'],\n",
" 'grid_subpixel_after': grid_summary['subpixel_nonuniformity_after'],\n",
" 'variants': {name: dict(sigma_um=float(r['sigma']), sigma_err_um=float(r['sigma_err']),\n",
" mtf50_lsf_lpmm=float(r['mtf50_lsf']),\n",
" mtf200_lsf=float(r['mtf200_lsf']),\n",
" chi2_ndf=float(r['chi2_ndf']),\n",
" mtf50_lpmm=float(r['mtf50']), mtf10_lpmm=float(r['mtf10']),\n",
" rows_kept=float(r['rows_kept']),\n",
" photons=float(r['photons']))\n",
" for name, r in results.items()},\n",
" 'relative_dqe_200lpmm': relative_dqe,\n",
"}\n",
"print(json.dumps(summary['variants'], indent=1))\n",
"out = GRID / EDGE_RUN / 'knife_edge_comparison.json'\n",
"json.dump(summary, open(out, 'w'), indent=1)\n",
"print(f'\\nwritten to {out}')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch_nightly",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,897 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "b3ef09b8",
"metadata": {},
"source": [
"# Knife edge, 12 keV, MOENCH040 — polychromatic 2–20 keV GridBased model\n",
"\n",
"The polychromatic GridBased network retrained for the 650 µm **MOENCH040** on\n",
"flat **2–20 keV** frames (`[32, 64, 96, 96]`, α(E,t)/β(E,t) charge-cloud table of\n",
"MOENCH040 @150 V), run over the same 2603 MaxIV knife-edge scan as the 12 keV\n",
"monochromatic model: four flux points (0–3 filters), edge and flat field each.\n",
"Reconstruction settings are those of the 12 keV runs (same ROI readout,\n",
"pedestal, column common mode, self-flattening); the only difference is the model\n",
"(and FP16 with an FP32 head, as for every poly model).\n",
"\n",
"**Edge width.** The ESF and its fit are the ones of `Plotting/fig_knife_edge.py\n",
"--fit odr`: `generate_presampled_esf()` and `get_odr_fitting()` from\n",
"`DeepLearning/src/funcs_postprocess.py`, ROI `(330, 465, 260, 490)` on the\n",
"990 × 990 super-frame, symmetric ±4σ window, **ESF bin width 0.3 µm**. The ODR\n",
"model is `p0/2 · (1 + erf((x − p1)/(√2 σ))) + p3`, so the fit gives **σ**, the\n",
"width of the Gaussian LSF, which is what the tables quote, with\n",
"**MTF50 = 1000 √(ln2 / 2) / (π σ) lp/mm** (the Gaussian's), plus MTF50 and\n",
"MTF(200 lp/mm) measured from the LSF by FFT with the first-crossing and\n",
"central-difference corrections of `Plotting/knife_edge_numbers.py`. Only the\n",
"flattened GridBased reconstructions are analysed.\n",
"\n",
"**Occupancy** is that of the flat field inside the ESF ROI: the reconstructed\n",
"photons per 25 µm pixel per frame over `ROI // 10`, the pixels over which\n",
"`DeepLearning/Notebooks/KnifeEdge_3filters_pos0.ipynb` sums the deposited energy.\n",
"The flux points are labelled with the 12 keV model's value, as in the\n",
"single-flux notebooks.\n",
"\n",
"The 12 keV model's images are re-fitted here with the same code, and so are the\n",
"2026-09-17 GridBased knife-edge notebooks since 2026-09-28: the numbers agree\n",
"digit for digit with `KnifeEdge_{0,1,2,3}filters*.ipynb`."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "4d16f0f1",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:35.617146Z",
"iopub.status.busy": "2026-09-28T08:22:35.616976Z",
"iopub.status.idle": "2026-09-28T08:22:37.557915Z",
"shell.execute_reply": "2026-09-28T08:22:37.557583Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Welcome to JupyROOT 6.28/10\n",
"poly model : 260927_poly2to20keV_Moench040_c96_00 tag 'poly2to20'\n",
"12 keV model: 260831_12keV_Moench040_01\n"
]
}
],
"source": [
"import json\n",
"import os\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"from scipy.optimize import curve_fit\n",
"\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/DeepLearning/src') # the code Plotting/ uses\n",
"sys.path.insert(0, '/home/xie_x1/MLXID/Plotting')\n",
"from ROOT import gROOT\n",
"gROOT.SetBatch(True)\n",
"from funcs_postprocess import generate_presampled_esf, get_odr_fitting\n",
"from knife_edge_numbers import mtf as mtf_from_lsf # first crossing, sinc-corrected\n",
"\n",
"GRID = Path('/mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased')\n",
"PUBLISHED = Path('/home/xie_x1/MLXID/DeepLearning/InferenceResults')\n",
"ETA = Path('/home/xie_x1/MLXID/EtaInterpolation/Results')\n",
"GRIDBASED = Path('/home/xie_x1/MLXID/GridBased')\n",
"OVERNIGHT = GRIDBASED / 'Results/overnight_poly2to20keV_Moench040'\n",
"\n",
"POLY_TAG = os.environ.get('GRID_POLY_TAG', 'poly2to20')\n",
"POLY_EXPERIMENT = GRIDBASED / (os.environ.get('GRID_POLY_EXPERIMENT')\n",
" or (OVERNIGHT / 'state/experiment').read_text().strip())\n",
"MONO_EXPERIMENT = GRIDBASED / 'Results/260831_12keV_Moench040_01'\n",
"\n",
"BIN_UM = 2.5 # super-pixel = 25 um / 10\n",
"ROI = (330, 465, 260, 490) # x1, x2, y1, y2 on the 990 x 990 frame, as published\n",
"CROP = 10 # 1010 -> 990\n",
"BIN_WIDTH_UM = 0.3 # presampled ESF bin\n",
"N_SIGMA = 4.0 # symmetric ODR window\n",
"FLUX = [('3filters_pos0', '3 filters'), ('2filters', '2 filters'),\n",
" ('1filters', '1 filter'), ('0filters', '0 filters')]\n",
"MONO, POLY = 'GridBased 12 keV', 'GridBased 2-20 keV'\n",
"\n",
"plt.rcParams.update({'figure.dpi': 110, 'image.cmap': 'gray', 'image.origin': 'lower',\n",
" 'image.interpolation': 'nearest'})\n",
"print('poly model :', POLY_EXPERIMENT.name, ' tag', repr(POLY_TAG))\n",
"print('12 keV model:', MONO_EXPERIMENT.name)"
]
},
{
"cell_type": "markdown",
"id": "3b2ee3ef",
"metadata": {},
"source": [
"## 1. The reconstructions"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "aeb7d57f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:37.559180Z",
"iopub.status.busy": "2026-09-28T08:22:37.559038Z",
"iopub.status.idle": "2026-09-28T08:22:40.056391Z",
"shell.execute_reply": "2026-09-28T08:22:40.056105Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"missing: nothing\n",
"\n",
"flux point occupancy model own occ. frames edge photons flat photons frame/s sub-pixel rms\n",
"3 filters 0.75% GridBased 2-20 keV 0.76% 14,000,000 1.9590e+08 3.3831e+08 7603 23.4 -> 3.1 %\n",
"3 filters 0.75% GridBased 12 keV 0.75% 14,000,000 1.9083e+08 3.3060e+08 7647 22.6 -> 4.3 %\n",
"2 filters 1.81% GridBased 2-20 keV 1.85% 7,000,000 2.3612e+08 4.1102e+08 7664 22.2 -> 2.8 %\n",
"2 filters 1.81% GridBased 12 keV 1.81% 7,000,000 2.3053e+08 4.0162e+08 7123 21.6 -> 3.9 %\n",
"1 filter 4.45% GridBased 2-20 keV 4.58% 7,000,000 5.8278e+08 1.0097e+09 7682 20.0 -> 2.2 %\n",
"1 filter 4.45% GridBased 12 keV 4.45% 7,000,000 5.6697e+08 9.8283e+08 6537 19.8 -> 3.0 %\n",
"0 filters 10.77% GridBased 2-20 keV 11.15% 3,500,000 7.0822e+08 1.2280e+09 7185 18.6 -> 1.3 %\n",
"0 filters 10.77% GridBased 12 keV 10.77% 3,500,000 6.8684e+08 1.1910e+09 6176 18.3 -> 1.6 %\n"
]
}
],
"source": [
"def crop(image):\n",
" return image[CROP:-CROP, CROP:-CROP] if image.shape[0] == 1010 else image\n",
"\n",
"\n",
"def run_dir(run, tag):\n",
" return GRID / (run + (f'_{tag}' if tag else ''))\n",
"\n",
"\n",
"def summary_of(run, tag):\n",
" path = run_dir(run, tag) / 'summary.json'\n",
" return json.load(open(path)) if path.exists() else None\n",
"\n",
"\n",
"def grid_pair(filters, tag, flattened):\n",
" name = 'binned_image_flattened.npy' if flattened else 'binned_image.npy'\n",
" edge, flat = run_dir(f'Edge_{filters}_12keV', tag), run_dir(f'Flat_{filters}_12keV', tag)\n",
" if not ((edge / name).exists() and (flat / name).exists()):\n",
" return None\n",
" return crop(np.load(edge / name).astype(float)), crop(np.load(flat / name).astype(float))\n",
"\n",
"\n",
"ETA_FILES = {'3filters_pos0': ('2603MaxIV_Edge3Filters_pos0_12keV_fromClusters.npy',\n",
" '2603MaxIV_FlatField3Filters_pos0_12keV_fromClusters.npy'),\n",
" '2filters': ('2603MaxIV_Edge2Filters_12keV_fromClusters.npy',\n",
" '2603MaxIV_Flat2Filters_12keV_fromClusters.npy'),\n",
" '1filters': ('2603MaxIV_Edge1Filters_12keV_fromClusters.npy',\n",
" '2603MaxIV_Flat1Filters_12keV_fromClusters.npy')}\n",
"CNN = {1: '1ph_260512_12keV_v260511_06', 2: '2ph_260610_12keV_v260610_01',\n",
" 3: '3ph_260611_12keV_v260611_00'}\n",
"CNN_DIRS = {'3filters_pos0': ('KnifeEdge_3filters_pos0', 'FlatField_3filters_pos0'),\n",
" '2filters': ('KnifeEdge_2filters', 'FlatField_2filters')}\n",
"\n",
"\n",
"def cnn_pair(filters, up_to):\n",
" knife_dir, flat_dir = CNN_DIRS[filters]\n",
" edge = flat = 0\n",
" for n in range(1, up_to + 1):\n",
" name = f'{n}Photon_ML_superFrame_etaInterpolated.npy'\n",
" edge = edge + crop(np.load(PUBLISHED / knife_dir / CNN[n] / 'augX1' / name).astype(float))\n",
" flat = flat + crop(np.load(PUBLISHED / flat_dir / CNN[n] / 'augX1' / name).astype(float))\n",
" return edge, flat\n",
"\n",
"\n",
"variants, missing = {}, []\n",
"for filters, label in FLUX:\n",
" v = {}\n",
" for name, tag, flattened in ((f'{POLY}, flattened', POLY_TAG, True),\n",
" (f'{MONO}, flattened', '', True)):\n",
" pair = grid_pair(filters, tag, flattened)\n",
" if pair is None:\n",
" missing.append(f'{label}: {name}')\n",
" else:\n",
" v[name] = pair\n",
" if filters in ETA_FILES:\n",
" v['eta'] = tuple(np.load(ETA / f).astype(float) for f in ETA_FILES[filters])\n",
" if filters in CNN_DIRS:\n",
" for n in (1, 2, 3):\n",
" v[f'cluster CNN <={n}ph'] = cnn_pair(filters, n)\n",
" variants[filters] = v\n",
"\n",
"def flat_occupancy(filters, tag):\n",
" # reconstructed photons per 25 um pixel per frame in the flat field, inside the\n",
" # ESF ROI: ROI // 10 in pixels of the 99 x 99 frame, both ends included as in\n",
" # DeepLearning/Notebooks; +1 because the 101 x 101 frame is cropped by 1 px\n",
" run = run_dir(f'Flat_{filters}_12keV', tag)\n",
" if not (run / 'native_image.npy').exists():\n",
" return np.nan\n",
" native = np.load(run / 'native_image.npy') / json.load(open(run / 'summary.json'))['n_frames']\n",
" x0, x1, y0, y1 = (v // 10 for v in ROI)\n",
" return float(native[y0 + 1:y1 + 2, x0 + 1:x1 + 2].mean())\n",
"\n",
"\n",
"# the flux point is labelled with the 12 keV model's value, the same number as in\n",
"# KnifeEdge_{0,1,2,3}filters*.ipynb; the 2-20 keV model counts 2-3 % more photons\n",
"occupancy = {f: flat_occupancy(f, '') for f, _ in FLUX}\n",
"print('missing:', missing or 'nothing')\n",
"print(f\"\\n{'flux point':10s} {'occupancy':>9s} {'model':20s} {'own occ.':>8s} {'frames':>11s} {'edge photons':>13s} \"\n",
" f\"{'flat photons':>13s} {'frame/s':>8s} {'sub-pixel rms':>14s}\")\n",
"for filters, label in FLUX:\n",
" for model, tag in ((POLY, POLY_TAG), (MONO, '')):\n",
" e, f = summary_of(f'Edge_{filters}_12keV', tag), summary_of(f'Flat_{filters}_12keV', tag)\n",
" if e is None or f is None:\n",
" continue\n",
" print(f\"{label:10s} {occupancy[filters]*100:8.2f}% {model:20s} {flat_occupancy(filters, tag)*100:7.2f}% \"\n",
" f\"{e['n_frames']:11,d} \"\n",
" f\"{e['n_photons']:13.4e} {f['n_photons']:13.4e} {e['frames_per_second']:8.0f} \"\n",
" f\"{f['subpixel_nonuniformity_before']*100:5.1f} -> {f['subpixel_nonuniformity_after']*100:4.1f} %\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "c5b40483",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:40.057689Z",
"iopub.status.busy": "2026-09-28T08:22:40.057511Z",
"iopub.status.idle": "2026-09-28T08:22:40.832086Z",
"shell.execute_reply": "2026-09-28T08:22:40.831793Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABwQAAAN7CAYAAABLVi50AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOy9d5hV1dn+f5/pM+dMoWMFK6CiEhUECwQVgw0sEbGB8bXEXmNLRP1pfC2xt2gU29eaCFGDGEVRg71gsKBRsQZUFKb32b8/eO917r1mnzNnYICZ4flc11zM7LPL2msf1r2ftlYsCIIAhmEYhmEYhmEYhmEYhmEYhmEYhmF0S7LWdgMMwzAMwzAMwzAMwzAMwzAMwzAMw1h9WEDQMAzDMAzDMAzDMAzDMAzDMAzDMLoxFhA0DMMwDMMwDMMwDMMwDMMwDMMwjG6MBQQNwzAMwzAMwzAMwzAMwzAMwzAMoxtjAUHDMAzDMAzDMAzDMAzDMAzDMAzD6MZYQNAwDMMwDMMwDMMwDMMwDMMwDMMwujEWEDQMwzAMwzAMwzAMwzAMwzAMwzCMbowFBA3DMAzDMAzDMAzDMAzDMAzDMAyjG2MBQcMwDMMwDMMwDMMwDMMwDMMwDMPoxlhA0DAMwzAMwzAMwzAMwzAMwzAMwzC6MRYQNAzDMAzDMNZ57r33XsRiMcydO7dDzzt37lzEYjHce++9oe01NTU47bTTsPHGGyM7OxsDBw7s0Ot2BGPGjOmU7TJSk+r7lopLLrkEsVgMX3755Wpt15qkvd/bNdEHAwcOxJgxY1bb+Q3DMAzDMAzDMDLBAoKGYRiGYRiGsYa56qqrcPPNN2PSpEm49957ccMNN6ztJnUp/vOf/+CSSy7BLrvsgv79+yMej2OrrbbCaaedhsWLF2d8HgaDon7OOeec1XgHxppk5syZuOSSS1brNS655BLMnDlztV4jU7788suU32v++MkP8+bNw4EHHohNN90UBQUF6NOnD7bbbjuccMIJeO+990L7pjvv//zP/7TZvjFjxoSOycnJQb9+/TBx4kS8+uqrKY9rbGzEnXfeidGjR6NXr17Iz8/HRhtthMmTJ+ONN95Iea2cnJy2O80wDMMwDMMw1gHszdgwDMMwDMMwVhO77747amtrkZubG9r+3HPPYejQobjmmmvWUsu6NnfffTduuukm7Lvvvvj1r3+NoqIivP7667jtttvw//7f/8O8efMwePDgjM93/fXXo3fv3qFtW221VUc3u9Px+9//Hueffz7y8/PXdlM6jH/+858IgiC0bebMmbjvvvtWa1Dw0ksvxZQpUzBx4sTVdo32MmbMGBx77LGRnw0ZMsT9/uc//xknnngi+vfvj6OPPhqbb745li9fjoULF+KJJ57AFltsgWHDhoWO33rrrXH++ee3Ou/mm2+eUduysrJw3333AQDq6+vxwQcf4K677sKsWbMwZ84c7LbbbqH9ly5div333x+vv/46dt11V1xwwQXo0aMHPvvsM9x///145JFHcNlll+EPf/hDRtc3DMMwDMMwjHURCwgahmEYhmEYxmoiKysLBQUFrbYvWbIEG2+88VpoUffgkEMOwXnnnYcePXq4bccffzx23nlnnHDCCbj44ovx2GOPZXy+iRMnrpPTo+bk5HS76qm8vLy13YROw2abbYYjjzwy7T5NTU244IILkEgk8NZbb2HDDTcMfd7Y2Iiff/651XH9+/dv89zpiMVirY4fPXo0DjzwQFx99dWtAoKTJk3C66+/jmuvvRZnn3126LMLL7wQ++67Ly6++GIMHDgQRx111Eq3yzAMwzAMwzC6MzZlqGEYhmEYhmGk4Pbbb0dOTg4OPfRQ1NfXA0iuB/bpp59iwoQJKC0tRSKRwL777ovPP/88dLy/phunqFy0aBFeeuklN2WeVi699957OOSQQ9C3b1/k5eVh0003xfnnn4+ampqM2/35559j6tSpWH/99ZGXl4cNN9wQJ510EpYuXdpq388++wwHHnggSktLUVxcjHHjxuH9999Pee4HHngA2267LfLz87Hhhhvid7/7HT7++ONW9wEAQRDgrrvuwvDhwxGPxxGPxzFq1KhVnlpxxx13DAUDyWGHHQYA+Pe//93uc1ZWVqKxsbHdx6Vbgy5qPTt+fz744APsvffeKCkpQa9evfA///M/qK6uRhAEuOaaa7D55psjPz8fW2+9Nf7xj3+0u1233347hgwZgoKCAgwcOBCXXXYZmpqa2mw7t3366ae4+OKLMWDAAOTn52PIkCF46KGHWl1n9uzZGDt2LPr27YuCggJsuOGG2GeffdJO/ZiO4447Djk5OVi+fLnb9t///tf9X9EpYZcvX47s7GycdNJJbpvf5wMHDnSVaDpNpb/OYkNDQ0b368P/4wBw3333ha7hk8mYwbZcffXV2HbbbVFYWIiSkhLsueeeePnll9tsT3tZunQpli1bhkGDBrUKBgJAbm4u+vXr1+HXjWLPPfcEsGJKYGXWrFl44YUXMGHChFbBQAAoLi7Go48+isLCQlxwwQUr9f/YMAzDMAzDMNYFulc6qGEYhmEYhmF0AEEQ4IILLsBVV12FM888E3/6059CDv7vvvsOu+++Ow444ABcddVV+M9//oObb74ZBxxwABYsWICsrOi8u4MOOgibb745zjzzTPTu3RsXXXQRAGDbbbcFsCK4MnHiRGy00UY49dRT0a9fP7z//vu47rrrMG/ePLz44ottVnTNnz8fY8aMQVFREX7zm99gwIAB+M9//oPbb78dc+bMwZtvvonS0lIAwDfffINRo0Zh+fLlOPHEEzFkyBC89tprGDNmDHr16tXq3LfeeitOOeUUDB48GJdeeilyc3Px8MMPt1qPjBxzzDG4//77MWHCBBxxxBEAgCeeeAIHHnggbr/9dpx44onpH0Q7+e677wAAffv2bddx2223HSoqKpCVlYVhw4bhd7/7HQ499NAObZvy3XffYezYsTjkkENw4IEH4rXXXsPdd9+N2tpa9OjRA//6179wwgknIDs7GzfeeCMOOuggfPrppxgwYEBG57/lllvw7bff4sQTT0TPnj0xY8YMTJs2DV988UWrQFgqpkyZglgshtNOOw1ZWVm47bbbcMQRR2DTTTfFzjvvDAB4+eWXsd9++2GrrbbCueeei169emHJkiWYN28e5s+fj1GjRrW7b/bcc0/85S9/wYsvvogDDzwQADBnzhz3/+/55593FWAvvvgiWlpasMcee6Q83w033IDrrrsOr7zyCh544AG33W9bJvcbxZAhQ/DAAw/gqKOOwm677Ybjjz8+cr9Mx4ympibss88+eOmllzB58mSceOKJqKmpwYMPPoixY8di5syZ2G+//TLoyRVTcUYlAcRiMff/u1+/fkgkEvjwww/x6quvZvzMGhsbI8/tT73bHj777DMAaDX2PP744wCA3/72tymPXW+99TBx4kQ8/PDDeO2117D77ruvdDsMwzAMwzAMo9sSGIZhGIZhGMY6zvTp0wMAwYsvvhjU1dUFhx12WJCVlRXceOONrfYdMGBAACB46KGHQtuvvPLKAEDw7LPPum0vvvhiACCYPn16q3OMHj06tK22tjbo379/MHz48KCuri702eOPPx4ACO69994272X77bcPNtlkk+Cnn34KbX/99deDrKys4JJLLnHbjjrqqABAMGPGjMh7GTBggNu2bNmyIB6PB5tuumlQUVHhttfV1QU77bRTACCYNm2a2z5z5swAQHDddde1auN+++0XlJSUhM7TERx00EGR/Z2K66+/Pvif//mfYPr06cGTTz4Z/OlPf3LP98orr8zoHNOmTQsABIsWLWr12ejRo0N9GATJ78/DDz8c2j5hwoQgFosF22+/fVBfX++2v/feewGA4IILLmizLfy+FRUVBV9++aXb3tzcHOy///4BgOCVV15J23ZuGz9+fNDc3Oy2f/3110Fubm4wefJkt+3MM88MAARLlixps22Z8sMPPwSxWCw46aST3LYpU6YE2223XbDtttsGRx99tNt+8sknB1lZWaHvelSfT5kyJUhl+rbnftMBIJgyZUrkZ+0ZM2644YYAQPDEE0+E9m1oaAiGDRsWbLLJJm22ZdGiRQGAlD/Z2dmh/a+//nr32XbbbReccsopwX333Rd8/fXXKe811U9tbW2b7Rs9enSQnZ0d/Pjjj8GPP/4YfPvtt8Hs2bODbbbZJgAQ3HHHHaH9d9hhhwBAqzHN59prrw0ABDfffHOraxmGYRiGYRiGEQQ2ZahhGIZhGIZh/B/Lli3DuHHjMHPmTDz++OM47bTTIvdbf/31MXny5NC2vfbaC8CKaQFXhueffx5LlizB1KlTUVlZiaVLl7qf0aNHIx6P49lnn017jg8++ADz58/HYYcdhpaWltA5NttsM2yxxRbuHC0tLZg5cya22morTJw4MXSeM844A4lEIrTtn//8J6qrq3HSSSehuLjYbc/Pz8eZZ57Zqi0PPPAACgsLMWnSpFA7li5digMPPBAVFRV47bXXVqqvovjjH/+IJ554AhMnTsSUKVMyOuaMM87AXXfdhalTp2L//ffHWWedhQULFmDTTTfFxRdfjG+//bbD2qesv/76bnpTMnr0aARBgJNOOim0Dt7222+PkpKSdn2vjjzyyFA1YVZWFs4//3wAwN/+9reMznHmmWeGKl032mgjDBo0KNSOsrIyACsquPzpSFeWPn36YOjQoZgzZ47bNmfOHOyxxx7YY489Wm3ffvvt0bNnz1W+bib3uypkOmY88MADGDhwIHbbbbfQ/5ny8nIccMABWLRoUcZtGj9+PJ577rlWP//85z9D+51xxhmYNWsW9t9/fyxatAi33HILpkyZggEDBuDggw/GTz/91Orcw4YNizx3pms4Njc3o0+fPujTpw823HBD/OpXv8J///tfXH/99TjhhBNC+5aXlwOAq2xOBT/n/oZhGIZhGIZLine truncated
"text/plain": [
"<Figure size 1848x902 with 8 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"shown = [(f, l) for f, l in FLUX if f'{POLY}, flattened' in variants[f]]\n",
"fig, axes = plt.subplots(2, max(len(shown), 1), figsize=(4.2 * max(len(shown), 1), 8.2), squeeze=False)\n",
"extent = [0, 990 * BIN_UM, 0, 990 * BIN_UM]\n",
"for column, (filters, label) in enumerate(shown):\n",
" for row, model in enumerate((MONO, POLY)):\n",
" ax = axes[row, column]\n",
" name = f'{model}, flattened'\n",
" if name not in variants[filters]:\n",
" ax.axis('off'); continue\n",
" image = variants[filters][name][0]\n",
" ax.imshow(image, extent=extent, vmax=np.percentile(image, 99.5))\n",
" ax.add_patch(plt.Rectangle((ROI[0] * BIN_UM, ROI[2] * BIN_UM), (ROI[1] - ROI[0]) * BIN_UM,\n",
" (ROI[3] - ROI[2]) * BIN_UM, edgecolor='red', facecolor='none', lw=1.2))\n",
" ax.set_title(f'{label} ({occupancy[filters]*100:.2f} %)\\n{name}', fontsize=9)\n",
" ax.set_xlabel('x [um]'); ax.set_ylabel('y [um]')\n",
"fig.suptitle('knife edge, 2.5 um bins, with the ESF ROI')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "19689795",
"metadata": {},
"source": [
"## 2. Edge spread function and ODR fit\n",
"\n",
"`generate_presampled_esf` fits an erf to every row of the ROI, fits a straight\n",
"line through the edge positions, and resamples the knife/flat ratio onto a\n",
"0.3 µm grid across the edge (bin content Σk/Σf). `get_odr_fitting` fits the ESF\n",
"with x and y errors inside |x − p1| ≤ 4σ, iterating the window."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "02f502b8",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:40.833998Z",
"iopub.status.busy": "2026-09-28T08:22:40.833910Z",
"iopub.status.idle": "2026-09-28T08:22:45.700834Z",
"shell.execute_reply": "2026-09-28T08:22:45.700450Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Final Edge Midpoint Fit: slope = -0.0871, intercept = 433.5999, chi2/ndf = 0.0389\n",
"Final Edge Midpoint Fit: slope = -0.0860, intercept = 433.1036, chi2/ndf = 0.0451\n",
"Final Edge Midpoint Fit: slope = -0.0866, intercept = 433.2062, chi2/ndf = 0.0458\n",
"Final Edge Midpoint Fit: slope = -0.0859, intercept = 432.9570, chi2/ndf = 0.0451\n",
"Final Edge Midpoint Fit: slope = -0.0858, intercept = 433.0080, chi2/ndf = 0.0419\n",
"Final Edge Midpoint Fit: slope = -0.0859, intercept = 433.0672, chi2/ndf = 0.0473\n",
"Final Edge Midpoint Fit: slope = -0.0855, intercept = 432.4900, chi2/ndf = 0.0377\n",
"Final Edge Midpoint Fit: slope = -0.0858, intercept = 432.5887, chi2/ndf = 0.0361\n",
"Final Edge Midpoint Fit: slope = -0.0858, intercept = 432.3990, chi2/ndf = 0.0751\n",
"Final Edge Midpoint Fit: slope = -0.0854, intercept = 432.2740, chi2/ndf = 0.0576\n",
"Final Edge Midpoint Fit: slope = -0.0855, intercept = 432.4402, chi2/ndf = 0.0479\n",
"Final Edge Midpoint Fit: slope = -0.0850, intercept = 432.2537, chi2/ndf = 0.0406\n",
"Final Edge Midpoint Fit: slope = -0.0853, intercept = 432.6611, chi2/ndf = 0.0386\n",
"Final Edge Midpoint Fit: slope = -0.0852, intercept = 432.6254, chi2/ndf = 0.0434\n",
"Final Edge Midpoint Fit: slope = -0.0859, intercept = 432.5449, chi2/ndf = 0.0615\n",
"Final Edge Midpoint Fit: slope = -0.0860, intercept = 433.2953, chi2/ndf = 0.0337\n",
"Final Edge Midpoint Fit: slope = -0.0858, intercept = 433.2022, chi2/ndf = 0.0302\n"
]
}
],
"source": [
"def analyse(edge, flat):\n",
" esf = generate_presampled_esf(edge, flat, ROI, pixel_size_um=BIN_UM,\n",
" bin_width_um=BIN_WIDTH_UM, chi2_threshold=15.0,\n",
" residual_tolerance_px=1.0)\n",
" n = esf['numpy']\n",
" fit = get_odr_fitting(n['x'], n['z'], n['x_err'], n['z_err'], n_sigma=N_SIGMA)\n",
" sigma, sigma_err = fit['sigma_um'], fit['sigma_err_um']\n",
" try:\n",
" mtf50_lsf, mtf200_lsf = mtf_from_lsf(dict(x=n['x'], z=n['z'], pars=fit['beta']))\n",
" except Exception:\n",
" mtf50_lsf = mtf200_lsf = np.nan\n",
" return dict(x=n['x'], z=n['z'], z_err=n['z_err'], fit=fit,\n",
" sigma=sigma, sigma_err=sigma_err,\n",
" chi2_ndf=fit['chi2_ndf'], n_fit=int(fit['mask'].sum()),\n",
" mtf50=1000 * np.sqrt(np.log(2) / 2) / (np.pi * sigma),\n",
" mtf200=float(np.exp(-2 * np.pi**2 * (sigma / 1000)**2 * 200.0**2)),\n",
" mtf50_lsf=mtf50_lsf, mtf200_lsf=mtf200_lsf,\n",
" rows_kept=esf['valid_lines_ratio'], photons=float(edge.sum()))\n",
"\n",
"\n",
"results = {}\n",
"for filters, label in FLUX:\n",
" results[filters] = {name: analyse(edge, flat) for name, (edge, flat) in variants[filters].items()}"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "022aa946",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:45.702053Z",
"iopub.status.busy": "2026-09-28T08:22:45.701965Z",
"iopub.status.idle": "2026-09-28T08:22:45.805285Z",
"shell.execute_reply": "2026-09-28T08:22:45.804879Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== 3 filters, occupancy 0.75 % (flat field, ESF ROI) ===\n",
" sigma [um] chi2/ndf MTF50 [lp/mm] MTF50 LSF MTF(200) LSF edge photons\n",
"GridBased 2-20 keV, flattened 1.420 +- 0.021 1.34 131.9 134.4 0.227 1.9590e+08\n",
"GridBased 12 keV, flattened 1.360 +- 0.019 1.10 137.8 140.3 0.252 1.9083e+08\n",
"eta 1.536 +- 0.019 1.06 122.0 127.4 0.170 1.6099e+08\n",
"cluster CNN <=1ph 1.355 +- 0.016 0.69 138.3 140.7 0.225 1.6099e+08\n",
"cluster CNN <=2ph 1.380 +- 0.016 0.81 135.8 138.1 0.221 1.8464e+08\n",
"cluster CNN <=3ph 1.381 +- 0.017 0.85 135.7 136.8 0.229 1.8828e+08\n",
"\n",
"=== 2 filters, occupancy 1.81 % (flat field, ESF ROI) ===\n",
" sigma [um] chi2/ndf MTF50 [lp/mm] MTF50 LSF MTF(200) LSF edge photons\n",
"GridBased 2-20 keV, flattened 1.391 +- 0.018 1.00 134.7 139.8 0.254 2.3612e+08\n",
"GridBased 12 keV, flattened 1.346 +- 0.019 1.15 139.2 143.8 0.259 2.3053e+08\n",
"eta 1.483 +- 0.024 1.61 126.4 131.7 0.187 1.5670e+08\n",
"cluster CNN <=1ph 1.352 +- 0.022 1.44 138.6 144.8 0.229 1.5670e+08\n",
"cluster CNN <=2ph 1.372 +- 0.022 1.50 136.5 142.7 0.223 2.0504e+08\n",
"cluster CNN <=3ph 1.386 +- 0.018 1.02 135.2 139.7 0.244 2.2076e+08\n",
"\n",
"=== 1 filter, occupancy 4.45 % (flat field, ESF ROI) ===\n",
" sigma [um] chi2/ndf MTF50 [lp/mm] MTF50 LSF MTF(200) LSF edge photons\n",
"GridBased 2-20 keV, flattened 1.551 +- 0.028 3.37 120.9 130.5 0.227 5.8278e+08\n",
"GridBased 12 keV, flattened 1.388 +- 0.020 1.82 135.0 141.3 0.251 5.6697e+08\n",
"eta 1.506 +- 0.023 1.83 124.4 130.0 0.218 2.3085e+08\n",
"\n",
"=== 0 filters, occupancy 10.77 % (flat field, ESF ROI) ===\n",
" sigma [um] chi2/ndf MTF50 [lp/mm] MTF50 LSF MTF(200) LSF edge photons\n",
"GridBased 2-20 keV, flattened 1.856 +- 0.037 7.36 101.0 113.1 0.173 7.0822e+08\n",
"GridBased 12 keV, flattened 1.473 +- 0.017 1.30 127.2 133.0 0.222 6.8684e+08\n"
]
}
],
"source": [
"header = (f\"{'':24s} {'sigma [um]':>16s} {'chi2/ndf':>8s} \"\n",
" f\"{'MTF50 [lp/mm]':>13s} {'MTF50 LSF':>9s} {'MTF(200) LSF':>12s} {'edge photons':>13s}\")\n",
"for filters, label in FLUX:\n",
" print(f'\\n=== {label}, occupancy {occupancy[filters]*100:.2f} % (flat field, ESF ROI) ===')\n",
" print(header)\n",
" for name, r in results[filters].items():\n",
" print(f\"{name:24s} {r['sigma']:7.3f} +- {r['sigma_err']:.3f} {r['chi2_ndf']:8.2f} \"\n",
" f\"{r['mtf50']:13.1f} {r['mtf50_lsf']:9.1f} {r['mtf200_lsf']:12.3f} {r['photons']:13.4e}\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "7b595528",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:45.806910Z",
"iopub.status.busy": "2026-09-28T08:22:45.806826Z",
"iopub.status.idle": "2026-09-28T08:22:46.448997Z",
"shell.execute_reply": "2026-09-28T08:22:46.448709Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAB4IAAAHKCAYAAADmRIbLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOzdd3hT1eMG8Dfde5cCbWmZBQptaVmyoWxFhoBsUHCADEUFNyAqCMIPUXDgV5YKCojIkiEgIBsFZK8W2lI66J5pkvP7o+SSNEmbtmmblvfzPDyaO8495+bevL333CETQggQEREREREREREREREREVGNYVHVFSAiIiIiIiIiIiIiIiIiItNiRzARERERERERERERERERUQ3DjmAiIiIiIiIiIiIiIiIiohqGHcFERERERERERERERERERDUMO4KJiIiIiIiIiIiIiIiIiGoYdgQTEREREREREREREREREdUw7AgmIiIiIiIiIiIiIiIiIqph2BFMRERERERERERERERERFTDsCOYiIiIiIiIiIiIiIiIiKiGYUcwEREREREREREREREREVENw45gIiIiIiIiIiIiIiIiIqIahh3BREREREREREREREREREQ1DDuCiYiIiIgeY4cOHYJMJsOaNWsqpPwJEyZAJpNVSNlVITU1FV5eXli4cGFVV8XsrVmzBjKZDIcOHTJq+oreFtWOHTsGCwsLHD9+vEKXYwo1bf8xJDk5GePGjUPdunUhk8nQrVs3AJW3TRSntHUw1+3enHEdEBERERFVHHYEExERERFRlVN3BBT3Lzo6WmuexMREvPXWW2jZsiVcXFzg7OyMBg0aYPDgwfjf//5ndNk//PCD0fWcN28erK2tMX36dJ1xv/76K9q3bw9HR0e4u7tjwIABuHDhglHlJicn4/nnn0doaCg8PT1hZ2eHBg0aYOTIkTh//rzR9aOSdejQAX379sWrr74KIURVV8ekcnJysHjxYrRr1w5ubm7SdjRp0iRcuXJF7zxF9w9LS0u4uroiKCgIw4cPx08//QS5XF7ifDKZDA4ODmjZsiU+/PBD5ObmGl3v119/HT///DNefvllrF+/Hu+++67BaaOjozF37lycO3fO6PJNzRzqUNlUKhXat28PmUyGnj17VnV1iIiIiIjISFZVXQEiIiIiIiK1oUOHYuDAgXrHeXt7S/9/9+5d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 truncated
"text/plain": [
"<Figure size 1936x462 with 4 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"colours = {f'{POLY}, flattened': '#c0392b', f'{MONO}, flattened': '#1f618d', 'eta': '#7d6608',\n",
" 'cluster CNN <=1ph': '#7f8c8d', 'cluster CNN <=2ph': '#8e44ad', 'cluster CNN <=3ph': '#4a235a'}\n",
"done = [(f, l) for f, l in FLUX if f'{POLY}, flattened' in results[f]]\n",
"fig, axes = plt.subplots(1, max(len(done), 1), figsize=(4.4 * max(len(done), 1), 4.2), squeeze=False)\n",
"for ax, (filters, label) in zip(axes[0], done):\n",
" for name in (f'{MONO}, flattened', f'{POLY}, flattened'):\n",
" if name not in results[filters]:\n",
" continue\n",
" r = results[filters][name]\n",
" centre = r['fit']['beta'][1]\n",
" ax.errorbar(r['x'] - centre, r['z'], yerr=r['z_err'], fmt='.', ms=3, lw=.6,\n",
" color=colours[name], label=f\"{name}: $\\\\sigma$ = {r['sigma']:.3f} $\\\\pm$ {r['sigma_err']:.3f} um\")\n",
" grid = np.linspace(-N_SIGMA * r['sigma'], N_SIGMA * r['sigma'], 300)\n",
" ax.plot(grid, r['fit']['model'](grid + centre), color=colours[name], lw=1.2)\n",
" ax.set_xlim(-8, 8); ax.set_ylim(-0.1, 1.4)\n",
" ax.set_title(f'{label} ({occupancy[filters]*100:.2f} %)', fontsize=10)\n",
" ax.set_xlabel('distance from the fitted edge [um]'); ax.set_ylabel('knife / flat')\n",
" ax.grid(alpha=.3); ax.legend(fontsize=7, loc='upper left')\n",
"fig.suptitle(f'presampled ESF ({BIN_WIDTH_UM} um bins) and ODR fit within $\\\\pm${N_SIGMA:g} sigma')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "5d493d1f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:46.450231Z",
"iopub.status.busy": "2026-09-28T08:22:46.450132Z",
"iopub.status.idle": "2026-09-28T08:22:46.793813Z",
"shell.execute_reply": "2026-09-28T08:22:46.793491Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1430x506 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, axes = plt.subplots(1, 2, figsize=(13, 4.6))\n",
"order = [f'{POLY}, flattened', f'{MONO}, flattened', 'eta', 'cluster CNN <=1ph', 'cluster CNN <=3ph']\n",
"for name in order:\n",
" points = [(occupancy[f] * 100, results[f][name]) for f, _ in FLUX if name in results[f]]\n",
" if not points:\n",
" continue\n",
" occ = np.array([p[0] for p in points])\n",
" style = dict(color=colours[name], marker='o', ls='-' if 'GridBased' in name else ':', lw=1.2, ms=5)\n",
" axes[0].errorbar(occ, [p[1]['sigma'] for p in points], yerr=[p[1]['sigma_err'] for p in points],\n",
" label=name, capsize=2, **style)\n",
" axes[1].plot(occ, [p[1]['mtf50'] for p in points], label=name, **style)\n",
"for ax, ylabel in zip(axes, ('LSF sigma [um]', 'MTF50 of the fitted Gaussian [lp/mm]')):\n",
" ax.set_xscale('log'); ax.set_xlabel('occupancy, flat field in the ESF ROI [%]')\n",
" ax.set_ylabel(ylabel); ax.grid(alpha=.3, which='both')\n",
"axes[0].legend(fontsize=8)\n",
"fig.suptitle('edge width against flux')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "ea39bb23",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:46.795017Z",
"iopub.status.busy": "2026-09-28T08:22:46.794926Z",
"iopub.status.idle": "2026-09-28T08:22:47.276187Z",
"shell.execute_reply": "2026-09-28T08:22:47.275894Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1936x429 with 4 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"frequency = np.linspace(0, 400, 800)\n",
"fig, axes = plt.subplots(1, max(len(done), 1), figsize=(4.4 * max(len(done), 1), 3.9), squeeze=False)\n",
"for ax, (filters, label) in zip(axes[0], done):\n",
" for name in (f'{MONO}, flattened', f'{POLY}, flattened'):\n",
" if name in results[filters]:\n",
" sigma_mm = results[filters][name]['sigma'] / 1000\n",
" ax.plot(frequency, np.exp(-2 * np.pi**2 * sigma_mm**2 * frequency**2), color=colours[name],\n",
" label=f\"{name}: {results[filters][name]['mtf50']:.0f} lp/mm\")\n",
" ax.axvline(200, color='k', ls=':', lw=.8)\n",
" ax.axhline(0.5, color='grey', lw=.5)\n",
" ax.set_title(f'{label} ({occupancy[filters]*100:.2f} %)', fontsize=10)\n",
" ax.set_xlabel('spatial frequency [lp/mm]'); ax.set_ylabel('MTF (Gaussian from the fit)')\n",
" ax.set_ylim(0, 1.02); ax.grid(alpha=.3); ax.legend(fontsize=7)\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "5d9c96d1",
"metadata": {},
"source": [
"### Relative DQE at 200 lp/mm\n",
"\n",
"Both models see the same beam at each flux point, so the ratio of the photons\n",
"they detect is the ratio of their quantum efficiencies, and DQE ∝ QE · MTF² is\n",
"comparable without the absolute flux. MTF(200) here is the one measured from the\n",
"LSF."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "e261c1be",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:47.277410Z",
"iopub.status.busy": "2026-09-28T08:22:47.277320Z",
"iopub.status.idle": "2026-09-28T08:22:47.380917Z",
"shell.execute_reply": "2026-09-28T08:22:47.380567Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"flux point occupancy photons 2-20 / 12 keV MTF(200) 12 keV MTF(200) 2-20 DQE(200) 2-20 / 12 keV\n",
"3 filters 0.75% 1.0266 0.252 0.227 0.837\n",
"2 filters 1.81% 1.0242 0.259 0.254 0.982\n",
"1 filter 4.45% 1.0279 0.251 0.227 0.846\n",
"0 filters 10.77% 1.0311 0.222 0.173 0.625\n"
]
}
],
"source": [
"print(f\"{'flux point':10s} {'occupancy':>9s} {'photons 2-20 / 12 keV':>22s} {'MTF(200) 12 keV':>15s} \"\n",
" f\"{'MTF(200) 2-20':>13s} {'DQE(200) 2-20 / 12 keV':>23s}\")\n",
"relative_dqe = {}\n",
"for filters, label in FLUX:\n",
" r = results[filters]\n",
" a, b = r.get(f'{POLY}, flattened'), r.get(f'{MONO}, flattened')\n",
" if a is None or b is None:\n",
" continue\n",
" qe = a['photons'] / b['photons']\n",
" relative_dqe[filters] = qe * (a['mtf200_lsf'] / b['mtf200_lsf'])**2\n",
" print(f\"{label:10s} {occupancy[filters]*100:8.2f}% {qe:22.4f} {b['mtf200_lsf']:15.3f} \"\n",
" f\"{a['mtf200_lsf']:13.3f} {relative_dqe[filters]:23.3f}\")"
]
},
{
"cell_type": "markdown",
"id": "9de8f3c7",
"metadata": {},
"source": [
"## 3. What the energy head reports at 12 keV\n",
"\n",
"The polychromatic model also measures every photon's energy. On a\n",
"monochromatic beam the spectrum shows where it puts the 12 keV line, how wide it\n",
"is, and how much ends up off the line: charge lost at the halo / edges below, and\n",
"pile-up (two photons in one cell) above — the model cannot report more than one\n",
"photon per cell or much beyond its 20 keV range."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "70897b20",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:47.382133Z",
"iopub.status.busy": "2026-09-28T08:22:47.382048Z",
"iopub.status.idle": "2026-09-28T08:22:48.050311Z",
"shell.execute_reply": "2026-09-28T08:22:48.050022Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"run line [keV] core sigma [keV] < 10.5 keV 10.5-13.5 > 13.5 keV\n",
"Edge_3filters_pos0 12.045 0.408 4.32% 94.84% 0.84%\n",
"Flat_3filters_pos0 12.055 0.407 4.01% 95.15% 0.84%\n",
"Edge_2filters 12.010 0.422 4.91% 93.08% 2.01%\n",
"Flat_2filters 12.006 0.422 4.85% 93.11% 2.04%\n",
"Edge_1filters 11.910 0.458 7.44% 87.38% 5.19%\n",
"Flat_1filters 11.891 0.459 7.44% 87.34% 5.21%\n",
"Edge_0filters 11.642 0.572 13.41% 72.68% 13.90%\n",
"Flat_0filters 11.637 0.574 13.48% 72.46% 14.05%\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1430x484 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def gauss(x, a, mu, sig):\n",
" return a * np.exp(-0.5 * ((x - mu) / sig)**2)\n",
"\n",
"\n",
"def line_fit(centres, counts, guess=12.0, half_window=0.8):\n",
" # Gaussian core of the line; weighted mean / rms of the window if the fit fails\n",
" near = np.abs(centres - guess) < 2\n",
" peak = centres[near][np.argmax(counts[near])]\n",
" window = np.abs(centres - peak) < half_window\n",
" x, y = centres[window], counts[window]\n",
" try:\n",
" pars, _ = curve_fit(gauss, x, y, p0=(y.max(), peak, 0.3), maxfev=20000)\n",
" return pars[1], abs(pars[2])\n",
" except Exception:\n",
" mean = np.average(x, weights=y)\n",
" return mean, np.sqrt(np.average((x - mean)**2, weights=y))\n",
"\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(13, 4.4))\n",
"print(f\"{'run':30s} {'line [keV]':>11s} {'core sigma [keV]':>16s} {'< 10.5 keV':>10s} \"\n",
" f\"{'10.5-13.5':>9s} {'> 13.5 keV':>10s}\")\n",
"spectra = {}\n",
"for filters, label in FLUX:\n",
" for kind, ax in (('Edge', axes[0]), ('Flat', axes[1])):\n",
" path = run_dir(f'{kind}_{filters}_12keV', POLY_TAG) / 'energy_spectrum.npy'\n",
" if not path.exists():\n",
" continue\n",
" centres, counts = np.load(path)\n",
" total = counts.sum()\n",
" mu, sig = line_fit(centres, counts)\n",
" low, mid, high = (counts[centres < 10.5].sum() / total, counts[(centres >= 10.5) & (centres <= 13.5)].sum() / total,\n",
" counts[centres > 13.5].sum() / total)\n",
" spectra[f'{kind}_{filters}'] = dict(line_keV=mu, core_sigma_keV=sig, below=low, line=mid, above=high)\n",
" print(f\"{kind + '_' + filters:30s} {mu:11.3f} {sig:16.3f} {low*100:9.2f}% {mid*100:8.2f}% {high*100:9.2f}%\")\n",
" ax.semilogy(centres, counts / total / np.diff(centres)[0], lw=1,\n",
" label=f'{label} ({occupancy[filters]*100:.2f} %)')\n",
"for ax, kind in zip(axes, ('knife edge', 'flat field')):\n",
" ax.set_xlim(0, 30); ax.set_ylim(1e-6, 5)\n",
" ax.set_xlabel('reconstructed photon energy [keV]'); ax.set_ylabel('photons / keV (normalised)')\n",
" ax.set_title(f'{kind}, {POLY}'); ax.grid(alpha=.3, which='both'); ax.legend(fontsize=8)\n",
"fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "1c638bc8",
"metadata": {},
"source": [
"## 4. Fake photons on dark runs (1000 frames each, pedestal of another run)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "50b9539e",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:48.051536Z",
"iopub.status.busy": "2026-09-28T08:22:48.051448Z",
"iopub.status.idle": "2026-09-28T08:22:48.232125Z",
"shell.execute_reply": "2026-09-28T08:22:48.231785Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"run model frames photons per frame\n",
"Pedestal_1filters_12keV GridBased 2-20 keV 1000 106 0.1060\n",
"Pedestal_1filters_12keV GridBased 12 keV 1000 0 0.0000\n",
"Pedestal_2filters_12keV GridBased 2-20 keV 1000 105 0.1050\n",
"Pedestal_3filters_pos0_12keV GridBased 2-20 keV 1000 73 0.0730\n"
]
}
],
"source": [
"print(f\"{'run':30s} {'model':20s} {'frames':>7s} {'photons':>8s} {'per frame':>10s}\")\n",
"dark = {}\n",
"for run in ('Pedestal_1filters_12keV', 'Pedestal_2filters_12keV', 'Pedestal_3filters_pos0_12keV'):\n",
" for model, tag in ((POLY, POLY_TAG), (MONO, ''), (MONO, 'darktest')):\n",
" s = summary_of(run, tag)\n",
" if s is None:\n",
" continue\n",
" dark[f'{run}_{tag}'] = s['n_photons']\n",
" print(f\"{run:30s} {model:20s} {s['n_frames']:7d} {s['n_photons']:8d} {s['n_photons']/s['n_frames']:10.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "eec26108",
"metadata": {},
"source": [
"## 5. The model on Monte Carlo\n",
"\n",
"Fixed-occupancy evaluation of the new checkpoint (`scripts/04_evaluate.py`,\n",
"threshold 0.3) and its energy response (`scripts/07_energy_response.py`,\n",
"occupancy ≤ 2.5 %), both on MOENCH040 2–20 keV frames the network never saw."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "4271e8af",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:48.233350Z",
"iopub.status.busy": "2026-09-28T08:22:48.233268Z",
"iopub.status.idle": "2026-09-28T08:22:48.349056Z",
"shell.execute_reply": "2026-09-28T08:22:48.348722Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"occupancy efficiency purity RMS [um] sigma68 [um] E sigma68 [keV]\n",
" 0.5% 99.54% 98.44% 1.171 0.775 0.348\n",
" 1% 99.25% 98.02% 1.257 0.808 0.364\n",
" 2% 98.62% 97.53% 1.400 0.858 0.391\n",
" 5% 96.65% 95.85% 1.859 1.041 0.524\n",
" 10% 93.61% 93.09% 2.650 1.461 0.925\n",
"\n",
"energy response (epoch 182):\n",
"E_true [keV] line - E [meV] core sigma [keV] efficiency\n",
" 2.375 70 0.202 95.58%\n",
" 3.875 8 0.304 97.77%\n",
" 5.875 -5 0.319 98.39%\n",
" 7.875 -7 0.339 98.64%\n",
" 9.875 1 0.381 99.41%\n",
" 11.875 -5 0.391 99.52%\n",
" 14.875 -13 0.388 99.77%\n",
" 17.875 -4 0.386 99.64%\n",
" 19.375 -85 0.317 99.77%\n"
]
}
],
"source": [
"eval_path = POLY_EXPERIMENT / 'eval_summary.json'\n",
"if eval_path.exists():\n",
" ev = json.load(open(eval_path))\n",
" print(f\"{'occupancy':>9s} {'efficiency':>10s} {'purity':>8s} {'RMS [um]':>9s} {'sigma68 [um]':>12s} {'E sigma68 [keV]':>15s}\")\n",
" for key, value in ev.items():\n",
" m = value['summary']\n",
" print(f\"{key:>9s} {m['efficiency']*100:9.2f}% {m['purity']*100:7.2f}% {m['rms_1d_um']:9.3f} \"\n",
" f\"{m['sigma68_1d_um']:12.3f} {m['energy_sigma68_keV']:15.3f}\")\n",
"else:\n",
" print('no eval_summary.json in', POLY_EXPERIMENT)\n",
"response_path = POLY_EXPERIMENT / 'energy_response.npz'\n",
"if response_path.exists():\n",
" r = np.load(response_path)\n",
" print(f\"\\nenergy response (epoch {int(r['epoch'])}):\")\n",
" print(f\"{'E_true [keV]':>12s} {'line - E [meV]':>14s} {'core sigma [keV]':>16s} {'efficiency':>10s}\")\n",
" for energy in (2.5, 4.0, 6.0, 8.0, 10.0, 12.0, 15.0, 18.0, 19.5):\n",
" i = int(np.argmin(np.abs(r['table_centre'] - energy)))\n",
" print(f\"{r['table_centre'][i]:12.3f} {(r['table_core_mu'][i]-r['table_centre'][i])*1000:14.0f} \"\n",
" f\"{r['table_core_sigma'][i]:16.3f} {r['table_efficiency'][i]*100:9.2f}%\")"
]
},
{
"cell_type": "markdown",
"id": "0c49b3fa",
"metadata": {},
"source": [
"## 6. Summary"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "89072280",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T08:22:48.350375Z",
"iopub.status.busy": "2026-09-28T08:22:48.350271Z",
"iopub.status.idle": "2026-09-28T08:22:48.475704Z",
"shell.execute_reply": "2026-09-28T08:22:48.475399Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"flux point occupancy sigma, 12 keV model [um] sigma, 2-20 keV model [um] MTF50 12 keV / 2-20 keV\n",
"3 filters 0.75% 1.360 +- 0.019 1.420 +- 0.021 138 / 132 lp/mm\n",
"2 filters 1.81% 1.346 +- 0.019 1.391 +- 0.018 139 / 135 lp/mm\n",
"1 filter 4.45% 1.388 +- 0.020 1.551 +- 0.028 135 / 121 lp/mm\n",
"0 filters 10.77% 1.473 +- 0.017 1.856 +- 0.037 127 / 101 lp/mm\n",
"\n",
"written to /home/xie_x1/MLXID/GridBased/Results/overnight_poly2to20keV_Moench040/report/knife_edge_poly2to20keV_summary.json\n"
]
}
],
"source": [
"summary = dict(\n",
" poly_experiment=POLY_EXPERIMENT.name, poly_tag=POLY_TAG, mono_experiment=MONO_EXPERIMENT.name,\n",
" roi_superpixel=list(ROI), bin_um=BIN_UM, esf_bin_width_um=BIN_WIDTH_UM, odr_window_sigma=N_SIGMA,\n",
" convention='sigma = ODR p2 (Gaussian LSF); MTF50 = 1000 sqrt(ln 2 / 2) / (pi sigma)',\n",
" occupancy_flat_esf_roi=occupancy,\n",
" occupancy_flat_esf_roi_poly={f: flat_occupancy(f, POLY_TAG) for f, _ in FLUX},\n",
" results={f: {name: {k: float(r[k]) for k in ('sigma', 'sigma_err', 'chi2_ndf', 'mtf50',\n",
" 'mtf50_lsf', 'mtf200_lsf', 'photons', 'rows_kept')}\n",
" for name, r in results[f].items()} for f, _ in FLUX},\n",
" relative_dqe_200lpmm=relative_dqe, spectra=spectra, dark_photons=dark, missing=missing)\n",
"out = OVERNIGHT / 'report' / 'knife_edge_poly2to20keV_summary.json'\n",
"out.parent.mkdir(parents=True, exist_ok=True)\n",
"json.dump(summary, open(out, 'w'), indent=1)\n",
"\n",
"print(f\"{'flux point':10s} {'occupancy':>9s} {'sigma, 12 keV model [um]':>24s} {'sigma, 2-20 keV model [um]':>26s} \"\n",
" f\"{'MTF50 12 keV / 2-20 keV':>24s}\")\n",
"for filters, label in FLUX:\n",
" a, b = results[filters].get(f'{POLY}, flattened'), results[filters].get(f'{MONO}, flattened')\n",
" if a is None or b is None:\n",
" continue\n",
" print(f\"{label:10s} {occupancy[filters]*100:8.2f}% {b['sigma']:15.3f} +- {b['sigma_err']:.3f} \"\n",
" f\"{a['sigma']:17.3f} +- {a['sigma_err']:.3f} {b['mtf50']:12.0f} / {a['mtf50']:.0f} lp/mm\")\n",
"print(f'\\nwritten to {out}')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch_nightly",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,661 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "86bf6c9c",
"metadata": {},
"source": [
"# Siemens star, 15 keV, MOENCH040 — polychromatic 2–20 keV GridBased model\n",
"\n",
"SOLEIL 2504 beamtime, MOENCH040 @150 V, 15 keV, 20 M frames of the full chip\n",
"(2000 files). The two regions of `DataProcess/Configs.py` are reconstructed:\n",
"\n",
"| region | x [px] | y [px] |\n",
"|---|---|---|\n",
"| `SiemenStarLowerLeft` | 130–240 | 110–220 |\n",
"| `SiemenStarLowerRight` | 235–345 (the notebooks use 234–344) | 110–220 |\n",
"\n",
"Both share the same rows, so one crop of the frame, x 126–349 and y 106–224\n",
"(the regions plus the network's halo R = 4), is read and reconstructed in one\n",
"pass; its valid region is x 130–345, y 110–220. Reconstruction: the 2–20 keV\n",
"MOENCH040 model, FP16 with an FP32 head, masked column common mode (median over the pixels without charge) over the\n",
"118 rows of the crop, self-flattening of the sub-pixel phase with one Rosenblatt table per\n",
"local-occupancy class (tables from the first 3·10⁸ photons, every photon\n",
"remapped; one table for the whole crop left a 25 µm grid in the dim star of\n",
"LowerLeft, whose occupancy is ~8x below the rest). The run is processed as four parts of 500\n",
"files; this notebook sums the parts it finds.\n",
"\n",
"For comparison, the published cluster-based reconstructions of the same regions\n",
"(`SiementStar_LowerLeft/Right.ipynb`): eta interpolation (single-photon clusters)\n",
"and the 1/2/3-photon CNNs summed (`≤3ph`), all at 2.5 µm."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "5e4befe5",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:40.244153Z",
"iopub.status.busy": "2026-09-28T10:33:40.244075Z",
"iopub.status.idle": "2026-09-28T10:33:40.777351Z",
"shell.execute_reply": "2026-09-28T10:33:40.777052Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"parts found: 4 of 4 ['SiemensStar_15keV_part0', 'SiemensStar_15keV_part1', 'SiemensStar_15keV_part2', 'SiemensStar_15keV_part3']\n",
"model 260927_poly2to20keV_Moench040_c96_00 (epoch 182), 20,000,000 frames, 1.6259e+10 photons (813.0 per frame in the 23650 px valid region)\n",
" SiemensStar_15keV_part0 files [0, 499, 1] 5,000,000 frames occupancy 3.29 % (illuminated 5.58 %) 2434 frame/s sub-pixel rms 17.2 -> 1.2 %\n",
" SiemensStar_15keV_part1 files [500, 999, 1] 5,000,000 frames occupancy 3.45 % (illuminated 5.88 %) 1970 frame/s sub-pixel rms 17.3 -> 1.1 %\n",
" SiemensStar_15keV_part2 files [1000, 1499, 1] 5,000,000 frames occupancy 3.50 % (illuminated 5.96 %) 2325 frame/s sub-pixel rms 17.3 -> 1.1 %\n",
" SiemensStar_15keV_part3 files [1500, 1999, 1] 5,000,000 frames occupancy 3.51 % (illuminated 5.97 %) 2391 frame/s sub-pixel rms 17.3 -> 1.1 %\n"
]
}
],
"source": [
"import json\n",
"import os\n",
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"from scipy.optimize import curve_fit\n",
"\n",
"GRID = Path('/mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased')\n",
"PUBLISHED = Path('/home/xie_x1/MLXID/DeepLearning/InferenceResults')\n",
"ETA = Path('/home/xie_x1/MLXID/EtaInterpolation/Results')\n",
"OVERNIGHT = Path('/home/xie_x1/MLXID/GridBased/Results/overnight_poly2to20keV_Moench040')\n",
"POLY_TAG = os.environ.get('GRID_POLY_TAG', 'poly2to20')\n",
"RUNS = [r.strip() for r in os.environ.get(\n",
" 'GRID_STAR_RUNS', ','.join(f'SiemensStar_15keV_part{i}' for i in range(4))).split(',')]\n",
"CNN = {1: '1ph_260616_15keV_v260511_00', 2: '2ph_260622_15keV_v260610_00',\n",
" 3: '3ph_260623_15keV_v260611_00'}\n",
"B = 10 # 2.5 um bins per 25 um pixel\n",
"# region -> (x0, x1, y0, y1) in chip pixels: the whole region, the zoom of the\n",
"# published notebooks, and the 50 x 50 px FRC box (SiementStar_LowerLeft.ipynb's\n",
"# [1200:2100, 1400:2300][200:-200, 220:-180]; the same box around the right star)\n",
"REGIONS = {'LowerLeft': dict(roi=(130, 240, 110, 220), zoom=(140, 190, 170, 210), frc=(162, 212, 140, 190)),\n",
" 'LowerRight': dict(roi=(234, 344, 110, 220), zoom=(284, 324, 160, 200), frc=(263, 313, 140, 190))}\n",
"\n",
"plt.rcParams.update({'figure.dpi': 110, 'image.origin': 'lower', 'image.interpolation': 'nearest'})\n",
"\n",
"\n",
"def run_dir(run):\n",
" return GRID / (run + (f'_{POLY_TAG}' if POLY_TAG else ''))\n",
"\n",
"\n",
"parts = [r for r in RUNS if (run_dir(r) / 'binned_image_flattened.npy').exists()]\n",
"print(f'parts found: {len(parts)} of {len(RUNS)} {parts}')\n",
"summaries = [json.load(open(run_dir(r) / 'summary.json')) for r in parts]\n",
"frames = np.array([s['n_frames'] for s in summaries], float)\n",
"flattened = sum(np.load(run_dir(r) / 'binned_image_flattened.npy').astype(float) for r in parts)\n",
"raw = sum(np.load(run_dir(r) / 'binned_image.npy').astype(float) for r in parts)\n",
"analog = sum(np.load(run_dir(r) / 'analog_image.npy') * n for r, n in zip(parts, frames)) / frames.sum()\n",
"spectrum_centres = np.load(run_dir(parts[0]) / 'energy_spectrum.npy')[0]\n",
"spectrum = sum(np.load(run_dir(r) / 'energy_spectrum.npy')[1] for r in parts)\n",
"crop_x0, _, crop_y0, _ = summaries[0]['crop']\n",
"print(f\"model {Path(summaries[0]['checkpoint']).parent.parent.name} (epoch {summaries[0]['epoch']}), \"\n",
" f\"{frames.sum():,.0f} frames, {sum(s['n_photons'] for s in summaries):.4e} photons \"\n",
" f\"({sum(s['n_photons'] for s in summaries)/frames.sum():.1f} per frame in the \"\n",
" f\"{summaries[0]['valid_pixels']} px valid region)\")\n",
"for s in summaries:\n",
" print(f\" {s['run']:28s} files {s['signal_files']} {s['n_frames']:>10,d} frames \"\n",
" f\"occupancy {s['occupancy_valid_region']*100:.2f} % (illuminated {s['occupancy_exposed']*100:.2f} %) \"\n",
" f\"{s['frames_per_second']:.0f} frame/s sub-pixel rms {s['subpixel_nonuniformity_before']*100:.1f} -> \"\n",
" f\"{s['subpixel_nonuniformity_after']*100:.1f} %\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "54a0702c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:40.778887Z",
"iopub.status.busy": "2026-09-28T10:33:40.778584Z",
"iopub.status.idle": "2026-09-28T10:33:40.970833Z",
"shell.execute_reply": "2026-09-28T10:33:40.970515Z"
}
},
"outputs": [],
"source": [
"def cut_grid(image, box, factor=B):\n",
" # chip-pixel box -> the GridBased crop's binned image\n",
" x0, x1, y0, y1 = box\n",
" return image[(y0 - crop_y0) * factor:(y1 - crop_y0) * factor,\n",
" (x0 - crop_x0) * factor:(x1 - crop_x0) * factor]\n",
"\n",
"\n",
"def cut_chip(image, box, factor=B):\n",
" # chip-pixel box -> a full-chip super-frame (4000 x 4000 at 2.5 um)\n",
" x0, x1, y0, y1 = box\n",
" return image[y0 * factor:y1 * factor, x0 * factor:x1 * factor]\n",
"\n",
"\n",
"def published(region):\n",
" eta = np.load(ETA / f'SiemenStar{region}_fromClusters.npy', mmap_mode='r')\n",
" single = cnn = None\n",
" for n, experiment in CNN.items():\n",
" path = (PUBLISHED / f'SiemenStar{region}' / experiment / 'augX1'\n",
" / f'{n}Photon_ML_superFrame_etaInterpolated.npy')\n",
" if path.exists():\n",
" image = np.asarray(np.load(path, mmap_mode='r'), float)\n",
" single = image if n == 1 else single\n",
" cnn = image if cnn is None else cnn + image\n",
" return eta, single, cnn\n",
"\n",
"\n",
"images = {}\n",
"for region, boxes in REGIONS.items():\n",
" eta, single, cnn = published(region)\n",
" images[region] = {}\n",
" for key, box in boxes.items():\n",
" images[region][key] = {\n",
" 'native readout (25 um)': cut_grid(analog, box, factor=1),\n",
" 'eta interpolation': np.asarray(cut_chip(eta, box), float),\n",
" 'cluster CNN 1ph': cut_chip(single, box) if single is not None else None,\n",
" 'cluster CNN <=3ph': cut_chip(cnn, box) if cnn is not None else None,\n",
" 'GridBased 2-20 keV': cut_grid(flattened, box),\n",
" }\n",
" images[region]['roi_raw_grid'] = cut_grid(raw, boxes['roi'])"
]
},
{
"cell_type": "markdown",
"id": "43f9cd97",
"metadata": {},
"source": [
"## 1. The two regions"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "29d6aaff",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:40.972383Z",
"iopub.status.busy": "2026-09-28T10:33:40.972301Z",
"iopub.status.idle": "2026-09-28T10:33:41.986287Z",
"shell.execute_reply": "2026-09-28T10:33:41.985991Z"
}
},
"outputs": [
{
"data": {
"image/png": "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C+U2T548WS7T3usBAD788EOcfPLJ0Ov1SEtLw1VXXYWamhooFIpm1SFbeg8AXn31VZx88skwmUwwGo3o378/Xn/99WbL0fVXXl6Oyy67DA6HA3q9HgMHDsS3337brv1vDbPZLIV1B4MQAvX19S0em0NBCIF77rkHCoUCN998M2KxGID2PXMVCgWuuOIKNDQ04M0332zx91944QUYDAZcfPHFh2V7GYZhGIZhGIZhGIZhCK5cxzAMwzAMwzAMwzDMYaVjx44AgEWLFuHhhx8+pMpfJSUlGDBgABoaGnDFFVegS5cu2L9/P/7zn//g448/xjfffIO8vLyE79x1110IBoP45z//CZVKhQULFmDMmDF46623cMUVV+CCCy7AqFGjsHHjRrz00ktISUnBwoULf9M67777btTX12PSpEkwmUx4+eWXceuttyIzMxMXXnghgCax4bBhw+ByuTB58mRkZmaiqqoK3333HTZu3IhRo0a1eSwuuOACrF27FldffTV69+6NYDCIXbt2Yc2aNQAAl8uFxYsX45ZbboHT6cQ999wjv+tyuQAAy5Ytw5YtWzBu3Djk5+fD4/HgzTffxMSJE1FVVYXbb7+92XovueQSaDQaXH/99TAajejatetBnMGWiUajGDFiBNauXYuRI0fi+uuvR3l5Of7zn//g1FNPxQcffIDBgwcDaKpWtXr1avzwww+yzeOaNWugVCrR0NCAL7/8Eqeddpp8H0gUdL7wwgu48sor0bt3b9x5552wWq3YsGED7rrrLnz//ffNRE4NDQ047bTTcNJJJ2HmzJnwer0tVshqjUgkghEjRmD9+vW46KKLcO2118Ln8+GVV17BkCFDsHz5cowcORKnn346Fi9ejAceeABut1uKMXNzc1FSUoLLLrsMp512Gq6++upDPs5arRannXYa1q5di1gsBqVSmXD8Pv/8cwSDQaSkpMj3LRYL+vbtm/A7CxcuxP79+3HllVfC5XLhnXfewezZs2E2m3HnnXfK5T788EOMHj0aFosFkydPRnp6OlatWoVp06bhiy++wMqVK+U2/JZj1x4WLFiAiooKXH311cjOzobZbAZwcNfDe++9h3PPPReZmZm48847YbPZ8O677+Kss85q3wkAMG3aNNx///3o1asX7r33XgDAK6+8gosuugi7d+/G3XffnbB8Y2MjTjvtNPTt2xf3338/Kioq8Nhjj+Gss87C7t275X78XlitVtTX10OtVmPAgAGYNm1aq4LpAxEKhXDFFVfgtddew6OPPopbbrkFwME9cydNmoQZM2bg+eefx+WXX57w+//973/x888/Y8KECbBYLL9txxmGYRiGYRiGYRiGYZIRDMMwDMMwDMMwDMMwh5GioiJhtVoFAJGeni4uuOAC8cgjj4gvv/xSxGKxZstPmDBBJLsoxo4dK2w2m9i1a1fC+7t37xYmk0lMnDhRvrdo0SIBQPTq1UsEAgH5/vfffy8ACIVCId54442E3xkzZozQaDTC6/Ue1nU2NDQIh8Mh/v73v8v3FixYIACIL7/8ss3j1hJ1dXVCoVCIa6+99oDL5ufni4EDB7b4WUNDQ7P3otGoOP3004XVahWhUEi+P336dAFAnH766SIcDrd7W/Pz80XHjh3bXOb5558XAMQ///nPhPd/+eUXkZKSIjp37iyi0agQQohNmzYJAGLOnDlyuYEDB4qRI0cKm80mpk2bJt8/55xzhM1mk98tKysTOp1OjB07ttk1N3fuXAFArFu3LuF3AYi77767xe0GIIYOHdrmvs2fP18AEO+8807C+6FQSPTu3VsUFhYmvD9w4ECRn5/f4romTJjQ5rraA+3n119/LYQQwu/3C51OJy677DIBQHz66adCCCEikYiwWCxizJgx8rtr164VAERGRoaoqamR70ejUdGtWzeRmZkp34tEIqKgoEAYDAaxe/fuhG2YNGmSACAWL14s36N7Z+3atfK9gz12LUHbbLPZREVFRcJnB3M9RCIRkZeXJ1JTU0VpaWnCvo8ZM6bF85P83vbt24VSqRR9+vQRPp9Pvt/Q0CCOP/54oVKpRFFRkXyfrr/Zs2cn/O6SJUsEALFw4cID7n97OdC1fMcdd4gbb7xRLF68WCxfvlzMmjVLOJ1OoVAoxKuvvtquddAzpKioSNTV1YnBgwcLnU4nli5dmrDcwTxzhRBi5MiRAoDYunVrwvsTJ04UAMTnn3/eru1jGIZhGIZhGIZhGIY5GLgtLMMwDMMwDMMwDMMwh5WCggL873//w4033giDwYA333wTt912G/r3748uXbrIKmOt4fF48N5772HEiBFITU2F2+2Wf2azGf3798dHH33U7HvXX3+9rMQFACeeeCJSU1ORmZmJCy64IGHZgQMHIhwOy9aDh2udRqMRf//732VrXKCpAhQALF++HH6//4DHLx69Xo+UlBR89dVX2L1790F9Nx6j0Sj/7/f7UV1djZqaGpx55pmoq6vDL7/80uw7t9xyyyFVHWyLt99+G0BTVa94unTpgosvvhg7duzAjz/+CADo06cPbDabvF58Ph82btyIM844A4MGDZLvRyILine truncated
"text/plain": [
"<Figure size 2530x506 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAACc8AAAIPCAYAAACvu7RHAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOydd5hT1fb3v+l10iaZzhQGGECKVBGlgwVBULEXsF97uWKXol7FdkW92AtiAQtiAawIiAqiCNLr9J4pSSYz6TnvH7xr/5LMwAwIYlmf55kHcnJyzj51r73Wd60tkyRJAsMwDMMwDMMwDMMwDMMwDMMwDMMwDMMwDMMwDMP8g5Af6wYwDMMwDMMwDMMwDMMwDMMwDMMwDMMwDMMwDMMwzB8Ni+cYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmGYfxwsnmMYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmH+cbB4jmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhvnHweI5hmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEY5h8Hi+cYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmGYfxwsnmMYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmH+cbB4jmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhvnHweI5hmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEY5h8Hi+cYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmGYfxwsnmMYhmEYhmEYhmEY5qgxbdo0yGSyY90MhjlizJ8/HzKZDKtWrTrsbYwcORK5ublHrE1/BEfiuA/ErFmzIJPJUFxcfMS3fSwoLi6GTCbDrFmzjnVT/jRwX8AwDMMwDMMwDMMwzJ8VFs8xDMMwDMMwDMMwDHNI1NbW4u6770bv3r1hMpmQlJSEzp0746yzzsJrr712rJv3t2Tu3LmYP3/+Mdt/bm4uunTpcsz2fzSRyWRxfxqNBnl5ebj66qtRXl5+rJsHl8uFWbNmHRXRGvPPZNeuXZg+fTrGjh2L5ORkyGQyXHXVVQdcf+TIka2eE/pbunTpH9jy/2Pw4MFQKpXtPqP9+/eHWq1GbW3tH9QyhmEYhmEYhmEYhmH+aiiPdQMYhmEYhmEYhmEYhvnrUFpaisGDB6Ourg5TpkzB1VdfDbVajcLCQnz99deYO3currzySrH+K6+8ghdffPEYtvjvwdy5c5Gbm4tp06Yd66b8LTnuuONw9913AwA8Hg++++47vPbaa1i+fDk2b96M5ORkse6ll16KCy64AGq1+g9pm8vlwuzZswHsFzExf35ycnLg8/mgVP45Xa9r167Fk08+iby8PAwaNAhffvllu7+x2+14+umnWy3v16/f0Whiu1x99dW45pprMH/+fNx///1trrNx40Zs3LgRZ599NlJSUv7gFjIMwzAMwzAMwzAM81fhz+nBYRiGYRiGYRiGYRjmT8mTTz6JmpoazJ07F7fcckur7xOrAKlUKqhUqj+qecxhEI1GEQgEoNPpjnVT/jACgQAUCoUQN6WlpeGSSy4R319//fVIS0vDM888g/nz5+Pf//63+E6hUEChUPzhbWaODJIkwefzQa/XH7V9yGQyaLXao7b938vEiRPR0NAAq9WK4uJi5OXltfsbg8EQ94wcay688ELcfvvteP3113Hfffe1OSXsq6++CgAHrarHMAzDMAzDMAzDMAzD07YyDMMwDMMwDMMwDNNhdu/eDQAYM2ZMm99nZWXFfZ42bVqbooaamhrcdNNNyM3NhVqtRmpqKi655BIUFxfHrTd//nzIZDKsWLECjzzyCDp37gytVou+ffvi888/BwBs374dEyZMgNlshsViwbRp0+D1en/3PleuXIm5c+eiW7du0Gg06Ny5c5uVl9atW4eJEyciIyMDGo0G6enpGD16ND777LMDnkeisbER06dPR9euXaHT6WC1WtG7d28h1iouLoZMJkNJSQlWr14dN10itXv9+vW44oorUFBQAIPBAIPBgMGDB+PNN99stb9Zs2ZBJpNh+/btuPPOO5GTkwO1Wo333nuv3bZ2BL/fj9mzZ6N79+7QarWw2WyYOHEifvnll7j1xo0bB4fDAUmSxLIff/wRMpkMSUlJCIVCYvnmzZshk8nw+OOPx21j5cqVOP3002G1WqHRaNCjRw889thjiEQiceuNHDkSubm5KCkpwQUXXAC73Q6tVtvudI9jx44FAOzZsyduOd0fidOoVlVV4dJLL0VycjIMBgOGDRuG77777oDPAABUV1eL3+h0OowYMQIbNmyI2xcJm2bPni2ufW5u7kHb3hYVFRWQyWRxQkAAuOaaayCTyXDttdfGLb/99tshk8lQU1MTt1ySpA49FwDwxRdfYNSoUTCZTNDpdDj++OMxb968uOt+MJqamnDfffehoKAAGo0GNpsNkydPxubNmzv0e7pW33zzDR599FHR5th7afHixRgxYoRoY79+/YToKpG33noLffr0gUajQVZWFu68807s2LEDMpkMs2bNEuvRcxu7DNgvVH322WfRt29f6HQ6mEwmjB49Gl9//XWrfeXm5mLkyJHYvXs3Jk2aBLPZDKPRiDPOOAP79u3r0PEfiOTkZFit1kP+XTQahcfjQTQa/V37J0KhEKZNmwa5XI4nn3xSLN+3bx+mTZuGjIwMqNVqZGVl4frrr0ddXZ1Yx2g04vzzz0dRURFWrlzZatt+vx/vvvsusrOzceqppx6R9jIMwzAMwzAMwzAM8/eEK88xDMMwDMMwDMMwDNNh8vPzAQBvvPEGHnvsscOalrCsrAxDhw6F1+vFlVdeiW7duqGiogIvvPACvvrqK/zyyy/Izs6O+80999yDQCCA6667DgqFAs888wwmTZqEDz/8EFdeeSXOO+88TJw4EWvXrsWbb74JjUaDl1566Xft895774XH48Hll18Oo9GIBQsW4Pbbb0d6ejouuOACAPvFhGPHjoXD4cANN9yA9PR0OJ1O/Prrr1i7di0mTpx40HNx3nnnYeXKlbjmmmvQr18/BAIB7Nu3DytWrAAAOBwOvPXWW7jttttgt9tx3333id86HA4AwJIlS7B161ZMmTIFOTk5cLvdeP/99zFt2jQ4nU7ccccdrfZ78cUXQ6VS4cYbb4TBYEBBQcEhXMG2iUQiGD9+PFauXIkJEybgxhtvRHV1NV544QWcfPLJ+PzzzzFq1CgA+4Vp33zzDTZt2iSmfVyxYgXkcjm8Xi/WrVuHYcOGieVAvGDz9ddfx1VXXYV+/frh7rvvhsViwQ8//IB77rkHGzduxKJFi+La5vV6MWzYMAwePBizZ89GU1MTjEbjQY9n7969ABA3ZeuBcLvdGDZsGAoLC3HFFVdgwIAB2LFjB8aPH48uXbq0+Zvm5mYMGzYMAwYMwEMPPYSamho8/fTTOO2001BYWIikpCQMHz4cTz/9NG677TacddZZOPvsswGg3ba3RWZmJrp3745vvvkmbjmd97aW9+rVC6mpqXHLO/JcAMBrr72Gq6++Grm5uZg+fTqMRiM+/PBD3Hjjjfjtt9/w8ssvH7S9Ho8HJ598Mvbu3YupU6eib9++aGxsxCuvvIITTzwRa9asQf/+/Tt07NOnT4fP58PUqVPhcDjQqVMnAPvFpLNnz8aoUaMwc+ZM6HQ6fPnll7j66quxd+9ezJkzR2xj3rx5uPHGG9G9e3fMnj0bKpUKCxcubCWiPBjTpk3DW2+9hZNPPhmPPvoompqa8Oqrr+LUU0/FggULWlV2q6iowPDhw3HmmWfisccew549e/Dcc8/hzDPPxJYtWyCX/3F50RUVFTAajfD5fNBqtRg9ejQeeuihDl+DRDweD8455xysWbMGixYtwnnnnQcA2LRpE0aOHAm9Xo8rrrgCOTk52LNnD1544QWsWLEC69evh9lsBrB/6tbXXnsNr732GkaPHh23/Q8//BAulwu33HLLH3qeGIZhGIZhGIZhGIb5CyIxDMMwDMMwDMMwDMN0kKKiIslisUgApNTUVOm8886TnnrqKWndunVSNBpttf7UqVOlRPfD5MmTJavVKu3bty9ueWFhoWQ0GqVp06aJZW+88YYEQOrTp4/k9/vF8o0bN0oAJJlMJr333ntx25k0aZKkUqmkpqamI7pPr9crJScnSyeeeKJY9swzz0gApHXr1h30vLWFy+WSZDKZ9K9//avddXNycqQRI0a0+Z3X6221LBKJSMOHD5csFosUDAbF8pkzZ0oApOHDh0uhUKjDbc3JyZHy8/MPus5rr70mAZCuu+66uOW7du2SNBqN1LVrVykSiUiSJEk///yzBEB6/PHHxXojRoyQJkyYIFmtVmnGjBli+RlnnCFZrVbx26qqKkmr1UqTJ09udc898cQTEgBp1apVcdsFIN17771ttpvOh9PplJxOp1RYWCjNnz9fslgsklKplLZs2RK3Pt0fK1euFMvuvfdeCYA0b968uHUXL14sAWj1DFCbHnnkkbjlCxculABIL730klhWVFQkAZBmzpzZZvsPhRtuuEGSyWRSTU1N3LYvvfRSCYBUWFgoSZIk1dbWSjKZTLrllltaHXdHnguXyyUZjUYpMzNTqq+vF8tDoZA0btw4CYC0Zs0asZzuy6KiIrHs1ltvlVQqVatnq7GxUcrKypJGjhzZ7vFSm7t27drqOdm4caMkk8mkm2++uc3zJJfLxfuisbFRMhgMUufOnSWPxyPW8/v90qBBg1pdn7au2YoVKyQA0oQJE6RwOCyW19bWSikpKZLFYol7Z+Xk5EgApHfffTeubY8++qgEQPryyy/bPf6OQG298sorD7jO1KlTpXvuuUdauHChtHjxYumee+6RDAaDpNFo4p61gxHbF5SVlUm9e/eWrFar9N1338Wtd/zxx0t5eXlx940kSdK6deskuVwuzZo1K2557969Ja1WKzULine truncated
"text/plain": [
"<Figure size 2530x506 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def limits(img):\n",
" # grey levels from the central 60 % of the image: in LowerLeft the unshadowed\n",
" # strip at x > 222 is several times brighter than the star (and brighter\n",
" # still for GridBased, which does not lose its photons to pile-up)\n",
" ny, nx = img.shape\n",
" centre = img[int(.2 * ny):int(.8 * ny), int(.2 * nx):int(.8 * nx)]\n",
" centre = centre[np.isfinite(centre)]\n",
" return np.percentile(centre, 0.5), np.percentile(centre, 99.5)\n",
"\n",
"\n",
"def show(region, key, title):\n",
" box = REGIONS[region][key]\n",
" panels = [(name, img) for name, img in images[region][key].items() if img is not None]\n",
" fig, axes = plt.subplots(1, len(panels), figsize=(4.6 * len(panels), 4.6))\n",
" for ax, (name, img) in zip(axes, panels):\n",
" low, high = limits(img)\n",
" ax.imshow(img, cmap='gray', extent=[box[0], box[1], box[2], box[3]], vmin=low, vmax=high)\n",
" ax.set_title(name, fontsize=10)\n",
" ax.set_xlabel('x [pixel, 25 um]'); ax.set_ylabel('y [pixel, 25 um]')\n",
" fig.suptitle(f'Siemens star {region}, {title}, 15 keV')\n",
" fig.tight_layout()\n",
"\n",
"\n",
"for region in REGIONS:\n",
" show(region, 'roi', 'whole region')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "b4daba25",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:41.989993Z",
"iopub.status.busy": "2026-09-28T10:33:41.989887Z",
"iopub.status.idle": "2026-09-28T10:33:42.917327Z",
"shell.execute_reply": "2026-09-28T10:33:42.917050Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAACdcAAAHtCAYAAADxptVPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOzdd3RU1doG8Gf6TCbJlPReaAKCgoJcEAMIFgRBRUVRARV7RbFeBdRrL9i7CMrFgh0RRQRERcVesCA1hCSkJ5NMkpnM/v7ge/fNpAcpis9vLdYiM2fOnJk55+z27ncblFIKRERERERERERERERERERERERERKQZ9/UBEBEREREREREREREREREREREREf3VMLiOiIiIiIiIiIiIiIiIiIiIiIiIqAkG1xERERERERERERERERERERERERE1weA6IiIiIiIiIiIiIiIiIiIiIiIioiYYXEdERERERERERERERERERERERETUBIPriIiIiIiIiIiIiIiIiIiIiIiIiJpgcB0RERERERERERERERERERERERFREwyuIyIiIiIiIiIiIiIiIiIiIiIiImqCwXVERERERERERERERERERERERERETTC4joiIiIiIiIiIiIiIiIiIiIiIiKgJBtcRERERERERNTJlyhQYDIZ9fRhEe1UoFMKsWbOQnZ0Ns9nMa2APq6mpwWWXXYb09HSYTCZkZmbu8r6GDRv2p17/V2MwGDBlypQObbty5UoYDAY8//zz+rHNmzfDYDBg1qxZnX7vfXH//zPH+1f0V/o8nTmX9gctXQ9ERERERERE9OcxuI6IiIiIiIj2ezt27MB1112HPn36IDo6GlFRUcjOzsYJJ5yAZ599dl8f3n5pzpw5+3SAPzMzE127dt1n778nGQwGjBw5crfuc968eZg9ezaGDx+OZ599Fi+88AIAYNasWXjzzTd363sRcNddd+Hhhx/Gqaeeiueffx5z5sxpc/t9fT3R/m/lypWYNWsWysvL9/Wh7DfuvPNOnHrqqejWrRuMRiPMZnOr2z7//PMwGAwt/pswYcJePOr/eeyxx2AwGHDHHXe0ud1DDz0Eg8GABx54YC8dGREREREREdHe1XqLnoiIiIiIiGg/sHXrVgwcOBDFxcWYMGECpk2bBqvVio0bN2LZsmWYM2cOzjnnHL39008/jSeeeGIfHvH+Yc6c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 truncated
"text/plain": [
"<Figure size 2530x506 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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odPpMG7cOFgsFjRs2PAs3mD1eL1e9O7dG5s3b0bfvn0xbtw4pKWl4a233sL111+PdevWoWvXrgAqA9M2btyI3377DS1atAAAbNq0CWq1GkVFRdi5cyc6duwojwP+AZvvvfce7r77brRo0QJPPPEEQkJCsH37dvzvf//Dr7/+WiUopaioCB07dkSbNm0wffp0FBYWIigo6IzPc+jQIQDw27K1JvLz89GxY0ckJyfjzjvvRKtWrbB//3707t0b9erVq/Y3xcXF6NixI1q1aoVnnnkG6enpeOWVV3DTTTchOTkZVqsVnTp1wiuvvILx48dj4MCBGDRoEADUWvaaWLp0aZVje/fuxYwZMxARESGPffXVV+jfvz9sNhseeOABhIeHY+3atZgyZQq+//57rFmzBmp15brQkydPonXr1sjIyMCYMWPQrFkz/P7775g/fz6++uor/PjjjwgPD/e755tvvonMzEyMGTMGQUFBePfddzFy5EjodDr873//Q8eOHfHss8/izz//xBtvvIGRI0di/fr1tT7fb7/9hk6dOsHr9WLs2LFITEzEd999h9mzZ2Pjxo34/vvvYTabce+996JHjx64/fbb0bFjR9xzzz0AgPbt21d73UaNGmHp0qVVzj+dkydPolOnTujfvz9efPFFHDx4EK+99hr69++P3bt3yzrzeDzo3bs3tm7diqFDh+K+++5DSUkJ3n//fXTr1g0rV65E3759a31eAHj99ddx4sQJ3HfffXA4HFixYgWmTp2K5OTkGoMBA2X16tV4+eWXcf/99+Ouu+7Cxo0bMWfOHPzxxx/YsGGDfJ5Aef755/F///d/6NOnD+6++27o9XocO3YMX331FdLS0vzaSSD9gxg9ejSWLFmCAQMGYPjw4QCAzz//HAMHDsRbb72F++67T577xBNP4MUXX8S1116LF154AaWlpXjvvfewatWqs66fkSNHQqVS4aGHHoJarcabb76J4cOHIzExEe3atZPnTZkyBc888wyaNWuGp556CgDw/vvvY+jQoUhOTsbkyZMBVG7P/fLLL2Pbtm1+fZXaZUFBAa6//nocOnQII0eORPPmzZGbm4v58+fjuuuuw7Zt29CyZUu/MgbaPgLtO0BlwFeHDh2wb98+DB8+HO3bt8fhw4fx5ptv4quvvsL27dvPuMXp8uXLMWLECLRr1w4rVqxASEgIPB4PevbsiePHj2Ps2LG46qqrUFhYiL/++gvffPMNHn/88bN+P/+E5s2bo6CgAGq1Gi1atMCkSZNw6623ntO1hBD43//+hxdffBHjx4/H7NmzoVKpzup9Dh48GA899BAWLVqEadOm+fW9Y8eOYcOGDejcuTPq169/Xp6fYRiGYRiGYRiGYf4Line truncated
"text/plain": [
"<Figure size 2530x506 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"for region in REGIONS:\n",
" show(region, 'zoom', 'zoom of the published notebooks')"
]
},
{
"cell_type": "markdown",
"id": "cba943fe",
"metadata": {},
"source": [
"### GridBased 2–20 keV at full size\n",
"\n",
"The flattened reconstruction of each region at 2.5 µm, and the same photons\n",
"before the sub-pixel flattening (the 25 µm pixel grid is visible there)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "76f70e13",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:42.920209Z",
"iopub.status.busy": "2026-09-28T10:33:42.920105Z",
"iopub.status.idle": "2026-09-28T10:33:43.673514Z",
"shell.execute_reply": "2026-09-28T10:33:43.673248Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABmYAAAMwCAYAAAA6Vd9WAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOz9eZzkdXXvj79q6aruquqq7qqu3nu6Z3p2hmUQBAURDBE30BijJsGAu17jEqMQQ77BGK8oUcyNSYg3XtGLRDHJ1XhdkhhFIEGv7OPADAOz9/TeXdVVvVR3LZ/fH/17nvlUTw8MyO779XjMA7q6uurzeX/ey3md8zrnBDzP8+Tg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODwlCP4TF+Ag4ODg4ODg4ODg4ODg4ODg4ODg4ODg4PDrwpcYMbBwcHBwcHBwcHBwcHBwcHBwcHBwcHBweFpggvMODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4PE1wgRkHBwcHBwcHBwcHBwcHBwcHBwcHBwcHB4enCS4w4+Dg4ODg4ODg4ODg4ODg4ODg4ODg4ODg8DTBBWYcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHByeJrjAjIODg4ODg4ODg4ODg4ODg4ODg4ODg4ODw9MEF5hxcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHB4muACMw4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODk8TXGDGwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHhaYILzDg4ODzl+MlPfqJAIKCf/OQnj/nej3/84woEAk/9RT1NGBgY0OWXX/5MX8aTCp7nN77xjWf6UhyeIG6++WZt3bpVkUjkSV9vP/7xj/WCF7xATU1NCgQCOnDgwPNuXT9RPB/3AwcHBwcHh6cCjj9c/kxfxpMKxx+e+3iq+MPll1+uxsbGJ+3zwGqc5NmAWq2mq666Sv39/QoGgzr//PMlPT/W/eWXX66BgYFn+jIcHJ5TcIEZBweH42J6elpXX321tm/frmQyqWg0qjVr1uiNb3yj/uVf/kWe5z0t1zEwMKBAIGD/GhsbtX79en3wgx/U+Pj403INTyXm5+d1/fXX6xWveIV6enoUjUY1ODioj33sY5qfnz+hz5ientbnPvc5nX/++ero6FBjY6O2bt2qv/iLv1ClUjnm/Z7n6X/8j/+hjRs3KhqNauPGjfqrv/qrp+2ZSlK5XFZbW5vOOuusR33fC1/4QnV0dKx6H4+Gr3zlKwoEAvrZz372y1zmswKBQEDvec97npTP2rdvny699FJ1d3fri1/8om688UZJ0ic/+Ul9+9vf/qU+u1Ao6A1veIM8z9MXvvAF3Xjjjcpms7/UZ373u9/Vxz/+8VV/92Rcs4ODg4ODg8OTB8cfnh44/uD4w2PhucIfngo8FZzkycJNN92kT33qU3rVq16lr371q7rqqqt+6c883nPYuXOnPv7xjz9rglIODg7HIvxMX4CDg8OzE/fee69e/epXa2pqSr/1W7+lt7/97YrFYhoaGtK//du/6XWve53+9m//Vu9973sf87POO+88LSwsKBKJPOHrOeWUU/TRj35UklQqlbRjxw793d/9nX70ox/p/vvvVygUesKf/Uxj3759et/73qeXvvSlev/7369MJqOf/vSnuvbaa/Vf//Vf+slPfqJg8NHj6HfccYf+6I/+SBdddJGuvPJKxWIx/fCHP9QVV1yh+++/X1/72tfq3v+JT3xCH//4x/WWt7xFV1xxhW699VZ98IMfVD6f15/+6Z8+lbdraGho0Bvf+EZdf/31euSRR7R+/fpj3vPwww/rzjvv1Ac+8AGFw+7IejJw++23q1Kp6LOf/axOO+00e/2Tn/yk3vzmN+t1r3vdE/7se++9V7lcTl/+8pd/qc/x47vf/a6++MUvrhqceTKu2cHBwcHBweHJgeMPTx8cf3D84enEU8kfngo8FZzkycKPf/xjtbS06G//9m+ftMyj4z2HnTt36s/+7M90/vnnPy2ZLH//93+vWq32lH+Pg8PzCe6UcnBwOAYzMzO65JJLVKvVdPfdd2vbtm11v//TP/1T3Xrrrcrlco/6OfPz84rFYgoGg790enJXV5cuvfTSutdaW1v1iU98Qjt27ND27dt/qc9/JtHZ2an7779fJ598sr32zne+Uxs3btTHPvYxfe9739PFF1/8qJ9x0kkn6eGHH64zuN7znvfoPe95j774xS/qyiuvtM8fHR3VNddco7e97W36X//rf0mS3vGOdygUCulTn/qU3vWud6mzs/PJv9FVcOmll+r666/XTTfdpKuvvvqY39900032vl81VKtVVavVX8ohsRrGxsYkSS0tLU/q5z7Vn+3g4ODg4ODw7IXjD08vHH9w/GE1PBf5w1OBp+J62Zt+WYyNjSmVSj2vyi+ChoaGZ/oSHByec3ClzBwcHI7BF7/4RQ0NDelzn/vcMaQKvPSlL61TZJD2fcstt+hDH/qQOjs7FY/HJR2/RvT3vvc9nXrqqWpsbNTGjRvNyD9RtLe3S1KdEmp6elof/ehHdcopp6i5uVmJREIXXHCB/uu//uuYv//mN7+pM888U8lkUqlUSieffLI++clP1r2nWCzqiiuu0Nq1a60Uwx/90R9pcXGx7n1LS0u68sor7b4vvPBC7d69+4Tuo62trY5Ugd/8zd+UJD3wwAOP+Rlr165dVQWz2mf8y7/8ixYXF/X+97+/7r3vf//7tbi4qH/5l385oev2o1Kp6M1vfrOampr0/e9/X9JyuYPrr79ep556qpqampTJZPS7v/u7Ghoasr978YtfrMHBQSNQK/EP//AP2rhxo84888zHfU0nip07d+qSSy5RS0uLYrGYXvziF+vf/u3f7Pee56mtrU3/7b/9t7q/6+vrUyAQ0L59++y1m2++WYFAQDt37rTXxsbG9O53v9vKTGzYsEHXXnttXdmHAwcOKBAI6NOf/rSuv/56bdiwQdFoVHfccccJ38eJjPfAwICuvPJKSctzJhAI6PLLL1cgENDi4qK++tWvWskP6h2fKM4//3y96U1vkiRdcMEFj/kZ3/nOd3TxxRert7dX0WhU/f39uvLKK+vW1uWXX64vfvGLklRXjoTxerRrPtG1S5mHH/zgB9q+fbsaGxu1YcMGff3rXz/mmp+O/cDBwcHBweG5CMcfjsLxhxOD4w/L+FXmD34cPHhQr371q5VIJNTe3q4//MM/1NLS0jHv+8d//EedffbZisfjSqVSuvjii/Xggw/a7x+Lk/znf/6nXvaylymRSKi5uVkvf/nLdeedd9Z9x6PtTZJ0zz336OKLL1Zra6uampr0whe+UN/97ncf9f54Xj/4wQ908OBBG7OvfOUrq75/aWlJV199tc4880z7nrPOOkvf+c536t53vOfwla98Rb/9279dNw4rv+9E7oOxuP322/XHf/zH6urqUlNTk17+8pdr//79de9drcfM4+FaDzzwgC688ELFYjF1dnbqiiuu0L//+7+fcL8xB4fnIlzGjIODwzH4zne+o8bGRv3Wb/3W4/7b97///WptbdVVV12lfD5/3Pf9+Mc/1mtf+1qtX79ef/7nf65SqWQH/Wool8uanJyUJC0uLur+++/Xtddeq+3bt+ukk06y9+3bt0//+I//qDe84Q1av369ZmZm9L/+1//Sr/3ar+muu+4yovgf//EfevOb36yXvexluuaaaxQKhfTQQw/p9ttvt88qlUq64IILtHfvXr3rXe/S4OCg7rvvPn3uc5/T7t276+q4vvvd79ZXvvIVveENb9AFF1ygO++8U7/+67+uUqn0uMcQDA8PS1omXk/mZ9x9992KRqM65ZRT6t67fft2RSIR3XPPPY/rOxYXF/XGN75RP/rRj/S9731PL3vZyyQtz4W/+7u/01ve8ha9973v1ejoqL7whS/ojjvu0L333msKpt/93d/VJz7xCd155511BOrnP/+5Hn74YX3iE594Ird+QtizZ4/OOeccRaNR/cEf/IESiYRuuOEGvepVr9K3vvUtXXLJJQoEAjr33HN166232t/t379fQ0NDCgaDuvXWW7Vu3TpJ0m233aZ0Om1zcnJyUmeffbYWFxf1rne9S11dXbr99tt15ZVXanh4WH/5l39Zdz1f+9rXNDc3p3e9611KJBLHXQ+r4UTG+y//8i/1jW98QzfffLM+//nPq62tTYODg7rwwgv1tre9TWeffbbe9a53SZI6Ojoe11heddVV2rZtm/7mb/5Gf/zHf6wtW7Y86md8+ctfVigU0u///u+rtbVVP/vZz/S5z31OQ0NDRrTf/e536/Dhw/rxj39stawlKZvN6sYbbzzuNT+etStJP/vZz/Stb31L733ve/X2t79dX/rSl3TppZdq+/bt2rx58+P+zKdiP3BwcHBwcHg2w/GHZTzT9oLjD44/PJf4A6jVanrFK16h0047TZ/5zGf0n//5n7ruuus0PT2tG264wd732c9+Vh/96Ef1+te/Xm95y1s0Ozurv/3bv9U555yje+65R2vXrn1UTnLbbbfp13/919Xb26s/+ZM/Ua1W09/93d/ppS99qW699dZjgnmr7U233367Xv7yl2vbtm266qqrFI1GdfPNN+uSSy7RP/3TP+n1r3/9qvcIf/mLv/gLDQ8P6/Of/7yk5UDjaigUCrr++uv1pje9SW9961tVKpX0D//wD3rd616nH/zgB7rooosk6bicaHBwUO973/vqxsH/fY/3Pv7gD/5AjY2N+tjHPqbJyUl99rOf1e/+7u+eUCDwRLjWyMiIXvrSl6pSqegjH/mI0um0vva1r+mHP/zhY36+g8NzGp6Dg4PDCqTTae/UU0895vXZ2VlvYmLC/uXzefvdDTfc4EnyzjrrLK9cLtf93S233OJJ8m655RZ77fTTT/fa2tq8qakpe+3BBx/0QqGQt3Jr6u/v9yQd8++MM87whoeH695bKpWLine truncated
"text/plain": [
"<Figure size 1650x814 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1650x814 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"for region, boxes in REGIONS.items():\n",
" box = boxes['roi']\n",
" fig, axes = plt.subplots(1, 2, figsize=(15, 7.4))\n",
" for ax, img, name in ((axes[0], images[region]['roi']['GridBased 2-20 keV'], 'flattened'),\n",
" (axes[1], images[region]['roi_raw_grid'], 'before flattening')):\n",
" low, high = limits(img)\n",
" ax.imshow(img, cmap='gray', extent=[box[0], box[1], box[2], box[3]], vmin=low, vmax=high)\n",
" ax.set_title(f'GridBased 2-20 keV, {region}, {name}', fontsize=11)\n",
" ax.set_xlabel('x [pixel, 25 um]'); ax.set_ylabel('y [pixel, 25 um]')\n",
" fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "318433ac",
"metadata": {},
"source": [
"## 2. Profiles and FRC\n",
"\n",
"A row through the zoom (normalised to its mean), and the one-image Fourier ring\n",
"correlation as `SiementStar_LowerLeft.ipynb` computes it (`frc.one_frc` and\n",
"`frc.frc_res` on the 50 × 50 px box x 162–212, y 140–190, no window; for\n",
"LowerRight the same box around its star). The cutoffs are in that notebook's\n",
"units, so they compare with its ML 2.041 / eta 2.053 for LowerLeft."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "6162c863",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:43.677997Z",
"iopub.status.busy": "2026-09-28T10:33:43.677903Z",
"iopub.status.idle": "2026-09-28T10:33:43.883956Z",
"shell.execute_reply": "2026-09-28T10:33:43.883755Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABfYAAAHCCAYAAACt2ZiFAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOzdd3wU5dbA8d/M7iab3ulVpakgWADLpdsB21VREOEFUbBduRYsKIpiBdu1NxABr2AB7ICA5aqAilJEEAgtlPS+beZ5/9hCQtoGEnYTztfPfkxmZ555ZmY3zJw5cx5NKaUQQgghhBBCCCGEEEIIIUSDoIe6A0IIIYQQQgghhBBCCCGECJ4E9oUQQgghhBBCCCGEEEKIBkQC+0IIIYQQQgghhBBCCCFEAyKBfSGEEEIIIYQQQgghhBCiAZHAvhBCCCGEEEIIIYQQQgjRgEhgXwghhBBCCCGEEEIIIYRoQCSwL4QQQgghhBBCCCGEEEI0IBLYF0IIIYQQQgghhBBCCCEaEAnsCyGEEEIIIYQQQgghhBANiAT2hRBCCCGEEEIIIYQQQogGRAL7QgjRQG3fvp1LL72UtLQ0NE1j1KhRoe6SEEIIIYQQx6x+/frRrl27w14+PT0dTdOYMmVKnfVJCCFE4yWBfSFEWFixYgWapvHoo4+Guit1bubMmWiaxnvvvVen7Y4aNYqVK1dyzz33MHv2bG688UbWrl3LlClTSE9Pr9N1ieD99ddf3HXXXQwaNIiUlBQ0TWPs2LFVzu/xeHjqqac4+eSTsdvtJCUlcfHFF7NmzZoql3njjTc45ZRTiIqKIi0tjWuvvZYdO3bUx+YIIYQQQlTpWDiHL/uKi4vjjDPO4IUXXsAwjFB3kRUrVjBlyhTy8vJC3ZVj1qpVq7j99tvp06cP8fHxNX4fnE4nL774IqeddhpJSUkkJCTQrVs3Hn/8cYqKiirM7/F4ePLJJ+nUqRORkZG0aNGC8ePHk52dXZ+bJYRoICSwL4QQDZDT6eS7777juuuu484772TEiBGceeaZrF27locfflgC+yH0448/8swzz7Bt2zbOOOOMaudVSnHZZZdxzz33kJaWxtNPP82kSZPYtGkT55xzDt99912FZSZPnsy4ceOIi4vjueee4/bbb2fJkiWceeaZ7N69u742SwghhBDimDR+/Hhmz57Nu+++y+TJkykpKeH222/n5ptvrjDv119/zV9//XXU+rZixQoefvhhCeyH0Oeff85//vMfMjMzOfXUU2ucf9iwYdx22220adOGxx57jGnTptG2bVvuu+8+Lrzwwgrzjx49mkmTJtGxY0f+85//MHr0aN5991369OlDYWFhfWySEKIBsYa6A0II0VgVFxcTExNTL23v378fpRTJycn10n516nO7GoMhQ4aQk5NDUlIS6enptG/fvsp5Fy1axKeffsr555/PF198gaZpANx444107tyZG264gT///DMwffPmzTz++OOceuqprFixAqvV+8/4xRdfzOmnn859993Hu+++W/8bKYQQQgjRSB16rnvWWWcxYsSIwO8TJkygS5cuvPHGG0ydOpW0tLTAexEREUe1r/VNKUVpaSnR0dGh7krYGj9+PHfeeSexsbGsWLGC/v37Vznv33//zSeffMLll1/Ohx9+GJh+8803M2TIED799FM2bNjASSedBMA333zDe++9x9ChQ1m4cGFg/p49e3LppZfy9NNP88gjj9Tfxgkhwp5k7AshGpxNmzYxbNgwmjZtSmRkJMcddxx33nknBQUFgXn27NmDpmn8+9//LrfsuHHj0DSNG2+8sdz0iRMnomka+/fvD0xzuVw89dRTdOvWjaioKOLj4xk0aBDffvttuWXL1sL88MMP6dmzJ9HR0Vx88cW13ratW7cyatQoWrRoQUREBK1atWLChAlkZWUF5unXrx9t27YF4OGHHw48GtyuXTtGjx4NQP/+/QPTy9be37lzJ5s2bcLtdtfYF/+j1TNnzuS1116jW7du2O32ctlJc+bMoVevXsTGxhITE0Pv3r15//33y7Xz2GOPoWka69atC0xzu93ExsaiaRo//vhjYLpSitTUVC666KJq+3bxxRcTFRVFbm5uhfcyMjKwWq0MGzasxm2sDykpKSQlJQU17zfffAN4yyr5g/cAiYmJXHLJJfz111/88MMPgelz587FMAxuu+22QFAfoEePHvTv358FCxZQWlpa43r9n4vly5dz9tlnExMTQ2pqKqNHjyYzMzMwn2EY9O3bF7vdzq+//lqh7xaLhUsvvTSobRVCCCHEsa2xnMPHxsbSq1cvTNNk69at5d6rqsb+l19+Sa9evYiKiqJJkybccMMN5OTkVDtO1hdffEHv3r0Dy9x0002UlJSUW9fDDz8MQPv27QPn/mXr82/dupVNmzZVuz1+/tJDS5cu5fHHH6djx45ERkby1FNPAWCaJi+88EKgHGR8fDwDBgxgyZIl5dq54YYbsFqt5Z4iyMjICPRv7969gel5eXlYLBYmTJhQbd9OPvlkWrZsWWn5o59//hlN05g0aVJQ21nXmjZtSmxsbFDz5ufnA9CyZcsK77Vq1Qqg3E0Uf8LOxIkTy817ySWXcMIJJwSV0FP2c75gwQJOPfVUoqKiaNGiBXfccQfFxcWBeYuKiujcuTMpKSns2rWrXDuzZs1C0zT+9a9/BbWtQoijQzL2hRANytq1a+nTpw+GYTB+/HiOO+44vv/+e6ZPn87SpUv53//+R3R0NC1btqRz584sXbq03PLLli1D1/VKp5988sk0bdoU8NYyvOiii1i5ciXXXHNN4ET6vffeY8CAAXzyyScMHjy4XBsLFy7kueee46abbuKGG25AKVXrbevXrx/R0dH83//9H23btmXLli288sorLFu2jFWrVpGQkMD999/PpZdeyh133MFll13G5ZdfDkDr1q2ZO3cur7/+Ovfddx9dunQB4Pjjjw+sY+TIkaxcuZLt27cHPbDX888/z/79+xk3bhwtW7YkLi4OgAcffJCpU6fSrVs3Jk+eDMB7773HNddcw7Zt27jvvvsAGDRoEA888ABLly6la9euAPz0008UFxcHjsWZZ54Z2AfZ2dkMHDiw2j7ddNNNfP7558yePZvbbrut3HvvvPMOhmFwww031Lht+fn5Qd3kALDZbCQkJAQ1b7CcTidApVlQ/mk//fQT55xzDuC9cAFv5tihzjrrLJYtW8a6devo2bNnjev+7bffWLBgAaNHj2bEiBH8/PPPzJw5k1WrVrF69Wqio6OxWCzMmzeP7t27c9VVV/Hrr78SHx/PgQMHGD58OK1bt+add9457O0XQgghxLGhsZ3D+wP6KSkpNc67aNEiLrvsMpo3b86kSZNISkpi4cKFXHDBBVUu88UXX/Cf//yHG2+8kVGjRrFs2TJee+01AF599VUA7r//fpKTk/n444959tlnSU1NBaBbt26BdgYOHMiOHTtqdV1y1113UVpayvXXX09aWhqtW7cGvIkos2fP5pxzzuHxxx+nsLCQN998k/PPP59333038FTDoEGDePPNN1m+fDmXXXYZ4D1O/iSWpUuXct111wGwfPlyTNOs8dz/xhtv5LbbbuOzzz5j6NCh5d578803axzTyi83NzfosRHsdnvQAftgnXTSSbRo0YI333yTbt26MXDgQJRSfPXVV7zzzjuMGTOm3NO+P//8M7qu07t37wptnXnmmcyePZsDBw7QpEmTGtf96aefMmPGDCZMmMCYMWNYunQpzz33HH/88QdLlixB13ViY2P54IMP6NWrF1dffTXffvstVquVP//8k5tvvpnTTz89cKNHCBEmlBBChIHly5crQE2dOrXa+f7xj38oTdPUjz/+WG76ww8/XGH5m2++WWmapvbv36+UUmr79u0KUNddd50C1LZt25RSSh04cEBpmqZuv/32wLLPPfecAtRHH31Ubj0ul0v16NFDtW/fPjDN367ValUbNmyo0Od33nlHAWr27NnVblv37t1V+/btVXZ2drnpP/30k9J1XU2ZMqXCOh966KFK17V8+fJK19G3b18FqO3bt1fbF6UOHpOkpKTAPvTbvHmz0nVdnXrqqaqkpCQwvaioSJ188snKYrEE1uHxeFRCQoK66KKLAvM99NBDKikpSQ0ePFj16dMnMP3pp59WgPrtt9+q7ZthGKpNmzaqa9eu5aabpqnat2+vjj/+eGWaZo3b6N8fwbz69u1bY3uH8h+nMWPGVPr+Cy+8oIBynz3/dvTo0UMB6tZbbw1MP/nkkxVQbp/7vfbaawpQH374YY398m/T/Pnzy01/6qmnKv0efvnll0rTNHXllVcqwzDUoEGDlM1mUz/99FON6xJCCCFE43UsnMO/8sorKjMzUx04cED9/vvvaty4cQpQvXv3rrBM3759Vdu2bQO/ezwe1aZNGxUfH68yMjIC0w3DUJdccokC1PXXX1+hT1FRUWrr1q3l2j7//POVzWZTRUVFgWkPPfRQtef2bdu2VcGGffzb26FDh3LrUEqpZcuWKUANHjxYeTyewPQDBw6oJk2aqMTERFVYWBiYpmmamjBhQmC+66+/Xp1yyimqW7duauTIkYHpN998s9J1vcL1z6Hy8vJUdHS0GjJkSLnphYWFKjY2Vg0YMCCobfTvj2BeZY9LsIL5Pvzyyy/qlFNOKbcuXdcrXSY2NlY1adKk0nbuvfdeBahffvml2j75P1Oapqmff/653Hs333xzpdep/uuKO++8U5WUlKiTTz5ZJSQkVPhMCiFCT0rxCCEajMzMTL777jvOP//8ClkLd955JzExMeVqFfozIPzlTpYtW4bFYuHhhx/GYrGwbNkywFtSRClVLlNk9uzZtGvXjn/84x9kZWUFXvn5+QwdOpTt27ezefPmcn24+OKLOfHEEw9r29avX8/atWsZNmwYpmmWW+fxxx9Phw4d+Oqrrw6r7bJWrFiBUirobH3wZvkLine truncated
"text/plain": [
"<Figure size 1540x462 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, axes = plt.subplots(1, len(REGIONS), figsize=(7 * len(REGIONS), 4.2))\n",
"for ax, region in zip(np.atleast_1d(axes), REGIONS):\n",
" box = REGIONS[region]['zoom']\n",
" for name, img in images[region]['zoom'].items():\n",
" if img is None or name.startswith('native'):\n",
" continue\n",
" row = img[img.shape[0] // 2]\n",
" ax.plot(box[0] + (np.arange(len(row)) + 0.5) / B, row / row.mean(), lw=1, label=name)\n",
" ax.set_xlabel('x [pixel, 25 um]'); ax.set_ylabel('intensity / mean')\n",
" ax.set_title(f'{region}: row y = {(box[2] + box[3]) / 2:.0f} px'); ax.grid(alpha=.3); ax.legend(fontsize=8)\n",
"fig.tight_layout()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "81b5a618",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:43.885036Z",
"iopub.status.busy": "2026-09-28T10:33:43.884951Z",
"iopub.status.idle": "2026-09-28T10:33:44.783796Z",
"shell.execute_reply": "2026-09-28T10:33:44.783573Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"DIPlib -- a quantitative image analysis library\n",
"Version 3.5.1 (Jul 5 2024)\n",
"For more information see https://diplib.org\n",
"region reconstruction FRC cutoff: median range crossed\n",
"LowerLeft eta interpolation 2.053 2.053-2.053 2/10\n",
"LowerLeft cluster CNN 1ph 2.045 2.045-2.045 1/10\n",
"LowerLeft cluster CNN <=3ph 2.050 2.020-2.079 2/10\n",
"LowerLeft GridBased 2-20 keV nan nan-nan 0/10\n",
"LowerRight eta interpolation nan nan-nan 0/10\n",
"LowerRight cluster CNN 1ph nan nan-nan 0/10\n",
"LowerRight cluster CNN <=3ph nan nan-nan 0/10\n",
"LowerRight GridBased 2-20 keV nan nan-nan 0/10\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1540x462 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# one_frc splits the counts binomially with its own (unseeded) generator, so one\n",
"# call is one draw: repeat it and quote the median and the range\n",
"N_REPEAT = 10\n",
"frc_results = {}\n",
"try:\n",
" import frc\n",
" fig, axes = plt.subplots(1, len(REGIONS), figsize=(7 * len(REGIONS), 4.2))\n",
" for ax, region in zip(np.atleast_1d(axes), REGIONS):\n",
" frc_results[region] = {}\n",
" for name, img in images[region]['frc'].items():\n",
" if img is None or name.startswith('native'):\n",
" continue\n",
" square = frc.util.square_image(np.array(img, float), add_padding=False)\n",
" size = square.shape[0]\n",
" values, first = [], None\n",
" for _ in range(N_REPEAT):\n",
" curve = frc.one_frc(square)\n",
" xs = np.arange(len(curve)) / size\n",
" first = curve if first is None else first\n",
" try:\n",
" values.append(float(frc.frc_res(xs, curve, size)[0]))\n",
" except Exception: # the curve never crosses 1/7\n",
" values.append(np.nan)\n",
" values = np.array(values)\n",
" found = np.isfinite(values)\n",
" frc_results[region][name] = dict(\n",
" median=float(np.median(values[found])) if found.any() else np.nan,\n",
" low=float(values[found].min()) if found.any() else np.nan,\n",
" high=float(values[found].max()) if found.any() else np.nan,\n",
" crossings=int(found.sum()), repeats=N_REPEAT)\n",
" r = frc_results[region][name]\n",
" ax.plot(np.arange(len(first)) / size, first, lw=1,\n",
" label=f\"{name}: {r['median']:.3f} ({r['crossings']}/{N_REPEAT} crossed)\")\n",
" ax.axhline(1 / 7, color='k', lw=.6, ls=':')\n",
" ax.set_xlabel('spatial frequency (1/pixels)'); ax.set_ylabel('FRC')\n",
" ax.set_title(f'{region}, FRC box {REGIONS[region][\"frc\"]}'); ax.grid(alpha=.3); ax.legend(fontsize=8)\n",
" fig.tight_layout()\n",
" print(f\"{'region':11s} {'reconstruction':20s} {'FRC cutoff: median':>18s} {'range':>15s} {'crossed':>8s}\")\n",
" for region, values in frc_results.items():\n",
" for name, r in values.items():\n",
" print(f\"{region:11s} {name:20s} {r['median']:18.3f} {r['low']:7.3f}-{r['high']:.3f} \"\n",
" f\"{r['crossings']:5d}/{r['repeats']}\")\n",
"except Exception as error:\n",
" print('FRC skipped:', repr(error))"
]
},
{
"cell_type": "markdown",
"id": "533a651d",
"metadata": {},
"source": [
"## 3. Photon statistics\n",
"\n",
"Photons in each whole region. The cluster chains keep only clusters that fall in\n",
"the 1/2/3-photon energy windows; at this occupancy the rest is lost to pile-up\n",
"(`SiementStar_LowerLeft.ipynb`: 45 % of the photons are in single-photon\n",
"clusters)."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "47cb5a91",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:44.785013Z",
"iopub.status.busy": "2026-09-28T10:33:44.784889Z",
"iopub.status.idle": "2026-09-28T10:33:44.798000Z",
"shell.execute_reply": "2026-09-28T10:33:44.797767Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"region eta (1ph) CNN 1ph CNN <=3ph GridBased photons per frame: GridBased / CNN <=3ph\n",
"LowerLeft 1.4060e+09 1.4060e+09 1.6677e+09 3.8487e+09 192.4 / 83.4 = 2.31\n",
"LowerRight 2.7039e+09 2.7039e+09 4.4703e+09 1.3339e+10 667.0 / 223.5 = 2.98\n",
"(GridBased over 20,000,000 frames; the cluster chains used all 20 M)\n"
]
}
],
"source": [
"counts = {}\n",
"CLUSTER_FRAMES = 20e6 # the cluster chains used the whole run\n",
"print(f\"{'region':11s} {'eta (1ph)':>12s} {'CNN 1ph':>12s} {'CNN <=3ph':>12s} {'GridBased':>12s} \"\n",
" f\"photons per frame: GridBased / CNN <=3ph\")\n",
"for region in REGIONS:\n",
" c = {name: float(np.nansum(img)) for name, img in images[region]['roi'].items()\n",
" if img is not None and not name.startswith('native')}\n",
" counts[region] = c\n",
" cnn = c.get('cluster CNN <=3ph', np.nan)\n",
" print(f\"{region:11s} {c['eta interpolation']:12.4e} {c.get('cluster CNN 1ph', np.nan):12.4e} {cnn:12.4e} {c['GridBased 2-20 keV']:12.4e} \"\n",
" f\"{c['GridBased 2-20 keV'] / frames.sum():8.1f} / {cnn / CLUSTER_FRAMES:6.1f} = \"\n",
" f\"{(c['GridBased 2-20 keV'] / frames.sum()) / (cnn / CLUSTER_FRAMES):.2f}\")\n",
"print(f'(GridBased over {frames.sum():,.0f} frames; the cluster chains used all 20 M)')"
]
},
{
"cell_type": "markdown",
"id": "b2edee48",
"metadata": {},
"source": [
"## 4. Energy spectrum from the energy head"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "75e762b8",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:44.799203Z",
"iopub.status.busy": "2026-09-28T10:33:44.799120Z",
"iopub.status.idle": "2026-09-28T10:33:45.047119Z",
"shell.execute_reply": "2026-09-28T10:33:45.046919Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"15 keV line at 15.013 keV, core sigma 0.538 keV; below 13.5 keV 12.12 %, 13.5-16.5 keV 82.46 %, above 16.5 keV 5.41 %\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 825x462 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def gauss(x, a, mu, sig):\n",
" return a * np.exp(-0.5 * ((x - mu) / sig)**2)\n",
"\n",
"\n",
"total = spectrum.sum()\n",
"near = np.abs(spectrum_centres - 15) < 2\n",
"peak = spectrum_centres[near][np.argmax(spectrum[near])]\n",
"window = np.abs(spectrum_centres - peak) < 0.8\n",
"try:\n",
" (a, mu, sig), _ = curve_fit(gauss, spectrum_centres[window], spectrum[window],\n",
" p0=(spectrum[window].max(), peak, 0.3), maxfev=20000)\n",
"except Exception:\n",
" if spectrum[window].sum() > 0:\n",
" mu = np.average(spectrum_centres[window], weights=spectrum[window])\n",
" sig = np.sqrt(np.average((spectrum_centres[window] - mu)**2, weights=spectrum[window]))\n",
" else:\n",
" mu = sig = np.nan\n",
"fractions = dict(below=spectrum[spectrum_centres < 13.5].sum() / total,\n",
" line=spectrum[(spectrum_centres >= 13.5) & (spectrum_centres <= 16.5)].sum() / total,\n",
" above=spectrum[spectrum_centres > 16.5].sum() / total)\n",
"print(f'15 keV line at {mu:.3f} keV, core sigma {abs(sig):.3f} keV; below 13.5 keV {fractions[\"below\"]*100:.2f} %, '\n",
" f'13.5-16.5 keV {fractions[\"line\"]*100:.2f} %, above 16.5 keV {fractions[\"above\"]*100:.2f} %')\n",
"fig, ax = plt.subplots(figsize=(7.5, 4.2))\n",
"ax.semilogy(spectrum_centres, spectrum / total / np.diff(spectrum_centres)[0], lw=1)\n",
"ax.axvline(15, color='grey', lw=.6, ls=':')\n",
"ax.set_xlim(0, 30); ax.set_ylim(1e-6, 5)\n",
"ax.set_xlabel('reconstructed photon energy [keV]'); ax.set_ylabel('photons / keV (normalised)')\n",
"ax.set_title('Siemens star, both regions, GridBased 2-20 keV'); ax.grid(alpha=.3, which='both')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "42d1185c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T10:33:45.048244Z",
"iopub.status.busy": "2026-09-28T10:33:45.048159Z",
"iopub.status.idle": "2026-09-28T10:33:45.133257Z",
"shell.execute_reply": "2026-09-28T10:33:45.133003Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"written /home/xie_x1/MLXID/GridBased/Results/overnight_poly2to20keV_Moench040/report/siemens_star_poly2to20keV_summary.json\n"
]
}
],
"source": [
"summary = dict(parts=parts, frames=float(frames.sum()),\n",
" photons=float(sum(s['n_photons'] for s in summaries)),\n",
" model=summaries[0]['checkpoint'], epoch=summaries[0]['epoch'], crop=summaries[0]['crop'],\n",
" regions={k: v['roi'] for k, v in REGIONS.items()}, photon_counts=counts,\n",
" frc_cutoff=frc_results, line_keV=float(mu), line_sigma_keV=float(abs(sig)),\n",
" spectrum_fractions=fractions)\n",
"out = OVERNIGHT / 'report' / os.environ.get('GRID_STAR_SUMMARY', 'siemens_star_poly2to20keV_summary.json')\n",
"out.parent.mkdir(parents=True, exist_ok=True)\n",
"json.dump(summary, open(out, 'w'), indent=1)\n",
"regions_dir = GRID / f'SiemensStar_15keV_{POLY_TAG}_regions' # the two regions as their own arrays\n",
"regions_dir.mkdir(exist_ok=True)\n",
"for region in REGIONS:\n",
" np.save(regions_dir / f'{region}_binned_image_flattened.npy', images[region]['roi']['GridBased 2-20 keV'])\n",
" np.save(regions_dir / f'{region}_binned_image.npy', cut_grid(raw, REGIONS[region]['roi']))\n",
"json.dump(summary, open(regions_dir / 'summary.json', 'w'), indent=1)\n",
"print('written', out)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch_nightly",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,659 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "86bf6c9c",
"metadata": {},
"source": [
"# Siemens star, 15 keV, MOENCH040 — polychromatic 2–20 keV GridBased model\n",
"\n",
"SOLEIL 2504 beamtime, MOENCH040 @150 V, 15 keV, 20 M frames of the full chip\n",
"(2000 files). The two regions of `DataProcess/Configs.py` are reconstructed:\n",
"\n",
"| region | x [px] | y [px] |\n",
"|---|---|---|\n",
"| `SiemenStarLowerLeft` | 130–240 | 110–220 |\n",
"| `SiemenStarLowerRight` | 235–345 (the notebooks use 234–344) | 110–220 |\n",
"\n",
"Both share the same rows, so one crop of the frame, x 126–349 and y 106–224\n",
"(the regions plus the network's halo R = 4), is read and reconstructed in one\n",
"pass; its valid region is x 130–345, y 110–220. Reconstruction: the 2–20 keV\n",
"MOENCH040 model, FP16 with an FP32 head, masked column common mode (median over the pixels without charge) over the\n",
"118 rows of the crop, self-flattening of the sub-pixel phase with one Rosenblatt table per\n",
"local-occupancy class (tables from the first 3·10⁸ photons, every photon\n",
"remapped; one table for the whole crop left a 25 µm grid in the dim star of\n",
"LowerLeft, whose occupancy is ~8x below the rest). The run is processed as four parts of 500\n",
"files; this notebook sums the parts it finds.\n",
"\n",
"For comparison, the published cluster-based reconstructions of the same regions\n",
"(`SiementStar_LowerLeft/Right.ipynb`): eta interpolation (single-photon clusters)\n",
"and the 1/2/3-photon CNNs summed (`≤3ph`), all at 2.5 µm."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "5e4befe5",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:42.386960Z",
"iopub.status.busy": "2026-09-28T09:23:42.386752Z",
"iopub.status.idle": "2026-09-28T09:23:42.764863Z",
"shell.execute_reply": "2026-09-28T09:23:42.764621Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"parts found: 2 of 2 ['SiemensStar_15keV_part0', 'SiemensStar_15keV_part1']\n",
"model 260927_poly2to20keV_Moench040_c96_00 (epoch 182), 10,000,000 frames, 7.9688e+09 photons (796.9 per frame in the 23650 px valid region)\n",
" SiemensStar_15keV_part0 files [0, 499, 1] 5,000,000 frames occupancy 3.29 % (illuminated 5.58 %) 2434 frame/s sub-pixel rms 17.2 -> 1.2 %\n",
" SiemensStar_15keV_part1 files [500, 999, 1] 5,000,000 frames occupancy 3.45 % (illuminated 5.88 %) 1970 frame/s sub-pixel rms 17.3 -> 1.1 %\n"
]
}
],
"source": [
"import json\n",
"import os\n",
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"from scipy.optimize import curve_fit\n",
"\n",
"GRID = Path('/mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased')\n",
"PUBLISHED = Path('/home/xie_x1/MLXID/DeepLearning/InferenceResults')\n",
"ETA = Path('/home/xie_x1/MLXID/EtaInterpolation/Results')\n",
"OVERNIGHT = Path('/home/xie_x1/MLXID/GridBased/Results/overnight_poly2to20keV_Moench040')\n",
"POLY_TAG = os.environ.get('GRID_POLY_TAG', 'poly2to20')\n",
"RUNS = [r.strip() for r in os.environ.get(\n",
" 'GRID_STAR_RUNS', ','.join(f'SiemensStar_15keV_part{i}' for i in range(4))).split(',')]\n",
"CNN = {1: '1ph_260616_15keV_v260511_00', 2: '2ph_260622_15keV_v260610_00',\n",
" 3: '3ph_260623_15keV_v260611_00'}\n",
"B = 10 # 2.5 um bins per 25 um pixel\n",
"# region -> (x0, x1, y0, y1) in chip pixels: the whole region, the zoom of the\n",
"# published notebooks, and the 50 x 50 px FRC box (SiementStar_LowerLeft.ipynb's\n",
"# [1200:2100, 1400:2300][200:-200, 220:-180]; the same box around the right star)\n",
"REGIONS = {'LowerLeft': dict(roi=(130, 240, 110, 220), zoom=(140, 190, 170, 210), frc=(162, 212, 140, 190)),\n",
" 'LowerRight': dict(roi=(234, 344, 110, 220), zoom=(284, 324, 160, 200), frc=(263, 313, 140, 190))}\n",
"\n",
"plt.rcParams.update({'figure.dpi': 110, 'image.origin': 'lower', 'image.interpolation': 'nearest'})\n",
"\n",
"\n",
"def run_dir(run):\n",
" return GRID / (run + (f'_{POLY_TAG}' if POLY_TAG else ''))\n",
"\n",
"\n",
"parts = [r for r in RUNS if (run_dir(r) / 'binned_image_flattened.npy').exists()]\n",
"print(f'parts found: {len(parts)} of {len(RUNS)} {parts}')\n",
"summaries = [json.load(open(run_dir(r) / 'summary.json')) for r in parts]\n",
"frames = np.array([s['n_frames'] for s in summaries], float)\n",
"flattened = sum(np.load(run_dir(r) / 'binned_image_flattened.npy').astype(float) for r in parts)\n",
"raw = sum(np.load(run_dir(r) / 'binned_image.npy').astype(float) for r in parts)\n",
"analog = sum(np.load(run_dir(r) / 'analog_image.npy') * n for r, n in zip(parts, frames)) / frames.sum()\n",
"spectrum_centres = np.load(run_dir(parts[0]) / 'energy_spectrum.npy')[0]\n",
"spectrum = sum(np.load(run_dir(r) / 'energy_spectrum.npy')[1] for r in parts)\n",
"crop_x0, _, crop_y0, _ = summaries[0]['crop']\n",
"print(f\"model {Path(summaries[0]['checkpoint']).parent.parent.name} (epoch {summaries[0]['epoch']}), \"\n",
" f\"{frames.sum():,.0f} frames, {sum(s['n_photons'] for s in summaries):.4e} photons \"\n",
" f\"({sum(s['n_photons'] for s in summaries)/frames.sum():.1f} per frame in the \"\n",
" f\"{summaries[0]['valid_pixels']} px valid region)\")\n",
"for s in summaries:\n",
" print(f\" {s['run']:28s} files {s['signal_files']} {s['n_frames']:>10,d} frames \"\n",
" f\"occupancy {s['occupancy_valid_region']*100:.2f} % (illuminated {s['occupancy_exposed']*100:.2f} %) \"\n",
" f\"{s['frames_per_second']:.0f} frame/s sub-pixel rms {s['subpixel_nonuniformity_before']*100:.1f} -> \"\n",
" f\"{s['subpixel_nonuniformity_after']*100:.1f} %\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "54a0702c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:42.766071Z",
"iopub.status.busy": "2026-09-28T09:23:42.765944Z",
"iopub.status.idle": "2026-09-28T09:23:43.851086Z",
"shell.execute_reply": "2026-09-28T09:23:43.850546Z"
}
},
"outputs": [],
"source": [
"def cut_grid(image, box, factor=B):\n",
" # chip-pixel box -> the GridBased crop's binned image\n",
" x0, x1, y0, y1 = box\n",
" return image[(y0 - crop_y0) * factor:(y1 - crop_y0) * factor,\n",
" (x0 - crop_x0) * factor:(x1 - crop_x0) * factor]\n",
"\n",
"\n",
"def cut_chip(image, box, factor=B):\n",
" # chip-pixel box -> a full-chip super-frame (4000 x 4000 at 2.5 um)\n",
" x0, x1, y0, y1 = box\n",
" return image[y0 * factor:y1 * factor, x0 * factor:x1 * factor]\n",
"\n",
"\n",
"def published(region):\n",
" eta = np.load(ETA / f'SiemenStar{region}_fromClusters.npy', mmap_mode='r')\n",
" single = cnn = None\n",
" for n, experiment in CNN.items():\n",
" path = (PUBLISHED / f'SiemenStar{region}' / experiment / 'augX1'\n",
" / f'{n}Photon_ML_superFrame_etaInterpolated.npy')\n",
" if path.exists():\n",
" image = np.asarray(np.load(path, mmap_mode='r'), float)\n",
" single = image if n == 1 else single\n",
" cnn = image if cnn is None else cnn + image\n",
" return eta, single, cnn\n",
"\n",
"\n",
"images = {}\n",
"for region, boxes in REGIONS.items():\n",
" eta, single, cnn = published(region)\n",
" images[region] = {}\n",
" for key, box in boxes.items():\n",
" images[region][key] = {\n",
" 'native readout (25 um)': cut_grid(analog, box, factor=1),\n",
" 'eta interpolation': np.asarray(cut_chip(eta, box), float),\n",
" 'cluster CNN 1ph': cut_chip(single, box) if single is not None else None,\n",
" 'cluster CNN <=3ph': cut_chip(cnn, box) if cnn is not None else None,\n",
" 'GridBased 2-20 keV': cut_grid(flattened, box),\n",
" }\n",
" images[region]['roi_raw_grid'] = cut_grid(raw, boxes['roi'])"
]
},
{
"cell_type": "markdown",
"id": "43f9cd97",
"metadata": {},
"source": [
"## 1. The two regions"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "29d6aaff",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:43.852994Z",
"iopub.status.busy": "2026-09-28T09:23:43.852857Z",
"iopub.status.idle": "2026-09-28T09:23:45.025073Z",
"shell.execute_reply": "2026-09-28T09:23:45.024813Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 2530x506 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAACc8AAAIPCAYAAACvu7RHAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOydd3hU1dbG32mZmilJJp0UAoSOVBGlgwVBULEXsF97uWKXol5FxSvqxV4QC1gQC2JFQFQQRZRe0/tM2mSSmcmU/f3Bt7Yzk4QEBEFdv+fJA3PmzDn71LX3Wu9aWyGEEGAYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmGYfxDKo90AhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhvmzYfEcwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAM84+DxXMMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzDMPw4WzzEMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzD/OFg8xzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMw/zjYPEcwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAM84+DxXMMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzDMPw4WzzEMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzD/OFg8xzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMw/zjYPEcwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAM84+DxXMMwzAMwzAMwzAMwxwxpk+fDoVCcbSbwTCHjYULF0KhUGD16tWHvI1Ro0YhKyvrsLXpz+BwHHdbzJ49GwqFAgUFBYd920eDgoICKBQKzJ49+2g35ZiBbQHDMAzDMAzDMAzDMMcqLJ5jGIZhGIZhGIZhGOagqKqqwl133YU+ffrAbDYjNjYWnTt3xplnnolXXnnlaDfvb8n8+fOxcOHCo7b/rKwsdOnS5ajt/0iiUCgi/rRaLbKzs3HVVVehpKTkaDcPdXV1mD179hERrTH/THbt2oUZM2Zg3LhxiI+Ph0KhwJVXXtnm+qNGjWrxnNDf8uXL/8SW/86QIUOgVqvbfUYHDBiAmJgYVFVV/UktYxiGYRiGYRiGYRjmr4b6aDeAYRiGYRiGYRiGYZi/DkVFRRgyZAicTiemTp2Kq666CjExMcjLy8NXX32F+fPn44orrpDrv/TSS3j++eePYov/HsyfPx9ZWVmYPn360W7K35JevXrhrrvuAgC4XC58++23eOWVV7BixQps3rwZ8fHxct1LLrkE559/PmJiYv6UttXV1WHOnDkA9ouYmGOfzMxMeDweqNXHput13bp1mDdvHrKzszF48GB88cUX7f4mISEBTz75ZIvl/fv3PxJNbJerrroKV199NRYuXIj77ruv1XU2bdqETZs24ayzzkJiYuKf3EKGYRiGYRiGYRiGYf4qHJseHIZhGIZhGIZhGIZhjknmzZuHyspKzJ8/HzfffHOL76OrAGk0Gmg0mj+recwhEAqF4PP5oNfrj3ZT/jR8Ph9UKpUUNyUnJ+Piiy+W31933XVITk7GU089hYULF+Lf//63/E6lUkGlUv3pbWYOD0IIeDweGAyGI7YPhUIBnU53xLb/R5k0aRJqampgs9lQUFCA7Ozsdn9jNBojnpGjzQUXXIDbbrsNr776Ku69995Wp4R9+eWXAeCAVfUYhmEYhmEYhmEYhmF42laGYRiGYRiGYRiGYTrM7t27AQBjx45t9fv09PSIz9OnT29V1FBZWYkbb7wRWVlZiImJQVJSEi6++GIUFBRErLdw4UIoFAqsXLkSDz/8MDp37gydTod+/frhs88+AwBs374dEydOhMVigdVqxfTp0+F2u//wPletWoX58+ejW7du0Gq16Ny5c6uVl9avX49JkyYhNTUVWq0WKSkpGDNmDD755JM2zyNRW1uLGTNmoGvXrtDr9bDZbOjTp48UaxUUFEChUKCwsBBr1qyJmC6R2r1hwwZcfvnlyM3NhdFohNFoxJAhQ/D666+32N/s2bOhUCiwfft23HHHHcjMzERMTAzeeeeddtvaEbxeL+bMmYPu3btDp9MhLi4OkyZNws8//xyx3vjx42G32yGEkMt++OEHKBQKxMbGwu/3y+WbN2+GQqHAY489FrGNVatW4bTTToPNZoNWq0WPHj3w6KOPIhgMRqw3atQoZGVlobCwEOeffz4SEhKg0+nane5x3LhxAIA9e/ZELKf7I3oa1fLyclxyySWIj4+H0WjE8OHD8e2337b5DABARUWF/I1er8fIkSOxcePGiH2RsGnOnDny2mdlZR2w7a1RWloKhUIRIQQEgKuvvhoKhQLXXHNNxPLbbrsNCoUC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 truncated
"text/plain": [
"<Figure size 2530x506 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def limits(img):\n",
" # grey levels from the central 60 % of the image: in LowerLeft the unshadowed\n",
" # strip at x > 222 is several times brighter than the star (and brighter\n",
" # still for GridBased, which does not lose its photons to pile-up)\n",
" ny, nx = img.shape\n",
" centre = img[int(.2 * ny):int(.8 * ny), int(.2 * nx):int(.8 * nx)]\n",
" centre = centre[np.isfinite(centre)]\n",
" return np.percentile(centre, 0.5), np.percentile(centre, 99.5)\n",
"\n",
"\n",
"def show(region, key, title):\n",
" box = REGIONS[region][key]\n",
" panels = [(name, img) for name, img in images[region][key].items() if img is not None]\n",
" fig, axes = plt.subplots(1, len(panels), figsize=(4.6 * len(panels), 4.6))\n",
" for ax, (name, img) in zip(axes, panels):\n",
" low, high = limits(img)\n",
" ax.imshow(img, cmap='gray', extent=[box[0], box[1], box[2], box[3]], vmin=low, vmax=high)\n",
" ax.set_title(name, fontsize=10)\n",
" ax.set_xlabel('x [pixel, 25 um]'); ax.set_ylabel('y [pixel, 25 um]')\n",
" fig.suptitle(f'Siemens star {region}, {title}, 15 keV')\n",
" fig.tight_layout()\n",
"\n",
"\n",
"for region in REGIONS:\n",
" show(region, 'roi', 'whole region')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "b4daba25",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:45.028865Z",
"iopub.status.busy": "2026-09-28T09:23:45.028776Z",
"iopub.status.idle": "2026-09-28T09:23:46.061555Z",
"shell.execute_reply": "2026-09-28T09:23:46.061121Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAACdcAAAHtCAYAAADxptVPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOzdd3hTZf8/8Hd20jRNmu492CAoKIgoFhAcCIKKiqICKu6J4vwqoD7ugXuLgDw4cCOiiICoqPi4xYFsWrpn2rRNmvv3B7/P/TTdRYYPvl/XxXXR5OTk5OTk3Otzf26DUkqBiIiIiIiIiIiIiIiIiIiIiIiIiDTj/j4AIiIiIiIiIiIiIiIiIiIiIiIior8bBtcRERERERERERERERERERERERERNcHgOiIiIiIiIiIiIiIiIiIiIiIiIqImGFxHRERERERERERERERERERERERE1ASD64iIiIiIiIiIiIiIiIiIiIiIiIiaYHAdERERERERERERERERERERERERURMMriMiIiIiIiIiIiIiIiIiIiIiIiJqgsF1RERERERERERERERERERERERERE0wuI6IiIiIiIiIiIiIiIiIiIiIiIioCQbXERERERERERERERERERERERERETXB4DoiIiIiIiKiRqZMmQKDwbC/D4NonwqFQpg1axays7NhNpv5G9jLampqcOWVVyI9PR0mkwmZmZm7va9hw4b9pdf/3RgMBkyZMqVD265atQoGgwEvvfSSfmzLli0wGAyYNWtWp997f9z//8rx/h39nT5PZ66lA0FLvwciIiIiIiIi+usYXEdEREREREQHvMLCQtx4443o27cvoqKi4HK5kJ2djZNPPhkvvPDC/j68A9KcOXP26wB/ZmYmunbtut/ef28yGAwYOXLkHt3nvHnzMHv2bAwfPhwvvPACFixYAACYNWsW3n777T36XgTce++9eOyxx3DGGWfgpZdewpw5c9rcfn//nujAt2rVKsyaNQvl5eX7+1AOGPfccw/OOOMMdOvWDUajEWazudVtX3rpJRgMhhb/TZgwYR8e9X89+eSTMBgMuPvuu9vc7tFHH4XBYMDDDz+8j46MiIiIiIiIaN9qvUVPREREREREdADYtm0bBg0ahOLiYkyYMAHTpk2D1WrFpk2bsHz5csyZMwfnn3++3v65557D008/vR+P+MAwZ84c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gw/i448/xhdffIGIiAgAQFVVFY488kisX78ekyZNwpAhQ7Bx40Y8+eSTWLZsGT7//HP07t077D3ff/99PPnkk7jkkktwwQUXYNGiRZgxYwbsdjvmzp2LlJQU3Hrrrdi5cyceeughjB8/Hj///DOMxrbnp+bm5mLgwIEoLCzEtGnT0K9fP/zwww947rnnsGzZMqxbtw4JCQk45ZRT0LVr12a/j379+rW43478ngCguroaQ4cOxaGHHoo77rgDBQUFePjhh3H88cdj06ZNcLlcetupU6di/vz5GDduHCZNmgQAePPNN3HyySfjqaeewsUXX9zmZxVLlizBQw89hEsvvRTnn38+Pv74Y8yZMwc//vgjli9f3u45a8t3332HxYsXY+rUqTj77LPx1Vdf4aWXXsLXX3+NdevW6WugoxYuXIizzz4bRx55JGbOnInIyEjk5ubik08+wYYNG9CzZ8+w7Y877jh4PB7cdNNNqKmpwZw5czB27Fhs2LAh7L47a9YszJ49G8OHD8fMmTPhcDjw4YcfYtq0afjzzz9xzz336G2ffvppXHLJJejevTtmzpwJi8WChQsXthmc1pqO3OuBXYFP06ZNQ2ZmJmbMmIHIyEgsXrwYl19+OX744Qc8++yzAIBbbrkFXq8Xb731Fh5++GH9m5TrsqP3k8Y6en109LcDdO6e3ZLPPvsM48aNQ0pKCpYuXaqD69sr1/alcePG6UDiXr164YorrsDFF1+824HNjz32GK6++mqcdtppuq7Rme/zqKOOQq9evbB48WI89thjcLvdet81NTVYtGgRsrKyWp3AQERERERERHRAUEREREREREQHsM2bNyuPx6MAqISEBHX66aerBx98UH355ZcqFAo1237y5MmqaXN5/PjxKjo6Wm3cuDHs8U2bNqnIyEg1ZcoU/djcuXMVANWvXz9VW1urH//uu+8UAGUwGNSrr74atp9x48Ypi8Wiqqqq9uh7+nw+FRMTo4444gj92COPPKIAqC+//LLN89aS8vJyZTAY1MUXX9zuthkZGSonJ6fF53w+X7PHGhoa1NFHH608Ho+qr6/Xj8+cOVMBUEcffbQKBAIdPtaMjAzVpUuXNrd54YUXFAB1ySWXhD3++++/K5vNprp166YaGhqUUkqtW7dOAVD33Xef3i4nJ0eNGTNGRUdHq9tuu00/fuKJJ6ro6Gj92p07dyq73a7Gjx/f7Jq7//77FQC1atWqsP0CUDfffHOLxw1AHXPMMW1+tjlz5igA6s033wx7vL6+XvXv319lZWWFPZ6Tk6MyMjJafK/Jkye3+V67o7KyUh188MHK6XSqdevW6ceHDh2qDAaDWrt2bdj2s2fPVgDUHXfcoR+79dZbFQB1773Line truncated
"text/plain": [
"<Figure size 2530x506 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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aKxzS9aePXsCAPbt2xdwnNrHyduopqWl4dZbb0VYWBgsFgs6deqE7777rtY+AFQFb9E1JpMJXbp0wS+//BLwLAqUmDZtmvz2ZxrUUdt2hDXd88cff0S/fv3gdDphNBqRlJSEp59+GpWVldXue+zYMdx5552oV68e9Ho9YmJicNdddyEtLS3gvI0bN0KlUmHBggV455130LRpUxiNRjRu3BiLFi2S9xoyZIisx+TkZKSnpwf9jn/99ReGDBmCiIgI2W8fffRRFBYWynPomwghsHDhQlkHtW2NTOUGEHB+Td917969GDhwIGw2G0JCQtC3b18cOHCg2nmVlZV48cUX0aJFC5hMJoSGhqJnz5747rvvgnpPZV2+9dZbSEpKgtFoREJCAp566il4vd6A86kf1IRKpao1q+Vnn32G1q1bw2QyITo6GuPGjUNJSclplY8QQuC1115Dq1atZP0kJiZi+PDhyMjIqHaPuvqH8r5z585Fu3bt5BjYsWPHWrfYffXVV9GkSRO5RfPTTz9drb5OBY2je/fuxeTJkxEfHy/HoA8//LDGaz744AO0b98eISEhsFgs6NChAz766KOAcxISEuS4rWxjyjosKirCE088IcvvdDqRnJyMHTt21FreYNoHEFzfIfLz8zF+/HjUr18fBoMBERERGDp0aLXxsjZ++OEHuFwuXH755Thy5AiAquD7u+66C/Xr14fRaITL5UKbNm3wwgsvBHXPmjAYDDJw7nQpKioKkEX/lLfeegtarRY333wzKioq5DOC+Z5jxoyBEALz58+v8d7z5s0DUJWljmEYhmEYhmEYhmEuJngJGMMwDMMwDMMwDHPRkZiYCACYP38+XnjhhTPKcHLkyBF07NgRxcXFuOOOO9C4cWMcO3YMb731Fr755hv8/PPPiIuLC7jmf//7HyoqKjB27FhoNBrMnj0bAwcOxGeffYY77rgDN998M/r374+tW7di4cKFMBgMmDNnzj965qRJk1BYWIjRo0cjJCQEixYtwvjx4xEVFYUhQ4YAqAqU6dmzJ9xuN+677z5ERUUhKysLv/76K7Zu3Vrn9mk333wzNmzYgLvuugutWrVCRUUFDhw4gPXr1wMA3G43Fi9ejHHjxsHlcuGJJ56Q17rdbgDAsmXLsGvXLgwePBjx8fEoKCjAJ598glGjRiErKwuPPvpotecOHz4cOp0O999/PywWC5o0aXIaX7BmfD4f+vTpgw0bNqBfv364//77kZ6ejrfeegvXXHMN1qxZg27dugGoCkxbt24dfv/9d7Rq1QoAsH79eqjVahQXF2Pbtm3o1KmTPA4EBmy+9957uPPOO9GqVSs8/vjjsNvt2LJlC/73v//ht99+qxaUUlxcjE6dOqFdu3aYNm0aioqKEBIScsr32b9/PwAEbNlaGwUFBejUqRNSUlJw++23o02bNtizZw/69OmDhg0b1nhNSUkJOnXqhDZt2uDpp59GRkYGXnnlFVx//fVISUmB1WpF586d8corr2DcuHG44YYbMGjQIACos+y1sXjx4mrH/vzzT0yfPh2RkZHy2FdffYUBAwbAZrPhvvvuQ0REBFavXo3Jkyfjhx9+wKpVq6BWV60LPXbsGNq2bYvMzEyMGTMGLVq0wB9//IG5c+fiq6++wk8//YSIiIiAZ7755pvIysrCmDFjEBISgnfffRcjR46ETqfD//73P3Tq1AnPPPMM/vrrL7zxxhsYOXIkvv766zrf7/fff0fnzp3h8/kwduxYNGjQAN9//z1eeuklrFu3Dj/88APMZjPuvvtu9OzZE7feeis6deqEu+66CwDQsWPHGu+blJSExYsXVzv/ZI4dO4bOnTtjwIABeOGFF7Bv3z689tprGDBgAHbu3CnrzOv1ok+fPti0aROGDh2Ke+65B6WlpXj//ffRvXt3LF++HP369avzfQHg9ddfx9GjR3HPPffA6XRi2bJlmDJlClJSUmoNBgyWlStX4uWXX8a9996LO+64A+vWrcOsWbOwY8cOrF27Vr5PsDz33HP4v//7P/Tt2xd33nkn9Ho9Dh8+jK+++grp6ekB7SSY/kGMHj0aixYtwsCBAzF8+HAAwOeff44bbrgBb731Fu655x557uOPP44XXngBV155JZ5//nmUlZXhvffew4oVK067fkaOHAmVSoUHH3wQarUab775JoYPH44GDRqgQ4cO8rzJkyfj6aefRosWLfDkk08CAN5//30MHToUKSkpmDRpEoCq7blffvllbN68OaCvUrssLCzENddcg/3792PkyJFo2bIl8vLyMHfuXFx11VXYvHkzWrduHVDGYNtHsH0HqAr4uvrqq7F7924MHz4cHTt2xIEDB/Dmm2/iq6++wpYtW065xenSpUsxYsQIdOjQAcuWLYPdbofX60WvXr1w5MgRjB07FpdddhmKiorw999/49tvv8Vjjz122t/nn9CyZUsUFhZCrVajVatWmDhxIm6++eYzupcQAv/73//wwgsvYNy4cXjppZegUqlO63sOHjwYDz74IBYsWICpU6cG9L3Dhw9j7dq16NKlCxo1anRW3p9hGIZhGIZhGIZh/jULine truncated
"text/plain": [
"<Figure size 2530x506 with 5 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"for region in REGIONS:\n",
" show(region, 'zoom', 'zoom of the published notebooks')"
]
},
{
"cell_type": "markdown",
"id": "cba943fe",
"metadata": {},
"source": [
"### GridBased 2–20 keV at full size\n",
"\n",
"The flattened reconstruction of each region at 2.5 µm, and the same photons\n",
"before the sub-pixel flattening (the 25 µm pixel grid is visible there)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "76f70e13",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:46.065314Z",
"iopub.status.busy": "2026-09-28T09:23:46.065072Z",
"iopub.status.idle": "2026-09-28T09:23:46.886751Z",
"shell.execute_reply": "2026-09-28T09:23:46.886518Z"
}
},
"outputs": [
{
"data": {
"image/png": "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P//zP/HsZz8bf/jDHyRR/MUvfoFXvvKVeNaznoUrr7wSJpMJu3fvxi233CLbajQaOOecc7B//3686U1vwuTkJP70pz/h05/+NO6///4+H9c3v/nN+MpXvoKXvexlOOecc3DHHXfgOc95DhqNxkM+h0QikQCwlng9mtv44x//CJvNhuOOO67vu9u3b4fVasWdd975kP5Gs9nEy1/+cvzyl7/Ej3/8YzzrWc8CsHYvfOELX8CrX/1qvPWtb0UymcTnPvc53HrrrbjrrruEwfS3f/u3+MhHPoI77rijL4G6/fbbsXfvXnzkIx95OId+RNizZw/OOOMM2Gw2vPvd74bb7ca1116L5z//+fje976HCy+8EAaDAc94xjNw0003ye/Nzs5iaWkJRqMRN910EzZs2AAAuPnmmxEMBuWezGazOP3009FsNvGmN70J8Xgct9xyCy677DIkEgl89rOf7dufr33ta6hWq3jTm94Et9t9yOdhPRzJ+f7sZz+Lb37zm7jhhhvwmc98BuFwGJOTkzj33HPxute9Dqeffjre9KY3AQAGBgYe0rm8/PLLceyxx+Lf/u3f8MEPfhBbtmw57Da+/OUvw2Qy4e/+7u8QCARw22234dOf/jSWlpYk0X7zm9+MxcVF/OpXvxIvawCIRCK47rrrDrnPD+XZBYDbbrsN3/ve9/DWt74Vr3/96/GlL30JF110EbZv347Nmzc/5G0+FuuBgoKCgoLCExkqf1jD4x0vqPxB5Q9PpvyB6PV6eN7znocTTjgBn/jEJ/Cb3/wGV199NfL5PK699lr53qc+9Sm8733vw0te8hK8+tWvRqVSwec//3mcccYZuPPOOzExMXHYnOTmm2/Gc57zHAwPD+NDH/oQer0evvCFL+Css87CTTfddFAzb7216ZZbbsFzn/tcHHvssbj88sths9lwww034MILL8R3vvMdvOQlL1n3GJm/fPKTn0QikcBnPvMZAGuNxvVQKpVwzTXX4BWveAVe+9rXotFo4Otf/zpe/OIX46c//SnOO+88ADhkTjQ5OYm3ve1tfedB//ce6nG8+93vht1uxwc+8AFks1l86lOfwt/+7d8eUSPwSHKtlZUVnHXWWeh0Onjve9+LYDCIr33ta/j5z3/+oNtXUHhSQ1NQUFA4AMFgUDv++OMP+rxSqWiZTEb+KxaL8rNrr71WA6CddtppWrvd7vu9G2+8UQOg3XjjjfLZiSeeqIXDYS2Xy8ln9957r2YymbQDl6axsTENwEH/nXzyyVoikej7bqPR0DqdTt9n+Xxei0aj2hve8Ab57F3vepfm9XoP+q4eV155peZwOLR777237/N/+7d/0wBoN998s6ZpmrZz504NgPa6172u73sf+MALine truncated
"text/plain": [
"<Figure size 1650x814 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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dx18Ph8+97nPHdXR+OMf/4i3vvWtWLhwIb74xS8in8/js5/9rJCqB6NUKmFmZgYAUCgUsGPHDtxyyy044YQTsHz5cnnf/v378R//8R945zvfiYULFyKZTOJf//Vf8aY3vQlbt26VQPGhhx7CpZdeis2bN+Pmm2+GyWRCT08PnnjiCfmsfD6Ps846C/39/bjqqquwYMECvPDCC7j11lvR3d2N++67T9579dVX46677sI73/lOnHXWWXjuuefw5je/Gfl8/mWPITE+Pg5gLvB6LT/j+eefF2dZjxNOOAFWqxXbtm17Wd9RKBTw7ne/Gw8//DAeeOABbN68GcDcXPj+97+PD3zgA/jwhz+MyclJfPe738WWLVuwfft2Uf28733vw0033YTnnnuuJoB69tln0dfXh5tuuumV3Poxobe3Fxs2bIDNZsPHP/5xOJ1O3HnnnXjLW96CX/7yl7j44othMBhw+umn47HHHpO/GxgYwOjoKIxGIx577DHMnz8fAPD444/D7/fLnJyZmcEpp5yCQqGAq666Ck1NTXjiiSfwqU99CuPj44c4p//2b/+GTCaDq666Ck6n84jrAQASiQTi8TgWLFjwkvd5xx134Oqrr8Ypp5yC6667DtPT0/jABz6A1tbWw77/F7/4BSKRCK655hrU1dXh29/+Nt7+9rdjaGgIfr8f73jHO9Dd3Y177rkH3/rWt2R+vZx+x0uXLsVPf/pTXH/99fB4PPjc5z531M84lrXd0NCA2267Df/v//0/vP3tb8c73vEOAMCCBQtQX19/1Gu+55578Fd/9Vc466yz8MUvfhHVahU/+tGPsHnzZjz22GOH9DJ/73vfi3A4jC9+8Yvo6+vDd7/7XVgsFvzsZz+T9xzrZ+7Zswdnn3023G43Pv/5z8NqteKOO+6A0+k85vFUUFBQUFB4I6Hihzmo+OHYoOIHFT+8kvhBj5/97GeIx+P48Ic/DIPBgH/+53/GOeecg2effRbLli0DcGxjebSYRNM0vOMd78Dvfvc7fPCDH8TJJ5+MJ598El/84hexZ88e/OIXv6i5pn379uGSSy7BlVdeiQ996ENob2+Hpmm45JJL8OCDD+LKK6/EypUr0d/fj9tuuw3btm3Dn/70pyO28rr66qsxb9483HjjjbjqqqtwxhlnHDU+uPfeexGNRnHllVciHA5j586d+OEPf4gXX3wRTz75JAAc9Tl8+9vfxt/+7d/WjAO/7+Xex8DAAC688EJcfvnleN/73od77rkHH//4x7Fs2TKcc845L/l8jyXW+tznPoebb74Z5557Li666CL09fXhggsugN/vP+I8VVA4rqApKCgoHAZ+v19bvXr1Ia+n02ktEonIv0QiIb+78847NQDLine truncated
"text/plain": [
"<Figure size 1650x814 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"for region, boxes in REGIONS.items():\n",
" box = boxes['roi']\n",
" fig, axes = plt.subplots(1, 2, figsize=(15, 7.4))\n",
" for ax, img, name in ((axes[0], images[region]['roi']['GridBased 2-20 keV'], 'flattened'),\n",
" (axes[1], images[region]['roi_raw_grid'], 'before flattening')):\n",
" low, high = limits(img)\n",
" ax.imshow(img, cmap='gray', extent=[box[0], box[1], box[2], box[3]], vmin=low, vmax=high)\n",
" ax.set_title(f'GridBased 2-20 keV, {region}, {name}', fontsize=11)\n",
" ax.set_xlabel('x [pixel, 25 um]'); ax.set_ylabel('y [pixel, 25 um]')\n",
" fig.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "318433ac",
"metadata": {},
"source": [
"## 2. Profiles and FRC\n",
"\n",
"A row through the zoom (normalised to its mean), and the one-image Fourier ring\n",
"correlation as `SiementStar_LowerLeft.ipynb` computes it (`frc.one_frc` and\n",
"`frc.frc_res` on the 50 × 50 px box x 162–212, y 140–190, no window; for\n",
"LowerRight the same box around its star). The cutoffs are in that notebook's\n",
"units, so they compare with its ML 2.041 / eta 2.053 for LowerLeft."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "6162c863",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:46.891035Z",
"iopub.status.busy": "2026-09-28T09:23:46.890941Z",
"iopub.status.idle": "2026-09-28T09:23:47.101421Z",
"shell.execute_reply": "2026-09-28T09:23:47.101191Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABfYAAAHCCAYAAACt2ZiFAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOzdd3gU5fbA8e9sT28EQgsggqCCiApYLt0uWFFUQPyBiqJw5Vqwgyh2bNfeQAT1YgHsAgJ6vSqiogIivZc00rfOvL8/ZndJSNtAQjZwPs/DQ3Z2yjszu8nMmfOeV1NKKYQQQgghhBBCCCGEEEII0ShYGroBQgghhBBCCCGEEEIIIYSInAT2hRBCCCGEEEIIIYQQQohGRAL7QgghhBBCCCGEEEIIIUQjIoF9IYQQQgghhBBCCCGEEKIRkcC+EEIIIYQQQgghhBBCCNGISGBfCCGEEEIIIYQQQgghhGhEJLAvhBBCCCGEEEIIIYQQQjQiEtgXQgghhBBCCCGEEEIIIRoRCewLIYQQQgghhBBCCCGEEI2IBPaFEEIIIYQQQgghhBBCiEZEAvtCCNFIbdq0iYsuuoj09HQ0TWPkyJEN3SQhhBBCCCGOWH379qVt27YHvPzmzZvRNI1JkybVWZuEEEIcviSwL4SICkuWLEHTNB566KGGbkqdmz59Opqm8c4779TpekeOHMnSpUu58847mTlzJjfccAMrVqxg0qRJbN68uU63JSL3999/c/vttzNw4EDS0tLQNI3Ro0dXOX8gEODxxx/n+OOPx+VykZKSwvnnn8/y5curXOa1117jhBNOICYmhvT0dK666iq2bNlSH7sjhBBCCFGlI+Eavuy/hIQETjnlFJ577jl0XW/oJrJkyRImTZpEfn5+QzfliLVs2TLGjx9P7969SUxMrPH74PV6ef755znppJNISUkhKSmJrl278sgjj1BcXFxh/kAgwGOPPcYxxxyD0+mkRYsW3HjjjeTm5tbnbgkhGgkJ7AshRCPk9Xr57rvvGD58OLfddhvDhg3j1FNPZcWKFUyePFkC+w3ohx9+4Mknn2Tjxo2ccsop1c6rlOLiiy/mzjvvJD09nSeeeIKJEyeyZs0azjjjDL777rsKy9x3331cf/31JCQk8MwzzzB+/HgWLFjAqaeeyvbt2+trt4QQQgghjkg33ngjM2fO5O233+a+++6jtLSU8ePHM3bs2Arzfv311/z999+HrG1Llixh8uTJEthvQJ9//jn//ve/yc7Opnv37jXOP3ToUMaNG0dmZiYPP/wwU6dOpU2bNtx9992ce+65Fea/9tprmThxIh07duTf//431157LW+//Ta9e/emqKioPnZJCNGI2Bq6AUIIcbgqKSkhLi6uXta9Z88elFKkpqbWy/qrU5/7dTgYNGgQeXl5pKSksHnzZtq1a1flvPPnz+fTTz/l7LPP5osvvkDTNABuuOEGOnXqxHXXXcdff/0Vnr527VoeeeQRunfvzpIlS7DZzD/j559/PieffDJ33303b7/9dv3vpBBCCCHEYWr/a93TTjuNYcOGhV/fdNNNdO7cmddee40pU6aQnp4efs/hcBzSttY3pRRut5vY2NiGbkrUuvHGG7ntttuIj49nyZIl9OvXr8p5169fz9y5c7nkkkv48MMPw9PHjh3LoEGD+PTTT1m1ahXHHXccAN988w3vvPMOgwcPZt68eeH5e/TowUUXXcQTTzzBgw8+WH87J4SIepKxL4RodNasWcPQoUNp1qwZTqeTo446ittuu43CwsLwPDt27EDTNP71r3+VW/b6669H0zRuuOGGctMnTJiApmns2bMnPM3n8/H444/TtWtXYmJiSExMZODAgXz77bflli1bC/PDDz+kR48exMbGcv7559d63zZs2MDIkSNp0aIFDoeDVq1acdNNN5GTkxOep2/fvrRp0waAyZMnh7sGt23blmuvvRaAfv36haeXrb2/detW1qxZg9/vr7Etoa7V06dP55VXXqFr1664XK5y2UmzZs2iZ8+exMfHExcXR69evXjvvffKrefhhx9G0zT+/PPP8DS/3098fDyapvHDDz+EpyulaNKkCeedd161bTv//POJiYlh7969Fd7buXMnNpuNoUOH1riP9SEtLY2UlJSI5v3mm28As6xSKHgPkJyczIUXXsjff//N999/H54+e/ZsdF1n3Lhx4aA+wIknnki/fv344IMPcLvdNW439LlYvHgxp59+OnFxcTRp0oRrr72W7Ozs8Hy6rtOnTx9cLhe//vprhbZbrVYuuuiiiPZVCCGEEEe2w+UaPj4+np49e2IYBhs2bCj3XlU19r/88kt69uxJTEwMTZs25brrriMvL6/acbK++OILevXqFV5mzJgxlJaWltvW5MmTAWjXrl342r9sff4NGzawZs2aavcnJFR6aOHChTzyyCN07NgRp9PJ448/DoBhGDz33HPhcpCJiYn079+fBQsWlFvPddddh81mK9eLYOfOneH27dq1Kzw9Pz8fq9XKTTfdVG3bjj/+eFq2bFlp+aOffvoJTdOYOHFiRPtZ15o1a0Z8fHxE8xYUFADQsmXLCu+1atUKoNxDlFDCzoQJE8rNe+GFF3L00UdHlNBT9nP+wQcf0L17d2JiYmjRogW33norJSUl4XmLi4vp1KkTaWlpbNu2rdx6ZsyYgaZp/POf/4xoX4UQh4Zk7AshGpUVK1bQu3dvdF3nxhtv5KijjuK///0vTz31FAsXLuR///sfsbGxtGzZkk6dOrFw4cJyyy9atAiLxVLp9OOPP55mzZoBZi3D8847j6VLl3LllVeGL6Tfeecd+vfvz9y5c7ngggvKrWPevHk888wzjBkzhuuuuw6lVK33rW/fvsTGxvJ///d/tGnThnXr1vHSSy+xaNEili1bRlJSEvfccw8XXXQRt956KxdffDGXXHIJAK1bt2b27Nm8+uqr3H333XTu3BmA9u3bh7cxYsQIli5dyqZNmyIe2OvZZ59lz549XH/99bRs2ZKEhAQA7r//fqZMmULXrl257777AHjnnXe48sor2bhxI3fffTcAAwcO5N5772XhwoV06dIFgB9//JGSkpLwuTj11FPDxyA3N5cBAwZU26YxY8bw+eefM3PmTMaNG1fuvbfeegtd17nuuutq3LeCgoKIHnIA2O12kpKSIpo3Ul6vF6DSLKjQtB9//JEzzjgDMG9cwMwc299pp53GokWL+PPPP+nRo0eN2/7tt9/44IMPuPbaaxk2bBg//fQT06dPZ9myZfz888/ExsZitVp599136datG5dffjm//voriYmJZGVlcfXVV9O6dWveeuutA95/IYQQQhwZDrdr+FBAPy0trcZ558+fz8UXX0zz5s2ZOHEiKSkpzJs3j3POOafKZb744gv+/e9/c8MNNzBy5EgWLVrEK6+8AsDLL78MwD333ENqaioff/wxTz/9NE2aNAGga9eu4fUMGDCALVu21Oq+5Pbbb8ftdnPNNdeQnp5O69atATMRZebMmZxxxhk88sgjFBUV8frrr3P22Wfz9ttvh3s1DBw4kNdff53Fixdz8cUXA+Z5CiWxLFy4kOHDhwOwePFiDMOo8dr/hhtuYNy4cXz22WcMHjy43Huvv/56jWNahezduzfisRFcLlfEAftIHXfccbRo0YLXX3+drl27MmDAAJRSfPXVV7z11luMGjWqXG/fn376CYvFQq9evSqs69RTT2XmzJlkZWXRtGnTGrf96aefMm3aNG666SZGjRrFwoULeeaZZ/jjjz9YsGABFouF+Ph4/vOf/9CzZ0+uuOIKvv32W2w2G3/99Rdjx47l5JNPDj/oEUJECSWEEFFg8eLFClBTpkypdr5//OMfStM09cMPP5SbPnny5ArLjx07Vmmapvbs2aOUUmrTpk0KUMOHD1eA2rhxo1JKqaysLKVpmho/fnx42WeeeUYB6qOPPiq3HZ/Pp0488UTVrl278LTQem02m1q1alWFNr/11lsKUDNnzqx237p166batWuncnNzy03/8ccflcViUZMmTaqwzQceeKDSbS1evLjSbfTp00cBatOmTdW2Ral95yQlJSV8DEPWrl2rLBaL6t69uyotLQ1PLy4uVscff7yyWq3hbQQCAZWUlKTOO++88HwPPPCASklJURdccIHq3bt3ePoTTzyhAPXbb79V2zZd11VmZqbq0qVLuemGYah27dqp9u3bK8MwatzH0PGI5F+fPn1qXN/+Qudp1KhRlb7/3HPPKaDcZy+0HyeeeKIC1C233BKefvzxxyug3DEPeeWVVxSgPvzwwxrbFdqnOXPmlJv++OOPV/o9/PLLL5WmaWrIkCFK13U1cOBAZbfb1Y8//ljjtoQQQghx+DoSruFfeukllZ2drbKystTvv/+urr/+egWoXr16VVimT58+qk2bNuHXgUBAZWZmqsTERLVz587wdF3X1YUXXqgAdc0111RoU0xMjNqwYUO5dZ999tnKbrer4uLi8LQHHnig2mv7Nm3aqEjDPqH97dChQ7ltKKXUokWLFKAuuOACFQgEwtOzsrJU06ZNVXJysioqKgpP0zRN3XTTTeH5rrnmGnXCCSeorl27qhEjRoSnjx07Vlkslgr3P/vLz89XsbGxatCgQeWmFxUVqfj4eNW/f/+I9jF0PCL5V/a8RCqS78Mvv/yiTjjhhHLbslgslS4THx+vmjZtWul67rrrLgWoX375pdo2hT5Tmqapn376qdx7Y8eOrfQ+NXRfcdttt6nS0lJ1/PHHq6SkpAqfSSFEw5NSPEKIRiM7O5vvvvuOs88+u0LWwm233UZcXFy5WoWhDIhQuZNFixZhtVqZPHkyVquVRYsWAWZJEaVUuUyRmTNn0rZtW/7xj3+Qk5MT/ldQUMDgwYPZtGkTa9euLdeG888/n2OPPfaA9m3lypWsWLGCoUOHYhhGuW22b9+eDh068NVXXx3QustasmQJSqmIs/XLine truncated
"text/plain": [
"<Figure size 1540x462 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, axes = plt.subplots(1, len(REGIONS), figsize=(7 * len(REGIONS), 4.2))\n",
"for ax, region in zip(np.atleast_1d(axes), REGIONS):\n",
" box = REGIONS[region]['zoom']\n",
" for name, img in images[region]['zoom'].items():\n",
" if img is None or name.startswith('native'):\n",
" continue\n",
" row = img[img.shape[0] // 2]\n",
" ax.plot(box[0] + (np.arange(len(row)) + 0.5) / B, row / row.mean(), lw=1, label=name)\n",
" ax.set_xlabel('x [pixel, 25 um]'); ax.set_ylabel('intensity / mean')\n",
" ax.set_title(f'{region}: row y = {(box[2] + box[3]) / 2:.0f} px'); ax.grid(alpha=.3); ax.legend(fontsize=8)\n",
"fig.tight_layout()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "81b5a618",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:47.102562Z",
"iopub.status.busy": "2026-09-28T09:23:47.102478Z",
"iopub.status.idle": "2026-09-28T09:23:48.132289Z",
"shell.execute_reply": "2026-09-28T09:23:48.132064Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"DIPlib -- a quantitative image analysis library\n",
"Version 3.5.1 (Jul 5 2024)\n",
"For more information see https://diplib.org\n",
"region reconstruction FRC cutoff: median range crossed\n",
"LowerLeft eta interpolation 2.049 2.033-2.053 5/10\n",
"LowerLeft cluster CNN 1ph 2.033 2.024-2.045 4/10\n",
"LowerLeft cluster CNN <=3ph 2.062 2.062-2.062 1/10\n",
"LowerLeft GridBased 2-20 keV 2.169 2.151-2.342 10/10\n",
"LowerRight eta interpolation nan nan-nan 0/10\n",
"LowerRight cluster CNN 1ph nan nan-nan 0/10\n",
"LowerRight cluster CNN <=3ph nan nan-nan 0/10\n",
"LowerRight GridBased 2-20 keV nan nan-nan 0/10\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABfgAAAHCCAYAAACzEKg2AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAABDrAAAQ6wFQlOh8AAEAAElEQVR4nOzdd3wURRvA8d9eyV16TwgthN5771V6F0SUoiCKXVRQsVAUGxaK2F6QIiqCiCJK70WagPSahARCeq9X5v3jzElIaNLh+fK5T8js7Ozs7iWZmZt5VlNKKYQQQgghhBBCCCGEEEIIcUfR3eoKCCGEEEIIIYQQQgghhBDi6skAvxBCCCGEEEIIIYQQQghxB5IBfiGEEEIIIYQQQgghhBDiDiQD/EIIIYQQQgghhBBCCCHEHUgG+IUQQgghhBBCCCGEEEKIO5AM8AshhBBCCCGEEEIIIYQQdyAZ4BdCCCGEEEIIIYQQQggh7kAywC+EEEIIIYQQQgghhBBC3IFkgF8IIYQQQgghhBBCCCGEuAPJAL8QQgghhBBCCCGEEEIIcQeSAX4hxB0tPDycXr16ERgYiKZpDB069FZX6aYrU6YMrVu3vtXVuKQJEyYQGBhIenr6ra6KENckIiICk8nEDz/8cKurIoQQQtzWWrduTZkyZf7z/hEREWiaxrhx465bne4md0Lf59FHH6VatWrY7fZbXRUhrsnWrVvR6XRs27btVldFiCLJAL8Qt5H169ejaRpvv/32ra7KdTd79mw0TePbb7+9ruUOHTqUDRs2MGbMGObNm8fjjz/O3r17GTduHBEREddc/rhx49A07aKv6Oho4N8OyPkvs9lMxYoVefHFF0lOTi6y/LS0NN5//32aNWuGn58fRqOR4OBgOnXqxNdff01ubu41n8OtdvbsWT744ANeffVVPD09nekxMTG8/vrrdOnShZCQEDRNo3379pctb9GiRbRp0wYfHx9cXV0pX758oc7N8ePHGTduHM2aNaNYsWK4u7tTtWpVnn32WWJiYq7pfJKTk5k2bRpdunQhNDQUs9lMWFgY999/P3v27CmUPyMjgwkTJtCrVy9CQ0PRNI3y5csXWbZSivnz5/PQQw9RqVIl3N3dCQkJoU2bNvz8888opa6p7v/1muebPn16off+hRYvXkzjxo1xd3fH19eX7t278/fff19TvQEWLlzIsGHDqFOnDi4uLmiaxubNmy+aPzw8nCFDhhASEoLJZKJMmTK8+OKLpKWlFZk/MTGRkSNHUrx4cUwmE5UqVeL999/HarUWyFemTBmGDRvGmDFjyM7OvubzEkIIcfe4F9ry5788PT1p0KABU6dOxWaz3eoqsn79esaNG0dKSso1lzV06NCLtv8NBkOBY1643c3NjRo1ajBhwoSLthXi4uJ4/fXXqVevHj4+Pri4uFCiRAl69erFggUL7ooB8T179jBnzhwmTpyITvfv0NPSpUsZPnw41apVw9PTk8DAQJo0acKsWbMKtbvy2e12vvrqKxo3boynpyceHh5UrVqVV155pUC+EydO8PDDD1OlShVnX6FixYqMGDGCkydPXtP52Gw2Ro4cSf369QkMDMRkMlG6dGl69OjBxo0bC+W/2nb3p59+Stu2bSlevDhms5mgoCCaNWvG3Llzr/nn6+jRo7z88su0b98ef39/NE1j+PDhV7z/Sy+9VOi9f6Gvv/6aWrVq4erqSmBgIAMHDiQyMvKa6g3w1VdfMWjQIKpVq4bBYLhkPwRg37599OnTh4CAAGebfuLEiRftV0dGRjJw4EACAwNxdXWlVq1afP3114XyNW3alE6dOvH8889fc59MiBvh4j+dQghxm8vNzWXTpk08/fTTvPTSS8702bNnM378+GueNXS+N954g4oVKxZK9/PzK/B969atGTZsGABJSUksX76cjz/+mFWrVrFr1y5cXFyceffs2UOPHj04c+YMnTt3ZuzYsfj5+REXF8eaNWt4/PHH2blzJ1999dV1OYdb5YMPPkDTNB5//PEC6UePHuWdd96hRIkSNGjQgKVLl162rKeeeorPP/+c7t27M3HiRFxdXYmKimLr1q0F8s2cOZOpU6fStWtX+vXrh5ubG3/++SczZsxg/vz5bNmyhcqVK/+n89m+fTvPPfccrVq1YsSIERQrVozjx4/zxRdf8PPPPzN//nwGDBjgzJ+QkMBbb71FUFAQderUISkp6aJl5+bm8vDDD1O1alV69epFxYoVSUhIYObMmfTp04eRI0cyY8aM/1Rv+G/XPN/p06d59dVX8fDwICMjo8g8M2fOZPjw4VSvXp3333+f3Nxcpk+fTrNmzdi8eTO1atX6z3X/7LPP+PPPP6lRowaVK1dm//79F8179OhRmjZtSnp6Oo8//jjVq1fnwIEDTJs2jQ0bNrBp0yZcXV2d+dPT02nZsiVHjx7lySefpGbNmmzatIlXXnmFAwcOMG/evALljxo1is8//5xvvvmGJ5988j+fkxBCCHGnGTlyJE2bNkUpRUxMDHPmzOG5557j0KFDfPHFFwXyrly58qYOhK1fv57x48czdOhQfHx8rkuZU6dOxdfXt0Da+YPV+e6//3569uwJQHx8PD/++CNvvfUWW7duZfny5QXyrlq1igceeID09HT69OnD4MGD8fT05MyZMyxfvpwBAwZw8uRJXnvttetyDrfK+PHjKVOmDL179y6Q/thjj+Hq6krPnj2pVq0aaWlp/PDDDwwbNozFixezdOlSNE1z5rdarfTt25fff/+dBx54wPnhS0RERKEJXdHR0Zw5c4ZevXpRokQJTCYTx44dY9asWfzwww9s2bKFGjVq/KfzsVqtbN++naZNm/LQQw/h7e3N2bNn+fbbb2nVqhWzZs3ikUcecea/2nb3jh07KFmyJJ06dSIgIIC0tDR+++03hgwZwtq1a5k9e/Z/qjfAtm3bmDx5MmFhYTRo0IAVK1Zc8b67du3i008/xcPD46IfWL3xxhu8/fbbNGvWjE8//ZT4+HimTJnC+vXrnef1X02aNImEhARq165NmTJlLvlBzebNm2nfvj1Go5GnnnqKsLAwtm3bxltvvcX27dsLvbeio6Np3LgxqampPP/884SFhbF06VJGjBjB6dOnmThxYoHyR40aRYcOHfjjjz/o0qXLfz4nIW4IJYS4baxbt04BauLEibe6KtdNRkaGUkqpb775RgFq3rx5163syMhIBai33nqrQHr+sdatW3fNx3jrrbcUoDZt2nTJfOHh4QpQw4YNK7StZ8+eClCLFi1ypsXGxqqQkBDl7u6u1qxZU2SZe/fuVZMnT75sHUNDQ1WrVq0um+9WyMjIUN7e3uqRRx4ptC0tLU3FxsY6vwdUu3btLlrW3LlzFaC++uqryx53586dKikpqVD6l19+qQDVr1+/KzyDwsLDw9XRo0cLpe/fv18ZjUZVrFgxZbPZnOk5OTnq9OnTzu9DQ0NVuXLliizbYrGo1atXF0rPyMhQFSpUUIA6ePDgf6771V7z83Xq1EnVq1dPPfzwwwpQUVFRBbYnJSUpLy8vVbJkSZWamupMj4qKUp6enqpFixb/ud5KKRUREaHy8vKUUpf/uezVq5cC1HfffVcg/bvvvlOAeueddwqkv/HGGwpQH330UYH0559/XgFF/ow2bdpUVa9e/VpOSQghxF3mXmzLp6enq5IlSyqdTqfi4uKu67Hz29cXtvUvJr99EB4efs3HHjJkSJHtnQtd7J5brVZVr149Bahdu3Y50w8fPqzc3d1VsWLF1N69e4ssc+3atWrmzJmXrSOghgwZcvmTuQUiIiKUXq9X48ePL7Rt1apVym63F0izWq2qefPmClDLli0rsG3ChAlKp9Op5cuX/+f6/Pnnnwoosk9yrdLS0lRgYKAKCwsrlP5f293n69y5swJUZGTkf65jQkKCs290qX7rhfLy8lSNGjVUr169VKtWrZRery+U5+jRo0qv16u6desqi8XiTP/rr7+UTqdTgwYN+s/1VkqpU6dOOftWl/u5rF27ttI0TW3evLlA+qRJkxSg5s+fXyB90KBBClA//fRTgfTevXsrvV6vjh07ViDdZrOpkiVLqq5du17TOQlxI0iIHiHuUEeOHGHAgAEEBwdjMpkoW7YsL730UoHwE2fOnEHTNF588cUC+44YMaLIGdWjRo1C0zRiY2OdaXl5eXzwwQfUrFkTV1dXvLy8aN++faFliOfHyPzpp59o2LAhbm5udO3a9arP7eTJkwwdOpTixYvj4uJCyZIlefLJJ0lISHDmad26NaGhoYBjdkj+ktgyZco4Z060adPGmX4r41PmL8U8fvy4M+3DDz8kJiaG9957j7Zt2xa5X61atQrdu0vZt28f9913H56ennh7e9O3b1/Cw8ML5cvJyWH8+PFUrlwZs9mMn58f3bt3Z9euXc48Z8+eJSgoiPLlyxcKaZIftujTTz+9bJ3++OMPUlNTi3wfeHp6EhQUdMXnN3HiRGrVqsVjjz0GOGZdX2z5cv369QvNtgKcM+uvJWRMmTJlilzNUb16dapXr865c+eIi4tzpptMJkqVKnVFZRsMBtq1a1co3d3dnW7dugHXVverveb55s6dy6pVq/j666/R6/VF5vnll19IS0tj+PDheHl5OdNLlixJ//792bRp0zWFzQoNDcVoNF5R3rVr1+Lq6lpgJQXAAw88gNlsZtasWQXS586di6urKyNHjiyQnv/zN3fu3ELH6NKlCwcOHODAgQNXcxpCCCEEcPe05T08PGjUqBF2u73QzNqLraZdvnw5jRo1wtXVlaCgIB577DGSkpIu2Wb/448/aNy4sXOfJ554gqysrALHGj9+PABhYWHOPsCtit+v1+udz8g6vw/wxhtvkJmZ6QxnUpQ2bdrw6KOPXvGx1q1bR7NmzXB3dycgIIBHHnmE+Pj4QvlSUlIYNWoUYWFhmEwmgoODefDBBwvUb//+/bi5udG0adNLine truncated
"text/plain": [
"<Figure size 1540x462 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# one_frc splits the counts binomially with its own (unseeded) generator, so one\n",
"# call is one draw: repeat it and quote the median and the range\n",
"N_REPEAT = 10\n",
"frc_results = {}\n",
"try:\n",
" import frc\n",
" fig, axes = plt.subplots(1, len(REGIONS), figsize=(7 * len(REGIONS), 4.2))\n",
" for ax, region in zip(np.atleast_1d(axes), REGIONS):\n",
" frc_results[region] = {}\n",
" for name, img in images[region]['frc'].items():\n",
" if img is None or name.startswith('native'):\n",
" continue\n",
" square = frc.util.square_image(np.array(img, float), add_padding=False)\n",
" size = square.shape[0]\n",
" values, first = [], None\n",
" for _ in range(N_REPEAT):\n",
" curve = frc.one_frc(square)\n",
" xs = np.arange(len(curve)) / size\n",
" first = curve if first is None else first\n",
" try:\n",
" values.append(float(frc.frc_res(xs, curve, size)[0]))\n",
" except Exception: # the curve never crosses 1/7\n",
" values.append(np.nan)\n",
" values = np.array(values)\n",
" found = np.isfinite(values)\n",
" frc_results[region][name] = dict(\n",
" median=float(np.median(values[found])) if found.any() else np.nan,\n",
" low=float(values[found].min()) if found.any() else np.nan,\n",
" high=float(values[found].max()) if found.any() else np.nan,\n",
" crossings=int(found.sum()), repeats=N_REPEAT)\n",
" r = frc_results[region][name]\n",
" ax.plot(np.arange(len(first)) / size, first, lw=1,\n",
" label=f\"{name}: {r['median']:.3f} ({r['crossings']}/{N_REPEAT} crossed)\")\n",
" ax.axhline(1 / 7, color='k', lw=.6, ls=':')\n",
" ax.set_xlabel('spatial frequency (1/pixels)'); ax.set_ylabel('FRC')\n",
" ax.set_title(f'{region}, FRC box {REGIONS[region][\"frc\"]}'); ax.grid(alpha=.3); ax.legend(fontsize=8)\n",
" fig.tight_layout()\n",
" print(f\"{'region':11s} {'reconstruction':20s} {'FRC cutoff: median':>18s} {'range':>15s} {'crossed':>8s}\")\n",
" for region, values in frc_results.items():\n",
" for name, r in values.items():\n",
" print(f\"{region:11s} {name:20s} {r['median']:18.3f} {r['low']:7.3f}-{r['high']:.3f} \"\n",
" f\"{r['crossings']:5d}/{r['repeats']}\")\n",
"except Exception as error:\n",
" print('FRC skipped:', repr(error))"
]
},
{
"cell_type": "markdown",
"id": "533a651d",
"metadata": {},
"source": [
"## 3. Photon statistics\n",
"\n",
"Photons in each whole region. The cluster chains keep only clusters that fall in\n",
"the 1/2/3-photon energy windows; at this occupancy the rest is lost to pile-up\n",
"(`SiementStar_LowerLeft.ipynb`: 45 % of the photons are in single-photon\n",
"clusters)."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "47cb5a91",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:48.133530Z",
"iopub.status.busy": "2026-09-28T09:23:48.133348Z",
"iopub.status.idle": "2026-09-28T09:23:48.148052Z",
"shell.execute_reply": "2026-09-28T09:23:48.147815Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"region eta (1ph) CNN 1ph CNN <=3ph GridBased photons per frame: GridBased / CNN <=3ph\n",
"LowerLeft 1.4060e+09 1.4060e+09 1.6677e+09 1.8519e+09 185.2 / 83.4 = 2.22\n",
"LowerRight 2.7039e+09 2.7039e+09 4.4703e+09 6.5713e+09 657.1 / 223.5 = 2.94\n",
"(GridBased over 10,000,000 frames; the cluster chains used all 20 M)\n"
]
}
],
"source": [
"counts = {}\n",
"CLUSTER_FRAMES = 20e6 # the cluster chains used the whole run\n",
"print(f\"{'region':11s} {'eta (1ph)':>12s} {'CNN 1ph':>12s} {'CNN <=3ph':>12s} {'GridBased':>12s} \"\n",
" f\"photons per frame: GridBased / CNN <=3ph\")\n",
"for region in REGIONS:\n",
" c = {name: float(np.nansum(img)) for name, img in images[region]['roi'].items()\n",
" if img is not None and not name.startswith('native')}\n",
" counts[region] = c\n",
" cnn = c.get('cluster CNN <=3ph', np.nan)\n",
" print(f\"{region:11s} {c['eta interpolation']:12.4e} {c.get('cluster CNN 1ph', np.nan):12.4e} {cnn:12.4e} {c['GridBased 2-20 keV']:12.4e} \"\n",
" f\"{c['GridBased 2-20 keV'] / frames.sum():8.1f} / {cnn / CLUSTER_FRAMES:6.1f} = \"\n",
" f\"{(c['GridBased 2-20 keV'] / frames.sum()) / (cnn / CLUSTER_FRAMES):.2f}\")\n",
"print(f'(GridBased over {frames.sum():,.0f} frames; the cluster chains used all 20 M)')"
]
},
{
"cell_type": "markdown",
"id": "b2edee48",
"metadata": {},
"source": [
"## 4. Energy spectrum from the energy head"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "75e762b8",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:48.149351Z",
"iopub.status.busy": "2026-09-28T09:23:48.149259Z",
"iopub.status.idle": "2026-09-28T09:23:48.409693Z",
"shell.execute_reply": "2026-09-28T09:23:48.409471Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"15 keV line at 15.022 keV, core sigma 0.514 keV; below 13.5 keV 11.38 %, 13.5-16.5 keV 83.31 %, above 16.5 keV 5.31 %\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 825x462 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def gauss(x, a, mu, sig):\n",
" return a * np.exp(-0.5 * ((x - mu) / sig)**2)\n",
"\n",
"\n",
"total = spectrum.sum()\n",
"near = np.abs(spectrum_centres - 15) < 2\n",
"peak = spectrum_centres[near][np.argmax(spectrum[near])]\n",
"window = np.abs(spectrum_centres - peak) < 0.8\n",
"try:\n",
" (a, mu, sig), _ = curve_fit(gauss, spectrum_centres[window], spectrum[window],\n",
" p0=(spectrum[window].max(), peak, 0.3), maxfev=20000)\n",
"except Exception:\n",
" if spectrum[window].sum() > 0:\n",
" mu = np.average(spectrum_centres[window], weights=spectrum[window])\n",
" sig = np.sqrt(np.average((spectrum_centres[window] - mu)**2, weights=spectrum[window]))\n",
" else:\n",
" mu = sig = np.nan\n",
"fractions = dict(below=spectrum[spectrum_centres < 13.5].sum() / total,\n",
" line=spectrum[(spectrum_centres >= 13.5) & (spectrum_centres <= 16.5)].sum() / total,\n",
" above=spectrum[spectrum_centres > 16.5].sum() / total)\n",
"print(f'15 keV line at {mu:.3f} keV, core sigma {abs(sig):.3f} keV; below 13.5 keV {fractions[\"below\"]*100:.2f} %, '\n",
" f'13.5-16.5 keV {fractions[\"line\"]*100:.2f} %, above 16.5 keV {fractions[\"above\"]*100:.2f} %')\n",
"fig, ax = plt.subplots(figsize=(7.5, 4.2))\n",
"ax.semilogy(spectrum_centres, spectrum / total / np.diff(spectrum_centres)[0], lw=1)\n",
"ax.axvline(15, color='grey', lw=.6, ls=':')\n",
"ax.set_xlim(0, 30); ax.set_ylim(1e-6, 5)\n",
"ax.set_xlabel('reconstructed photon energy [keV]'); ax.set_ylabel('photons / keV (normalised)')\n",
"ax.set_title('Siemens star, both regions, GridBased 2-20 keV'); ax.grid(alpha=.3, which='both')\n",
"fig.tight_layout()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "42d1185c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-28T09:23:48.410840Z",
"iopub.status.busy": "2026-09-28T09:23:48.410746Z",
"iopub.status.idle": "2026-09-28T09:23:48.503131Z",
"shell.execute_reply": "2026-09-28T09:23:48.502878Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"written /home/xie_x1/MLXID/GridBased/Results/overnight_poly2to20keV_Moench040/report/siemens_star_poly2to20keV_summary_50pct.json\n"
]
}
],
"source": [
"summary = dict(parts=parts, frames=float(frames.sum()),\n",
" photons=float(sum(s['n_photons'] for s in summaries)),\n",
" model=summaries[0]['checkpoint'], epoch=summaries[0]['epoch'], crop=summaries[0]['crop'],\n",
" regions={k: v['roi'] for k, v in REGIONS.items()}, photon_counts=counts,\n",
" frc_cutoff=frc_results, line_keV=float(mu), line_sigma_keV=float(abs(sig)),\n",
" spectrum_fractions=fractions)\n",
"out = OVERNIGHT / 'report' / os.environ.get('GRID_STAR_SUMMARY', 'siemens_star_poly2to20keV_summary.json')\n",
"out.parent.mkdir(parents=True, exist_ok=True)\n",
"json.dump(summary, open(out, 'w'), indent=1)\n",
"regions_dir = GRID / f'SiemensStar_15keV_{POLY_TAG}_regions' # the two regions as their own arrays\n",
"regions_dir.mkdir(exist_ok=True)\n",
"for region in REGIONS:\n",
" np.save(regions_dir / f'{region}_binned_image_flattened.npy', images[region]['roi']['GridBased 2-20 keV'])\n",
" np.save(regions_dir / f'{region}_binned_image.npy', cut_grid(raw, REGIONS[region]['roi']))\n",
"json.dump(summary, open(regions_dir / 'summary.json', 'w'), indent=1)\n",
"print('written', out)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch_nightly",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+583
View File
@@ -0,0 +1,583 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "32f9dbc8",
"metadata": {},
"source": [
"# XRF AgMo_1_factor_10 -- polychromatic GridBased model on measured lines\n",
"\n",
"2603 MaxIV, MOENCH014 at 150 V, full 400x400 chip flood-illuminated by the\n",
"fluorescence of an Ag/Mo sample; slit factor 10, **0.67 % occupancy**, 1.0e8\n",
"photons in 100 000 frames. Mo Ka 17.44 / Kb 19.64 and Ag Ka 22.10 keV sit in\n",
"the middle of the 3-30 keV training range; Compton + elastic scatter forms a\n",
"bump at 28-30 keV. The clean case -- `XRF_CuCrCo_1_factor_10.ipynb` has the\n",
"low-energy lines.\n",
"\n",
"Model `Results/260919_poly3to30keV_Moench014_c96_00`, the default since\n",
"2026-09-19: `[32, 64, 96, 96]`, flat 3-30 keV, chosen from four sizes in\n",
"`Results/overnight_poly3to30keV/report`. Reconstruction from\n",
"`scripts/05_infer_measurement.py --config Configs/infer_measurement_poly_Moench014.yaml\n",
"--run AgMo_1_factor_10 --save-photons` (FP16, masked column common mode). The previous\n",
"5-30 keV model's reconstructions are kept as `<run>_poly5to30`.\n",
"Compared throughout:\n",
"\n",
"* **grid direct** -- the model output;\n",
"* **grid corrected** -- $E = g^{-1}(E_\\mathrm{pred})$, where $g(E)$ is the MC\n",
" position of a line of true energy $E$ (`scripts/07_energy_response.py`);\n",
"* **classical 3x3** -- 3x3 sum around a local maximum on the same calibrated\n",
" frames (first 10 000 frames), no model.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "930c69d4",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:13.369303Z",
"iopub.status.busy": "2026-09-19T09:10:13.369219Z",
"iopub.status.idle": "2026-09-19T09:10:13.639588Z",
"shell.execute_reply": "2026-09-19T09:10:13.639268Z"
}
},
"outputs": [],
"source": [
"import json\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"REPO = Path('/home/xie_x1/MLXID/GridBased')\n",
"sys.path.insert(0, str(REPO / 'src'))\n",
"from xrf_analysis import (EnergyCorrection, classical_and_model, fit_lines,\n",
" line_centroid, load_reconstruction)\n",
"\n",
"CONFIG = REPO / 'Configs/infer_measurement_poly_Moench014.yaml'\n",
"RUN = 'AgMo_1_factor_10'\n",
"EXPERIMENT = REPO / 'Results/260919_poly3to30keV_Moench014_c96_00'\n",
"RESPONSE = EXPERIMENT / 'energy_response.npz'\n",
"GRID = Path('/mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased/XRF_2603MaxIV')\n",
"EDGES = np.arange(0.0, 40.025, 0.05) # 50 eV bins\n",
"CENTRES = 0.5 * (EDGES[1:] + EDGES[:-1])\n",
"\n",
"FIT_GROUPS = [ # each line fitted on its own window\n",
" dict(label='Mo Ka', alpha=['Mo_Ka'], window=(16.0, 18.9)),\n",
" dict(label='Mo Kb', alpha=['Mo_Kb'], window=(18.75, 20.7)),\n",
" dict(label='Ag Ka', alpha=['Ag_Ka'], window=(20.6, 23.6)),\n",
"] # Ag Kb sits on the rising Compton bump and is left out\n",
"SPECTRUM_ZOOM = (14.5, 27.5)\n",
"LINES = [n for g in FIT_GROUPS for n in g['alpha']]\n",
"PEAKS = LINES + [e[0] for g in FIT_GROUPS for e in g.get('extra', ())]\n",
"COLOURS = {'grid direct': '#1f6fb4', 'grid corrected': '#10695f',\n",
" 'classical 3x3': '#6b6b6b'}"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "81b6499b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:13.641460Z",
"iopub.status.busy": "2026-09-19T09:10:13.641283Z",
"iopub.status.idle": "2026-09-19T09:10:20.295943Z",
"shell.execute_reply": "2026-09-19T09:10:20.295634Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"AgMo_1_factor_10: 100000 frames, 1054 photons/frame, occupancy 0.69 %, 1.1 % of the photons below 3 keV\n"
]
}
],
"source": [
"reconstruction = load_reconstruction(GRID, RUN)\n",
"summary = reconstruction['summary']\n",
"n_frames = summary['n_frames']\n",
"energy = np.array(reconstruction['photons'][:, 2])\n",
"correction = EnergyCorrection(RESPONSE) # core-based g\n",
"correction_mean = EnergyCorrection(RESPONSE, estimator='mean')\n",
"\n",
"reference = classical_and_model(CONFIG, RUN, n_frames=10_000) # cached\n",
"classical, grid_subset = reference['classical'], reference['grid']\n",
"n_subset = int(reference['n_frames'])\n",
"\n",
"energies = {'grid direct': energy, 'grid corrected': correction(energy),\n",
" 'classical 3x3': classical[:, 3]}\n",
"frames_of = {'grid direct': n_frames, 'grid corrected': n_frames,\n",
" 'classical 3x3': n_subset}\n",
"print(f\"{RUN}: {n_frames} frames, {summary['photons_per_frame']:.0f} photons/frame, \"\n",
" f\"occupancy {summary['occupancy_valid_region']*100:.2f} %, \"\n",
" f\"{(energy < 3).mean()*100:.1f} % of the photons below 3 keV\")"
]
},
{
"cell_type": "markdown",
"id": "f7b8cb19",
"metadata": {},
"source": [
"## 1. The MC energy response\n",
"\n",
"The energy head is a regression trained on a flat 3-30 keV spectrum. On MC\n",
"(occupancy <= 2.5 %) the **core** of the predicted energy -- what a line fit\n",
"measures -- stays within ~25 meV of the truth from 4 to 28 keV and moves only\n",
"within ~0.75 keV of the edges (+98 meV at 3.2 keV, -93 meV at 29.6 keV), where\n",
"the energy scale is also compressed. The **mean** is pulled towards the centre\n",
"by ~0.1 keV at 5-12 keV and more near the edges, because the few per cent of\n",
"photons whose energy is off by more than 1 keV (the tails) are cut by the\n",
"bounded range. That mean is the `energy_bias_keV` of `eval_summary.json`. It\n",
"is not a line shift, and correcting with it moves lines at 5-12 keV by ~0.1 keV\n",
"the wrong way. The correction used here therefore inverts the core position\n",
"$g(E)$; with the 3-30 keV model it matters only below ~4 keV."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "72f22c9b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:20.297268Z",
"iopub.status.busy": "2026-09-19T09:10:20.297102Z",
"iopub.status.idle": "2026-09-19T09:10:20.563301Z",
"shell.execute_reply": "2026-09-19T09:10:20.563003Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Mo_Ka: g - E = -12 meV, g' = 1.00, MC core sigma 0.275 keV\n",
"Mo_Kb: g - E = -8 meV, g' = 1.00, MC core sigma 0.290 keV\n",
"Ag_Ka: g - E = -6 meV, g' = 1.00, MC core sigma 0.301 keV\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1300x420 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"response = np.load(RESPONSE)\n",
"centre = response['table_centre']\n",
"figure, axes = plt.subplots(1, 2, figsize=(13, 4.2))\n",
"for curve, colour, label in ((correction, COLOURS['grid corrected'], 'core of E_pred (used)'),\n",
" (correction_mean, '#c0392b', 'mean of E_pred')):\n",
" axes[0].plot(curve.grid, curve.g_grid - curve.grid, color=colour, lw=1.5, label=label)\n",
"for name in LINES:\n",
" axes[0].axvline(line_centroid(name), color='k', lw=.5, ls=':')\n",
"axes[0].axhline(0, color='k', lw=.6); axes[0].set_ylim(-0.6, 0.3)\n",
"axes[0].set_xlabel('E_true [keV]'); axes[0].set_ylabel('g(E) - E [keV]')\n",
"axes[0].set_title('MC: where a line of true energy E appears'); axes[0].legend(fontsize=8)\n",
"axes[0].grid(alpha=.3)\n",
"axes[1].plot(centre, response['table_core_sigma'], 'k.', ms=3, label='core sigma, raw')\n",
"axes[1].plot(correction.grid, correction.mc_sigma(correction.grid) / correction.slope(correction.grid),\n",
" color=COLOURS['grid corrected'], lw=1.2, label=\"core sigma / g' (true keV)\")\n",
"axes[1].set_ylim(0, 0.4); axes[1].set_xlabel('E_true [keV]'); axes[1].set_ylabel('keV')\n",
"axes[1].set_title('MC: energy resolution'); axes[1].legend(fontsize=8); axes[1].grid(alpha=.3)\n",
"figure.tight_layout()\n",
"for name in LINES:\n",
" e = line_centroid(name)\n",
" print(f\"{name}: g - E = {(correction.forward(np.array([e]))[0] - e)*1000:+.0f} meV, \"\n",
" f\"g' = {correction.slope(e):.2f}, MC core sigma {correction.mc_sigma(e):.3f} keV\")"
]
},
{
"cell_type": "markdown",
"id": "dd0b6fa5",
"metadata": {},
"source": [
"## 2. Spectra\n",
"\n",
"Per frame, 50 eV bins; grey = outside the 3-30 keV training range."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "5b61a197",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:20.564617Z",
"iopub.status.busy": "2026-09-19T09:10:20.564520Z",
"iopub.status.idle": "2026-09-19T09:10:25.475927Z",
"shell.execute_reply": "2026-09-19T09:10:25.475651Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAANICAYAAAC8Js0MAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3xN9//A8de92XsPRIZEQowgiRF7702pXaql+lVUtapUqy2qRa2Woihtjdp77y0SIwRBgoiEyN733t8fftKmESvjJvF+Ph4e32/POfdz3udz8sm9ed/PeX8UGo1GgxBCCCGEEEIIIYQQQohiQantAIQQQgghhBBCCCGEEEL8Q5K2QgghhBBCCCGEEEIIUYxI0lYIIYQQQgghhBBCCCGKEUnaCiGEEEIIIYQQQgghRDEiSVshhBBCCCGEEEIIIYQoRiRpK4QQQgghhBBCCCGEEMWIJG2FEEIIIYQQQgghhBCiGJGkrRBCCCGEEEIIIYQQQhQjkrQVQgghhBBCCCGEEEKIYkSStkIIIYQQ4qXMmTMHhUJB1apV89XO5MmTUSgUKJVKbt68mWt/cnIy5ubmKBQKBg0alK9z5eXy5ct88MEH1KtXDxMTExQKBQcPHnzt9r744gucnZ3R1dXF0tKywOJ86vjx40yePJm4uLgCb/tlJCYmMm7cOFq1aoWdnR0KhYLJkyfneXxgYCAtWrTA1NQUS0tLunXr9sx7LYQQQgghnk2StkIIIYQQ4qUsXboUeJLwPHXqVL7bMzU15bfffsu1fe3atWRmZqKnp5fvc+Tl7NmzbNy4EWtra5o3b56vtjZt2sS3337LgAEDOHToEHv37i2gKP9x/PhxvvrqK60lbR89esSiRYtIT0+nS5cuzz326tWrNGnShIyMDNasWcPSpUu5du0aDRs2JCYmpmgCFkIIIYQo4SRpK4QQQgghXujs2bMEBwfTvn17AJYsWZLvNnv16sXy5ctRq9U5ti9ZsoSuXbuir6+f73PkpX///kRGRrJt2zYGDBiQr7YuXboEwMiRI6lfvz5+fn4FEWKRSElJeanjXFxcePz4MYcOHWLq1KnPPXbSpEkYGBiwdetW2rVrR7du3di2bRsxMTH88MMPBRG2EEIIIUSpJ0lbIYQQQgjxQk+TtNOmTSMgIIC//vorV8Lv7t279OjRAzMzMywtLenbty9nzpxBoVCwbNmyXG0OHjyYO3fusGfPnuxt165d4+jRowwePPiZcURERNCvXz/s7e0xMDCgcuXK/Pjjj7kSvy+iVBbMx2BXV1e++OILABwcHHKUDVi9ejWtWrWiTJkyGBkZUblyZT777DOSk5NztXPq1Ck6duyIjY0NhoaGuLu7M2rUKOBJOYlPPvkEADc3NxQKRY5yDmq1mu+//55KlSphYGCAvb09AwYM4O7duznO0aRJE6pWrcrhw4cJCAjA2Ng4z37+r6fnfJGsrCy2bt1K9+7dMTc3z97u4uJC06ZN2bBhw0udTwghhBDiTaer7QCEEEIIIUTxlpqayp9//om/vz9Vq1Zl8ODBvPvuu6xdu5aBAwcCT+rQNm3alNjYWKZPn46Hhwc7d+6kV69eebZbsWJFGjZsyNKlS2ndujXwpASDq6vrM0sWxMTEEBAQQEZGBlOmTMHV1ZWtW7cyduxYwsLCWLBgQeF0wHNs2LCB+fPns2TJEnbu3ImFhQVOTk4AXL9+nXbt2jFq1ChMTEy4evUq06dP5/Tp0+zfvz+7jV27dtGxY0cqV67MzJkzcXZ25vbt2+zevRuAd999l9jYWObOncv69espU6YMAN7e3gAMHz6cRYsW8eGHH9KhQwdu377NxIkTOXjwIIGBgdja2maf6/79+/Tr149x48bx3XffFVjy+qmwsDBSU1OpXr16rn3Vq1dnz549pKWlYWhoWKDnFUIIIYQobSRpK4QQQgghnmvdunXEx8czZMgQ4ElZg1GjRrFkyZLspO3y5cu5ceMGO3bsoE2bNgC0atWKlJQUFi5cmGfbgwcPZtiwYcTGxmJhYcGKFSt4//33nzmrc+bMmdy7d49Tp05Ru3ZtAFq3bo1KpeKXX35h1KhReHp6FvTlP1fNmjWzk7S+vr45EqRPZ+ACaDQa6tevT+XKlWncuDEXLlzITmyOGDECZ2dnTp06lSOZ+c477wDg5OSEs7Nz9vlcXV2zj7l69SqLFi3igw8+YO7cuTniqlOnDrNmzeLbb7/N3h4bG8vatWtp1qxZAfbCPx49egSAtbV1rn3W1tZoNBoeP36cnXgWQgghhBDPJuURhBBCCCHEcy1ZsgQjIyN69+4NPFlArGfPnhw5coTr168DcOjQIczMzLITtk+9/fbbz227Z8+e6Ovrs2rVKrZv305UVBSDBg165rH79+/H29s7O2H71KBBg9BoNDlmrxYHN2/epE+fPjg6OqKjo4Oenh6NGzcG4MqVK8CTchBhYWEMGTLktWafHjhwACBXn9WuXZvKlSuzb9++HNutrKwKLWH7b88rpfAyZRaEEEIIId50krQVQgghhBB5unHjBocPH6Z9+/ZoNBri4uKIi4ujR48ewJNyBvBkhqWDg0Ou1z9r27+ZmJjQq1cvli5dypIlS2jRogUuLi7PPPbRo0fPnKFZtmzZ7P3FRVJSEg0bNuTUqVN88803HDx4kDNnzrB+/XrgSckJeFLyAcierfuqnl5zXv3y3z4p7BmuNjY2OeL6t9jYWBQKBZaWloUagxBCCCFEaSDlEYQQQgghRJ6WLl2KRqNh3bp1rFu3Ltf+5cuX880332BjY8Pp06dz7Y+KinrhOQYPHszixYu5cOECq1atyvM4Gxsb7t+/n2t7ZGQkQI7SBNq2f/9+IiMjOXjwYPbsWoC4uLgcx9nZ2QHkWjTsZT1Nkt6/fz9X4jcyMjJXnxT2LFd3d3eMjIy4ePFirn0XL17Ew8ND6tkKIYQQQrwEmWkrhBBCCCGeSaVSsXz5ctzd3Tlw4ECufx9//DH3799nx44dNG7cmMTERHbs2JGjjb/++uuF56lXrx6DBw+ma9eudO3aNc/jmjdvTkhICIGBgTm2r1ixAoVCQdOmTV/vQgvB0+SogYFBju3/re/r6emJu7s7S5cuJT09Pc/2nrbzdIbuU09LHaxcuTLH9jNnznDlypVnLuhWmHR1denYsSPr168nMTExe3tERAQHDhygW7duRRqPEEIIIURJJTNthRBCCCHEM+3YsYPIyEimT59OkyZNcu2vWrUq8+bNY8mSJaxcuZJZs2bRr18/vvnmGzw8PNixYwe7du0CQKl8/lyBJUuWvDCe0aNHs2LFCtq3b8/XX3+Ni4sL27ZtY8GCBQwfPvyVFiFLSUlh+/btAJw8eRJ4Upf34cOHmJiY0LZt25du61kCAgKwsrJi2LBhfPnll+jp6bFq1SqCg4NzHTt//nw6duxI3bp1GT16NM7OzkRERLBr167smcfVqlUD4KeffmLgwIHo6enh5eWFl5cX7733HnPnzkWpVNK2bVtu377NxIkTKV++PKNHj87Xdfzbjh07SE5Ozk7GhoSEZM++bteuHcbGxgB89dVX+Pv706FDBz777DPS0tKYNGkStra2fPzxxwUWjxBCCCFEqaYRQgghhBDiGbp06aLR19fXREdH53lM7969Nbq6upqoqChNRESEplu3bhpTU1ONmZmZpnv37prt27drAM2mTZuyX/Pll19qAE1MTMxzz29iYqIZOHBgjm3h4eGaPn36aGxsbDR6enoaLy8vzYwZMzQqleqVru3WrVsa4Jn/XFxcXqmtvK7n+PHjmnr16mmMjY01dnZ2mnfffVcTGBioATS//fZbjmNPnDihadu2rcbCwkJjYGCgcXd314wePTrHMePHj9eULVtWo1QqNYDmwIEDGo1Go1GpVJrp06drPD09NXp6ehpbW1tNv379NHfu3Mnx+saNG2uqVKnyStf2by4uLnn22a1bt3Ice/bsWU3z5s01xsbGGnNzc02XLl00N27ceO1zCyGEEEK8aRQajUZT5JliIYQQQgjxRvjuu+/44osviIiIeO3FtoQQQgghhHjTSHkEIYQQQghRIObNmwdApUqVyMzMZP/+/cyZM4d+/fpJwlYIIYQQQohXIElbIYQQQghRIIyNjZk1axa3b98mPT0dZ2dnPv30U7744osii0GlUvG8B8kUCgU6OjpF3lZxpVarUavVzz1GV1f+ZBBCCCGEKGpSHkEIIYQQQpQaTZo04dChQ3nud3Fx4fbt20XeVnE1efJkvvrqq+cec+vWLVxdXYsmICGEEEIIAUjSVgghhBBClCKhoaEkJibmud/AwIBq1aoVeVvFVWRkJJGRkc89pnr16ujr6xdRREIIIYQQAiRpK4QQQgghhBBCCCGEEMWKUtsBCCGEEEIIIYQQQgghhPiHrCrwAmq1msjISMzMzFAoFNoORwghhBBCCCGEEEIIUUJpNBoSExMpW7YsSmXe82klafsCkZGRlC9fXtthCCGEEEIIIYQQQgghSok7d+7g5OSU535J2r6AmZkZAOfOncPU1FTL0byYjY0Nenp62g6jxFOr1cTExGBnZ/fcbz2EEM8nY0mIgiFjqXgbP348U6dO1XYYb5TX7XMZS0VDxkTpJ2NJiPxLT0/n1q1bWFhYyJPdb5ikpCR8fX2zc455kYXIXiAhIQELCwtCQ0Nf2JnFga2trSRtC4BarSY6Ohp7e3v5ECJEPshYEqJgyFgq3uLi4rC0tNR2GG+U1+1zGUtFQ8ZE6SdjSYj8S09PJywsDEtLS0navmESExPx8vIiPj4ec3PzPI+T365CCCGEEELkw9dff63tEN440ufFm9wfIYQQIv8kaZuH+fPn4+3tjb+/v7ZDEUIIIYQQQgghhBBCvEEkaZuHESNGEBISwpkzZ7QdihBCCCGEKMYmTZqk7RDeONLnxZvcHyGEECL/JGkrhBBCCCFEPsij4EVP+rx4k/sjhBBC5J8Line truncated
"text/plain": [
"<Figure size 1400x850 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"histograms = {name: np.histogram(values, EDGES)[0] for name, values in energies.items()}\n",
"figure, axes = plt.subplots(2, 1, figsize=(14, 8.5))\n",
"for axis, log in zip(axes, (True, False)):\n",
" for name, counts in histograms.items():\n",
" axis.plot(CENTRES, counts / frames_of[name], lw=1, color=COLOURS[name], label=name)\n",
" for name in LINES:\n",
" axis.axvline(line_centroid(name), color='k', lw=.4, ls='--')\n",
" axis.axvspan(0, 3, color='0.9'); axis.axvspan(30, 40, color='0.9')\n",
" axis.set_xlabel('energy [keV]'); axis.set_ylabel('photons / frame / 50 eV')\n",
" axis.grid(alpha=.3)\n",
" if log:\n",
" axis.set_yscale('log'); axis.set_xlim(2, 33); axis.set_ylim(1e-4, None)\n",
" axis.legend(fontsize=9); axis.set_title(RUN)\n",
" else:\n",
" axis.set_xlim(*SPECTRUM_ZOOM)\n",
"figure.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "7cce07c1",
"metadata": {},
"source": [
"## 3. Line positions and energy resolution\n",
"\n",
"Grid lines: Gaussian core + a wide Gaussian at the same position (the tails of\n",
"section 1) + an erfc low-energy step, on an exp(polynomial) background.\n",
"Classical lines: Gaussian + step. Ka is fitted as the tabulated doublet, so the\n",
"position is the intensity-weighted centroid. `sigma` is the core width; FWHM is\n",
"that of the whole line shape."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "07db24a1",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:25.477272Z",
"iopub.status.busy": "2026-09-19T09:10:25.477178Z",
"iopub.status.idle": "2026-09-19T09:10:25.617926Z",
"shell.execute_reply": "2026-09-19T09:10:25.617645Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" table | position [keV] | core sigma [keV] | FWHM [keV] | MC sigma\n",
" | direct corr. 3x3 | direct corr. 3x3 | grid 3x3 ratio |\n",
"Mo_Ka 17.44 | 17.47 17.48 17.52 | 0.327 0.327 0.464 | 0.82 1.09 0.75 | 0.275\n",
"Mo_Kb 19.64 | 19.66 19.67 19.74 | 0.353 0.352 0.492 | 0.83 1.16 0.72 | 0.290\n",
"Ag_Ka 22.10 | 22.10 22.11 22.17 | 0.378 0.378 0.489 | 0.95 1.15 0.82 | 0.301\n",
"grid FWHM: direct output\n"
]
}
],
"source": [
"fits = {}\n",
"for variant, counts in histograms.items():\n",
" for group in FIT_GROUPS:\n",
" fits[variant, group['label']] = fit_lines(\n",
" CENTRES, counts, group['alpha'], group.get('beta'), window=group['window'],\n",
" beta_ratio_fixed=group.get('ratio_fixed'),\n",
" fit_stretch=group.get('fit_stretch', True), extra_peaks=group.get('extra', ()),\n",
" tails=variant != 'classical 3x3')\n",
"\n",
"\n",
"def peak(variant, name):\n",
" group = next(g for g in FIT_GROUPS\n",
" if name in g['alpha'] or name in [e[0] for e in g.get('extra', ())])\n",
" return fits[variant, group['label']]['lines'][name]\n",
"\n",
"\n",
"def fwhm_of(variant, name):\n",
" \"\"\"FWHM of the line shape; for the extra peaks (no doublet) from sigma.\"\"\"\n",
" line = peak(variant, name)\n",
" return line.get('fwhm', 2.3548 * line['sigma'])\n",
"\n",
"\n",
"print(f\"{'':8s} {'table':>6s} | {'position [keV]':^26s} | {'core sigma [keV]':^26s} | \"\n",
" f\"{'FWHM [keV]':^20s} | MC sigma\")\n",
"print(f\"{'':8s} {'':>6s} | {'direct':>8s} {'corr.':>8s} {'3x3':>8s} | \"\n",
" f\"{'direct':>8s} {'corr.':>8s} {'3x3':>8s} | {'grid':>6s} {'3x3':>6s} {'ratio':>6s} |\")\n",
"for name in PEAKS:\n",
" table = f\"{line_centroid(name):6.2f}\" if name in LINES else f\"{'':6s}\"\n",
" positions = [peak(v, name)['mu'] for v in energies]\n",
" sigmas = [peak(v, name)['sigma'] for v in energies]\n",
" grid_fwhm = fwhm_of('grid direct', name)\n",
" classical_fwhm = fwhm_of('classical 3x3', name)\n",
" mc = correction.mc_sigma(line_centroid(name)) if name in LINES else \\\n",
" correction.mc_sigma(peak('grid corrected', name)['mu'])\n",
" print(f\"{name:8s} {table} | \" + ' '.join(f'{p:8.2f}' for p in positions) + ' | ' +\n",
" ' '.join(f'{s:8.3f}' for s in sigmas) +\n",
" f\" | {grid_fwhm:6.2f} {classical_fwhm:6.2f} {grid_fwhm/classical_fwhm:6.2f} | {mc:.3f}\")\n",
"print('grid FWHM: direct output')"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "cc95eb5a",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:25.619193Z",
"iopub.status.busy": "2026-09-19T09:10:25.619065Z",
"iopub.status.idle": "2026-09-19T09:10:26.626942Z",
"shell.execute_reply": "2026-09-19T09:10:26.626658Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAACkYAAAJOCAYAAADoTSlEAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3gU5frG8e9ueg8QCBBC770FqVIEBKUpCIJBEBsSUI7HgtgL2LERUIogARSkKQpIl947SAkQEmogBNLb7vz+4GeOSC/JJJv7c11cx53dnb1n9MzDu/vM+1oMwzAQEREREREREREREREREREREREREXEAVrMDiIiIiIiIiIiIiIiIiIiIiIiIiIjcLWqMFBERERERERERERERERERERERERGHocZIEREREREREREREREREREREREREXEYaowUEREREREREREREREREREREREREYehxkgRERERERERERERERERERERERERcRhqjBQRERERERERERERERERERERERERh6HGSBERERERERERERERERERERERERFxGGqMFBERERERERERERERERERERERERGHocZIEREREREREREREREREREREREREXEYaoyUAq9jx46MGTPmms9369aNd95556b398/Xr169mlKlSt1hQhERkUvKli3LvHnzcmz/AwcO5NVXX73j/UyePJm6deveeSAREZEb0Hgud7zzzjt069bN7BgiIvmWxnL5W6tWrfjyyy/NjiEi4pA0phMRkfxC4zqR/EmNkVLgLVy4kEGDBuXIvlu0aMHx48dzZN9RUVFYLBYuXLhwS+8rW7YsFouFQ4cOXbY9LCwMi8Vy21/y/fuHspSUFDp27Mi9997LxYsXb2ufIiKSu7799ls+/vjjHP+cIUOGEBwcjK+vL0FBQQwdOpSMjIybeu/ChQupVasWhQoVonDhwrRr147du3ff9Gf/XT/LlCmD3W6/7LmaNWtisVjYsWPHrRxOtn//WHb48GHKly/PCy+8gGEYt7VPERG5Po3nLtF4TkSkYNNYTmM5EZH8qqCN6f7Wpk0bPDw8iI+Pv6Mc/25+sdlsDBgwgOrVq3PixIk72reIiOSugjCu+1tycjK+vr7cc889t/zef+vfvz9Dhw7Nfnz27FkaNGjAI488ctPHJY5NjZFSYBmGgc1mMzVDVlaWKZ9bpUoV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 truncated
"text/plain": [
"<Figure size 2640x600 with 12 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"shown = ('grid direct', 'classical 3x3')\n",
"figure, axes = plt.subplots(2, len(FIT_GROUPS) * 2, figsize=(4.4 * len(FIT_GROUPS) * 2, 6),\n",
" gridspec_kw=dict(height_ratios=[3, 1]), squeeze=False)\n",
"for column, (group, variant) in enumerate((g, v) for g in FIT_GROUPS for v in shown):\n",
" fit = fits[variant, group['label']]\n",
" x = fit['centres']\n",
" axes[0, column].plot(x, fit['counts'], 'k.', ms=2.5)\n",
" axes[0, column].plot(x, fit['model'](x), color=COLOURS[variant], lw=1.3)\n",
" for part, curve in fit['components'](x).items():\n",
" axes[0, column].plot(x, curve, lw=.7, ls='--', label=part)\n",
" axes[0, column].set_yscale('log')\n",
" axes[0, column].set_ylim(max(fit['counts'].min() * 0.5, 1), fit['counts'].max() * 2)\n",
" axes[0, column].set_title(f\"{variant}: {group['label']}\", fontsize=9)\n",
" axes[0, column].legend(fontsize=6)\n",
" axes[1, column].plot(x, (fit['counts'] - fit['model'](x)) / np.sqrt(np.maximum(fit['counts'], 1)),\n",
" '.', ms=2.5, color=COLOURS[variant])\n",
" axes[1, column].axhline(0, color='k', lw=.6)\n",
" axes[1, column].set_xlabel('energy [keV]'); axes[1, column].set_ylabel('pull')\n",
"figure.tight_layout()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "8327278e",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:26.628157Z",
"iopub.status.busy": "2026-09-19T09:10:26.628054Z",
"iopub.status.idle": "2026-09-19T09:10:26.732058Z",
"shell.execute_reply": "2026-09-19T09:10:26.731776Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 750x440 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"figure, axis = plt.subplots(figsize=(7.5, 4.4))\n",
"for offset, variant in zip((-0.08, 0.0, 0.08), energies):\n",
" axis.errorbar([line_centroid(n) + offset for n in LINES],\n",
" [peak(variant, n)['sigma'] for n in LINES],\n",
" [peak(variant, n)['sigma_err'] for n in LINES],\n",
" fmt='o', ms=5, capsize=3, color=COLOURS[variant], label=variant)\n",
"e = np.linspace(min(line_centroid(n) for n in LINES) - 1, max(line_centroid(n) for n in LINES) + 1, 200)\n",
"e = e[(e >= 5) & (e <= 30)]\n",
"axis.plot(e, correction.mc_sigma(e), 'k--', lw=1, label='MC core sigma (raw)')\n",
"axis.plot(e, correction.mc_sigma(e) / correction.slope(e), 'k:', lw=1, label=\"MC / g' (true keV)\")\n",
"axis.set_xlabel('line energy [keV]'); axis.set_ylabel('core sigma [keV]'); axis.set_ylim(0, None)\n",
"axis.set_title('energy resolution'); axis.legend(fontsize=8); axis.grid(alpha=.3)\n",
"figure.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "fadc5f4d",
"metadata": {},
"source": [
"## 4. Reconstruction efficiency\n",
"\n",
"Without truth, three handles on the 10 000 frames of the classical reference:\n",
"\n",
"* **isolated photons**: classical clusters with no other cluster within 4 px\n",
" that have a grid photon within 1.5 px. Above 5 keV the 2.5 keV classical\n",
" threshold finds every isolated photon, so this is the absolute efficiency.\n",
" The MC equivalent is photons with no neighbour within 4 px.\n",
"* **pile-up**: classical clusters holding two or more grid photons within 2 px.\n",
"* **energy balance**: summed reconstructed energy / deposited energy per frame\n",
" (calibrated frame minus its baseline)."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "af106e1d",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:26.733295Z",
"iopub.status.busy": "2026-09-19T09:10:26.733190Z",
"iopub.status.idle": "2026-09-19T09:10:27.674956Z",
"shell.execute_reply": "2026-09-19T09:10:27.674675Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"isolated photons >= 5 keV found: 99.97 % (3-5 keV: 92.9 %; MC: 99.96 %)\n",
"grid / classical photon count: 1.091\n",
"classical clusters with >= 2 grid photons: 8.5 % (Poisson, second photon within 2 px: 8.3 %)\n",
"energy balance: grid 0.9968, classical 0.9980\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 750x420 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"match = reference['classical_match']\n",
"found = match >= 0\n",
"isolated = reference['isolation_classical'] > 4.0\n",
"above5 = classical[:, 3] >= 5\n",
"edges_e = np.arange(2.5, 30.01, 0.5)\n",
"which = np.digitize(classical[:, 3], edges_e) - 1\n",
"fraction = np.array([found[isolated & (which == i)].mean() if (isolated & (which == i)).sum() > 200\n",
" else np.nan for i in range(len(edges_e) - 1)])\n",
"\n",
"mc = json.load(open(EXPERIMENT / 'eval_summary.json'))['1%']['pair']\n",
"far = [i for i, edge in enumerate(mc['edges']) if edge >= 4.0]\n",
"mc_isolated = sum(mc['n_match'][i] for i in far) / sum(mc['n_true'][i] for i in far)\n",
"\n",
"figure, axis = plt.subplots(figsize=(7.5, 4.2))\n",
"axis.plot(0.5 * (edges_e[1:] + edges_e[:-1]), fraction * 100, 'o-', ms=3,\n",
" color=COLOURS['grid direct'], label='isolated classical clusters found by the grid')\n",
"axis.axhline(mc_isolated * 100, color='k', ls='--', lw=1, label='MC, isolated photons, 1 %')\n",
"axis.axvspan(0, 3, color='0.9'); axis.set_ylim(90, 100.5); axis.set_xlim(2.5, 30)\n",
"axis.set_xlabel('classical 3x3 energy [keV]'); axis.set_ylabel('%')\n",
"axis.set_title('detection efficiency'); axis.legend(fontsize=8); axis.grid(alpha=.3)\n",
"figure.tight_layout()\n",
"\n",
"multiplicity = reference['grid_per_classical']\n",
"occupancy = summary['occupancy_valid_region']\n",
"balance = reference['balance'][:, 1].sum()\n",
"low = (classical[:, 3] >= 3) & ~above5\n",
"print(f\"isolated photons >= 5 keV found: {found[isolated & above5].mean()*100:.2f} % \"\n",
" f\"(3-5 keV: {found[isolated & low].mean()*100:.1f} %; MC: {mc_isolated*100:.2f} %)\")\n",
"print(f\"grid / classical photon count: {len(grid_subset) / len(classical):.3f}\")\n",
"print(f\"classical clusters with >= 2 grid photons: {(multiplicity >= 2).mean()*100:.1f} % \"\n",
" f\"(Poisson, second photon within 2 px: {(1 - np.exp(-occupancy * np.pi * 4))*100:.1f} %)\")\n",
"print(f\"energy balance: grid {grid_subset[:, 3].sum() / balance:.4f}, \"\n",
" f\"classical {classical[:, 3].sum() / balance:.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "16e3c938",
"metadata": {},
"source": [
"## Conclusions\n",
"\n",
"* **Lines.** All resolved; the grid puts them within 0.03 keV of the tables,\n",
" the classical 3x3 sum 0.06-0.10 keV high (the MOENCH014 LUT reads 0.3-0.5 %\n",
" high here). The MC-based correction changes nothing at these energies\n",
" (<= 12 meV).\n",
"* **Energy resolution.** FWHM 0.82 / 0.83 / 0.95 keV for Mo Ka / Mo Kb /\n",
" Ag Ka against 1.09 / 1.16 / 1.15 keV for the classical sum, 18-28 %\n",
" narrower. The core widths (0.33-0.38 keV) are 19-26 % above the MC\n",
" (0.28-0.30 keV): at these energies the data carry an extra term of ~1 % of E\n",
" (pixel-to-pixel gain residuals of the calibration, not in the MC). The same\n",
" holds for the previous 5-30 keV model.\n",
"* **Efficiency.** 99.97 % of the isolated photons above 5 keV (MC 99.96 %).\n",
" The grid finds 9.1 % more photons than the classical finder: pile-up the 3x3\n",
" finder merges (8.5 % of its clusters hold two grid photons, 8.3 % expected\n",
" from Poisson), plus low-energy photons that the 3-30 keV model now reports.\n",
" Energy balance 0.997."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "a8e470c1",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:27.676156Z",
"iopub.status.busy": "2026-09-19T09:10:27.676067Z",
"iopub.status.idle": "2026-09-19T09:10:27.708535Z",
"shell.execute_reply": "2026-09-19T09:10:27.708203Z"
}
},
"outputs": [],
"source": [
"output = dict(\n",
" run=RUN, epoch=summary['epoch'], n_frames=n_frames,\n",
" photons_per_frame=summary['photons_per_frame'], occupancy=occupancy,\n",
" lines={name: {variant: dict(mu=float(peak(variant, name)['mu']),\n",
" sigma=float(peak(variant, name)['sigma']),\n",
" fwhm=float(fwhm_of(variant, name)))\n",
" for variant in energies}\n",
" for name in PEAKS},\n",
" efficiency_isolated_above5keV=float(found[isolated & above5].mean()),\n",
" efficiency_isolated_mc=mc_isolated,\n",
" grid_over_classical_count=len(grid_subset) / len(classical),\n",
" energy_balance_grid=float(grid_subset[:, 3].sum() / balance),\n",
")\n",
"with open(GRID / RUN / 'xrf_energy_analysis.json', 'w') as handle:\n",
" json.dump(output, handle, indent=1)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch_nightly",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+591
View File
@@ -0,0 +1,591 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a4f1f029",
"metadata": {},
"source": [
"# XRF CuCrCo_1_factor_10 -- polychromatic GridBased model on measured lines\n",
"\n",
"2603 MaxIV, MOENCH014 at 150 V, full 400x400 chip flood-illuminated by the\n",
"fluorescence of a Cu/Cr/Co sample under a 12 keV beam; slit factor 10,\n",
"**1.28 % occupancy**, 2.0e8 photons in 100 000 frames. Cr 5.41, Co 6.93 and\n",
"Cu 8.04 keV Ka lines 1.1-1.5 keV apart with their Kb lines in between (Co Kb\n",
"0.39 keV below Cu Ka), plus the elastic line at 12 keV. Cr Ka sat 0.4 keV\n",
"above the lower edge of the previous 5-30 keV model, which biased and\n",
"compressed it; the 3-30 keV model puts it 2.4 keV inside its range.\n",
"`XRF_AgMo_1_factor_10.ipynb` has the high-energy lines.\n",
"\n",
"Model `Results/260919_poly3to30keV_Moench014_c96_00`, the default since\n",
"2026-09-19: `[32, 64, 96, 96]`, flat 3-30 keV, chosen from four sizes in\n",
"`Results/overnight_poly3to30keV/report`. Reconstruction from\n",
"`scripts/05_infer_measurement.py --config Configs/infer_measurement_poly_Moench014.yaml\n",
"--run CuCrCo_1_factor_10 --save-photons` (FP16, masked column common mode). The previous\n",
"5-30 keV model's reconstructions are kept as `<run>_poly5to30`.\n",
"Compared throughout:\n",
"\n",
"* **grid direct** -- the model output;\n",
"* **grid corrected** -- $E = g^{-1}(E_\\mathrm{pred})$, where $g(E)$ is the MC\n",
" position of a line of true energy $E$ (`scripts/07_energy_response.py`);\n",
"* **classical 3x3** -- 3x3 sum around a local maximum on the same calibrated\n",
" frames (first 10 000 frames), no model.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "865cec36",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:29.570680Z",
"iopub.status.busy": "2026-09-19T09:10:29.570598Z",
"iopub.status.idle": "2026-09-19T09:10:29.817584Z",
"shell.execute_reply": "2026-09-19T09:10:29.817269Z"
}
},
"outputs": [],
"source": [
"import json\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"REPO = Path('/home/xie_x1/MLXID/GridBased')\n",
"sys.path.insert(0, str(REPO / 'src'))\n",
"from xrf_analysis import (EnergyCorrection, classical_and_model, fit_lines,\n",
" line_centroid, load_reconstruction)\n",
"\n",
"CONFIG = REPO / 'Configs/infer_measurement_poly_Moench014.yaml'\n",
"RUN = 'CuCrCo_1_factor_10'\n",
"EXPERIMENT = REPO / 'Results/260919_poly3to30keV_Moench014_c96_00'\n",
"RESPONSE = EXPERIMENT / 'energy_response.npz'\n",
"GRID = Path('/mnt/sls_det_storage/moench_data/MLXID/InferenceResults/GridBased/XRF_2603MaxIV')\n",
"EDGES = np.arange(0.0, 40.025, 0.05) # 50 eV bins\n",
"CENTRES = 0.5 * (EDGES[1:] + EDGES[:-1])\n",
"\n",
"FIT_GROUPS = [\n",
" # one joint fit; each Kb tied to its Ka with the tabulated offset. Co Kb\n",
" # cannot be separated from Cu Ka, so its Kb/Ka ratio is fixed at 0.135.\n",
" dict(label='Cr/Co/Cu K', alpha=['Cr_Ka', 'Co_Ka', 'Cu_Ka'],\n",
" beta={'Cr_Kb': 'Cr_Ka', 'Co_Kb': 'Co_Ka', 'Cu_Kb': 'Cu_Ka'},\n",
" ratio_fixed={'Co_Kb': 0.135}, fit_stretch=False, window=(4.85, 9.6)),\n",
" dict(label='elastic', alpha=[], extra=[('elastic', 12.0)], window=(10.9, 13.2)),\n",
"]\n",
"SPECTRUM_ZOOM = (4.0, 13.5)\n",
"LINES = [n for g in FIT_GROUPS for n in g['alpha']]\n",
"PEAKS = LINES + [e[0] for g in FIT_GROUPS for e in g.get('extra', ())]\n",
"COLOURS = {'grid direct': '#1f6fb4', 'grid corrected': '#10695f',\n",
" 'classical 3x3': '#6b6b6b'}"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "b782342f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:29.819386Z",
"iopub.status.busy": "2026-09-19T09:10:29.819219Z",
"iopub.status.idle": "2026-09-19T09:10:40.369835Z",
"shell.execute_reply": "2026-09-19T09:10:40.369529Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CuCrCo_1_factor_10: 100000 frames, 1985 photons/frame, occupancy 1.29 %, 0.7 % of the photons below 3 keV\n"
]
}
],
"source": [
"reconstruction = load_reconstruction(GRID, RUN)\n",
"summary = reconstruction['summary']\n",
"n_frames = summary['n_frames']\n",
"energy = np.array(reconstruction['photons'][:, 2])\n",
"correction = EnergyCorrection(RESPONSE) # core-based g\n",
"correction_mean = EnergyCorrection(RESPONSE, estimator='mean')\n",
"\n",
"reference = classical_and_model(CONFIG, RUN, n_frames=10_000) # cached\n",
"classical, grid_subset = reference['classical'], reference['grid']\n",
"n_subset = int(reference['n_frames'])\n",
"\n",
"energies = {'grid direct': energy, 'grid corrected': correction(energy),\n",
" 'classical 3x3': classical[:, 3]}\n",
"frames_of = {'grid direct': n_frames, 'grid corrected': n_frames,\n",
" 'classical 3x3': n_subset}\n",
"print(f\"{RUN}: {n_frames} frames, {summary['photons_per_frame']:.0f} photons/frame, \"\n",
" f\"occupancy {summary['occupancy_valid_region']*100:.2f} %, \"\n",
" f\"{(energy < 3).mean()*100:.1f} % of the photons below 3 keV\")"
]
},
{
"cell_type": "markdown",
"id": "d8f721c4",
"metadata": {},
"source": [
"## 1. The MC energy response\n",
"\n",
"The energy head is a regression trained on a flat 3-30 keV spectrum. On MC\n",
"(occupancy <= 2.5 %) the **core** of the predicted energy -- what a line fit\n",
"measures -- stays within ~25 meV of the truth from 4 to 28 keV and moves only\n",
"within ~0.75 keV of the edges (+98 meV at 3.2 keV, -93 meV at 29.6 keV), where\n",
"the energy scale is also compressed. The **mean** is pulled towards the centre\n",
"by ~0.1 keV at 5-12 keV and more near the edges, because the few per cent of\n",
"photons whose energy is off by more than 1 keV (the tails) are cut by the\n",
"bounded range. That mean is the `energy_bias_keV` of `eval_summary.json`. It\n",
"is not a line shift, and correcting with it moves lines at 5-12 keV by ~0.1 keV\n",
"the wrong way. The correction used here therefore inverts the core position\n",
"$g(E)$; with the 3-30 keV model it matters only below ~4 keV."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "a9ff6281",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:40.371116Z",
"iopub.status.busy": "2026-09-19T09:10:40.370965Z",
"iopub.status.idle": "2026-09-19T09:10:40.638231Z",
"shell.execute_reply": "2026-09-19T09:10:40.637990Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Cr_Ka: g - E = -7 meV, g' = 1.00, MC core sigma 0.256 keV\n",
"Co_Ka: g - E = +0 meV, g' = 1.00, MC core sigma 0.264 keV\n",
"Cu_Ka: g - E = +2 meV, g' = 1.00, MC core sigma 0.267 keV\n"
]
},
{
"data": {
"image/png": "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z/jnn38YNWoUs2fPZvTo0Tnq9P3337N//37mzZvHP//8Q7169Thw4ABt2rQhNjaWJUuWsG3bNry9vRk0aFCB9tYLCwtjzJgxbNiwgc2bN9O/f3/eeecdZs6cme9zC+OHH35gz549LFiwgDVr1pCUlMQLL7xAXFycNs3TtCUlJYVOnTqxcuVKJk6cyN9//81rr73G119/Tf/+/QGoWrUqfn5+NG7cGDc3N/z8/PDz88PHxyfXPLds2YKbmxuNGzfWpt2yZYtOmsmTJxMeHs6SJUv466+/cHR0LFS/fPnllwwePJgGDRqwYcMGVq1aRUJCAu3atSMoKOixzy3s66c47sHIkSMxNjZm1apV/PHHHxgbGzNlyhSmTJlCjx492LZtG2PHjuWNN97gypUr2udZWVkxcuRI1qxZo1M+ZC33NjExyfUNzKMURSEjIyPHV36B7vv37wPw+eef8/fff7N8+XLc3Nzo2LFjroHjRYsWsXPnThYsWMDq1asxMDDg+eefx8/PL0fafv36Ubt2bf744w+mTZvG1q1b6d69O+np6do0Bb3vYWFh1K1blwULFrBr1y6++uorIiIiaNasGffu3ctRdm73Y+jQoWzdupXPPvuM3bt3s2zZMrp27UpMTEy+/SuEEKJsKy3jashastqsWTMCAwP55ptv2L59Oz179mT8+PFMnz49R/pPPvmEGzdusGzZMpYuXUpISAi9evUiMzNTm2b16tV069YNGxsbfvvtNzZs2EDFihXp3r17jkAbQP/+/alduzYbN25kyZIlALz++ussWLCA119/nW3btvHSSy/Rr18/nUCWi4sLvXv3ZsmSJTrlQ9YYoVq1avTr1y/fPsht3FjQNhTk7/ns2bMZNWoUDRs2ZPPmzXz33XecP3+eVq1aERISkm/98tOzZ0++/PJLIGtcmT0+7tmzZ67pFUWhb9++zJs3j6FDh/L3338zceJEfvvtNzp37qz9MD9bQd4/CSEeQxGinFm+fLkCKKdOnVIOHDigAEpgYKCiKIrSrFkzZcSIEYqiKErDhg2VDh06aJ/Xo0cPBVBSUlIKXJahoaHSuXPnfNO99tpripubm/b7rl27KqNHj1bs7OyU3377TVEURTl69KgCKLt379amc3Z2VszMzJQbN25oryUnJysVK1ZUxowZo702ZswYxcrKSiedoijKvHnzFEC5ePGioiiKEhoaqgBKrVq1lLS0NJ209erVUxo3bqykp6frXH/xxReVqlWrKpmZmfm2M1tmZqaSnp6uzJgxQ6lUqZKi0Wgem/7zzz9XHv111KFDB537k113T09PJSMjQ3v95MmTCqCsXbu2SNqyZMkSBVA2bNigc/2rr77KcX86dOigNGzY8LFty/bo6y1b9mu0ffv2OR7LrV8U5b/XeGhoqKIoihIeHq4YGRkp77zzjk66hIQEpUqVKsrAgQMfW7fCvn6K8h5kt2XYsGE66e7fv6+YmpoqgwYN0rnu5+enADp9ee3aNcXAwED59ttvtdeSk5OVSpUqKa+//vpj264oigLk+bVq1ap8n/+wjIwMJT09XenSpYvSr18/7fXsvqtWrZqSnJysvR4fH69UrFhR6dq1q/Za9n1/7733dPJes2aNAiirV69WFOXp7ntGRoaSmJioWFpaKt999532el73Q1EUxcrKSpkwYUIBe0IIIUR5UBrH1d27d1eqV6+uxMXF6Vx/++23FTMzM+X+/fuKovw3xnrhhRd00m3YsEEBFD8/P0VRFCUpKUmpWLGi0qtXL510mZmZSqNGjZTmzZtrr2X/jf7ss8900l68eFEBlI8++kjn+tq1axVAGT58uPZadr22bNmivXb79m3FyMhImT59+mPbnte4sTBtyO/v+YMHDxRzc/Mc/RYeHq6YmpoqQ4YM0V7LbawKKJ9//nmOfJ2dnXX6YePGjQqgHDhwIEfa4cOHK87Oztrvd+7cqQDK119/rZNu/fr1CqAsXbpUp5yCvH8SQuRNZhSKcq1Dhw7UqlWLX3/9lQsXLnDq1KkCzS4qqIyMjFw/ZXxUly5duH79OqGhoaSkpODr60uPHj3o1KkTe/bsAbJmGZqamtK2bVud53p7e1OzZk3t92ZmZtSpU0dnyeD27dvp1KkT1apV05kN9fzzzwNZG00/rHfv3jp7wly9epXLly9r99p7OI8XXniBiIiIXJdoPGz//v107doVW1tbDA0NMTY25rPPPiMmJoaoqKh8+6igevbsiaGhofZ7Ly8vAG1/PG1b9u/fj6WlJQMGDNC5nr1UoiD3+0m89NJLT/zcXbt2kZGRwbBhw3Taa2ZmRocOHfJcEp2tsK+f4rgHj7b/+PHjpKamMnDgQJ3rLVu2zLEUxc3NjRdffJHFixdrZwD+/vvvxMTE8Pbbbz+27dkGDhzIqVOncny98MIL+T53yZIl+Pj4YGZmhpGREcbGxuzbty/X5Tn9+/fHzMxM+721tTW9evXi8OHDOWYWPLr35cCBAzEyMtJuFVCY+56YmMhHH31E7dq1MTIywsjICCsrK5KSknKtZ26vx+bNm7NixQpmzZrF8ePHdWY2CiGEKP9Kw7gLine truncated
"text/plain": [
"<Figure size 1300x420 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"response = np.load(RESPONSE)\n",
"centre = response['table_centre']\n",
"figure, axes = plt.subplots(1, 2, figsize=(13, 4.2))\n",
"for curve, colour, label in ((correction, COLOURS['grid corrected'], 'core of E_pred (used)'),\n",
" (correction_mean, '#c0392b', 'mean of E_pred')):\n",
" axes[0].plot(curve.grid, curve.g_grid - curve.grid, color=colour, lw=1.5, label=label)\n",
"for name in LINES:\n",
" axes[0].axvline(line_centroid(name), color='k', lw=.5, ls=':')\n",
"axes[0].axhline(0, color='k', lw=.6); axes[0].set_ylim(-0.6, 0.3)\n",
"axes[0].set_xlabel('E_true [keV]'); axes[0].set_ylabel('g(E) - E [keV]')\n",
"axes[0].set_title('MC: where a line of true energy E appears'); axes[0].legend(fontsize=8)\n",
"axes[0].grid(alpha=.3)\n",
"axes[1].plot(centre, response['table_core_sigma'], 'k.', ms=3, label='core sigma, raw')\n",
"axes[1].plot(correction.grid, correction.mc_sigma(correction.grid) / correction.slope(correction.grid),\n",
" color=COLOURS['grid corrected'], lw=1.2, label=\"core sigma / g' (true keV)\")\n",
"axes[1].set_ylim(0, 0.4); axes[1].set_xlabel('E_true [keV]'); axes[1].set_ylabel('keV')\n",
"axes[1].set_title('MC: energy resolution'); axes[1].legend(fontsize=8); axes[1].grid(alpha=.3)\n",
"figure.tight_layout()\n",
"for name in LINES:\n",
" e = line_centroid(name)\n",
" print(f\"{name}: g - E = {(correction.forward(np.array([e]))[0] - e)*1000:+.0f} meV, \"\n",
" f\"g' = {correction.slope(e):.2f}, MC core sigma {correction.mc_sigma(e):.3f} keV\")"
]
},
{
"cell_type": "markdown",
"id": "2d694291",
"metadata": {},
"source": [
"## 2. Spectra\n",
"\n",
"Per frame, 50 eV bins; grey = outside the 3-30 keV training range."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "88a7f95b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:40.639432Z",
"iopub.status.busy": "2026-09-19T09:10:40.639337Z",
"iopub.status.idle": "2026-09-19T09:10:49.511881Z",
"shell.execute_reply": "2026-09-19T09:10:49.511595Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1400x850 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"histograms = {name: np.histogram(values, EDGES)[0] for name, values in energies.items()}\n",
"figure, axes = plt.subplots(2, 1, figsize=(14, 8.5))\n",
"for axis, log in zip(axes, (True, False)):\n",
" for name, counts in histograms.items():\n",
" axis.plot(CENTRES, counts / frames_of[name], lw=1, color=COLOURS[name], label=name)\n",
" for name in LINES:\n",
" axis.axvline(line_centroid(name), color='k', lw=.4, ls='--')\n",
" axis.axvspan(0, 3, color='0.9'); axis.axvspan(30, 40, color='0.9')\n",
" axis.set_xlabel('energy [keV]'); axis.set_ylabel('photons / frame / 50 eV')\n",
" axis.grid(alpha=.3)\n",
" if log:\n",
" axis.set_yscale('log'); axis.set_xlim(2, 33); axis.set_ylim(1e-4, None)\n",
" axis.legend(fontsize=9); axis.set_title(RUN)\n",
" else:\n",
" axis.set_xlim(*SPECTRUM_ZOOM)\n",
"figure.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "02b8297f",
"metadata": {},
"source": [
"## 3. Line positions and energy resolution\n",
"\n",
"Grid lines: Gaussian core + a wide Gaussian at the same position (the tails of\n",
"section 1) + an erfc low-energy step, on an exp(polynomial) background.\n",
"Classical lines: Gaussian + step. Ka is fitted as the tabulated doublet, so the\n",
"position is the intensity-weighted centroid. `sigma` is the core width; FWHM is\n",
"that of the whole line shape."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e00064c3",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:49.513200Z",
"iopub.status.busy": "2026-09-19T09:10:49.513104Z",
"iopub.status.idle": "2026-09-19T09:10:54.511973Z",
"shell.execute_reply": "2026-09-19T09:10:54.511727Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" table | position [keV] | core sigma [keV] | FWHM [keV] | MC sigma\n",
" | direct corr. 3x3 | direct corr. 3x3 | grid 3x3 ratio |\n",
"Cr_Ka 5.41 | 5.42 5.42 5.46 | 0.245 0.245 0.398 | 0.58 0.94 0.62 | 0.256\n",
"Co_Ka 6.93 | 6.95 6.95 6.99 | 0.265 0.264 0.408 | 0.62 0.96 0.65 | 0.264\n",
"Cu_Ka 8.04 | 8.08 8.07 8.11 | 0.305 0.305 0.441 | 0.72 1.04 0.69 | 0.267\n",
"elastic | 11.99 11.99 12.05 | 0.311 0.312 0.470 | 0.73 1.11 0.66 | 0.279\n",
"grid FWHM: direct output\n"
]
}
],
"source": [
"fits = {}\n",
"for variant, counts in histograms.items():\n",
" for group in FIT_GROUPS:\n",
" fits[variant, group['label']] = fit_lines(\n",
" CENTRES, counts, group['alpha'], group.get('beta'), window=group['window'],\n",
" beta_ratio_fixed=group.get('ratio_fixed'),\n",
" fit_stretch=group.get('fit_stretch', True), extra_peaks=group.get('extra', ()),\n",
" tails=variant != 'classical 3x3')\n",
"\n",
"\n",
"def peak(variant, name):\n",
" group = next(g for g in FIT_GROUPS\n",
" if name in g['alpha'] or name in [e[0] for e in g.get('extra', ())])\n",
" return fits[variant, group['label']]['lines'][name]\n",
"\n",
"\n",
"def fwhm_of(variant, name):\n",
" \"\"\"FWHM of the line shape; for the extra peaks (no doublet) from sigma.\"\"\"\n",
" line = peak(variant, name)\n",
" return line.get('fwhm', 2.3548 * line['sigma'])\n",
"\n",
"\n",
"print(f\"{'':8s} {'table':>6s} | {'position [keV]':^26s} | {'core sigma [keV]':^26s} | \"\n",
" f\"{'FWHM [keV]':^20s} | MC sigma\")\n",
"print(f\"{'':8s} {'':>6s} | {'direct':>8s} {'corr.':>8s} {'3x3':>8s} | \"\n",
" f\"{'direct':>8s} {'corr.':>8s} {'3x3':>8s} | {'grid':>6s} {'3x3':>6s} {'ratio':>6s} |\")\n",
"for name in PEAKS:\n",
" table = f\"{line_centroid(name):6.2f}\" if name in LINES else f\"{'':6s}\"\n",
" positions = [peak(v, name)['mu'] for v in energies]\n",
" sigmas = [peak(v, name)['sigma'] for v in energies]\n",
" grid_fwhm = fwhm_of('grid direct', name)\n",
" classical_fwhm = fwhm_of('classical 3x3', name)\n",
" mc = correction.mc_sigma(line_centroid(name)) if name in LINES else \\\n",
" correction.mc_sigma(peak('grid corrected', name)['mu'])\n",
" print(f\"{name:8s} {table} | \" + ' '.join(f'{p:8.2f}' for p in positions) + ' | ' +\n",
" ' '.join(f'{s:8.3f}' for s in sigmas) +\n",
" f\" | {grid_fwhm:6.2f} {classical_fwhm:6.2f} {grid_fwhm/classical_fwhm:6.2f} | {mc:.3f}\")\n",
"print('grid FWHM: direct output')"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "d7c0b45e",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:54.513111Z",
"iopub.status.busy": "2026-09-19T09:10:54.513023Z",
"iopub.status.idle": "2026-09-19T09:10:55.229598Z",
"shell.execute_reply": "2026-09-19T09:10:55.229316Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABtYAAAJOCAYAAAAj/DNqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzddVxV9xvA8c+lG0RQQcHGDlRsxFbsqbO7c1MXxsL1jG06J7o5nV3bnM6J3cxEFAsDFJvujnvP7w9+uxsmKnCJ5/168dJ74nuec8Tz3HO+pVIURUEIIYQQQgghhBBCCCGEEEII8Vx6ug5ACCGEEEIIIYQQQgghhBBCiMJAKtaEEEIIIYQQQgghhBBCCCGEyAGpWBNCCCGEEEIIIYQQQgghhBAiB6RiTQghhBBCCCGEEEIIIYQQQogckIo1IYQQQgghhBBCCCGEEEIIIXJAKtaEEEIIIYQQQgghhBBCCCGEyAGpWBNCCCGEEEIIIYQQQgghhBAiB6RiTQghhBBCCCGEEEIIIYQQQogckIo1IYQQQgghhBBCCCGEEEIIIXJAKtaEeIynpyfLli175vpevXrxySef5Li8/27v4+NDuXLlXjNCIYQQQgghhBD/kGe4fx09ehQbG5s8KXvChAnMnDkzT8oWQoiiokKFCuzYsSPPys+te/GaNWuoX7/+6wekA5988gm9evXKk7Jr1arFrl278qRsUbRIxZoQj9mzZw+TJk3Kk7Ld3d158OBBnpR9584dVCoVsbGxL73vX3/9RatWrbC0tKRkyZI0btyYH3/88YX7nThxIlsS/vvvv/H09KREiRLY2NhQr149FixYQHp6eo5jGTt2LIsXLwZArVbz7bffUrt2bczNzXFwcKBz584cOnToZU8ReDLxJicn4+npSatWrYiLi3ulMoUQIq/IA5kQQgiRM8XxGS6vPe2l5Y8//sj8+fN1E5AQQggg/+7FU6dOxcnJCSsrK8qWLcu0adNy/H5vz5491KlThxIlSmBra0uHDh24fPlyHkf88lq3bq19B/mPq1ev0q1bN90EJAoVqVgT4v8URUGtVus0hszMzHw/5vLlyxk+fDhjx47lwYMHREZGsmzZsme2zvhvjH/99Rfdu3cHYNeuXXh6etKpUycCAwOJjY1l69atBAQEEBISkqNYFEXB29tbW+bgwYP55Zdf8PLyIjo6mrt37zJlyhS2bdv2mmcNsbGxdOjQAT09Pfbt24e1tfVrlymEEIVJcXkge51GGl9++SXTpk3Tfl69ejWNGjXCwsKC0qVL4+HhwW+//fZS8VStWhV/f38AoqKieOuttyhfvjwWFhZUqFCBESNGcPPmzZcq8x+PPxjeunWLSpUq8fbbb6MoyiuVKYQQBVlxfYYTQggh8tqkSZO4fv068fHx+Pv7c/HiRRYsWJCjfevXr8/+/fuJiYkhPDycrl275lkPMyF0RSrWRJH24MEDOnTogJWVFQ0bNuSrr76iQoUK2vUVKlTg66+/pmnTppiZmREQEPDES6lt27ZRpUoVrK2tGTt27AsfnJ63/ePDcrRu3Zr333+fjh07Ym5uzp49e0hMTGTKlCk4OztTqlQphg0blq03VWBgID169MDe3h5bW1t69+4NQOPGjQEoV64cFhYWbNy48YXXJyEhgZkzZ7JkyRKGDh2KtbU1KpWKRo0aaSvW/ol5+fLlODs706xZM+3+O3fupEePHiiKwltvvcXMmTOZNm0adnZ2AFSvXp01a9ZQvnx5AM6dO0eLFi2wsbGhZs2abN68OVs8vr6+lChRgsqVK3Ps2DG2b9/Ozp078fDwwNjYGCMjI7p166Yd5mXEiBHZXnjGxsaiUqm4c+fOc887NDQUDw8PnJ2d2bFjB6ampi+8VkIIIV6Nrh/IXraRxrMakMyaNYuPP/6YuXPnEhYWxqNHj/j000/Zvn17jmO5fv06aWlp1K9fn7i4OJo3b87du3fZt2+f9vo0adKEPXv2vNQ5Ps2lS5do2bIlw4YN4/vvv0elUr12mUIIkR/kGe7FwsPDGTx4MI6Ojjg6OjJt2jTS0tKeuu3GjRupXbs2lpaWODs789FHH2kbWyiKwsyZMylTpgxWVla4uLiwa9cuduzYwVdffcWuXbuwsLDAwsICePL561nnJYQQRV18fLz2vm9lZYWbmxv3799/Yrt79+7RoUMH7O3tKVGiBF27ds32zurAgQPUrVsXS0tLSpcuzcSJEwFIS0tj1KhR2NnZYW1tTe3atfH19QVe7l48ZMgQHB0dtTn1yJEjOT7HGjVqYG5urv2sp6dHYGAgAPfv38fOzo4DBw4AkJ6eToMGDfjss88AcHBwwMHBAcjKNfr6+ty9e5eMjIwcH//8+fO0adMGW1tbqlSpws8///zMbd9//33Kly+PpaUlNWvWzNb4MTo6mjfeeANbW1tsbGxo2LAhd+/e5Z133sHHx4eZM2diYWGBp6cn8OTIMQcOHKBJkybY2Njg4ODA119/neNzEEWbVKyJIm3QoEGUL1+esLAwNm/ezKpVq57YZs2aNaxdu5bExESqVauWbV1gYCCDBg1i0aJFREVF0bBhQ/bu3fvM473s9v8c/4svviAxMZH27dszatQooqOjuXTpEsHBwWRkZDBlyhQAkpKSaN++PbVr1+bOnTuEhoYydepUAM6ePQtkPYgmJiYyePBgAOrWrcumTZueeuxTp06RlJREv379nhtjQkICFy9e5Pr16xw7dgyA27dvExsbS6NGjQgMDCQ4OJiBAwc+s4zY2Fg6d+7MgAEDiIiIYPny5YwdO5YTJ05ot/nrr7/o0aMHAPv27aNx48ZUrlz5ubG9rIcPH9KyZUuaN2/Oxo0bMTQ0zNXyhRDiZcgDWd4+kOWkkcYnn3xCt27dmDhxIra2ttrhMcPCwrh58yatWrXi9u3bfPPNN2zatInu3btjbm6Ovr4+rVu3zpZjN2zYQI0aNbCxsaFly5ZcuHAhWzw7d+7UVtQtXrwYlUrFtm3bqF69Onp6etjY2DBx4kTefvtt4MmHuh07dmR7ufwsJ0+epE2bNsyZM+el5hQSQoiCQJ7hnv8MpygKPXr0oEyZMgQFBXH58mUuXrzIF1988dTtbW1t+eOPP4iPj2fnzp2sWLFCW/aBAwfYtGkT58+fJz4+noMHD+Li4kKvXr2YM2cO3bp1IzExkcTExCfKfd55CSFEUTdixAiCgoI4ffo0sbGxrFix4qmNtjUaDTNmzOD+/fvcvXsXMzMzxo4dq10/fPhw3nvvPRISErh9+zZDhw4FYO3atVy8eJGgoCBiY2P5448/KFOmzBPlv+he3K5dO65du0ZUVBQDBgygb9++JCQk5Pg8582bh6WlJaVKleLixYvasp2cnFixYgXDhg0jPDxcWzn1wQcfaPe9d+8eNjY2mJiY8PbbbzN79mztO7h/1t27d++pxw0NDaVDhw5MnDiRiIgIduzYwdy5c5856ki9evXw9fUlNjaWjz/+mKFDhxIcHAzAN998Q2ZmJg8ePCAqKopVq1ZhaWnJt99+i7u7O/PnzycxMfGpjRsvXLhAz549ef/994mIiOD69eu0adMmx9dPFG1SsSaKrPv37+Pj48O8efMwNTXFxcWFCRMmPLHdxIkTqVatGvr6+hgZGWVbt2XLFtq1a0f37t0xMDBgwoQJVK1a9ZnHfNntIevBsXHjxqhUKhITE9m2bRtLly7FxsYGc3NzPvvsM7Zu3YparWbXrl0YGhry5ZdfYm5ujpGR0Qtv6JcuXWLQoEFPXRcREYG9vf0T5/04jUbDvHnzMDMzw8zMDMh6OditWzdUKhUREREAlC1b9plleHt7Y29vz9SpUzE0NMTDw4NBgwaxdu1a7Tb/7RkQERHx3PJe1Y0bN7h//z6jRo1CT09ugUII3ZIHsrx9IMtpI429e/fSpEkTwsPD+fzzz4GsIY47deqEoaEhBw8exMHBAXd392eW4ePjw8SJE/npp5+IiIigb9++dOrUKVuPhccbkLz55psYGBjk4Arm3OHDh/H09GTx4sXyglMIUejIM1yW5z3DnTt3jsDAQBYuXIiZmRklS5Zkzpw5z6yI8/T0xMXFBZVKRf369Rk4cCBHjx4FwNDQkNTUVK5evUpGRgbOzs64uLg8N7Z/vMp5CSFEURAWFsb27dtZsWIFjo6O6Onp4erqqh296b8qVKiAp6cnJiYmWFlZ8cEHH3D8+HE0Gg2QdR8OCgoiIiICc3Nzmjdvrl2ekJDAtWvXUBQFFxcXnJycnij/RffikSNHYm1tjaGhIe+99x4ajYZLly7l+FxnzZpFQkICAQEBTJgwIduzZO/evenRowft27dn3bp1bNy4EX19fe16Z2dnYmNjiY2NZcmSJTRq1OiJdc7Ozk897vr162nVqhX9+vVDX1+f2rVrM3LkyGfmusGDB1OqVCn09fUZMGAA1atX5+TJk9prGRUVRWBgIPr6+tSvXx9bW9scnf+KFSsYMGAAffr0wdDQEGtra5o2bZqjfUXRJ2+VRZH16NEjTExMsiW2p92wn3UT/6eMf4Yx/Mfjn19n+8ePf+fOHTQaDZUqVcLGxgYbGxvc3NzQ09MjNDSUu3fvUrly5VwbzsnOzo7IyMgXznVjaWmZbfgT+HcYyH/KgazeYM/y4MGDJ1rZV6pUSTsR+L179wgJCdEmKDs7u+eW96ratm3L3Llz6dixo7aFqBBC6II8kGXJyweynDbSqF27NiNGjMDAwEDbgORlG3usW7eOIUOG0KpVKwwNDZk2bRolSpTA29sbyJpP7cqVK7Ru3fqlYntZR48epVSpUnTp0iXXyxZCiLwmz3AvdufOHWJjY7VDWtnY2NC3b1/CwsKeuv2+ffto3ry5tvf6jz/+SGRkJABt2rTh008/5aOPPsLOzo4+ffpoW/i/SG6flxBCFBZ3797F2Nj4ubnoHxEREQwaNEg753SrVq1IT0/XNlLcvn07V65coVq1ariLine truncated
"text/plain": [
"<Figure size 1760x600 with 8 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"shown = ('grid direct', 'classical 3x3')\n",
"figure, axes = plt.subplots(2, len(FIT_GROUPS) * 2, figsize=(4.4 * len(FIT_GROUPS) * 2, 6),\n",
" gridspec_kw=dict(height_ratios=[3, 1]), squeeze=False)\n",
"for column, (group, variant) in enumerate((g, v) for g in FIT_GROUPS for v in shown):\n",
" fit = fits[variant, group['label']]\n",
" x = fit['centres']\n",
" axes[0, column].plot(x, fit['counts'], 'k.', ms=2.5)\n",
" axes[0, column].plot(x, fit['model'](x), color=COLOURS[variant], lw=1.3)\n",
" for part, curve in fit['components'](x).items():\n",
" axes[0, column].plot(x, curve, lw=.7, ls='--', label=part)\n",
" axes[0, column].set_yscale('log')\n",
" axes[0, column].set_ylim(max(fit['counts'].min() * 0.5, 1), fit['counts'].max() * 2)\n",
" axes[0, column].set_title(f\"{variant}: {group['label']}\", fontsize=9)\n",
" axes[0, column].legend(fontsize=6)\n",
" axes[1, column].plot(x, (fit['counts'] - fit['model'](x)) / np.sqrt(np.maximum(fit['counts'], 1)),\n",
" '.', ms=2.5, color=COLOURS[variant])\n",
" axes[1, column].axhline(0, color='k', lw=.6)\n",
" axes[1, column].set_xlabel('energy [keV]'); axes[1, column].set_ylabel('pull')\n",
"figure.tight_layout()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "48f4f57f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:55.230821Z",
"iopub.status.busy": "2026-09-19T09:10:55.230692Z",
"iopub.status.idle": "2026-09-19T09:10:55.331040Z",
"shell.execute_reply": "2026-09-19T09:10:55.330825Z"
}
},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 750x440 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"figure, axis = plt.subplots(figsize=(7.5, 4.4))\n",
"for offset, variant in zip((-0.08, 0.0, 0.08), energies):\n",
" axis.errorbar([line_centroid(n) + offset for n in LINES],\n",
" [peak(variant, n)['sigma'] for n in LINES],\n",
" [peak(variant, n)['sigma_err'] for n in LINES],\n",
" fmt='o', ms=5, capsize=3, color=COLOURS[variant], label=variant)\n",
"e = np.linspace(min(line_centroid(n) for n in LINES) - 1, max(line_centroid(n) for n in LINES) + 1, 200)\n",
"e = e[(e >= 5) & (e <= 30)]\n",
"axis.plot(e, correction.mc_sigma(e), 'k--', lw=1, label='MC core sigma (raw)')\n",
"axis.plot(e, correction.mc_sigma(e) / correction.slope(e), 'k:', lw=1, label=\"MC / g' (true keV)\")\n",
"axis.set_xlabel('line energy [keV]'); axis.set_ylabel('core sigma [keV]'); axis.set_ylim(0, None)\n",
"axis.set_title('energy resolution'); axis.legend(fontsize=8); axis.grid(alpha=.3)\n",
"figure.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "32c28598",
"metadata": {},
"source": [
"## 4. Reconstruction efficiency\n",
"\n",
"Without truth, three handles on the 10 000 frames of the classical reference:\n",
"\n",
"* **isolated photons**: classical clusters with no other cluster within 4 px\n",
" that have a grid photon within 1.5 px. Above 5 keV the 2.5 keV classical\n",
" threshold finds every isolated photon, so this is the absolute efficiency.\n",
" The MC equivalent is photons with no neighbour within 4 px.\n",
"* **pile-up**: classical clusters holding two or more grid photons within 2 px.\n",
"* **energy balance**: summed reconstructed energy / deposited energy per frame\n",
" (calibrated frame minus its baseline)."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "045ab180",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:55.332205Z",
"iopub.status.busy": "2026-09-19T09:10:55.332119Z",
"iopub.status.idle": "2026-09-19T09:10:56.819582Z",
"shell.execute_reply": "2026-09-19T09:10:56.819355Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"isolated photons >= 5 keV found: 99.99 % (3-5 keV: 100.0 %; MC: 99.96 %)\n",
"grid / classical photon count: 1.067\n",
"classical clusters with >= 2 grid photons: 10.2 % (Poisson, second photon within 2 px: 15.0 %)\n",
"energy balance: grid 0.9961, classical 0.9991\n"
]
},
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 750x420 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"match = reference['classical_match']\n",
"found = match >= 0\n",
"isolated = reference['isolation_classical'] > 4.0\n",
"above5 = classical[:, 3] >= 5\n",
"edges_e = np.arange(2.5, 30.01, 0.5)\n",
"which = np.digitize(classical[:, 3], edges_e) - 1\n",
"fraction = np.array([found[isolated & (which == i)].mean() if (isolated & (which == i)).sum() > 200\n",
" else np.nan for i in range(len(edges_e) - 1)])\n",
"\n",
"mc = json.load(open(EXPERIMENT / 'eval_summary.json'))['1%']['pair']\n",
"far = [i for i, edge in enumerate(mc['edges']) if edge >= 4.0]\n",
"mc_isolated = sum(mc['n_match'][i] for i in far) / sum(mc['n_true'][i] for i in far)\n",
"\n",
"figure, axis = plt.subplots(figsize=(7.5, 4.2))\n",
"axis.plot(0.5 * (edges_e[1:] + edges_e[:-1]), fraction * 100, 'o-', ms=3,\n",
" color=COLOURS['grid direct'], label='isolated classical clusters found by the grid')\n",
"axis.axhline(mc_isolated * 100, color='k', ls='--', lw=1, label='MC, isolated photons, 1 %')\n",
"axis.axvspan(0, 3, color='0.9'); axis.set_ylim(90, 100.5); axis.set_xlim(2.5, 30)\n",
"axis.set_xlabel('classical 3x3 energy [keV]'); axis.set_ylabel('%')\n",
"axis.set_title('detection efficiency'); axis.legend(fontsize=8); axis.grid(alpha=.3)\n",
"figure.tight_layout()\n",
"\n",
"multiplicity = reference['grid_per_classical']\n",
"occupancy = summary['occupancy_valid_region']\n",
"balance = reference['balance'][:, 1].sum()\n",
"low = (classical[:, 3] >= 3) & ~above5\n",
"print(f\"isolated photons >= 5 keV found: {found[isolated & above5].mean()*100:.2f} % \"\n",
" f\"(3-5 keV: {found[isolated & low].mean()*100:.1f} %; MC: {mc_isolated*100:.2f} %)\")\n",
"print(f\"grid / classical photon count: {len(grid_subset) / len(classical):.3f}\")\n",
"print(f\"classical clusters with >= 2 grid photons: {(multiplicity >= 2).mean()*100:.1f} % \"\n",
" f\"(Poisson, second photon within 2 px: {(1 - np.exp(-occupancy * np.pi * 4))*100:.1f} %)\")\n",
"print(f\"energy balance: grid {grid_subset[:, 3].sum() / balance:.4f}, \"\n",
" f\"classical {classical[:, 3].sum() / balance:.4f}\")"
]
},
{
"cell_type": "markdown",
"id": "4e5818b8",
"metadata": {},
"source": [
"## Conclusions\n",
"\n",
"* **Lines.** The grid separates Cr, Co and Cu Ka cleanly; the classical 3x3\n",
" sum shows Cu Ka only as a shoulder. Grid positions within 0.04 keV of the\n",
" tables, classical 0.05-0.07 keV high (the LUT reads ~0.9 % high at\n",
" 5-8 keV). The pile-up continuum at 13-25 keV is 5-10x lower than in the\n",
" classical spectrum.\n",
"* **Cr Ka and the training range.** With Cr Ka 2.4 keV inside the 3-30 keV\n",
" range, the model puts it at +0.01 keV with a 0.245 keV core (MC 0.256)\n",
" straight away. The previous 5-30 keV model, whose edge sat 0.4 keV below the\n",
" line, gave +0.03 keV with a compressed 0.18 keV core, and its MC correction\n",
" overshot. Here the correction changes nothing (<= 7 meV).\n",
"* **Energy resolution.** FWHM 0.58 / 0.62 / 0.72 keV for Cr / Co / Cu Ka and\n",
" 0.73 keV for the elastic line, against 0.94 / 0.96 / 1.04 / 1.11 keV for the\n",
" classical sum: 31-38 % narrower. The core widths agree with the MC within\n",
" +-5 % for Cr and Co; Cu and the elastic line are 12-14 % wider.\n",
"* **Efficiency.** 99.99 % of the isolated photons above 5 keV and 100 % at\n",
" 3-5 keV (MC 99.96 %). The grid finds 6.7 % more photons than the classical\n",
" finder (pile-up), and the energy balance is 0.996."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "403d0ea6",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-19T09:10:56.820785Z",
"iopub.status.busy": "2026-09-19T09:10:56.820668Z",
"iopub.status.idle": "2026-09-19T09:10:56.892665Z",
"shell.execute_reply": "2026-09-19T09:10:56.892382Z"
}
},
"outputs": [],
"source": [
"output = dict(\n",
" run=RUN, epoch=summary['epoch'], n_frames=n_frames,\n",
" photons_per_frame=summary['photons_per_frame'], occupancy=occupancy,\n",
" lines={name: {variant: dict(mu=float(peak(variant, name)['mu']),\n",
" sigma=float(peak(variant, name)['sigma']),\n",
" fwhm=float(fwhm_of(variant, name)))\n",
" for variant in energies}\n",
" for name in PEAKS},\n",
" efficiency_isolated_above5keV=float(found[isolated & above5].mean()),\n",
" efficiency_isolated_mc=mc_isolated,\n",
" grid_over_classical_count=len(grid_subset) / len(classical),\n",
" energy_balance_grid=float(grid_subset[:, 3].sum() / balance),\n",
")\n",
"with open(GRID / RUN / 'xrf_energy_analysis.json', 'w') as handle:\n",
" json.dump(output, handle, indent=1)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch_nightly",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+635
View File
@@ -0,0 +1,635 @@
# GridBased
Grid-based X-ray reconstruction for MÖNCH. A fully-convolutional network reads a
calibrated frame and predicts, for **every pixel cell**, whether a photon
converted there, where inside the cell, and with what energy. The number of
photons in a frame is an output of the model, so one set of weights covers any
multiplicity and any flux, and those same weights run on a 32×32 training frame
or on a full detector frame.
Design rationale, work-package split and open questions:
[`docs/WORKFLOW_DESIGN.md`](docs/WORKFLOW_DESIGN.md).
**Stage 1: 12 keV monochromatic, MOENCH040 @150 V** — the point where this
workflow can be compared head-to-head with the published cluster-based results
(σ = 0.8 µm on MC, MTF50 = 160 lp/mm at DanMAX). Done: gate G2 on MC, knife
edge at four flux points.
**Stage 2 (current): polychromatic 3–30 keV, MOENCH014 @150 V** — per-photon
energy from the same network. Done: α(E,t)/β(E,t) charge LUT, MC training and
evaluation, XRF lines at 2603 MaxIV (energy resolution and efficiency against the
classical 3×3 sum), and a scan of network sizes. Default model:
`Results/260919_poly3to30keV_Moench014_c96_00`, `[32, 64, 96, 96]`.
---
## Pipeline
```
McGeneration single-photon stamps (12 keV: existing, 10 M events, noiseless 5×5)
or α(E,t)/β(E,t) charge-cloud LUT (3–30 keV: scripts/01_build_charge_lut.py)
│ scripts/02_generate_frames.py ← superpose stamps / sample clouds + noise
▼
MC frames + truth photon list (HDF5)
│ scripts/03_train.py ← fully-convolutional GridNet
▼
model checkpoint
│ scripts/04_evaluate.py ← efficiency/purity/RMS vs occupancy
│ scripts/07_energy_response.py ← MC line position g(E) and width (poly)
│ scripts/05_infer_measurement.py ← raw ADU → photon list → image
▼
benchmark plots, reconstructed images
```
## Conventions
* Pixel `(row r, col c)` covers `x ∈ [c, c+1)`, `y ∈ [r, r+1)`. A photon position
is a float in `[0, S)`; its cell is `(⌊y⌋, ⌊x⌋)` and its in-cell offset is
`(x−c−0.5, y−r−0.5) ∈ [−0.5, 0.5]`.
* Energies are keV throughout; positions are pixels (25 µm) unless a name says `_um`.
* **Duplicate suppression works in position space.** Every cell predicts a
physical position, so two cells that point at the same place — within
`eval.merge_distance` (0.1 px in the current configs) — are merged and the
stronger one kept, while two
cells that point at different places both survive. Predictions that merge are
always less than 1 px apart and therefore in adjacent cells, so comparing the 8
neighbours is enough and the decode costs the same as a max-pool.
* **Receptive-field radius R = number of 3×3 conv layers = the halo.** Cells
within R of a frame border are supervised nowhere and reported nowhere, so the
zero padding stays invisible to everything that is used.
`scripts/00_selftest.py` checks that a 32×32 frame and the same content embedded
in a larger frame give bit-identical predictions inside the halo — that is what
makes train-small / infer-large exact — and that R is tight, the ring at R−1
already differing.
* **One photon per cell sets a known ceiling.** For a Poisson field of occupancy
λ, a single slot per 25 µm cell can represent a fraction
`(1−e^−λ)/λ ≈ 1 − λ/2` of the photons: 99.5 % at 1 %, 97.5 % at 5 %, 95.2 % at
10 %, 90.6 % at 20 %, 78.7 % at 50 %. The generator reports the loss per shard
as `n_same_cell`, and it follows the formula to a few per mille. The ceiling is
not a hard bound on the *measured* efficiency: the offset channels are unbounded,
so a cell can report a position inside its neighbour.
## Data layout
One HDF5 file per shard:
| dataset | shape | meaning |
|---|---|---|
| `frames` | (N, S, S) f4 | calibrated frame, keV |
| `photons` | (M, 4) f4 | `x, y, E[keV], z[µm]`, frame coordinates |
| `frame_ptr` | (N+1,) i8 | photons of frame *i* = `photons[ptr[i]:ptr[i+1]]` |
| `occupancy` | (N,) f4 | occupancy each frame was thrown at |
Splits draw from **disjoint stamp files** (train 0–11, val 12–13,
test/eval_scan 14–15), so every single-photon event belongs to exactly one split.
## Running
Training, evaluation and inference need an environment with PyTorch
(`conda activate pytorch_nightly`); generation needs numpy/h5py/omegaconf.
```bash
cd /home/xie_x1/MLXID/GridBased
# 0. self-tests (coordinates, D4, translation equivariance, position-space merging)
python scripts/00_selftest.py
# 1. generate MC frames
python scripts/02_generate_frames.py --config Configs/generate_frames_12keV.yaml
# subsets / quick check:
python scripts/02_generate_frames.py --config Configs/generate_frames_12keV.yaml --splits eval_scan
python scripts/02_generate_frames.py --config Configs/generate_frames_12keV.yaml --smoke
# 2. train (-u keeps progress live when the output is redirected)
python -u scripts/03_train.py --config Configs/train_grid_12keV.yaml
python scripts/03_train.py --config Configs/train_grid_12keV.yaml --epochs 3 --tag smoke
# 3. evaluate the occupancy scan (0.5 / 1 / 2 / 5 / 10 / 20 / 50 %)
python scripts/04_evaluate.py --checkpoint Results/<exp>/Models/best.pth
# 4. run over measured data
python scripts/05_infer_measurement.py --config Configs/infer_measurement_12keV.yaml \
--run Edge_3filters_pos0_12keV
# polychromatic 3-30 keV, MOENCH014: charge LUT (+ its validation), frames, training
python scripts/01_build_charge_lut.py --config Configs/charge_lut_Moench014_3to30keV.yaml
python scripts/01b_validate_charge_lut.py --lut data/charge_lut_Moench014_150V_3to30keV.npz \
--outdir Results/charge_lut_3to30keV
python scripts/02_generate_frames.py --config Configs/generate_frames_poly3to30keV_Moench014.yaml
python -u scripts/03_train.py --config Configs/train_grid_poly3to30keV_Moench014_c96.yaml
python scripts/04_evaluate.py --checkpoint Results/<poly exp>/Models/best.pth
# MC energy response (line position g(E), core width, efficiency) -> energy_response.npz
python scripts/07_energy_response.py --checkpoint Results/<poly exp>/Models/best.pth
# an XRF run, with its photon list for Notebooks/XRF_*.ipynb
python scripts/05_infer_measurement.py --config Configs/infer_measurement_poly_Moench014.yaml \
--run AgMo_1_factor_10 --save-photons
# the whole chain for several network sizes, unattended, with a comparison report
# (resumable: finished stages are skipped); see Results/overnight_poly3to30keV/
setsid nohup bash scripts/overnight_poly3to30keV.sh \
> Results/overnight_poly3to30keV/driver.log 2>&1 < /dev/null &
python scripts/08_compare_models.py --out <dir> --model <label> <experiment> <xrf tag> ...
```
The previous 5–30 keV chain is `Configs/charge_lut_Moench014.yaml`,
`generate_frames_poly_Moench014.yaml` and `train_grid_poly_Moench014.yaml`.
Training outputs land in `Results/<YYMMDD>_<name>_<nn>/`: `config.yaml`,
`history.json`, `Models/{best,last,E*}.pth`, `Plots/training_curves.png`,
`Plots/eval_scan.png`, `Plots/eval_differential.png`, `eval_summary.json`.
## Knobs that matter
| where | key | why |
|---|---|---|
| `Configs/generate_frames_12keV.yaml` | `splits.train.occupancy` | log-uniform 0.2 – 10 % gives one model for all fluxes; `{mode: fixed, value: …}` specialises it to one. |
| | `frame.size` | 32 px training frame; the valid region is `(32−2R)²` = 56 % of it. |
| | `noise.pixel_keV` | 0.13 keV, which is what the measured runs deliver once the column common mode is removed. |
| | `noise.common_mode_*` | available for studying an uncorrected readout; the measurement pipeline removes common mode from the data directly. |
| | `edge_augmentation.fraction` | frames containing a sensor boundary, which shrinks the discarded detector border from R to 1 px. |
| `Configs/train_grid_12keV.yaml` | `model.channels` | length sets R (and the halo); width sets capacity. |
| | `model.norm` | `none` keeps each cell's prediction a function of its own receptive field, which is what makes train-small / infer-large exact; `group` is there if training ever needs stabilising. |
| | `loss.w_ene` | 0.1 — at 12 keV the energy head is nearly constant; it earns its weight in the polychromatic extension. |
| | `eval.threshold` | the efficiency/purity working point; `04_evaluate.py` sweeps it. |
| `Configs/generate_frames_poly3to30keV_Moench014.yaml` | `spectrum.train` | flat 3–30 keV. The energy head is biased within ~0.75 keV of both edges, so the range should reach ~1 keV past the lines of interest; below 3 keV the MC also lacks the entrance-window physics. |
| `Configs/train_grid_poly3to30keV_Moench014_*.yaml` | `model.channels` | `[32, 64, 96, 96]` is the default; see "Choosing the size". |
| | `loss.w_ene`, `data.energy_scale` | 1.0 and 15 keV: here the energy head carries the spectrum. |
| `Configs/infer_measurement_poly_Moench014.yaml` | `calibration.common_mode` | `column_masked` for full-chip or higher-occupancy runs (see Measurement pipeline). |
## Results — `Results/260831_12keV_Moench040_01`
2.4 M training frames (61.5 M photons, occupancy log-uniform 0.2–10 %), GridNet
with 92 932 parameters, 150 epochs in ~105 min on one RTX 4090; `best.pth` is
epoch 135. This is the checkpoint every measurement run in
`Configs/infer_measurement_12keV.yaml` is reconstructed with. Evaluated on the
fixed-occupancy scan at objectness threshold 0.3, decoding in position space
with a 0.1 px merge distance:
| occupancy | efficiency | purity | RMS | σ68 | sub-pixel non-unif. |
|---|---|---|---|---|---|
| 0.5 % | 99.66 % | 99.90 % | **0.798 µm** | 0.59 µm | 0.0 % |
| 1 % | 99.44 % | 99.86 % | 0.851 µm | 0.61 µm | 2.8 % |
| 2 % | 99.04 % | 99.76 % | 0.899 µm | 0.63 µm | 3.6 % |
| 5 % | 97.98 % | 99.41 % | 1.145 µm | 0.71 µm | 3.5 % |
| 10 % | 96.39 % | 98.27 % | 1.757 µm | 0.94 µm | 6.6 % |
| 20 % † | 93.62 % | 94.86 % | 3.352 µm | 1.98 µm | 22.5 % |
| 50 % † | 86.74 % | 89.93 % | 6.469 µm | 6.03 µm | 89.7 % |
† outside the 0.2–10 % training range; the model is extrapolating. Both points
were added to `eval_scan` to find where the chain breaks, and it breaks in the
sub-pixel phase long before it breaks in efficiency: the excess rms of the phase
histogram goes 6.6 % → 22.5 % → 89.7 % at 10 / 20 / 50 %, and the shape is the
R1 failure mode the metric was written for. Binned 10x10 over the cell, the
centre 2x2 bins sit at 1.20 / 1.43 / 3.52 times flat while the border ring sits
at 0.92 / 0.71 / 0.21, so by 50 % the predictions have collapsed onto the cell
centres and the sub-pixel information is gone even though 86.7 % of the photons
are still "found". See the G2 section for how to read that efficiency.
For reference, the cluster-based single-photon model reaches σ = 0.8 µm on 12 keV
MC and 96–98 % efficiency at 0.7–1.7 % occupancy, with the photon count supplied
by the cluster energy. Position resolution matches on the first attempt.
The predecessor `Results/260830_12keV_Moench040_00` — 120 epochs on a 10x smaller
training set, decoded with a 3×3 local maximum — reached 95.5 / 80.8 / 66.5 %
efficiency at 1 / 5 / 10 % occupancy. Nearly all of that deficit was the local
maximum itself, which silently imposes a 2 px minimum separation between
reported photons; `src/decode.py` documents why it was replaced. The offset
channels are unbounded, so a cell may report a position outside its own
footprint and the position-space decode is not held to the one-photon-per-cell
ceiling (~95 % at 10 % occupancy) that bounded the old one. A finer output grid
— cells of 25/s µm — remains the lever if the grid itself ever becomes the limit.
## Baseline comparison — gate G2
`scripts/06_baseline_comparison.py` runs three chains over the *same* simulated
frames and scores them against the same truth with the same matcher (grid model
`260831_12keV_Moench040_01/Models/best.pth`, epoch 135, 2000 frames per point,
valid region = frame minus the halo). The scan runs to 50 % occupancy, five
times past the top of the 0.2–10 % training range, to find where each chain
stops working:
| occupancy | chain | efficiency | purity | RMS | σ68 |
|---|---|---|---|---|---|
| 0.5 % | grid | 99.61 % | 99.90 % | **0.805 µm** | 0.593 µm |
| | cluster + 1/2/3-photon CNNs | 99.64 % | 99.73 % | 0.891 µm | 0.595 µm |
| | cluster + eta interpolation | 87.15 % | 99.86 % | 1.430 µm | 1.161 µm |
| 1 % | grid | 99.44 % | 99.89 % | 0.860 µm | 0.610 µm |
| | cluster + CNNs | 98.26 % | 99.37 % | 1.264 µm | 0.620 µm |
| | cluster + eta | 75.49 % | 99.90 % | 1.391 µm | 1.078 µm |
| 2 % | grid | 99.01 % | 99.79 % | 0.895 µm | 0.624 µm |
| | cluster + CNNs | 91.79 % | 98.00 % | 1.886 µm | 0.675 µm |
| | cluster + eta | 58.70 % | 99.84 % | 1.374 µm | 1.038 µm |
| 5 % | grid | 97.96 % | 99.41 % | 1.141 µm | 0.710 µm |
| | cluster + CNNs | 55.76 % | 92.58 % | 3.522 µm | 0.926 µm |
| | cluster + eta | 26.27 % | 99.87 % | 1.608 µm | 1.082 µm |
| 10 % | grid | 96.37 % | 98.29 % | 1.771 µm | 0.945 µm |
| | cluster + CNNs | 16.12 % | 85.83 % | 5.083 µm | 1.643 µm |
| | cluster + eta | 6.99 % | 99.88 % | 2.001 µm | 1.279 µm |
| 20 % | grid | 93.56 % | 94.84 % | 3.340 µm | 1.979 µm |
| | cluster + CNNs | 0.98 % | (84.85 %) | — | — |
| | cluster + eta | 0.57 % | (100 %) | — | — |
| 50 % | grid | 86.77 % | 89.92 % | 6.470 µm | 6.025 µm |
| | cluster + CNNs | 0.00 % | — | — | — |
| | cluster + eta | 0.00 % | — | — | — |
At 0.5 % the grid chain and the cluster chain agree on efficiency and the grid
chain is slightly sharper; from 1 % upwards the gap opens, because merged
clusters fall outside all three energy windows and are dropped. The published
reference points sit where they should: 0.8 µm for the cluster-based CNN and
~1.1 µm for eta at 12 keV.
Above 10 % the cluster chains are not merely worse, they are gone, and it
happens twice over. On 200 of these frames the finder itself returns 3446 raw
clusters at 2 %, 5229 at 10 %, but only 1449 at 20 % and 213 at 50 %: the charge
has merged into a few large connected objects rather than many small ones. Of
what it does return, the fraction that falls inside one of the three
single-/double-/triple-photon energy windows goes 76.5 % → 41.7 % → 22.2 % →
0.9 %. The two effects multiply, leaving 10 / 0 / 0 usable clusters per 2000
frames at 50 % against 21 348 / 3974 / 850 at 2 %. The purity and resolution of
those rows are computed over a handful of matches and mean nothing, which is why
they are bracketed or left out.
The two cluster chains do not depend on the grid checkpoint, so the five rows up
to 10 % are byte-identical to the earlier comparison run at epoch 100 of
`260831_12keV_Moench040_00`; the grid rows there moved by at most +0.7 points of
efficiency and −0.9 points of purity at 10 %.
The grid chain keeps producing an image at 20 % and 50 %, but its own numbers
have to be read carefully there:
* the model is extrapolating — it never saw anything above 10 % occupancy in
training;
* the matcher accepts a prediction within 1 px (25 µm) of a true photon, and at
50 % occupancy that circle contains ~1.6 photons on average, so a prediction
that is merely *near* a photon still counts. 86.77 % is therefore an upper
bound on "found the right photon", not a resolution claim;
* the honest signal is σ68: 0.593 µm at 0.5 %, then 0.945 / 1.979 / 6.025 µm at
10 / 20 / 50 %. At 50 % the matched positions are spread over a quarter of
the 25 µm matching window, so the chain detects that photons are there without
locating them.
The generator reports how many photons share a cell with another, which is the
ceiling a one-photon-per-cell output grid could reach: 95.20 % at 10 %, 90.62 %
at 20 %, 78.69 % at 50 %. The measured efficiency sits above that ceiling at
every point (96.37 / 93.56 / 86.77 %) because the offset channels are unbounded
and a cell may report a position inside its neighbour — the same effect noted in
the Results section. A finer output grid is what would turn that from an
artefact of the matching radius into real resolution at these fluxes.
Gate G2 asked for σ ≤ 0.8 µm on 12 keV MC and ≥ 96 % efficiency at 1.7 %
occupancy with purity at least as good as the cluster chain. The scan has no
1.7 % point, so the claim is read off the harder 2 % one: 0.805 µm at 0.5 %,
99.01 % efficiency and 99.79 % purity at 2 %, against 91.79 % and 98.00 % for
the cluster chain there.
The comparison needs both torch and `aare`, so it runs in the `mlxid_demo`
environment. On these 32×32 frames a `VarClusterFinder` is built per frame:
reusing one, as `_processFrames` does, is exact at its 101×101 ROI (verified on
real measurement frames) but overruns the internal hit buffer on frames this
small.
## Measurement pipeline
`src/measurement.py` + `scripts/05_infer_measurement.py` take a run from raw ADU
to a photon list in a single pass, with pedestal subtraction, common-mode removal
and the 3D-LUT all running on the GPU.
```bash
python scripts/05_infer_measurement.py --config Configs/infer_measurement_12keV.yaml \
--run Edge_3filters_pos0_12keV
python scripts/05_infer_measurement.py --config ... --run Flat_3filters_pos0_12keV
python scripts/05_infer_measurement.py --config ... --run Pedestal_3filters_pos0_12keV # fake-rate test
```
Each run writes, as `.npy` and `.png`: `binned_image` (reconstruction at
`pixel_size / binning_factor`), `native_image` (the same photons rebinned to the
25 µm pitch), `analog_image` (mean calibrated energy per frame — the classical
native readout), plus `comparison.png` with the last two side by side, the energy
spectrum, the per-frame photon count and `summary.json`.
`summary.json` reports the occupancy twice: averaged over the valid region, and
**inside the illuminated area** (pixels above half the 99th-percentile density).
For a knife-edge or slit run the second number is the one that describes the flux
the model sees.
Measured on `Edge_3filters_pos0_12keV` (MOENCH040 @150 V, 12 keV, 140 files,
14 M frames, 287 GB):
| | |
|---|---|
| GPU calibration vs `UsefulFuncs.convertAduFrameToEnergyFrame` | agrees to 2.4e-6 keV (float32 rounding) |
| pixel noise, raw | 0.1401 keV |
| pixel noise, column common mode removed | **0.1291 keV** — the 0.13 keV the model was trained with |
| frame-mean fluctuation | 28.5 eV → 1.0 eV after the correction |
| pedestal stability over the whole run | −2 ADU = 0.015 keV, a tenth of the noise |
| photon occupancy | **0.66 % inside the illuminated area** (0.16 % averaged over a ROI that is mostly knife-edge shadow). Matches the 0.69 % of the published DanMAX working point and sits inside the 0.2–10 % training range. |
| fake rate on dark frames (cross-run pedestal, p>0.3) | **0 photons in 1000 frames** |
| throughput | 5200–5900 frame/s while sharing the GPU with a training job → ~45 min for the full 14 M-frame run |
The MOENCH040 LUT is `(1740, 400, 400)`, so `convertAduFrameToEnergyFrame` takes
its `else` branch (ADU from −1000 in steps of 10, signed natively). The MOENCH014
LUT is the 1640-bin variant that starts at 0 ADU and handles negative values
through `|adu|` and the sign; `GpuCalibrator` implements both (agreement with the
numpy reference: 2e-6 keV).
Common mode `column_masked` takes the column median over the pixels without
charge. The plain median is pulled up by the photons in the column, which on
the full-chip XRF runs (0.7–1.3 % occupancy) leaves every pixel 4–7 meV low —
tens of meV on a 3×3 sum, growing with the flux. At the 101×101 knife-edge ROI
(~0.8 hit pixels per column) the effect is negligible and `column` is kept.
### Inference speed (RTX 4090)
| model | frame | precision | model only | end-to-end run |
|---|---|---|---|---|
| 12 keV `[32, 64, 64, 64]` | 101×101 | FP32 (TF32) | 11 665 frame/s (119 Mpx/s) | 7 805 frame/s (80 Mpx/s) |
| | | FP16 + channels-last | 28 991 frame/s (296 Mpx/s) | 12 265 frame/s (125 Mpx/s) |
| 5–30 keV `[32, 64, 128, 128]` | 400×400 | FP32 (TF32) | 428 frame/s (68 Mpx/s) | 360 frame/s (58 Mpx/s) |
| | | FP16 + channels-last | 973 frame/s (156 Mpx/s) | 694 frame/s (111 Mpx/s) |
| 3–30 keV `[32, 64, 96, 96]` (default) | 400×400 | FP16 + channels-last | 1 172 frame/s (188 Mpx/s) | **777 frame/s** (124 Mpx/s) |
At the same frame size the 12 keV model runs 1.7x more pixels per second than
the 5-30 keV one. End-to-end keeps a smaller share of the model speed on
101×101 frames (42-67 %) than on 400×400 frames (71-84 %). Per frame the loop
adds ~0.05 ms of work that does not scale with the frame size: decode splits
the batch frame by frame, and the energy histogram and image accumulation run
once per frame. In FP16 that is more than the 0.04 ms the 12 keV model itself
needs per 101×101 frame.
`inference.precision: fp16` runs the network under FP16 autocast in
channels-last layout (`predict_fp16`). An FP16 head alone would round every
energy to the FP16 grid: 7.3 meV steps below 15 keV, 14.6 meV above. That is
harmless for the resolution, but a 50 eV spectrum then shows a comb. So the
offsets and energy of the cells above threshold, about 1 % of the cells, are
recomputed in FP32 from the same features. That costs nothing measurable;
converting the whole feature map to FP32 for the head would cost 15 %. Both
models are trained with AMP, and the FP16 photon list matches the FP32 one:
- on MC, efficiency, purity, position RMS/σ68 and the energy core width agree
to the last digit quoted;
- on AgMo/CuCrCo, 99.997 % of the photons are identical, with positions within
~20 nm. The only differences are the 0.03 % of photons at a cell border,
where the two cells claiming the photon swap.
The poly config uses FP16; the 12 keV config still runs FP32, as its published
numbers were made that way. In the FP16 poly loop the model takes 67 % of the
time, decode 12 % and calibration 10 %. Reading the raw file runs in a prefetch
thread. TF32 and batches above 32 bring nothing, because these convolutions are
bandwidth-bound.
**Full-size MÖNCH, 800×1200** (virtual frames tiled 2×3 from real AgMo raw
frames, ~6500 photons per frame; the Moench014 LUT tiled to 1640×800×1200 =
6.3 GB on the GPU; frames served from RAM, so file reading is not included):
| model | model only, FP32 | model only, FP16 | end-to-end, FP16 |
|---|---|---|---|
| poly `[32, 64, 96, 96]` (default) | 90 frame/s | 198 frame/s | **144 frame/s** |
| poly `[32, 64, 128, 128]` | 72 frame/s | 157 frame/s | — |
| poly `[32, 64, 64, 64]` | 125 frame/s | 287 frame/s | — |
| 12 keV `[32, 64, 64, 64]` | 124 frame/s | 302 frame/s | — |
Per 800×1200 frame, end to end in FP16 with the default model:
- upload 0.25 ms;
- calibration 0.54 ms;
- model 4.95 ms;
- decode 1.0 ms;
- image + histogram 0.19 ms.
The pixel rate is the same as at 400×400 (≈ 190 Mpx/s for the model), and batch
size (4–16) makes no difference. The FP32 path needs ~15–22 GB at batch 16
next to the LUT; FP16 needs 12 GB. Reading the raw file needs 1.9 MB per
frame, 280 MB/s at 144 frame/s.
The last two 128-channel layers carry 92 % of the MACs of `[32, 64, 128, 128]`.
Narrower variants were trained and compared (see "Choosing the size"), and
`[32, 64, 96, 96]` replaced it as the default at no measurable cost.
## Knife-edge test — `Notebooks/KnifeEdge_*.ipynb`
Four flux points of the same DanMAX knife-edge scan, one notebook each, all with
the same ROI and the same ESF and fit as `Plotting/fig_knife_edge.py --fit odr`
(`generate_presampled_esf` + `get_odr_fitting` of
`DeepLearning/src/funcs_postprocess.py`, ESF bins 0.3 µm, symmetric ±4σ window),
so the only thing that differs between the rows is the reconstruction. The edge
sits in the same place at every flux point, so one ROI serves all four and the
notebooks are directly comparable:
| notebook | measurement | frames | occupancy (illuminated) | classical chains available |
|---|---|---|---|---|
| `KnifeEdge_0filters` | `{Edge,Flat}_0filters_12keV` | 3.5 M | 9.42 % | none — this point was never clustered |
| `KnifeEdge_1filters` | `{Edge,Flat}_1filters_12keV` | 7 M | 3.90 % | eta only (the CNN images are older model versions) |
| `KnifeEdge_2filters` | `{Edge,Flat}_2filters_12keV` | 7 M | 1.59 % | eta + the 1/2/3-photon CNNs |
| `KnifeEdge_3filters_pos0` | `{Edge,Flat}_3filters_pos0_12keV` | 14 M | 0.66 % | eta + the 1/2/3-photon CNNs |
LSF width σ [µm], with MTF50 [lp/mm] of the fitted Gaussian in brackets:
| reconstruction | 0 filters | 1 filter | 2 filters | 3 filters |
|---|---|---|---|---|
| eta interpolation | — | 1.506 ± 0.023 (124) | 1.483 ± 0.024 (126) | 1.536 ± 0.019 (122) |
| cluster + CNN, 1 photon | — | — | 1.352 ± 0.022 (139) | 1.355 ± 0.016 (138) |
| cluster + CNN, ≤2 photons | — | — | 1.372 ± 0.022 (137) | 1.380 ± 0.016 (136) |
| cluster + CNN, ≤3 photons | — | — | 1.386 ± 0.018 (135) | 1.381 ± 0.017 (136) |
| **GridBased 12 keV** | **1.473 ± 0.017 (127)** | **1.388 ± 0.020 (135)** | **1.346 ± 0.019 (139)** | **1.360 ± 0.019 (138)** |
| GridBased poly 2–20 keV | 1.856 ± 0.037 (101) | 1.551 ± 0.028 (121) | 1.391 ± 0.018 (135) | 1.420 ± 0.021 (132) |
The ODR model is `A/2 (1 + erf((x - c)/(sqrt(2) sigma))) + B`, so σ is the width
of the Gaussian LSF and `MTF50 = 1000 sqrt(ln 2 / 2) / (pi sigma)`. The GridBased
rows are the self-flattened reconstructions. (Until 2026-09-28 this table took
the fitted σ for √2 σ, so its MTF50 was √2 too high, and binned the ESF with an
older copy of `generate_presampled_esf` that made σ ~3 % too small.) Photon
counts are compared within a flux point only; every chain there sees the same
beam, so their ratio is the ratio of quantum efficiencies and the relative
DQE ∝ QE·MTF² is well defined without the absolute flux. MTF(200 lp/mm) is
measured from the LSF (`Plotting/knife_edge_numbers.py`):
| flux point | GridBased vs ≤3-photon chain | GridBased vs eta | sub-pixel excess rms |
|---|---|---|---|
| 0 filters, 9.42 % | — | — | 18.3 % → 1.6 % |
| 1 filter, 3.90 % | — | 2.46x photons, 3.24x DQE | 19.8 % → 3.0 % |
| 2 filters, 1.59 % | 1.044x photons, 1.17x DQE | 1.47x photons, 2.82x DQE | 21.6 % → 3.8 % |
| 3 filters, 0.66 % | 1.014x photons, 1.23x DQE | 1.19x photons, 2.60x DQE | 22.6 % → 4.3 % |
At the two low-flux points, where the cluster chain is in its comfort zone, the
grid chain matches the sharpest cluster chain to within the errors and collects
as many photons as the ≤3-photon chain or slightly more, which puts its DQE at
200 lp/mm 17–23 % above that chain and 2.6–2.8x above eta. The interesting part
is what happens above it: there is no comparable cluster + CNN image beyond
1.59 % — at 3.90 % only older model versions were ever run, at 9.42 % the
measurement was never clustered at all — while the grid chain keeps resolving
the edge: σ stays at 1.35–1.36 µm up to 1.59 % and grows to 1.39 and 1.47 µm at
3.90 and 9.42 %.
The polychromatic 2–20 keV MOENCH040 model (`KnifeEdge_poly2to20keV_Moench040.ipynb`,
same runs and processing) is 3–4 % wider at the two low-flux points, on par with
the ≤3-photon chain, and falls behind above 4 %, where its fit also degrades
(χ²/NDF 3.4 and 7.4). Its 12 keV line moves from 12.05 to 11.64 keV with flux;
the plain column common mode is the suspect (on the Siemens star the masked one
moved the 15 keV line from 14.45 to 15.01 keV), not yet tested on the knife edge.
Sub-pixel flattening is about image uniformity rather than resolution: it takes
the sub-pixel phase from ~20 % to ~2–4 % excess rms.
The ESF width depends strongly on the ROI: a band of 500 rows across this
strongly slanted edge gives a width 1.6x that of the published 230-row ROI.
Cross-method comparisons are only meaningful at a fixed ROI.
## Results — polychromatic, default `Results/260919_poly3to30keV_Moench014_c96_00`
1.2 M training frames, flat **3–30 keV**, occupancy log-uniform 0.2–10 %. Every
photon gets its own energy, conversion depth and charge cloud from the
α(E,t)/β(E,t) table `data/charge_lut_Moench014_150V_3to30keV.npz` (WP1.1,
validated by `01b_validate_charge_lut.py`). GridNet channels
**[32, 64, 96, 96]** (R = 4, 157 636 parameters), 300 epochs, `best.pth` =
epoch 224. It has been the default in `infer_measurement_poly_Moench014.yaml`
since 2026-09-19. Fixed-occupancy scan at threshold 0.3:
| occupancy | efficiency | purity | RMS | σ68 | energy σ68 | sub-pixel non-unif. |
|---|---|---|---|---|---|---|
| 0.5 % | 99.67 % | 99.73 % | 1.70 µm | 0.99 µm | 0.28 keV | 22.2 % |
| 1 % | 99.42 % | 99.54 % | 1.78 µm | 1.04 µm | 0.29 keV | 22.2 % |
| 2 % | 98.88 % | 99.19 % | 1.89 µm | 1.08 µm | 0.31 keV | 21.8 % |
| 5 % | 97.25 % | 98.07 % | 2.29 µm | 1.28 µm | 0.42 keV | 18.3 % |
| 10 % | 94.37 % | 96.22 % | 2.96 µm | 1.70 µm | 0.89 keV | 16.3 % |
Efficiencies include the 3–5 keV photons (97–98 % found), which the 5–30 keV
model never saw. Against energy (`scripts/07_energy_response.py`, occupancy
≤ 2.5 %, 4.3e5 matched photons, 0.25 keV bins):
| E_true | core σ | line position g(E) − E | tails, \|ΔE\| > 1 keV | efficiency |
|---|---|---|---|---|
| 3.1 keV | 0.13 keV * | +125 meV * | 2.8 % | 97.5 % |
| 3.6 keV | 0.23 keV | +16 meV | 2.3 % | 97.6 % |
| 4.1 keV | 0.26 keV | +4 meV | 2.3 % | 98.0 % |
| 5.4 keV | 0.26 keV | −12 meV | 3.0 % | 98.3 % |
| 7.9 keV | 0.26 keV | 0 meV | 4.0 % | 98.8 % |
| 12.1 keV | 0.28 keV | −7 meV | 4.3 % | 99.2 % |
| 17.4 keV | 0.27 keV | −11 meV | 4.4 % | 99.5 % |
| 22.1 keV | 0.30 keV | 0 meV | 4.7 % | 99.5 % |
| 27.6 keV | 0.31 keV | −5 meV | 4.9 % | 99.7 % |
| 29.6 keV | 0.24 keV * | −83 meV * | 7.0 % | 99.6 % |
\* within ~0.75 keV of a training edge, where the line is shifted and the
energy scale compressed. Between 4 and 28 keV the line position stays within
25 meV.
The `energy_bias_keV` of `eval_summary.json` is the *mean* per 2.5 keV bin. It
is set by the 3–7 % of photons in the tails, which the bounded range cuts from
one side, so it is pulled towards the centre (~+0.1 keV at 5–12 keV) even where
the line does not move. Correcting per photon with the mean is therefore wrong;
the notebooks invert the core position g(E) instead. With the 3–30 keV model
that correction only matters below ~4 keV.
### Choosing the size — `Results/overnight_poly3to30keV/report`
Four networks trained overnight on the same 3–30 keV frames
(`scripts/overnight_poly3to30keV.sh`, report by `scripts/08_compare_models.py`):
| channels | params | FP16 frame/s (400×400) | MC 1 %: RMS / E σ68 / purity | MC 10 %: E σ68 / purity | XRF FWHM Co / Cu / Mo / Ag [keV] | dark-run photons / 1000 frames |
|---|---|---|---|---|---|---|
| [32, 64, 128, 128] | 240 772 | 965 | 1.772 µm / 0.290 keV / 99.55 % | 0.872 keV / 96.28 % | 0.62 / 0.72 / 0.81 / 0.94 | 0 |
| **[32, 64, 96, 96]** | 157 636 | **1 185** | 1.777 µm / 0.291 keV / 99.54 % | 0.889 keV / 96.22 % | 0.62 / 0.72 / 0.82 / 0.95 | **0** |
| [32, 64, 64, 64] | 92 932 | 1 761 | 1.785 µm / 0.292 keV / 99.52 % | 0.919 keV / 96.17 % | 0.63 / 0.72 / 0.82 / 0.95 | 15 (all 2.3–3.2 keV) |
| [32, 64, 128] (R = 3) | 93 188 | 1 712 | 1.790 µm / 0.297 keV / 99.37 % | 1.040 keV / 95.64 % | 0.63 / 0.73 / 0.82 / 0.93 | 8 (2.4–3.7 keV) |
`[32, 64, 96, 96]` matches the widest network on every number and is 23 %
faster. (The XRF runs of this comparison were redone with clean FP16 energies,
see Inference speed.) `[32, 64, 64, 64]` loses ~5 % of energy resolution at 10 % occupancy
and reports photons on dark frames below 3.2 keV. Three layers (R = 3) cost
the most at high occupancy.
### Previous default — 5–30 keV `[32, 64, 128, 128]`, `Results/260901_poly5to30keV_Moench014_00`
Same architecture and data volume, flat 5–30 keV, `best.pth` = epoch 290. MC at
1 % occupancy: 99.52 % efficiency, 99.59 % purity, 1.76 µm RMS, 0.29 keV energy
σ68. Its 5 keV edge shifted Cr Kα (5.41 keV) by +52 meV on MC and compressed
the scale there (g′ = 0.82). On data that meant +33 meV with a 0.18 keV core,
which is why the range was extended. Its XRF reconstructions carry the tag
`_poly5to30`.
## XRF test — `Notebooks/XRF_*.ipynb`
Two flood-field runs from the 2603 MaxIV XRF series (MOENCH014 @150 V, full
400×400 chip, slit factor 10), reconstructed with the default 3–30 keV
`[32, 64, 96, 96]` model and `Configs/infer_measurement_poly_Moench014.yaml`:
* `AgMo_1_factor_10`: 0.69 % occupancy, 1.05e8 photons;
* `CuCrCo_1_factor_10`: 1.29 % occupancy, 1.99e8 photons, 12 keV beam.
The reference is the classical 3×3 sum around a local maximum on the *same*
calibrated frames (first 10 k frames), with no model. The yaml carries more
samples and the full flux series (slit factor 1–20). The reconstructions of the
previous 5–30 keV model are kept as `<run>_poly5to30`, and those of all four
sizes as `<run>_c128/_c96/_c64/_r3`.
**Energy resolution.** Core σ of the fitted line in keV, FWHM of the whole
line shape in brackets, straight from the model (no correction):
| line | E [keV] | GridBased | classical 3×3 | FWHM grid / 3×3 | MC core σ |
|---|---|---|---|---|---|
| Cr Kα | 5.41 | 0.245 (0.58) | 0.398 (0.94) | 0.62 | 0.256 |
| Co Kα | 6.93 | 0.265 (0.62) | 0.408 (0.96) | 0.65 | 0.264 |
| Cu Kα | 8.04 | 0.305 (0.72) | 0.441 (1.04) | 0.69 | 0.267 |
| elastic | 12.0 | 0.311 (0.73) ‡ | 0.470 (1.11) ‡ | 0.66 | 0.279 |
| Mo Kα | 17.44 | 0.327 (0.82) | 0.464 (1.09) | 0.75 | 0.275 |
| Mo Kβ | 19.64 | 0.353 (0.83) | 0.492 (1.16) | 0.72 | 0.290 |
| Ag Kα | 22.10 | 0.378 (0.95) | 0.489 (1.15) | 0.82 | 0.301 |
‡ FWHM = 2.355 σ.
* The grid lines are 18–38 % narrower than the 3×3 sum at every energy. The
core σ stays below the 0.38 keV noise floor of a fixed 3×3 sum
(√9 × 0.126 keV) at every line, the gain that design decision D5 anticipated.
In CuCrCo that separates Cu Kα from Co Kβ; the 3×3 sum shows Cu only as a
shoulder.
* Cr and Co match the MC within ±5 %. Cu, the elastic line and the lines at
17–22 keV are 12–26 % wider, an extra term that grows with E (~1 % of E) and
that the MC noise model (0.13 keV per pixel) does not contain.
Pixel-to-pixel gain residuals of the LUT would do this (WP1.3).
* The previous 5–30 keV model put Cr Kα 0.4 keV above its training edge and
compressed it (0.182 keV core, 0.48 keV FWHM, +33 meV); the MC correction
restored the width but overshot the position. With the 3 keV edge the line
needs no correction.
**Line positions.** The grid puts every line within 0.04 keV of the tables (Cr Kα
+0.01 keV). The classical 3×3 sum reads 0.05–0.10 keV high: the MOENCH014 LUT
(2025-03-05) is 0.3–1 % high (~0.9 % at 5–8 keV, 0.3–0.5 % at 17–22 keV). The
MC correction changes no line by more than 12 meV.
**Efficiency**, without truth, against the classical finder:
| | AgMo | CuCrCo | MC |
|---|---|---|---|
| isolated photons ≥ 5 keV found | 99.97 % | 99.99 % | 99.96 % (no neighbour within 4 px, 1 %) |
| isolated photons 3–5 keV found | 92.9 % | 100.0 % | 97–98 % |
| grid / classical photon count | 1.091 | 1.067 | — |
| classical clusters holding ≥ 2 grid photons | 8.5 % | 10.2 % | Poisson: 8.3 % / 15.0 % |
| reconstructed / deposited energy | 0.997 | 0.996 | — |
The extra grid photons are pile-up that the 3×3 finder merges; in CuCrCo the
pile-up continuum at 13–25 keV is 5–10× lower than in the classical spectrum.
On a dark run (`Pedestal_CuCrCo_1`, 1000 full frames) the model reports
**0 photons** (< 2e-8 per pixel per frame).
The sub-pixel phase of these flood fields has 48 % (AgMo) and 20 % (CuCrCo)
excess rms against 22 % on MC, so R1 is worse on data at high energies.
Self-flattening takes both to 0.5 % and 3.2 %.
## Status
- [x] WP0 data contract, self-tests
- [x] WP1.1 α(E,t) / β(E,t) charge-cloud LUT, MOENCH014 @150 V, with leave-one-out validation
- [x] WP1.2 frame generator: stamp reuse at 12 keV, per-photon LUT sampling at 3–30 keV
- [ ] WP1.3 noise / physics completion (escape peak, Compton, LUT gain residuals) and gate G1 (simulated vs measured spectra); the XRF widths at 17–22 keV ask for the gain term
- [x] WP2 halo / valid-region handling, verified exact; edge augmentation
- [ ] WP2 ablation of R and training-frame size
- [x] WP3 model, loss, decode, training loop; 12 keV, 5–30 keV and 3–30 keV models
- [x] WP3 network size scan on 3–30 keV (four widths / depths); `[32, 64, 96, 96]` is the default
- [x] WP4 metrics: efficiency/purity, RMS/σ68, energy, pair resolution, sub-pixel uniformity
- [x] WP4 baseline comparison against eta interpolation and the 1/2/3-photon models (gate G2 passed)
- [x] WP4 gate G3: efficiency, purity and energy response tabulated over 3–30 keV; self-flattening available
- [x] WP5.1/5.2 measurement pipeline: GPU calibration (both LUT variants, masked common mode), inference, images
- [x] WP5.3 XRF lines (AgMo, CuCrCo): positions, energy resolution, efficiency against the classical sum
- [ ] WP5.3 energy-resolved imaging (the XRF runs are flood fields; needs a structured sample)
- [x] WP5.4 ESF / MTF / relative DQE from the knife edge at four flux points (0.66–9.42 % occupancy), against every published chain
- [ ] WP5.4 SOLEIL 15 keV Siemens star
- [x] WP5.5 fake rate on dark runs (12 keV: 0 / 1000 frames of 101×101; poly: 0 / 1000 full frames)
- [x] gate G4: knife-edge σ = 1.360 µm (MTF50 138 lp/mm) at 0.66 %, equal within the errors to the best published chain (CNN 1 photon, 1.355 µm, 138 lp/mm); XRF lines resolved and in place
- [ ] WP6 extensions (full-size MÖNCH, ONNX/TensorRT, online), WP7 write-up
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

@@ -0,0 +1,56 @@
experiment:
name: 12keV_Moench040
results_dir: Results
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/12keV_Moench040_150V
train_shards:
- 0
- 11
val_shards:
- 0
- 1
max_train_frames: null
max_val_frames: 8000
input_scale: 12.0
energy_scale: 12.0
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels:
- 32
- 64
- 64
- 64
predict_energy: true
norm: none
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 0.1
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.02
training:
epochs: 120
lr: 0.001
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 4
amp: true
seed: 0
checkpoint_epochs:
- 20
- 40
- 60
- 80
- 100
- 120
eval:
threshold: 0.5
nms_kernel: 3
match_radius: 1.0
pixel_size_um: 25.0
@@ -0,0 +1,707 @@
{
"0.5%": {
"occupancy": 0.005,
"summary": {
"n_true": 23133,
"n_pred": 21671,
"n_match": 21662,
"efficiency": 0.9364111874810876,
"purity": 0.9995846984449264,
"f1": 0.9669672350682975,
"rms_x_um": 0.8069253991016199,
"rms_y_um": 0.8141111360845683,
"rms_1d_um": 0.8106481995439455,
"sigma68_1d_um": 0.5971485185623169,
"bias_x_um": 0.029450980219833518,
"bias_y_um": 0.05757446632899468,
"energy_bias_keV": 0.00028054833731353256,
"energy_rms_keV": 0.07483918327806448,
"energy_sigma68_keV": 0.07464508714675899,
"subpixel_nonuniformity": 0.1002893136346688
},
"roc": [
[
0.05,
0.9763171443281277,
0.9989464442493415,
0.8159768304327072
],
[
0.1,
0.9763171443281277,
0.9989464442493415,
0.8159768304327072
],
[
0.2,
0.9763171443281277,
0.9989464442493415,
0.8159768304327072
],
[
0.3,
0.9763171443281277,
0.9989464442493415,
0.8159768304327072
],
[
0.4,
0.9733996910931869,
0.9989432898908066,
0.8167144888609401
],
[
0.5,
0.9359876437274756,
0.999633431085044,
0.8222016542994832
],
[
0.6,
0.8948000686459585,
0.9998082454458294,
0.8305491713874892
],
[
0.7,
0.8603054745151879,
1.0,
0.8374268117182482
],
[
0.8,
0.8237515016303415,
1.0,
0.8467556683498757
],
[
0.9,
0.7707224987128882,
1.0,
0.8538555501900706
],
[
0.95,
0.719238029860992,
1.0,
0.8661173888491417
],
[
0.98,
0.632744122189806,
1.0,
0.8919477349473682
]
],
"pair": {
"edges": [
0.0,
0.5,
1.0,
1.5,
2.0,
2.5,
3.0,
4.0,
6.0
],
"n_true": [
30.0,
72.0,
110.0,
133.0,
166.0,
214.0,
425.0,
974.0,
3392.0
],
"n_match": [
15.0,
36.0,
68.0,
105.0,
151.0,
207.0,
405.0,
927.0,
3243.0
],
"efficiency": [
0.5,
0.5,
0.6181818181818182,
0.7894736842105263,
0.9096385542168675,
0.9672897196261683,
0.9529411764705882,
0.9517453798767967,
0.9560731132075472
]
}
},
"1%": {
"occupancy": 0.01,
"summary": {
"n_true": 46085,
"n_pred": 42138,
"n_match": 42125,
"efficiency": 0.9140718238038408,
"purity": 0.9996914898666287,
"f1": 0.9549663919839497,
"rms_x_um": 0.8771180639142474,
"rms_y_um": 0.8505284150844781,
"rms_1d_um": 0.8640159984688428,
"sigma68_1d_um": 0.6083438944816589,
"bias_x_um": 0.03757140162436828,
"bias_y_um": 0.06257573362627794,
"energy_bias_keV": 0.0010086130883644171,
"energy_rms_keV": 0.07440421101232769,
"energy_sigma68_keV": 0.07450108985900879,
"subpixel_nonuniformity": 0.10732684673233057
},
"roc": [
[
0.05,
0.9549361812972128,
0.9991823385118561,
0.8634292507991568
],
[
0.1,
0.9549361812972128,
0.9991823385118561,
0.8634292507991568
],
[
0.2,
0.9549361812972128,
0.9991823385118561,
0.8634292507991568
],
[
0.3,
0.9549361812972128,
0.9991823385118561,
0.8634292507991568
],
[
0.4,
0.9512025701137449,
0.9992702727355651,
0.8634067361783272
],
[
0.5,
0.912824520274377,
0.9997147204260175,
0.8694773749521033
],
[
0.6,
0.8734045324303203,
0.9999005964214712,
0.8792576600557114
],
[
0.7,
0.8391942346097074,
1.0,
0.8867004362713521
],
[
0.8,
0.8051575931232091,
1.0,
0.8950094142285888
],
[
0.9,
0.7541026308934619,
1.0,
0.9060466598739252
],
[
0.95,
0.6986194321437874,
1.0,
0.9245163116881274
],
[
0.98,
0.6051054962229747,
1.0,
0.9456982525459215
]
],
"pair": {
"edges": [
0.0,
0.5,
1.0,
1.5,
2.0,
2.5,
3.0,
4.0,
6.0
],
"n_true": [
70.0,
257.0,
429.0,
549.0,
633.0,
710.0,
1367.0,
2724.0,
4740.0
],
"n_match": [
34.0,
123.0,
238.0,
412.0,
592.0,
676.0,
1313.0,
2591.0,
4496.0
],
"efficiency": [
0.4857142857142857,
0.4785992217898833,
0.5547785547785548,
0.7504553734061931,
0.9352290679304898,
0.952112676056338,
0.9604974396488661,
0.9511747430249633,
0.9485232067510548
]
}
},
"2%": {
"occupancy": 0.02,
"summary": {
"n_true": 91745,
"n_pred": 80535,
"n_match": 80505,
"efficiency": 0.8774865115265137,
"purity": 0.9996274911529148,
"f1": 0.934583236591595,
"rms_x_um": 0.9062968560920226,
"rms_y_um": 0.9036639447537416,
"rms_1d_um": 0.9050500337729438,
"sigma68_1d_um": 0.6241321563720703,
"bias_x_um": 0.037169365266577575,
"bias_y_um": 0.05946783403174254,
"energy_bias_keV": 0.0007606992869042187,
"energy_rms_keV": 0.07450746470698477,
"energy_sigma68_keV": 0.07439136505126953,
"subpixel_nonuniformity": 0.136064844482477
},
"roc": [
[
0.05,
0.9168807419340793,
0.9989563567362428,
0.8890069983133503
],
[
0.1,
0.9168807419340793,
0.9989563567362428,
0.8890069983133503
],
[
0.2,
0.9168807419340793,
0.9989563567362428,
0.8890069983133503
],
[
0.3,
0.9168807419340793,
0.9989563567362428,
0.8890069983133503
],
[
0.4,
0.9139635128662864,
0.9990956687291765,
0.8897402274953791
],
[
0.5,
0.8770409718291462,
0.9996526054590571,
0.8984772887356761
],
[
0.6,
0.8434710671833501,
0.9999483817684406,
0.9070232836914548
],
[
0.7,
0.8102930291287499,
1.0,
0.9146903664251781
],
[
0.8,
0.7783341315800931,
1.0,
0.9218143778572377
],
[
0.9,
0.7249096529803631,
1.0,
0.9316294201788095
],
[
0.95,
0.6720947446336047,
1.0,
0.9400250797214569
],
[
0.98,
0.5827056211085471,
1.0,
0.9581336780761671
]
],
"pair": {
"edges": [
0.0,
0.5,
1.0,
1.5,
2.0,
2.5,
3.0,
4.0,
6.0
],
"n_true": [
371.0,
952.0,
1465.0,
1900.0,
2135.0,
2119.0,
4094.0,
5721.0,
4210.0
],
"n_match": [
176.0,
446.0,
809.0,
1439.0,
1969.0,
2013.0,
3863.0,
5437.0,
3991.0
],
"efficiency": [
0.4743935309973046,
0.4684873949579832,
0.5522184300341297,
0.7573684210526316,
0.922248243559719,
0.9499764039641341,
0.9435759648265755,
0.950358328963468,
0.9479809976247031
]
}
},
"5%": {
"occupancy": 0.05,
"summary": {
"n_true": 229596,
"n_pred": 178693,
"n_match": 178615,
"efficiency": 0.7779534486663531,
"purity": 0.9995634971711259,
"f1": 0.8749439735089607,
"rms_x_um": 1.099822974575873,
"rms_y_um": 1.0991446082401635,
"rms_1d_um": 1.0994958355148743,
"sigma68_1d_um": 0.6926536560058594,
"bias_x_um": 0.043093383777743244,
"bias_y_um": 0.053363671092027595,
"energy_bias_keV": 0.00043349460802242964,
"energy_rms_keV": 0.07511608523801999,
"energy_sigma68_keV": 0.07507779865264892,
"subpixel_nonuniformity": 0.19363629313163472
},
"roc": [
[
0.05,
0.8082587151177953,
0.9990973371445765,
1.083904970790536
],
[
0.1,
0.8082587151177953,
0.9990973371445765,
1.083904970790536
],
[
0.2,
0.8082587151177953,
0.9990973371445765,
1.083904970790536
],
[
0.3,
0.8082065548117883,
0.9990972789408072,
1.0839295375488778
],
[
0.4,
0.8049378423020082,
0.9991798679155696,
1.0843313360801736
],
[
0.5,
0.7783013127010345,
0.9995757318625371,
1.0927949851904588
],
[
0.6,
0.7494218899417543,
0.99990720764609,
1.1004481450175467
],
[
0.7,
0.7208206554811788,
1.0,
1.103618542638603
],
[
0.8,
0.6899243675562896,
1.0,
1.1099255135872232
],
[
0.9,
0.6402851430061723,
1.0,
1.1104694596707287
],
[
0.95,
0.5873424324089368,
1.0,
1.112976807792225
],
[
0.98,
0.4996783447796227,
1.0,
1.1147969880866666
]
],
"pair": {
"edges": [
0.0,
0.5,
1.0,
1.5,
2.0,
2.5,
3.0,
4.0,
6.0
],
"n_true": [
2071.0,
5881.0,
8150.0,
9016.0,
8526.0,
7056.0,
9418.0,
6208.0,
1189.0
],
"n_match": [
879.0,
2570.0,
4252.0,
6749.0,
7847.0,
6650.0,
8874.0,
5838.0,
1105.0
],
"efficiency": [
0.4244326412361178,
0.437000510117327,
0.5217177914110429,
0.7485581188997338,
0.9203612479474549,
0.9424603174603174,
0.9422382671480144,
0.9403994845360825,
0.9293523969722456
]
}
},
"10%": {
"occupancy": 0.1,
"summary": {
"n_true": 459644,
"n_pred": 296422,
"n_match": 296313,
"efficiency": 0.6446576045809366,
"purity": 0.9996322810047837,
"f1": 0.7838283959336884,
"rms_x_um": 1.504689585816301,
"rms_y_um": 1.5027888173805302,
"rms_1d_um": 1.503747791841089,
"sigma68_1d_um": 0.8647918701171875,
"bias_x_um": 0.05110033320099986,
"bias_y_um": 0.041113979922734765,
"energy_bias_keV": 0.0010869613583709497,
"energy_rms_keV": 0.07619505041448714,
"energy_sigma68_keV": 0.075958251953125,
"subpixel_nonuniformity": 0.3057987053821632
},
"roc": [
[
0.05,
0.6654160478709084,
0.9992044758017189,
1.4912192081439517
],
[
0.1,
0.6654160478709084,
0.9992044758017189,
1.4912192081439517
],
[
0.2,
0.6654160478709084,
0.9992044758017189,
1.4912192081439517
],
[
0.3,
0.6653639387544185,
0.999204413548446,
1.491247660189543
],
[
0.4,
0.6626282101386971,
0.9992403902822343,
1.4930164576670335
],
[
0.5,
0.6446766195079162,
0.9997171755262555,
1.5047942652142676
],
[
0.6,
0.6240761487888973,
0.9999165089613715,
1.508506729948408
],
[
0.7,
0.6014868467905127,
0.9999711228865562,
1.4978212257870458
],
[
0.8,
0.5729484206595277,
0.9999848420542048,
1.478323164043431
],
[
0.9,
0.5211606437212857,
1.0,
1.4475402949684142
],
[
0.95,
0.46470041600444667,
1.0,
1.4131163405725333
],
[
0.98,
0.3782427068949046,
1.0,
1.3692874407279128
]
],
"pair": {
"edges": [
0.0,
0.5,
1.0,
1.5,
2.0,
2.5,
3.0,
4.0,
6.0
],
"n_true": [
8521.0,
21685.0,
25993.0,
22696.0,
16020.0,
9724.0,
8002.0,
2362.0,
140.0
],
"n_match": [
3267.0,
8495.0,
12545.0,
16713.0,
14588.0,
9070.0,
7340.0,
2093.0,
120.0
],
"efficiency": [
0.3834057035559207,
0.39174544616094076,
0.4826299388296849,
0.7363852661261896,
0.9106117353308365,
0.932743726861374,
0.9172706823294177,
0.8861134631668078,
0.8571428571428571
]
}
}
}
@@ -0,0 +1,734 @@
{
"train_loss": [
0.23993744641510628,
0.13196626334205636,
0.11608033124735159,
0.10756625584152744,
0.10190933465703193,
0.09787552357674154,
0.09481275513402426,
0.09245659170817414,
0.0903575554362865,
0.08874685361806459,
0.0873924339768599,
0.08617739734044069,
0.08512466338839832,
0.08428180176394981,
0.08355168168102028,
0.08280495518426885,
0.08215654689099007,
0.08170365822067129,
0.08109293564788306,
0.08064124489097738,
0.08023965582172034,
0.0798451739940852,
0.07947116269627656,
0.0790973027398772,
0.07883267661232576,
0.078509494558915,
0.07818648193663696,
0.07799587040948766,
0.0776730109097609,
0.07739392428667848,
0.07722281022126545,
0.07699475143196234,
0.07687328443360558,
0.07667010834911717,
0.07647448817910099,
0.0763024338376942,
0.07610494880405282,
0.07602332044977261,
0.07588070763022567,
0.07576910217104751,
0.07558895031282462,
0.07540235324119682,
0.07532636232857007,
0.07520397323140976,
0.07502932811559455,
0.07500694960896462,
0.07489564324646647,
0.07470230517258894,
0.07468266000329113,
0.07455802075127527,
0.07448664903958808,
0.07439325880279155,
0.07426496459173164,
0.07423872732842618,
0.07412104704591228,
0.07319306893117146,
0.07315842396199003,
0.07315353711265891,
0.07308529504239877,
0.07304423302412033,
0.07295355107734654,
0.07292400597094727,
0.07291898736481733,
0.0728418159446696,
0.07279567202610801,
0.0727923296256788,
0.07273335824049651,
0.07272118880247484,
0.07269519763097183,
0.0726498324531374,
0.07262815370471908,
0.07259573691237316,
0.07256286855282117,
0.07255131185150197,
0.07250253140544688,
0.07249244097361825,
0.07245224617270549,
0.07246539340202429,
0.07238171729105132,
0.07233716793986497,
0.07235877035362616,
0.07234092938575572,
0.07226683528598879,
0.0722777515474862,
0.07225812967616187,
0.07224044434352517,
0.07217869057599356,
0.0722048584224702,
0.07215245076819952,
0.07215746614600958,
0.0721534180316752,
0.07210610139808889,
0.07205967306931729,
0.07205103477711611,
0.07200603136989579,
0.071978636726085,
0.072040851431697,
0.07196603934881908,
0.07196702520296439,
0.07194990338707938,
0.07192121945488542,
0.07187795462514192,
0.07188086827129539,
0.07186853657441816,
0.07180670765700692,
0.07186382735424388,
0.07178202015422325,
0.0717921020699667,
0.07176873955394568,
0.07174791631029408,
0.07173027876633335,
0.07173992375363153,
0.07170136809237738,
0.0711954400627183,
0.0711862329262425,
0.0711553716274692,
0.07116745289101035,
0.07114298475494761,
0.07113313009383966,
0.07113049572916143
],
"val_loss": [
0.1447843129634857,
0.12180081671476364,
0.11107439863681794,
0.10671789038181305,
0.10051913315057755,
0.09682093042135238,
0.09403118115663528,
0.0919740365743637,
0.08973157322406769,
0.0881431828737259,
0.08681886410713195,
0.08582492661476135,
0.08494884830713272,
0.08402856802940369,
0.08506861454248428,
0.08367731243371963,
0.08199280154705048,
0.08202545255422593,
0.08186581325531006,
0.08074425691366195,
0.08054110908508301,
0.08009980624914169,
0.07967528188228608,
0.07916471648216247,
0.0788530655503273,
0.07914198207855225,
0.07845976740121842,
0.07806322836875916,
0.07768297368288041,
0.0778607325553894,
0.07737875044345856,
0.07682216674089432,
0.07743264657258987,
0.07693758219480515,
0.07715221554040909,
0.07661716121435165,
0.07610375595092773,
0.07629099428653717,
0.0761166660785675,
0.07626022684574127,
0.07677691966295243,
0.0755103154182434,
0.07530945605039596,
0.07527553778886795,
0.07565634047985077,
0.07560070466995239,
0.07543402361869812,
0.07513380587100983,
0.07498476415872574,
0.07448901307582856,
0.07489384853839874,
0.07509817326068878,
0.07472086840867996,
0.07455998528003692,
0.07458109045028687,
0.0736760407090187,
0.0739127681851387,
0.07348479974269867,
0.0737179503440857,
0.07337816250324249,
0.07349218189716339,
0.0736083345413208,
0.0733383971452713,
0.07308566790819168,
0.07324155068397523,
0.07341372239589691,
0.073162602186203,
0.07318145686388015,
0.07304516649246216,
0.07317802047729492,
0.07307149517536163,
0.07285879522562028,
0.07301505696773529,
0.0728074852824211,
0.0728095685839653,
0.07308397686481476,
0.07293559992313385,
0.07273350220918655,
0.07274707239866257,
0.07276939767599105,
0.07303122365474701,
0.0727171242237091,
0.07276431161165238,
0.07267237484455108,
0.07290990674495697,
0.07271710741519928,
0.07273158556222915,
0.07252232605218888,
0.07247878211736679,
0.07271854496002197,
0.07257393556833268,
0.07263265854120254,
0.07244504135847092,
0.07236862510442733,
0.07256510639190673,
0.07235708618164062,
0.07260376578569412,
0.07234354418516159,
0.07233692508935928,
0.07253305220603942,
0.07241593664884567,
0.07230457752943038,
0.07217080211639404,
0.07222512978315353,
0.07210155928134918,
0.07223321187496186,
0.0721174014210701,
0.07204049426317215,
0.07205111521482467,
0.07227177548408509,
0.07243644344806671,
0.07211517387628555,
0.07222532117366791,
0.07178664696216583,
0.07166123247146607,
0.07161745244264603,
0.07157982540130615,
0.07163847053050995,
0.07152747827768326,
0.07152942806482315
],
"efficiency": [
0.7528893166175447,
0.7789511673268773,
0.7703755896783728,
0.770607594284094,
0.7880852745817473,
0.7913505245881918,
0.787389260764584,
0.7842185311530628,
0.7761757048213994,
0.7814001048317107,
0.7793034706170463,
0.7828436890450863,
0.7848200245753026,
0.7847942462857781,
0.7884719489246157,
0.7829811732558839,
0.7834537752305009,
0.7854215179975425,
0.7875267449753818,
0.7811681002259897,
0.7830499153612828,
0.7835998522044734,
0.7836256304939979,
0.7808759462780447,
0.7834881462832003,
0.7834881462832003,
0.7828179107555616,
0.7847083186540296,
0.7819844127276008,
0.77849575087861,
0.7824827929917424,
0.7801369686450071,
0.7765194153483936,
0.778676198905282,
0.7806353489091488,
0.7815633673320329,
0.7851723278654718,
0.7842958660216366,
0.779561253512292,
0.7792862850906966,
0.7842099383898881,
0.7804806791720014,
0.778968352853227,
0.7857652285245367,
0.7806697199618481,
0.7795956245649913,
0.7819414489117266,
0.7791745791694236,
0.7861261245778806,
0.7856019660242144,
0.7818641140431528,
0.7830671008876324,
0.7839607482578173,
0.7856621153664384,
0.7824226436495184,
0.7838404495733693,
0.7830499153612828,
0.7849661015492752,
0.7798620002234118,
0.7815461818056832,
0.7844075719429097,
0.7840896397054401,
0.7854129252343676,
0.7821476752279231,
0.778727755484331,
0.7834194041778014,
0.7814001048317107,
0.7830069515454084,
0.782388272596819,
0.7829639877295342,
0.7832217706247798,
0.7836771870730471,
0.7837373364152711,
0.7827835397028623,
0.7851895133918214,
0.7849403232597506,
0.7816063311479072,
0.7836170377308231,
0.7823624943072944,
0.7835740739149488,
0.7788996107478282,
0.7836514087835225,
0.781090765357416,
0.7827577614133376,
0.7835139245727248,
0.7836170377308231,
0.780970466672968,
0.7841669745740137,
0.7794065837751446,
0.7810478015415417,
0.7818039647009288,
0.7809275028570938,
0.7837803002311453,
0.78320458509843,
0.7834537752305009,
0.7793206561433961,
0.7820101910171253,
0.7850090653651495,
0.7807814258831213,
0.7820445620698248,
0.7807040910145475,
0.7836256304939979,
0.7817438153587049,
0.7823624943072944,
0.7807814258831213,
0.7823109377282452,
0.7792175429852978,
0.7797073304862645,
0.7815461818056832,
0.7815977383847323,
0.7802486745662803,
0.7853527758921436,
0.7817438153587049,
0.7798620002234118,
0.7827749469396874,
0.7846739476013301,
0.779793258118013,
0.784270087732112,
0.7812368423313885,
0.782680426544764
],
"purity": [
0.999475275195346,
0.9992614557038768,
0.9994648948741388,
0.9995207525299808,
0.9993244494808068,
0.9992947048611112,
0.9993347510769398,
0.9994305550992696,
0.9994799504293175,
0.9993955512572534,
0.9995260976900018,
0.9993747394747812,
0.9994200551494726,
0.99937629255162,
0.9993900845168598,
0.999374849196078,
0.9994190443828168,
0.9993767903610242,
0.9993457638207393,
0.9995052498488264,
0.9994516281161232,
0.9994081997216346,
0.9994082191780822,
0.9994061365885846,
0.9994300245527885,
0.9994628900897741,
0.9994076089341349,
0.999343415552297,
0.9994399051133368,
0.9994484219351565,
0.9994183238948154,
0.9994055743912641,
0.9995022894684451,
0.9994705960206468,
0.999526905854265,
0.9994945166040307,
0.9993656626637794,
0.9994196677871823,
0.9994822077779002,
0.9994599955918008,
0.9994414937304934,
0.9995378114270622,
0.9995258939104932,
0.9994098296156242,
0.9994389624104815,
0.9994491996871454,
0.9994179214303757,
0.9995150018738564,
0.9993227670427858,
0.9994097070397902,
0.9994288413167405,
0.9994187576767855,
0.9994413163026094,
0.9993551348751804,
0.9994511887252212,
0.9994740821089307,
0.9994297057501015,
0.9994201630107762,
0.9994714005682444,
0.999439591231251,
0.9994197503831838,
0.9994085691755016,
0.9993767835470856,
0.9995058691761192,
0.9995367715180659,
0.9994409304670971,
0.9994724405121723,
0.9994625601877749,
0.9994072838232388,
0.9993529140801509,
0.9994407894736842,
0.9994192099063065,
0.9994083034745734,
0.9994514416114452,
0.9993984666367722,
0.999420143979344,
0.9995055271081028,
0.9994410713894307,
0.9993962943449245,
0.9994300870213279,
0.9995368736767819,
0.9993972866942819,
0.999472231690288,
0.9994843211691646,
0.9994300432947882,
0.9994410713894307,
0.9994941330430097,
0.9994414631475195,
0.9995371747826375,
0.9994941831057157,
0.9994617336570255,
0.9994611298677019,
0.9994083358899066,
0.9994188596491228,
0.9994299995615381,
0.9994710335783477,
0.9994838284553292,
0.9994311282258859,
0.9994830165434706,
0.9994509235465946,
0.9994609757439085,
0.9994191717169503,
0.9994397328294591,
0.999407264305237,
0.9995379893737556,
0.9994401387577667,
0.9995150286022904,
0.9995153330983433,
0.9994725390650755,
0.9995055216746332,
0.9995266715100279,
0.9994095198521613,
0.9995165950714671,
0.9994934143870314,
0.9994624009830382,
0.9994090093245196,
0.9994933697519714,
0.9994305925122915,
0.999472330321219,
0.9994733030482587
],
"rms_1d_um": [
1.9957276837545026,
1.7052481326275368,
1.607867353051229,
1.5386362101751911,
1.4629472412433182,
1.419914456951616,
1.3875089916354204,
1.3591751371155003,
1.326955817601164,
1.315046204610855,
1.2944686631475242,
1.2741938167909073,
1.2518345619757278,
1.2519973544040521,
1.2741819383148978,
1.2461789741144633,
1.2332135193270557,
1.2172420617410113,
1.2288223278707853,
1.2137821315364055,
1.204417753396746,
1.2135477588750274,
1.2047470100444082,
1.1973607131520918,
1.196213916650463,
1.194062803180352,
1.1883971038278132,
1.187188264079335,
1.1885839781315253,
1.1753985039433763,
1.1746504828554816,
1.1702461369570036,
1.179932844769689,
1.1743969547180375,
1.177318024941057,
1.1702283508280227,
1.1631606694457637,
1.1648540738437512,
1.1673295463691271,
1.168178838019741,
1.1621849474883104,
1.1537410372846353,
1.1528237603597007,
1.1518643664912749,
1.1614324534698262,
1.153850993134394,
1.1631865967283666,
1.1556078817047648,
1.146499683802479,
1.1463666684110478,
1.1523505848087052,
1.14384742248673,
1.1469606929660685,
1.150249619593332,
1.1508898901234208,
1.1458063869532469,
1.147080695841803,
1.1317223721682037,
1.138034884854095,
1.1380879723439374,
1.1386982084711657,
1.13600211390625,
1.1397208783193582,
1.136983103621553,
1.1425336955942123,
1.1428391395149426,
1.1382956332255045,
1.1302274503514875,
1.1297301429781763,
1.127794394977013,
1.1339898038771516,
1.1304566907200795,
1.1279934435712042,
1.12812557259591,
1.1295535353868382,
1.1225942721027018,
1.1339557244653264,
1.123414887240159,
1.1365932894609592,
1.1325205470753563,
1.1213523879628808,
1.1287775216512113,
1.1324143040551773,
1.1302923364599637,
1.1260996057335697,
1.1214342597589066,
1.1221972484155256,
1.1189983160600643,
1.1268814581388924,
1.1323275730408926,
1.1268825173018757,
1.1230667281991895,
1.1221255234378922,
1.1236895497601695,
1.1172322185277896,
1.1200865175757602,
1.122599075223511,
1.1191134799003264,
1.1184832079599694,
1.1290631628817986,
1.1190859962749526,
1.11778158117159,
1.1213144900355483,
1.124063858296662,
1.1204100766073202,
1.1224352881906237,
1.1167503432162673,
1.125525359080101,
1.1167700528311282,
1.1260481804297091,
1.1178404242441295,
1.1128263993848184,
1.113918510834081,
1.1147003843523995,
1.1115423619213338,
1.11573074170728,
1.1153441908738733,
1.1124969441390817,
1.1192881645507677,
1.1111686110256012
],
"sigma68_1d_um": [
1.136922836303711,
0.9791268849372863,
0.9145471787452696,
0.9118080139160156,
0.8578772139549252,
0.8295297622680664,
0.8217811584472656,
0.8090757513046263,
0.7811546325683594,
0.7753372192382812,
0.7677555084228516,
0.7633209228515625,
0.7532179975509639,
0.7490158081054688,
0.7757356321811676,
0.7672293353080749,
0.7415539669990537,
0.7388114929199219,
0.7558584213256836,
0.7328510284423828,
0.7368087768554688,
0.7319211959838867,
0.7253408432006836,
0.7309436798095703,
0.7240888905525208,
0.7355690002441406,
0.7283923172950745,
0.7188796997070312,
0.7212638854980469,
0.7211685180664062,
0.7194218039512632,
0.7114887237548828,
0.721362826824188,
0.7144180607795713,
0.7258976221084595,
0.7123145985603332,
0.708557459115982,
0.7151126861572266,
0.7138252258300781,
0.7142543792724609,
0.7302761077880859,
0.7109642028808594,
0.7056151497364044,
0.7036361837387082,
0.7135629653930664,
0.7150155591964719,
0.7172107696533203,
0.7133007049560547,
0.7050911688804624,
0.7032394409179688,
0.7080078125,
0.7175922393798828,
0.7073402404785156,
0.7059208810329436,
0.7112503051757812,
0.6974570918083188,
0.703470892906189,
0.6975650787353516,
0.7024765014648438,
0.695037841796875,
0.6990194320678711,
0.7006645202636719,
0.6958484649658203,
0.6944295620918272,
0.6971134591102598,
0.6967544555664062,
0.6978034973144531,
0.695037841796875,
0.6933689117431641,
0.6963491439819336,
0.6952524185180664,
0.6919384002685547,
0.6938934326171875,
0.6922245025634766,
0.6925344467163086,
0.6983280181884766,
0.6984710693359375,
0.6919846677780152,
0.6938605761528015,
0.6942783188819884,
0.6996631622314453,
0.6937503814697266,
0.6947517395019531,
0.6936073303222656,
0.6963729858398438,
0.6979465484619141,
0.6951332092285156,
0.6941241729259491,
0.6917715072631836,
0.6977422571182251,
0.6914138793945312,
0.6928348565101623,
0.6919384002685547,
0.6921394133567811,
0.6954669952392578,
0.6922006607055664,
0.6928920745849609,
0.6909171366691589,
0.6928443908691406,
0.6922006607055664,
0.6932782983779904,
0.6922483444213867,
0.6909519720077515,
0.6924539065361023,
0.6911993026733398,
0.6916999816894531,
0.6906032562255859,
0.6901264190673828,
0.6897427773475646,
0.6913661956787109,
0.6920814514160156,
0.6891675257682799,
0.6932094836235047,
0.6890773773193359,
0.687408447265625,
0.6854057312011719,
0.6884750032424926,
0.6864724326133727,
0.6877899169921875,
0.6863389420509337
]
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

@@ -0,0 +1,64 @@
experiment:
name: 12keV_Moench040
results_dir: Results
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/12keV_Moench040_150V
train_shards:
- 0
- 11
val_shards:
- 0
- 1
max_train_frames: null
max_val_frames: 8000
input_scale: 12.0
energy_scale: 12.0
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels:
- 32
- 64
- 64
- 64
predict_energy: true
norm: none
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 0.1
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.02
training:
epochs: 1000
lr: 0.001
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 4
amp: true
seed: 0
checkpoint_epochs:
- 20
- 40
- 60
- 80
- 100
- 200
- 300
- 400
- 500
- 600
- 700
- 800
- 900
- 1000
eval:
threshold: 0.3
nms_kernel: 3
match_radius: 1.0
pixel_size_um: 25.0
File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

@@ -0,0 +1,312 @@
{
"0.5%": {
"occupancy": 0.005,
"n_clusters": {
"1": 7967,
"2": 447,
"3": 26
},
"grid": {
"n_true": 5827,
"n_pred": 5810,
"n_match": 5805,
"efficiency": 0.9962244722841943,
"purity": 0.9991394148020654,
"f1": 0.9976798143851509,
"rms_x_um": 0.7751722440025511,
"rms_y_um": 0.8334788377150387,
"rms_1d_um": 0.804855703165607,
"sigma68_1d_um": 0.5931564760208129,
"bias_x_um": -0.008547874897538743,
"bias_y_um": -0.004963172498599504,
"energy_bias_keV": 4.7871657838911765e-05,
"energy_rms_keV": 0.07488460080343108,
"energy_sigma68_keV": 0.0749839256286621,
"subpixel_nonuniformity": 0.05240391513816991
},
"cluster+dl": {
"n_true": 5827,
"n_pred": 5822,
"n_match": 5806,
"efficiency": 0.9963960871803672,
"purity": 0.9972518035039505,
"f1": 0.996823761696283,
"rms_x_um": 0.8350069908627891,
"rms_y_um": 0.9438987529996069,
"rms_1d_um": 0.8911333851614491,
"sigma68_1d_um": 0.5948124534264206,
"bias_x_um": 0.009228541323870772,
"bias_y_um": -0.001344026449815599,
"energy_bias_keV": -0.0020763391960582937,
"energy_rms_keV": 0.0751517504762937,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.03405747622316486
},
"cluster+eta": {
"n_true": 5827,
"n_pred": 5085,
"n_match": 5078,
"efficiency": 0.8714604427664321,
"purity": 0.9986234021632252,
"f1": 0.9307184750733137,
"rms_x_um": 1.082732698577549,
"rms_y_um": 1.707804172101413,
"rms_1d_um": 1.4298435589774205,
"sigma68_1d_um": 1.1606203316388393,
"bias_x_um": -0.0503576774662195,
"bias_y_um": -0.0505550725893291,
"energy_bias_keV": -0.001967431617186923,
"energy_rms_keV": 0.07507446519993481,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
}
},
"1%": {
"occupancy": 0.01,
"n_clusters": {
"1": 13719,
"2": 1525,
"3": 171
},
"grid": {
"n_true": 11517,
"n_pred": 11449,
"n_match": 11437,
"efficiency": 0.9930537466354086,
"purity": 0.9989518735260722,
"f1": 0.9959940782025604,
"rms_x_um": 0.8600146625936027,
"rms_y_um": 0.8508395013933928,
"rms_1d_um": 0.8554812700460162,
"sigma68_1d_um": 0.6103871524333954,
"bias_x_um": -0.016917289736555038,
"bias_y_um": 1.3734552840872996e-05,
"energy_bias_keV": 0.001381515695715822,
"energy_rms_keV": 0.07480540928892503,
"energy_sigma68_keV": 0.07508318786621093,
"subpixel_nonuniformity": 0.0
},
"cluster+dl": {
"n_true": 11517,
"n_pred": 11389,
"n_match": 11317,
"efficiency": 0.9826343665885213,
"purity": 0.9936781104574589,
"f1": 0.9881253819959837,
"rms_x_um": 1.2557356118085499,
"rms_y_um": 1.2717035058014219,
"rms_1d_um": 1.2637589164174359,
"sigma68_1d_um": 0.6203093953244389,
"bias_x_um": 0.0022295850593656115,
"bias_y_um": -0.009725683011503684,
"energy_bias_keV": -0.0005942877499556029,
"energy_rms_keV": 0.07511853024698728,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
},
"cluster+eta": {
"n_true": 11517,
"n_pred": 8703,
"n_match": 8694,
"efficiency": 0.7548840843969784,
"purity": 0.9989658738366081,
"f1": 0.8599406528189911,
"rms_x_um": 1.229084279121203,
"rms_y_um": 1.536032603232305,
"rms_1d_um": 1.391442339494398,
"sigma68_1d_um": 1.0775229320144666,
"bias_x_um": 0.013097150067134751,
"bias_y_um": -0.052921709356355914,
"energy_bias_keV": -0.0001451129443842377,
"energy_rms_keV": 0.07458658542337013,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
}
},
"2%": {
"occupancy": 0.02,
"n_clusters": {
"1": 21348,
"2": 3974,
"3": 850
},
"grid": {
"n_true": 22967,
"n_pred": 22739,
"n_match": 22706,
"efficiency": 0.9886358688553142,
"purity": 0.9985487488455956,
"f1": 0.9935675841246226,
"rms_x_um": 0.8840547685138826,
"rms_y_um": 0.897443485929105,
"rms_1d_um": 0.8908252397116158,
"sigma68_1d_um": 0.6226322579383851,
"bias_x_um": -0.014619298898143663,
"bias_y_um": 0.00443699905276561,
"energy_bias_keV": 0.0016991545489776674,
"energy_rms_keV": 0.07503275623361767,
"energy_sigma68_keV": 0.07495996570587157,
"subpixel_nonuniformity": 0.0343833649100895
},
"cluster+dl": {
"n_true": 22967,
"n_pred": 21511,
"n_match": 21081,
"efficiency": 0.9178821787782471,
"purity": 0.9800102273255543,
"f1": 0.9479293133684068,
"rms_x_um": 1.7980454666261474,
"rms_y_um": 1.9693026447914417,
"rms_1d_um": 1.8856215468354824,
"sigma68_1d_um": 0.6749886225163936,
"bias_x_um": 0.014511438090085866,
"bias_y_um": 0.0087098963865713,
"energy_bias_keV": -0.0004957638906067483,
"energy_rms_keV": 0.07521611663195726,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.011337602563848385
},
"cluster+eta": {
"n_true": 22967,
"n_pred": 13504,
"n_match": 13482,
"efficiency": 0.5870161536117038,
"purity": 0.9983708530805687,
"f1": 0.7393271366291027,
"rms_x_um": 1.2353796613534593,
"rms_y_um": 1.4988550896341988,
"rms_1d_um": 1.3736238983235192,
"sigma68_1d_um": 1.0383780446953095,
"bias_x_um": 0.006909224694715276,
"bias_y_um": -0.03681126667657988,
"energy_bias_keV": -0.0002414711574616126,
"energy_rms_keV": 0.07536852939956286,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
}
},
"5%": {
"occupancy": 0.05,
"n_clusters": {
"1": 24379,
"2": 7903,
"3": 3054
},
"grid": {
"n_true": 57515,
"n_pred": 56257,
"n_match": 56116,
"efficiency": 0.9756759106320091,
"purity": 0.9974936452352596,
"f1": 0.9864641563829414,
"rms_x_um": 1.1096336462326366,
"rms_y_um": 1.1405961865081073,
"rms_1d_um": 1.125226807707352,
"sigma68_1d_um": 0.7069267165660855,
"bias_x_um": -0.004130621426834062,
"bias_y_um": 0.002833291005575017,
"energy_bias_keV": 0.0018168478360816108,
"energy_rms_keV": 0.0751094479278836,
"energy_sigma68_keV": 0.07504721689224243,
"subpixel_nonuniformity": 0.04489852797698968
},
"cluster+dl": {
"n_true": 57515,
"n_pred": 34642,
"n_match": 32070,
"efficiency": 0.5575936712162045,
"purity": 0.9257548640378731,
"f1": 0.6959861974673656,
"rms_x_um": 3.4728496839199163,
"rms_y_um": 3.5706318408598943,
"rms_1d_um": 3.522469318121268,
"sigma68_1d_um": 0.9264733225456439,
"bias_x_um": 0.09430688218434768,
"bias_y_um": -0.010416793708416822,
"energy_bias_keV": -0.0007335710124059522,
"energy_rms_keV": 0.07520841930408713,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
},
"cluster+eta": {
"n_true": 57515,
"n_pred": 15127,
"n_match": 15108,
"efficiency": 0.2626793010518995,
"purity": 0.998743967739803,
"f1": 0.41595771041546215,
"rms_x_um": 1.5317970911426355,
"rms_y_um": 1.6795084045360822,
"rms_1d_um": 1.6077192482035705,
"sigma68_1d_um": 1.0824790068734453,
"bias_x_um": -0.013584749600640284,
"bias_y_um": -0.08245495721077989,
"energy_bias_keV": -0.0005271548406769114,
"energy_rms_keV": 0.07594497954518455,
"energy_sigma68_keV": 0.07631969451904297,
"subpixel_nonuniformity": 0.0
}
},
"10%": {
"occupancy": 0.1,
"n_clusters": {
"1": 13316,
"2": 5141,
"3": 2348
},
"grid": {
"n_true": 115143,
"n_pred": 111060,
"n_match": 110196,
"efficiency": 0.9570360334540527,
"purity": 0.9922204213938411,
"f1": 0.9743106855346745,
"rms_x_um": 1.7376297019554447,
"rms_y_um": 1.7358375881557624,
"rms_1d_um": 1.7367396954261378,
"sigma68_1d_um": 0.9419664311408997,
"bias_x_um": -0.0024142715432361847,
"bias_y_um": 0.0065774764031168555,
"energy_bias_keV": 0.002688690471001825,
"energy_rms_keV": 0.07537169200633662,
"energy_sigma68_keV": 0.0753557710647583,
"subpixel_nonuniformity": 0.08680207623139889
},
"cluster+dl": {
"n_true": 115143,
"n_pred": 21623,
"n_match": 18558,
"efficiency": 0.16117349730335323,
"purity": 0.858252786384868,
"f1": 0.2713832385241946,
"rms_x_um": 4.976208387415414,
"rms_y_um": 5.1820745880493195,
"rms_1d_um": 5.082711541224097,
"sigma68_1d_um": 1.6431294292211531,
"bias_x_um": 0.23931323555584702,
"bias_y_um": -0.0812059417026731,
"energy_bias_keV": -0.00028108258046445075,
"energy_rms_keV": 0.075351238539105,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.04207391458993803
},
"cluster+eta": {
"n_true": 115143,
"n_pred": 8060,
"n_match": 8050,
"efficiency": 0.0699130646239893,
"purity": 0.9987593052109182,
"f1": 0.13067863607217356,
"rms_x_um": 1.865479807388517,
"rms_y_um": 2.127355542162366,
"rms_1d_um": 2.000837040997611,
"sigma68_1d_um": 1.2786324015140729,
"bias_x_um": -0.015563024079104761,
"bias_y_um": 0.030070619494817805,
"energy_bias_keV": 0.000531021615733271,
"energy_rms_keV": 0.07509626089279871,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
}
}
}
@@ -0,0 +1,56 @@
experiment:
name: 12keV_Moench040
results_dir: Results
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/12keV_Moench040_150V
train_shards:
- 0
- 11
val_shards:
- 0
- 1
max_train_frames: null
max_val_frames: 8000
input_scale: 12.0
energy_scale: 12.0
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels:
- 32
- 64
- 64
- 64
predict_energy: true
norm: none
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 0.1
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.02
training:
epochs: 200
lr: 0.001
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 4
amp: true
seed: 0
checkpoint_epochs:
- 20
- 40
- 60
- 80
- 100
- 200
eval:
threshold: 0.3
merge_distance: 0.3
match_radius: 1.0
pixel_size_um: 25.0
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

@@ -0,0 +1,436 @@
{
"0.5%": {
"occupancy": 0.005,
"n_clusters": {
"1": 7967,
"2": 447,
"3": 26
},
"grid": {
"n_true": 5827,
"n_pred": 5810,
"n_match": 5804,
"efficiency": 0.9960528573880213,
"purity": 0.9989672977624785,
"f1": 0.9975079487840509,
"rms_x_um": 0.7745434329391213,
"rms_y_um": 0.8335377276606065,
"rms_1d_um": 0.8045842853682927,
"sigma68_1d_um": 0.5930900573730469,
"bias_x_um": -0.001291214885751105,
"bias_y_um": -0.005550693841246717,
"energy_bias_keV": -0.0014806291631465941,
"energy_rms_keV": 0.07487331357185813,
"energy_sigma68_keV": 0.07521422080993652,
"subpixel_nonuniformity": 0.05038651004579847
},
"cluster+dl": {
"n_true": 5827,
"n_pred": 5822,
"n_match": 5806,
"efficiency": 0.9963960871803672,
"purity": 0.9972518035039505,
"f1": 0.996823761696283,
"rms_x_um": 0.8350069908627891,
"rms_y_um": 0.9438987529996069,
"rms_1d_um": 0.8911333851614491,
"sigma68_1d_um": 0.5948124534264206,
"bias_x_um": 0.009228541323870772,
"bias_y_um": -0.001344026449815599,
"energy_bias_keV": -0.0020763391960582937,
"energy_rms_keV": 0.0751517504762937,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.03405747622316486
},
"cluster+eta": {
"n_true": 5827,
"n_pred": 5085,
"n_match": 5078,
"efficiency": 0.8714604427664321,
"purity": 0.9986234021632252,
"f1": 0.9307184750733137,
"rms_x_um": 1.082732698577549,
"rms_y_um": 1.707804172101413,
"rms_1d_um": 1.4298435589774205,
"sigma68_1d_um": 1.1606203316388393,
"bias_x_um": -0.0503576774662195,
"bias_y_um": -0.0505550725893291,
"energy_bias_keV": -0.001967431617186923,
"energy_rms_keV": 0.07507446519993481,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
}
},
"1%": {
"occupancy": 0.01,
"n_clusters": {
"1": 13719,
"2": 1525,
"3": 171
},
"grid": {
"n_true": 11517,
"n_pred": 11465,
"n_match": 11452,
"efficiency": 0.9943561691412695,
"purity": 0.9988661142607937,
"f1": 0.9966060395091811,
"rms_x_um": 0.8661869603229555,
"rms_y_um": 0.8529609720773376,
"rms_1d_um": 0.8596026616510635,
"sigma68_1d_um": 0.6102085113525391,
"bias_x_um": -0.005408274182230703,
"bias_y_um": -0.000675048105291166,
"energy_bias_keV": -0.00018757687887444266,
"energy_rms_keV": 0.07481183059546273,
"energy_sigma68_keV": 0.07515103788375854,
"subpixel_nonuniformity": 0.0
},
"cluster+dl": {
"n_true": 11517,
"n_pred": 11389,
"n_match": 11317,
"efficiency": 0.9826343665885213,
"purity": 0.9936781104574589,
"f1": 0.9881253819959837,
"rms_x_um": 1.2557356118085499,
"rms_y_um": 1.2717035058014219,
"rms_1d_um": 1.2637589164174359,
"sigma68_1d_um": 0.6203093953244389,
"bias_x_um": 0.0022295850593656115,
"bias_y_um": -0.009725683011503684,
"energy_bias_keV": -0.0005942877499556029,
"energy_rms_keV": 0.07511853024698728,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
},
"cluster+eta": {
"n_true": 11517,
"n_pred": 8703,
"n_match": 8694,
"efficiency": 0.7548840843969784,
"purity": 0.9989658738366081,
"f1": 0.8599406528189911,
"rms_x_um": 1.229084279121203,
"rms_y_um": 1.536032603232305,
"rms_1d_um": 1.391442339494398,
"sigma68_1d_um": 1.0775229320144666,
"bias_x_um": 0.013097150067134751,
"bias_y_um": -0.052921709356355914,
"energy_bias_keV": -0.0001451129443842377,
"energy_rms_keV": 0.07458658542337013,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
}
},
"2%": {
"occupancy": 0.02,
"n_clusters": {
"1": 21348,
"2": 3974,
"3": 850
},
"grid": {
"n_true": 22967,
"n_pred": 22786,
"n_match": 22739,
"efficiency": 0.990072713023033,
"purity": 0.9979373299394365,
"f1": 0.9939894651716827,
"rms_x_um": 0.885974177465255,
"rms_y_um": 0.9034271571451504,
"rms_1d_um": 0.8947571981462589,
"sigma68_1d_um": 0.6241321563720703,
"bias_x_um": -0.007395124869504104,
"bias_y_um": 0.002606461653006287,
"energy_bias_keV": 3.3262636894401553e-06,
"energy_rms_keV": 0.07501879666750082,
"energy_sigma68_keV": 0.07489024848937986,
"subpixel_nonuniformity": 0.029694204725319234
},
"cluster+dl": {
"n_true": 22967,
"n_pred": 21511,
"n_match": 21081,
"efficiency": 0.9178821787782471,
"purity": 0.9800102273255543,
"f1": 0.9479293133684068,
"rms_x_um": 1.7980454666261474,
"rms_y_um": 1.9693026447914417,
"rms_1d_um": 1.8856215468354824,
"sigma68_1d_um": 0.6749886225163936,
"bias_x_um": 0.014511438090085866,
"bias_y_um": 0.0087098963865713,
"energy_bias_keV": -0.0004957638906067483,
"energy_rms_keV": 0.07521611663195726,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.011337602563848385
},
"cluster+eta": {
"n_true": 22967,
"n_pred": 13504,
"n_match": 13482,
"efficiency": 0.5870161536117038,
"purity": 0.9983708530805687,
"f1": 0.7393271366291027,
"rms_x_um": 1.2353796613534593,
"rms_y_um": 1.4988550896341988,
"rms_1d_um": 1.3736238983235192,
"sigma68_1d_um": 1.0383780446953095,
"bias_x_um": 0.006909224694715276,
"bias_y_um": -0.03681126667657988,
"energy_bias_keV": -0.0002414711574616126,
"energy_rms_keV": 0.07536852939956286,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
}
},
"5%": {
"occupancy": 0.05,
"n_clusters": {
"1": 24379,
"2": 7903,
"3": 3054
},
"grid": {
"n_true": 57515,
"n_pred": 56675,
"n_match": 56339,
"efficiency": 0.9795531600452057,
"purity": 0.9940714600794001,
"f1": 0.9867589105876172,
"rms_x_um": 1.1322819427596782,
"rms_y_um": 1.1493623024279898,
"rms_1d_um": 1.1408551638667521,
"sigma68_1d_um": 0.7100862455368041,
"bias_x_um": 0.005627413540319478,
"bias_y_um": 0.0024938211393812516,
"energy_bias_keV": -0.0001618699775754979,
"energy_rms_keV": 0.07509903651258737,
"energy_sigma68_keV": 0.0750441799163818,
"subpixel_nonuniformity": 0.04059674469906677
},
"cluster+dl": {
"n_true": 57515,
"n_pred": 34642,
"n_match": 32070,
"efficiency": 0.5575936712162045,
"purity": 0.9257548640378731,
"f1": 0.6959861974673656,
"rms_x_um": 3.4728496839199163,
"rms_y_um": 3.5706318408598943,
"rms_1d_um": 3.522469318121268,
"sigma68_1d_um": 0.9264733225456439,
"bias_x_um": 0.09430688218434768,
"bias_y_um": -0.010416793708416822,
"energy_bias_keV": -0.0007335710124059522,
"energy_rms_keV": 0.07520841930408713,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
},
"cluster+eta": {
"n_true": 57515,
"n_pred": 15127,
"n_match": 15108,
"efficiency": 0.2626793010518995,
"purity": 0.998743967739803,
"f1": 0.41595771041546215,
"rms_x_um": 1.5317970911426355,
"rms_y_um": 1.6795084045360822,
"rms_1d_um": 1.6077192482035705,
"sigma68_1d_um": 1.0824790068734453,
"bias_x_um": -0.013584749600640284,
"bias_y_um": -0.08245495721077989,
"energy_bias_keV": -0.0005271548406769114,
"energy_rms_keV": 0.07594497954518455,
"energy_sigma68_keV": 0.07631969451904297,
"subpixel_nonuniformity": 0.0
}
},
"10%": {
"occupancy": 0.1,
"n_clusters": {
"1": 13316,
"2": 5141,
"3": 2348
},
"grid": {
"n_true": 115143,
"n_pred": 112894,
"n_match": 110959,
"efficiency": 0.9636625761010222,
"purity": 0.9828600279908587,
"f1": 0.9731666352390183,
"rms_x_um": 1.7641263035880943,
"rms_y_um": 1.7773972091960508,
"rms_1d_um": 1.770774194091949,
"sigma68_1d_um": 0.9449005126953125,
"bias_x_um": 0.0020271530072638495,
"bias_y_um": 0.0023051986111262317,
"energy_bias_keV": 0.000466546579235357,
"energy_rms_keV": 0.07535016847283477,
"energy_sigma68_keV": 0.07532508831024169,
"subpixel_nonuniformity": 0.06860082570918818
},
"cluster+dl": {
"n_true": 115143,
"n_pred": 21623,
"n_match": 18558,
"efficiency": 0.16117349730335323,
"purity": 0.858252786384868,
"f1": 0.2713832385241946,
"rms_x_um": 4.976208387415414,
"rms_y_um": 5.1820745880493195,
"rms_1d_um": 5.082711541224097,
"sigma68_1d_um": 1.6431294292211531,
"bias_x_um": 0.23931323555584702,
"bias_y_um": -0.0812059417026731,
"energy_bias_keV": -0.00028108258046445075,
"energy_rms_keV": 0.075351238539105,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.04207391458993803
},
"cluster+eta": {
"n_true": 115143,
"n_pred": 8060,
"n_match": 8050,
"efficiency": 0.0699130646239893,
"purity": 0.9987593052109182,
"f1": 0.13067863607217356,
"rms_x_um": 1.865479807388517,
"rms_y_um": 2.127355542162366,
"rms_1d_um": 2.000837040997611,
"sigma68_1d_um": 1.2786324015140729,
"bias_x_um": -0.015563024079104761,
"bias_y_um": 0.030070619494817805,
"energy_bias_keV": 0.000531021615733271,
"energy_rms_keV": 0.07509626089279871,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.0
}
},
"20%": {
"occupancy": 0.2,
"n_clusters": {
"1": 2255,
"2": 567,
"3": 206
},
"grid": {
"n_true": 231173,
"n_pred": 228056,
"n_match": 216296,
"efficiency": 0.9356455987507192,
"purity": 0.9484337180341671,
"f1": 0.9419962589470613,
"rms_x_um": 3.3385438577531343,
"rms_y_um": 3.341137638469033,
"rms_1d_um": 3.3398790857128735,
"sigma68_1d_um": 1.9790590143203723,
"bias_x_um": 0.007890699622890065,
"bias_y_um": -0.024009301283525575,
"energy_bias_keV": 0.0008519556226341287,
"energy_rms_keV": 0.07538286248419279,
"energy_sigma68_keV": 0.0753316879272461,
"subpixel_nonuniformity": 0.22801329767689676
},
"cluster+dl": {
"n_true": 231173,
"n_pred": 2673,
"n_match": 2268,
"efficiency": 0.009810834310235193,
"purity": 0.8484848484848485,
"f1": 0.01939738118248762,
"rms_x_um": 5.600804109825136,
"rms_y_um": 5.6995637998987,
"rms_1d_um": 5.661411684798306,
"sigma68_1d_um": 2.332519201468676,
"bias_x_um": 0.34324095369484175,
"bias_y_um": -0.3626348764505862,
"energy_bias_keV": -0.002491474151611328,
"energy_rms_keV": 0.07581166746186241,
"energy_sigma68_keV": 0.07571983337402344,
"subpixel_nonuniformity": 0.12366690732059063
},
"cluster+eta": {
"n_true": 231173,
"n_pred": 1314,
"n_match": 1314,
"efficiency": 0.005684054798787056,
"purity": 1.0,
"f1": 0.011303857850116351,
"rms_x_um": 2.7328261611140303,
"rms_y_um": 3.506679426249117,
"rms_1d_um": 3.1479970134845514,
"sigma68_1d_um": 2.194430853531226,
"bias_x_um": 0.15264096415091147,
"bias_y_um": -0.17790697024658175,
"energy_bias_keV": -0.001253210790625446,
"energy_rms_keV": 0.0767208912432988,
"energy_sigma68_keV": 0.07631969451904297,
"subpixel_nonuniformity": 0.0
}
},
"50%": {
"occupancy": 0.5,
"n_clusters": {
"1": 10,
"2": 0,
"3": 0
},
"grid": {
"n_true": 575839,
"n_pred": 555660,
"n_match": 499668,
"efficiency": 0.8677217069354455,
"purity": 0.8992333441313033,
"f1": 0.8831965383972941,
"rms_x_um": 6.495733528908615,
"rms_y_um": 6.442457447026989,
"rms_1d_um": 6.469658583455593,
"sigma68_1d_um": 6.024599075317383,
"bias_x_um": 0.03891640526856525,
"bias_y_um": -0.12327075587659811,
"energy_bias_keV": 0.00048489472400298797,
"energy_rms_keV": 0.0754558084371303,
"energy_sigma68_keV": 0.07545852661132812,
"subpixel_nonuniformity": 0.8956750975587188
},
"cluster+dl": {
"n_true": 575839,
"n_pred": 4,
"n_match": 4,
"efficiency": 6.9463860558246315e-06,
"purity": 1.0,
"f1": 1.3892675607761142e-05,
"rms_x_um": 3.06150730399529,
"rms_y_um": 1.149143139756944,
"rms_1d_um": 2.326963994002116,
"sigma68_1d_um": 1.6002655395772307,
"bias_x_um": 0.2986684557981789,
"bias_y_um": -0.22318579722195864,
"energy_bias_keV": 0.05219006538391113,
"energy_rms_keV": 0.05516777821186253,
"energy_sigma68_keV": 0.10123404273986816,
"subpixel_nonuniformity": 3.391164991562633
},
"cluster+eta": {
"n_true": 575839,
"n_pred": 4,
"n_match": 4,
"efficiency": 6.9463860558246315e-06,
"purity": 1.0,
"f1": 1.3892675607761142e-05,
"rms_x_um": 8.111969195945287,
"rms_y_um": 6.438365439935207,
"rms_1d_um": 7.81500657487365,
"sigma68_1d_um": 8.139267043592614,
"bias_x_um": -4.3387687281502165,
"bias_y_um": 1.1187145300044943,
"energy_bias_keV": 0.05219006538391113,
"energy_rms_keV": 0.05516777821186253,
"energy_sigma68_keV": 0.10123404273986816,
"subpixel_nonuniformity": 0.0
}
}
}
@@ -0,0 +1,56 @@
experiment:
name: 12keV_Moench040
results_dir: Results
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/12keV_Moench040_150V
train_shards:
- 0
- 11
val_shards:
- 0
- 1
max_train_frames: null
max_val_frames: 8000
input_scale: 12.0
energy_scale: 12.0
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels:
- 32
- 64
- 64
- 64
predict_energy: true
norm: none
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 0.1
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.02
training:
epochs: 150
lr: 0.001
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 4
amp: true
seed: 0
checkpoint_epochs:
- 25
- 50
- 75
- 100
- 125
- 150
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,914 @@
{
"train_loss": [
0.11549803302764892,
0.08318714067379633,
0.07850522745132446,
0.07633025724887849,
0.07498112595160802,
0.07409019044558207,
0.073438043709596,
0.07298669574975968,
0.07263013871828715,
0.07234264065146447,
0.07211381237904231,
0.07190426727851232,
0.07173082770824432,
0.07156399731993675,
0.07143148451566696,
0.07131937425533931,
0.07119757150053978,
0.0711051474304994,
0.07102341193238894,
0.07093638002713522,
0.07087182137012482,
0.0707879904782772,
0.0707431219124794,
0.07067672574718793,
0.07061363537311555,
0.07056085242668787,
0.07051102187474569,
0.07045817246754964,
0.07042840806245804,
0.07037664879163107,
0.07033685771465302,
0.07029899738510449,
0.07026120421051979,
0.07021202997763952,
0.07018921863516171,
0.07015228042960167,
0.07012904616991679,
0.06943530514121056,
0.06937986017425855,
0.06935595335682233,
0.0693249940419197,
0.0693047307741642,
0.0692897141079108,
0.06925967580596606,
0.06925501446247101,
0.0692465750904878,
0.06923532518029213,
0.06921389070312182,
0.06920927806854248,
0.0691958756562074,
0.06881222748597463,
0.06879483518679937,
0.0687770151857535,
0.06876321306665738,
0.06876243152062098,
0.0687486151723067,
0.0687376529943943,
0.06874020044883092,
0.068733786555926,
0.06872081969221433,
0.06870851689457894,
0.06871340822935104,
0.0684949930759271,
0.06849531310518582,
0.06847809389829636,
0.06847691614627838,
0.06847435382127762,
0.0684768702741464,
0.0684674886683623,
0.06846663992245992,
0.06845621688842773,
0.06844923645218214,
0.06845370108524958,
0.0684496110912164,
0.06843825560331344,
0.0684471672519048,
0.0683291863032182,
0.06831207599600156,
0.0683146153430144,
0.06831508771657943,
0.06831361457149188,
0.06830971352418264,
0.068305537712574,
0.06830678311626116,
0.06830184397061666,
0.06825398347139358,
0.06824112318714459,
0.0682399377099673,
0.06823793432633082,
0.06823979368209838,
0.0682375610311826,
0.06823894005179405,
0.06823871238708495,
0.06823483720302581,
0.06823230539282163,
0.06823040001312891,
0.06823249892552694,
0.06823164344350496,
0.06822990950345993,
0.06822754492282868,
0.06822746344765028,
0.06821993305365244,
0.06822170446475347,
0.0682206405377388,
0.0682171655523777,
0.06818979968905449,
0.06819452638745307,
0.06818412415504456,
0.06819036424557368,
0.06819010560194651,
0.06818557546933492,
0.06818316753983497,
0.06816729074835777,
0.06817552491823832,
0.06817265658418338,
0.06817131195624669,
0.06816850638389588,
0.06816655315597853,
0.06816250649571419,
0.06815451193173726,
0.06816244826118151,
0.06816369408448537,
0.06816202482104301,
0.06816153821428617,
0.06815770435969035,
0.06815292869488399,
0.0681535164296627,
0.06814899826804798,
0.06816054877201716,
0.06815148009300231,
0.06815126408775647,
0.06815599991202355,
0.06815279254118602,
0.06815057228724161,
0.06814749926010767,
0.06815252566814423,
0.06815187471707662,
0.06815179258743922,
0.0681491514325142,
0.06815271788438161,
0.06814763328433036,
0.06814989734133084,
0.0681492163904508,
0.06816013643821081,
0.0681472523287932,
0.06814959580858548,
0.06814836641192436,
0.06814832310160002,
0.06815474447727203,
0.0681435151231289
],
"val_loss": [
0.08761753863096237,
0.08118068277835847,
0.07696007710695267,
0.07577203887701034,
0.07487226217985153,
0.07417588114738465,
0.07336923974752427,
0.07277206259965896,
0.07290583366155624,
0.07238588005304336,
0.07192310702800751,
0.07196190804243088,
0.07193700063228607,
0.07154968839883805,
0.07180907106399537,
0.07135853445529938,
0.07156657356023788,
0.07113019919395447,
0.07104292362928391,
0.0710218738913536,
0.07134625041484832,
0.07105319321155548,
0.0710515564084053,
0.07085513561964035,
0.07069623172283172,
0.07083315581083298,
0.0707534500360489,
0.07064944076538086,
0.07058667445182801,
0.07049528861045837,
0.07071359670162201,
0.0702024462223053,
0.070397798538208,
0.0705282074213028,
0.07057095313072205,
0.07052047687768936,
0.0708088281750679,
0.06966710406541825,
0.069708351790905,
0.06975338733196258,
0.06956660330295562,
0.06984174424409867,
0.06944911295175553,
0.06961576420068741,
0.06933906447887421,
0.06956946402788163,
0.06945883828401565,
0.06948346525430679,
0.06955201673507691,
0.06945740842819213,
0.06912153285741807,
0.06900744903087616,
0.06909787338972091,
0.06909513080120086,
0.06915528351068496,
0.06901137292385101,
0.06893064504861832,
0.06902213388681412,
0.0690176460146904,
0.06905519688129425,
0.06893697673082352,
0.06903553849458695,
0.06877595669031143,
0.06876467853784561,
0.06878680843114852,
0.06872492188215255,
0.06882990360260009,
0.06871770906448364,
0.0687373777627945,
0.06877333170175552,
0.06867993527650833,
0.06869136583805084,
0.06870175909996033,
0.06872516995668411,
0.0686790629029274,
0.06882987052202225,
0.06860003381967544,
0.06863409441709518,
0.06863678550720215,
0.06858499819040298,
0.0686147688627243,
0.06860790497064591,
0.06860097223520278,
0.06860156106948853,
0.06866604322195054,
0.06857422769069671,
0.068542486846447,
0.0685451517701149,
0.06852209347486496,
0.06857949012517929,
0.06853350526094437,
0.06852165567874909,
0.06850533437728881,
0.06854811006784439,
0.0685215277671814,
0.06851877021789551,
0.06849726730585098,
0.06852265816926956,
0.06855925232172012,
0.06849010229110718,
0.06854420799016953,
0.06848812222480774,
0.06852071714401245,
0.06853020858764648,
0.06851188629865647,
0.06849872785806656,
0.06847186905145645,
0.0684665448665619,
0.06849229705333709,
0.06848304557800293,
0.0684949095249176,
0.06847141987085342,
0.0684635078907013,
0.06846057045459747,
0.06846488583087922,
0.06845710617303848,
0.06846144515275955,
0.06846437907218933,
0.06844912987947464,
0.06844370687007904,
0.06845692706108093,
0.06845452892780304,
0.06844369173049927,
0.06844812226295471,
0.06844230926036834,
0.06844818329811096,
0.06844752019643784,
0.06844786524772645,
0.06844825381040573,
0.0684421449303627,
0.06844730162620545,
0.06844427931308747,
0.06844561773538589,
0.06844404506683349,
0.06844152700901031,
0.06844258272647857,
0.06844352567195892,
0.06844338977336883,
0.06844412106275559,
0.06844491696357727,
0.06844289910793304,
0.0684460974931717,
0.06844513207674026,
0.068443852186203,
0.06844429087638855,
0.06844322699308396,
0.06844419091939927,
0.06844343626499176,
0.06844278138875962,
0.06844274979829788
],
"efficiency": [
0.9808896946991243,
0.978973508511132,
0.9799960473289395,
0.9772721414025108,
0.9798070065390928,
0.9791023999587547,
0.9795062598279729,
0.9785524631155641,
0.9802108664083109,
0.9786899473263617,
0.9795320381174975,
0.978732911142236,
0.979265662459077,
0.9792570696959021,
0.9787844677212851,
0.9795664091701969,
0.9784321644311161,
0.9785266848260396,
0.9789047664057331,
0.9788360243003342,
0.9790766216692302,
0.9789477302216074,
0.9790594361428805,
0.9790594361428805,
0.9793601828540004,
0.9789219519320828,
0.9783892006152418,
0.9788961736425582,
0.9797554499600436,
0.9793945539066998,
0.9789992868006565,
0.9784579427206407,
0.9786641690368372,
0.9773838473237839,
0.9781571960095208,
0.9789477302216074,
0.9782517164044442,
0.9786641690368372,
0.9798327848286174,
0.9791625493009787,
0.9790336578533559,
0.9790938071955799,
0.9779939335091985,
0.9786727618000121,
0.9791367710114541,
0.9786212052209629,
0.9792914407486015,
0.9788188387739846,
0.9777361506139529,
0.978380607852067,
0.9791195854851045,
0.9790164723270062,
0.9794117394330495,
0.9787586894317606,
0.9785868341682635,
0.9789219519320828,
0.9781571960095208,
0.9787071328527115,
0.9795406308806723,
0.9785094992996898,
0.9788188387739846,
0.9780368973250728,
0.9789391374584325,
0.9789305446952576,
0.9788188387739846,
0.9786727618000121,
0.9792742552222519,
0.9785009065365149,
0.9787844677212851,
0.9791539565378039,
0.978853209826684,
0.9784751282469903,
0.9788188387739846,
0.9788188387739846,
0.9786813545631868,
0.9789219519320828,
0.9786985400895366,
0.9790766216692302,
0.9788274315371595,
0.9789477302216074,
0.978913359168908,
0.9789992868006565,
0.9789821012743067,
0.9788961736425582,
0.9790164723270062,
0.9788875808793834,
0.9787500966685857,
0.9788446170635091,
0.9788875808793834,
0.9790336578533559,
0.9790594361428805,
0.9789047664057331,
0.9788875808793834,
0.9789305446952576,
0.9789047664057331,
0.9788703953530337,
0.9785610558787389,
0.9788618025898588,
0.9790250650901811,
0.9789305446952576,
0.9790164723270062,
0.9789563229847822,
0.9788446170635091,
0.9791195854851045,
0.9789391374584325,
0.9787844677212851,
0.9789047664057331,
0.9789305446952576,
0.9789047664057331,
0.9788875808793834,
0.9791539565378039,
0.9789219519320828,
0.978973508511132,
0.9789305446952576,
0.9789477302216074,
0.9788961736425582,
0.9788188387739846,
0.9789391374584325,
0.9788961736425582,
0.9788789881162085,
0.9788961736425582,
0.979085214432405,
0.978853209826684,
0.978973508511132,
0.9789649157479571,
0.9789992868006565,
0.9789906940374816,
0.9789477302216074,
0.9789992868006565,
0.9789563229847822,
0.9789563229847822,
0.9788961736425582,
0.9790078795638314,
0.9789821012743067,
0.9789305446952576,
0.9789906940374816,
0.9789906940374816,
0.9789821012743067,
0.9790078795638314,
0.978973508511132,
0.9789992868006565,
0.9790078795638314,
0.9789992868006565,
0.9790078795638314,
0.9789821012743067,
0.9789821012743067,
0.978973508511132,
0.9789821012743067,
0.9790078795638314,
0.9789391374584325
],
"purity": [
0.9767853781253744,
0.9848635471685065,
0.9858666712769268,
0.9899811112174995,
0.9889506595779741,
0.9893291888794346,
0.9902101303868172,
0.990459044338917,
0.9896157749997831,
0.9909946751122403,
0.9898234737381368,
0.9906589201224604,
0.9909913043478261,
0.9911636037885179,
0.9914526938811037,
0.9905462823776795,
0.9915187084751962,
0.9918304069119287,
0.991100091348036,
0.9912115833072291,
0.9909378696166423,
0.9911091005576386,
0.991041141167261,
0.9911963254227851,
0.991043867657928,
0.9912295966310514,
0.9924863149820439,
0.9916348949356731,
0.9912628449220177,
0.991613234385739,
0.9912647798359101,
0.9918816745352869,
0.9920043201059123,
0.9924613250268304,
0.9927961556239698,
0.9917043871866296,
0.9923382000435825,
0.992704674412321,
0.9914703810939823,
0.9925094937811378,
0.9923442725752508,
0.9923620243683646,
0.9926217927473793,
0.9920475925022646,
0.9927254669640369,
0.9924967320261437,
0.9925882701318609,
0.992368540265533,
0.9930616768923295,
0.9924516460815676,
0.9925523945575861,
0.9926034987454697,
0.9920708143299795,
0.9923075582813534,
0.9930590071589889,
0.9925768453335192,
0.9929866799256798,
0.9926703852187555,
0.9920027846669277,
0.9930671224633952,
0.9928182959140984,
0.9932631138027628,
0.9925683269587642,
0.992697994998388,
0.9926539148620975,
0.9925749692802426,
0.9923720622425789,
0.9931623335281138,
0.9927055645126149,
0.9925007838901857,
0.9927925886546456,
0.9930668806195331,
0.992541474975603,
0.9927750324644199,
0.9928519125492521,
0.9928276991991076,
0.9930338195419235,
0.9928720808644127,
0.9929222052734801,
0.9928624962961672,
0.9927065179505054,
0.9928196101327152,
0.9925513564371962,
0.9928275116781705,
0.9928024328822509,
0.9927582330437208,
0.9929822420211143,
0.9928877112550226,
0.9929399459600802,
0.9927852568291726,
0.9926297632115415,
0.9929314146758126,
0.9928620608511491,
0.9927585485726237,
0.9929054525170827,
0.992740804001708,
0.9931367675657763,
0.9929830372552779,
0.9928111466438947,
0.9928623718887262,
0.9928976034858388,
0.9929923648153959,
0.9930175389657938,
0.9927339890748469,
0.9926461618889953,
0.9930344268440461,
0.9930266208748104,
0.9928018056487525,
0.9929141064191398,
0.9930005317155235,
0.9926823531461526,
0.9928796158304355,
0.9928713354481123,
0.9929575623403903,
0.9929230688780624,
0.9929400074957945,
0.9929740755591974,
0.9929316611032186,
0.9929573168074332,
0.9929398844232932,
0.9929313530662762,
0.9928115851108323,
0.9930089436705661,
0.9929405612689559,
0.993061669208978,
0.9929493995224068,
0.9928887649457943,
0.9930182692978174,
0.9929320923098376,
0.9929664008367107,
0.9929144769524407,
0.9929746267225675,
0.9928802363378096,
0.992931969113315,
0.9929748716563092,
0.992949338074446,
0.9929839546092368,
0.9929925480454962,
0.9929927312660147,
0.9930097967437158,
0.9929840157576392,
0.9929927312660147,
0.9929753614725595,
0.9930013857779095,
0.9929579306077271,
0.9929925480454962,
0.9930097967437158,
0.9929406227939446,
0.9929754226947882,
0.9930268640064153
],
"rms_1d_um": [
1.6518027539794375,
1.504959609515673,
1.4434803632079867,
1.3929337206509127,
1.3889829255642838,
1.3557948642462418,
1.3534493343880807,
1.3346827936556243,
1.3387511021931806,
1.3282341367673838,
1.3240096560183134,
1.3164025842707385,
1.318616826569897,
1.3113489967760021,
1.3067453177081807,
1.3106004290897346,
1.3053162108734278,
1.3068550316130951,
1.2936912652797414,
1.2848354424110855,
1.3025805903766177,
1.2744657305349245,
1.3018190673573597,
1.294878492660909,
1.2812935040052742,
1.2920731451099592,
1.2794158917434293,
1.2811052546058228,
1.2855345245588357,
1.2677654344424227,
1.2737460890980223,
1.2745940317386353,
1.2728228024034562,
1.2715848796325957,
1.2686894108221383,
1.2660857999864843,
1.2659234569562197,
1.251547579215351,
1.259892240596265,
1.2569207474628257,
1.2525772739138858,
1.2547406284918396,
1.2425543367640481,
1.259691377609029,
1.2519940137486916,
1.2541406468640504,
1.245735781709769,
1.2441952674493566,
1.2440942301785545,
1.2414551609062303,
1.239263904030335,
1.243810650881112,
1.2434232102302194,
1.2434599058044593,
1.2461881799810886,
1.239423895305759,
1.2373037167896699,
1.2391955861445278,
1.2425751971858434,
1.2374395687648991,
1.245147558787495,
1.2354060878053612,
1.2367463431263799,
1.2360888364221885,
1.2373770863473879,
1.237143623457706,
1.2379887913482324,
1.236886581386335,
1.2322798196198916,
1.2367419977305765,
1.2321263416877766,
1.230330805082824,
1.2326743802255173,
1.240304856444659,
1.2337514924833444,
1.239528013935537,
1.2323571298499751,
1.235188832440222,
1.231610584019876,
1.2342383179998853,
1.2348576601443781,
1.2342280448211749,
1.2350061169415218,
1.2361027848729111,
1.2310652218690377,
1.234599808073783,
1.2354957264509132,
1.233094696098922,
1.2338188563582193,
1.2332609355616289,
1.2352743617679904,
1.2338121086497793,
1.2349447901528963,
1.2341980109114,
1.2345296910562715,
1.2333577056292377,
1.232961511544981,
1.2330551226476572,
1.2361446024400964,
1.2344230878078382,
1.2346198049168138,
1.2349447709373782,
1.2309930949828072,
1.235229165826261,
1.231999449482239,
1.2326119172175054,
1.2319315570409963,
1.2325102103486516,
1.2309485656691728,
1.234818785346898,
1.2306357129830934,
1.2342077209574014,
1.229782963262012,
1.2328603977826504,
1.2325025013776514,
1.2328272993508755,
1.2320834300155519,
1.2312744992942681,
1.2330552895207854,
1.2321872107895615,
1.2328192804492104,
1.2344017833153023,
1.2344088713033987,
1.2317319130876823,
1.2328836229477833,
1.2312717539408862,
1.2319299202289258,
1.2326362497512104,
1.2335916472439719,
1.2329094812195518,
1.2316179173291961,
1.232136345858377,
1.2325858042751041,
1.2326276038654307,
1.2336751824462562,
1.233972178868589,
1.2338528085537162,
1.2330993942821638,
1.2331512283836805,
1.2337571690829185,
1.233387926457071,
1.233457575569663,
1.2331978766845648,
1.2332512704502663,
1.233935626328691,
1.233122942728529,
1.233181678209779,
1.233262819987371,
1.2341576733540283,
1.233040257804744
],
"sigma68_1d_um": [
0.8577781558036803,
0.8123240303993223,
0.7792949676513672,
0.7739067077636719,
0.7715650343894959,
0.7624583315849303,
0.7544517517089844,
0.7504940032958984,
0.7524013519287109,
0.7503747940063477,
0.7458209991455078,
0.7477760314941406,
0.7470074391365051,
0.7445335388183594,
0.7444691777229305,
0.74462890625,
0.7469968581199644,
0.737730908393859,
0.7428169250488281,
0.7399559020996094,
0.7451674020290372,
0.7429361343383789,
0.7429836583137512,
0.7400032663345337,
0.7349491119384766,
0.7413864135742188,
0.7349491119384766,
0.7387638092041016,
0.7363319396972656,
0.7315158843994141,
0.7396697998046875,
0.7327079772949219,
0.7345676422119141,
0.7363796234130859,
0.7356643676757812,
0.7356166839599609,
0.7392006599903106,
0.7273197174072266,
0.730133056640625,
0.7278919219970703,
0.7254123687744141,
0.7317543029785156,
0.7262945175170898,
0.7277011871337891,
0.7242202758789062,
0.7252216339111328,
0.7256474280357361,
0.7262706756591797,
0.7274356091022491,
0.7245063781738281,
0.7229804992675781,
0.721891703605651,
0.7235050201416016,
0.7220555377006527,
0.7221221923828125,
0.7217884063720703,
0.7205397200584412,
0.7222652435302734,
0.721471973657608,
0.7233142852783203,
0.72174072265625,
0.7203102111816406,
0.720498030185699,
0.718772704601288,
0.7185935974121094,
0.7194995880126953,
0.7213115692138672,
0.7187366485595703,
0.7188320159912109,
0.7208824157714844,
0.7189750671386719,
0.7186412811279297,
0.7197380065917969,
0.7207393646240234,
0.7204055786132812,
0.7207411158084869,
0.7184820950031281,
0.7184982299804688,
0.7184982299804688,
0.7180213928222656,
0.7184401273727409,
0.7188081741333008,
0.7189750671386719,
0.7185960149765012,
0.7188796997070312,
0.7185935974121094,
0.7174215054512024,
0.7190227508544922,
0.717616081237793,
0.7184982299804688,
0.7181166005134583,
0.7170677185058594,
0.7177993130683895,
0.7177829742431641,
0.7178783416748047,
0.7189273834228516,
0.7178735756874083,
0.7168292999267578,
0.7180690765380859,
0.7174968719482422,
0.7183074951171875,
0.7174015045166016,
0.7176876068115234,
0.71810605764389,
0.7181167602539062,
0.7181310892105095,
0.7179121041297913,
0.7177352905273438,
0.7172346115112305,
0.7172107696533203,
0.717592542171478,
0.7175445556640625,
0.7170200347900391,
0.7170084071159364,
0.7172584533691406,
0.7172584533691406,
0.7176580929756162,
0.7176608490943904,
0.7172107696533203,
0.7176876068115234,
0.7177377080917355,
0.7169246673583984,
0.7174783635139466,
0.7172584533691406,
0.7175922393798828,
0.7175445556640625,
0.7172346115112305,
0.7173299789428711,
0.7171154022216797,
0.7173206448554993,
0.7172107696533203,
0.7170677185058594,
0.7173061370849609,
0.7171869277954102,
0.7168769836425781,
0.7170677185058594,
0.7170677185058594,
0.7171714866161347,
0.7170677185058594,
0.7173142266273497,
0.7172584533691406,
0.7173061370849609,
0.7170677185058594,
0.7172298645973206,
0.7173061370849609,
0.7171869277954102,
0.7173299789428711,
0.7172990965843201,
0.7171154022216797,
0.717473030090332
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@@ -0,0 +1,56 @@
experiment:
name: poly5to30keV_Moench014
results_dir: Results
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly5to30keV_Moench014_150V
train_shards:
- 0
- 11
val_shards:
- 0
- 1
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0
energy_scale: 15.0
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels:
- 32
- 64
- 128
- 128
predict_energy: true
norm: none
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03
training:
epochs: 300
lr: 0.001
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs:
- 50
- 100
- 150
- 200
- 250
- 300
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@@ -0,0 +1,56 @@
experiment:
name: poly3to30keV_Moench014_c128
results_dir: Results
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly3to30keV_Moench014_150V
train_shards:
- 0
- 11
val_shards:
- 0
- 1
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0
energy_scale: 15.0
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels:
- 32
- 64
- 128
- 128
predict_energy: true
norm: none
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03
training:
epochs: 300
lr: 0.001
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs:
- 50
- 100
- 150
- 200
- 250
- 300
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

@@ -0,0 +1,56 @@
experiment:
name: poly3to30keV_Moench014_c64
results_dir: Results
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly3to30keV_Moench014_150V
train_shards:
- 0
- 11
val_shards:
- 0
- 1
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0
energy_scale: 15.0
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels:
- 32
- 64
- 64
- 64
predict_energy: true
norm: none
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03
training:
epochs: 300
lr: 0.001
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs:
- 50
- 100
- 150
- 200
- 250
- 300
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

@@ -0,0 +1,56 @@
experiment:
name: poly3to30keV_Moench014_c96
results_dir: Results
data:
folder: /mnt/sls_det_storage/moench_data/MLXID/Samples/GridBased/poly3to30keV_Moench014_150V
train_shards:
- 0
- 11
val_shards:
- 0
- 1
max_train_frames: null
max_val_frames: 8000
input_scale: 15.0
energy_scale: 15.0
augment: true
batch_size_train: 256
batch_size_val: 512
num_workers: 8
model:
channels:
- 32
- 64
- 96
- 96
predict_energy: true
norm: none
prior_prob: 0.01
loss:
w_obj: 1.0
w_off: 1.0
w_ene: 1.0
focal_alpha: 0.25
focal_gamma: 2.0
smooth_l1_beta_offset: 0.01
smooth_l1_beta_energy: 0.03
training:
epochs: 300
lr: 0.001
weight_decay: 0.0
scheduler_factor: 0.5
scheduler_patience: 6
amp: true
seed: 0
checkpoint_epochs:
- 50
- 100
- 150
- 200
- 250
- 300
eval:
threshold: 0.3
merge_distance: 0.1
match_radius: 1.0
pixel_size_um: 25.0
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Loaded 100 of 222 files, more files were not shown because too many files have changed in this diff. Show more