specsr 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- specsr-1.0.2/.gitignore +273 -0
- specsr-1.0.2/ARCHITECTURE.md +222 -0
- specsr-1.0.2/CHANGELOG.md +71 -0
- specsr-1.0.2/LICENSE +21 -0
- specsr-1.0.2/PKG-INFO +293 -0
- specsr-1.0.2/README.md +218 -0
- specsr-1.0.2/configs/finetune/sr1.yaml +38 -0
- specsr-1.0.2/configs/finetune/sr2.yaml +91 -0
- specsr-1.0.2/configs/finetune/sr2_fluxfix_smoke.yaml +71 -0
- specsr-1.0.2/configs/sr1.yaml +31 -0
- specsr-1.0.2/configs/sr2.yaml +136 -0
- specsr-1.0.2/configs/sweeps/sr1.yaml +186 -0
- specsr-1.0.2/configs/sweeps/sr2.yaml +194 -0
- specsr-1.0.2/configs/sweeps/zhead.yaml +168 -0
- specsr-1.0.2/configs/zhead.yaml +21 -0
- specsr-1.0.2/configs/zhead_sr2.yaml +19 -0
- specsr-1.0.2/docs/_static/.gitkeep +0 -0
- specsr-1.0.2/docs/_static/architecture.png +0 -0
- specsr-1.0.2/docs/api/index.md +33 -0
- specsr-1.0.2/docs/changelog.md +2 -0
- specsr-1.0.2/docs/conf.py +73 -0
- specsr-1.0.2/docs/guides/data.md +136 -0
- specsr-1.0.2/docs/guides/evaluation.md +51 -0
- specsr-1.0.2/docs/guides/installation.md +89 -0
- specsr-1.0.2/docs/guides/training.md +181 -0
- specsr-1.0.2/docs/guides/tutorials.md +62 -0
- specsr-1.0.2/docs/index.md +95 -0
- specsr-1.0.2/pyproject.toml +104 -0
- specsr-1.0.2/src/specsr/__init__.py +34 -0
- specsr-1.0.2/src/specsr/checkpoints.py +125 -0
- specsr-1.0.2/src/specsr/cli.py +158 -0
- specsr-1.0.2/src/specsr/config.py +71 -0
- specsr-1.0.2/src/specsr/data/__init__.py +37 -0
- specsr-1.0.2/src/specsr/data/augment.py +195 -0
- specsr-1.0.2/src/specsr/data/build.py +345 -0
- specsr-1.0.2/src/specsr/data/datasets.py +254 -0
- specsr-1.0.2/src/specsr/data/grid.py +257 -0
- specsr-1.0.2/src/specsr/data/ingest.py +204 -0
- specsr-1.0.2/src/specsr/data/splits.py +354 -0
- specsr-1.0.2/src/specsr/data/stitch.py +213 -0
- specsr-1.0.2/src/specsr/evaluation/__init__.py +268 -0
- specsr-1.0.2/src/specsr/evaluation/figures.py +376 -0
- specsr-1.0.2/src/specsr/evaluation/runner.py +74 -0
- specsr-1.0.2/src/specsr/inference/__init__.py +24 -0
- specsr-1.0.2/src/specsr/inference/pipeline.py +421 -0
- specsr-1.0.2/src/specsr/linefit.py +170 -0
- specsr-1.0.2/src/specsr/metrics.py +354 -0
- specsr-1.0.2/src/specsr/models/__init__.py +36 -0
- specsr-1.0.2/src/specsr/models/blocks.py +142 -0
- specsr-1.0.2/src/specsr/models/lines.py +142 -0
- specsr-1.0.2/src/specsr/models/loaders.py +205 -0
- specsr-1.0.2/src/specsr/models/sr1.py +94 -0
- specsr-1.0.2/src/specsr/models/sr2.py +464 -0
- specsr-1.0.2/src/specsr/models/zhead.py +327 -0
- specsr-1.0.2/src/specsr/paths.py +118 -0
- specsr-1.0.2/src/specsr/plotting.py +1402 -0
- specsr-1.0.2/src/specsr/runtime.py +191 -0
- specsr-1.0.2/src/specsr/training/__init__.py +28 -0
- specsr-1.0.2/src/specsr/training/common.py +272 -0
- specsr-1.0.2/src/specsr/training/losses.py +756 -0
- specsr-1.0.2/src/specsr/training/runner.py +114 -0
- specsr-1.0.2/src/specsr/training/sr1.py +307 -0
- specsr-1.0.2/src/specsr/training/sr2.py +643 -0
- specsr-1.0.2/src/specsr/training/zhead.py +418 -0
- specsr-1.0.2/src/specsr/training/zhead_sources.py +210 -0
- specsr-1.0.2/src/specsr/training/ztransform.py +127 -0
- specsr-1.0.2/src/specsr/wandb_plots.py +136 -0
- specsr-1.0.2/tests/test_augment.py +186 -0
- specsr-1.0.2/tests/test_cli_sweep_overrides.py +110 -0
- specsr-1.0.2/tests/test_data_and_config.py +370 -0
- specsr-1.0.2/tests/test_evaluation_decode.py +177 -0
- specsr-1.0.2/tests/test_grid.py +179 -0
- specsr-1.0.2/tests/test_inference_pipeline.py +199 -0
- specsr-1.0.2/tests/test_ingest.py +243 -0
- specsr-1.0.2/tests/test_losses.py +715 -0
- specsr-1.0.2/tests/test_models.py +501 -0
- specsr-1.0.2/tests/test_notify_run.py +138 -0
- specsr-1.0.2/tests/test_package_reproduces_checkpoints.py +158 -0
- specsr-1.0.2/tests/test_public_release.py +222 -0
- specsr-1.0.2/tests/test_run_artifacts.py +155 -0
- specsr-1.0.2/tests/test_splits.py +234 -0
- specsr-1.0.2/tests/test_sr2_loss_line_weighting.py +189 -0
- specsr-1.0.2/tests/test_sweep_configs.py +261 -0
- specsr-1.0.2/tests/test_tutorial_samples_are_held_out.py +73 -0
- specsr-1.0.2/tests/test_version_is_single_sourced.py +53 -0
- specsr-1.0.2/tests/test_zhead_checkpoint_naming.py +51 -0
- specsr-1.0.2/tests/test_zhead_sources.py +272 -0
- specsr-1.0.2/tutorials_for_user/01_quickstart.ipynb +441 -0
- specsr-1.0.2/tutorials_for_user/02_your_own_spectrum.ipynb +406 -0
- specsr-1.0.2/tutorials_for_user/03_batches_and_trust.ipynb +410 -0
- specsr-1.0.2/tutorials_for_user/README.md +81 -0
- specsr-1.0.2/tutorials_for_user/sample_24_spectra.npz +0 -0
- specsr-1.0.2/tutorials_for_user/sample_one_spectrum.npz +0 -0
specsr-1.0.2/.gitignore
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
/lib/
|
|
18
|
+
/lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
#.idea/
|
|
169
|
+
|
|
170
|
+
# PyPI configuration file
|
|
171
|
+
.pypirc
|
|
172
|
+
# Raw + derived data (repo root only; must not match src/specsr/data)
|
|
173
|
+
/data/
|
|
174
|
+
line_detect/
|
|
175
|
+
sup_res/
|
|
176
|
+
.DS_Store
|
|
177
|
+
toy_model/.DS_Store
|
|
178
|
+
line_detect/line-finding/
|
|
179
|
+
|
|
180
|
+
*.pkl
|
|
181
|
+
*.npz
|
|
182
|
+
*.npy
|
|
183
|
+
wandb/
|
|
184
|
+
.codex
|
|
185
|
+
.claude
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------
|
|
188
|
+
# specsr project-specific
|
|
189
|
+
# ---------------------------------------------------------------
|
|
190
|
+
# Model weights are NOT version-controlled. They are published to the
|
|
191
|
+
# Hugging Face Hub and fetched on demand by specsr.checkpoints.
|
|
192
|
+
*.pth
|
|
193
|
+
*.pt
|
|
194
|
+
*.ckpt
|
|
195
|
+
*.safetensors
|
|
196
|
+
|
|
197
|
+
# The tutorial spectra are the exception to the blanket *.npz rule above. They
|
|
198
|
+
# are small (~0.9 MB), drawn from the validation split, and the notebooks are
|
|
199
|
+
# useless without them -- a tutorial that needs an 88 GB survey tree first is not
|
|
200
|
+
# a tutorial. Weights stay ignored; those come from the Hub.
|
|
201
|
+
!tutorials_for_user/*.npz
|
|
202
|
+
|
|
203
|
+
# Raw JWST/JADES data lives outside the repo (see SPECSR_JADES_ROOT);
|
|
204
|
+
# only derived products are built here, and those are regenerable.
|
|
205
|
+
JADES_data/
|
|
206
|
+
|
|
207
|
+
# Local caches / editor and OS cruft
|
|
208
|
+
.virtual_documents/
|
|
209
|
+
.ipynb_checkpoints/
|
|
210
|
+
htmlcov/
|
|
211
|
+
.ruff_cache/
|
|
212
|
+
|
|
213
|
+
# Build artefacts from the src/ layout
|
|
214
|
+
src/*.egg-info/
|
|
215
|
+
|
|
216
|
+
# ---------------------------------------------------------------
|
|
217
|
+
# LaTeX build artefacts (papers are compiled locally, not on Overleaf)
|
|
218
|
+
# ---------------------------------------------------------------
|
|
219
|
+
*.aux
|
|
220
|
+
*.bbl
|
|
221
|
+
*.blg
|
|
222
|
+
*.fdb_latexmk
|
|
223
|
+
*.fls
|
|
224
|
+
*.lof
|
|
225
|
+
*.lot
|
|
226
|
+
*.toc
|
|
227
|
+
*.synctex.gz
|
|
228
|
+
*.synctex(busy)
|
|
229
|
+
*.nav
|
|
230
|
+
*.snm
|
|
231
|
+
*.vrb
|
|
232
|
+
*.run.xml
|
|
233
|
+
*.bcf
|
|
234
|
+
*.glo
|
|
235
|
+
*.gls
|
|
236
|
+
*.glg
|
|
237
|
+
*.ist
|
|
238
|
+
*.acn
|
|
239
|
+
*.acr
|
|
240
|
+
*.alg
|
|
241
|
+
*.spl
|
|
242
|
+
*.figlist
|
|
243
|
+
*.makefile
|
|
244
|
+
*.figlist-*
|
|
245
|
+
# latexmk/pdflatex logs and the hyperref bookmark file
|
|
246
|
+
*.out
|
|
247
|
+
# Built PDFs: regenerable from source
|
|
248
|
+
|
|
249
|
+
# Sphinx autosummary stubs: regenerated on every docs build
|
|
250
|
+
docs/api/generated/
|
|
251
|
+
|
|
252
|
+
# Training run outputs (checkpoints + per-stage logs). Every stage writes here
|
|
253
|
+
# via --out-dir; nothing is written back into the source tree.
|
|
254
|
+
runs/
|
|
255
|
+
|
|
256
|
+
# Local checkpoint archives, kept out of git -- weights live on the Hugging Face
|
|
257
|
+
# Hub, and these are working copies. `*.pth` above already excludes the weights
|
|
258
|
+
# themselves; these lines also exclude the READMEs and MD5SUMS beside them.
|
|
259
|
+
checkpoints_baseline_*/
|
|
260
|
+
checkpoints_errorfix_*/
|
|
261
|
+
checkpoints_run5_*/
|
|
262
|
+
|
|
263
|
+
# Cached evaluation predictions and derived result tables (all regenerable).
|
|
264
|
+
# Figures do NOT live here: figure scripts write straight into the manuscript's
|
|
265
|
+
# own plot directory, or into SPECSR_OUTPUT_DIR when there is no manuscript.
|
|
266
|
+
cache/
|
|
267
|
+
checkpoints/release/MD5SUMS
|
|
268
|
+
checkpoints/release/config_logR.yaml
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# Package outputs (figures, predictions, evaluation tables). See
|
|
272
|
+
# specsr.paths.output_dir / SPECSR_OUTPUT_DIR.
|
|
273
|
+
/outputs/
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
This is the PUBLIC architecture document, shipped to the released repository as
|
|
3
|
+
ARCHITECTURE.md by scripts/make_public_release.sh.
|
|
4
|
+
|
|
5
|
+
It is a different file from the development ARCHITECTURE.md in the private
|
|
6
|
+
repository. That one is a decisions log -- why code moved between directories,
|
|
7
|
+
which experiment forced which change, what a given failure cost -- and is
|
|
8
|
+
addressed to whoever maintains the working tree. This one describes the design
|
|
9
|
+
as it stands, for somebody reading or extending the code.
|
|
10
|
+
-->
|
|
11
|
+
|
|
12
|
+
# Architecture
|
|
13
|
+
|
|
14
|
+
How `specsr` is put together, and the constraints the code enforces on itself.
|
|
15
|
+
For how to *use* it, start with the [documentation](https://aryana-haghjoo.github.io/specsr/)
|
|
16
|
+
or the notebooks in `tutorials_for_user/`.
|
|
17
|
+
|
|
18
|
+
## The model
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
Three stages, trained in order. Each consumes the frozen output of the one
|
|
23
|
+
before it, so an error in an early stage is inherited rather than corrected.
|
|
24
|
+
|
|
25
|
+
**SR1 — super-resolution backbone.** A 1D residual CNN (16 blocks x 120
|
|
26
|
+
channels) that maps a prism spectrum on the common grid to a grating-like
|
|
27
|
+
reconstruction on the same grid, emitting a mean and a per-pixel log-variance.
|
|
28
|
+
It is deliberately conservative: it sharpens structure the input supports and is
|
|
29
|
+
not asked to invent line profiles.
|
|
30
|
+
|
|
31
|
+
**ZHead — redshift inference.** Six dilated convolution blocks x 128 channels
|
|
32
|
+
with attention pooling over wavelength, reading SR1's mean and log-variance. The
|
|
33
|
+
output is a softmax distribution over 1,024 redshift bins spanning 0 <= z <= 15;
|
|
34
|
+
the point estimate is a soft-argmax in a window around the modal bin, and the
|
|
35
|
+
spread comes from the full distribution.
|
|
36
|
+
|
|
37
|
+
Two properties matter and both are structural rather than tuned. The head must
|
|
38
|
+
be **position-aware** — on a logarithmic wavelength grid a redshift is a
|
|
39
|
+
translation, so a convolutional trunk followed by a global mean pool cannot
|
|
40
|
+
measure one even in principle. And it must predict a **distribution** rather than
|
|
41
|
+
a point: the characteristic failure is a misidentified line, which puts a second
|
|
42
|
+
mode elsewhere, and a Gaussian head answers such a posterior with the mean,
|
|
43
|
+
landing in the empty valley between the modes.
|
|
44
|
+
`tests/test_models.py` pins both properties against synthetic data.
|
|
45
|
+
|
|
46
|
+
**SR2 — physics-informed residual refiner.** Multi-head self-attention over 98
|
|
47
|
+
emission-line tokens (`specsr.models.lines`), each emitting a parametric
|
|
48
|
+
Gaussian — amplitude, width, offset — gated by a presence probability, plus a
|
|
49
|
+
CNN branch for smooth continuum corrections. The two branches sum to a residual
|
|
50
|
+
that is added to SR1's output.
|
|
51
|
+
|
|
52
|
+
SR2's five input channels are the prism spectrum, SR1's mean and sigma, a line
|
|
53
|
+
mask `m(lambda; z, sigma_z)` built at the *predicted* redshift, and the redshift
|
|
54
|
+
itself. There is no teacher forcing anywhere: SR2 only ever sees redshifts ZHead
|
|
55
|
+
inferred, never the catalogue value, so the chain behaves at training time the
|
|
56
|
+
way it behaves at inference time.
|
|
57
|
+
|
|
58
|
+
The presence gate is **supervised**, not merely penalised — trained by
|
|
59
|
+
class-balanced cross-entropy against the lines the reference spectrum actually
|
|
60
|
+
shows. A blanket sparsity penalty applies the same downward gradient to every
|
|
61
|
+
line logit whether or not the line is real; because the model forms
|
|
62
|
+
`amplitude * presence`, that becomes a constant multiplier on every amplitude and
|
|
63
|
+
can silently disable the entire line branch while every loss curve still looks
|
|
64
|
+
healthy. `train/presence_sep` — the ratio of gate output on real lines to absent
|
|
65
|
+
ones — is logged and guarded for exactly this reason.
|
|
66
|
+
|
|
67
|
+
Line-mask widths grow with ZHead's predicted `sigma_z`, capped: a spectrum whose
|
|
68
|
+
redshift is uncertain gets a wider mask rather than a confidently misplaced
|
|
69
|
+
narrow one. The cap matters because the same mask weights SR2's in-line loss
|
|
70
|
+
term, and an uncapped mask on a hopeless redshift would relax that term into a
|
|
71
|
+
plain reconstruction loss without any visible symptom.
|
|
72
|
+
|
|
73
|
+
## The wavelength grid
|
|
74
|
+
|
|
75
|
+
Everything runs on one grid: **logarithmic, constant R = 4,000, 1.0-5.3 µm,
|
|
76
|
+
6,671 pixels** (`specsr.data.grid.DEFAULT_GRID`). A log grid holds
|
|
77
|
+
lambda/dlambda fixed, which is how a spectrograph actually samples — a fixed
|
|
78
|
+
*fractional* step matches the instrument at every wavelength instead of being too
|
|
79
|
+
coarse in the blue and wasteful in the red.
|
|
80
|
+
|
|
81
|
+
Two rules follow, and both are load-bearing:
|
|
82
|
+
|
|
83
|
+
- **Regrid the low-resolution data up onto the high-resolution sampling; never
|
|
84
|
+
resample the high-resolution spectra down.** A linear grid coarse enough to
|
|
85
|
+
undersample the gratings makes the *grid*, not the model, the ceiling on
|
|
86
|
+
achievable sharpness — and the resulting model looks like it has hit a
|
|
87
|
+
fundamental limit.
|
|
88
|
+
- **Resampling integrates; it does not interpolate.** The physically meaningful
|
|
89
|
+
quantity in an emission line is its integrated flux, an area, not the height of
|
|
90
|
+
the curve at particular abscissae. `resample_flux_conserving` is the default
|
|
91
|
+
for that reason, and `SpecSRPipeline` applies it to any input handed to it on a
|
|
92
|
+
different grid.
|
|
93
|
+
|
|
94
|
+
## Package layout
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
src/specsr/
|
|
98
|
+
data/ ingest, grid, stitching, augmentation, datasets, group splits
|
|
99
|
+
models/ SR1, ZHead, SR2, the line catalogue, checkpoint loaders
|
|
100
|
+
training/ the three training loops, losses, redshift transform
|
|
101
|
+
inference/ SpecSRPipeline
|
|
102
|
+
evaluation/ split selection, chain inference, the published figures
|
|
103
|
+
plotting.py every figure in the paper
|
|
104
|
+
metrics.py linefit.py line fitting and summary statistics
|
|
105
|
+
checkpoints.py on-demand weight fetching from the Hugging Face Hub
|
|
106
|
+
paths.py environment-driven data, cache and output directories
|
|
107
|
+
configs/ stage configs and W&B sweep definitions
|
|
108
|
+
scripts/ thin entry points: argument parsing and file IO only
|
|
109
|
+
tests/ unit tests plus checkpoint-reproduction guards
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**All library code lives under `src/specsr/`.** `scripts/` parses arguments and
|
|
113
|
+
moves files; it defines no model, dataset or loss. This is not style — the
|
|
114
|
+
package was once a parallel implementation that nothing imported during training,
|
|
115
|
+
and an unexercised copy drifts silently. It did, within a day, and anyone loading
|
|
116
|
+
a checkpoint through the package would have got a different network from the one
|
|
117
|
+
that trained. `tests/test_package_reproduces_checkpoints.py` loads real
|
|
118
|
+
checkpoints through the *package* classes and asserts on measured numbers, so the
|
|
119
|
+
drift cannot recur unnoticed.
|
|
120
|
+
|
|
121
|
+
## Data flow
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
raw JADES x1d products
|
|
125
|
+
| specsr.data.ingest read prism and G140M/G235M/G395M extractions
|
|
126
|
+
| specsr.data.stitch one 1-5 µm grating reference per galaxy
|
|
127
|
+
| quality cuts coverage and S/N; secure catalogue redshifts only
|
|
128
|
+
| specsr.data.grid both members resampled onto DEFAULT_GRID
|
|
129
|
+
| specsr.data.augment training galaxies only -> 21 rows each
|
|
130
|
+
v
|
|
131
|
+
paired .npz (LR, HR, sigma, z, parent id, provenance)
|
|
132
|
+
| specsr.data.splits split over parent galaxies
|
|
133
|
+
v
|
|
134
|
+
specsr train sr1 -> zhead -> sr2 each freezing the previous stage
|
|
135
|
+
v
|
|
136
|
+
specsr infer / evaluate, SpecSRPipeline.from_pretrained()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Invariants the code enforces
|
|
140
|
+
|
|
141
|
+
These are the assumptions that, when broken, produce results that look correct.
|
|
142
|
+
Each is checked in code rather than left to discipline.
|
|
143
|
+
|
|
144
|
+
**Splits are drawn over parent galaxies, never over rows.** Augmentation
|
|
145
|
+
multiplies each galaxy into 21 highly correlated rows differing only by a small
|
|
146
|
+
redshift offset and noise. A row-wise split puts near-duplicates of every
|
|
147
|
+
held-out galaxy into training and every held-out number comes out optimistic.
|
|
148
|
+
`specsr.data.splits` groups by parent and refuses to return a leaking split;
|
|
149
|
+
`assert_no_group_leakage` is called before training, not after.
|
|
150
|
+
|
|
151
|
+
**Only training galaxies are augmented.** The builder decides the split *before*
|
|
152
|
+
augmenting, so a held-out galaxy contributes exactly one row — its real observed
|
|
153
|
+
spectrum — and `train + val` accounts for every row in the file. A row that must
|
|
154
|
+
never be read is a trap for whoever does not know that. The builder then
|
|
155
|
+
re-derives the split from the file it just wrote and refuses to finish if the two
|
|
156
|
+
disagree.
|
|
157
|
+
|
|
158
|
+
**Evaluation is on original spectra only.** The built product augments every
|
|
159
|
+
galaxy, so an unfiltered held-out partition would be ~95% synthetic rows: it
|
|
160
|
+
would report performance on perturbations rather than observations, over
|
|
161
|
+
correlated samples, inflating apparent *n* and understating uncertainties. The
|
|
162
|
+
default configuration is 80/20 over galaxies with **no sealed test partition**,
|
|
163
|
+
which means the reported number is selected on the set it is reported from; pass
|
|
164
|
+
`val_frac=0.1, allow_empty_test=False` to `get_training_split` to restore a
|
|
165
|
+
three-way split with a withheld test set. Switching costs no retraining, because
|
|
166
|
+
galaxies are permuted once under a fixed seed and training takes the first
|
|
167
|
+
`train_frac`.
|
|
168
|
+
|
|
169
|
+
**Upstream checkpoints are named explicitly, never discovered.** Every stage
|
|
170
|
+
takes its predecessors' paths on the command line, so a checkpoint records what
|
|
171
|
+
it was trained on top of. Evaluating against a directory a running job is
|
|
172
|
+
overwriting compares refiners sitting on top of *different* backbones; both
|
|
173
|
+
numbers are real and neither is comparable to the other.
|
|
174
|
+
|
|
175
|
+
**Checkpoints are never written into the source tree.** Every stage takes
|
|
176
|
+
`--out-dir`, which makes it structurally impossible for a rehearsal run to
|
|
177
|
+
address a real run's output path.
|
|
178
|
+
|
|
179
|
+
**A checkpoint carries what is needed to decode it.** The redshift head stores
|
|
180
|
+
its normalisation bounds; without them the pipeline cannot convert a network
|
|
181
|
+
output back into a redshift, and it raises rather than guessing. Guessing does
|
|
182
|
+
not fail visibly — it returns confident, plausible, wrong redshifts.
|
|
183
|
+
|
|
184
|
+
**Verify checkpoints by measurement, not by hash.** A hash proves two files
|
|
185
|
+
agree; it does not prove either is correct. Check a checkpoint's stored config,
|
|
186
|
+
or measure it against a known number.
|
|
187
|
+
|
|
188
|
+
**Every run logs figures, not only scalars.** Each stage writes a validation
|
|
189
|
+
panel to Weights & Biases alongside its metrics, and the redshift panel is drawn
|
|
190
|
+
by the same `plotting.plot_redshift_panel` the published figure uses. Both of
|
|
191
|
+
this project's expensive failures were invisible in loss curves: a refiner can
|
|
192
|
+
manufacture lines in the wrong place while its loss falls, and scalar redshift
|
|
193
|
+
metrics cannot distinguish ordinary scatter about the diagonal from a second
|
|
194
|
+
locus off it.
|
|
195
|
+
|
|
196
|
+
## Numerical stability
|
|
197
|
+
|
|
198
|
+
Gradient clipping is not a defence against an exploding gradient.
|
|
199
|
+
`torch.nn.utils.clip_grad_norm_` scales by `max_norm / total_norm`; a single
|
|
200
|
+
non-finite element makes the norm infinite, the scale zero, and their product
|
|
201
|
+
NaN — so clipping *launders* an overflow into every weight, and an adaptive
|
|
202
|
+
optimiser's moments then make it permanent. All three stages call
|
|
203
|
+
`specsr.training.common.clip_grad_or_skip`, which skips the optimiser step when
|
|
204
|
+
the gradient norm is not finite. SR2's line branch is the one that needs it: it
|
|
205
|
+
runs at a gradient norm around 0.04 and spikes many orders of magnitude above
|
|
206
|
+
that, and a meaningful fraction of its steps are skipped. This is containment,
|
|
207
|
+
not a fix.
|
|
208
|
+
|
|
209
|
+
## Extending it
|
|
210
|
+
|
|
211
|
+
- **A new spectral feature** goes in `specsr.models.lines`. The list order *is*
|
|
212
|
+
the token order the checkpoints were built with, so append rather than insert,
|
|
213
|
+
and expect existing SR2 weights not to load if you change the count.
|
|
214
|
+
- **A different wavelength grid** means rebuilding the dataset and retraining
|
|
215
|
+
from SR1 up: the grid is baked into every checkpoint's input length.
|
|
216
|
+
- **A new stage or loss term** goes under `src/specsr/training/`, with its
|
|
217
|
+
hyperparameters added to that stage's `DEFAULTS`. Sweep configs are validated
|
|
218
|
+
against `DEFAULTS`, so a search axis the trainer does not read fails the test
|
|
219
|
+
suite rather than quietly wasting a sweep.
|
|
220
|
+
- **A new figure** goes in `specsr.plotting` as a function, called from a thin
|
|
221
|
+
script. Wrap it in `plt.rc_context(PAPER_RC)` rather than setting fonts
|
|
222
|
+
locally.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `specsr` are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
<!--
|
|
8
|
+
This is the PUBLIC changelog, shipped to the released repository as
|
|
9
|
+
CHANGELOG.md by scripts/make_public_release.sh.
|
|
10
|
+
|
|
11
|
+
It is deliberately not the same file as the development CHANGELOG.md in the
|
|
12
|
+
private repository. That one records the internal history -- which run
|
|
13
|
+
directory produced which checkpoint, which review comment prompted which
|
|
14
|
+
change -- and is addressed to whoever maintains the working tree. This one is
|
|
15
|
+
addressed to somebody installing the package, and starts at the first public
|
|
16
|
+
release. Add entries here when a change affects users.
|
|
17
|
+
-->
|
|
18
|
+
|
|
19
|
+
## [1.0.2] - 2026-08-15
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Published to PyPI: `pip install "specsr[hub]"`.
|
|
24
|
+
- `scripts/notify-run --setup` asks for your email address and mail server and
|
|
25
|
+
writes the configuration for you, instead of requiring a hand-written file.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- `specsr.__version__` reported a stale hardcoded version rather than the
|
|
30
|
+
installed one.
|
|
31
|
+
- `log_checkpoint_artifact` could raise `AttributeError` at the end of training
|
|
32
|
+
when a `wandb/` run directory shadowed an uninstalled `wandb` library.
|
|
33
|
+
|
|
34
|
+
## [1.0.1] - 2026-08-15
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- First public release of `specsr`: a physics-informed, three-stage model that
|
|
39
|
+
super-resolves JWST/NIRSpec prism spectra (R ~ 100) towards the medium
|
|
40
|
+
gratings (R ~ 1000).
|
|
41
|
+
- `SpecSRPipeline.from_pretrained()` downloads the released weights from the
|
|
42
|
+
Hugging Face Hub on first use; no local data or credentials required.
|
|
43
|
+
- Command line: `specsr build-dataset | train | infer | evaluate`.
|
|
44
|
+
- Three tutorial notebooks with bundled held-out spectra, runnable without the
|
|
45
|
+
survey data.
|
|
46
|
+
- `SPECSR_OUTPUT_DIR` collects everything the package writes -- figures,
|
|
47
|
+
predictions, evaluation tables -- in one place, defaulting to `./outputs`.
|
|
48
|
+
- `ARCHITECTURE.md` describes the three stages, the wavelength grid, and the
|
|
49
|
+
invariants the code enforces on itself.
|
|
50
|
+
- Optional start/finish email notifications for training runs, via
|
|
51
|
+
`scripts/notify-run`. Off unless configured with your own address and mail
|
|
52
|
+
server; unconfigured it is a transparent passthrough.
|
|
53
|
+
|
|
54
|
+
Archived on Zenodo: [10.5281/zenodo.21943197](https://doi.org/10.5281/zenodo.21943197)
|
|
55
|
+
(all versions: [10.5281/zenodo.21943196](https://doi.org/10.5281/zenodo.21943196)).
|
|
56
|
+
|
|
57
|
+
### Known limitations
|
|
58
|
+
|
|
59
|
+
These are measured, not suspected, and are documented in the paper:
|
|
60
|
+
|
|
61
|
+
- **Absolute flux scale is not predicted.** Training standardises each spectrum
|
|
62
|
+
independently, so the model learns a shape-to-shape mapping and returns output
|
|
63
|
+
on the input's scale. Compare in normalised units.
|
|
64
|
+
- **Integrated line fluxes are systematically under-recovered**, and recovery
|
|
65
|
+
depends strongly on line brightness. Reconstructions are suited to locating
|
|
66
|
+
and detecting lines rather than to measuring their strengths.
|
|
67
|
+
- **The redshift head is a conditioning stage, not a redshift pipeline.** Its
|
|
68
|
+
catastrophic-outlier rate is high enough that a redshift should be checked
|
|
69
|
+
against the line positions it implies.
|
|
70
|
+
- **Super-resolution does not improve on direct analysis of the prism** for the
|
|
71
|
+
[O III] doublet ratio test. See the paper for the comparison.
|
specsr-1.0.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aryana Haghjoo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|