spectro-kernel 0.3.0__tar.gz → 0.4.1__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 (299) hide show
  1. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/CHANGELOG.md +154 -0
  2. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/PKG-INFO +1 -1
  3. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/gen_catalogue.py +3 -0
  4. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/advanced/doppler_tomogram.py +311 -0
  5. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/classification/classify_template_chi2.py +292 -0
  6. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/classification/pickles_atlas.py +151 -0
  7. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/kinematics/rotation_curve.py +241 -0
  8. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/lines/_line_flux.py +152 -0
  9. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/lines/detect.py +348 -0
  10. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/lines/vr_ratio.py +287 -0
  11. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/nebular/__init__.py +0 -0
  12. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/nebular/bpt_line_ratios.py +242 -0
  13. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/nebular/oiii_electron_temperature.py +218 -0
  14. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/nebular/sii_electron_density.py +222 -0
  15. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/rv/cross_correlate.py +350 -0
  16. spectro_kernel-0.4.1/src/spectro_kernel/algorithms/rv/redshift_lines.py +239 -0
  17. spectro_kernel-0.4.1/src/spectro_kernel/py.typed +0 -0
  18. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/types/enums.py +5 -0
  19. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/version.py +1 -1
  20. spectro_kernel-0.4.1/src/spectro_mcp/py.typed +0 -0
  21. spectro_kernel-0.4.1/tests/unit/test_classify_template_chi2.py +108 -0
  22. spectro_kernel-0.4.1/tests/unit/test_cross_correlate_extension.py +118 -0
  23. spectro_kernel-0.4.1/tests/unit/test_detect_lines_blind.py +119 -0
  24. spectro_kernel-0.4.1/tests/unit/test_doppler_tomogram.py +79 -0
  25. spectro_kernel-0.4.1/tests/unit/test_nebular_diagnostics.py +167 -0
  26. spectro_kernel-0.4.1/tests/unit/test_redshift_lines.py +50 -0
  27. spectro_kernel-0.4.1/tests/unit/test_rotation_curve.py +68 -0
  28. spectro_kernel-0.4.1/tests/unit/test_vr_ratio.py +60 -0
  29. spectro_kernel-0.3.0/src/spectro_kernel/algorithms/lines/detect.py +0 -157
  30. spectro_kernel-0.3.0/src/spectro_kernel/algorithms/rv/cross_correlate.py +0 -151
  31. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/.gitignore +0 -0
  32. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/LICENSE +0 -0
  33. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/README.md +0 -0
  34. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/concepts/algorithms.md +0 -0
  35. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/concepts/architecture.md +0 -0
  36. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/concepts/data-types.md +0 -0
  37. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/concepts/pipelines.md +0 -0
  38. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/contributing.md +0 -0
  39. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/cookbook/bess-dashboard.md +0 -0
  40. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/cookbook/multi-star-viewer.md +0 -0
  41. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/cookbook/web-playground.md +0 -0
  42. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/getting-started.md +0 -0
  43. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_ew_timeseries.png +0 -0
  44. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_halpha_phase_stack.png +0 -0
  45. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_halpha_timeseries.png +0 -0
  46. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_overlay.png +0 -0
  47. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_pc1_vs_phase.png +0 -0
  48. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_pca_2d.png +0 -0
  49. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_pca_3d.png +0 -0
  50. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_periodogram.png +0 -0
  51. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_phase_folded.png +0 -0
  52. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_rv_timeseries.png +0 -0
  53. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_similarity.png +0 -0
  54. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_cyg_similarity_date.png +0 -0
  55. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_dra_halpha_phase_stack.png +0 -0
  56. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_dra_pc1_vs_phase.png +0 -0
  57. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_dra_pca_2d.png +0 -0
  58. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_dra_pca_3d.png +0 -0
  59. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_dra_periodogram.png +0 -0
  60. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_dra_phase_folded.png +0 -0
  61. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_dra_rv_timeseries.png +0 -0
  62. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_dra_similarity_date.png +0 -0
  63. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/images/notebooks/alpha_dra_similarity_phase.png +0 -0
  64. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/index.md +0 -0
  65. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/notebooks/alpha-cyg-time-series.md +0 -0
  66. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/notebooks/alpha-dra-binary-period.md +0 -0
  67. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/notebooks/aurora-line-monitor.md +0 -0
  68. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/notebooks/be-star-variability.md +0 -0
  69. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/notebooks/claude-mcp-end-to-end.md +0 -0
  70. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/notebooks/exoplanet-transit-rv.md +0 -0
  71. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/notebooks/full-reduction-walkthrough.md +0 -0
  72. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/notebooks/native-vs-easyspec-showdown.md +0 -0
  73. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/notebooks/your-first-sb2.md +0 -0
  74. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/reference.md +0 -0
  75. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/tutorials/add-an-algorithm.md +0 -0
  76. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/tutorials/analyse-a-spectrum.md +0 -0
  77. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/tutorials/discover-the-catalogue.md +0 -0
  78. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/tutorials/first-spectrum.md +0 -0
  79. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/tutorials/long-slit-reduction.md +0 -0
  80. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/usage/cli.md +0 -0
  81. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/usage/library.md +0 -0
  82. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/usage/mcp.md +0 -0
  83. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/docs/why.md +0 -0
  84. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/playground/README.md +0 -0
  85. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/pyproject.toml +0 -0
  86. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/__init__.py +0 -0
  87. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/adapters/__init__.py +0 -0
  88. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/adapters/easyspec.py +0 -0
  89. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/__init__.py +0 -0
  90. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/_common.py +0 -0
  91. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/advanced/__init__.py +0 -0
  92. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/advanced/aperture_photometry.py +0 -0
  93. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/advanced/disentangle_sb2.py +0 -0
  94. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/catalogs/__init__.py +0 -0
  95. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/catalogs/gaia.py +0 -0
  96. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/catalogs/simbad.py +0 -0
  97. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/catalogs/vizier.py +0 -0
  98. {spectro_kernel-0.3.0/src/spectro_kernel/algorithms/embedding → spectro_kernel-0.4.1/src/spectro_kernel/algorithms/classification}/__init__.py +0 -0
  99. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/continuum/__init__.py +0 -0
  100. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/continuum/compare_normalisations.py +0 -0
  101. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/continuum/normalize_edges.py +0 -0
  102. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/continuum/normalize_max.py +0 -0
  103. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/continuum/normalize_percentile.py +0 -0
  104. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/continuum/normalize_polynomial.py +0 -0
  105. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/continuum/normalize_region.py +0 -0
  106. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/continuum/subtract_continuum.py +0 -0
  107. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/__init__.py +0 -0
  108. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/air_vacuum.py +0 -0
  109. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/barycentric.py +0 -0
  110. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/doppler_shift.py +0 -0
  111. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/extinction_correct_easyspec.py +0 -0
  112. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/fit_telluric_scaling.py +0 -0
  113. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/flux_calibrate_easyspec.py +0 -0
  114. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/remove_telluric.py +0 -0
  115. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/response_from_standard.py +0 -0
  116. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/corrections/synth_telluric.py +0 -0
  117. /spectro_kernel-0.3.0/src/spectro_kernel/py.typed → /spectro_kernel-0.4.1/src/spectro_kernel/algorithms/embedding/__init__.py +0 -0
  118. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/embedding/embed_band_power.py +0 -0
  119. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/embedding/embed_continuum_subtracted.py +0 -0
  120. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/embedding/embed_lick_indices.py +0 -0
  121. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/embedding/embed_log_lambda.py +0 -0
  122. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/embedding/embed_pretrained.py +0 -0
  123. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/embedding/embed_remote.py +0 -0
  124. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/embedding/embed_spectrum.py +0 -0
  125. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/embedding/embed_wavelets.py +0 -0
  126. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/exports/__init__.py +0 -0
  127. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/exports/export_csv.py +0 -0
  128. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/exports/export_fits.py +0 -0
  129. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/exports/export_fits_bess.py +0 -0
  130. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/exports/export_hdf5.py +0 -0
  131. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/exports/export_votable.py +0 -0
  132. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/extraction/__init__.py +0 -0
  133. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/extraction/boxcar.py +0 -0
  134. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/extraction/detect_trace.py +0 -0
  135. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/extraction/easyspec_extract.py +0 -0
  136. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/extraction/optimal.py +0 -0
  137. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/extraction/sky_lateral_bands.py +0 -0
  138. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/io/__init__.py +0 -0
  139. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/io/read_ascii.py +0 -0
  140. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/io/read_echelle.py +0 -0
  141. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/io/read_fits.py +0 -0
  142. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/io/read_sdss.py +0 -0
  143. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/io/read_votable.py +0 -0
  144. /spectro_kernel-0.3.0/src/spectro_mcp/py.typed → /spectro_kernel-0.4.1/src/spectro_kernel/algorithms/kinematics/__init__.py +0 -0
  145. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/lines/__init__.py +0 -0
  146. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/lines/_profiles.py +0 -0
  147. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/lines/catalogs.py +0 -0
  148. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/lines/compare_line_fits.py +0 -0
  149. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/lines/equivalent_width.py +0 -0
  150. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/lines/fit_gaussian.py +0 -0
  151. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/lines/fit_lorentzian.py +0 -0
  152. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/lines/fit_voigt.py +0 -0
  153. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/quality/__init__.py +0 -0
  154. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/quality/compare_snr_methods.py +0 -0
  155. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/quality/snr_der.py +0 -0
  156. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/quality/snr_edge.py +0 -0
  157. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/quality/snr_linear_fit.py +0 -0
  158. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/__init__.py +0 -0
  159. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/_easyspec_apply.py +0 -0
  160. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/_easyspec_helpers.py +0 -0
  161. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/bias_combine.py +0 -0
  162. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/clip_cosmic_rays.py +0 -0
  163. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/dark_combine.py +0 -0
  164. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/dark_subtract.py +0 -0
  165. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/denoise_2d.py +0 -0
  166. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +0 -0
  167. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +0 -0
  168. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +0 -0
  169. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +0 -0
  170. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +0 -0
  171. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +0 -0
  172. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +0 -0
  173. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/extract_spectrum_sum.py +0 -0
  174. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/flat_combine.py +0 -0
  175. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/flat_normalize.py +0 -0
  176. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/geometry.py +0 -0
  177. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/subtract_sky_2d.py +0 -0
  178. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +0 -0
  179. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/rv/__init__.py +0 -0
  180. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/rv/fit_keplerian_orbit.py +0 -0
  181. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/rv/measure.py +0 -0
  182. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/rv/precision_bouchy.py +0 -0
  183. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/smoothing/__init__.py +0 -0
  184. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/smoothing/compare_smoothings.py +0 -0
  185. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/smoothing/smooth_gaussian.py +0 -0
  186. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/smoothing/smooth_savgol.py +0 -0
  187. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/stacking/__init__.py +0 -0
  188. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/stacking/merge_echelle_orders.py +0 -0
  189. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/stacking/stack.py +0 -0
  190. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/timeseries/__init__.py +0 -0
  191. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/timeseries/lomb_scargle.py +0 -0
  192. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/timeseries/phase_fold.py +0 -0
  193. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/transforms/__init__.py +0 -0
  194. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/transforms/clip_sigma.py +0 -0
  195. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/transforms/combine_arithmetic.py +0 -0
  196. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/transforms/extract_region.py +0 -0
  197. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/transforms/mask_range.py +0 -0
  198. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/transforms/resample.py +0 -0
  199. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/transforms/resample_flux_conserving.py +0 -0
  200. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/viz/__init__.py +0 -0
  201. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/viz/plot_3d_surface.py +0 -0
  202. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/viz/plot_animation.py +0 -0
  203. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/viz/plot_dynamic_spectrum.py +0 -0
  204. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/viz/plot_plotly.py +0 -0
  205. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/wavelength_calibration/__init__.py +0 -0
  206. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/wavelength_calibration/arc_geometry.py +0 -0
  207. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/wavelength_calibration/easyspec_wavelength.py +0 -0
  208. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/wavelength_calibration/in_situ.py +0 -0
  209. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/wavelength_calibration/lamp_atlas.py +0 -0
  210. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/wavelength_calibration/match_lamp.py +0 -0
  211. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/algorithms/wavelength_calibration/solar.py +0 -0
  212. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/base.py +0 -0
  213. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/cli.py +0 -0
  214. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/embeddings.py +0 -0
  215. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/errors.py +0 -0
  216. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/io/__init__.py +0 -0
  217. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/io/ascii.py +0 -0
  218. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/io/fits.py +0 -0
  219. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/io/votable.py +0 -0
  220. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/pipeline.py +0 -0
  221. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/presets/__init__.py +0 -0
  222. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/presets/catalog/analysis/balmer_quick.yaml +0 -0
  223. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/presets/catalog/analysis/embed_quick.yaml +0 -0
  224. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/presets/catalog/analysis/quality_report.yaml +0 -0
  225. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/presets/catalog/analysis/rv_quick.yaml +0 -0
  226. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/presets/catalog/analysis/snr_check.yaml +0 -0
  227. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/presets/catalog/analysis/time_series_overview.yaml +0 -0
  228. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/presets/catalog/reduction/full_reduction_easyspec.yaml +0 -0
  229. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/presets/loader.py +0 -0
  230. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/registry.py +0 -0
  231. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/types/__init__.py +0 -0
  232. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/types/catalog.py +0 -0
  233. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/types/context.py +0 -0
  234. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/types/history.py +0 -0
  235. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/types/image.py +0 -0
  236. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/types/line.py +0 -0
  237. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/types/spectrum.py +0 -0
  238. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_kernel/types/timeseries.py +0 -0
  239. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_mcp/__init__.py +0 -0
  240. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_mcp/__main__.py +0 -0
  241. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_mcp/auth.py +0 -0
  242. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_mcp/auto_tools.py +0 -0
  243. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_mcp/observability.py +0 -0
  244. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_mcp/server.py +0 -0
  245. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_mcp/session.py +0 -0
  246. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/src/spectro_mcp/url_safety.py +0 -0
  247. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/conftest.py +0 -0
  248. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/reference/conftest.py +0 -0
  249. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/reference/data/.gitkeep +0 -0
  250. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/reference/data/README.md +0 -0
  251. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/reference/data/sun/sun_reference_stis_002.fits +0 -0
  252. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/reference/data/vega/alpha_lyr_stis_011.fits +0 -0
  253. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/reference/test_known_answers.py +0 -0
  254. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/reference/test_sun.py +0 -0
  255. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/reference/test_vega.py +0 -0
  256. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_base.py +0 -0
  257. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_cli.py +0 -0
  258. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_combine.py +0 -0
  259. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_combine_arithmetic.py +0 -0
  260. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_continuum.py +0 -0
  261. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_corrections.py +0 -0
  262. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_dark_flat_combine.py +0 -0
  263. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_denoise_2d.py +0 -0
  264. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_detect_trace.py +0 -0
  265. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_easyspec_apply_staging.py +0 -0
  266. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_easyspec_wrappers.py +0 -0
  267. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_embedding.py +0 -0
  268. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_embedding_pretrained.py +0 -0
  269. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_embedding_remote_lick.py +0 -0
  270. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_embedding_tier1.py +0 -0
  271. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_export_fits_bess.py +0 -0
  272. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_extract_boxcar.py +0 -0
  273. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_extract_optimal.py +0 -0
  274. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_geometry_corrections.py +0 -0
  275. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_io.py +0 -0
  276. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_lamp_atlas_match.py +0 -0
  277. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_line_profiles.py +0 -0
  278. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_lines.py +0 -0
  279. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_mcp.py +0 -0
  280. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_mcp_production.py +0 -0
  281. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_mcp_security.py +0 -0
  282. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_measure_arc_geometry.py +0 -0
  283. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_misc_algorithms.py +0 -0
  284. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_no_circular_imports.py +0 -0
  285. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_normalize_to_region.py +0 -0
  286. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_pipeline.py +0 -0
  287. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_quality.py +0 -0
  288. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_read_sdss.py +0 -0
  289. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_registry.py +0 -0
  290. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_response_from_standard.py +0 -0
  291. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_sky_lateral_bands.py +0 -0
  292. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_smoothing.py +0 -0
  293. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_timeseries.py +0 -0
  294. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_transforms.py +0 -0
  295. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_types.py +0 -0
  296. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_viz.py +0 -0
  297. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_wavelength_calibration_in_situ.py +0 -0
  298. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/tests/unit/test_wavelength_calibration_solar.py +0 -0
  299. {spectro_kernel-0.3.0 → spectro_kernel-0.4.1}/website/README.md +0 -0
