spectro-kernel 0.3.0__tar.gz → 0.4.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 (297) hide show
  1. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/CHANGELOG.md +118 -0
  2. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/PKG-INFO +1 -1
  3. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/gen_catalogue.py +3 -0
  4. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/advanced/doppler_tomogram.py +311 -0
  5. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/classification/classify_template_chi2.py +292 -0
  6. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/classification/pickles_atlas.py +151 -0
  7. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/kinematics/rotation_curve.py +241 -0
  8. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/lines/_line_flux.py +152 -0
  9. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/lines/detect.py +348 -0
  10. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/lines/vr_ratio.py +287 -0
  11. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/nebular/__init__.py +0 -0
  12. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/nebular/bpt_line_ratios.py +242 -0
  13. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/nebular/oiii_electron_temperature.py +218 -0
  14. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/nebular/sii_electron_density.py +222 -0
  15. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/rv/redshift_lines.py +239 -0
  16. spectro_kernel-0.4.0/src/spectro_kernel/py.typed +0 -0
  17. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/types/enums.py +5 -0
  18. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/version.py +1 -1
  19. spectro_kernel-0.4.0/src/spectro_mcp/py.typed +0 -0
  20. spectro_kernel-0.4.0/tests/unit/test_classify_template_chi2.py +108 -0
  21. spectro_kernel-0.4.0/tests/unit/test_detect_lines_blind.py +119 -0
  22. spectro_kernel-0.4.0/tests/unit/test_doppler_tomogram.py +79 -0
  23. spectro_kernel-0.4.0/tests/unit/test_nebular_diagnostics.py +167 -0
  24. spectro_kernel-0.4.0/tests/unit/test_redshift_lines.py +50 -0
  25. spectro_kernel-0.4.0/tests/unit/test_rotation_curve.py +68 -0
  26. spectro_kernel-0.4.0/tests/unit/test_vr_ratio.py +60 -0
  27. spectro_kernel-0.3.0/src/spectro_kernel/algorithms/lines/detect.py +0 -157
  28. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/.gitignore +0 -0
  29. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/LICENSE +0 -0
  30. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/README.md +0 -0
  31. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/concepts/algorithms.md +0 -0
  32. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/concepts/architecture.md +0 -0
  33. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/concepts/data-types.md +0 -0
  34. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/concepts/pipelines.md +0 -0
  35. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/contributing.md +0 -0
  36. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/cookbook/bess-dashboard.md +0 -0
  37. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/cookbook/multi-star-viewer.md +0 -0
  38. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/cookbook/web-playground.md +0 -0
  39. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/getting-started.md +0 -0
  40. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_ew_timeseries.png +0 -0
  41. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_halpha_phase_stack.png +0 -0
  42. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_halpha_timeseries.png +0 -0
  43. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_overlay.png +0 -0
  44. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_pc1_vs_phase.png +0 -0
  45. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_pca_2d.png +0 -0
  46. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_pca_3d.png +0 -0
  47. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_periodogram.png +0 -0
  48. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_phase_folded.png +0 -0
  49. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_rv_timeseries.png +0 -0
  50. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_similarity.png +0 -0
  51. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_similarity_date.png +0 -0
  52. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_halpha_phase_stack.png +0 -0
  53. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_pc1_vs_phase.png +0 -0
  54. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_pca_2d.png +0 -0
  55. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_pca_3d.png +0 -0
  56. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_periodogram.png +0 -0
  57. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_phase_folded.png +0 -0
  58. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_rv_timeseries.png +0 -0
  59. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_similarity_date.png +0 -0
  60. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_similarity_phase.png +0 -0
  61. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/index.md +0 -0
  62. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/notebooks/alpha-cyg-time-series.md +0 -0
  63. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/notebooks/alpha-dra-binary-period.md +0 -0
  64. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/notebooks/aurora-line-monitor.md +0 -0
  65. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/notebooks/be-star-variability.md +0 -0
  66. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/notebooks/claude-mcp-end-to-end.md +0 -0
  67. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/notebooks/exoplanet-transit-rv.md +0 -0
  68. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/notebooks/full-reduction-walkthrough.md +0 -0
  69. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/notebooks/native-vs-easyspec-showdown.md +0 -0
  70. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/notebooks/your-first-sb2.md +0 -0
  71. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/reference.md +0 -0
  72. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/tutorials/add-an-algorithm.md +0 -0
  73. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/tutorials/analyse-a-spectrum.md +0 -0
  74. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/tutorials/discover-the-catalogue.md +0 -0
  75. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/tutorials/first-spectrum.md +0 -0
  76. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/tutorials/long-slit-reduction.md +0 -0
  77. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/usage/cli.md +0 -0
  78. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/usage/library.md +0 -0
  79. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/usage/mcp.md +0 -0
  80. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/docs/why.md +0 -0
  81. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/playground/README.md +0 -0
  82. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/pyproject.toml +0 -0
  83. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/__init__.py +0 -0
  84. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/adapters/__init__.py +0 -0
  85. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/adapters/easyspec.py +0 -0
  86. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/__init__.py +0 -0
  87. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/_common.py +0 -0
  88. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/advanced/__init__.py +0 -0
  89. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/advanced/aperture_photometry.py +0 -0
  90. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/advanced/disentangle_sb2.py +0 -0
  91. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/catalogs/__init__.py +0 -0
  92. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/catalogs/gaia.py +0 -0
  93. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/catalogs/simbad.py +0 -0
  94. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/catalogs/vizier.py +0 -0
  95. {spectro_kernel-0.3.0/src/spectro_kernel/algorithms/embedding → spectro_kernel-0.4.0/src/spectro_kernel/algorithms/classification}/__init__.py +0 -0
  96. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/__init__.py +0 -0
  97. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/compare_normalisations.py +0 -0
  98. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_edges.py +0 -0
  99. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_max.py +0 -0
  100. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_percentile.py +0 -0
  101. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_polynomial.py +0 -0
  102. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_region.py +0 -0
  103. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/subtract_continuum.py +0 -0
  104. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/__init__.py +0 -0
  105. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/air_vacuum.py +0 -0
  106. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/barycentric.py +0 -0
  107. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/doppler_shift.py +0 -0
  108. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/extinction_correct_easyspec.py +0 -0
  109. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/fit_telluric_scaling.py +0 -0
  110. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/flux_calibrate_easyspec.py +0 -0
  111. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/remove_telluric.py +0 -0
  112. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/response_from_standard.py +0 -0
  113. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/synth_telluric.py +0 -0
  114. /spectro_kernel-0.3.0/src/spectro_kernel/py.typed → /spectro_kernel-0.4.0/src/spectro_kernel/algorithms/embedding/__init__.py +0 -0
  115. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_band_power.py +0 -0
  116. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_continuum_subtracted.py +0 -0
  117. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_lick_indices.py +0 -0
  118. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_log_lambda.py +0 -0
  119. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_pretrained.py +0 -0
  120. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_remote.py +0 -0
  121. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_spectrum.py +0 -0
  122. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_wavelets.py +0 -0
  123. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/__init__.py +0 -0
  124. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_csv.py +0 -0
  125. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_fits.py +0 -0
  126. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_fits_bess.py +0 -0
  127. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_hdf5.py +0 -0
  128. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_votable.py +0 -0
  129. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/__init__.py +0 -0
  130. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/boxcar.py +0 -0
  131. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/detect_trace.py +0 -0
  132. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/easyspec_extract.py +0 -0
  133. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/optimal.py +0 -0
  134. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/sky_lateral_bands.py +0 -0
  135. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/__init__.py +0 -0
  136. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_ascii.py +0 -0
  137. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_echelle.py +0 -0
  138. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_fits.py +0 -0
  139. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_sdss.py +0 -0
  140. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_votable.py +0 -0
  141. /spectro_kernel-0.3.0/src/spectro_mcp/py.typed → /spectro_kernel-0.4.0/src/spectro_kernel/algorithms/kinematics/__init__.py +0 -0
  142. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/__init__.py +0 -0
  143. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/_profiles.py +0 -0
  144. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/catalogs.py +0 -0
  145. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/compare_line_fits.py +0 -0
  146. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/equivalent_width.py +0 -0
  147. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/fit_gaussian.py +0 -0
  148. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/fit_lorentzian.py +0 -0
  149. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/fit_voigt.py +0 -0
  150. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/__init__.py +0 -0
  151. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/compare_snr_methods.py +0 -0
  152. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/snr_der.py +0 -0
  153. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/snr_edge.py +0 -0
  154. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/snr_linear_fit.py +0 -0
  155. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/__init__.py +0 -0
  156. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/_easyspec_apply.py +0 -0
  157. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/_easyspec_helpers.py +0 -0
  158. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/bias_combine.py +0 -0
  159. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/clip_cosmic_rays.py +0 -0
  160. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/dark_combine.py +0 -0
  161. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/dark_subtract.py +0 -0
  162. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/denoise_2d.py +0 -0
  163. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +0 -0
  164. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +0 -0
  165. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +0 -0
  166. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +0 -0
  167. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +0 -0
  168. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +0 -0
  169. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +0 -0
  170. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/extract_spectrum_sum.py +0 -0
  171. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/flat_combine.py +0 -0
  172. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/flat_normalize.py +0 -0
  173. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/geometry.py +0 -0
  174. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/subtract_sky_2d.py +0 -0
  175. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +0 -0
  176. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/__init__.py +0 -0
  177. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/cross_correlate.py +0 -0
  178. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/fit_keplerian_orbit.py +0 -0
  179. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/measure.py +0 -0
  180. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/precision_bouchy.py +0 -0
  181. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/smoothing/__init__.py +0 -0
  182. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/smoothing/compare_smoothings.py +0 -0
  183. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/smoothing/smooth_gaussian.py +0 -0
  184. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/smoothing/smooth_savgol.py +0 -0
  185. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/stacking/__init__.py +0 -0
  186. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/stacking/merge_echelle_orders.py +0 -0
  187. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/stacking/stack.py +0 -0
  188. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/timeseries/__init__.py +0 -0
  189. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/timeseries/lomb_scargle.py +0 -0
  190. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/timeseries/phase_fold.py +0 -0
  191. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/__init__.py +0 -0
  192. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/clip_sigma.py +0 -0
  193. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/combine_arithmetic.py +0 -0
  194. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/extract_region.py +0 -0
  195. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/mask_range.py +0 -0
  196. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/resample.py +0 -0
  197. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/resample_flux_conserving.py +0 -0
  198. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/__init__.py +0 -0
  199. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/plot_3d_surface.py +0 -0
  200. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/plot_animation.py +0 -0
  201. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/plot_dynamic_spectrum.py +0 -0
  202. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/plot_plotly.py +0 -0
  203. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/__init__.py +0 -0
  204. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/arc_geometry.py +0 -0
  205. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/easyspec_wavelength.py +0 -0
  206. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/in_situ.py +0 -0
  207. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/lamp_atlas.py +0 -0
  208. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/match_lamp.py +0 -0
  209. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/solar.py +0 -0
  210. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/base.py +0 -0
  211. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/cli.py +0 -0
  212. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/embeddings.py +0 -0
  213. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/errors.py +0 -0
  214. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/io/__init__.py +0 -0
  215. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/io/ascii.py +0 -0
  216. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/io/fits.py +0 -0
  217. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/io/votable.py +0 -0
  218. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/pipeline.py +0 -0
  219. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/__init__.py +0 -0
  220. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/balmer_quick.yaml +0 -0
  221. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/embed_quick.yaml +0 -0
  222. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/quality_report.yaml +0 -0
  223. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/rv_quick.yaml +0 -0
  224. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/snr_check.yaml +0 -0
  225. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/time_series_overview.yaml +0 -0
  226. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/reduction/full_reduction_easyspec.yaml +0 -0
  227. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/loader.py +0 -0
  228. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/registry.py +0 -0
  229. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/types/__init__.py +0 -0
  230. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/types/catalog.py +0 -0
  231. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/types/context.py +0 -0
  232. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/types/history.py +0 -0
  233. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/types/image.py +0 -0
  234. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/types/line.py +0 -0
  235. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/types/spectrum.py +0 -0
  236. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_kernel/types/timeseries.py +0 -0
  237. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_mcp/__init__.py +0 -0
  238. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_mcp/__main__.py +0 -0
  239. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_mcp/auth.py +0 -0
  240. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_mcp/auto_tools.py +0 -0
  241. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_mcp/observability.py +0 -0
  242. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_mcp/server.py +0 -0
  243. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_mcp/session.py +0 -0
  244. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/src/spectro_mcp/url_safety.py +0 -0
  245. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/conftest.py +0 -0
  246. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/reference/conftest.py +0 -0
  247. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/reference/data/.gitkeep +0 -0
  248. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/reference/data/README.md +0 -0
  249. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/reference/data/sun/sun_reference_stis_002.fits +0 -0
  250. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/reference/data/vega/alpha_lyr_stis_011.fits +0 -0
  251. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/reference/test_known_answers.py +0 -0
  252. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/reference/test_sun.py +0 -0
  253. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/reference/test_vega.py +0 -0
  254. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_base.py +0 -0
  255. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_cli.py +0 -0
  256. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_combine.py +0 -0
  257. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_combine_arithmetic.py +0 -0
  258. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_continuum.py +0 -0
  259. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_corrections.py +0 -0
  260. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_dark_flat_combine.py +0 -0
  261. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_denoise_2d.py +0 -0
  262. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_detect_trace.py +0 -0
  263. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_easyspec_apply_staging.py +0 -0
  264. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_easyspec_wrappers.py +0 -0
  265. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_embedding.py +0 -0
  266. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_embedding_pretrained.py +0 -0
  267. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_embedding_remote_lick.py +0 -0
  268. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_embedding_tier1.py +0 -0
  269. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_export_fits_bess.py +0 -0
  270. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_extract_boxcar.py +0 -0
  271. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_extract_optimal.py +0 -0
  272. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_geometry_corrections.py +0 -0
  273. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_io.py +0 -0
  274. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_lamp_atlas_match.py +0 -0
  275. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_line_profiles.py +0 -0
  276. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_lines.py +0 -0
  277. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_mcp.py +0 -0
  278. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_mcp_production.py +0 -0
  279. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_mcp_security.py +0 -0
  280. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_measure_arc_geometry.py +0 -0
  281. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_misc_algorithms.py +0 -0
  282. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_no_circular_imports.py +0 -0
  283. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_normalize_to_region.py +0 -0
  284. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_pipeline.py +0 -0
  285. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_quality.py +0 -0
  286. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_read_sdss.py +0 -0
  287. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_registry.py +0 -0
  288. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_response_from_standard.py +0 -0
  289. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_sky_lateral_bands.py +0 -0
  290. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_smoothing.py +0 -0
  291. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_timeseries.py +0 -0
  292. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_transforms.py +0 -0
  293. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_types.py +0 -0
  294. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_viz.py +0 -0
  295. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_wavelength_calibration_in_situ.py +0 -0
  296. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/tests/unit/test_wavelength_calibration_solar.py +0 -0
  297. {spectro_kernel-0.3.0 → spectro_kernel-0.4.0}/website/README.md +0 -0
