spectro-kernel 0.1.5__tar.gz → 0.2.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.1.5 → spectro_kernel-0.2.0}/CHANGELOG.md +151 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/PKG-INFO +1 -1
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/gen_catalogue.py +8 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/index.md +7 -1
- spectro_kernel-0.2.0/docs/tutorials/long-slit-reduction.md +168 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/continuum/normalize_region.py +97 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/embedding/embed_pretrained.py +8 -13
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/embedding/embed_remote.py +5 -10
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/embedding/embed_wavelets.py +5 -10
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/exports/export_fits_bess.py +278 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/exports/export_hdf5.py +76 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/extraction/sky_lateral_bands.py +146 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/_easyspec_helpers.py +9 -7
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/reduction/denoise_2d.py +270 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +84 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +89 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +71 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +73 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +85 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +80 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +78 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/reduction/geometry.py +260 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/transforms/combine_arithmetic.py +131 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/wavelength_calibration/in_situ.py +394 -0
- spectro_kernel-0.2.0/src/spectro_kernel/algorithms/wavelength_calibration/solar.py +252 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/version.py +1 -1
- spectro_kernel-0.2.0/tests/unit/test_combine_arithmetic.py +99 -0
- spectro_kernel-0.2.0/tests/unit/test_denoise_2d.py +125 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_easyspec_wrappers.py +5 -2
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_embedding_pretrained.py +29 -26
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_embedding_remote_lick.py +2 -2
- spectro_kernel-0.2.0/tests/unit/test_export_fits_bess.py +132 -0
- spectro_kernel-0.2.0/tests/unit/test_geometry_corrections.py +119 -0
- spectro_kernel-0.2.0/tests/unit/test_normalize_to_region.py +63 -0
- spectro_kernel-0.2.0/tests/unit/test_sky_lateral_bands.py +94 -0
- spectro_kernel-0.2.0/tests/unit/test_wavelength_calibration_in_situ.py +129 -0
- spectro_kernel-0.2.0/tests/unit/test_wavelength_calibration_solar.py +122 -0
- spectro_kernel-0.1.5/src/spectro_kernel/algorithms/exports/export_hdf5.py +0 -73
- spectro_kernel-0.1.5/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +0 -85
- spectro_kernel-0.1.5/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +0 -90
- spectro_kernel-0.1.5/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +0 -72
- spectro_kernel-0.1.5/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +0 -74
- spectro_kernel-0.1.5/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +0 -86
- spectro_kernel-0.1.5/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +0 -81
- spectro_kernel-0.1.5/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +0 -79
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/.gitignore +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/LICENSE +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/README.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/concepts/algorithms.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/concepts/architecture.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/concepts/data-types.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/concepts/pipelines.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/contributing.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/cookbook/bess-dashboard.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/cookbook/multi-star-viewer.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/cookbook/web-playground.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/getting-started.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_ew_timeseries.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_halpha_phase_stack.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_halpha_timeseries.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_overlay.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_pc1_vs_phase.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_pca_2d.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_pca_3d.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_periodogram.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_phase_folded.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_rv_timeseries.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_similarity.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_cyg_similarity_date.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_dra_halpha_phase_stack.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_dra_pc1_vs_phase.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_dra_pca_2d.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_dra_pca_3d.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_dra_periodogram.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_dra_phase_folded.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_dra_rv_timeseries.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_dra_similarity_date.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/images/notebooks/alpha_dra_similarity_phase.png +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/notebooks/alpha-cyg-time-series.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/notebooks/alpha-dra-binary-period.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/notebooks/aurora-line-monitor.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/notebooks/be-star-variability.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/notebooks/claude-mcp-end-to-end.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/notebooks/exoplanet-transit-rv.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/notebooks/full-reduction-walkthrough.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/notebooks/native-vs-easyspec-showdown.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/notebooks/your-first-sb2.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/reference.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/tutorials/add-an-algorithm.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/tutorials/analyse-a-spectrum.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/tutorials/discover-the-catalogue.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/tutorials/first-spectrum.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/usage/cli.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/usage/library.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/usage/mcp.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/docs/why.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/playground/README.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/pyproject.toml +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/adapters/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/adapters/easyspec.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/_common.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/advanced/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/advanced/aperture_photometry.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/advanced/disentangle_sb2.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/catalogs/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/catalogs/gaia.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/catalogs/simbad.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/catalogs/vizier.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/continuum/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/continuum/compare_normalisations.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/continuum/normalize_edges.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/continuum/normalize_max.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/continuum/normalize_percentile.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/continuum/normalize_polynomial.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/continuum/subtract_continuum.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/corrections/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/corrections/air_vacuum.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/corrections/barycentric.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/corrections/doppler_shift.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/corrections/extinction_correct_easyspec.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/corrections/fit_telluric_scaling.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/corrections/flux_calibrate_easyspec.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/corrections/remove_telluric.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/corrections/synth_telluric.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/embedding/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/embedding/embed_band_power.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/embedding/embed_continuum_subtracted.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/embedding/embed_lick_indices.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/embedding/embed_log_lambda.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/embedding/embed_spectrum.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/exports/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/exports/export_csv.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/exports/export_fits.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/exports/export_votable.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/extraction/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/extraction/easyspec_extract.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/io/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/io/read_ascii.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/io/read_echelle.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/io/read_fits.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/io/read_sdss.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/io/read_votable.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/lines/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/lines/_profiles.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/lines/catalogs.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/lines/compare_line_fits.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/lines/detect.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/lines/equivalent_width.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/lines/fit_gaussian.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/lines/fit_lorentzian.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/lines/fit_voigt.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/quality/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/quality/compare_snr_methods.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/quality/snr_der.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/quality/snr_edge.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/quality/snr_linear_fit.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/_easyspec_apply.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/bias_combine.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/clip_cosmic_rays.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/dark_subtract.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/extract_spectrum_sum.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/flat_normalize.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/subtract_sky_2d.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/rv/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/rv/cross_correlate.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/rv/fit_keplerian_orbit.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/rv/measure.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/rv/precision_bouchy.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/smoothing/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/smoothing/compare_smoothings.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/smoothing/smooth_gaussian.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/smoothing/smooth_savgol.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/stacking/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/stacking/merge_echelle_orders.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/stacking/stack.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/timeseries/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/timeseries/lomb_scargle.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/timeseries/phase_fold.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/transforms/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/transforms/clip_sigma.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/transforms/extract_region.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/transforms/mask_range.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/transforms/resample.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/transforms/resample_flux_conserving.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/viz/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/viz/plot_3d_surface.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/viz/plot_animation.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/viz/plot_dynamic_spectrum.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/viz/plot_plotly.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/wavelength_calibration/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/algorithms/wavelength_calibration/easyspec_wavelength.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/base.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/cli.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/embeddings.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/errors.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/io/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/io/ascii.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/io/fits.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/io/votable.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/pipeline.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/presets/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/presets/catalog/analysis/balmer_quick.yaml +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/presets/catalog/analysis/embed_quick.yaml +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/presets/catalog/analysis/quality_report.yaml +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/presets/catalog/analysis/rv_quick.yaml +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/presets/catalog/analysis/snr_check.yaml +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/presets/catalog/analysis/time_series_overview.yaml +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/presets/catalog/reduction/full_reduction_easyspec.yaml +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/presets/loader.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/py.typed +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/registry.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/types/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/types/catalog.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/types/context.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/types/enums.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/types/history.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/types/image.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/types/line.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/types/spectrum.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_kernel/types/timeseries.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_mcp/__init__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_mcp/__main__.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_mcp/auth.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_mcp/auto_tools.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_mcp/observability.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_mcp/py.typed +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_mcp/server.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_mcp/session.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/src/spectro_mcp/url_safety.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/conftest.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/reference/conftest.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/reference/data/.gitkeep +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/reference/data/README.md +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/reference/data/sun/sun_reference_stis_002.fits +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/reference/data/vega/alpha_lyr_stis_011.fits +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/reference/test_known_answers.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/reference/test_sun.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/reference/test_vega.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_base.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_cli.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_combine.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_continuum.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_corrections.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_embedding.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_embedding_tier1.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_io.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_line_profiles.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_lines.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_mcp.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_mcp_production.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_mcp_security.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_misc_algorithms.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_no_circular_imports.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_pipeline.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_quality.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_read_sdss.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_registry.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_smoothing.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_timeseries.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_transforms.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_types.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/tests/unit/test_viz.py +0 -0
- {spectro_kernel-0.1.5 → spectro_kernel-0.2.0}/website/README.md +0 -0
|
@@ -6,6 +6,157 @@ Until `1.0.0` the public API may change between minor versions.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.0] — 2026-06-09
|
|
10
|
+
|
|
11
|
+
A library expansion focused on long-slit reduction (aurora, stellar,
|
|
12
|
+
solar — wherever a 2-D spectrograph frame becomes a 1-D spectrum).
|
|
13
|
+
**13 new algorithms** ported from a downstream aurora-reduction codebase
|
|
14
|
+
into the kernel's standard pattern: same ``BaseAlgorithm`` / ``@register_algorithm``
|
|
15
|
+
shape, every reference cited, every parameter documented. Existing
|
|
16
|
+
behaviour is untouched; this is a pure additive bump.
|
|
17
|
+
|
|
18
|
+
The minor-version bump (``0.1.x`` → ``0.2.0``) signals the broader scope
|
|
19
|
+
even though no API breaks landed.
|
|
20
|
+
|
|
21
|
+
### Added — geometric long-slit corrections (3 algorithms)
|
|
22
|
+
|
|
23
|
+
- ``correct_tilt_affine`` — vertical shear re-aligning the slit with
|
|
24
|
+
the detector columns (Howell 2006 §5.2 ; Tody 1986 IRAF heritage).
|
|
25
|
+
- ``correct_slant_affine`` — horizontal shear pivoted on the trace row,
|
|
26
|
+
making monochromatic lines parallel to the rows.
|
|
27
|
+
- ``correct_smile_polynomial`` — closed-form Schroeder 2000 even-order
|
|
28
|
+
expansion ``dx(dy) = dy²/(2R) + dy⁴/(8R³) + 5·dy⁶/(16R⁵)`` undoing
|
|
29
|
+
the cylindrical "smile" of concave-grating spectrographs (Bottema 1980).
|
|
30
|
+
|
|
31
|
+
All three are no-ops when their amplitude parameter is zero, so they
|
|
32
|
+
can be composed unconditionally in a preset.
|
|
33
|
+
|
|
34
|
+
### Added — 2D image denoising (3 algorithms)
|
|
35
|
+
|
|
36
|
+
- ``outlier_rejection_mad_adaptive`` — vectorised Hwang & Haddad 1995
|
|
37
|
+
rank-conditional median filter with MAD as the local dispersion
|
|
38
|
+
estimator. Suppresses impulsive noise (CMOS RTS, hot pixels) without
|
|
39
|
+
smearing narrow spectral lines.
|
|
40
|
+
- ``denoise_gaussian_2d`` — 2D companion of the existing
|
|
41
|
+
``smooth_gaussian`` (which is 1D only).
|
|
42
|
+
- ``denoise_median_2d`` — square median filter, optionally banded by
|
|
43
|
+
rows so the science aperture is cleaned without disturbing the sky.
|
|
44
|
+
|
|
45
|
+
### Added — sky extraction + in-situ wavelength calibration
|
|
46
|
+
|
|
47
|
+
- ``extract_sky_lateral_bands`` — median-combine two off-trace bands
|
|
48
|
+
into a 1-D pixel-axis sky reference, stored at
|
|
49
|
+
``ctx.extras["sky_spectrum"]``. Builds the input for in-situ
|
|
50
|
+
calibration when no arc lamp is available.
|
|
51
|
+
- ``fit_emission_lines_gaussian`` — multi-line centroid fitter
|
|
52
|
+
(Gaussian + constant continuum). Distinct from ``fit_gaussian_line``
|
|
53
|
+
(single line, linear continuum) — both have their use.
|
|
54
|
+
- ``wavelength_calibration_in_situ`` — Stoughton et al. 2002 recipe:
|
|
55
|
+
centroid sky lines, derive Δλ vs. the catalogue, resample science on
|
|
56
|
+
a uniform Å grid and apply the shift.
|
|
57
|
+
- ``wavelength_calibration_solar`` — built-in catalogue of 15 strong
|
|
58
|
+
Fraunhofer lines (Ca II K/H, Hβ, Mg b triplet, Na D, Hα, O₂ telluric
|
|
59
|
+
bands), peak detection on the inverted spectrum, polynomial fit.
|
|
60
|
+
Lets daylight / twilight / solar spectra be calibrated without an arc
|
|
61
|
+
lamp.
|
|
62
|
+
|
|
63
|
+
### Added — 1D postprocessing (2 algorithms)
|
|
64
|
+
|
|
65
|
+
- ``combine_spectra_arithmetic`` — element-wise ``add``/``sub``/``mul``/``div``
|
|
66
|
+
between ``ctx.spectrum`` and a reference. The reference comes from
|
|
67
|
+
``reference_path`` (FITS) or ``ctx.extras["reference_spectrum"]`` —
|
|
68
|
+
the same idiom ``remove_telluric_division`` uses. IRAF ``sarith``
|
|
69
|
+
pass-through outside the reference's domain.
|
|
70
|
+
- ``normalize_to_region`` — divide the flux by its NaN-safe mean over
|
|
71
|
+
``[wave_lo, wave_hi]``. Sits beside the existing ``normalize_edges`` /
|
|
72
|
+
``normalize_polynomial`` / ``normalize_max`` / ``normalize_percentile``
|
|
73
|
+
family with the explicit "normalise to *this* band" semantic.
|
|
74
|
+
|
|
75
|
+
### Added — BeSS / ARAS-compliant FITS exporter
|
|
76
|
+
|
|
77
|
+
- ``export_fits_bess`` — writes ``ctx.spectrum`` (linear Å grid) with
|
|
78
|
+
the full BeSS observation-header convention (Teyssier 2015 §3.2 ;
|
|
79
|
+
Buil 2012 ARAS Observation Guide): OBJNAME, BSS_INST, BSS_SITE,
|
|
80
|
+
OBSERVER, DATE-OBS, MJD-OBS, JD-OBS (computed offline via Meeus 1991
|
|
81
|
+
§7), EXPTIME, BSS_VHEL, BSS_ESRC, plus the ``-32000`` masked-pixel
|
|
82
|
+
sentinel. Generates the canonical filename
|
|
83
|
+
``_<object>_<date>_<time>_<observer>.fits`` automatically.
|
|
84
|
+
|
|
85
|
+
### Improved — catalogue page
|
|
86
|
+
|
|
87
|
+
``docs/gen_catalogue.py`` now has friendly titles for every category
|
|
88
|
+
the kernel registers — the categories that joined the catalogue with
|
|
89
|
+
v0.1 (preprocessing, extraction, wavelength_calibration, embedding, …)
|
|
90
|
+
no longer fall back to the raw ``snake_case`` rendering.
|
|
91
|
+
|
|
92
|
+
### Numbers
|
|
93
|
+
|
|
94
|
+
**97 algorithms** are registered after this release, up from 84 in
|
|
95
|
+
v0.1.6. 243 tests pass (47 new). ``ruff check`` is clean, ``mkdocs
|
|
96
|
+
build --strict`` is clean. Boot-time discovery stays at ~1.9 s thanks
|
|
97
|
+
to v0.1.5/v0.1.6's lazy-import work.
|
|
98
|
+
|
|
99
|
+
## [0.1.6] — 2026-06-09
|
|
100
|
+
|
|
101
|
+
A hygiene pass on top of v0.1.5: same algorithms, same numerics, less
|
|
102
|
+
dead code, one consistent pattern for optional dependencies.
|
|
103
|
+
|
|
104
|
+
### Improved — single lazy-import pattern across all optional deps
|
|
105
|
+
|
|
106
|
+
v0.1.5 introduced the "import inside ``run()``" pattern for the seven
|
|
107
|
+
heavy optional dependencies (astroquery, photutils, specutils, easyspec,
|
|
108
|
+
astroscrappy, plotly, astropy.timeseries) and left four files
|
|
109
|
+
(``embed_wavelets``, ``embed_remote``, ``embed_pretrained``,
|
|
110
|
+
``export_hdf5``) on the older ``try: import X; _HAVE_X = bool`` pattern.
|
|
111
|
+
v0.1.6 unifies all of them. Concretely:
|
|
112
|
+
|
|
113
|
+
- ``export_hdf5`` no longer gates its ``@register_algorithm`` on a
|
|
114
|
+
module-level ``_HAVE_H5PY``; the algorithm always registers and fails
|
|
115
|
+
at call time with the install hint when h5py is missing.
|
|
116
|
+
- ``embed_wavelets`` (pywt), ``embed_remote`` (httpx) and
|
|
117
|
+
``embed_pretrained`` (torch) drop their module-level imports + bool
|
|
118
|
+
flags. Torch in particular was a multi-second cold import paid by
|
|
119
|
+
every spectro-kernel boot whenever the user had torch installed for
|
|
120
|
+
unrelated reasons; it now loads only when ``embed_pretrained`` is
|
|
121
|
+
actually invoked. Registry discovery drops by another ~1 s (2.9 s →
|
|
122
|
+
1.9 s on a warm Python cache).
|
|
123
|
+
|
|
124
|
+
### Cleaned — dead ``if HAVE_EASYSPEC:`` wrappers (7 files)
|
|
125
|
+
|
|
126
|
+
v0.1.5 made ``HAVE_EASYSPEC = True`` unconditional in
|
|
127
|
+
``_easyspec_helpers``, so every ``if HAVE_EASYSPEC: @register_algorithm
|
|
128
|
+
...`` guard in the easyspec wrappers became an always-true single-arm
|
|
129
|
+
branch (4-space indent, no else). Removed across
|
|
130
|
+
``easyspec_{bias,dark,flat,flat_normalize,subtract_bias,subtract_dark,
|
|
131
|
+
cosmic_ray}.py``. ``HAVE_EASYSPEC`` itself is no longer exported.
|
|
132
|
+
|
|
133
|
+
### Fixed — test guards using ``has_algorithm`` as a proxy for "extra installed"
|
|
134
|
+
|
|
135
|
+
v0.1.5 decoupled "the algorithm registered" from "the optional extra is
|
|
136
|
+
installed" (the algorithm now always registers; the extra controls
|
|
137
|
+
whether it succeeds at run time). The tests in ``test_viz.py`` and
|
|
138
|
+
``test_easyspec_wrappers.py`` were skipping on
|
|
139
|
+
``has_algorithm("plot_spectrum_plotly")`` / ``has_algorithm("bias_combine_easyspec")``
|
|
140
|
+
which is no longer the right proxy — they now use
|
|
141
|
+
``pytest.importorskip("plotly")`` / ``pytest.importorskip("easyspec")``.
|
|
142
|
+
This was caught by CI when v0.1.5 was tagged; the release was re-tagged
|
|
143
|
+
once the guards were fixed.
|
|
144
|
+
|
|
145
|
+
### Fixed — ``test_embedding_pretrained`` / ``test_embedding_remote`` patches
|
|
146
|
+
|
|
147
|
+
The tests monkey-patched ``embed_pretrained._HAVE_TORCH`` and
|
|
148
|
+
``embed_remote._httpx.post`` — both symbols vanished with the lazy-import
|
|
149
|
+
unification. Rewritten to inject into ``sys.modules`` (for the torch
|
|
150
|
+
"missing" case) and to patch ``httpx.post`` directly.
|
|
151
|
+
|
|
152
|
+
### Fixed — ``test_embedding_is_deterministic[multiscale_dct]`` (ULP-level)
|
|
153
|
+
|
|
154
|
+
The σ=1 ``gaussian_filter1d`` path that v0.1.5 unblocked is not bit-stable
|
|
155
|
+
between repeated calls (IEEE-754 / SIMD reordering, ~5.55e-17 max diff).
|
|
156
|
+
``assert_array_equal`` is too strong a contract for "no source of
|
|
157
|
+
randomness between calls"; relaxed to
|
|
158
|
+
``assert_allclose(rtol=0, atol=1e-14)``.
|
|
159
|
+
|
|
9
160
|
## [0.1.5] — 2026-06-09
|
|
10
161
|
|
|
11
162
|
### Fixed — `embed_wavelets` positional information (HIGH)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spectro-kernel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.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/
|
|
@@ -12,6 +12,12 @@ from spectro_kernel.registry import list_algorithms
|
|
|
12
12
|
|
|
13
13
|
_CATEGORY_TITLES = {
|
|
14
14
|
"io": "Input / output",
|
|
15
|
+
"master_creation": "Master frames",
|
|
16
|
+
"preprocessing": "Preprocessing (2D image)",
|
|
17
|
+
"cosmic_ray": "Cosmic-ray rejection",
|
|
18
|
+
"extraction": "Extraction (2D → 1D)",
|
|
19
|
+
"wavelength_calibration": "Wavelength calibration",
|
|
20
|
+
"flux_calibration": "Flux calibration",
|
|
15
21
|
"continuum": "Continuum",
|
|
16
22
|
"smoothing": "Smoothing",
|
|
17
23
|
"resampling": "Resampling",
|
|
@@ -23,9 +29,11 @@ _CATEGORY_TITLES = {
|
|
|
23
29
|
"radial_velocity": "Radial velocity",
|
|
24
30
|
"timeseries": "Time series",
|
|
25
31
|
"stacking": "Stacking",
|
|
32
|
+
"embedding": "Embeddings",
|
|
26
33
|
"catalog": "External catalogues",
|
|
27
34
|
"visualization": "Visualisation",
|
|
28
35
|
"export": "Export",
|
|
36
|
+
"advanced": "Advanced",
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
_BACKEND_NOTE = {
|
|
@@ -58,6 +58,12 @@ through MCP, you can do from a terminal, and vice-versa.
|
|
|
58
58
|
|
|
59
59
|
## Status
|
|
60
60
|
|
|
61
|
-
`v0.
|
|
61
|
+
`v0.2.0` — alpha. The catalogue covers ~97 algorithms across image-frame
|
|
62
|
+
reduction (bias / dark / flat, geometric rectification, denoising,
|
|
63
|
+
cosmic-ray clipping, sky subtraction, trace extraction), wavelength &
|
|
64
|
+
flux calibration (arc-lamp polynomial, in-situ from sky lines, solar
|
|
65
|
+
Fraunhofer), 1-D analysis (continuum, smoothing, line fitting, radial
|
|
66
|
+
velocity, time series), embeddings, exports (FITS / CSV / VOTable / HDF5
|
|
67
|
+
/ BeSS), and visualisation. The API may change until `v1.0.0`. See the
|
|
62
68
|
[changelog](https://github.com/matthieulel/spectro-kernel/blob/main/CHANGELOG.md)
|
|
63
69
|
for release notes.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Tutorial: long-slit reduction (aurora, stellar, solar)
|
|
2
|
+
|
|
3
|
+
Take a raw 2-D detector frame and walk it all the way to a flux-calibrated
|
|
4
|
+
1-D spectrum on a uniform Å grid, ready for line fitting or BeSS submission.
|
|
5
|
+
This tutorial shows how the **13 long-slit bricks added in v0.2.0** compose
|
|
6
|
+
into a complete reduction. The example uses an aurora-style setup (sky
|
|
7
|
+
emission lines as wavelength reference), but the same recipe works for
|
|
8
|
+
stellar long-slit, solar twilight, or any small-spectrograph field setup.
|
|
9
|
+
|
|
10
|
+
> If you only need 1-D analysis on an already-reduced spectrum, see
|
|
11
|
+
> [Analyse a spectrum](analyse-a-spectrum.md) instead.
|
|
12
|
+
|
|
13
|
+
## Pipeline overview
|
|
14
|
+
|
|
15
|
+
The new bricks form three groups, with one entry point each:
|
|
16
|
+
|
|
17
|
+
| Group | Bricks | When to use |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| **Geometry** | `correct_tilt_affine`, `correct_slant_affine`, `correct_smile_polynomial` | Once per instrument: align slit with columns, rectify monochromatic lines, undo cylindrical smile of concave gratings. |
|
|
20
|
+
| **2-D denoising** | `outlier_rejection_mad_adaptive`, `denoise_gaussian_2d`, `denoise_median_2d` | After cosmic-ray clipping, before extraction. CMOS RTS / hot pixels. |
|
|
21
|
+
| **In-situ λ** | `extract_sky_lateral_bands` + `fit_emission_lines_gaussian` + `wavelength_calibration_in_situ` (or `wavelength_calibration_solar`) | When no arc lamp is available — field aurora monitors, twilight, daytime solar. |
|
|
22
|
+
|
|
23
|
+
Two postprocessing helpers and the BeSS exporter round out the set:
|
|
24
|
+
|
|
25
|
+
| Group | Bricks |
|
|
26
|
+
|---|---|
|
|
27
|
+
| **Postprocessing 1-D** | `combine_spectra_arithmetic` (response/std-star division), `normalize_to_region` (normalise to a chosen continuum band) |
|
|
28
|
+
| **Export** | `export_fits_bess` (BeSS / ARAS-compliant FITS with full header, JD/MJD, canonical filename) |
|
|
29
|
+
|
|
30
|
+
## A complete preset
|
|
31
|
+
|
|
32
|
+
The kernel's preset format strings these bricks together. The example
|
|
33
|
+
below is the field-aurora workflow; substitute parameters for stellar or
|
|
34
|
+
solar setups.
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
# presets/aurora_long_slit.yaml
|
|
38
|
+
name: aurora_long_slit
|
|
39
|
+
description: |
|
|
40
|
+
Long-slit aurora reduction: geometry → denoising → extraction →
|
|
41
|
+
in-situ wavelength calibration → continuum normalisation.
|
|
42
|
+
steps:
|
|
43
|
+
# ── 1. Geometric rectification (instrument-specific, one-shot values).
|
|
44
|
+
- algorithm: correct_tilt_affine
|
|
45
|
+
params: {tilt_deg: 0.4}
|
|
46
|
+
- algorithm: correct_slant_affine
|
|
47
|
+
params: {slant_deg: 0.6, pivot_row: 120}
|
|
48
|
+
- algorithm: correct_smile_polynomial
|
|
49
|
+
params: {reference_row: 120, smile_radius: 1500.0, polynomial_order: 4}
|
|
50
|
+
|
|
51
|
+
# ── 2. Per-frame denoising (skip the science band when running median).
|
|
52
|
+
- algorithm: outlier_rejection_mad_adaptive
|
|
53
|
+
params: {kernel_size: 3, threshold: 0.6}
|
|
54
|
+
- algorithm: denoise_median_2d
|
|
55
|
+
params: {kernel_size: 3, row_lo: 0, row_hi: 100} # sky strip only
|
|
56
|
+
|
|
57
|
+
# ── 3. Sky-aware extraction.
|
|
58
|
+
- algorithm: extract_sky_lateral_bands
|
|
59
|
+
params:
|
|
60
|
+
band_above_lo: 80
|
|
61
|
+
band_above_hi: 110
|
|
62
|
+
band_below_lo: 130
|
|
63
|
+
band_below_hi: 160
|
|
64
|
+
- algorithm: subtract_sky_2d
|
|
65
|
+
params: {trace_row: 120, trace_half_width: 6, sky_offset: 4, sky_half_width: 10}
|
|
66
|
+
- algorithm: extract_spectrum_sum
|
|
67
|
+
params: {trace_row: 120, half_width: 6}
|
|
68
|
+
|
|
69
|
+
# ── 4. In-situ wavelength calibration from the simultaneously-acquired
|
|
70
|
+
# sky reference (no arc lamp needed).
|
|
71
|
+
- algorithm: wavelength_calibration_in_situ
|
|
72
|
+
params:
|
|
73
|
+
polynomial_coef: [1.0, 4000.0] # initial λ(x) ≈ 4000 + x
|
|
74
|
+
reference_wavelengths: [5577.34, 6300.30, 6363.78]
|
|
75
|
+
guess_positions: [1577, 2300, 2363]
|
|
76
|
+
search_width: 40.0
|
|
77
|
+
oversampling: 2.0
|
|
78
|
+
|
|
79
|
+
# ── 5. Continuum normalisation to a known line-free band.
|
|
80
|
+
- algorithm: normalize_to_region
|
|
81
|
+
params: {wave_lo: 6400.0, wave_hi: 6500.0}
|
|
82
|
+
|
|
83
|
+
# ── 6. BeSS-compliant FITS for sharing.
|
|
84
|
+
- algorithm: export_fits_bess
|
|
85
|
+
params:
|
|
86
|
+
object_name: "aurora_2026-06-09"
|
|
87
|
+
instrument: "Alpy-600"
|
|
88
|
+
site: "Skibotn"
|
|
89
|
+
observer: "field-monitor"
|
|
90
|
+
date_obs_utc: "2026-06-09T22:13:45"
|
|
91
|
+
exposure_seconds: 30.0
|
|
92
|
+
vhelio_kms: 0.0
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Save and run with the CLI:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
spectro run aurora_long_slit.yaml --image my_raw_frame.fits
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
… or in Python:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from spectro_kernel import WorkContext, run_preset
|
|
105
|
+
from spectro_kernel.io import read_fits_image # 2D variant
|
|
106
|
+
|
|
107
|
+
ctx = WorkContext(image=read_fits_image("my_raw_frame.fits"))
|
|
108
|
+
result = run_preset("aurora_long_slit", ctx)
|
|
109
|
+
print(result.history) # full audit trail
|
|
110
|
+
bess_blob = ctx.exports["fits_bess"]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Variants
|
|
114
|
+
|
|
115
|
+
### Stellar long-slit (arc-lamp calibration)
|
|
116
|
+
|
|
117
|
+
Replace step 4 with `wavelength_calibrate_polynomial` (existing brick)
|
|
118
|
+
on identifications from your arc-lamp frame. Steps 1–3, 5 and 6 stay
|
|
119
|
+
as-is.
|
|
120
|
+
|
|
121
|
+
### Solar / twilight (no sky lines, no arc lamp)
|
|
122
|
+
|
|
123
|
+
Use `wavelength_calibration_solar` in place of step 3+4. It needs only
|
|
124
|
+
two instrument-specific values (an approximate ``λ_min`` and a
|
|
125
|
+
dispersion guess in Å/pixel) and the built-in Fraunhofer catalogue:
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
- algorithm: wavelength_calibration_solar
|
|
129
|
+
params:
|
|
130
|
+
approx_wavelength_min_angstrom: 3800.0
|
|
131
|
+
approx_dispersion_angstrom_per_pixel: 1.0
|
|
132
|
+
poly_order: 3
|
|
133
|
+
min_lines_for_fit: 6
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Response correction with a standard star
|
|
137
|
+
|
|
138
|
+
After step 5 (or in place of it), divide by the instrumental response:
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
- algorithm: combine_spectra_arithmetic
|
|
142
|
+
params:
|
|
143
|
+
operation: div
|
|
144
|
+
reference_path: "/path/to/response.fits"
|
|
145
|
+
min_denominator: 1.0e-5
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The reference can also live in `ctx.extras["reference_spectrum"]` if
|
|
149
|
+
your pipeline derives it on the fly — same idiom as the existing
|
|
150
|
+
`remove_telluric_division`.
|
|
151
|
+
|
|
152
|
+
## What the bricks share
|
|
153
|
+
|
|
154
|
+
Every algorithm above follows the same kernel contract:
|
|
155
|
+
|
|
156
|
+
- **No new context fields**: 2-D frames go through `ctx.image`, the
|
|
157
|
+
science spectrum through `ctx.spectrum`, second spectra (sky, response,
|
|
158
|
+
standard) through `ctx.extras[...]`.
|
|
159
|
+
- **Literature references** on every class (`references = […]`).
|
|
160
|
+
- **No-op when zero**: geometric corrections and the Gaussian denoiser
|
|
161
|
+
pass through unchanged when their amplitude parameter is zero, so a
|
|
162
|
+
single preset can run on instruments with and without those distortions.
|
|
163
|
+
- **Provenance**: every step writes a fingerprint of what it did into
|
|
164
|
+
`ctx.image.meta` (for 2-D) or `ctx.spectrum.meta` (for 1-D), and the
|
|
165
|
+
per-step `metrics` are JSON-serialisable for later auditing.
|
|
166
|
+
|
|
167
|
+
See [Algorithm catalogue](../catalogue.md) for the full parameter and
|
|
168
|
+
reference list of each brick.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Algorithm: normalise a spectrum by its mean flux over a chosen wavelength window."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from ...base import AlgorithmOutput, BaseAlgorithm
|
|
10
|
+
from ...errors import InvalidParameterError
|
|
11
|
+
from ...registry import register_algorithm
|
|
12
|
+
from ...types import AlgorithmCategory, WorkContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@register_algorithm(
|
|
16
|
+
"normalize_to_region",
|
|
17
|
+
category=AlgorithmCategory.CONTINUUM,
|
|
18
|
+
version="1.0.0",
|
|
19
|
+
)
|
|
20
|
+
class NormalizeToRegion(BaseAlgorithm):
|
|
21
|
+
"""Divide the flux by its NaN-safe mean over ``[wave_lo, wave_hi]``.
|
|
22
|
+
|
|
23
|
+
The window should be a line-free continuum region so the output sits
|
|
24
|
+
near unity inside it. The companion of ``normalize_edges`` /
|
|
25
|
+
``normalize_polynomial`` / ``normalize_max`` /
|
|
26
|
+
``normalize_percentile`` with the explicit "normalise to *this*
|
|
27
|
+
band" convention — exactly what the IRAF ``continuum`` task does
|
|
28
|
+
when scaled by a measured band mean.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
backend = "numpy"
|
|
32
|
+
references = [
|
|
33
|
+
"Tody 1986, Proc. SPIE 627, 733 — IRAF continuum heritage.",
|
|
34
|
+
]
|
|
35
|
+
long_description = (
|
|
36
|
+
"out = flux / nanmean(flux[wave_lo:wave_hi]). The window is "
|
|
37
|
+
"closed at both ends and clamped to the spectrum's wavelength "
|
|
38
|
+
"range. Uncertainty (if present) is scaled by |region_mean|. "
|
|
39
|
+
"If the region mean is zero or non-finite the algorithm fails "
|
|
40
|
+
"with a clear message rather than emitting NaN."
|
|
41
|
+
)
|
|
42
|
+
default_params = {"wave_lo": 0.0, "wave_hi": 0.0}
|
|
43
|
+
required_params = ["wave_lo", "wave_hi"]
|
|
44
|
+
param_descriptions = {
|
|
45
|
+
"wave_lo": "Lower bound of the reference window (same unit as the wavelength axis).",
|
|
46
|
+
"wave_hi": "Upper bound of the reference window.",
|
|
47
|
+
}
|
|
48
|
+
input_requirements = ["spectrum"]
|
|
49
|
+
output_produces = ["spectrum", "metrics.region_mean"]
|
|
50
|
+
|
|
51
|
+
def run(self, ctx: WorkContext, params: dict[str, Any]) -> AlgorithmOutput:
|
|
52
|
+
wave_lo = float(params["wave_lo"])
|
|
53
|
+
wave_hi = float(params["wave_hi"])
|
|
54
|
+
if wave_hi <= wave_lo:
|
|
55
|
+
raise InvalidParameterError("wave_hi must be strictly greater than wave_lo.")
|
|
56
|
+
spec = ctx.spectrum
|
|
57
|
+
if spec is None:
|
|
58
|
+
return AlgorithmOutput.fail("No ctx.spectrum to normalise.")
|
|
59
|
+
|
|
60
|
+
wave = np.asarray(spec.wavelength, dtype=np.float64)
|
|
61
|
+
flux = np.asarray(spec.flux, dtype=np.float64)
|
|
62
|
+
# Nearest-sample window so users don't need to know the grid step.
|
|
63
|
+
lo_idx = int(np.argmin(np.abs(wave - wave_lo)))
|
|
64
|
+
hi_idx = int(np.argmin(np.abs(wave - wave_hi)))
|
|
65
|
+
lo_idx, hi_idx = sorted((lo_idx, hi_idx))
|
|
66
|
+
if hi_idx == lo_idx:
|
|
67
|
+
return AlgorithmOutput.fail(
|
|
68
|
+
f"Reference window [{wave_lo}, {wave_hi}] is narrower than "
|
|
69
|
+
"one sample of the spectrum's wavelength grid."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
region_mean = float(np.nanmean(flux[lo_idx : hi_idx + 1]))
|
|
73
|
+
if not np.isfinite(region_mean) or region_mean == 0.0:
|
|
74
|
+
return AlgorithmOutput.fail(
|
|
75
|
+
f"Reference region mean is {region_mean} — pick a window "
|
|
76
|
+
"with finite, non-zero flux."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
out = spec.with_flux(flux / region_mean, flux_unit="normalized")
|
|
80
|
+
if spec.uncertainty is not None:
|
|
81
|
+
out.uncertainty = spec.uncertainty / abs(region_mean)
|
|
82
|
+
out.meta["normalized_to_region"] = (wave_lo, wave_hi)
|
|
83
|
+
out.meta["normalization_factor"] = region_mean
|
|
84
|
+
ctx.spectrum = out
|
|
85
|
+
|
|
86
|
+
return AlgorithmOutput.ok(
|
|
87
|
+
metrics={
|
|
88
|
+
"region_mean": region_mean,
|
|
89
|
+
"wave_lo": wave_lo,
|
|
90
|
+
"wave_hi": wave_hi,
|
|
91
|
+
"n_samples": float(hi_idx - lo_idx + 1),
|
|
92
|
+
},
|
|
93
|
+
message=(
|
|
94
|
+
f"Normalised to mean {region_mean:.4g} over "
|
|
95
|
+
f"[{wave_lo:g}, {wave_hi:g}] ({hi_idx - lo_idx + 1} samples)."
|
|
96
|
+
),
|
|
97
|
+
)
|
|
@@ -28,13 +28,6 @@ from ...errors import InvalidParameterError
|
|
|
28
28
|
from ...registry import register_algorithm
|
|
29
29
|
from ...types import AlgorithmCategory, WorkContext
|
|
30
30
|
|
|
31
|
-
try:
|
|
32
|
-
import torch as _torch
|
|
33
|
-
_HAVE_TORCH = True
|
|
34
|
-
except ImportError: # pragma: no cover - exercised only when the extra is missing
|
|
35
|
-
_HAVE_TORCH = False
|
|
36
|
-
_torch = None # type: ignore[assignment]
|
|
37
|
-
|
|
38
31
|
|
|
39
32
|
@register_algorithm(
|
|
40
33
|
"embed_pretrained",
|
|
@@ -110,7 +103,9 @@ class EmbedPretrained(BaseAlgorithm):
|
|
|
110
103
|
_model_cache: dict[str, tuple[Any, str]] = {}
|
|
111
104
|
|
|
112
105
|
def run(self, ctx: WorkContext, params: dict[str, Any]) -> AlgorithmOutput:
|
|
113
|
-
|
|
106
|
+
try:
|
|
107
|
+
import torch # noqa: PLC0415 - lazy: torch is multi-second cold import
|
|
108
|
+
except ImportError:
|
|
114
109
|
return AlgorithmOutput.fail(
|
|
115
110
|
"embed_pretrained requires PyTorch. Install with: "
|
|
116
111
|
"pip install 'spectro-kernel[embedding-ml]' "
|
|
@@ -141,7 +136,7 @@ class EmbedPretrained(BaseAlgorithm):
|
|
|
141
136
|
device = str(params["device"])
|
|
142
137
|
|
|
143
138
|
try:
|
|
144
|
-
model, model_sha = self._load_cached(model_path, device)
|
|
139
|
+
model, model_sha = self._load_cached(torch, model_path, device)
|
|
145
140
|
except Exception as exc: # noqa: BLE001 - surface any torch.load error
|
|
146
141
|
return AlgorithmOutput.fail(
|
|
147
142
|
f"Failed to load model from {model_path}: "
|
|
@@ -155,8 +150,8 @@ class EmbedPretrained(BaseAlgorithm):
|
|
|
155
150
|
flux = np.interp(indices, np.arange(flux.size), flux).astype(np.float32)
|
|
156
151
|
|
|
157
152
|
model.eval()
|
|
158
|
-
with
|
|
159
|
-
tensor_in =
|
|
153
|
+
with torch.no_grad():
|
|
154
|
+
tensor_in = torch.from_numpy(flux).to(device)
|
|
160
155
|
tensor_out = model(tensor_in)
|
|
161
156
|
vec = tensor_out.detach().cpu().numpy().astype(np.float64).ravel()
|
|
162
157
|
|
|
@@ -189,14 +184,14 @@ class EmbedPretrained(BaseAlgorithm):
|
|
|
189
184
|
),
|
|
190
185
|
)
|
|
191
186
|
|
|
192
|
-
def _load_cached(self, model_path: Path, device: str) -> tuple[Any, str]:
|
|
187
|
+
def _load_cached(self, torch: Any, model_path: Path, device: str) -> tuple[Any, str]:
|
|
193
188
|
"""Load the model from disk, caching by (path, mtime, device)."""
|
|
194
189
|
key = f"{model_path}:{model_path.stat().st_mtime}:{device}"
|
|
195
190
|
cached = type(self)._model_cache.get(key)
|
|
196
191
|
if cached is not None:
|
|
197
192
|
return cached
|
|
198
193
|
model_sha = _sha256_file(model_path)
|
|
199
|
-
model =
|
|
194
|
+
model = torch.load(model_path, map_location=device, weights_only=False)
|
|
200
195
|
type(self)._model_cache[key] = (model, model_sha)
|
|
201
196
|
return model, model_sha
|
|
202
197
|
|
|
@@ -39,13 +39,6 @@ from ...errors import InvalidParameterError
|
|
|
39
39
|
from ...registry import register_algorithm
|
|
40
40
|
from ...types import AlgorithmCategory, WorkContext
|
|
41
41
|
|
|
42
|
-
try:
|
|
43
|
-
import httpx as _httpx
|
|
44
|
-
_HAVE_HTTPX = True
|
|
45
|
-
except ImportError: # pragma: no cover - exercised only when the extra is missing
|
|
46
|
-
_HAVE_HTTPX = False
|
|
47
|
-
_httpx = None # type: ignore[assignment]
|
|
48
|
-
|
|
49
42
|
|
|
50
43
|
@register_algorithm(
|
|
51
44
|
"embed_remote",
|
|
@@ -110,7 +103,9 @@ class EmbedRemote(BaseAlgorithm):
|
|
|
110
103
|
]
|
|
111
104
|
|
|
112
105
|
def run(self, ctx: WorkContext, params: dict[str, Any]) -> AlgorithmOutput:
|
|
113
|
-
|
|
106
|
+
try:
|
|
107
|
+
import httpx
|
|
108
|
+
except ImportError:
|
|
114
109
|
return AlgorithmOutput.fail(
|
|
115
110
|
"embed_remote requires httpx. Install with: "
|
|
116
111
|
"pip install 'spectro-kernel[embedding-remote]'"
|
|
@@ -139,14 +134,14 @@ class EmbedRemote(BaseAlgorithm):
|
|
|
139
134
|
"dim": int(params["dim"]),
|
|
140
135
|
}
|
|
141
136
|
try:
|
|
142
|
-
response =
|
|
137
|
+
response = httpx.post(
|
|
143
138
|
str(endpoint),
|
|
144
139
|
json=body,
|
|
145
140
|
headers=headers,
|
|
146
141
|
timeout=float(params["timeout_s"]),
|
|
147
142
|
)
|
|
148
143
|
response.raise_for_status()
|
|
149
|
-
except
|
|
144
|
+
except httpx.HTTPError as exc:
|
|
150
145
|
return AlgorithmOutput.fail(f"Remote endpoint failed: {exc}")
|
|
151
146
|
|
|
152
147
|
try:
|
|
@@ -11,13 +11,6 @@ from ...errors import InvalidParameterError
|
|
|
11
11
|
from ...registry import register_algorithm
|
|
12
12
|
from ...types import AlgorithmCategory, WorkContext
|
|
13
13
|
|
|
14
|
-
try:
|
|
15
|
-
import pywt as _pywt
|
|
16
|
-
_HAVE_PYWT = True
|
|
17
|
-
except ImportError: # pragma: no cover - exercised only when the extra is missing
|
|
18
|
-
_HAVE_PYWT = False
|
|
19
|
-
_pywt = None # type: ignore[assignment]
|
|
20
|
-
|
|
21
14
|
|
|
22
15
|
@register_algorithm(
|
|
23
16
|
"embed_wavelets",
|
|
@@ -83,7 +76,9 @@ class EmbedWavelets(BaseAlgorithm):
|
|
|
83
76
|
]
|
|
84
77
|
|
|
85
78
|
def run(self, ctx: WorkContext, params: dict[str, Any]) -> AlgorithmOutput:
|
|
86
|
-
|
|
79
|
+
try:
|
|
80
|
+
import pywt
|
|
81
|
+
except ImportError:
|
|
87
82
|
return AlgorithmOutput.fail(
|
|
88
83
|
"embed_wavelets requires PyWavelets. "
|
|
89
84
|
"Install with: pip install 'spectro-kernel[embedding-wavelets]'"
|
|
@@ -107,10 +102,10 @@ class EmbedWavelets(BaseAlgorithm):
|
|
|
107
102
|
|
|
108
103
|
# Cap the level so we don't ask for more decomposition than the
|
|
109
104
|
# spectrum length allows (PyWavelets warns otherwise).
|
|
110
|
-
max_level =
|
|
105
|
+
max_level = pywt.dwt_max_level(flux.size, wavelet)
|
|
111
106
|
effective_level = min(level, max_level)
|
|
112
107
|
|
|
113
|
-
coeffs =
|
|
108
|
+
coeffs = pywt.wavedec(flux, wavelet, level=effective_level)
|
|
114
109
|
# Concatenate approximation + all detail bands into one flat vector
|
|
115
110
|
# whose position encodes (band, position-within-band). This stable
|
|
116
111
|
# ordering is critical for the embedding to be useful: each
|