Dual-energy reconstruction assumes that measurements acquired with different spectra describe sufficiently consistent anatomy. SpectraShift studies what happens when that assumption fails using a physics-first CT simulation with known material ground truth.
SpectraShift is a compact, CPU-oriented educational/research simulation. It uses synthetic 2D phantoms, energy-dependent x-ray attenuation, polychromatic photon formation, Poisson noise, filtered backprojection, calibrated image-domain material decomposition, and controlled inter-energy motion. It is not a validated clinical scanner model.
How much quantitative iodine, hydroxyapatite (HAp), and elemental-calcium error is introduced when sequential low- and high-energy acquisitions observe slightly different anatomy, and how is that sensitivity related to spectral and material-basis conditioning?
Material decomposition combines measurements whose spectral responses are often nearly collinear. Small spatial inconsistency can therefore be interpreted as a material change rather than motion. A controlled simulation provides exact geometry, concentration, and calcium-mass truth, allowing mismatch error to be separated from ordinary quantum noise and partial-volume effects.
xraydb material attenuation + educational tungsten spectra
↓
supersampled water / iodinated-water / HAp-in-water phantom
↓
known low/high cardiac-like displacement
↓
basis path lengths in cm (parallel-beam Radon transform)
↓
polychromatic Beer–Lambert counts → Poisson noise → log sinograms
↓
ramp-filter FBP in cm⁻¹ → noiseless insert calibration
↓
nonnegative, volume-constrained three-material decomposition
↓
concentration, calcium mass, cross-talk, CNR, residual, and conditioning
The default motion sweep evaluates inter-energy delays of 0, 10, 25, 50, 75, and 100 ms. A 60 bpm sinusoidal model uses 1.5 mm x amplitude and 0.5 mm y amplitude, with the reference phase chosen for nonzero x velocity. The low-energy anatomy is the evaluation reference; the high-energy anatomy is displaced by the configured delay.
Two small companion studies separate important confounders:
- photon counts of 20,000, 100,000, and 500,000 photons/ray at 0/50/100 ms;
- HAp inclusion diameters of 0.5, 1.0, and 2.0 mm at 0/50/100 ms.
The spectral study evaluates six selected kVp/filter pairs with matched anatomy and a fixed 75 ms mismatch. It recalibrates the basis matrix for each pair and records spectral overlap, mean energies, singular values, condition number, response angle, and decomposition error.
All values below come from the committed CSV/JSON outputs generated with seed 2026.
The supersampled reference phantom contains a 12 mm iodinated vessel and two 2 mm HAp inclusions. For the 1 mm slice, the analytical ground truth is 0.624 mg iodine, 1.262 mg HAp, and 0.504 mg elemental calcium.
At 100 ms, the physical low/high displacement was 0.887 mm. HAp-to-iodine cross-talk increased from 9.78% at 0 ms to 54.26%, and RMS decomposition residual increased from 0.02234 to 0.02706 cm⁻¹. Iodine RMSE was non-monotonic (0.720 mg/mL at 0 ms and 0.710 mg/mL at 100 ms).
Whole-support calcium error decreased from +372.7% to +287.5% as motion increased. This surprising direction is retained rather than corrected: the absolute estimate is dominated by diffuse positive HAp produced by the nonnegative estimator under poor conditioning. Motion changes projection onto the feasible response triangle, reducing some diffuse HAp while increasing localized reciprocal cross-talk and residual.
Matched-anatomy condition numbers ranged from 41.15 for 90/140 kVp with 0.4 mm Sn to 8773.69 for 80/120 kVp with 0.2 mm Sn. The latter had spectral overlap 0.498 but a response-vector angle of only 0.013°, showing that spectral overlap alone is not a substitute for calibrated basis conditioning. Under 75 ms mismatch, its HAp-to-iodine cross-talk reached 59.79%.
The 0.5 mm inclusions contained only 0.0274 mg true calcium and had +7644.9% error at 0 ms, compared with +372.7% for 2.0 mm inclusions. At 0 ms, 20,000 photons/ray increased calcium error to +605.4%; the 500,000-photon result was not strictly monotonic because beam-hardening/calibration bias and the constrained estimator remained dominant.
Detailed definitions and interpretation are in the technical note.
- Energies are in keV; the isolated xraydb adapter converts them to eV.
- Mass attenuation is in cm²/g and linear attenuation in cm⁻¹.
- Geometry is in mm; Radon sums are multiplied by pixel spacing in cm.
- Concentration uses 1 mg/mL = 0.001 g/cm³.
- Slice mass uses pixel area × slice thickness with 1000 mm³ = 1 mL.
- HAp attenuation and calcium fraction are derived from
Ca5(PO4)3OHstoichiometry. - Default spectra are an 80 kVp beam with 2.5 mm Al and a 140 kVp beam with 2.5 mm Al + 0.4 mm Sn.
- Default images are 256×256 at 0.5 mm spacing with 360 projection views and 1 keV spectral sampling.
- Decomposition support is estimated from reconstructed attenuation; ground-truth masks are used only for evaluation metrics.
Python 3.11 or newer is required. From the repository root:
python -m venv .venv
# Windows: .venv\Scripts\activate# macOS/Linux: source .venv/bin/activate
python -m pip install -e ".[test]"
python -m pytest
python -m spectrashift.cli reproduce --quickQuick mode uses 96×96 images and 90 views for the motion/spectral sweeps and writes them under ignored results/quick/ and figures/quick/ directories. It still regenerates the default reference and baseline figures.
The baseline configuration uses matched 0 ms anatomy. Controlled mismatch is introduced only by the motion and spectral experiments.
Regenerate the canonical experiment outputs with:
python -m spectrashift.cli reproduceIndividual entry points are also available:
python experiments/plot_spectral_physics.py
python experiments/plot_phantom_ground_truth.py
python experiments/run_baseline_reconstruction.py
python experiments/run_material_decomposition_baseline.py
python -m spectrashift.cli motion-sweep --config configs/motion_sweep.yaml --run --overwrite
python -m spectrashift.cli spectral-sweep --config configs/spectral_sweep.yaml --run --overwriteResolved YAML configurations and runtime package versions are saved beside results. Randomness uses explicitly passed numpy.random.Generator instances. Sweep seeds are stable functions of the global seed and experimental condition; corresponding streams are reused across comparisons as a common-random-numbers design.
src/spectrashift/ reusable physics, reconstruction, decomposition, metrics, sweeps
configs/ validated baseline, motion, and spectral experiment settings
experiments/ thin standalone experiment entry points
tests/ unit, invariant, numerical, and reduced integration tests
figures/ canonical generated figures
results/ canonical scalar tables, calibration, config, and provenance
docs/ research-style technical note
- Physics-motivated educational tungsten spectra, not measured scanner spectra.
- 2D parallel-beam geometry; no cone beam, helical sampling, or through-plane motion.
- No scatter, electronic detector noise, detector response, pile-up, focal-spot blur, bow-tie variation, or vendor corrections.
- Global sinusoidal rigid translation rather than deformable cardiac motion.
- Image-domain decomposition with homogeneous-insert effective coefficients.
- Additive water/iodine/HAp basis assumptions neglect solution displacement and chemistry.
- No clinically validated material-detection threshold; nonnegative whole-body integration can accumulate positive bias.
- No claim of clinical, diagnostic, or commercial-scanner validation.
python -m pytestThe suite checks unit conversions, xraydb keV/eV handling, attenuation positivity, filtration, spectral validity, Radon path-length scaling, Beer–Lambert monotonicity, Poisson statistics, reconstruction scaling, fraction conservation, motion geometry, analytical HAp/calcium mass, pure-basis recovery, virtual non-contrast, conditioning diagnostics, deterministic sweeps, CSV safety, and reduced end-to-end runs. GitHub Actions runs the suite and the quick reproduction smoke test on Python 3.11.
MIT; see LICENSE.