@@ -6,6 +6,124 @@ Until `1.0.0` the public API may change between minor versions.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.0] — 2026-06-26
10
+
11
+ A scientific catalogue expansion driven by a downstream
12
+ dashboard's needs (STAROS, see `stuff/spectro_kernel_spec_25_06_26.md`).
13
+ Adds **8 new algorithms across 3 new categories** (`nebular`,
14
+ `kinematics`, `classification`), extends `detect_lines` with a
15
+ blind mode (no catalogue identification), and factors a shared
16
+ Gaussian-line-flux primitive used by the nebular diagnostics.
17
+
18
+ Each new brick cites the literature paper that defines its formula
19
+ (no fabricated references) and reproduces the published coefficients
20
+ verbatim in code.
21
+
22
+ ### Added — nebular diagnostics (3 algorithms)
23
+
24
+ - **`oiii_electron_temperature`** (category `nebular`) — electron
25
+ temperature Te from the [O III] λ4363/λ5007 auroral-to-nebular
26
+ ratio. Implements the 5-level inversion of **Aller 1984**
27
+ (*Physics of Thermal Gaseous Nebulae*), as derived in
28
+ **Osterbrock & Ferland 2006** §5.6 :
29
+ ``Te = 32900 / ln(R / 7.90)``. Valid for ``n_e ≲ 10⁵ cm⁻³`` and
30
+ ``Te ∈ [3000, 30000] K`` ; outside that range the algorithm reports
31
+ ``te_kelvin = NaN`` and tags the regime in ``extras["regime"]``.
32
+ - **`sii_electron_density`** (category `nebular`) — electron density
33
+ n_e from the [S II] λ6716/λ6731 ratio. Uses the polynomial-plus-arctan
34
+ fit of **Proxauf, Öttl & Kimeswenger 2014** (A&A 561, A10, Eq. 6,
35
+ coefficients copied verbatim) at Te ≈ 10⁴ K for
36
+ ``0.45 ≲ R ≲ 1.43`` ; saturates cleanly at the low- and
37
+ high-density limits with explicit regime labelling.
38
+ - **`bpt_line_ratios`** (category `nebular`) — measures the 4
39
+ standard BPT log-ratios and classifies a galaxy as
40
+ ``HII / Composite / Seyfert / LINER`` in the [N II]/Hα plane.
41
+ Demarcations from **Kauffmann et al. 2003** (MNRAS 346, 1055),
42
+ **Kewley & Dopita 2001** (ApJ 556, 121) and **Cid Fernandes
43
+ et al. 2010** (MNRAS 403, 1036) ; origin paper **Baldwin, Phillips
44
+ & Terlevich 1981** (PASP 93, 5).
45
+
46
+ ### Added — binary / variable diagnostics (2 algorithms)
47
+
48
+ - **`vr_ratio`** (category `line_fitting`) — Violet/Red intensity
49
+ ratio of a double-peaked emission line, the standard asymmetry
50
+ diagnostic for Be-star circumstellar discs (Hα by default) and
51
+ any other source with a resolved double profile (symbiotic stars,
52
+ novae, AGN BLR). Theory grounding : **Okazaki 1991** (A&A 246,
53
+ 415, one-armed density waves), **Hummel & Vrancken 2000** (A&A
54
+ 359, 1075). Detection via ``scipy.signal.find_peaks`` on the
55
+ edge-continuum-normalised window.
56
+ - **`doppler_tomogram`** (category `advanced`) — Doppler tomography
57
+ of a binary system by **Marsh & Horne 1988** (MNRAS 235, 269) §2
58
+ back-projection. Reconstructs the brightness distribution
59
+ ``I(V_x, V_y)`` from N phase-resolved spectra of a known-period
60
+ binary. NaN-aware averaging of the per-phase contributions keeps
61
+ the phase-coverage footprint out of the brightness map.
62
+ Complements the existing ``disentangle_sb2`` ; MEM (Horne 1985)
63
+ is left to a future v2.
64
+
65
+ ### Added — extragalactic kinematics (2 algorithms)
66
+
67
+ - **`redshift_lines`** (category `radial_velocity`) — redshift z by
68
+ per-line Gaussian fits against a list of anchor lines (SDSS
69
+ spec1d style, **Stoughton et al. 2002** AJ 123, 485) ; default
70
+ list = 9 strong galaxy lines ([O II], Ca II H+K, Hβ, [O III],
71
+ Mg b, Na D, Hα, [N II]). Tries emission + absorption fits and
72
+ keeps the lower residual, so the same brick covers
73
+ emission-line galaxies and quiescent absorption-line systems.
74
+ - **`rotation_curve`** (category **kinematics**, new) — projected
75
+ long-slit rotation curve ``v_los(r)`` from per-spectrum Hα fits
76
+ indexed by slit offset (arcsec from galactic centre). Origin :
77
+ **Rubin et al. 1980** (ApJ 238, 471), reviewed in **Sofue &
78
+ Rubin 2001** (ARA&A 39, 137). Simple projection only — no
79
+ inclination correction, no asymmetric drift, no 2-D modelling.
80
+
81
+ ### Added — spectral classification (1 algorithm + atlas loader)
82
+
83
+ - **`classify_template_chi2`** (category **classification**, new) —
84
+ spectral type by unweighted χ² against a Pickles-style template
85
+ atlas (**Pickles 1998**, PASP 110, 863). Returns top-N matches
86
+ plus confidence flags (``ambiguous`` = χ²[1]/χ²[0] < 1.20 ;
87
+ ``teff_unstable`` = top-3 ``ΔT_eff`` > 500 K). The atlas itself
88
+ is **not bundled** (≈ 1 MB of data, redistribution OK with
89
+ attribution but kept out of the thin core install) ; a loader
90
+ helper :mod:`spectro_kernel.algorithms.classification.pickles_atlas`
91
+ reads the downloadable ASCII templates from ESO
92
+ (``ftp.eso.org/pub/stecf/standards/hststan/uvklib/``) or VizieR
93
+ (``J/PASP/110/863/``) into the algorithm's expected
94
+ :class:`Atlas` shape.
95
+
96
+ ### Added — blind line detection (extension)
97
+
98
+ - **`detect_lines`** v1.1.0 → **v1.2.0**: a new ``catalog=None``
99
+ mode runs a blind detection (no catalogue identification) and
100
+ emits a richer schema per line (continuum, signed amplitude, SNR,
101
+ FWHM, prominence) plus the MAD-robust noise level
102
+ (``1.4826 · MAD``, Hampel 1974) used for the prominence
103
+ threshold. The v1.1.0 catalogue mode is preserved bit-exact.
104
+
105
+ ### Added — shared primitive
106
+
107
+ - **`spectro_kernel.algorithms.lines._line_flux.gaussian_line_flux`** —
108
+ factored "Gaussian + local continuum" fit used by the three
109
+ nebular bricks. Window-edge continuum (median of the two outer
110
+ bands), ``curve_fit`` with seeds, analytic flux
111
+ ``F = |amp| · |σ| · √(2π)``. Returns ``None`` on bad fits so
112
+ callers can degrade gracefully.
113
+
114
+ ### Added — new categories
115
+
116
+ `AlgorithmCategory` gains `NEBULAR`, `KINEMATICS` and
117
+ `CLASSIFICATION` for the new specialised domains.
118
+ ``docs/gen_catalogue.py`` gets matching titles so the catalogue
119
+ page renders them as *Nebular diagnostics*, *Kinematics* and
120
+ *Spectral classification*.
121
+
122
+ ### Numbers
123
+
124
+ **113 algorithms** registered (was 105). 325 tests pass (32 new),
125
+ ruff clean, mkdocs strict OK.
126
+
9
127
  ## [0.3.0] — 2026-06-09
10
128
 
11
129
  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.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/
@@ -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"]