SpectraPyle
Stack thousands of astronomical spectra, uncover hidden signals.
SpectraPyle is a flexible, scalable tool for stacking galaxy spectra. It shifts spectra to a common rest frame, normalizes, resamples, sigma-clips, and combines them using median, mean, weighted mean, or geometric mean statistics. Output is a FITS file ready for scientific analysis.
Supported instruments: Euclid (NISP, grisms red/blue) · DESI (merged, back-compatibility only) · Generic (any standard FITS, grism default)
Documentation: Full documentation, tutorials, configuration reference, and API documentation are available on Read the Docs.
Quick Start
Install
Install the latest stable release from PyPI:
pip install spectraPyle
This installs SpectraPyle together with the dependencies required by its interactive GUI.
If you also need a local JupyterLab environment and Voilà, install the notebook extras:
pip install "spectraPyle[notebook]"
For development or an editable installation from the source repository, see the Development section below.
Try SpectraPyle immediately
SpectraPyle includes a ready-to-use sample of 100 Euclid NISP Q1 spectra together with a pre-configured default.gui. This quick-start sample is intended for users who want to see the complete stacking workflow and its output immediately, before configuring their own data.
In a Jupyter notebook, launch the GUI with:
from spectraPyle.gui import start
start()
When SpectraPyle is launched for the first time in a working directory, it creates:
configs/GUI/default.gui
Load this configuration from the GUI and run SpectraPyle. The bundled catalogue and example spectra are located automatically: no input paths need to be configured.
The example performs a complete stack of the supplied Euclid spectra and generates the standard SpectraPyle outputs, including the interactive stacked-spectrum visualization.
The example data are installed as read-only package resources and are not copied into your project directory. Output files are instead written to the local ./outputs directory.
Once you have verified the workflow, you can modify the GUI configuration for your own catalogue and spectra and save project-specific GUI, JSON, or YAML configurations under ./configs/.
Run via GUI
The GUI can be launched directly from any Jupyter environment:
from spectraPyle.gui import start
start()
It is rendered inline in the notebook using ipywidgets; no local server is required.
On ESA DataLabs, SpectraPyle can therefore be used directly from a notebook. For Euclid internal data, see the DataLabs documentation for the dedicated workflow and configuration details.
For a standalone local Voilà interface, the source repository also provides:
python notebooks/run_gui.py
when SpectraPyle has been installed from source with the notebook extras.
Run via CLI
After installation, use the command-line entry point:
spectrapyle --config path/to/config.yaml
Override individual config keys at runtime:
spectrapyle --config config.yaml --instrument.grisms '["red","blue"]'
Minimal YAML config example
instrument:
instrument_name: euclid
survey_name: wide
grisms:
- red
data_release: Q1
io:
input_dir: /path/to/catalog
filename_in: my_catalog
filename_in_extention: fits
output_dir: /path/to/output
grism_io:
red:
spectra_dir: /path/to/spectra
redshift:
z_type: rest_frame
norm:
norm_type: interval
norm_range: [5100, 5500]
resampling:
pixel_resampling_type: lambda
pixel_size_type: instrumental
nyquist_sampling: 5.0
sigmaclip:
sigma_clipping_conditions: 3.0
Configuration
Configs are YAML or JSON. All inputs pass through a strict validation pipeline:
Widgets / JSON / YAML / CLI
↓
normalize_raw_config() runtime/runtime_adapter.py — empty strings→None, path coercion
↓
StackingConfig (Pydantic v2) schema/schema.py — typed field-level validation
↓
StackingConfigResolver schema/schema.py — cross-field rules, instrument defaults
↓
flatten_schema_model() runtime/runtime_adapter.py — back to flat dict (legacy shim)
↓
Stacking(flat_dict).run() stacking/stacking.py
Key config blocks
| Block | Purpose |
|---|---|
instrument |
instrument_name (euclid/desi/generic), grisms list |
io / grism_io |
Input catalog, output dir, per-grism spectra paths |
redshift |
z_type (rest_frame, observed_frame, custom, …), z_value |
norm |
Scalar normalization plus optional continuum normalization |
resampling |
Pixel grid type and size |
sigmaclip |
Sigma-clipping thresholds |
bootstrap |
Bootstrap uncertainty estimation |
parallel |
Multiprocessing CPU fraction |
plot |
Plotly output options |
Optional continuum normalization
Continuum normalization is a separate, instrument-independent preprocessing step. It fits each individual spectral segment before resampling and divides the flux and error by the fitted smooth continuum.
norm:
spectra_normalization: median
continuum_normalize: true
continuum_norm_order: 2
continuum_mask_features: true
continuum_emission_mask_half_width: 12.0
continuum_sigma_clip: 3.0
The scalar normalization does not change the intrinsic EW of an individual spectrum. Continuum normalization instead removes smooth spectral-shape differences before stacking; this can be useful for absorption-line work but does not imply that an EW measured on the final stack is exactly the mean of individual EWs. See the full documentation for details and caveats.
Multi-grism
instrument:
grisms: ["red", "blue"]
io:
grism_io:
red:
spectra_dir: /path/to/red_spectra
blue:
spectra_dir: /path/to/blue_spectra
Old single-grism configs (grism_type: str) are auto-migrated by migrate_v1_to_v2() in
runtime/runtime_adapter.py.
Output Format
SpectraPyle writes a .fits file to output_dir.
HDU[0] — metadata header
from astropy.io import fits
with fits.open("result.fits") as hdu:
redshift = hdu[0].header["REDSHIFT"]
HDU[1] — stacked spectra table
with fits.open("result.fits") as hdu:
data = hdu[1].data
wave = data.field("wavelength")
spec = data.field("specMean")
| Column | Description |
|---|---|
wavelength |
Wavelength array (Å) |
specMean / specMeanDispersion / specMeanError |
Arithmetic mean stack + 1σ dispersion + 1σ uncertainty |
specMedian / specMedianDispersion / specMedianError |
Median stack (robust dispersion via MAD × 1.4826) |
specGeometricMean / specGeometricMeanDispersion / specGeometricMeanError |
Geometric mean (lenient) — non-positive flux values are excluded per pixel; result is NaN if all pixels have non-positive flux |
specStrictGeometricMean / specStrictGeometricMeanDispersion / specStrictGeometricMeanError |
Geometric mean (strict) — returns NaN for any pixel with ≥1 non-positive flux value; use when strict positivity is required |
specMode / specModeDispersion / specModeError |
Mode (Half-Sample Mode estimator, parameter-free); dispersion via MAD × 1.4826 |
specWeightedMean / specWeightedMeanDispersion / specWeightedMeanError |
Inverse-variance weighted mean |
spec16th, spec84th, spec98th, spec99th |
16th, 84th, 97.73rd (≈2σ), and 99.73rd (≈3σ) percentiles |
initialPixelCount |
Total spectra submitted to the stack |
goodPixelCount |
Good flux values used per pixel |
badPixelCount |
Bad (masked) pixels per pixel |
sigmaClippedCount |
Values removed by sigma-clipping per pixel |
geomMeanPixelCount |
Spectra with positive flux that contributed to geometric mean per pixel |
templateNormMaskedCount |
Pixels masked during template normalization |
Visualization
Stacked spectrum plot
After a run, SpectraPyle automatically generates an interactive Plotly figure with two panels: pixel counts (top) and all six stacked estimators with error/dispersion bands, percentile envelopes, and astrophysical line markers (bottom).
from spectraPyle.plot.plot import plotting
plotting("result_STACKING.fits", width=1200, height=700)
2D flux distribution — H5 array viewer
The intermediate HDF5 file (*_array.h5) stores the full matrix of individual resampled spectra before combination.
Use plot_h5_heatmap() to inspect the spectral distribution after a run:
from spectraPyle.plot.plot import plot_h5_heatmap
fig = plot_h5_heatmap(
h5_path="result_array.h5",
fits_path="result_STACKING.fits",
metric="specMedian",
mode="heatmap", # or "lines"
)
fig.show()
Binning: by default, one bin per original wavelength pixel with pixel-exact half-pixel edges. This preserves the physical spacing of linear and log-linear grids without any flux redistribution. Reducing nbinsx below n_pixels uses uniform bins and prints a warning.
Supported Instruments
| Instrument | Grisms | Spectra modes |
|---|---|---|
| Euclid (NISP) | red, blue |
individual fits, combined fits, metadata path |
| DESI (back-compat) | merged |
individual fits, combined fits, metadata path |
| Generic | default |
individual fits, combined fits |
DESI note: The DESI driver is maintained for back-compatibility with Euclid–DESI crossmatched samples. It is not under active development. For new DESI-only workflows, consider using the Generic driver.
ESA DataLabs users: If you are working on the DataLabs platform with Euclid internal data, use
spectra_mode: metadata pathand map the three SIR catalogue columns (datalabs_path,file_name,hdu_index) to SpectraPyle's metadata configuration. Access to internal data releases requires DataLabs authentication. See the full documentation on Read the Docs for the complete workflow.
Architecture
Config pipeline
Widgets / JSON / YAML / CLI
↓
normalize_raw_config() runtime/runtime_adapter.py
↓
StackingConfig (Pydantic v2) schema/schema.py
↓
StackingConfigResolver schema/schema.py
↓
flatten_schema_model() runtime/runtime_adapter.py ← legacy shim, to be deprecated
↓
Stacking(flat_dict).run() stacking/stacking.py
Key modules
| Module | Responsibility |
|---|---|
schema/schema.py |
Pydantic models: StackingConfig, StackingConfigResolver. Instrument rules from instruments_rules.json. |
runtime/runtime_adapter.py |
Input normalization, GUI→schema adapter, JSON/YAML loaders, version migration. |
stacking/stacking.py |
Stacking class: orchestrates the full run. Chunks spectra, writes HDF5, sigma-clips, computes statistics, saves FITS. |
process/processes.py |
main_parallel(): multiprocessing loop — read → shift → resample → normalize. |
instruments/euclid.py |
Euclid spectral reader and instrument-specific handling. |
instruments/desi.py |
DESI driver (back-compatibility with Euclid crossmatched samples only). |
instruments/generic.py |
Generic FITS driver: binary table + WCS header fallback, auto-detects wavelength range. |
instruments/instruments_rules.json |
Authoritative per-instrument constants, quality defaults, grisms, data releases. |
io/IO.py |
Catalog reader, wavelength grid builder, FITS output writer. |
io/filename_builder.py |
Auto-generates filename_out from config fields. |
spectrum/spectra.py |
Per-spectrum read, redshift shift, extinction correction. |
spectrum/resampling.py |
Wavelength grid resampling. |
spectrum/normalization.py |
Flux normalization; francis1991_normalize for template mode. |
statistic/statistics.py |
Stacking statistics and bootstrap uncertainties. |
plot/plot.py |
Plotly interactive visualization of the final stack and HDF5 spectral distribution. |
physics/extinction.py |
Galactic extinction correction (dust_extinction). |
utils/log.py |
Unified logging. |
Adding a new instrument
- Add an entry to
instruments/instruments_rules.jsonwith constants, quality defaults, surveys, grisms, and data releases. - Create
instruments/<name>.pyimplementing the required spectral-reading and instrument-specific functions. - Register any instrument- or release-specific filename conventions.
Development
Clone the repository and install SpectraPyle in editable mode with all optional dependencies:
git clone https://github.com/squai/SpectraPyle.git
cd SpectraPyle/project_root
pip install -e ".[all]"
Lint and format
ruff check src/
black src/
Sanity check
spectrapyle --config path/to/config.yaml
Generate documentation locally
The latest documentation is built automatically on Read the Docs after updates to the repository. To build and preview it locally:
cd docs
make html
The generated documentation is available under docs/_build/html/.
Citing SpectraPyle
If you use SpectraPyle in your research, please cite:
Euclid Collaboration, Quai S., Pozzetti L., et al. 2026, Astronomy & Astrophysics, 707, A232.
DOI: 10.1051/0004-6361/202557329
BibTeX:
@ARTICLE{Quai2026,
author = {{Euclid Collaboration: Quai}, S. and {Pozzetti}, L. and {Talia}, M. and others},
title = "{Euclid preparation. LXXXII. Predicting star-forming galaxy scaling relations with the spectral stacking code SpectraPyle}",
journal = {\aap},
year = 2026,
month = mar,
volume = {707},
pages = {A232},
doi = {10.1051/0004-6361/202557329},
archivePrefix = {arXiv},
eprint = {2509.16120},
primaryClass = {astro-ph.GA}
}
Publications using SpectraPyle must also include the following acknowledgment:
ELSA: Euclid Legacy Science Advanced analysis tools (Grant Agreement no. 101135203) is funded by the European Union. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or Innovate UK. Neither the European Union nor the granting authority can be held responsible for them. UK participation is funded through the UK Horizon guarantee scheme under Innovate UK grant 10093177.
If you use SpectraPyle with Euclid data, you must also follow the Euclid Consortium publication policy and include the standard Euclid acknowledgment text.
For the full reference and detailed acknowledgment requirements, see the SpectraPyle documentation.
Contributors & Development Team
Lead Developer & Maintainer:
- Salvatore Quai (University of Bologna) — salvatore.quai@unibo.it
Contributors:
- Lucia Pozzetti
- Margherita Talia
- Zhiying Mao
- Xavier Lopez Lopez
- Marcel Lindmeir
- Elisabeta Lusso
- Sotiria Fotopoulou
- Michele Moresco
For inquiries or contributions, please contact Salvatore Quai.
Contributing & Collaboration
We welcome discussions about new features, extensions, and scientific collaborations.
If you have ideas for:
- New stacking statistics or methods — we'd like to hear about them
- Support for additional instruments or data formats — let's discuss
- Performance improvements or refactoring — contributions are valued
- Joint research projects using SpectraPyle — we're open to collaboration
Please open an issue or pull request on the GitHub repository, or contact the development team:
- salvatore.quai@unibo.it, salvatore.quai@gmail.com (lead developer)
All contributions are reviewed and credited. We follow standard open-source practices for code review and testing.
License
MIT — see LICENSE.
Contact
Salvatore Quai — salvatore.quai@unibo.it, salvatore.quai@gmail.com
Metadata
Release files for spectraPyle 5.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| spectrapyle-5.0.3.tar.gz | 944.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| spectrapyle-5.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.0 MB
Release files / spectrapyle-5.0.3.tar.gz
| Download URL | spectrapyle-5.0.3.tar.gz |
|---|---|
| Size | 944.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
871611e3725dae97789277f15efea53ff3f0ab2a7e927cbd25867f5f82077bff
|
|
BLAKE2b-256 checksum How to use checksums |
3b6735a70cc0d8ec9acf8b34b50c481b1b0b4f2f81df4844a21608a4afe86234
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 22, 2026.
Transparency logRelease files / spectrapyle-5.0.3-py3-none-any.whl
| Download URL | spectrapyle-5.0.3-py3-none-any.whl |
|---|---|
| Size | 1.1 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
56b66d490e6656215e45ce8d1ffee3fa3bfa846f0e51968080948e48207509b1
|
|
BLAKE2b-256 checksum How to use checksums |
dfefc54cf66c1c00d3a1d9c73657ef46518fb9560823ee00f269eaf50f629f47
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 22, 2026.
Transparency logSecurity for spectraPyle 5.0.3
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.
Attestation for spectrapyle-5.0.3.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 22, 2026.
Transparency logA 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.