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.
Files changed (214) hide show
  1. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/.gitignore +6 -0
  2. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/CHANGELOG.md +99 -0
  3. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/PKG-INFO +10 -2
  4. spectro_kernel-0.1.2/playground/README.md +99 -0
  5. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/pyproject.toml +11 -2
  6. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/_common.py +36 -0
  7. spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_band_power.py +140 -0
  8. spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_continuum_subtracted.py +132 -0
  9. spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_lick_indices.py +211 -0
  10. spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_log_lambda.py +123 -0
  11. spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_pretrained.py +210 -0
  12. spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_remote.py +193 -0
  13. spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_spectrum.py +116 -0
  14. spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/embed_wavelets.py +148 -0
  15. spectro_kernel-0.1.2/src/spectro_kernel/embeddings.py +234 -0
  16. spectro_kernel-0.1.2/src/spectro_kernel/presets/catalog/analysis/embed_quick.yaml +21 -0
  17. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/context.py +12 -0
  18. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/enums.py +3 -0
  19. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/version.py +1 -1
  20. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/auto_tools.py +15 -1
  21. spectro_kernel-0.1.2/src/spectro_mcp/py.typed +0 -0
  22. spectro_kernel-0.1.2/tests/unit/test_embedding.py +178 -0
  23. spectro_kernel-0.1.2/tests/unit/test_embedding_pretrained.py +69 -0
  24. spectro_kernel-0.1.2/tests/unit/test_embedding_remote_lick.py +152 -0
  25. spectro_kernel-0.1.2/tests/unit/test_embedding_tier1.py +138 -0
  26. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_registry.py +8 -0
  27. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/LICENSE +0 -0
  28. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/README.md +0 -0
  29. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/concepts/algorithms.md +0 -0
  30. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/concepts/architecture.md +0 -0
  31. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/concepts/data-types.md +0 -0
  32. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/concepts/pipelines.md +0 -0
  33. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/contributing.md +0 -0
  34. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/cookbook/bess-dashboard.md +0 -0
  35. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/cookbook/multi-star-viewer.md +0 -0
  36. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/cookbook/web-playground.md +0 -0
  37. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/gen_catalogue.py +0 -0
  38. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/getting-started.md +0 -0
  39. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/index.md +0 -0
  40. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/aurora-line-monitor.md +0 -0
  41. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/be-star-variability.md +0 -0
  42. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/claude-mcp-end-to-end.md +0 -0
  43. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/exoplanet-transit-rv.md +0 -0
  44. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/full-reduction-walkthrough.md +0 -0
  45. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/native-vs-easyspec-showdown.md +0 -0
  46. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/notebooks/your-first-sb2.md +0 -0
  47. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/reference.md +0 -0
  48. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/tutorials/add-an-algorithm.md +0 -0
  49. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/tutorials/analyse-a-spectrum.md +0 -0
  50. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/tutorials/discover-the-catalogue.md +0 -0
  51. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/tutorials/first-spectrum.md +0 -0
  52. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/usage/cli.md +0 -0
  53. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/usage/library.md +0 -0
  54. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/usage/mcp.md +0 -0
  55. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/docs/why.md +0 -0
  56. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/__init__.py +0 -0
  57. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/adapters/__init__.py +0 -0
  58. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/adapters/easyspec.py +0 -0
  59. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/__init__.py +0 -0
  60. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/advanced/__init__.py +0 -0
  61. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/advanced/aperture_photometry.py +0 -0
  62. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/advanced/disentangle_sb2.py +0 -0
  63. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/catalogs/__init__.py +0 -0
  64. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/catalogs/gaia.py +0 -0
  65. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/catalogs/simbad.py +0 -0
  66. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/catalogs/vizier.py +0 -0
  67. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/__init__.py +0 -0
  68. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/compare_normalisations.py +0 -0
  69. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/normalize_edges.py +0 -0
  70. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/normalize_max.py +0 -0
  71. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/normalize_percentile.py +0 -0
  72. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/normalize_polynomial.py +0 -0
  73. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/continuum/subtract_continuum.py +0 -0
  74. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/__init__.py +0 -0
  75. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/air_vacuum.py +0 -0
  76. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/barycentric.py +0 -0
  77. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/doppler_shift.py +0 -0
  78. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/extinction_correct_easyspec.py +0 -0
  79. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/fit_telluric_scaling.py +0 -0
  80. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/flux_calibrate_easyspec.py +0 -0
  81. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/remove_telluric.py +0 -0
  82. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/corrections/synth_telluric.py +0 -0
  83. /spectro_kernel-0.1.0/src/spectro_kernel/py.typed → /spectro_kernel-0.1.2/src/spectro_kernel/algorithms/embedding/__init__.py +0 -0
  84. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/__init__.py +0 -0
  85. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/export_csv.py +0 -0
  86. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/export_fits.py +0 -0
  87. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/export_hdf5.py +0 -0
  88. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/exports/export_votable.py +0 -0
  89. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/extraction/__init__.py +0 -0
  90. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/extraction/easyspec_extract.py +0 -0
  91. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/__init__.py +0 -0
  92. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/read_ascii.py +0 -0
  93. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/read_echelle.py +0 -0
  94. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/read_fits.py +0 -0
  95. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/io/read_votable.py +0 -0
  96. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/__init__.py +0 -0
  97. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/_profiles.py +0 -0
  98. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/catalogs.py +0 -0
  99. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/compare_line_fits.py +0 -0
  100. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/detect.py +0 -0
  101. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/equivalent_width.py +0 -0
  102. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/fit_gaussian.py +0 -0
  103. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/fit_lorentzian.py +0 -0
  104. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/lines/fit_voigt.py +0 -0
  105. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/__init__.py +0 -0
  106. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/compare_snr_methods.py +0 -0
  107. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/snr_der.py +0 -0
  108. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/snr_edge.py +0 -0
  109. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/quality/snr_linear_fit.py +0 -0
  110. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/__init__.py +0 -0
  111. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/_easyspec_apply.py +0 -0
  112. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/_easyspec_helpers.py +0 -0
  113. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/bias_combine.py +0 -0
  114. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/clip_cosmic_rays.py +0 -0
  115. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/dark_subtract.py +0 -0
  116. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +0 -0
  117. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +0 -0
  118. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +0 -0
  119. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +0 -0
  120. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +0 -0
  121. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +0 -0
  122. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +0 -0
  123. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/extract_spectrum_sum.py +0 -0
  124. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/flat_normalize.py +0 -0
  125. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/subtract_sky_2d.py +0 -0
  126. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +0 -0
  127. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/__init__.py +0 -0
  128. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/cross_correlate.py +0 -0
  129. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/fit_keplerian_orbit.py +0 -0
  130. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/measure.py +0 -0
  131. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/rv/precision_bouchy.py +0 -0
  132. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/smoothing/__init__.py +0 -0
  133. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/smoothing/compare_smoothings.py +0 -0
  134. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/smoothing/smooth_gaussian.py +0 -0
  135. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/smoothing/smooth_savgol.py +0 -0
  136. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/stacking/__init__.py +0 -0
  137. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/stacking/merge_echelle_orders.py +0 -0
  138. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/stacking/stack.py +0 -0
  139. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/timeseries/__init__.py +0 -0
  140. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/timeseries/lomb_scargle.py +0 -0
  141. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/timeseries/phase_fold.py +0 -0
  142. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/__init__.py +0 -0
  143. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/clip_sigma.py +0 -0
  144. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/extract_region.py +0 -0
  145. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/mask_range.py +0 -0
  146. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/resample.py +0 -0
  147. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/transforms/resample_flux_conserving.py +0 -0
  148. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/__init__.py +0 -0
  149. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/plot_3d_surface.py +0 -0
  150. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/plot_animation.py +0 -0
  151. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/plot_dynamic_spectrum.py +0 -0
  152. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/viz/plot_plotly.py +0 -0
  153. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/wavelength_calibration/__init__.py +0 -0
  154. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/algorithms/wavelength_calibration/easyspec_wavelength.py +0 -0
  155. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/base.py +0 -0
  156. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/cli.py +0 -0
  157. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/errors.py +0 -0
  158. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/io/__init__.py +0 -0
  159. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/io/ascii.py +0 -0
  160. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/io/fits.py +0 -0
  161. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/io/votable.py +0 -0
  162. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/pipeline.py +0 -0
  163. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/__init__.py +0 -0
  164. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/balmer_quick.yaml +0 -0
  165. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/quality_report.yaml +0 -0
  166. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/rv_quick.yaml +0 -0
  167. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/snr_check.yaml +0 -0
  168. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/analysis/time_series_overview.yaml +0 -0
  169. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/catalog/reduction/full_reduction_easyspec.yaml +0 -0
  170. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/presets/loader.py +0 -0
  171. {spectro_kernel-0.1.0/src/spectro_mcp → spectro_kernel-0.1.2/src/spectro_kernel}/py.typed +0 -0
  172. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/registry.py +0 -0
  173. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/__init__.py +0 -0
  174. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/catalog.py +0 -0
  175. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/history.py +0 -0
  176. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/image.py +0 -0
  177. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/line.py +0 -0
  178. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/spectrum.py +0 -0
  179. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_kernel/types/timeseries.py +0 -0
  180. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/__init__.py +0 -0
  181. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/__main__.py +0 -0
  182. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/auth.py +0 -0
  183. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/observability.py +0 -0
  184. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/server.py +0 -0
  185. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/src/spectro_mcp/session.py +0 -0
  186. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/conftest.py +0 -0
  187. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/conftest.py +0 -0
  188. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/data/.gitkeep +0 -0
  189. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/data/README.md +0 -0
  190. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/data/sun/sun_reference_stis_002.fits +0 -0
  191. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/data/vega/alpha_lyr_stis_011.fits +0 -0
  192. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/test_known_answers.py +0 -0
  193. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/test_sun.py +0 -0
  194. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/reference/test_vega.py +0 -0
  195. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_base.py +0 -0
  196. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_cli.py +0 -0
  197. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_combine.py +0 -0
  198. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_continuum.py +0 -0
  199. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_corrections.py +0 -0
  200. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_easyspec_wrappers.py +0 -0
  201. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_io.py +0 -0
  202. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_line_profiles.py +0 -0
  203. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_lines.py +0 -0
  204. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_mcp.py +0 -0
  205. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_mcp_production.py +0 -0
  206. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_misc_algorithms.py +0 -0
  207. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_pipeline.py +0 -0
  208. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_quality.py +0 -0
  209. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_smoothing.py +0 -0
  210. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_timeseries.py +0 -0
  211. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_transforms.py +0 -0
  212. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_types.py +0 -0
  213. {spectro_kernel-0.1.0 → spectro_kernel-0.1.2}/tests/unit/test_viz.py +0 -0
  214. {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.0
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: Matthieu
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 = "Matthieu" }]
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
+ )