spectro-kernel 0.1.5__tar.gz → 0.2.0__tar.gz

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