@@ -6,6 +6,160 @@ Until `1.0.0` the public API may change between minor versions.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.1] — 2026-06-26
10
+
11
+ Backwards-compatible extension of `cross_correlate_rv` so downstream
12
+ RV consumers can delegate to the kernel without losing the
13
+ peak-confidence / error / CCF-curve / template-provenance surface
14
+ they currently maintain in-house.
15
+
16
+ ### Changed — `cross_correlate_rv` v1.1.0 → v1.2.0
17
+
18
+ - The CCF is now **Pearson-normalised** so the peak height reads
19
+ directly as a similarity score bounded in ``[-1, 1]``.
20
+ - New ``metrics.peak_strength`` (clamped to ``[0, 1]``) — UI-friendly
21
+ confidence indicator (1 ⇒ template-identical observed spectrum).
22
+ - New ``metrics.radial_velocity_error_kms`` — the canonical
23
+ **Tonry & Davis 1979** §III formula
24
+ ``σ_v = 3 · w / (8 · (1 + r))`` with
25
+ ``r = h / (√2 · σ_a)`` (peak height over noise antisymmetry RMS)
26
+ and ``w`` the CCF peak FWHM. Reports ``NaN`` when the CCF shape
27
+ forbids a clean estimate (no resolved peak, no antisymmetric
28
+ noise sample).
29
+ - New ``extras["ccf"] = {"lags_kms": [...], "ccf": [...]}`` — the CCF
30
+ curve restricted to the velocity search window, for downstream
31
+ plotting.
32
+ - New ``extras["template_id"]`` — best-effort label of the template
33
+ used (file basename for ``template_path``, key name for
34
+ ``template_key``).
35
+ - Pre-existing output ``metrics.radial_velocity_kms`` is preserved
36
+ bit-exact ; the ``v1.2.0`` bump reflects the *additions*. A
37
+ regression test (`tests/reference/test_known_answers.py`) covers
38
+ the existing 50 km/s recovery.
39
+
40
+ ### Numbers
41
+
42
+ 113 algorithms (unchanged). 331 tests pass (6 new), ruff clean,
43
+ mkdocs strict OK, twine strict OK.
44
+
45
+ ## [0.4.0] — 2026-06-26
46
+
47
+ A scientific catalogue expansion driven by a downstream
48
+ dashboard's needs (STAROS, see `stuff/spectro_kernel_spec_25_06_26.md`).
49
+ Adds **8 new algorithms across 3 new categories** (`nebular`,
50
+ `kinematics`, `classification`), extends `detect_lines` with a
51
+ blind mode (no catalogue identification), and factors a shared
52
+ Gaussian-line-flux primitive used by the nebular diagnostics.
53
+
54
+ Each new brick cites the literature paper that defines its formula
55
+ (no fabricated references) and reproduces the published coefficients
56
+ verbatim in code.
57
+
58
+ ### Added — nebular diagnostics (3 algorithms)
59
+
60
+ - **`oiii_electron_temperature`** (category `nebular`) — electron
61
+ temperature Te from the [O III] λ4363/λ5007 auroral-to-nebular
62
+ ratio. Implements the 5-level inversion of **Aller 1984**
63
+ (*Physics of Thermal Gaseous Nebulae*), as derived in
64
+ **Osterbrock & Ferland 2006** §5.6 :
65
+ ``Te = 32900 / ln(R / 7.90)``. Valid for ``n_e ≲ 10⁵ cm⁻³`` and
66
+ ``Te ∈ [3000, 30000] K`` ; outside that range the algorithm reports
67
+ ``te_kelvin = NaN`` and tags the regime in ``extras["regime"]``.
68
+ - **`sii_electron_density`** (category `nebular`) — electron density
69
+ n_e from the [S II] λ6716/λ6731 ratio. Uses the polynomial-plus-arctan
70
+ fit of **Proxauf, Öttl & Kimeswenger 2014** (A&A 561, A10, Eq. 6,
71
+ coefficients copied verbatim) at Te ≈ 10⁴ K for
72
+ ``0.45 ≲ R ≲ 1.43`` ; saturates cleanly at the low- and
73
+ high-density limits with explicit regime labelling.
74
+ - **`bpt_line_ratios`** (category `nebular`) — measures the 4
75
+ standard BPT log-ratios and classifies a galaxy as
76
+ ``HII / Composite / Seyfert / LINER`` in the [N II]/Hα plane.
77
+ Demarcations from **Kauffmann et al. 2003** (MNRAS 346, 1055),
78
+ **Kewley & Dopita 2001** (ApJ 556, 121) and **Cid Fernandes
79
+ et al. 2010** (MNRAS 403, 1036) ; origin paper **Baldwin, Phillips
80
+ & Terlevich 1981** (PASP 93, 5).
81
+
82
+ ### Added — binary / variable diagnostics (2 algorithms)
83
+
84
+ - **`vr_ratio`** (category `line_fitting`) — Violet/Red intensity
85
+ ratio of a double-peaked emission line, the standard asymmetry
86
+ diagnostic for Be-star circumstellar discs (Hα by default) and
87
+ any other source with a resolved double profile (symbiotic stars,
88
+ novae, AGN BLR). Theory grounding : **Okazaki 1991** (A&A 246,
89
+ 415, one-armed density waves), **Hummel & Vrancken 2000** (A&A
90
+ 359, 1075). Detection via ``scipy.signal.find_peaks`` on the
91
+ edge-continuum-normalised window.
92
+ - **`doppler_tomogram`** (category `advanced`) — Doppler tomography
93
+ of a binary system by **Marsh & Horne 1988** (MNRAS 235, 269) §2
94
+ back-projection. Reconstructs the brightness distribution
95
+ ``I(V_x, V_y)`` from N phase-resolved spectra of a known-period
96
+ binary. NaN-aware averaging of the per-phase contributions keeps
97
+ the phase-coverage footprint out of the brightness map.
98
+ Complements the existing ``disentangle_sb2`` ; MEM (Horne 1985)
99
+ is left to a future v2.
100
+
101
+ ### Added — extragalactic kinematics (2 algorithms)
102
+
103
+ - **`redshift_lines`** (category `radial_velocity`) — redshift z by
104
+ per-line Gaussian fits against a list of anchor lines (SDSS
105
+ spec1d style, **Stoughton et al. 2002** AJ 123, 485) ; default
106
+ list = 9 strong galaxy lines ([O II], Ca II H+K, Hβ, [O III],
107
+ Mg b, Na D, Hα, [N II]). Tries emission + absorption fits and
108
+ keeps the lower residual, so the same brick covers
109
+ emission-line galaxies and quiescent absorption-line systems.
110
+ - **`rotation_curve`** (category **kinematics**, new) — projected
111
+ long-slit rotation curve ``v_los(r)`` from per-spectrum Hα fits
112
+ indexed by slit offset (arcsec from galactic centre). Origin :
113
+ **Rubin et al. 1980** (ApJ 238, 471), reviewed in **Sofue &
114
+ Rubin 2001** (ARA&A 39, 137). Simple projection only — no
115
+ inclination correction, no asymmetric drift, no 2-D modelling.
116
+
117
+ ### Added — spectral classification (1 algorithm + atlas loader)
118
+
119
+ - **`classify_template_chi2`** (category **classification**, new) —
120
+ spectral type by unweighted χ² against a Pickles-style template
121
+ atlas (**Pickles 1998**, PASP 110, 863). Returns top-N matches
122
+ plus confidence flags (``ambiguous`` = χ²[1]/χ²[0] < 1.20 ;
123
+ ``teff_unstable`` = top-3 ``ΔT_eff`` > 500 K). The atlas itself
124
+ is **not bundled** (≈ 1 MB of data, redistribution OK with
125
+ attribution but kept out of the thin core install) ; a loader
126
+ helper :mod:`spectro_kernel.algorithms.classification.pickles_atlas`
127
+ reads the downloadable ASCII templates from ESO
128
+ (``ftp.eso.org/pub/stecf/standards/hststan/uvklib/``) or VizieR
129
+ (``J/PASP/110/863/``) into the algorithm's expected
130
+ :class:`Atlas` shape.
131
+
132
+ ### Added — blind line detection (extension)
133
+
134
+ - **`detect_lines`** v1.1.0 → **v1.2.0**: a new ``catalog=None``
135
+ mode runs a blind detection (no catalogue identification) and
136
+ emits a richer schema per line (continuum, signed amplitude, SNR,
137
+ FWHM, prominence) plus the MAD-robust noise level
138
+ (``1.4826 · MAD``, Hampel 1974) used for the prominence
139
+ threshold. The v1.1.0 catalogue mode is preserved bit-exact.
140
+
141
+ ### Added — shared primitive
142
+
143
+ - **`spectro_kernel.algorithms.lines._line_flux.gaussian_line_flux`** —
144
+ factored "Gaussian + local continuum" fit used by the three
145
+ nebular bricks. Window-edge continuum (median of the two outer
146
+ bands), ``curve_fit`` with seeds, analytic flux
147
+ ``F = |amp| · |σ| · √(2π)``. Returns ``None`` on bad fits so
148
+ callers can degrade gracefully.
149
+
150
+ ### Added — new categories
151
+
152
+ `AlgorithmCategory` gains `NEBULAR`, `KINEMATICS` and
153
+ `CLASSIFICATION` for the new specialised domains.
154
+ ``docs/gen_catalogue.py`` gets matching titles so the catalogue
155
+ page renders them as *Nebular diagnostics*, *Kinematics* and
156
+ *Spectral classification*.
157
+
158
+ ### Numbers
159
+
160
+ **113 algorithms** registered (was 105). 325 tests pass (32 new),
161
+ ruff clean, mkdocs strict OK.
162
+
9
163
  ## [0.3.0] — 2026-06-09
10
164
 
11
165
  A focused expansion driven by the LineMill stellar-spectroscopy work
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spectro-kernel
3
- Version: 0.3.0
3
+ Version: 0.4.1
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/
@@ -30,6 +30,9 @@ _CATEGORY_TITLES = {
30
30
  "timeseries": "Time series",
31
31
  "stacking": "Stacking",
32
32
  "embedding": "Embeddings",
33
+ "nebular": "Nebular diagnostics",
34
+ "kinematics": "Kinematics",
35
+ "classification": "Spectral classification",
33
36
  "catalog": "External catalogues",
34
37
  "visualization": "Visualisation",
35
38
  "export": "Export",
@@ -0,0 +1,311 @@
1
+ """Algorithm: Doppler tomography by back-projection (Marsh & Horne 1988).
2
+
3
+ Given N phase-resolved spectra of a known-period binary system, this
4
+ brick reconstructs the **brightness distribution in the velocity space
5
+ of the binary** ``I(V_x, V_y)`` by simple **back-projection** — the
6
+ direct inversion of the trailed-spectrogram → velocity-map mapping
7
+ established by Marsh & Horne 1988.
8
+
9
+ Mathematical model (§2 of Marsh & Horne 1988) :
10
+
11
+ For each spectrum taken at orbital phase ``φ ∈ [0, 1)``, the line
12
+ profile records the integral of ``I(V_x, V_y)`` along all lines of
13
+ sight projected onto the radial velocity
14
+
15
+ v(φ; V_x, V_y) = γ − V_x · sin(2π φ) + V_y · cos(2π φ) (Eq. 1)
16
+
17
+ where ``γ`` is the binary's systemic velocity. The back-projection
18
+ estimator inverts Eq. 1 by smearing each phase-spectrum along its own
19
+ sinusoidal locus in the ``(V_x, V_y)`` plane :
20
+
21
+ M(V_x, V_y) ≈ ⟨ I_φ(γ − V_x · sin 2πφ + V_y · cos 2πφ) ⟩_φ (Eq. 2)
22
+
23
+ where ``⟨·⟩_φ`` averages over phases that *contribute* to each
24
+ ``(V_x, V_y)`` pixel (NaN-aware mean — pixels with no contributing
25
+ phase are reported as ``0``, not ``NaN/0``).
26
+
27
+ Back-projection is the simplest tomographic reconstruction; **Maximum
28
+ Entropy Method (MEM)** (Horne 1985) is a more advanced alternative
29
+ that suppresses the streak artefacts of pure back-projection. MEM is
30
+ intentionally left out of v1.0.0 to keep the surface tight.
31
+
32
+ Phase computation : ``φ = ((JD_mid − epoch_HJD) / period) mod 1``.
33
+ Each input ``Spectrum1D`` must carry its **mid-exposure JD** in
34
+ ``spec.meta["jd_mid"]`` ; if absent, the algorithm falls back to
35
+ deriving it from ``spec.meta["dateobs"]`` and ``spec.meta["exptime_s"]``
36
+ (``jd_mid = Time(dateobs).jd + exptime_s / 2 / 86400``), which requires
37
+ ``astropy.time`` (imported lazily).
38
+
39
+ References
40
+ ----------
41
+ - **Marsh & Horne 1988**, MNRAS 235, 269 — *Images of accretion discs.
42
+ II. Doppler tomography* (the canonical back-projection paper).
43
+ - **Horne 1985**, MNRAS 213, 129 — MEM tomographic reconstruction
44
+ (not implemented; cited for completeness).
45
+ - **Marsh 2001**, *Astrotomography*, Lecture Notes in Physics 573 —
46
+ modern review (back-projection vs MEM vs χ² + regularised MEM).
47
+ """
48
+
49
+ from __future__ import annotations
50
+
51
+ import math
52
+ from typing import Any
53
+
54
+ import numpy as np
55
+
56
+ from ...base import AlgorithmOutput, BaseAlgorithm
57
+ from ...errors import InvalidParameterError
58
+ from ...registry import register_algorithm
59
+ from ...types import AlgorithmCategory, Spectrum1D, WorkContext
60
+
61
+ _SPEED_OF_LIGHT_KMS: float = 299792.458
62
+
63
+
64
+ def _resolve_jd_mid(spec: Spectrum1D) -> float | None:
65
+ """Extract or derive the mid-exposure JD from the spectrum's meta."""
66
+ if spec.meta is None:
67
+ return None
68
+ jd_mid = spec.meta.get("jd_mid")
69
+ if jd_mid is not None:
70
+ return float(jd_mid)
71
+ dateobs = spec.meta.get("dateobs")
72
+ exptime = spec.meta.get("exptime_s")
73
+ if dateobs is None or exptime is None:
74
+ return None
75
+ try:
76
+ from astropy.time import Time # noqa: PLC0415 - astropy is heavy
77
+ except ImportError:
78
+ return None
79
+ try:
80
+ return float(Time(dateobs).jd + float(exptime) / 2.0 / 86400.0)
81
+ except Exception: # noqa: BLE001 - astropy raises a variety of types
82
+ return None
83
+
84
+
85
+ def _line_excess_on_velocity_grid(
86
+ spec: Spectrum1D, line_centre_aa: float, velocity_grid: np.ndarray,
87
+ ) -> np.ndarray | None:
88
+ """Resample the spectrum onto a velocity grid and subtract the continuum.
89
+
90
+ Returns ``intensity − 1`` (the line excess above unit continuum) or
91
+ ``None`` if the input is too short / continuum non-positive.
92
+ """
93
+ wave = np.asarray(spec.wavelength, dtype=np.float64)
94
+ flux = np.asarray(spec.flux, dtype=np.float64)
95
+ finite = np.isfinite(wave) & np.isfinite(flux)
96
+ if int(finite.sum()) < 30:
97
+ return None
98
+ wave = wave[finite]
99
+ flux = flux[finite]
100
+ velocity = _SPEED_OF_LIGHT_KMS * (wave - line_centre_aa) / line_centre_aa
101
+ intensity = np.interp(velocity_grid, velocity, flux, left=np.nan, right=np.nan)
102
+ n_edge = max(5, int(0.1 * intensity.size))
103
+ finite_int = np.isfinite(intensity)
104
+ if int(finite_int.sum()) < n_edge * 2:
105
+ return None
106
+ head_edge = intensity[finite_int][:n_edge]
107
+ tail_edge = intensity[finite_int][-n_edge:]
108
+ continuum = 0.5 * (float(np.median(head_edge)) + float(np.median(tail_edge)))
109
+ if continuum <= 0.0:
110
+ return None
111
+ return intensity / continuum - 1.0
112
+
113
+
114
+ def _back_project(
115
+ trailed: np.ndarray, # (n_phases, n_velocity)
116
+ phases: np.ndarray, # (n_phases,)
117
+ velocity_grid: np.ndarray, # (n_velocity,)
118
+ gamma_kms: float,
119
+ n_velocity: int,
120
+ ) -> np.ndarray:
121
+ """Back-project the trailed spectrogram into the (V_x, V_y) plane."""
122
+ VX, VY = np.meshgrid(velocity_grid, velocity_grid, indexing="xy")
123
+ accumulator = np.zeros_like(VX, dtype=np.float64)
124
+ n_contrib = np.zeros_like(VX, dtype=np.float64)
125
+
126
+ for phase, row in zip(phases, trailed, strict=True):
127
+ phi = 2.0 * math.pi * float(phase)
128
+ v_proj = gamma_kms - VX * math.sin(phi) + VY * math.cos(phi)
129
+ # Resample the row onto v_proj; pixels with v_proj outside the
130
+ # velocity grid get NaN and are masked out of the average.
131
+ flat = np.interp(
132
+ v_proj.ravel(),
133
+ velocity_grid,
134
+ row,
135
+ left=np.nan,
136
+ right=np.nan,
137
+ ).reshape(VX.shape)
138
+ valid = np.isfinite(flat)
139
+ accumulator[valid] += flat[valid]
140
+ n_contrib[valid] += 1.0
141
+
142
+ with np.errstate(invalid="ignore"):
143
+ out = np.where(n_contrib > 0.0, accumulator / n_contrib, 0.0)
144
+ return out
145
+
146
+
147
+ @register_algorithm(
148
+ "doppler_tomogram",
149
+ category=AlgorithmCategory.ADVANCED,
150
+ version="1.0.0",
151
+ )
152
+ class DopplerTomogram(BaseAlgorithm):
153
+ """Doppler tomogram of a binary from N phase-resolved spectra.
154
+
155
+ Composes ``ctx.spectra`` (N ``Spectrum1D``, one per orbital phase)
156
+ into the ``(V_x, V_y)`` brightness map ``M`` by Marsh & Horne 1988
157
+ back-projection. Each input spectrum **must** carry its
158
+ mid-exposure timestamp in one of :
159
+
160
+ - ``spec.meta["jd_mid"]`` (preferred, no astropy dependency), OR
161
+ - ``spec.meta["dateobs"]`` + ``spec.meta["exptime_s"]`` (computed
162
+ with ``astropy.time.Time``).
163
+
164
+ Sits next to ``disentangle_sb2`` in the ``advanced`` category :
165
+ they share the binary-system focus but solve different problems —
166
+ disentangling produces two 1-D component spectra ; tomography
167
+ produces one 2-D velocity-space brightness map.
168
+
169
+ Outputs (under ``ctx.extras["tomography_result"]``) :
170
+
171
+ - ``velocity_grid_kms`` : (n_velocity,) symmetric grid.
172
+ - ``phase_grid`` : (n_spectra,) sorted phases.
173
+ - ``trailed_spectrogram`` : (n_phases, n_velocity) input intensity.
174
+ - ``doppler_map`` : (n_velocity, n_velocity) reconstructed M.
175
+
176
+ Companion bricks one could build later : ``doppler_tomogram_mem``
177
+ (Horne 1985) for sharper reconstructions, and a ``trailed_spectrum_view``
178
+ visualisation.
179
+ """
180
+
181
+ backend = "numpy"
182
+ references = [
183
+ "Marsh & Horne 1988, MNRAS 235, 269 — Doppler tomography by "
184
+ "back-projection (§2).",
185
+ "Horne 1985, MNRAS 213, 129 — MEM tomographic reconstruction "
186
+ "(not implemented; cited for completeness).",
187
+ "Marsh 2001, Lecture Notes in Physics 573 — astrotomography review.",
188
+ ]
189
+ long_description = (
190
+ "Back-projection of N phase-resolved spectra onto the (V_x, V_y) "
191
+ "plane. Pixels are averaged over the phases that contribute (NaN-"
192
+ "aware mean) so the phase-coverage footprint does not bleed into "
193
+ "the brightness map. JD_mid is read from each spectrum's "
194
+ "meta['jd_mid'], falling back to meta['dateobs'] + "
195
+ "meta['exptime_s'] via astropy.time.Time."
196
+ )
197
+ default_params: dict[str, Any] = {
198
+ "period_days": None,
199
+ "epoch_hjd": None,
200
+ "gamma_kms": 0.0,
201
+ "line_center_aa": 6562.82,
202
+ "velocity_window_kms": 1000.0,
203
+ "n_velocity": 121,
204
+ }
205
+ required_params = ["period_days", "epoch_hjd"]
206
+ param_descriptions = {
207
+ "period_days": "Orbital period of the binary (days).",
208
+ "epoch_hjd": "Reference epoch HJD of phase 0 (days).",
209
+ "gamma_kms": "Systemic velocity (km/s) added to the back-projection.",
210
+ "line_center_aa": "Rest wavelength (Å) of the line to tomogram.",
211
+ "velocity_window_kms": "Half-extent (km/s) of the V_x/V_y axes.",
212
+ "n_velocity": "Number of velocity samples per axis (odd, 11–401).",
213
+ }
214
+ input_requirements: list[str] = [] # validated dynamically
215
+ output_produces = ["extras.tomography_result", "metrics.n_spectra"]
216
+
217
+ def run(self, ctx: WorkContext, params: dict[str, Any]) -> AlgorithmOutput:
218
+ spectra = list(ctx.spectra or [])
219
+ if len(spectra) < 3:
220
+ return AlgorithmOutput.fail(
221
+ f"Need ≥ 3 spectra in ctx.spectra; got {len(spectra)}."
222
+ )
223
+
224
+ period = float(params["period_days"])
225
+ if period <= 0.0:
226
+ raise InvalidParameterError("period_days must be > 0.")
227
+ epoch_hjd = float(params["epoch_hjd"])
228
+ gamma_kms = float(params["gamma_kms"])
229
+ line_centre = float(params["line_center_aa"])
230
+ v_window = float(params["velocity_window_kms"])
231
+ if v_window <= 0.0:
232
+ raise InvalidParameterError("velocity_window_kms must be > 0.")
233
+ n_velocity = int(params["n_velocity"])
234
+ if not 11 <= n_velocity <= 401:
235
+ raise InvalidParameterError("n_velocity must be in [11, 401].")
236
+
237
+ velocity_grid = np.linspace(-v_window, +v_window, n_velocity)
238
+
239
+ rows: list[tuple[float, np.ndarray]] = []
240
+ skipped_no_jd = 0
241
+ skipped_bad_spec = 0
242
+ for spec in spectra:
243
+ jd_mid = _resolve_jd_mid(spec)
244
+ if jd_mid is None:
245
+ skipped_no_jd += 1
246
+ continue
247
+ excess = _line_excess_on_velocity_grid(spec, line_centre, velocity_grid)
248
+ if excess is None:
249
+ skipped_bad_spec += 1
250
+ continue
251
+ phase = ((jd_mid - epoch_hjd) / period) % 1.0
252
+ rows.append((float(phase), excess))
253
+
254
+ if len(rows) < 3:
255
+ return AlgorithmOutput.fail(
256
+ f"Only {len(rows)} usable spectra (need ≥ 3) — skipped "
257
+ f"{skipped_no_jd} for missing JD_mid and "
258
+ f"{skipped_bad_spec} for failed continuum / interpolation."
259
+ )
260
+
261
+ rows.sort(key=lambda row: row[0])
262
+ phases = np.asarray([row[0] for row in rows], dtype=np.float64)
263
+ trailed = np.stack([row[1] for row in rows], axis=0)
264
+ # NaN-aware: replace residual NaNs from np.interp by 0 before
265
+ # accumulation (they don't contribute, and "no data" ≠ "0 signal").
266
+ trailed_clean = np.where(np.isfinite(trailed), trailed, 0.0)
267
+ doppler_map = _back_project(
268
+ trailed_clean, phases, velocity_grid, gamma_kms, n_velocity,
269
+ )
270
+
271
+ result = {
272
+ "velocity_grid_kms": velocity_grid,
273
+ "phase_grid": phases,
274
+ "trailed_spectrogram": trailed,
275
+ "doppler_map": doppler_map,
276
+ "vx_axis_kms": velocity_grid,
277
+ "vy_axis_kms": velocity_grid,
278
+ "n_spectra": len(rows),
279
+ "n_skipped_no_jd": skipped_no_jd,
280
+ "n_skipped_bad_spec": skipped_bad_spec,
281
+ "period_days": period,
282
+ "epoch_hjd": epoch_hjd,
283
+ "gamma_kms": gamma_kms,
284
+ "line_center_aa": line_centre,
285
+ }
286
+ ctx.extras["tomography_result"] = result
287
+ ctx.metrics["n_spectra"] = float(len(rows))
288
+ ctx.metrics["period_days"] = period
289
+ ctx.metrics["epoch_hjd"] = epoch_hjd
290
+ ctx.metrics["gamma_kms"] = gamma_kms
291
+ ctx.metrics["line_center_aa"] = line_centre
292
+ return AlgorithmOutput.ok(
293
+ metrics={
294
+ "n_spectra": float(len(rows)),
295
+ "n_skipped_no_jd": float(skipped_no_jd),
296
+ "n_skipped_bad_spec": float(skipped_bad_spec),
297
+ "period_days": period,
298
+ "epoch_hjd": epoch_hjd,
299
+ "gamma_kms": gamma_kms,
300
+ "line_center_aa": line_centre,
301
+ },
302
+ artifacts={"tomography_result": result},
303
+ message=(
304
+ f"Doppler tomogram: {len(rows)} phase(s), "
305
+ f"V ∈ ±{v_window:.0f} km/s on {n_velocity}×{n_velocity} grid; "
306
+ f"peak |M| = {float(np.max(np.abs(doppler_map))):.3g}."
307
+ ),
308
+ )
309
+
310
+
311
+ __all__ = ["DopplerTomogram"]