spectro-kernel 0.2.0__tar.gz → 0.3.0__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.
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/CHANGELOG.md +188 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/PKG-INFO +1 -1
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/tutorials/long-slit-reduction.md +5 -2
- spectro_kernel-0.3.0/src/spectro_kernel/algorithms/corrections/response_from_standard.py +291 -0
- spectro_kernel-0.3.0/src/spectro_kernel/algorithms/extraction/boxcar.py +343 -0
- spectro_kernel-0.3.0/src/spectro_kernel/algorithms/extraction/detect_trace.py +286 -0
- spectro_kernel-0.3.0/src/spectro_kernel/algorithms/extraction/optimal.py +403 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/_easyspec_apply.py +39 -3
- spectro_kernel-0.3.0/src/spectro_kernel/algorithms/reduction/dark_combine.py +174 -0
- spectro_kernel-0.3.0/src/spectro_kernel/algorithms/reduction/flat_combine.py +165 -0
- spectro_kernel-0.3.0/src/spectro_kernel/algorithms/wavelength_calibration/arc_geometry.py +350 -0
- spectro_kernel-0.3.0/src/spectro_kernel/algorithms/wavelength_calibration/lamp_atlas.py +170 -0
- spectro_kernel-0.3.0/src/spectro_kernel/algorithms/wavelength_calibration/match_lamp.py +488 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/version.py +1 -1
- spectro_kernel-0.3.0/tests/unit/test_dark_flat_combine.py +188 -0
- spectro_kernel-0.3.0/tests/unit/test_detect_trace.py +104 -0
- spectro_kernel-0.3.0/tests/unit/test_easyspec_apply_staging.py +156 -0
- spectro_kernel-0.3.0/tests/unit/test_extract_boxcar.py +171 -0
- spectro_kernel-0.3.0/tests/unit/test_extract_optimal.py +178 -0
- spectro_kernel-0.3.0/tests/unit/test_lamp_atlas_match.py +219 -0
- spectro_kernel-0.3.0/tests/unit/test_measure_arc_geometry.py +152 -0
- spectro_kernel-0.3.0/tests/unit/test_response_from_standard.py +129 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/.gitignore +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/LICENSE +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/README.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/concepts/algorithms.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/concepts/architecture.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/concepts/data-types.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/concepts/pipelines.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/contributing.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/cookbook/bess-dashboard.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/cookbook/multi-star-viewer.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/cookbook/web-playground.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/gen_catalogue.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/getting-started.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_ew_timeseries.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_halpha_phase_stack.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_halpha_timeseries.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_overlay.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_pc1_vs_phase.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_pca_2d.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_pca_3d.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_periodogram.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_phase_folded.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_rv_timeseries.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_similarity.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_cyg_similarity_date.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_dra_halpha_phase_stack.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_dra_pc1_vs_phase.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_dra_pca_2d.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_dra_pca_3d.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_dra_periodogram.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_dra_phase_folded.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_dra_rv_timeseries.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_dra_similarity_date.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/images/notebooks/alpha_dra_similarity_phase.png +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/index.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/notebooks/alpha-cyg-time-series.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/notebooks/alpha-dra-binary-period.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/notebooks/aurora-line-monitor.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/notebooks/be-star-variability.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/notebooks/claude-mcp-end-to-end.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/notebooks/exoplanet-transit-rv.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/notebooks/full-reduction-walkthrough.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/notebooks/native-vs-easyspec-showdown.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/notebooks/your-first-sb2.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/reference.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/tutorials/add-an-algorithm.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/tutorials/analyse-a-spectrum.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/tutorials/discover-the-catalogue.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/tutorials/first-spectrum.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/usage/cli.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/usage/library.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/usage/mcp.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/docs/why.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/playground/README.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/pyproject.toml +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/adapters/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/adapters/easyspec.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/_common.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/advanced/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/advanced/aperture_photometry.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/advanced/disentangle_sb2.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/catalogs/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/catalogs/gaia.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/catalogs/simbad.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/catalogs/vizier.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/continuum/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/continuum/compare_normalisations.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/continuum/normalize_edges.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/continuum/normalize_max.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/continuum/normalize_percentile.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/continuum/normalize_polynomial.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/continuum/normalize_region.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/continuum/subtract_continuum.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/corrections/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/corrections/air_vacuum.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/corrections/barycentric.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/corrections/doppler_shift.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/corrections/extinction_correct_easyspec.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/corrections/fit_telluric_scaling.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/corrections/flux_calibrate_easyspec.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/corrections/remove_telluric.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/corrections/synth_telluric.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/embedding/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/embedding/embed_band_power.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/embedding/embed_continuum_subtracted.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/embedding/embed_lick_indices.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/embedding/embed_log_lambda.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/embedding/embed_pretrained.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/embedding/embed_remote.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/embedding/embed_spectrum.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/embedding/embed_wavelets.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/exports/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/exports/export_csv.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/exports/export_fits.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/exports/export_fits_bess.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/exports/export_hdf5.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/exports/export_votable.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/extraction/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/extraction/easyspec_extract.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/extraction/sky_lateral_bands.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/io/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/io/read_ascii.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/io/read_echelle.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/io/read_fits.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/io/read_sdss.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/io/read_votable.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/lines/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/lines/_profiles.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/lines/catalogs.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/lines/compare_line_fits.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/lines/detect.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/lines/equivalent_width.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/lines/fit_gaussian.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/lines/fit_lorentzian.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/lines/fit_voigt.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/quality/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/quality/compare_snr_methods.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/quality/snr_der.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/quality/snr_edge.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/quality/snr_linear_fit.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/_easyspec_helpers.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/bias_combine.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/clip_cosmic_rays.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/dark_subtract.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/denoise_2d.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/extract_spectrum_sum.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/flat_normalize.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/geometry.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/subtract_sky_2d.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/rv/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/rv/cross_correlate.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/rv/fit_keplerian_orbit.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/rv/measure.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/rv/precision_bouchy.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/smoothing/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/smoothing/compare_smoothings.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/smoothing/smooth_gaussian.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/smoothing/smooth_savgol.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/stacking/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/stacking/merge_echelle_orders.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/stacking/stack.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/timeseries/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/timeseries/lomb_scargle.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/timeseries/phase_fold.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/transforms/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/transforms/clip_sigma.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/transforms/combine_arithmetic.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/transforms/extract_region.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/transforms/mask_range.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/transforms/resample.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/transforms/resample_flux_conserving.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/viz/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/viz/plot_3d_surface.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/viz/plot_animation.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/viz/plot_dynamic_spectrum.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/viz/plot_plotly.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/wavelength_calibration/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/wavelength_calibration/easyspec_wavelength.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/wavelength_calibration/in_situ.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/algorithms/wavelength_calibration/solar.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/base.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/cli.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/embeddings.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/errors.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/io/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/io/ascii.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/io/fits.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/io/votable.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/pipeline.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/presets/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/presets/catalog/analysis/balmer_quick.yaml +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/presets/catalog/analysis/embed_quick.yaml +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/presets/catalog/analysis/quality_report.yaml +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/presets/catalog/analysis/rv_quick.yaml +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/presets/catalog/analysis/snr_check.yaml +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/presets/catalog/analysis/time_series_overview.yaml +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/presets/catalog/reduction/full_reduction_easyspec.yaml +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/presets/loader.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/py.typed +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/registry.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/types/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/types/catalog.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/types/context.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/types/enums.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/types/history.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/types/image.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/types/line.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/types/spectrum.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_kernel/types/timeseries.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_mcp/__init__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_mcp/__main__.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_mcp/auth.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_mcp/auto_tools.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_mcp/observability.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_mcp/py.typed +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_mcp/server.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_mcp/session.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/src/spectro_mcp/url_safety.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/conftest.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/reference/conftest.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/reference/data/.gitkeep +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/reference/data/README.md +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/reference/data/sun/sun_reference_stis_002.fits +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/reference/data/vega/alpha_lyr_stis_011.fits +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/reference/test_known_answers.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/reference/test_sun.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/reference/test_vega.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_base.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_cli.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_combine.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_combine_arithmetic.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_continuum.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_corrections.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_denoise_2d.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_easyspec_wrappers.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_embedding.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_embedding_pretrained.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_embedding_remote_lick.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_embedding_tier1.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_export_fits_bess.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_geometry_corrections.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_io.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_line_profiles.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_lines.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_mcp.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_mcp_production.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_mcp_security.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_misc_algorithms.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_no_circular_imports.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_normalize_to_region.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_pipeline.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_quality.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_read_sdss.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_registry.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_sky_lateral_bands.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_smoothing.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_timeseries.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_transforms.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_types.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_viz.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_wavelength_calibration_in_situ.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/tests/unit/test_wavelength_calibration_solar.py +0 -0
- {spectro_kernel-0.2.0 → spectro_kernel-0.3.0}/website/README.md +0 -0
|
@@ -6,6 +6,194 @@ Until `1.0.0` the public API may change between minor versions.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] — 2026-06-09
|
|
10
|
+
|
|
11
|
+
A focused expansion driven by the LineMill stellar-spectroscopy work
|
|
12
|
+
(Alpy 600 / Star'Ex). **Seven new algorithms + one package data
|
|
13
|
+
module**, all genuinely literature-based, none duplicating existing
|
|
14
|
+
bricks. The minor-version bump signals a meaningful catalogue
|
|
15
|
+
expansion (105 algorithms, up from 98), not an API break — every
|
|
16
|
+
existing brick remains untouched.
|
|
17
|
+
|
|
18
|
+
### Added — automatic wavelength calibration
|
|
19
|
+
|
|
20
|
+
- **`lamp_atlas`** (package data, not an algorithm) — curated
|
|
21
|
+
persistent-line lists for `Ne`, `Ar`, `NeAr` and `ThAr` arc lamps,
|
|
22
|
+
sourced from the **NIST Atomic Spectra Database** (Kramida, Ralchenko,
|
|
23
|
+
Reader, NIST ASD Team). Embedded — no network at runtime. Sibling of
|
|
24
|
+
the existing ``lines.catalogs`` module. API: ``get_atlas(lamp,
|
|
25
|
+
wave_min=None, wave_max=None) -> ndarray`` of ``(wavelength_angstrom,
|
|
26
|
+
relative_intensity)``.
|
|
27
|
+
- **`match_lamp_lines`** — automatic identification of arc-line peaks
|
|
28
|
+
against the bundled atlas. IRAF ``autoidentify`` heritage (Tody 1986
|
|
29
|
+
SPIE 627 733 ; Tody 1993 ASP Conf 52 173), with the robust ThAr-style
|
|
30
|
+
σ-clipped polynomial refinement (Murphy et al. 2007 MNRAS 378 221).
|
|
31
|
+
Writes ``ctx.extras["lamp_identifications"]`` in the exact shape
|
|
32
|
+
``wavelength_calibrate_polynomial`` consumes — the old
|
|
33
|
+
hand-identified ``(pixel, λ)`` pairs become optional. Linear seed by
|
|
34
|
+
global anchor-pair brute-force (the local-greedy method's known
|
|
35
|
+
failure mode on Alpy 600 plein visible), then σ-clipped polyfit.
|
|
36
|
+
|
|
37
|
+
### Added — native master-frame combiners
|
|
38
|
+
|
|
39
|
+
- **`dark_combine`** and **`flat_combine`** — pure-numpy companions to
|
|
40
|
+
the existing native ``bias_combine``. Required by cloud workers that
|
|
41
|
+
source frames from S3 / R2 / memory queues, where the existing
|
|
42
|
+
``*_easyspec`` variants (file round-trip) don't fit. Median (robust
|
|
43
|
+
default) or σ-clipped mean. ``dark_combine`` enforces uniform EXPTIME
|
|
44
|
+
by default (so the downstream ``dark_subtract(scale_by_exptime=True)``
|
|
45
|
+
has a well-defined factor). ``flat_combine`` produces the **raw**
|
|
46
|
+
master flat — the existing ``flat_normalize`` keeps the
|
|
47
|
+
normalisation responsibility. Howell 2006 ch. 4.
|
|
48
|
+
|
|
49
|
+
### Added — flux calibration
|
|
50
|
+
|
|
51
|
+
- **`response_from_standard`** — derive the instrumental response
|
|
52
|
+
curve from a standard-star observation. Observed/catalogue ratio,
|
|
53
|
+
Balmer + telluric masking, low-frequency spline (or polynomial) fit.
|
|
54
|
+
Output goes to ``ctx.extras["reference_spectrum"]`` so the existing
|
|
55
|
+
``combine_spectra_arithmetic(operation="div")`` consumes it as the
|
|
56
|
+
"second spectrum" — same idiom as ``remove_telluric_division``.
|
|
57
|
+
References: Oke 1990 AJ 99 1621 ; Bessell 1999 PASP 111 1426 ; Hamuy
|
|
58
|
+
1992/1994 ; CALSPEC Bohlin et al. 2014 PASP 126 711 ; Hayes & Latham
|
|
59
|
+
1975 ApJ 197 593.
|
|
60
|
+
|
|
61
|
+
### Added — optimal extraction (Horne 1986)
|
|
62
|
+
|
|
63
|
+
- **`extract_spectrum_optimal`** — full Horne 1986 estimator: empirical
|
|
64
|
+
spatial profile (smoothed à la Marsh 1989), per-pixel variance model
|
|
65
|
+
``V = signal/gain + RON²``, inverse-variance weighting, iterative
|
|
66
|
+
σ-clip cosmic-ray rejection, propagated 1-σ ``Spectrum1D.uncertainty``.
|
|
67
|
+
|
|
68
|
+
**This is NOT a replacement of ``extract_spectrum_boxcar``**. The
|
|
69
|
+
boxcar (with ``extraction_weights="gaussian"``) is a *profile-only*
|
|
70
|
+
estimator without variance model, cosmic rejection or uncertainty
|
|
71
|
+
propagation — it remains the right tool for high-SNR sources, wide
|
|
72
|
+
apertures, extended emission (aurora, nebulae). Pick
|
|
73
|
+
``extract_spectrum_optimal`` when SNR matters most (faint, RN-limited
|
|
74
|
+
targets), when residual cosmics survive ``clip_cosmic_rays``, or
|
|
75
|
+
when downstream code needs ``Spectrum1D.uncertainty``. Cross-refs
|
|
76
|
+
added to both bricks' ``long_description`` to make the choice
|
|
77
|
+
explicit. References: Horne 1986 PASP 98 609 ; Marsh 1989 PASP 101
|
|
78
|
+
1032 ; Tody 1986 IRAF apall.
|
|
79
|
+
|
|
80
|
+
### Added — automation bricks
|
|
81
|
+
|
|
82
|
+
- **`detect_trace`** — pure-numpy trace detection that **exposes** the
|
|
83
|
+
trace as a product on ``ctx.extras["trace"] = {"center_row",
|
|
84
|
+
"poly_coef", "fwhm_px", "snr", "rms_px"}``. Today every
|
|
85
|
+
``extract_spectrum_*`` recomputes the trace internally and throws the
|
|
86
|
+
result away ; downstream bricks (``subtract_sky_2d``,
|
|
87
|
+
``correct_smile_polynomial`` reference_row, ``correct_slant_affine``
|
|
88
|
+
pivot_row) take it as a hand-tuned parameter. ``detect_trace`` makes
|
|
89
|
+
the automatic-reduction preset feasible. Tody 1986 SPIE 627 733
|
|
90
|
+
(IRAF ``apall`` / ``aptrace``).
|
|
91
|
+
- **`measure_arc_geometry`** — measure ``smile_radius`` and
|
|
92
|
+
``slant_deg`` directly from an arc-lamp 2-D pose. The two geometry
|
|
93
|
+
correctors (``correct_smile_polynomial``, ``correct_slant_affine``)
|
|
94
|
+
consume them but until now had no measurement step ; they were
|
|
95
|
+
hand-tuned. The brick tracks each detected arc line row-by-row
|
|
96
|
+
across the slit, then fits one common Schroeder parabola + linear
|
|
97
|
+
tilt slope. Pairs with ``detect_trace`` (uses the science trace as
|
|
98
|
+
the reference row ``y₀``). IRAF ``identify/reidentify/fitcoords``
|
|
99
|
+
heritage (Tody 1986) ; PypeIt wavelength/tilts module (Prochaska et
|
|
100
|
+
al. 2020 JOSS 5 2308) ; Schroeder 2000 ch. 15 §15.3 for the smile
|
|
101
|
+
parametrisation.
|
|
102
|
+
|
|
103
|
+
### Improved — cross-references in extraction docstrings
|
|
104
|
+
|
|
105
|
+
The ``long_description`` of both ``extract_spectrum_boxcar`` and
|
|
106
|
+
``extract_spectrum_optimal`` now point at each other, so users can
|
|
107
|
+
choose between them with the precise scientific criterion (SNR
|
|
108
|
+
regime, need for uncertainty propagation, cosmic-ray residuals)
|
|
109
|
+
spelled out in the catalogue.
|
|
110
|
+
|
|
111
|
+
### Numbers
|
|
112
|
+
|
|
113
|
+
**105 algorithms** registered after this release (was 98). **294
|
|
114
|
+
tests** pass (35 new), 2 skipped. ``ruff`` clean, ``mkdocs build
|
|
115
|
+
--strict`` clean, ``twine check --strict`` clean.
|
|
116
|
+
|
|
117
|
+
## [0.2.1] — 2026-06-09
|
|
118
|
+
|
|
119
|
+
End-to-end aurora-parity patch. Diagnosed and validated bit-exactly on
|
|
120
|
+
real M42 long-slit data: with these two changes a downstream aurora
|
|
121
|
+
reduction pipeline running entirely on spectro-kernel matches its
|
|
122
|
+
science-core baseline at **Pearson 0.99996** (median |Δ| 0.82 %),
|
|
123
|
+
where v0.2.0 alone scored 0.107 (output silently shifted by +32768 ADU
|
|
124
|
+
on every step).
|
|
125
|
+
|
|
126
|
+
### Fixed — uint16 ``BZERO``/``BSCALE`` leak in the easyspec staging helpers (BUG)
|
|
127
|
+
|
|
128
|
+
The shared helper ``stage_target`` in
|
|
129
|
+
[`reduction/_easyspec_apply.py`](src/spectro_kernel/algorithms/reduction/_easyspec_apply.py)
|
|
130
|
+
wrote ``ctx.image.data`` as float64 on a new ``PrimaryHDU`` and copied
|
|
131
|
+
the source header verbatim. When the source frame originated from a
|
|
132
|
+
**uint16 raw file** (``BITPIX=16``, ``BZERO=32768``, ``BSCALE=1`` —
|
|
133
|
+
astropy's convention), the scaling keywords landed on the float64 HDU
|
|
134
|
+
and astropy then re-applied them on read-back, **silently adding 32768
|
|
135
|
+
ADU to every pixel**. The companion ``build_corrected_imageframe``
|
|
136
|
+
re-attached the same scaling block to the corrected ``ImageFrame``, so
|
|
137
|
+
the next pipeline step that re-staged it inherited the leak.
|
|
138
|
+
|
|
139
|
+
The fix strips a small set of reserved / scaling keywords —
|
|
140
|
+
``SIMPLE, BITPIX, EXTEND, BZERO, BSCALE, BLANK, END, NAXIS*, PCOUNT,
|
|
141
|
+
GCOUNT, XTENSION`` — before they reach either the staged HDU or the
|
|
142
|
+
header re-attached downstream. astropy re-derives the structural keys
|
|
143
|
+
from the data itself; the non-structural metadata (``EXPTIME``,
|
|
144
|
+
``OBSERVER``, etc.) survives.
|
|
145
|
+
|
|
146
|
+
Affects all four easyspec apply wrappers in one shot:
|
|
147
|
+
``subtract_bias_easyspec``, ``subtract_dark_easyspec``,
|
|
148
|
+
``flat_normalize_easyspec``, ``extract_spectrum_easyspec``. Native-numpy
|
|
149
|
+
algorithms (``dark_subtract``, ``flat_normalize``, ``subtract_sky_2d``,
|
|
150
|
+
the v0.2.0 geometry / denoising suite) were never affected — they
|
|
151
|
+
operate directly on ``ctx.image.data`` without round-tripping through a
|
|
152
|
+
FITS file.
|
|
153
|
+
|
|
154
|
+
5 regression tests added in
|
|
155
|
+
[`tests/unit/test_easyspec_apply_staging.py`](tests/unit/test_easyspec_apply_staging.py).
|
|
156
|
+
|
|
157
|
+
### Added — ``extract_spectrum_boxcar`` (literature aperture extraction)
|
|
158
|
+
|
|
159
|
+
New algorithm in
|
|
160
|
+
[`algorithms/extraction/boxcar.py`](src/spectro_kernel/algorithms/extraction/boxcar.py).
|
|
161
|
+
Same easyspec tracing step as ``extract_spectrum_easyspec``, but
|
|
162
|
+
extraction is a **pure-numpy aperture sum** (clamped to the detector
|
|
163
|
+
bounds on every column).
|
|
164
|
+
|
|
165
|
+
Why a new brick: ``extract_spectrum_easyspec`` delegates the extraction
|
|
166
|
+
to ``easyspec.extracting``, whose per-column internal sky window
|
|
167
|
+
crashes with ``ValueError: broadcast (W,) vs (S,)`` when the aperture
|
|
168
|
+
half-width is wide (≥ a few hundred px) and the trace sits near a
|
|
169
|
+
detector edge — the typical aurora / extended-source geometry. The new
|
|
170
|
+
brick keeps easyspec's argmax tracing (the trace itself is fine) but
|
|
171
|
+
performs the aperture sum in numpy, with ``max(0, …)`` / ``min(rows,
|
|
172
|
+
…)`` clipping on every column so it is **edge-safe by construction**.
|
|
173
|
+
|
|
174
|
+
Supports two weightings:
|
|
175
|
+
|
|
176
|
+
- ``"tophat"`` — unweighted box sum (standard IRAF ``apall``).
|
|
177
|
+
- ``"gaussian"`` — profile-weighted Horne 1986 estimator (the average
|
|
178
|
+
column profile is fit with ``astropy.modeling`` to derive the
|
|
179
|
+
weights).
|
|
180
|
+
|
|
181
|
+
``shift_y_pixels = 0`` disables the background subtraction (pure
|
|
182
|
+
column sum); a positive value samples sky from two symmetric flanking
|
|
183
|
+
windows. References: **Horne 1986, PASP 98, 609** ; **Tody 1986, Proc.
|
|
184
|
+
SPIE 627, 733** (IRAF ``apall`` / ``aptrace``). easyspec is imported
|
|
185
|
+
lazily inside ``run()`` (v0.1.5+ pattern).
|
|
186
|
+
|
|
187
|
+
6 tests in
|
|
188
|
+
[`tests/unit/test_extract_boxcar.py`](tests/unit/test_extract_boxcar.py),
|
|
189
|
+
including the wide-aperture edge case that motivated the brick.
|
|
190
|
+
|
|
191
|
+
### Numbers
|
|
192
|
+
|
|
193
|
+
**98 algorithms** registered (was 97). 254 tests pass (11 new), 3
|
|
194
|
+
skipped. ``ruff check`` clean, ``mkdocs build --strict`` clean,
|
|
195
|
+
``twine check --strict`` clean.
|
|
196
|
+
|
|
9
197
|
## [0.2.0] — 2026-06-09
|
|
10
198
|
|
|
11
199
|
A library expansion focused on long-slit reduction (aurora, stellar,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spectro-kernel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A shared catalogue of astronomical spectroscopy algorithms, composable into reproducible pipelines, usable as a Python library, a CLI, or an MCP server.
|
|
5
5
|
Project-URL: Homepage, https://github.com/matthieulel/spectro-kernel
|
|
6
6
|
Project-URL: Documentation, https://matthieulel.github.io/spectro-kernel/
|
|
@@ -63,8 +63,11 @@ steps:
|
|
|
63
63
|
band_below_hi: 160
|
|
64
64
|
- algorithm: subtract_sky_2d
|
|
65
65
|
params: {trace_row: 120, trace_half_width: 6, sky_offset: 4, sky_half_width: 10}
|
|
66
|
-
-
|
|
67
|
-
|
|
66
|
+
# extract_spectrum_boxcar is the edge-safe boxcar for extended sources
|
|
67
|
+
# (aurora, nebulae). For a stellar point source, extract_spectrum_sum
|
|
68
|
+
# or extract_spectrum_easyspec are also valid choices.
|
|
69
|
+
- algorithm: extract_spectrum_boxcar
|
|
70
|
+
params: {trace_method: "argmax", trace_half_width: 6, extraction_weights: "tophat", shift_y_pixels: 0}
|
|
68
71
|
|
|
69
72
|
# ── 4. In-situ wavelength calibration from the simultaneously-acquired
|
|
70
73
|
# sky reference (no arc lamp needed).
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Algorithm: build the instrumental-response curve from a standard star.
|
|
2
|
+
|
|
3
|
+
Closes the flux-calibration loop in the native (non-easyspec) chain.
|
|
4
|
+
The recipe is the textbook standard-star method (Oke 1990, Bessell
|
|
5
|
+
1999, Hamuy 1992/1994) :
|
|
6
|
+
|
|
7
|
+
1. Take the observed standard ``ctx.spectrum`` — already wavelength-
|
|
8
|
+
calibrated, ideally already extinction-corrected.
|
|
9
|
+
2. Take the catalogue spectrum from ``ctx.extras["catalog_spectrum"]``
|
|
10
|
+
(e.g. a CALSPEC / Oke star, loaded via the existing ``read_*``
|
|
11
|
+
readers or fetched manually upstream).
|
|
12
|
+
3. Resample the catalogue to the observed wavelength grid and compute
|
|
13
|
+
the ratio ``observed / catalog``. This is the **raw** sensitivity
|
|
14
|
+
curve (instrument + telescope + atmosphere combined).
|
|
15
|
+
4. Mask Balmer + telluric windows (configurable via
|
|
16
|
+
``exclude_regions``). These features are intrinsic to the source or
|
|
17
|
+
the atmosphere — keeping them in the fit would imprint their wings
|
|
18
|
+
into the response.
|
|
19
|
+
5. Fit a low-frequency smoother (spline or polynomial) to the masked
|
|
20
|
+
ratio. The smoother captures the broad sensitivity envelope without
|
|
21
|
+
chasing line residuals.
|
|
22
|
+
6. Optionally apply an extinction correction first, when the user
|
|
23
|
+
supplies an ``airmass``. The default is to assume the input has
|
|
24
|
+
already been extinction-corrected (the spec's recommendation; pair
|
|
25
|
+
with ``extinction_correct_easyspec`` upstream).
|
|
26
|
+
|
|
27
|
+
The resulting response is written to
|
|
28
|
+
``ctx.extras["reference_spectrum"]`` so the existing
|
|
29
|
+
``combine_spectra_arithmetic(operation="div")`` brick consumes it
|
|
30
|
+
directly as the "second spectrum" — same idiom as
|
|
31
|
+
``remove_telluric_division``.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
import numpy as np
|
|
39
|
+
from scipy.interpolate import UnivariateSpline
|
|
40
|
+
|
|
41
|
+
from ...base import AlgorithmOutput, BaseAlgorithm
|
|
42
|
+
from ...errors import InvalidParameterError
|
|
43
|
+
from ...registry import register_algorithm
|
|
44
|
+
from ...types import AlgorithmCategory, Spectrum1D, WorkContext
|
|
45
|
+
|
|
46
|
+
# Default exclude windows: Balmer lines + main visible telluric bands.
|
|
47
|
+
# These are conservative widths suitable for amateur resolutions
|
|
48
|
+
# (Alpy 600 ~ 11 Å, Star'Ex LR ~ 4 Å). User can override entirely.
|
|
49
|
+
_DEFAULT_EXCLUDE_REGIONS_ANGSTROM: tuple[tuple[float, float], ...] = (
|
|
50
|
+
(3960.0, 3985.0), # Ca II H + Hε blend
|
|
51
|
+
(3925.0, 3945.0), # Ca II K
|
|
52
|
+
(4090.0, 4115.0), # Hδ
|
|
53
|
+
(4330.0, 4355.0), # Hγ
|
|
54
|
+
(4850.0, 4875.0), # Hβ
|
|
55
|
+
(5870.0, 5900.0), # Na D
|
|
56
|
+
(6540.0, 6585.0), # Hα (+ [N II])
|
|
57
|
+
(6860.0, 6960.0), # O₂ B-band telluric
|
|
58
|
+
(7150.0, 7350.0), # H₂O telluric
|
|
59
|
+
(7580.0, 7700.0), # O₂ A-band telluric
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
_OKE = "Oke 1990, AJ 99, 1621 — flux calibration with secondary standards."
|
|
63
|
+
_BESSELL = "Bessell 1999, PASP 111, 1426 — UBVRI flux standards review."
|
|
64
|
+
_HAMUY = (
|
|
65
|
+
"Hamuy et al. 1992 PASP 104, 533 + 1994 PASP 106, 566 — Southern "
|
|
66
|
+
"spectrophotometric standards."
|
|
67
|
+
)
|
|
68
|
+
_BOHLIN = "Bohlin et al. 2014, PASP 126, 711 — CALSPEC HST standard stars."
|
|
69
|
+
_HAYES_LATHAM = "Hayes & Latham 1975, ApJ 197, 593 — mean atmospheric extinction."
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _build_mask(
|
|
73
|
+
wavelength: np.ndarray, exclude_regions: list[tuple[float, float]],
|
|
74
|
+
) -> np.ndarray:
|
|
75
|
+
"""Boolean keep-mask : True where the sample is NOT in any exclude window."""
|
|
76
|
+
keep = np.ones(wavelength.shape, dtype=bool)
|
|
77
|
+
for lo, hi in exclude_regions:
|
|
78
|
+
keep &= ~((wavelength >= float(lo)) & (wavelength <= float(hi)))
|
|
79
|
+
return keep
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _sigma_clip_residuals(
|
|
83
|
+
x: np.ndarray, y: np.ndarray, fit_values: np.ndarray, sigma: float,
|
|
84
|
+
) -> np.ndarray:
|
|
85
|
+
"""Keep samples whose residual lies within ±σ of the fit."""
|
|
86
|
+
res = y - fit_values
|
|
87
|
+
rms = float(np.sqrt(np.nanmean(res ** 2)))
|
|
88
|
+
if rms == 0.0:
|
|
89
|
+
return np.ones(x.shape, dtype=bool)
|
|
90
|
+
return np.abs(res) <= sigma * rms
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@register_algorithm(
|
|
94
|
+
"response_from_standard",
|
|
95
|
+
category=AlgorithmCategory.FLUX_CALIBRATION,
|
|
96
|
+
version="1.0.0",
|
|
97
|
+
)
|
|
98
|
+
class ResponseFromStandard(BaseAlgorithm):
|
|
99
|
+
"""Derive the instrumental response curve from a standard-star observation.
|
|
100
|
+
|
|
101
|
+
Input contract :
|
|
102
|
+
|
|
103
|
+
- ``ctx.spectrum`` — the observed standard, wavelength-calibrated in
|
|
104
|
+
Ångström. Recommended : already extinction-corrected (via
|
|
105
|
+
``extinction_correct_easyspec`` or equivalent).
|
|
106
|
+
- ``ctx.extras["catalog_spectrum"]`` — the catalogue spectrum of
|
|
107
|
+
the same standard (CALSPEC / Oke / MILES …), as a
|
|
108
|
+
:class:`Spectrum1D`. The wavelength grid does not need to match
|
|
109
|
+
the observed grid ; the algorithm resamples by linear
|
|
110
|
+
interpolation.
|
|
111
|
+
|
|
112
|
+
Output : ``ctx.extras["reference_spectrum"]`` — the smoothed
|
|
113
|
+
sensitivity curve on the observed wavelength grid. Feeds directly
|
|
114
|
+
into ``combine_spectra_arithmetic(operation="div")``.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
backend = "scipy"
|
|
118
|
+
references = [_OKE, _BESSELL, _HAMUY, _BOHLIN, _HAYES_LATHAM]
|
|
119
|
+
long_description = (
|
|
120
|
+
"Computes observed / catalog (after resampling the catalogue to "
|
|
121
|
+
"the observed grid), masks Balmer + telluric windows, σ-clips, "
|
|
122
|
+
"fits a low-frequency spline (default) or polynomial. The "
|
|
123
|
+
"spline knot count controls the smoothness — too many knots "
|
|
124
|
+
"carve real features into the response, too few miss instrument "
|
|
125
|
+
"structure. Sensible defaults : 20 knots for the full visible. "
|
|
126
|
+
"Catalogue spectrum lives in ctx.extras['catalog_spectrum'], "
|
|
127
|
+
"consistent with the second-spectrum idiom used by "
|
|
128
|
+
"remove_telluric_division and combine_spectra_arithmetic."
|
|
129
|
+
)
|
|
130
|
+
default_params: dict[str, Any] = {
|
|
131
|
+
"fit": "spline",
|
|
132
|
+
"spline_knots": 20,
|
|
133
|
+
"poly_order": 5,
|
|
134
|
+
"exclude_regions": None, # None = use _DEFAULT_EXCLUDE_REGIONS_ANGSTROM
|
|
135
|
+
"sigma_clip": 3.0,
|
|
136
|
+
"max_iter": 3,
|
|
137
|
+
"catalog_key": "catalog_spectrum",
|
|
138
|
+
"output_key": "reference_spectrum",
|
|
139
|
+
}
|
|
140
|
+
required_params: list[str] = []
|
|
141
|
+
param_descriptions = {
|
|
142
|
+
"fit": "'spline' (default low-frequency UnivariateSpline) or 'polynomial'.",
|
|
143
|
+
"spline_knots": (
|
|
144
|
+
"Number of interior knots for the spline fit. More knots ⇒ "
|
|
145
|
+
"tighter fit (risks eating real features) ; fewer ⇒ smoother."
|
|
146
|
+
),
|
|
147
|
+
"poly_order": "Polynomial order when fit='polynomial'.",
|
|
148
|
+
"exclude_regions": (
|
|
149
|
+
"List of (wave_lo, wave_hi) windows (Å) to exclude from the "
|
|
150
|
+
"fit. None ⇒ a default set covering Balmer + visible "
|
|
151
|
+
"telluric bands."
|
|
152
|
+
),
|
|
153
|
+
"sigma_clip": "σ threshold for residual clipping during the fit (≥ 1).",
|
|
154
|
+
"max_iter": "Maximum σ-clip iterations.",
|
|
155
|
+
"catalog_key": "ctx.extras key holding the catalogue Spectrum1D.",
|
|
156
|
+
"output_key": "ctx.extras key receiving the fitted response curve.",
|
|
157
|
+
}
|
|
158
|
+
input_requirements = ["spectrum"]
|
|
159
|
+
output_produces = ["extras.reference_spectrum", "metrics.response_rms"]
|
|
160
|
+
|
|
161
|
+
def run(self, ctx: WorkContext, params: dict[str, Any]) -> AlgorithmOutput:
|
|
162
|
+
if ctx.spectrum is None:
|
|
163
|
+
return AlgorithmOutput.fail("No ctx.spectrum (the observed standard).")
|
|
164
|
+
catalog_key = str(params["catalog_key"])
|
|
165
|
+
catalog = ctx.extras.get(catalog_key)
|
|
166
|
+
if not isinstance(catalog, Spectrum1D):
|
|
167
|
+
return AlgorithmOutput.fail(
|
|
168
|
+
f"Catalogue spectrum missing at ctx.extras[{catalog_key!r}]."
|
|
169
|
+
)
|
|
170
|
+
fit = str(params["fit"])
|
|
171
|
+
if fit not in ("spline", "polynomial"):
|
|
172
|
+
raise InvalidParameterError("fit must be 'spline' or 'polynomial'.")
|
|
173
|
+
|
|
174
|
+
observed = ctx.spectrum
|
|
175
|
+
wave = np.asarray(observed.wavelength, dtype=np.float64)
|
|
176
|
+
obs_flux = np.asarray(observed.flux, dtype=np.float64)
|
|
177
|
+
if wave.size < 16:
|
|
178
|
+
return AlgorithmOutput.fail(
|
|
179
|
+
"Observed spectrum too short to build a response."
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Resample catalogue to the observed grid (NaN outside its domain).
|
|
183
|
+
cat_flux = np.interp(
|
|
184
|
+
wave,
|
|
185
|
+
np.asarray(catalog.wavelength, dtype=np.float64),
|
|
186
|
+
np.asarray(catalog.flux, dtype=np.float64),
|
|
187
|
+
left=np.nan, right=np.nan,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Raw response = observed / catalog. NaN/zero-safe.
|
|
191
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
192
|
+
raw_response = obs_flux / cat_flux
|
|
193
|
+
valid = np.isfinite(raw_response) & (cat_flux > 0.0)
|
|
194
|
+
|
|
195
|
+
# Apply user-supplied exclude regions on top of validity mask.
|
|
196
|
+
excludes = params.get("exclude_regions")
|
|
197
|
+
if excludes is None:
|
|
198
|
+
excludes = list(_DEFAULT_EXCLUDE_REGIONS_ANGSTROM)
|
|
199
|
+
excludes = [(float(lo), float(hi)) for lo, hi in excludes]
|
|
200
|
+
keep = valid & _build_mask(wave, excludes)
|
|
201
|
+
if int(keep.sum()) < max(int(params["spline_knots"]) + 4, 8):
|
|
202
|
+
return AlgorithmOutput.fail(
|
|
203
|
+
f"Only {int(keep.sum())} valid samples after masking — "
|
|
204
|
+
"loosen exclude_regions or lower spline_knots."
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
sigma_clip = float(params["sigma_clip"])
|
|
208
|
+
max_iter = int(params["max_iter"])
|
|
209
|
+
|
|
210
|
+
if fit == "spline":
|
|
211
|
+
knots = max(4, int(params["spline_knots"]))
|
|
212
|
+
for _ in range(max_iter):
|
|
213
|
+
x_fit = wave[keep]
|
|
214
|
+
y_fit = raw_response[keep]
|
|
215
|
+
try:
|
|
216
|
+
spline = UnivariateSpline(
|
|
217
|
+
x_fit, y_fit, k=3, s=len(x_fit),
|
|
218
|
+
)
|
|
219
|
+
# Choose knot count by clamping the smoothing factor: a
|
|
220
|
+
# smaller s gives more knots. We tune s so the spline
|
|
221
|
+
# uses roughly `knots` internal knots.
|
|
222
|
+
span = float(np.var(y_fit)) * len(x_fit)
|
|
223
|
+
spline.set_smoothing_factor(max(span / max(knots, 1), 1e-9))
|
|
224
|
+
except Exception as exc: # noqa: BLE001
|
|
225
|
+
return AlgorithmOutput.fail(
|
|
226
|
+
f"Spline fit failed: {type(exc).__name__}: {exc}"
|
|
227
|
+
)
|
|
228
|
+
fit_values = spline(x_fit)
|
|
229
|
+
new_keep_in = _sigma_clip_residuals(
|
|
230
|
+
x_fit, y_fit, fit_values, sigma_clip,
|
|
231
|
+
)
|
|
232
|
+
if int(new_keep_in.sum()) <= knots + 4:
|
|
233
|
+
break
|
|
234
|
+
# Update outer mask in-place (mask is reduced, never grown).
|
|
235
|
+
inner = np.where(keep)[0]
|
|
236
|
+
keep = np.zeros_like(keep)
|
|
237
|
+
keep[inner[new_keep_in]] = True
|
|
238
|
+
response = spline(wave)
|
|
239
|
+
else: # polynomial
|
|
240
|
+
order = int(params["poly_order"])
|
|
241
|
+
for _ in range(max_iter):
|
|
242
|
+
x_fit = wave[keep]
|
|
243
|
+
y_fit = raw_response[keep]
|
|
244
|
+
coef = np.polyfit(x_fit, y_fit, order)
|
|
245
|
+
fit_values = np.polyval(coef, x_fit)
|
|
246
|
+
new_keep_in = _sigma_clip_residuals(
|
|
247
|
+
x_fit, y_fit, fit_values, sigma_clip,
|
|
248
|
+
)
|
|
249
|
+
if int(new_keep_in.sum()) <= order + 1:
|
|
250
|
+
break
|
|
251
|
+
inner = np.where(keep)[0]
|
|
252
|
+
keep = np.zeros_like(keep)
|
|
253
|
+
keep[inner[new_keep_in]] = True
|
|
254
|
+
response = np.polyval(coef, wave)
|
|
255
|
+
|
|
256
|
+
# Build the response Spectrum1D, marked as such for downstream.
|
|
257
|
+
reference = Spectrum1D(
|
|
258
|
+
wavelength=wave,
|
|
259
|
+
flux=response.astype(np.float64),
|
|
260
|
+
flux_unit="response",
|
|
261
|
+
wavelength_unit=observed.wavelength_unit,
|
|
262
|
+
meta={
|
|
263
|
+
"kind": "instrumental_response",
|
|
264
|
+
"derived_from": "response_from_standard",
|
|
265
|
+
"fit": fit,
|
|
266
|
+
"n_fit_samples": int(keep.sum()),
|
|
267
|
+
"exclude_regions_angstrom": excludes,
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
output_key = str(params["output_key"])
|
|
271
|
+
ctx.extras[output_key] = reference
|
|
272
|
+
|
|
273
|
+
residuals = raw_response[keep] - response[keep]
|
|
274
|
+
rms = float(np.sqrt(np.nanmean(residuals ** 2))) if residuals.size else 0.0
|
|
275
|
+
ctx.metrics["response_rms"] = rms
|
|
276
|
+
return AlgorithmOutput.ok(
|
|
277
|
+
metrics={
|
|
278
|
+
"response_rms": rms,
|
|
279
|
+
"n_fit_samples": float(int(keep.sum())),
|
|
280
|
+
"median_response": float(np.nanmedian(response)),
|
|
281
|
+
},
|
|
282
|
+
artifacts={
|
|
283
|
+
"fit_method": fit,
|
|
284
|
+
"exclude_regions_angstrom": excludes,
|
|
285
|
+
"output_key": output_key,
|
|
286
|
+
},
|
|
287
|
+
message=(
|
|
288
|
+
f"Response derived ({fit}, {int(keep.sum())} samples used, "
|
|
289
|
+
f"RMS={rms:.4g})."
|
|
290
|
+
),
|
|
291
|
+
)
|