specsrbench
Benchmarking deep learning against classical deconvolution for galaxy spectral super-resolution.
Seven classical deconvolution methods — cubic interpolation, Wiener, Tikhonov, Wiener + total variation, Richardson–Lucy, wavelet-sparse FISTA, and a redshift-informed matched filter — scored against the SR2 deep-learning pipeline of Haghjoo et al. 2026 on JWST/NIRSpec prism spectra from JADES.
The deep-learning side is a separate package,
specsr, whose weights live
on the Hub. This package holds
the classical baselines, the metrics, the tuning, and the figures.
Install
Not on PyPI yet. Install the wheel from the latest release:
pip install https://github.com/aryana-haghjoo/specsr-benchmark/releases/download/v0.1.1/specsrbench-0.1.1-py3-none-any.whl
or from source:
git clone https://github.com/aryana-haghjoo/specsr-benchmark
cd specsr-benchmark
pip install -e . # the baselines and the figures
pip install -e '.[all]' # + the ML arm and the LSF derivation
[ml] pulls in torch and specsr (needed by build predictions and by
figure 1's toy CNN); [lsf] pulls in astropy (needed only to re-derive the
instrument LSF). Neither is required to draw the other five figures.
What it is for
The short version of the result: on this data, at the pixel level, no method beats cubic interpolation.
SR2 leads the raw mean-absolute-error table by 30%, and does it by producing a spectrum at 0.54 of the reference's amplitude — absolute error against a noisy reference falls when you shrink toward zero, whatever the reconstruction quality. On a scale-free metric all nine methods land within 1.2% of each other and SR2 ranks eighth of nine. What does survive is sharper and more interesting than a leaderboard: SR2 exceeds the reference's own line signal to noise on all four diagnostic lines while recovering only 36–53% of true line amplitudes, at false-detection rates of 0.30 (Hβ) and 0.44 ([O II]) against ≤ 0.09 for any classical method.
Getting there took four separate metric traps, each of which had to be closed
before the benchmark meant anything. They are documented in
docs/GUARDS.md, and they are the part of this repository
most likely to be useful to someone benchmarking something else.
Quick start
specsrbench paths # where it will look for inputs
specsrbench figures all # rebuild all six paper figures
specsrbench figures 4 # or just one
The figures read a cache of derived arrays. If you do not have one, build it (next section) or point at one you do have:
export SPECSRBENCH_CACHE=/path/to/cache_logR_tuned
| figure | what it shows | command |
|---|---|---|
| 1 | every method on a 1D toy where truth is known | specsrbench figures toy |
| 2 | one held-out galaxy, all methods, [O III] inset | specsrbench figures qualitative |
| 3 | residual maps over all 572 spectra vs redshift | specsrbench figures residuals |
| 4 | global fidelity: MAE, uncertainty-normalised, RMSE | specsrbench figures mae |
| 5 | per-line S/N, detection, false detection, width bias | specsrbench figures per-line-snr |
| 6 | error against redshift, in equal-count bins | specsrbench figures redshift |
Figure 1 trains a small CNN inline and needs specsrbench[ml]; the other five
are pure numpy and matplotlib.
Rebuilding everything from the data
No large files are distributed with this package. The chain runs from the raw JADES DR4 products and the published model weights, in six stages:
specsrbench build predictions # specsr chain over the held-out split (Hub weights)
specsrbench build sets # eval / tune / calib sets, galaxy-disjoint
specsrbench build lsf --jades-root <JADES DR4> # measure the instrument LSF
specsrbench build tune # search the classical parameters under four guards
specsrbench build classical # the classical reconstructions
specsrbench build lines # Gaussian line fits, S/N, the summary table
specsrbench build all runs them in order. --dry-run on any stage prints
what it would read and write without doing it.
What each stage needs:
| stage | needs | why |
|---|---|---|
predictions |
specsr, torch, network |
runs SR1 → ZHead → SR2, weights from the Hub |
sets |
the paired dataset | draws the three galaxy-disjoint sets |
lsf |
raw JADES DR4, astropy |
measures the prism→grating kernel |
tune, classical, lines |
nothing beyond the above | pure numpy/scipy |
The paired dataset (3.5 GB) is built by specsr from the JADES tree and is not
redistributable here:
specsr build --jades-root <JADES DR4 tree> --out paired_DR4_logR.npz
The one thing to get right
Match the checkpoint to the wavelength grid by reading its provenance, never
by its filename. More than one file called best_sr2.pth exists, trained on
different grids. Loading the wrong one does not fail — it produces plausible
spectra that are wrong. specsrbench build predictions pins the Hub revision
and records what it loaded in the output's provenance field.
Why the LSF is measured and not read
The evaluation products carry a sigma_pix array that was widely used as the
deconvolution kernel and does not describe the data: it is roughly constant
in nanometres, where a spectrograph's line-spread function is fixed in detector
pixels. Measured against the paired spectra it is up to 2.3× too broad at
5 µm. With it, Wiener, Tikhonov and TV merge line pairs that their own input
still resolves.
specsrbench build lsf measures the real one from the instrument: the prism
dispersion from the raw x1d products, the effective kernel width from
σ_eff² = σ_LR² − σ_HR² on fits to the four diagnostic lines, and the ratio of
the two. It comes out constant to a few per cent across a factor of three in
wavelength, which is the check that it is an instrumental LSF and not a curve
fitted to noise. The sets this package builds deliberately do not carry a
sigma_pix, so the kernel can only come from measuring it.
Tuning is guarded, in four ways
Mean absolute error is not a safe objective, and no single guard catches what the others do:
| trap | what it rewards | guard |
|---|---|---|
| smoothing | erasing every line incurs no line-shaped residual | median line S/N ≥ 0.9 × no-deconvolution baseline |
| shrinkage | scaling toward zero lowers error against a noisy reference | output std within [0.90, 1.15] of the target's |
| blurring | a unit-gain Wiener filter with snr ≤ 1 can only broaden | median FWHM bias ≤ the baseline's |
| merging | two close lines smeared into one peak look like one good line | resolvable [O III] pairs kept resolved ≥ baseline |
Each is blind to the others: line S/N is amplitude over sideband noise, so a global rescale leaves it unchanged; the amplitude guard is width-invariant; and all three of those are single-line statistics, so a Gaussian fitted to a blended doublet passes them. The fourth is measured on real spectra — a synthetic pair was tried and discarded as far too easy to hold apart.
specsrbench build tune applies all four inside the search, and
tests/test_invariants.py asserts them against whatever cache is present. A
method is allowed to fail guard 4 only if classical_params.json records that
no setting passes it; that is Tikhonov's case, and the paper says so.
Tests
pytest # the full suite
pytest -m "not slow" # skip the end-to-end figure builds
The suite runs without any data — tests that need a cache skip cleanly. With one present it checks the cache's structural invariants, the four guards, and rebuilds a slice of each classical cache from the recorded parameters to confirm it reproduces.
Layout
src/specsrbench/
paths.py where inputs and outputs are found
methods.py the nine reconstructions, their names and cache keys
metrics.py MAE, scale-free MAE, amplitude ratio, the guards
classical.py the seven classical deconvolvers, on the log R=4000 grid
data.py the cache loader every figure shares
figures/ one module per paper figure
build/ the six pipeline stages
scripts/ talk figures, which are not paper figures
tests/
Documentation
Full documentation, including the API reference, is at https://aryana-haghjoo.github.io/specsr-benchmark/.
Citing
Paper 2 is in preparation. Until it appears, cite the archived software release
and Haghjoo et al. 2026 for the models.
CITATION.cff carries the machine-readable metadata; GitHub's "Cite this
repository" button reads it.
License
MIT. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file specsrbench-0.1.1.tar.gz.
File metadata
- Download URL: specsrbench-0.1.1.tar.gz
- Upload date:
- Size: 100.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
017c1beccd952db13dac31e0b14e3026bb2a85811c8516e53625bccd9a7c5dfd
|
|
| MD5 |
72a6a59848de52d68d83ba910c539074
|
|
| BLAKE2b-256 |
bcbb890dee5c6169c6af92be884840f2958378b20432e427afdb1364bc96811c
|
Provenance
The following attestation bundles were made for specsrbench-0.1.1.tar.gz:
Publisher:
publish.yml on aryana-haghjoo/specsr-benchmark
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
specsrbench-0.1.1.tar.gz -
Subject digest:
017c1beccd952db13dac31e0b14e3026bb2a85811c8516e53625bccd9a7c5dfd - Sigstore transparency entry: 2594673714
- Sigstore integration time:
-
Permalink:
aryana-haghjoo/specsr-benchmark@d69014e61abc80a5f2ce57d5733f98ab8caeeb45 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/aryana-haghjoo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d69014e61abc80a5f2ce57d5733f98ab8caeeb45 -
Trigger Event:
push
-
Statement type:
File details
Details for the file specsrbench-0.1.1-py3-none-any.whl.
File metadata
- Download URL: specsrbench-0.1.1-py3-none-any.whl
- Upload date:
- Size: 74.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
61139e74709f60396a1c60813aa5340e20a02a3f848fb6e87fe97fa2437a686c
|
|
| MD5 |
1a5547ceea91e93cfb7697de79f4563e
|
|
| BLAKE2b-256 |
1cf215b07dd39a3f5184f2abeeff1e17c2f3b86fb3ba8051e0ec2f0cde37e94a
|
Provenance
The following attestation bundles were made for specsrbench-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on aryana-haghjoo/specsr-benchmark
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
specsrbench-0.1.1-py3-none-any.whl -
Subject digest:
61139e74709f60396a1c60813aa5340e20a02a3f848fb6e87fe97fa2437a686c - Sigstore transparency entry: 2594673832
- Sigstore integration time:
-
Permalink:
aryana-haghjoo/specsr-benchmark@d69014e61abc80a5f2ce57d5733f98ab8caeeb45 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/aryana-haghjoo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d69014e61abc80a5f2ce57d5733f98ab8caeeb45 -
Trigger Event:
push
-
Statement type: