spectro-kernel 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/.gitignore +6 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/CHANGELOG.md +99 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/PKG-INFO +10 -2
- spectro_kernel-0.1.2/playground/README.md +99 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/pyproject.toml +11 -2
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/_common.py +36 -0
- spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_band_power.py +140 -0
- spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_continuum_subtracted.py +132 -0
- spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_lick_indices.py +211 -0
- spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_log_lambda.py +123 -0
- spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_pretrained.py +210 -0
- spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_remote.py +193 -0
- spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_spectrum.py +116 -0
- spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_wavelets.py +148 -0
- spectro_kernel-0.1.2/src/spectro_kernel/embeddings.py +234 -0
- spectro_kernel-0.1.2/src/spectro_kernel/presets/catalog/analysis/embed_quick.yaml +21 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/context.py +12 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/enums.py +3 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/version.py +1 -1
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/auto_tools.py +15 -1
- spectro_kernel-0.1.2/src/spectro_mcp/py.typed +0 -0
- spectro_kernel-0.1.2/tests/unit/test_embedding.py +178 -0
- spectro_kernel-0.1.2/tests/unit/test_embedding_pretrained.py +69 -0
- spectro_kernel-0.1.2/tests/unit/test_embedding_remote_lick.py +152 -0
- spectro_kernel-0.1.2/tests/unit/test_embedding_tier1.py +138 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_registry.py +8 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/LICENSE +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/README.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/concepts/algorithms.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/concepts/architecture.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/concepts/data-types.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/concepts/pipelines.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/contributing.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/cookbook/bess-dashboard.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/cookbook/multi-star-viewer.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/cookbook/web-playground.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/gen_catalogue.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/getting-started.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/index.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/aurora-line-monitor.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/be-star-variability.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/claude-mcp-end-to-end.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/exoplanet-transit-rv.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/full-reduction-walkthrough.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/native-vs-easyspec-showdown.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/your-first-sb2.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/reference.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/tutorials/add-an-algorithm.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/tutorials/analyse-a-spectrum.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/tutorials/discover-the-catalogue.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/tutorials/first-spectrum.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/usage/cli.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/usage/library.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/usage/mcp.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/why.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/adapters/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/adapters/easyspec.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/advanced/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/advanced/aperture_photometry.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/advanced/disentangle_sb2.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/catalogs/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/catalogs/gaia.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/catalogs/simbad.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/catalogs/vizier.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/compare_normalisations.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/normalize_edges.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/normalize_max.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/normalize_percentile.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/normalize_polynomial.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/subtract_continuum.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/air_vacuum.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/barycentric.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/doppler_shift.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/extinction_correct_easyspec.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/fit_telluric_scaling.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/flux_calibrate_easyspec.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/remove_telluric.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/synth_telluric.py +0 -0
- /spectro_kernel-0.1.0/src/spectro_kernel/py.typed → /spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/export_csv.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/export_fits.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/export_hdf5.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/export_votable.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/extraction/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/extraction/easyspec_extract.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/read_ascii.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/read_echelle.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/read_fits.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/read_votable.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/_profiles.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/catalogs.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/compare_line_fits.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/detect.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/equivalent_width.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/fit_gaussian.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/fit_lorentzian.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/fit_voigt.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/compare_snr_methods.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/snr_der.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/snr_edge.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/snr_linear_fit.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/_easyspec_apply.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/_easyspec_helpers.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/bias_combine.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/clip_cosmic_rays.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/dark_subtract.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/extract_spectrum_sum.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/flat_normalize.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/subtract_sky_2d.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/cross_correlate.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/fit_keplerian_orbit.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/measure.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/precision_bouchy.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/smoothing/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/smoothing/compare_smoothings.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/smoothing/smooth_gaussian.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/smoothing/smooth_savgol.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/stacking/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/stacking/merge_echelle_orders.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/stacking/stack.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/timeseries/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/timeseries/lomb_scargle.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/timeseries/phase_fold.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/clip_sigma.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/extract_region.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/mask_range.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/resample.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/resample_flux_conserving.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/plot_3d_surface.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/plot_animation.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/plot_dynamic_spectrum.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/plot_plotly.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/wavelength_calibration/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/wavelength_calibration/easyspec_wavelength.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/base.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/cli.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/errors.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/io/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/io/ascii.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/io/fits.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/io/votable.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/pipeline.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/balmer_quick.yaml +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/quality_report.yaml +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/rv_quick.yaml +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/snr_check.yaml +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/time_series_overview.yaml +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/reduction/full_reduction_easyspec.yaml +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/loader.py +0 -0
- {spectro_kernel-0.1.0/src/spectro_mcp → spectro_kernel-0.1.2/src/spectro_kernel}/py.typed +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/registry.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/catalog.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/history.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/image.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/line.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/spectrum.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/timeseries.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/__init__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/__main__.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/auth.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/observability.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/server.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/session.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/conftest.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/conftest.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/data/.gitkeep +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/data/README.md +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/data/sun/sun_reference_stis_002.fits +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/data/vega/alpha_lyr_stis_011.fits +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/test_known_answers.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/test_sun.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/test_vega.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_base.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_cli.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_combine.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_continuum.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_corrections.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_easyspec_wrappers.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_io.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_line_profiles.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_lines.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_mcp.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_mcp_production.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_misc_algorithms.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_pipeline.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_quality.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_smoothing.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_timeseries.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_transforms.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_types.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_viz.py +0 -0
- {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/website/README.md +0 -0
|
@@ -221,3 +221,9 @@ __marimo__/
|
|
|
221
221
|
# These are kept on disk for the maintainer but never committed: not on
|
|
222
222
|
# GitHub, not in the PyPI sdist, not on the docs site.
|
|
223
223
|
stuff/
|
|
224
|
+
|
|
225
|
+
# Playground — personal FITS files, generated plots and the local venv.
|
|
226
|
+
# The example script and README ARE tracked; only user data + outputs are not.
|
|
227
|
+
playground/datas/
|
|
228
|
+
playground/output/
|
|
229
|
+
playground/venv/
|
|
@@ -6,6 +6,105 @@ Until `1.0.0` the public API may change between minor versions.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.2] — 2026-06-05
|
|
10
|
+
|
|
11
|
+
### Added — embedding category (8 algorithms total)
|
|
12
|
+
|
|
13
|
+
The kernel grows a new `embedding` category. Eight algorithms ship together;
|
|
14
|
+
all write to `ctx.embedding` (new typed slot) and to
|
|
15
|
+
`ctx.extras['embedding_provenance']` (recipe + version + parameters — what a
|
|
16
|
+
similarity layer needs to decide which vectors to re-index when an algorithm
|
|
17
|
+
evolves). Catalogue size: 75 → 83.
|
|
18
|
+
|
|
19
|
+
**Core (no extra dependencies — numpy / scipy only):**
|
|
20
|
+
|
|
21
|
+
- `embed_spectrum` — three baseline recipes on the raw flux array:
|
|
22
|
+
- `naive` : linear resample to `dim` + L2 norm.
|
|
23
|
+
- `dct` : DCT-II coefficients truncated to `dim` + L2 norm.
|
|
24
|
+
- `multiscale_dct` : DCT at four Gaussian scales (σ = 1, 2, 4, 8) + L2 norm.
|
|
25
|
+
Older strategy names `pca` and `autoencoder` still work but emit a
|
|
26
|
+
`DeprecationWarning`.
|
|
27
|
+
- `embed_log_lambda` — resample to a uniform log-wavelength grid before
|
|
28
|
+
embedding. Two spectra of the same object at different radial velocities
|
|
29
|
+
produce shifted but similar shapes on this grid, so embeddings here are
|
|
30
|
+
more **Doppler-robust** than on the raw axis.
|
|
31
|
+
- `embed_band_power` — split the wavelength range into N adjacent bands,
|
|
32
|
+
integrate the flux in each. Output is a very compact (8–32 dim) vector
|
|
33
|
+
that mimics multiband photometry; robust to dispersion differences
|
|
34
|
+
between instruments.
|
|
35
|
+
- `embed_continuum_subtracted` — subtract the polynomial continuum first,
|
|
36
|
+
embed the line residual only. Decouples shape from absolute flux level;
|
|
37
|
+
two observations of the same star at different airmass yield very similar
|
|
38
|
+
embeddings under this recipe.
|
|
39
|
+
- `embed_lick_indices` — the classical 17-element Lick/IDS feature vector
|
|
40
|
+
(Worthey 1994 + Trager 1998). Every dimension is a named, physically
|
|
41
|
+
interpretable index (Hβ → age, Mgb → [Mg/Fe], TiO → Teff, …). Compact
|
|
42
|
+
and explainable, at the cost of being visible-spectrum-only (4000–6500 Å).
|
|
43
|
+
|
|
44
|
+
**Optional extras (no kernel install bloat):**
|
|
45
|
+
|
|
46
|
+
- `embed_wavelets` — discrete wavelet transform (PyWavelets) + truncate to
|
|
47
|
+
the largest-magnitude coefficients. Better than DCT for spectra dominated
|
|
48
|
+
by sharp, localised features. Requires the extra
|
|
49
|
+
`pip install 'spectro-kernel[embedding-wavelets]'`.
|
|
50
|
+
- `embed_remote` — call a remote inference endpoint over HTTP, get back the
|
|
51
|
+
embedding as JSON. Allows plugging in a heavy ML model hosted elsewhere
|
|
52
|
+
(your own MCP cloud, a HuggingFace Inference Endpoint, …) without
|
|
53
|
+
installing torch locally. Requires `[embedding-remote]` (httpx only).
|
|
54
|
+
- `embed_pretrained` — load a local PyTorch model and run inference. For
|
|
55
|
+
users who actually want a learned encoder (Spender, AstroCLIP,
|
|
56
|
+
custom-trained on their dataset). Requires `[embedding-ml]` (torch); the
|
|
57
|
+
kernel install stays light by default — torch is **not** pulled by `[all]`.
|
|
58
|
+
|
|
59
|
+
### Façade for batch use
|
|
60
|
+
|
|
61
|
+
- New module `spectro_kernel.embeddings` with `embed(spectrum, …)` and
|
|
62
|
+
`embed_flux(flux, …)` — the same recipes as the algorithm, exposed as
|
|
63
|
+
pure functions. A downstream indexer can batch-embed thousands of spectra
|
|
64
|
+
without paying the per-call `WorkContext` / `ProcessingStep` overhead.
|
|
65
|
+
|
|
66
|
+
### Pipeline preset
|
|
67
|
+
|
|
68
|
+
- New `embed_quick.yaml` — normalise the continuum then embed (DCT, dim 256).
|
|
69
|
+
Drop it onto any spectrum with `spectro pipeline embed_quick` for an
|
|
70
|
+
immediate searchable vector.
|
|
71
|
+
|
|
72
|
+
### Internals
|
|
73
|
+
|
|
74
|
+
- `algorithms/_common.fit_polynomial_continuum_asymmetric` — sigma-clipped
|
|
75
|
+
polynomial continuum fit with asymmetric clip thresholds (favours
|
|
76
|
+
rejecting absorption over emission). Used by `embed_spectrum` when
|
|
77
|
+
`norm_method='continuum'` and available to any future algorithm that
|
|
78
|
+
needs the same recipe.
|
|
79
|
+
- `numpy.trapz` was deprecated in numpy 2.0; the embedding algorithms now
|
|
80
|
+
import `numpy.trapezoid` with a fallback for older numpy.
|
|
81
|
+
|
|
82
|
+
### Tests
|
|
83
|
+
|
|
84
|
+
- 30+ new unit tests covering shape, L2 invariance, dim respected,
|
|
85
|
+
parameter validation, algorithm vs façade parity, recipe fidelity,
|
|
86
|
+
Doppler robustness (log-lambda), scale invariance (continuum-subtracted),
|
|
87
|
+
pipeline preset end-to-end, `WorkContext` summary + hash integration.
|
|
88
|
+
Total suite: 138 → 157 passing (+3 skipped on optional extras).
|
|
89
|
+
|
|
90
|
+
## [0.1.1] — 2026-06-03
|
|
91
|
+
|
|
92
|
+
### Added — full-resolution spectrum retrieval over MCP
|
|
93
|
+
|
|
94
|
+
- New transverse MCP tool **`get_spectrum_data`** that returns the
|
|
95
|
+
current session spectrum as full-length wavelength + flux arrays
|
|
96
|
+
(via `Spectrum1D.to_dict()`). The existing `get_spectrum_preview`
|
|
97
|
+
still returns the 50-sample summary; use the new tool when a client
|
|
98
|
+
needs to plot or re-analyse the spectrum at full resolution.
|
|
99
|
+
- Catalogue tool count online: 87 → 88.
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
|
|
103
|
+
- `pyproject.toml` author renamed `Matthieu` → `matthieulel` so the
|
|
104
|
+
PyPI page matches the GitHub handle.
|
|
105
|
+
|
|
106
|
+
## [0.1.0] — 2026-06-03 (initial PyPI release)
|
|
107
|
+
|
|
9
108
|
### Changed — keep deployment runbooks + internal design notes out of the public artefacts
|
|
10
109
|
|
|
11
110
|
- Moved `SPECTRO_KERNEL_IMPLEMENTATION.md`, `SPECTRO_PLATFORM_PROPOSAL.md`,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spectro-kernel
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
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/
|
|
7
7
|
Project-URL: Source, https://github.com/matthieulel/spectro-kernel
|
|
8
8
|
Project-URL: Issues, https://github.com/matthieulel/spectro-kernel/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/matthieulel/spectro-kernel/blob/main/CHANGELOG.md
|
|
10
|
-
Author:
|
|
10
|
+
Author: matthieulel
|
|
11
11
|
License: MIT
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Keywords: astronomy,fits,mcp,pipeline,spectroscopy
|
|
@@ -37,10 +37,12 @@ Requires-Dist: easyspec>=1.0; extra == 'all'
|
|
|
37
37
|
Requires-Dist: emcee>=3.1; extra == 'all'
|
|
38
38
|
Requires-Dist: fastmcp>=2.0; extra == 'all'
|
|
39
39
|
Requires-Dist: h5py>=3.10; extra == 'all'
|
|
40
|
+
Requires-Dist: httpx>=0.27; extra == 'all'
|
|
40
41
|
Requires-Dist: matplotlib>=3.8; extra == 'all'
|
|
41
42
|
Requires-Dist: photutils>=2.0; extra == 'all'
|
|
42
43
|
Requires-Dist: plotly>=5.20; extra == 'all'
|
|
43
44
|
Requires-Dist: pyvo>=1.5; extra == 'all'
|
|
45
|
+
Requires-Dist: pywavelets>=1.5; extra == 'all'
|
|
44
46
|
Requires-Dist: redis>=5.0; extra == 'all'
|
|
45
47
|
Requires-Dist: sentry-sdk>=2.0; extra == 'all'
|
|
46
48
|
Requires-Dist: uvicorn[standard]>=0.30; extra == 'all'
|
|
@@ -55,6 +57,12 @@ Provides-Extra: docs
|
|
|
55
57
|
Requires-Dist: mkdocs-gen-files>=0.5; extra == 'docs'
|
|
56
58
|
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
57
59
|
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
60
|
+
Provides-Extra: embedding-ml
|
|
61
|
+
Requires-Dist: torch>=2.2; extra == 'embedding-ml'
|
|
62
|
+
Provides-Extra: embedding-remote
|
|
63
|
+
Requires-Dist: httpx>=0.27; extra == 'embedding-remote'
|
|
64
|
+
Provides-Extra: embedding-wavelets
|
|
65
|
+
Requires-Dist: pywavelets>=1.5; extra == 'embedding-wavelets'
|
|
58
66
|
Provides-Extra: mcp
|
|
59
67
|
Requires-Dist: fastmcp>=2.0; extra == 'mcp'
|
|
60
68
|
Requires-Dist: redis>=5.0; extra == 'mcp'
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# spectro-kernel playground
|
|
2
|
+
|
|
3
|
+
A separate sandbox for trying the library **as an end user would**: its own
|
|
4
|
+
virtual environment, its own installed copy of `spectro-kernel`, isolated
|
|
5
|
+
from the development venv at the repo root.
|
|
6
|
+
|
|
7
|
+
## Setup (one-shot, ~3 min)
|
|
8
|
+
|
|
9
|
+
From this directory:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 1. fresh venv just for the playground
|
|
13
|
+
python3.12 -m venv venv
|
|
14
|
+
source venv/bin/activate
|
|
15
|
+
|
|
16
|
+
# 2. install spectro-kernel in editable mode from the parent repo,
|
|
17
|
+
# with the [viz] extra for the Plotly plots.
|
|
18
|
+
pip install -e "..[viz]"
|
|
19
|
+
|
|
20
|
+
# 3. plotly is required to write the HTML files
|
|
21
|
+
pip install plotly
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Once PyPI publication is done you'll replace step 2 with simply
|
|
25
|
+
`pip install "spectro-kernel[viz]"` — but for now the editable install
|
|
26
|
+
lets you test the *exact* code in the repository.
|
|
27
|
+
|
|
28
|
+
## Run
|
|
29
|
+
|
|
30
|
+
Three flavours of the same end-to-end recipe:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
source venv/bin/activate # if not already in
|
|
34
|
+
|
|
35
|
+
# A. Library, full output (2 interactive HTML plots)
|
|
36
|
+
python quickstart.py
|
|
37
|
+
python quickstart.py <filename.fits> # specific file
|
|
38
|
+
|
|
39
|
+
# B. Library, minimal stdout, 1 PNG (needs kaleido: `pip install kaleido`)
|
|
40
|
+
python quickstart_min.py
|
|
41
|
+
python quickstart_min.py <filename.fits>
|
|
42
|
+
|
|
43
|
+
# C. Same recipe, but every call goes over JSON-RPC to the cloud MCP server.
|
|
44
|
+
# Needs fastmcp: `pip install fastmcp`. Uses a public CALSPEC Sun
|
|
45
|
+
# spectrum since the cloud server can't reach your local FITS files.
|
|
46
|
+
python quickstart_mcp.py
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Override the MCP URL with `SPECTRO_MCP_URL=...` (for testing a stdio server
|
|
50
|
+
through an HTTP wrapper, a different DO deploy, etc.).
|
|
51
|
+
|
|
52
|
+
You will see something like:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
=== α Cyg quickstart — alphacyg_20231006185128_gbertrand.fits ===
|
|
56
|
+
|
|
57
|
+
📥 loaded: 5925 pixels · 6496.8-6687.8 Angstrom
|
|
58
|
+
📊 SNR (DER_SNR): 124.6
|
|
59
|
+
🖼 raw plot: output/alphacyg_20231006185128_gbertrand__01_raw.html
|
|
60
|
+
✨ normalised: continuum_median was 1875 → flux now ~1.0
|
|
61
|
+
🔭 detected: 1 line(s) matched against the Balmer catalogue
|
|
62
|
+
· H-alpha 6562.81 Å (absorption)
|
|
63
|
+
🖼 normalised plot:output/alphacyg_20231006185128_gbertrand__02_normalised.html
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Open the two HTML files in your browser to inspect the spectra interactively.
|
|
67
|
+
|
|
68
|
+
## What's in here
|
|
69
|
+
|
|
70
|
+
| Path | Role | In Git? |
|
|
71
|
+
|------|------|---------|
|
|
72
|
+
| `quickstart.py` | The example script — read it as a guided tour | ✅ |
|
|
73
|
+
| `README.md` | This file | ✅ |
|
|
74
|
+
| `datas/alphacygni/*.fits` | Your personal spectra | ❌ gitignored |
|
|
75
|
+
| `output/*.html` | Generated Plotly HTML | ❌ gitignored |
|
|
76
|
+
| `venv/` | Local virtualenv | ❌ gitignored |
|
|
77
|
+
|
|
78
|
+
## What the script does
|
|
79
|
+
|
|
80
|
+
1. **Loads** one FITS spectrum into a `Spectrum1D` and parks it in a `WorkContext`.
|
|
81
|
+
2. **`snr_der`** — derivative-based SNR estimate (DER_SNR, Stoehr+ 2008).
|
|
82
|
+
3. **`plot_spectrum_plotly`** — first plot, raw spectrum.
|
|
83
|
+
4. **`normalize_polynomial`** — polynomial continuum fit (order 3), flux divided
|
|
84
|
+
so the continuum lands near 1.0.
|
|
85
|
+
5. **`detect_lines`** with the `balmer` catalogue — finds H-alpha (and any other
|
|
86
|
+
Balmer line that happens to be in the wavelength range).
|
|
87
|
+
6. **`plot_spectrum_plotly`** again, this time on the normalised spectrum, with
|
|
88
|
+
red vertical markers at each detected line.
|
|
89
|
+
|
|
90
|
+
Every step is a normal `run_algorithm(...)` call against the same shared
|
|
91
|
+
`WorkContext` — exactly the same recipe a real pipeline would use.
|
|
92
|
+
|
|
93
|
+
## Going further
|
|
94
|
+
|
|
95
|
+
- Edit `quickstart.py` to try other algorithms — e.g. `fit_gaussian_line` to
|
|
96
|
+
fit H-alpha properly, or `barycentric_correction` to apply the heliocentric
|
|
97
|
+
velocity correction.
|
|
98
|
+
- Loop over every file in `datas/alphacygni/` to build a time series.
|
|
99
|
+
- Use `stack_spectra` to coadd them (drop them into `ctx.spectra` first).
|
|
@@ -9,7 +9,7 @@ description = "A shared catalogue of astronomical spectroscopy algorithms, compo
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
requires-python = ">=3.11"
|
|
12
|
-
authors = [{ name = "
|
|
12
|
+
authors = [{ name = "matthieulel" }]
|
|
13
13
|
keywords = ["astronomy", "spectroscopy", "fits", "pipeline", "mcp"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
@@ -52,6 +52,12 @@ mcp = [
|
|
|
52
52
|
"uvicorn[standard]>=0.30",
|
|
53
53
|
]
|
|
54
54
|
monitoring = ["sentry-sdk>=2.0"]
|
|
55
|
+
# Embedding extras — kept thin on purpose so the kernel install stays light.
|
|
56
|
+
# Algorithms register themselves regardless; they fail with a clear error
|
|
57
|
+
# pointing at the right extra when their dependency is missing.
|
|
58
|
+
embedding-wavelets = ["pywavelets>=1.5"]
|
|
59
|
+
embedding-remote = ["httpx>=0.27"] # client for an external inference endpoint
|
|
60
|
+
embedding-ml = ["torch>=2.2"] # local PyTorch model inference
|
|
55
61
|
dev = [
|
|
56
62
|
"pytest>=8.0",
|
|
57
63
|
"pytest-cov>=5.0",
|
|
@@ -63,7 +69,10 @@ docs = [
|
|
|
63
69
|
"mkdocs-gen-files>=0.5",
|
|
64
70
|
]
|
|
65
71
|
all = [
|
|
66
|
-
"spectro-kernel[catalogs,viz,storage,reduction,mcp,monitoring]",
|
|
72
|
+
"spectro-kernel[catalogs,viz,storage,reduction,mcp,monitoring,embedding-wavelets,embedding-remote]",
|
|
73
|
+
# NOTE: embedding-ml (torch) is intentionally NOT pulled by [all] — torch is
|
|
74
|
+
# a multi-gigabyte install. Users who want local PyTorch embeddings opt in
|
|
75
|
+
# explicitly with [embedding-ml].
|
|
67
76
|
]
|
|
68
77
|
|
|
69
78
|
[project.scripts]
|
|
@@ -45,6 +45,42 @@ def fit_polynomial_continuum(
|
|
|
45
45
|
return np.polynomial.Polynomial.fit(x[mask], y[mask], deg=order)(x)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
def fit_polynomial_continuum_asymmetric(
|
|
49
|
+
flux: np.ndarray,
|
|
50
|
+
*,
|
|
51
|
+
order: int = 6,
|
|
52
|
+
iterations: int = 5,
|
|
53
|
+
sigma_low: float = 1.5,
|
|
54
|
+
sigma_high: float = 3.0,
|
|
55
|
+
) -> np.ndarray:
|
|
56
|
+
"""Asymmetric sigma-clipped polynomial continuum fit.
|
|
57
|
+
|
|
58
|
+
Companion to :func:`fit_polynomial_continuum` (symmetric clip): rejecting
|
|
59
|
+
absorption lines more aggressively (``sigma_low``) than emission lines
|
|
60
|
+
(``sigma_high``) is what you want when the continuum sits *above* the
|
|
61
|
+
lines, as in most stellar spectra. The wavelength axis is normalised to
|
|
62
|
+
``[-1, 1]`` internally so callers can pass a raw flux array without
|
|
63
|
+
worrying about polynomial conditioning.
|
|
64
|
+
"""
|
|
65
|
+
n = int(flux.size)
|
|
66
|
+
x = np.linspace(-1.0, 1.0, n)
|
|
67
|
+
mask = np.ones(n, dtype=bool)
|
|
68
|
+
coeffs = np.zeros(order + 1)
|
|
69
|
+
for _ in range(max(1, iterations)):
|
|
70
|
+
if mask.sum() < order + 1:
|
|
71
|
+
break
|
|
72
|
+
coeffs = np.polyfit(x[mask], flux[mask], order)
|
|
73
|
+
continuum = np.polyval(coeffs, x)
|
|
74
|
+
residuals = flux - continuum
|
|
75
|
+
sigma = float(np.std(residuals[mask]))
|
|
76
|
+
if sigma == 0.0:
|
|
77
|
+
break
|
|
78
|
+
mask = (residuals > -sigma_low * sigma) & (residuals < sigma_high * sigma)
|
|
79
|
+
if mask.sum() >= order + 1:
|
|
80
|
+
coeffs = np.polyfit(x[mask], flux[mask], order)
|
|
81
|
+
return np.polyval(coeffs, x)
|
|
82
|
+
|
|
83
|
+
|
|
48
84
|
def r_squared(observed: np.ndarray, modelled: np.ndarray) -> float:
|
|
49
85
|
"""Return the coefficient of determination of *modelled* against *observed*."""
|
|
50
86
|
observed = np.asarray(observed, dtype=np.float64)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Algorithm: band-power embedding — integrated flux in N wavelength bands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
# numpy renamed trapz -> trapezoid in 2.0; support both.
|
|
10
|
+
try:
|
|
11
|
+
from numpy import trapezoid as _trapezoid
|
|
12
|
+
except ImportError: # pragma: no cover - numpy < 2.0
|
|
13
|
+
from numpy import trapz as _trapezoid
|
|
14
|
+
|
|
15
|
+
from ...base import AlgorithmOutput, BaseAlgorithm
|
|
16
|
+
from ...errors import InvalidParameterError
|
|
17
|
+
from ...registry import register_algorithm
|
|
18
|
+
from ...types import AlgorithmCategory, WorkContext
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@register_algorithm(
|
|
22
|
+
"embed_band_power",
|
|
23
|
+
category=AlgorithmCategory.EMBEDDING,
|
|
24
|
+
version="1.0.0",
|
|
25
|
+
)
|
|
26
|
+
class EmbedBandPower(BaseAlgorithm):
|
|
27
|
+
"""Embed a spectrum as the integrated flux in N adjacent wavelength bands.
|
|
28
|
+
|
|
29
|
+
The wavelength range is split into ``n_bands`` non-overlapping bands;
|
|
30
|
+
each band's value is the trapezoidal integral of the flux within it.
|
|
31
|
+
Optionally takes a logarithm to compress the dynamic range (~ astronomical
|
|
32
|
+
magnitudes), then L2-normalises the vector. The output captures the gross
|
|
33
|
+
shape of the spectral energy distribution while being **very compact**
|
|
34
|
+
(typically 8–32 dimensions) and **robust to resolution differences**
|
|
35
|
+
between instruments — band sums average over many pixels.
|
|
36
|
+
|
|
37
|
+
Spacing can be linear or logarithmic in wavelength; logarithmic is the
|
|
38
|
+
natural choice for stellar spectra (matches the spacing of photometric
|
|
39
|
+
filter systems like SDSS *ugriz* or 2MASS *JHK*).
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
backend = "numpy"
|
|
43
|
+
references = [
|
|
44
|
+
"Connolly et al. 1995, AJ, 110, 1071 — photometric SED classification.",
|
|
45
|
+
"Bolzonella et al. 2000, A&A, 363, 476 — band-power feature vectors "
|
|
46
|
+
"for spectro-photometric classification.",
|
|
47
|
+
]
|
|
48
|
+
long_description = (
|
|
49
|
+
"Equivalent to converting a spectrum into a 'virtual photometric "
|
|
50
|
+
"catalogue' with N synthetic broad bands. Very cheap and well-suited "
|
|
51
|
+
"to indexing very heterogeneous datasets (different dispersions, "
|
|
52
|
+
"resolutions, telescopes)."
|
|
53
|
+
)
|
|
54
|
+
default_params: dict[str, Any] = {
|
|
55
|
+
"n_bands": 16,
|
|
56
|
+
"spacing": "log", # linear | log
|
|
57
|
+
"log_flux": True,
|
|
58
|
+
"epsilon": 1.0e-12, # added before log to avoid -inf on zero bands
|
|
59
|
+
}
|
|
60
|
+
param_descriptions = {
|
|
61
|
+
"n_bands": "Number of bands; also the output dim. Typically 8–32.",
|
|
62
|
+
"spacing": "Band edges in wavelength: 'linear' or 'log'.",
|
|
63
|
+
"log_flux": "Take log10(integrated_flux + epsilon) before L2 norm.",
|
|
64
|
+
"epsilon": "Small floor added before log_flux to keep zero bands finite.",
|
|
65
|
+
}
|
|
66
|
+
input_requirements = ["spectrum"]
|
|
67
|
+
output_produces = [
|
|
68
|
+
"embedding",
|
|
69
|
+
"extras.embedding_provenance",
|
|
70
|
+
"metrics.embedding_dim",
|
|
71
|
+
"metrics.embedding_norm",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
def run(self, ctx: WorkContext, params: dict[str, Any]) -> AlgorithmOutput:
|
|
75
|
+
spec = ctx.spectrum
|
|
76
|
+
if spec is None or spec.flux is None or spec.flux.size == 0:
|
|
77
|
+
return AlgorithmOutput.fail(
|
|
78
|
+
"embed_band_power requires a non-empty spectrum on ctx.spectrum."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
n_bands = int(params["n_bands"])
|
|
82
|
+
if n_bands <= 0:
|
|
83
|
+
raise InvalidParameterError("n_bands must be a positive integer.")
|
|
84
|
+
spacing = str(params["spacing"]).lower()
|
|
85
|
+
if spacing not in ("linear", "log"):
|
|
86
|
+
raise InvalidParameterError("spacing must be 'linear' or 'log'.")
|
|
87
|
+
if spacing == "log" and float(spec.wavelength_min) <= 0.0:
|
|
88
|
+
return AlgorithmOutput.fail(
|
|
89
|
+
"spacing='log' requires strictly positive wavelengths; "
|
|
90
|
+
"use spacing='linear' or call extract_region first."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
wave = np.asarray(spec.wavelength, dtype=np.float64)
|
|
94
|
+
flux = np.asarray(spec.flux, dtype=np.float64)
|
|
95
|
+
|
|
96
|
+
if spacing == "linear":
|
|
97
|
+
edges = np.linspace(spec.wavelength_min, spec.wavelength_max, n_bands + 1)
|
|
98
|
+
else:
|
|
99
|
+
edges = np.exp(
|
|
100
|
+
np.linspace(
|
|
101
|
+
np.log(spec.wavelength_min),
|
|
102
|
+
np.log(spec.wavelength_max),
|
|
103
|
+
n_bands + 1,
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
band_power = np.empty(n_bands, dtype=np.float64)
|
|
108
|
+
for i in range(n_bands):
|
|
109
|
+
mask = (wave >= edges[i]) & (wave <= edges[i + 1])
|
|
110
|
+
if mask.sum() < 2:
|
|
111
|
+
band_power[i] = 0.0
|
|
112
|
+
else:
|
|
113
|
+
band_power[i] = float(_trapezoid(flux[mask], wave[mask]))
|
|
114
|
+
|
|
115
|
+
if bool(params["log_flux"]):
|
|
116
|
+
band_power = np.log10(np.abs(band_power) + float(params["epsilon"]))
|
|
117
|
+
|
|
118
|
+
norm = float(np.linalg.norm(band_power))
|
|
119
|
+
if norm > 0.0:
|
|
120
|
+
band_power = band_power / norm
|
|
121
|
+
|
|
122
|
+
ctx.embedding = band_power
|
|
123
|
+
ctx.extras["embedding_provenance"] = {
|
|
124
|
+
"algorithm": "embed_band_power",
|
|
125
|
+
"n_bands": n_bands,
|
|
126
|
+
"spacing": spacing,
|
|
127
|
+
"log_flux": bool(params["log_flux"]),
|
|
128
|
+
"band_edges": edges.tolist(),
|
|
129
|
+
"version": self.version,
|
|
130
|
+
}
|
|
131
|
+
ctx.metrics["embedding_dim"] = float(n_bands)
|
|
132
|
+
ctx.metrics["embedding_norm"] = float(np.linalg.norm(band_power))
|
|
133
|
+
return AlgorithmOutput.ok(
|
|
134
|
+
metrics={
|
|
135
|
+
"embedding_dim": float(n_bands),
|
|
136
|
+
"embedding_norm": float(np.linalg.norm(band_power)),
|
|
137
|
+
},
|
|
138
|
+
artifacts={"provenance": ctx.extras["embedding_provenance"]},
|
|
139
|
+
message=f"Band-power embedded ({n_bands} {spacing}-spaced bands).",
|
|
140
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Algorithm: embed only the line residual after subtracting the continuum."""
|
|
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 ...embeddings import VALID_NORM_METHODS, VALID_STRATEGIES, embed_flux
|
|
11
|
+
from ...errors import InvalidParameterError
|
|
12
|
+
from ...registry import register_algorithm
|
|
13
|
+
from ...types import AlgorithmCategory, WorkContext
|
|
14
|
+
from .._common import fit_polynomial_continuum
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@register_algorithm(
|
|
18
|
+
"embed_continuum_subtracted",
|
|
19
|
+
category=AlgorithmCategory.EMBEDDING,
|
|
20
|
+
version="1.0.0",
|
|
21
|
+
)
|
|
22
|
+
class EmbedContinuumSubtracted(BaseAlgorithm):
|
|
23
|
+
"""Subtract the polynomial continuum, then embed the line residual.
|
|
24
|
+
|
|
25
|
+
Decouples the *shape* of the spectrum (its absorption / emission lines)
|
|
26
|
+
from its *absolute flux level* (continuum). Two observations of the same
|
|
27
|
+
object at different airmass — and therefore different continua — should
|
|
28
|
+
yield very similar embeddings under this recipe, even when the raw
|
|
29
|
+
spectra look obviously different in flux.
|
|
30
|
+
|
|
31
|
+
Equivalent to chaining the existing ``subtract_continuum`` algorithm and
|
|
32
|
+
``embed_spectrum`` in a pipeline; kept as a single registered algorithm
|
|
33
|
+
so callers don't have to author a pipeline and so a single provenance
|
|
34
|
+
record describes the whole flow.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
backend = "scipy"
|
|
38
|
+
references = [
|
|
39
|
+
"Sousa et al. 2007, A&A, 469, 783 — continuum-normalised spectra for "
|
|
40
|
+
"stellar parameter retrieval.",
|
|
41
|
+
"Worthey et al. 1994, ApJS, 94, 687 — line-strength indices on "
|
|
42
|
+
"continuum-flattened spectra.",
|
|
43
|
+
]
|
|
44
|
+
long_description = (
|
|
45
|
+
"Uses the symmetric sigma-clipped polynomial continuum helper from "
|
|
46
|
+
"``algorithms._common``; the residual (flux - continuum) is then "
|
|
47
|
+
"passed to one of the standard embedding recipes. Set "
|
|
48
|
+
"``norm_method='none'`` to skip a second normalisation step — the "
|
|
49
|
+
"residual already has zero mean."
|
|
50
|
+
)
|
|
51
|
+
default_params: dict[str, Any] = {
|
|
52
|
+
"dim": 256,
|
|
53
|
+
"strategy": "dct",
|
|
54
|
+
"continuum_order": 3,
|
|
55
|
+
"sigma_clip": 3.0,
|
|
56
|
+
"norm_method": "none",
|
|
57
|
+
}
|
|
58
|
+
param_descriptions = {
|
|
59
|
+
"dim": "Output vector length (positive integer).",
|
|
60
|
+
"strategy": (
|
|
61
|
+
f"Recipe applied to the residual: one of {VALID_STRATEGIES}."
|
|
62
|
+
),
|
|
63
|
+
"continuum_order": "Polynomial degree for the continuum fit (2-5 typical).",
|
|
64
|
+
"sigma_clip": "Sigma threshold for the continuum-fit clip (default 3.0).",
|
|
65
|
+
"norm_method": (
|
|
66
|
+
f"Pre-embedding flux normalisation: one of {VALID_NORM_METHODS}. "
|
|
67
|
+
"Defaults to 'none' because the residual is already zero-centred."
|
|
68
|
+
),
|
|
69
|
+
}
|
|
70
|
+
input_requirements = ["spectrum"]
|
|
71
|
+
output_produces = [
|
|
72
|
+
"embedding",
|
|
73
|
+
"extras.embedding_provenance",
|
|
74
|
+
"metrics.embedding_dim",
|
|
75
|
+
"metrics.embedding_norm",
|
|
76
|
+
"metrics.continuum_rms",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
def run(self, ctx: WorkContext, params: dict[str, Any]) -> AlgorithmOutput:
|
|
80
|
+
spec = ctx.spectrum
|
|
81
|
+
if spec is None or spec.flux is None or spec.flux.size == 0:
|
|
82
|
+
return AlgorithmOutput.fail(
|
|
83
|
+
"embed_continuum_subtracted requires a non-empty spectrum on ctx.spectrum."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
wave = np.asarray(spec.wavelength, dtype=np.float64)
|
|
87
|
+
flux = np.asarray(spec.flux, dtype=np.float64)
|
|
88
|
+
|
|
89
|
+
continuum = fit_polynomial_continuum(
|
|
90
|
+
wave,
|
|
91
|
+
flux,
|
|
92
|
+
order=int(params["continuum_order"]),
|
|
93
|
+
sigma_clip=float(params["sigma_clip"]),
|
|
94
|
+
)
|
|
95
|
+
residual = flux - continuum
|
|
96
|
+
rms = float(np.std(residual))
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
vec = embed_flux(
|
|
100
|
+
residual,
|
|
101
|
+
dim=int(params["dim"]),
|
|
102
|
+
strategy=str(params["strategy"]),
|
|
103
|
+
norm_method=str(params["norm_method"]),
|
|
104
|
+
)
|
|
105
|
+
except ValueError as exc:
|
|
106
|
+
raise InvalidParameterError(str(exc)) from exc
|
|
107
|
+
|
|
108
|
+
ctx.embedding = vec
|
|
109
|
+
ctx.extras["embedding_provenance"] = {
|
|
110
|
+
"algorithm": "embed_continuum_subtracted",
|
|
111
|
+
"strategy": str(params["strategy"]),
|
|
112
|
+
"dim": int(vec.size),
|
|
113
|
+
"continuum_order": int(params["continuum_order"]),
|
|
114
|
+
"sigma_clip": float(params["sigma_clip"]),
|
|
115
|
+
"norm_method": str(params["norm_method"]),
|
|
116
|
+
"version": self.version,
|
|
117
|
+
}
|
|
118
|
+
ctx.metrics["embedding_dim"] = float(vec.size)
|
|
119
|
+
ctx.metrics["embedding_norm"] = float(np.linalg.norm(vec))
|
|
120
|
+
ctx.metrics["continuum_rms"] = rms
|
|
121
|
+
return AlgorithmOutput.ok(
|
|
122
|
+
metrics={
|
|
123
|
+
"embedding_dim": float(vec.size),
|
|
124
|
+
"embedding_norm": float(np.linalg.norm(vec)),
|
|
125
|
+
"continuum_rms": rms,
|
|
126
|
+
},
|
|
127
|
+
artifacts={"provenance": ctx.extras["embedding_provenance"]},
|
|
128
|
+
message=(
|
|
129
|
+
f"Continuum-subtracted embedded ({params['strategy']}, "
|
|
130
|
+
f"dim={vec.size}, residual RMS={rms:.4g})."
|
|
131
|
+
),
|
|
132
|
+
)
|