Skip to main content

specsrbench

tests docs python license DOI PyPI dataset

Benchmarking deep learning against classical deconvolution for galaxy spectral super-resolution.

Seven classical deconvolution methods on a 1-D toy problem, each panel showing one method's reconstruction against the known truth

Every method in the benchmark, on a 1-D toy where the truth is known: a close doublet the instrument blends into one blob, and a weak isolated line. Drawn by scripts/make_classical_toy_figure.py using the same deconvolvers the benchmark runs.

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

pip install specsrbench          # the baselines and the figures
pip install 'specsrbench[all]'   # + the ML arm and the LSF derivation

Or from source, for development:

git clone https://github.com/aryana-haghjoo/specsr-benchmark
cd specsr-benchmark
pip install -e '.[dev]'

[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 eight methods land within 1.2% of each other and SR2 ranks seventh of eight. 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.

Line flux ratios fare no better. Measured from integrated Gaussian fluxes, with Hβ and [O III] fitted jointly, no method improves on the interpolated prism spectrum; SR2's ratios are unbiased in the median but scatter about twice as widely as the reference's.

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.

Start here: the tutorial

If you want to see what this does before installing survey data, run the notebook. It needs no JADES tree, no GPU and no configuration — 24 held-out spectra download from the Hub on first use and everything after that is numpy and scipy.

pip install specsrbench          # enough: fetching the sample is built in
jupyter lab tutorials_for_user/01_quickstart.ipynb

It runs all six classical deconvolvers at the tuned parameters, scores them against the grating reference alongside the deep-learning prediction, and then demonstrates live why the obvious metric reverses the result — see tutorials_for_user/.

from specsrbench.sample import load_sample

s = load_sample()          # 1.9 MB, cached after the first call
recon = s.reconstruct()    # all six classical methods, a few seconds

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, line fluxes, 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 eight methods and the reference, 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
  sample.py     the 24-spectrum tutorial sample, fetched from the Hub
scripts/        talk figures, which are not paper figures
tutorials_for_user/  the tutorial notebook
tests/
figures/        where `specsrbench figures all` writes its PDFs

Documentation

Full documentation, including the API reference, is at https://aryana-haghjoo.github.io/specsr-benchmark/, and the tutorial has its own page.

The tutorial sample is hosted separately, on the Hub: https://huggingface.co/datasets/aryana-haghjoo/specsr-benchmark.

Citing

Paper 2 is in preparation. Until it appears, cite the archived software release and Haghjoo et al. 2026 for the models.

Every release is archived on Zenodo:

  • 10.5281/zenodo.22104971 — cite this one unless you need a specific version. It is the concept DOI and always resolves to the latest release.
  • 10.5281/zenodo.22105099 — v0.1.1 specifically, for when the exact version matters (a result you are reproducing, say).

CITATION.cff carries the machine-readable metadata; GitHub's "Cite this repository" button reads it.

License

MIT. See LICENSE.

Metadata

Release files for specsrbench 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for specsrbench 0.2.1
File Size Uploaded Provenance Actions
specsrbench-0.2.1.tar.gz 841.3 kB With provenance Details for specsrbench-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for specsrbench 0.2.1
File Interpreter ABI Platform Actions
specsrbench-0.2.1-py3-none-any.whl Python 3 none any Details for specsrbench-0.2.1-py3-none-any.whl

Total release size: 926.8 kB

Release files / specsrbench-0.2.1.tar.gz

Download URL specsrbench-0.2.1.tar.gz
Size 841.3 kB
Tags Source
SHA-256 checksum
How to use checksums
1955dd0cd85fd1d196250247bf5d6c2b5aaec7359e5f33f36eb3edf470fa5819
BLAKE2b-256 checksum
How to use checksums
af8ef878b51b0288d9631f4a19288a5f29a96ab4ac06e2ee4c5000c4b80581a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / specsrbench-0.2.1-py3-none-any.whl

Download URL specsrbench-0.2.1-py3-none-any.whl
Size 85.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3f751c69056ef0704bea994b78c2f3cbfa1676414dada27de3313dc4139af8a3
BLAKE2b-256 checksum
How to use checksums
55f60625f3c7e5bde55d95fcef111096e52cab525e44228ef489af4726777302
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Security for specsrbench 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for specsrbench-0.2.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page