spectro-kernel 0.2.1__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.2.1 → spectro_kernel-0.4.0}/CHANGELOG.md +226 -0
  2. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/PKG-INFO +1 -1
  3. {spectro_kernel-0.2.1 → 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/corrections/response_from_standard.py +291 -0
  8. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/boxcar.py +10 -2
  9. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/extraction/detect_trace.py +286 -0
  10. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/extraction/optimal.py +403 -0
  11. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/kinematics/rotation_curve.py +241 -0
  12. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/lines/_line_flux.py +152 -0
  13. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/lines/detect.py +348 -0
  14. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/lines/vr_ratio.py +287 -0
  15. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/nebular/__init__.py +0 -0
  16. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/nebular/bpt_line_ratios.py +242 -0
  17. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/nebular/oiii_electron_temperature.py +218 -0
  18. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/nebular/sii_electron_density.py +222 -0
  19. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/reduction/dark_combine.py +174 -0
  20. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/reduction/flat_combine.py +165 -0
  21. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/rv/redshift_lines.py +239 -0
  22. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/wavelength_calibration/arc_geometry.py +350 -0
  23. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/wavelength_calibration/lamp_atlas.py +170 -0
  24. spectro_kernel-0.4.0/src/spectro_kernel/algorithms/wavelength_calibration/match_lamp.py +488 -0
  25. spectro_kernel-0.4.0/src/spectro_kernel/py.typed +0 -0
  26. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/types/enums.py +5 -0
  27. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/version.py +1 -1
  28. spectro_kernel-0.4.0/src/spectro_mcp/py.typed +0 -0
  29. spectro_kernel-0.4.0/tests/unit/test_classify_template_chi2.py +108 -0
  30. spectro_kernel-0.4.0/tests/unit/test_dark_flat_combine.py +188 -0
  31. spectro_kernel-0.4.0/tests/unit/test_detect_lines_blind.py +119 -0
  32. spectro_kernel-0.4.0/tests/unit/test_detect_trace.py +104 -0
  33. spectro_kernel-0.4.0/tests/unit/test_doppler_tomogram.py +79 -0
  34. spectro_kernel-0.4.0/tests/unit/test_extract_optimal.py +178 -0
  35. spectro_kernel-0.4.0/tests/unit/test_lamp_atlas_match.py +219 -0
  36. spectro_kernel-0.4.0/tests/unit/test_measure_arc_geometry.py +152 -0
  37. spectro_kernel-0.4.0/tests/unit/test_nebular_diagnostics.py +167 -0
  38. spectro_kernel-0.4.0/tests/unit/test_redshift_lines.py +50 -0
  39. spectro_kernel-0.4.0/tests/unit/test_response_from_standard.py +129 -0
  40. spectro_kernel-0.4.0/tests/unit/test_rotation_curve.py +68 -0
  41. spectro_kernel-0.4.0/tests/unit/test_vr_ratio.py +60 -0
  42. spectro_kernel-0.2.1/src/spectro_kernel/algorithms/lines/detect.py +0 -157
  43. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/.gitignore +0 -0
  44. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/LICENSE +0 -0
  45. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/README.md +0 -0
  46. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/concepts/algorithms.md +0 -0
  47. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/concepts/architecture.md +0 -0
  48. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/concepts/data-types.md +0 -0
  49. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/concepts/pipelines.md +0 -0
  50. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/contributing.md +0 -0
  51. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/cookbook/bess-dashboard.md +0 -0
  52. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/cookbook/multi-star-viewer.md +0 -0
  53. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/cookbook/web-playground.md +0 -0
  54. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/getting-started.md +0 -0
  55. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_ew_timeseries.png +0 -0
  56. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_halpha_phase_stack.png +0 -0
  57. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_halpha_timeseries.png +0 -0
  58. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_overlay.png +0 -0
  59. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_pc1_vs_phase.png +0 -0
  60. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_pca_2d.png +0 -0
  61. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_pca_3d.png +0 -0
  62. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_periodogram.png +0 -0
  63. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_phase_folded.png +0 -0
  64. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_rv_timeseries.png +0 -0
  65. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_similarity.png +0 -0
  66. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_cyg_similarity_date.png +0 -0
  67. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_halpha_phase_stack.png +0 -0
  68. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_pc1_vs_phase.png +0 -0
  69. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_pca_2d.png +0 -0
  70. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_pca_3d.png +0 -0
  71. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_periodogram.png +0 -0
  72. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_phase_folded.png +0 -0
  73. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_rv_timeseries.png +0 -0
  74. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_similarity_date.png +0 -0
  75. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/images/notebooks/alpha_dra_similarity_phase.png +0 -0
  76. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/index.md +0 -0
  77. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/notebooks/alpha-cyg-time-series.md +0 -0
  78. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/notebooks/alpha-dra-binary-period.md +0 -0
  79. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/notebooks/aurora-line-monitor.md +0 -0
  80. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/notebooks/be-star-variability.md +0 -0
  81. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/notebooks/claude-mcp-end-to-end.md +0 -0
  82. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/notebooks/exoplanet-transit-rv.md +0 -0
  83. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/notebooks/full-reduction-walkthrough.md +0 -0
  84. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/notebooks/native-vs-easyspec-showdown.md +0 -0
  85. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/notebooks/your-first-sb2.md +0 -0
  86. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/reference.md +0 -0
  87. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/tutorials/add-an-algorithm.md +0 -0
  88. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/tutorials/analyse-a-spectrum.md +0 -0
  89. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/tutorials/discover-the-catalogue.md +0 -0
  90. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/tutorials/first-spectrum.md +0 -0
  91. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/tutorials/long-slit-reduction.md +0 -0
  92. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/usage/cli.md +0 -0
  93. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/usage/library.md +0 -0
  94. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/usage/mcp.md +0 -0
  95. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/docs/why.md +0 -0
  96. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/playground/README.md +0 -0
  97. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/pyproject.toml +0 -0
  98. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/__init__.py +0 -0
  99. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/adapters/__init__.py +0 -0
  100. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/adapters/easyspec.py +0 -0
  101. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/__init__.py +0 -0
  102. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/_common.py +0 -0
  103. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/advanced/__init__.py +0 -0
  104. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/advanced/aperture_photometry.py +0 -0
  105. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/advanced/disentangle_sb2.py +0 -0
  106. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/catalogs/__init__.py +0 -0
  107. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/catalogs/gaia.py +0 -0
  108. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/catalogs/simbad.py +0 -0
  109. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/catalogs/vizier.py +0 -0
  110. {spectro_kernel-0.2.1/src/spectro_kernel/algorithms/embedding → spectro_kernel-0.4.0/src/spectro_kernel/algorithms/classification}/__init__.py +0 -0
  111. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/__init__.py +0 -0
  112. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/compare_normalisations.py +0 -0
  113. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_edges.py +0 -0
  114. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_max.py +0 -0
  115. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_percentile.py +0 -0
  116. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_polynomial.py +0 -0
  117. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/normalize_region.py +0 -0
  118. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/continuum/subtract_continuum.py +0 -0
  119. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/__init__.py +0 -0
  120. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/air_vacuum.py +0 -0
  121. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/barycentric.py +0 -0
  122. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/doppler_shift.py +0 -0
  123. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/extinction_correct_easyspec.py +0 -0
  124. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/fit_telluric_scaling.py +0 -0
  125. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/flux_calibrate_easyspec.py +0 -0
  126. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/remove_telluric.py +0 -0
  127. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/corrections/synth_telluric.py +0 -0
  128. /spectro_kernel-0.2.1/src/spectro_kernel/py.typed → /spectro_kernel-0.4.0/src/spectro_kernel/algorithms/embedding/__init__.py +0 -0
  129. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_band_power.py +0 -0
  130. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_continuum_subtracted.py +0 -0
  131. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_lick_indices.py +0 -0
  132. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_log_lambda.py +0 -0
  133. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_pretrained.py +0 -0
  134. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_remote.py +0 -0
  135. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_spectrum.py +0 -0
  136. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/embedding/embed_wavelets.py +0 -0
  137. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/__init__.py +0 -0
  138. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_csv.py +0 -0
  139. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_fits.py +0 -0
  140. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_fits_bess.py +0 -0
  141. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_hdf5.py +0 -0
  142. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/exports/export_votable.py +0 -0
  143. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/__init__.py +0 -0
  144. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/easyspec_extract.py +0 -0
  145. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/extraction/sky_lateral_bands.py +0 -0
  146. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/__init__.py +0 -0
  147. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_ascii.py +0 -0
  148. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_echelle.py +0 -0
  149. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_fits.py +0 -0
  150. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_sdss.py +0 -0
  151. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/io/read_votable.py +0 -0
  152. /spectro_kernel-0.2.1/src/spectro_mcp/py.typed → /spectro_kernel-0.4.0/src/spectro_kernel/algorithms/kinematics/__init__.py +0 -0
  153. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/__init__.py +0 -0
  154. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/_profiles.py +0 -0
  155. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/catalogs.py +0 -0
  156. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/compare_line_fits.py +0 -0
  157. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/equivalent_width.py +0 -0
  158. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/fit_gaussian.py +0 -0
  159. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/fit_lorentzian.py +0 -0
  160. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/lines/fit_voigt.py +0 -0
  161. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/__init__.py +0 -0
  162. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/compare_snr_methods.py +0 -0
  163. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/snr_der.py +0 -0
  164. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/snr_edge.py +0 -0
  165. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/quality/snr_linear_fit.py +0 -0
  166. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/__init__.py +0 -0
  167. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/_easyspec_apply.py +0 -0
  168. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/_easyspec_helpers.py +0 -0
  169. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/bias_combine.py +0 -0
  170. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/clip_cosmic_rays.py +0 -0
  171. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/dark_subtract.py +0 -0
  172. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/denoise_2d.py +0 -0
  173. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +0 -0
  174. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +0 -0
  175. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +0 -0
  176. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +0 -0
  177. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +0 -0
  178. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +0 -0
  179. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +0 -0
  180. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/extract_spectrum_sum.py +0 -0
  181. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/flat_normalize.py +0 -0
  182. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/geometry.py +0 -0
  183. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/subtract_sky_2d.py +0 -0
  184. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +0 -0
  185. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/__init__.py +0 -0
  186. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/cross_correlate.py +0 -0
  187. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/fit_keplerian_orbit.py +0 -0
  188. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/measure.py +0 -0
  189. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/rv/precision_bouchy.py +0 -0
  190. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/smoothing/__init__.py +0 -0
  191. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/smoothing/compare_smoothings.py +0 -0
  192. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/smoothing/smooth_gaussian.py +0 -0
  193. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/smoothing/smooth_savgol.py +0 -0
  194. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/stacking/__init__.py +0 -0
  195. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/stacking/merge_echelle_orders.py +0 -0
  196. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/stacking/stack.py +0 -0
  197. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/timeseries/__init__.py +0 -0
  198. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/timeseries/lomb_scargle.py +0 -0
  199. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/timeseries/phase_fold.py +0 -0
  200. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/__init__.py +0 -0
  201. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/clip_sigma.py +0 -0
  202. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/combine_arithmetic.py +0 -0
  203. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/extract_region.py +0 -0
  204. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/mask_range.py +0 -0
  205. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/resample.py +0 -0
  206. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/transforms/resample_flux_conserving.py +0 -0
  207. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/__init__.py +0 -0
  208. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/plot_3d_surface.py +0 -0
  209. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/plot_animation.py +0 -0
  210. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/plot_dynamic_spectrum.py +0 -0
  211. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/viz/plot_plotly.py +0 -0
  212. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/__init__.py +0 -0
  213. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/easyspec_wavelength.py +0 -0
  214. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/in_situ.py +0 -0
  215. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/algorithms/wavelength_calibration/solar.py +0 -0
  216. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/base.py +0 -0
  217. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/cli.py +0 -0
  218. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/embeddings.py +0 -0
  219. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/errors.py +0 -0
  220. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/io/__init__.py +0 -0
  221. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/io/ascii.py +0 -0
  222. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/io/fits.py +0 -0
  223. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/io/votable.py +0 -0
  224. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/pipeline.py +0 -0
  225. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/__init__.py +0 -0
  226. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/balmer_quick.yaml +0 -0
  227. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/embed_quick.yaml +0 -0
  228. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/quality_report.yaml +0 -0
  229. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/rv_quick.yaml +0 -0
  230. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/snr_check.yaml +0 -0
  231. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/analysis/time_series_overview.yaml +0 -0
  232. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/catalog/reduction/full_reduction_easyspec.yaml +0 -0
  233. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/presets/loader.py +0 -0
  234. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/registry.py +0 -0
  235. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/types/__init__.py +0 -0
  236. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/types/catalog.py +0 -0
  237. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/types/context.py +0 -0
  238. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/types/history.py +0 -0
  239. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/types/image.py +0 -0
  240. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/types/line.py +0 -0
  241. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/types/spectrum.py +0 -0
  242. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_kernel/types/timeseries.py +0 -0
  243. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_mcp/__init__.py +0 -0
  244. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_mcp/__main__.py +0 -0
  245. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_mcp/auth.py +0 -0
  246. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_mcp/auto_tools.py +0 -0
  247. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_mcp/observability.py +0 -0
  248. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_mcp/server.py +0 -0
  249. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_mcp/session.py +0 -0
  250. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/src/spectro_mcp/url_safety.py +0 -0
  251. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/conftest.py +0 -0
  252. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/reference/conftest.py +0 -0
  253. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/reference/data/.gitkeep +0 -0
  254. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/reference/data/README.md +0 -0
  255. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/reference/data/sun/sun_reference_stis_002.fits +0 -0
  256. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/reference/data/vega/alpha_lyr_stis_011.fits +0 -0
  257. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/reference/test_known_answers.py +0 -0
  258. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/reference/test_sun.py +0 -0
  259. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/reference/test_vega.py +0 -0
  260. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_base.py +0 -0
  261. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_cli.py +0 -0
  262. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_combine.py +0 -0
  263. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_combine_arithmetic.py +0 -0
  264. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_continuum.py +0 -0
  265. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_corrections.py +0 -0
  266. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_denoise_2d.py +0 -0
  267. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_easyspec_apply_staging.py +0 -0
  268. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_easyspec_wrappers.py +0 -0
  269. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_embedding.py +0 -0
  270. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_embedding_pretrained.py +0 -0
  271. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_embedding_remote_lick.py +0 -0
  272. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_embedding_tier1.py +0 -0
  273. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_export_fits_bess.py +0 -0
  274. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_extract_boxcar.py +0 -0
  275. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_geometry_corrections.py +0 -0
  276. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_io.py +0 -0
  277. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_line_profiles.py +0 -0
  278. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_lines.py +0 -0
  279. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_mcp.py +0 -0
  280. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_mcp_production.py +0 -0
  281. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_mcp_security.py +0 -0
  282. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_misc_algorithms.py +0 -0
  283. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_no_circular_imports.py +0 -0
  284. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_normalize_to_region.py +0 -0
  285. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_pipeline.py +0 -0
  286. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_quality.py +0 -0
  287. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_read_sdss.py +0 -0
  288. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_registry.py +0 -0
  289. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_sky_lateral_bands.py +0 -0
  290. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_smoothing.py +0 -0
  291. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_timeseries.py +0 -0
  292. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_transforms.py +0 -0
  293. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_types.py +0 -0
  294. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_viz.py +0 -0
  295. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_wavelength_calibration_in_situ.py +0 -0
  296. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/tests/unit/test_wavelength_calibration_solar.py +0 -0
  297. {spectro_kernel-0.2.1 → spectro_kernel-0.4.0}/website/README.md +0 -0
@@ -6,6 +6,232 @@ 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
+
127
+ ## [0.3.0] — 2026-06-09
128
+
129
+ A focused expansion driven by the LineMill stellar-spectroscopy work
130
+ (Alpy 600 / Star'Ex). **Seven new algorithms + one package data
131
+ module**, all genuinely literature-based, none duplicating existing
132
+ bricks. The minor-version bump signals a meaningful catalogue
133
+ expansion (105 algorithms, up from 98), not an API break — every
134
+ existing brick remains untouched.
135
+
136
+ ### Added — automatic wavelength calibration
137
+
138
+ - **`lamp_atlas`** (package data, not an algorithm) — curated
139
+ persistent-line lists for `Ne`, `Ar`, `NeAr` and `ThAr` arc lamps,
140
+ sourced from the **NIST Atomic Spectra Database** (Kramida, Ralchenko,
141
+ Reader, NIST ASD Team). Embedded — no network at runtime. Sibling of
142
+ the existing ``lines.catalogs`` module. API: ``get_atlas(lamp,
143
+ wave_min=None, wave_max=None) -> ndarray`` of ``(wavelength_angstrom,
144
+ relative_intensity)``.
145
+ - **`match_lamp_lines`** — automatic identification of arc-line peaks
146
+ against the bundled atlas. IRAF ``autoidentify`` heritage (Tody 1986
147
+ SPIE 627 733 ; Tody 1993 ASP Conf 52 173), with the robust ThAr-style
148
+ σ-clipped polynomial refinement (Murphy et al. 2007 MNRAS 378 221).
149
+ Writes ``ctx.extras["lamp_identifications"]`` in the exact shape
150
+ ``wavelength_calibrate_polynomial`` consumes — the old
151
+ hand-identified ``(pixel, λ)`` pairs become optional. Linear seed by
152
+ global anchor-pair brute-force (the local-greedy method's known
153
+ failure mode on Alpy 600 plein visible), then σ-clipped polyfit.
154
+
155
+ ### Added — native master-frame combiners
156
+
157
+ - **`dark_combine`** and **`flat_combine`** — pure-numpy companions to
158
+ the existing native ``bias_combine``. Required by cloud workers that
159
+ source frames from S3 / R2 / memory queues, where the existing
160
+ ``*_easyspec`` variants (file round-trip) don't fit. Median (robust
161
+ default) or σ-clipped mean. ``dark_combine`` enforces uniform EXPTIME
162
+ by default (so the downstream ``dark_subtract(scale_by_exptime=True)``
163
+ has a well-defined factor). ``flat_combine`` produces the **raw**
164
+ master flat — the existing ``flat_normalize`` keeps the
165
+ normalisation responsibility. Howell 2006 ch. 4.
166
+
167
+ ### Added — flux calibration
168
+
169
+ - **`response_from_standard`** — derive the instrumental response
170
+ curve from a standard-star observation. Observed/catalogue ratio,
171
+ Balmer + telluric masking, low-frequency spline (or polynomial) fit.
172
+ Output goes to ``ctx.extras["reference_spectrum"]`` so the existing
173
+ ``combine_spectra_arithmetic(operation="div")`` consumes it as the
174
+ "second spectrum" — same idiom as ``remove_telluric_division``.
175
+ References: Oke 1990 AJ 99 1621 ; Bessell 1999 PASP 111 1426 ; Hamuy
176
+ 1992/1994 ; CALSPEC Bohlin et al. 2014 PASP 126 711 ; Hayes & Latham
177
+ 1975 ApJ 197 593.
178
+
179
+ ### Added — optimal extraction (Horne 1986)
180
+
181
+ - **`extract_spectrum_optimal`** — full Horne 1986 estimator: empirical
182
+ spatial profile (smoothed à la Marsh 1989), per-pixel variance model
183
+ ``V = signal/gain + RON²``, inverse-variance weighting, iterative
184
+ σ-clip cosmic-ray rejection, propagated 1-σ ``Spectrum1D.uncertainty``.
185
+
186
+ **This is NOT a replacement of ``extract_spectrum_boxcar``**. The
187
+ boxcar (with ``extraction_weights="gaussian"``) is a *profile-only*
188
+ estimator without variance model, cosmic rejection or uncertainty
189
+ propagation — it remains the right tool for high-SNR sources, wide
190
+ apertures, extended emission (aurora, nebulae). Pick
191
+ ``extract_spectrum_optimal`` when SNR matters most (faint, RN-limited
192
+ targets), when residual cosmics survive ``clip_cosmic_rays``, or
193
+ when downstream code needs ``Spectrum1D.uncertainty``. Cross-refs
194
+ added to both bricks' ``long_description`` to make the choice
195
+ explicit. References: Horne 1986 PASP 98 609 ; Marsh 1989 PASP 101
196
+ 1032 ; Tody 1986 IRAF apall.
197
+
198
+ ### Added — automation bricks
199
+
200
+ - **`detect_trace`** — pure-numpy trace detection that **exposes** the
201
+ trace as a product on ``ctx.extras["trace"] = {"center_row",
202
+ "poly_coef", "fwhm_px", "snr", "rms_px"}``. Today every
203
+ ``extract_spectrum_*`` recomputes the trace internally and throws the
204
+ result away ; downstream bricks (``subtract_sky_2d``,
205
+ ``correct_smile_polynomial`` reference_row, ``correct_slant_affine``
206
+ pivot_row) take it as a hand-tuned parameter. ``detect_trace`` makes
207
+ the automatic-reduction preset feasible. Tody 1986 SPIE 627 733
208
+ (IRAF ``apall`` / ``aptrace``).
209
+ - **`measure_arc_geometry`** — measure ``smile_radius`` and
210
+ ``slant_deg`` directly from an arc-lamp 2-D pose. The two geometry
211
+ correctors (``correct_smile_polynomial``, ``correct_slant_affine``)
212
+ consume them but until now had no measurement step ; they were
213
+ hand-tuned. The brick tracks each detected arc line row-by-row
214
+ across the slit, then fits one common Schroeder parabola + linear
215
+ tilt slope. Pairs with ``detect_trace`` (uses the science trace as
216
+ the reference row ``y₀``). IRAF ``identify/reidentify/fitcoords``
217
+ heritage (Tody 1986) ; PypeIt wavelength/tilts module (Prochaska et
218
+ al. 2020 JOSS 5 2308) ; Schroeder 2000 ch. 15 §15.3 for the smile
219
+ parametrisation.
220
+
221
+ ### Improved — cross-references in extraction docstrings
222
+
223
+ The ``long_description`` of both ``extract_spectrum_boxcar`` and
224
+ ``extract_spectrum_optimal`` now point at each other, so users can
225
+ choose between them with the precise scientific criterion (SNR
226
+ regime, need for uncertainty propagation, cosmic-ray residuals)
227
+ spelled out in the catalogue.
228
+
229
+ ### Numbers
230
+
231
+ **105 algorithms** registered after this release (was 98). **294
232
+ tests** pass (35 new), 2 skipped. ``ruff`` clean, ``mkdocs build
233
+ --strict`` clean, ``twine check --strict`` clean.
234
+
9
235
  ## [0.2.1] — 2026-06-09
10
236
 
11
237
  End-to-end aurora-parity patch. Diagnosed and validated bit-exactly on
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spectro-kernel
3
- Version: 0.2.1
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"]