spectro-kernel 0.8.0__tar.gz → 0.9.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 (398) hide show
  1. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/CHANGELOG.md +127 -0
  2. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/PKG-INFO +2 -1
  3. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/usage/library.md +29 -0
  4. spectro_kernel-0.9.0/examples/longslit_selfcalibrated.py +59 -0
  5. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/pyproject.toml +1 -0
  6. spectro_kernel-0.9.0/src/spectro_kernel/algorithms/continuum/normalize_region.py +210 -0
  7. spectro_kernel-0.9.0/src/spectro_kernel/algorithms/corrections/convert_flux_to_rayleigh.py +219 -0
  8. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/boxcar.py +100 -4
  9. spectro_kernel-0.9.0/src/spectro_kernel/algorithms/quality/measure_line_offsets.py +132 -0
  10. spectro_kernel-0.9.0/src/spectro_kernel/algorithms/reduction/correct_smile_xcorr.py +370 -0
  11. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +6 -1
  12. spectro_kernel-0.9.0/src/spectro_kernel/algorithms/reduction/flat_normalize_response.py +271 -0
  13. spectro_kernel-0.9.0/src/spectro_kernel/algorithms/reduction/subtract_bias.py +325 -0
  14. spectro_kernel-0.9.0/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +226 -0
  15. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/resample.py +30 -5
  16. spectro_kernel-0.9.0/src/spectro_kernel/algorithms/wavelength_calibration/_line_fit.py +76 -0
  17. spectro_kernel-0.9.0/src/spectro_kernel/algorithms/wavelength_calibration/refine_zeropoint.py +168 -0
  18. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/version.py +1 -1
  19. spectro_kernel-0.9.0/tests/unit/test_convert_flux_to_rayleigh.py +134 -0
  20. spectro_kernel-0.9.0/tests/unit/test_correct_smile_xcorr.py +162 -0
  21. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_examples_run.py +1 -0
  22. spectro_kernel-0.9.0/tests/unit/test_extract_boxcar_fixed.py +141 -0
  23. spectro_kernel-0.9.0/tests/unit/test_flat_normalize_response.py +147 -0
  24. spectro_kernel-0.9.0/tests/unit/test_measure_line_offsets.py +86 -0
  25. spectro_kernel-0.9.0/tests/unit/test_normalize_to_region_guard.py +114 -0
  26. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_presets.py +4 -2
  27. spectro_kernel-0.9.0/tests/unit/test_refine_wavelength_zeropoint.py +142 -0
  28. spectro_kernel-0.9.0/tests/unit/test_resample_oversampling.py +60 -0
  29. spectro_kernel-0.9.0/tests/unit/test_subtract_bias.py +172 -0
  30. spectro_kernel-0.9.0/tests/unit/test_subtract_bias_margin.py +176 -0
  31. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_uncertainty_propagation.py +2 -1
  32. spectro_kernel-0.9.0/tests/unit/test_wavelength_calibrate_apply.py +140 -0
  33. spectro_kernel-0.8.0/src/spectro_kernel/algorithms/continuum/normalize_region.py +0 -112
  34. spectro_kernel-0.8.0/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +0 -106
  35. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/.gitignore +0 -0
  36. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/AGENTS.md +0 -0
  37. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/LICENSE +0 -0
  38. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/README.md +0 -0
  39. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/.drafts/README.md +0 -0
  40. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/.drafts/notebooks/aurora-line-monitor.md +0 -0
  41. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/concepts/algorithms.md +0 -0
  42. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/concepts/architecture.md +0 -0
  43. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/concepts/data-types.md +0 -0
  44. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/concepts/pipelines.md +0 -0
  45. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/contributing.md +0 -0
  46. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/cookbook/multi-star-viewer.md +0 -0
  47. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/cookbook/web-playground.md +0 -0
  48. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/getting-started.md +0 -0
  49. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_ew_timeseries.png +0 -0
  50. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_halpha_phase_stack.png +0 -0
  51. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_halpha_timeseries.png +0 -0
  52. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_overlay.png +0 -0
  53. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_pc1_vs_phase.png +0 -0
  54. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_pca_2d.png +0 -0
  55. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_pca_3d.png +0 -0
  56. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_periodogram.png +0 -0
  57. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_phase_folded.png +0 -0
  58. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_rv_timeseries.png +0 -0
  59. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_similarity.png +0 -0
  60. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_similarity_date.png +0 -0
  61. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_halpha_phase_stack.png +0 -0
  62. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_pc1_vs_phase.png +0 -0
  63. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_pca_2d.png +0 -0
  64. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_pca_3d.png +0 -0
  65. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_periodogram.png +0 -0
  66. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_phase_folded.png +0 -0
  67. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_rv_timeseries.png +0 -0
  68. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_similarity_date.png +0 -0
  69. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_similarity_phase.png +0 -0
  70. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/index.md +0 -0
  71. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/alpha-cyg-time-series.md +0 -0
  72. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/alpha-dra-binary-period.md +0 -0
  73. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/be-star-variability.md +0 -0
  74. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/claude-mcp-end-to-end.md +0 -0
  75. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/exoplanet-transit-rv.md +0 -0
  76. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/full-reduction-walkthrough.md +0 -0
  77. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/native-vs-easyspec-showdown.md +0 -0
  78. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/your-first-sb2.md +0 -0
  79. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/recipes/format.md +0 -0
  80. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/recipes/index.md +0 -0
  81. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/recipes/share.md +0 -0
  82. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/reference/python.md +0 -0
  83. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/add-an-algorithm.md +0 -0
  84. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/analyse-a-spectrum.md +0 -0
  85. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/discover-the-catalogue.md +0 -0
  86. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/first-spectrum.md +0 -0
  87. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/long-slit-reduction.md +0 -0
  88. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/usage/cli.md +0 -0
  89. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/usage/mcp.md +0 -0
  90. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/validation/.nav.md +0 -0
  91. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/validation/index.md +0 -0
  92. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/why.md +0 -0
  93. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/examples/mcp_client.py +0 -0
  94. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/examples/quickstart.py +0 -0
  95. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/examples/recipe_with_profile.py +0 -0
  96. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/examples/wavelength_calibration.py +0 -0
  97. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/__init__.py +0 -0
  98. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/adapters/__init__.py +0 -0
  99. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/adapters/easyspec.py +0 -0
  100. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/__init__.py +0 -0
  101. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/_common.py +0 -0
  102. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/__init__.py +0 -0
  103. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/_bands.py +0 -0
  104. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/activity_index_caii_hk.py +0 -0
  105. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/activity_index_halpha.py +0 -0
  106. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/ccf_bisector.py +0 -0
  107. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/advanced/__init__.py +0 -0
  108. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/advanced/aperture_photometry.py +0 -0
  109. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/advanced/disentangle_sb2.py +0 -0
  110. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/advanced/doppler_tomogram.py +0 -0
  111. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/__init__.py +0 -0
  112. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/gaia.py +0 -0
  113. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/simbad.py +0 -0
  114. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/standard_star.py +0 -0
  115. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/vizier.py +0 -0
  116. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/classification/__init__.py +0 -0
  117. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/classification/classify_template_chi2.py +0 -0
  118. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/classification/pickles_atlas.py +0 -0
  119. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/__init__.py +0 -0
  120. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/compare_normalisations.py +0 -0
  121. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_edges.py +0 -0
  122. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_max.py +0 -0
  123. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_percentile.py +0 -0
  124. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_polynomial.py +0 -0
  125. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_spline.py +0 -0
  126. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/subtract_continuum.py +0 -0
  127. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/__init__.py +0 -0
  128. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/air_vacuum.py +0 -0
  129. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/atmospheric_extinction.py +0 -0
  130. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/barycentric.py +0 -0
  131. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/deredden_interstellar.py +0 -0
  132. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/doppler_shift.py +0 -0
  133. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/extinction_correct_easyspec.py +0 -0
  134. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/fit_telluric_scaling.py +0 -0
  135. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/flux_calibrate_easyspec.py +0 -0
  136. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/remove_telluric.py +0 -0
  137. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/response_from_standard.py +0 -0
  138. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/synth_telluric.py +0 -0
  139. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/__init__.py +0 -0
  140. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_band_power.py +0 -0
  141. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_continuum_subtracted.py +0 -0
  142. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_lick_indices.py +0 -0
  143. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_log_lambda.py +0 -0
  144. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_pretrained.py +0 -0
  145. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_remote.py +0 -0
  146. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_spectrum.py +0 -0
  147. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_wavelets.py +0 -0
  148. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/__init__.py +0 -0
  149. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/_bess_contract.py +0 -0
  150. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_csv.py +0 -0
  151. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_fits.py +0 -0
  152. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_fits_bess.py +0 -0
  153. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_hdf5.py +0 -0
  154. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_votable.py +0 -0
  155. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/__init__.py +0 -0
  156. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/_aperture.py +0 -0
  157. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/detect_trace.py +0 -0
  158. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/easyspec_extract.py +0 -0
  159. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/optimal.py +0 -0
  160. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/sky_lateral_bands.py +0 -0
  161. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/__init__.py +0 -0
  162. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_ascii.py +0 -0
  163. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_echelle.py +0 -0
  164. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_fits.py +0 -0
  165. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_sdss.py +0 -0
  166. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_votable.py +0 -0
  167. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/kinematics/__init__.py +0 -0
  168. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/kinematics/rotation_curve.py +0 -0
  169. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/kinematics/vsini_fourier.py +0 -0
  170. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/__init__.py +0 -0
  171. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/_line_flux.py +0 -0
  172. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/_profiles.py +0 -0
  173. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/catalogs.py +0 -0
  174. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/compare_line_fits.py +0 -0
  175. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/detect.py +0 -0
  176. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/equivalent_width.py +0 -0
  177. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/fit_gaussian.py +0 -0
  178. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/fit_lorentzian.py +0 -0
  179. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/fit_voigt.py +0 -0
  180. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/vr_ratio.py +0 -0
  181. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/__init__.py +0 -0
  182. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/balmer_decrement_extinction.py +0 -0
  183. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/bpt_line_ratios.py +0 -0
  184. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/oiii_electron_temperature.py +0 -0
  185. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/sii_electron_density.py +0 -0
  186. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/__init__.py +0 -0
  187. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/compare_snr_methods.py +0 -0
  188. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/measure_resolving_power.py +0 -0
  189. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/snr_der.py +0 -0
  190. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/snr_edge.py +0 -0
  191. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/snr_linear_fit.py +0 -0
  192. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/validate_bess_header.py +0 -0
  193. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/__init__.py +0 -0
  194. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/_easyspec_apply.py +0 -0
  195. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/_easyspec_helpers.py +0 -0
  196. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/bias_combine.py +0 -0
  197. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/clip_cosmic_rays.py +0 -0
  198. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/dark_combine.py +0 -0
  199. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/dark_subtract.py +0 -0
  200. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/denoise_2d.py +0 -0
  201. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +0 -0
  202. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +0 -0
  203. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +0 -0
  204. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +0 -0
  205. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +0 -0
  206. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +0 -0
  207. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/extract_spectrum_sum.py +0 -0
  208. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/flat_combine.py +0 -0
  209. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/flat_normalize.py +0 -0
  210. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/geometry.py +0 -0
  211. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/subtract_sky_2d.py +0 -0
  212. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/__init__.py +0 -0
  213. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/cross_correlate.py +0 -0
  214. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/fit_keplerian_orbit.py +0 -0
  215. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/measure.py +0 -0
  216. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/precision_bouchy.py +0 -0
  217. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/redshift_lines.py +0 -0
  218. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/__init__.py +0 -0
  219. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/_kernel_uncertainty.py +0 -0
  220. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/_nan_bridge.py +0 -0
  221. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/compare_smoothings.py +0 -0
  222. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/smooth_gaussian.py +0 -0
  223. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/smooth_savgol.py +0 -0
  224. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/stacking/__init__.py +0 -0
  225. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/stacking/merge_echelle_orders.py +0 -0
  226. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/stacking/stack.py +0 -0
  227. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/__init__.py +0 -0
  228. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/_light_curve.py +0 -0
  229. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/box_least_squares.py +0 -0
  230. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/lomb_scargle.py +0 -0
  231. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/phase_dispersion_minimization.py +0 -0
  232. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/phase_fold.py +0 -0
  233. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/temporal_variance_spectrum.py +0 -0
  234. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/__init__.py +0 -0
  235. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/clip_sigma.py +0 -0
  236. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/combine_arithmetic.py +0 -0
  237. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/extract_region.py +0 -0
  238. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/mask_range.py +0 -0
  239. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/resample_flux_conserving.py +0 -0
  240. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/__init__.py +0 -0
  241. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/plot_3d_surface.py +0 -0
  242. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/plot_animation.py +0 -0
  243. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/plot_dynamic_spectrum.py +0 -0
  244. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/plot_plotly.py +0 -0
  245. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/__init__.py +0 -0
  246. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/arc_geometry.py +0 -0
  247. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/easyspec_wavelength.py +0 -0
  248. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/in_situ.py +0 -0
  249. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/lamp_atlas.py +0 -0
  250. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/match_lamp.py +0 -0
  251. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/reidentify.py +0 -0
  252. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/solar.py +0 -0
  253. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/base.py +0 -0
  254. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/cli.py +0 -0
  255. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/embeddings.py +0 -0
  256. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/errors.py +0 -0
  257. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/io/__init__.py +0 -0
  258. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/io/ascii.py +0 -0
  259. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/io/fits.py +0 -0
  260. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/io/votable.py +0 -0
  261. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/pipeline.py +0 -0
  262. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/__init__.py +0 -0
  263. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/balmer_quick.yaml +0 -0
  264. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/embed_quick.yaml +0 -0
  265. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/quality_report.yaml +0 -0
  266. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/rv_quick.yaml +0 -0
  267. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/snr_check.yaml +0 -0
  268. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/time_series_overview.yaml +0 -0
  269. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/reduction/full_reduction_easyspec.yaml +0 -0
  270. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/loader.py +0 -0
  271. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/preset.schema.json +0 -0
  272. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/variables.py +0 -0
  273. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/py.typed +0 -0
  274. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/registry.py +0 -0
  275. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/registry_export.py +0 -0
  276. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/__init__.py +0 -0
  277. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/catalog.py +0 -0
  278. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/context.py +0 -0
  279. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/enums.py +0 -0
  280. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/history.py +0 -0
  281. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/image.py +0 -0
  282. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/line.py +0 -0
  283. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/spectrum.py +0 -0
  284. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/timeseries.py +0 -0
  285. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/url_safety.py +0 -0
  286. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/__init__.py +0 -0
  287. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/__main__.py +0 -0
  288. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/auth.py +0 -0
  289. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/auto_tools.py +0 -0
  290. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/observability.py +0 -0
  291. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/py.typed +0 -0
  292. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/resources.py +0 -0
  293. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/server.py +0 -0
  294. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/session.py +0 -0
  295. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/url_safety.py +0 -0
  296. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/conftest.py +0 -0
  297. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/cases.py +0 -0
  298. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/conftest.py +0 -0
  299. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/test_known_answers.py +0 -0
  300. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/test_sun.py +0 -0
  301. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/test_validation_cases.py +0 -0
  302. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/test_vega.py +0 -0
  303. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_activity_bisector.py +0 -0
  304. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_activity_caii_hk.py +0 -0
  305. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_activity_halpha.py +0 -0
  306. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_aperture_photometry.py +0 -0
  307. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_barycentric_bjd.py +0 -0
  308. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_base.py +0 -0
  309. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_catalog_queries_mocked.py +0 -0
  310. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_classify_template_chi2.py +0 -0
  311. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_cli.py +0 -0
  312. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_combine.py +0 -0
  313. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_combine_arithmetic.py +0 -0
  314. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_continuum.py +0 -0
  315. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_corrections.py +0 -0
  316. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_cross_correlate_extension.py +0 -0
  317. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_dark_flat_combine.py +0 -0
  318. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_denoise_2d.py +0 -0
  319. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_detect_lines_blind.py +0 -0
  320. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_detect_trace.py +0 -0
  321. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_disentangle_sb2.py +0 -0
  322. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_docs_generators.py +0 -0
  323. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_doppler_tomogram.py +0 -0
  324. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_easyspec_apply_staging.py +0 -0
  325. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_easyspec_sessions.py +0 -0
  326. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_easyspec_wrappers.py +0 -0
  327. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_embedding.py +0 -0
  328. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_embedding_pretrained.py +0 -0
  329. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_embedding_remote_lick.py +0 -0
  330. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_embedding_tier1.py +0 -0
  331. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_export_fits_bess.py +0 -0
  332. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_export_hdf5.py +0 -0
  333. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_extract_boxcar.py +0 -0
  334. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_extract_optimal.py +0 -0
  335. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_extract_spectrum_sum.py +0 -0
  336. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_extraction_centring.py +0 -0
  337. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_atmospheric_extinction.py +0 -0
  338. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_balmer_decrement.py +0 -0
  339. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_calibrate_easyspec.py +0 -0
  340. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_deredden_interstellar.py +0 -0
  341. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_standard_star_reference.py +0 -0
  342. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_geometry_corrections.py +0 -0
  343. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_io.py +0 -0
  344. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_io_fits_zoo.py +0 -0
  345. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_lamp_atlas_match.py +0 -0
  346. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_line_profiles.py +0 -0
  347. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_lines.py +0 -0
  348. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mask_honoured.py +0 -0
  349. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp.py +0 -0
  350. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_http_entry.py +0 -0
  351. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_param_guard.py +0 -0
  352. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_production.py +0 -0
  353. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_resources.py +0 -0
  354. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_security.py +0 -0
  355. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_tools_extra.py +0 -0
  356. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_measure_arc_geometry.py +0 -0
  357. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_misc_algorithms.py +0 -0
  358. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_nebular_diagnostics.py +0 -0
  359. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_no_circular_imports.py +0 -0
  360. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_normalize_spline.py +0 -0
  361. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_normalize_to_region.py +0 -0
  362. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_output_produces_contract.py +0 -0
  363. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_pipeline.py +0 -0
  364. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_quality.py +0 -0
  365. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_read_sdss.py +0 -0
  366. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_redshift_lines.py +0 -0
  367. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_registry.py +0 -0
  368. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_reidentify.py +0 -0
  369. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_resample_flux_conserving.py +0 -0
  370. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_resolving_power.py +0 -0
  371. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_response_from_standard.py +0 -0
  372. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_rotation_curve.py +0 -0
  373. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_rv_precision_bouchy.py +0 -0
  374. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_sky_lateral_bands.py +0 -0
  375. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_smoothing.py +0 -0
  376. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_timeseries.py +0 -0
  377. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_timeseries_bls.py +0 -0
  378. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_timeseries_pdm.py +0 -0
  379. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_timeseries_tvs.py +0 -0
  380. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_transforms.py +0 -0
  381. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_types.py +0 -0
  382. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_url_safety_kernel.py +0 -0
  383. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_validate_bess_header.py +0 -0
  384. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_viz.py +0 -0
  385. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_vr_ratio.py +0 -0
  386. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_vsini_fourier.py +0 -0
  387. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_wavelength_calibration_in_situ.py +0 -0
  388. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_wavelength_calibration_solar.py +0 -0
  389. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/__init__.py +0 -0
  390. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/build.py +0 -0
  391. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate.py +0 -0
  392. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_catalogue.py +0 -0
  393. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_cli.py +0 -0
  394. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_llms.py +0 -0
  395. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_mcp.py +0 -0
  396. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_recipes.py +0 -0
  397. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_validation.py +0 -0
  398. {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/netlify.toml +0 -0
@@ -6,6 +6,133 @@ Until `1.0.0` the public API may change between minor versions.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.0] — 2026-10-23
10
+
11
+ Bricks for self-calibrating long-slit instruments, requested by NORVIS Redux,
12
+ which reduces auroral long-slit spectra with ten local bricks that implement
13
+ generic, published methods. Five new bricks and five extensions ; every
14
+ extension keeps its default behaviour **byte-identical to 0.8.1, metrics
15
+ included** (MINOR attribute bumps, no MAJOR). Each brick was checked bit for
16
+ bit against the Redux reference implementation on a synthetic harness covering
17
+ the apply, skip and failure branches (report in the pull request). No NORVIS
18
+ constant became a default : which lines, which header keyword, which geometry
19
+ are required parameters.
20
+
21
+ ### Added
22
+
23
+ - **`refine_wavelength_zeropoint`** (wavelength_calibration, 1.0.0, scipy) — a
24
+ guarded pure zero-point shift of a calibrated axis from one emission line of
25
+ the spectrum itself (Osterbrock, Fulbright, Martel, Keane, Trager & Basri
26
+ 1996, PASP 108, 277 ; Osterbrock, Fulbright & Bida 1997, PASP 109, 614 :
27
+ night-sky lines as wavelength standards). Gaussian on a linear baseline
28
+ inside `reference_wavelength ± search_halfwidth_angstrom`, Δλ = λ_ref − λ_fit
29
+ added to the whole axis, dispersion untouched. Skips (`applied = 0`, measured
30
+ centre, shift and S/N reported) on a faint line (`min_snr`), a centre outside
31
+ the window or a shift beyond `max_shift_angstrom` ; fails on a pixel axis.
32
+ `reference_wavelength` is required.
33
+ - **`measure_line_offsets`** (quality, 1.0.0, scipy) — where known lines land on
34
+ a calibrated axis, measured and never applied : per line
35
+ `line_<λ>_detected`, `line_<λ>_snr`, `line_<λ>_offset_angstrom`, plus
36
+ `n_lines_detected` and `mean_offset_angstrom`. Same fit helper as the
37
+ zero-point brick (`wavelength_calibration/_line_fit.py`).
38
+ `reference_wavelengths` is required.
39
+ - **`flat_normalize_response`** (preprocessing, 1.0.0, scipy) — the IRAF
40
+ `response` recipe (Massey, Valdes & Barnes 1992 ; Howell 2006 ch. 4) : the
41
+ flat over its own smoothed lamp spectrum, scaled to median 1 over the lit
42
+ area, applied only where the lamp gave signal and the response lies within
43
+ `[min_response, max_response]`. `pedestal_mode = "dark_margin"` holds the
44
+ science pedestal out of the division, `(image − p) / N + p`. Metrics : lit
45
+ span, lit rows, corrected fraction, pixel-response scatter, illumination
46
+ percentiles. Fills the gap the `flat_normalize` docstring declared.
47
+ - **`correct_smile_xcorr`** (preprocessing, 1.0.0, astropy) — line curvature
48
+ measured on the frame itself (Kelson 2003, PASP 115, 688 ; Massey, Valdes &
49
+ Barnes 1992 ; Tonry & Davis 1979) : row bands cross-correlated against the
50
+ band at `reference_row` with parabolic sub-pixel refinement, shift(row)
51
+ fitted with an astropy `Polynomial1D` under two 3σ clipping passes,
52
+ re-anchored so the reference row is untouched and held constant outside the
53
+ fitted rows, every row resampled. Skips on a narrow lit span, a reference row
54
+ outside the lit rows, fewer than `min_bands` bands, a fit rms above
55
+ `max_fit_rms_px` or an amplitude above `max_amplitude_px`. Fit written to
56
+ `ctx.extras[output_key]`.
57
+ - **`convert_flux_to_rayleigh`** (flux_calibration, 1.0.0, numpy) — a
58
+ response-corrected slit spectrum to rayleigh per nm (Hunten, Roach &
59
+ Chamberlain 1956, J. Atmos. Terr. Phys. 8, 345 ; Baker 1974, Appl. Opt. 13,
60
+ 2160 ; Baker & Romick 1976, Appl. Opt. 15, 1966) through the solid angle
61
+ Ω = (w / f) · (N · p / (m · f)) and the absolute response scale read from the
62
+ response header under `response_scale_keyword` (required : the keyword is a
63
+ convention of whoever built the response). A `normalize_to_region` step is
64
+ undone. Missing scale or exposure time goes to `on_missing`.
65
+ - `examples/longslit_selfcalibrated.py` — the ten bricks chained on a synthetic
66
+ frame, run by CI ; a section on self-calibrating long-slit instruments in the
67
+ library usage page.
68
+
69
+ ### Changed — extensions, defaults byte-identical to 0.8.1
70
+
71
+ - `extract_spectrum_boxcar` 3.0.0 → 3.1.0 : `trace_method = "fixed"` extracts
72
+ the window `trace_center_row ± trace_half_width` on every column without
73
+ tracing (IRAF `apall` without `aptrace`), for extended sources that fill the
74
+ slit ; no easyspec or matplotlib import in that mode. Meta `trace_method` /
75
+ `trace_center_row` and metric `trace_center_row` in fixed mode only.
76
+ - `wavelength_calibrate_polynomial` 1.0.1 → 1.1.0 : apply mode. `coefficients`
77
+ (`coefficients_order` lowest_first by default, or highest_first) and
78
+ `pixel_offset` evaluate a known dispersion solution instead of fitting one
79
+ (Massey & Hanson 2013, DOI 10.1007/978-94-007-5618-2_2) ; fails on a
80
+ non-increasing axis ; `required_params` is now empty, the pixel/wavelength
81
+ pairs are checked in `run()` when no coefficients are given. Metrics
82
+ `applied`, `wavelength_min_angstrom`, `wavelength_max_angstrom`,
83
+ `dispersion_angstrom_per_px` ; meta `wavelength_calibration = {method:
84
+ polynomial_coefficients, …}`.
85
+ - `resample_linear` 2.0.0 → 2.1.0 : `oversampling`, the grid holds
86
+ `round((npix − 1) · oversampling) + 1` samples over the same bounds ;
87
+ exclusive with `step` and `n_points`.
88
+ - `normalize_to_region` 3.0.0 → 3.1.0 : `window_selection` (`"nearest"`,
89
+ the default, or `"within"`) and a significance guard (`min_snr`,
90
+ `on_invalid`) on the standard error of the mean (Bevington & Robinson 2003) :
91
+ a negative mean, or one below `min_snr` standard errors, fails or skips
92
+ instead of inverting the spectrum or scaling it by noise.
93
+ - `subtract_bias` 1.0.0 → 1.1.0 : the pedestal from an explicit
94
+ `overscan_columns` range or, with `overscan_auto`, from the columns the
95
+ spectrum never reaches (public `unilluminated_columns` : trimmed-mean column
96
+ profile, noise-based threshold, contiguous lit span widened by
97
+ `guard_columns`). Estimator : astropy sigma-clipped mean, which resolves the
98
+ level of a coarsely quantised integer frame below its step where a median
99
+ cannot ; residual reported. `min_columns` and `max_level_adu` go to
100
+ `on_invalid`. Precedence : `bias_level` > `overscan_columns` >
101
+ `overscan_auto` > master frame.
102
+ - `tests/unit/test_uncertainty_propagation.py` pins the major version of
103
+ `resample_linear` instead of `2.0.0`, since the propagation rule of 2.0.0 is
104
+ kept by 2.1.0.
105
+
106
+ ## [0.8.1] — 2026-09-26
107
+
108
+ One native brick, the last of the September audit's "trivial and already requested"
109
+ gaps (item 1 of its catalogue table), plus the documentation hook for the published
110
+ recipe collection. No existing brick changes its numbers.
111
+
112
+ ### Added
113
+
114
+ - **`subtract_bias`** (preprocessing, 1.0.0, numpy) — the zero-level correction of
115
+ standard CCD reduction (Howell 2006, *Handbook of CCD Astronomy*, ch. 4 ; Massey
116
+ 1997, *A User's Guide to CCD Reductions with IRAF*) : `science − master_bias` in
117
+ float64 on `ctx.image`, master frame read from `ctx.extras['master_bias']` as written
118
+ by `bias_combine`, or a constant `bias_level` (overscan mean) when no bias frames
119
+ exist. No exposure-time scaling (a bias carries no dark current). Negative pixels are
120
+ kept unless `clip_negative=True` ; metrics `bias_median`, `median_after`,
121
+ `n_negative`, `n_clipped`. Native counterpart of `subtract_bias_easyspec`, which
122
+ AuroreX asked for after the BZERO staging bug of the easyspec wrappers (fixed in
123
+ 0.2.1) : the native path never stages a FITS file. Tests : exact recovery of a
124
+ synthetic truth under a gradient bias, integer-ADU input, constant level, precedence
125
+ of `bias_level` over the frame, clipping count, three failure paths, chaining after
126
+ `bias_combine`.
127
+ - `subtract_bias_easyspec` 1.0.1 → 1.0.2 : `long_description` cross-references the
128
+ native brick (docstring only).
129
+
130
+ ### Documentation
131
+
132
+ - The `docs` extra now installs `spectro-kernel-recipes>=0.2.0` (published on PyPI on
133
+ 2026-09-26), so the online recipe catalogue lists the ten campaign recipes next to the
134
+ seven bundled ones. No runtime change.
135
+
9
136
  ## [0.8.0] — 2026-09-26
10
137
 
11
138
  The "exactness" release : the four method corrections the September audit
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: spectro-kernel
3
- Version: 0.8.0
3
+ Version: 0.9.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://docs.spectrokernel.io/
@@ -59,6 +59,7 @@ Requires-Dist: mkdocs-literate-nav>=0.6; extra == 'docs'
59
59
  Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
60
60
  Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
61
61
  Requires-Dist: pyyaml>=6.0; extra == 'docs'
62
+ Requires-Dist: spectro-kernel-recipes>=0.2.0; extra == 'docs'
62
63
  Requires-Dist: zensical>=0.0.62; extra == 'docs'
63
64
  Provides-Extra: embedding-ml
64
65
  Requires-Dist: torch>=2.2; extra == 'embedding-ml'
@@ -146,3 +146,32 @@ class MyMetric(BaseAlgorithm):
146
146
  Once the module is imported, `run_algorithm("my_metric", ctx)` works - and so does the
147
147
  CLI and the MCP server. The full walkthrough is in
148
148
  [Tutorial: add an algorithm](../tutorials/add-an-algorithm.md).
149
+
150
+ ## Self-calibrating long-slit instruments
151
+
152
+ Some slit spectrographs calibrate themselves: no arc lamp, no bias frames, a
153
+ dispersion solution stored in every frame. The catalogue covers that chain
154
+ without a single instrument-specific brick:
155
+
156
+ - `subtract_bias` with `overscan_auto` measures the pedestal on the columns the
157
+ spectrum never reaches (a sigma-clipped mean, which resolves a coarsely
158
+ quantised integer frame below its step);
159
+ - `flat_normalize_response` divides by a flat normalised along the dispersion,
160
+ the IRAF `response` recipe, so the lamp spectrum is not imprinted;
161
+ - `correct_smile_xcorr` measures the line curvature on the frame itself and
162
+ straightens it, anchored on the extraction row;
163
+ - `extract_spectrum_boxcar` with `trace_method="fixed"` sums a fixed band of
164
+ rows, the right aperture for a source that fills the slit;
165
+ - `wavelength_calibrate_polynomial` with `coefficients` applies the stored
166
+ dispersion polynomial, `resample_linear` with `oversampling` puts it on a
167
+ uniform grid, `refine_wavelength_zeropoint` takes the night's zero-point
168
+ drift out on a sky line and `measure_line_offsets` reports where the other
169
+ lines land without moving anything;
170
+ - `normalize_to_region` with its significance guard, then
171
+ `convert_flux_to_rayleigh` for an absolute radiance in R/nm when the
172
+ response carries an absolute scale.
173
+
174
+ Every one of these bricks skips rather than guesses when its input is doubtful
175
+ (`applied = 0` in the metrics, context untouched). The chain runs end to end
176
+ in [`examples/longslit_selfcalibrated.py`](https://github.com/matthieulel/spectro-kernel/blob/main/examples/longslit_selfcalibrated.py)
177
+ on a synthetic frame.
@@ -0,0 +1,59 @@
1
+ """Self-calibrating long-slit reduction: a synthetic sky frame to rayleigh per nm, no arc lamp.
2
+
3
+ Run with ``python examples/longslit_selfcalibrated.py``: margin pedestal, response flat, smile
4
+ measured on the frame, stored dispersion polynomial refined on [O I] 5577, R/nm. Deterministic.
5
+ """
6
+
7
+ import numpy as np
8
+
9
+ from spectro_kernel import WorkContext, run_algorithm
10
+ from spectro_kernel.types import ImageFrame, Spectrum1D
11
+
12
+ ROWS, COLS, PED, REF = 240, 600, 300.0, 120
13
+ COEF = [5000.0, 2.5, -1.0e-4] # stored solution lambda(column), lowest degree first
14
+ LINES = (5577.34, 6300.30) # [O I] night-sky lines (air)
15
+ GEOM = {"slit_width_um": 20.0, "focal_mm": 50.0, "pixel_um": 2.9, "aperture_rows": 121}
16
+
17
+
18
+ def synthetic(rng: np.random.Generator) -> tuple[ImageFrame, ImageFrame]:
19
+ cols, rows = np.arange(COLS, dtype=float), np.arange(ROWS, dtype=float)[:, None]
20
+ wave = np.polynomial.polynomial.polyval(cols, COEF) - 1.0 # 1 A zero-point drift
21
+ smile = 6.0 * ((rows - REF) / 100.0) ** 2 # line curvature, in columns
22
+ lit, slit = (cols >= 80) & (cols < 520), (rows >= 30) & (rows < 210)
23
+ centres = [np.interp(w, wave, cols) + smile for w in LINES]
24
+ sky = 30.0 * lit + sum(800.0 * np.exp(-0.5 * ((cols - c) / 2.0) ** 2) for c in centres)
25
+ lamp = 2000.0 * (1 + 0.4 * (cols - 80) / 440) * lit
26
+ pattern = 1.0 + 0.02 * rng.uniform(-1, 1, (ROWS, COLS))
27
+ noise = rng.normal(0, 2.0, (2, ROWS, COLS))
28
+ science = ImageFrame(pattern * slit * sky + PED + noise[0], header={"EXPTIME": 30.0})
29
+ return science, ImageFrame(pattern * slit * lamp + PED + noise[1], frame_type="flat")
30
+
31
+
32
+ def main() -> None:
33
+ science, flat = synthetic(np.random.default_rng(0))
34
+ ctx = WorkContext(image=science, extras={"master_flat": flat})
35
+ ctx.extras["reference_spectrum"] = Spectrum1D(
36
+ [5000.0, 7000.0], [1, 1], header={"RESPABS": 2.5e8}
37
+ )
38
+ bias = run_algorithm("subtract_bias", ctx, {"overscan_auto": True})
39
+ print(f"subtract_bias: pedestal {bias.metrics['bias_median']:.1f} ADU from the unlit margin")
40
+ run_algorithm("flat_normalize_response", ctx, {"pedestal_mode": "dark_margin"})
41
+ run_algorithm("correct_smile_xcorr", ctx, {"reference_row": REF})
42
+ boxcar = {"trace_method": "fixed", "trace_center_row": REF, "trace_half_width": 60}
43
+ run_algorithm("extract_spectrum_boxcar", ctx, boxcar)
44
+ run_algorithm("wavelength_calibrate_polynomial", ctx, {"coefficients": COEF})
45
+ run_algorithm("resample_linear", ctx, {"oversampling": 2.0})
46
+ zp = run_algorithm("refine_wavelength_zeropoint", ctx, {"reference_wavelength": LINES[0]})
47
+ off = run_algorithm("measure_line_offsets", ctx, {"reference_wavelengths": list(LINES)})
48
+ delta, rest = zp.metrics["delta_angstrom"], off.metrics["line_6300_offset_angstrom"]
49
+ print(f"refine_wavelength_zeropoint: {delta:+.2f} A applied; [O I] 6300 now {rest:+.2f} A off")
50
+ window = {"wave_lo": 5800.0, "wave_hi": 5900.0, "window_selection": "within", "min_snr": 3.0}
51
+ run_algorithm("normalize_to_region", ctx, {**window, "on_invalid": "skip"})
52
+ ray = {**GEOM, "samples_per_pixel": 2.0, "response_scale_keyword": "RESPABS"}
53
+ out = run_algorithm("convert_flux_to_rayleigh", ctx, ray)
54
+ omega, unit = out.metrics["solid_angle_sr"], ctx.spectrum.flux_unit
55
+ print(f"convert_flux_to_rayleigh: solid angle {omega:.3e} sr, spectrum now in {unit}")
56
+
57
+
58
+ if __name__ == "__main__":
59
+ main()
@@ -70,6 +70,7 @@ docs = [
70
70
  "mkdocs-literate-nav>=0.6", # fallback engine's nav plugin (Zensical has it built in)
71
71
  "jsonschema>=4.0", # validates docs/ai/algorithms.json at build time
72
72
  "pyyaml>=6.0",
73
+ "spectro-kernel-recipes>=0.2.0", # the campaign collection, so the online catalogue lists its recipes
73
74
  ]
74
75
  all = [
75
76
  "spectro-kernel[catalogs,viz,storage,reduction,mcp,monitoring,embedding-wavelets,embedding-remote]",
@@ -0,0 +1,210 @@
1
+ """Algorithm: normalise a spectrum by its mean flux over a chosen wavelength window."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import numpy as np
8
+
9
+ from ...base import AlgorithmOutput, BaseAlgorithm
10
+ from ...errors import InvalidParameterError
11
+ from ...registry import register_algorithm
12
+ from ...types import AlgorithmCategory, WorkContext
13
+
14
+
15
+ @register_algorithm(
16
+ "normalize_to_region",
17
+ category=AlgorithmCategory.CONTINUUM,
18
+ # 2.0.0: uncertainty rule (σ_out = σ_in / |region_mean|, in place since
19
+ # 1.0.0) documented in long_description. No numerical change.
20
+ # 3.0.0: masked samples (Spectrum1D.mask) are ignored by the region mean
21
+ # (n_samples counts the samples actually used) ; the output keeps their
22
+ # flux, divided like every other sample, and carries the mask through.
23
+ # 3.1.0: window_selection ("nearest" | "within"), significance guard
24
+ # (min_snr, on_invalid). Defaults byte-identical to 3.0.0, metrics included.
25
+ version="3.1.0",
26
+ )
27
+ class NormalizeToRegion(BaseAlgorithm):
28
+ """Divide the flux by its NaN-safe mean over ``[wave_lo, wave_hi]``.
29
+
30
+ The window should be a line-free continuum region so the output sits
31
+ near unity inside it. The companion of ``normalize_edges`` /
32
+ ``normalize_polynomial`` / ``normalize_max`` /
33
+ ``normalize_percentile`` with the explicit "normalise to *this*
34
+ band" convention — exactly what the IRAF ``continuum`` task does
35
+ when scaled by a measured band mean.
36
+
37
+ Two options, off by default, protect the division: ``window_selection =
38
+ "within"`` takes the samples inside ``[wave_lo, wave_hi]`` instead of the
39
+ nearest-sample indices, and a significance guard (``min_snr`` > 0 or
40
+ ``on_invalid = "skip"``) refuses to divide by a window mean that is
41
+ negative, or not at least ``min_snr`` standard errors above zero — a
42
+ negative level turns the spectrum upside down, a level lost in the noise
43
+ scales it by noise. Invalid windows then fail, or skip with ``applied = 0``
44
+ and the spectrum untouched.
45
+ """
46
+
47
+ backend = "numpy"
48
+ references = [
49
+ "Tody 1986, Proc. SPIE 627, 733 — IRAF continuum heritage.",
50
+ "Bevington & Robinson 2003, Data Reduction and Error Analysis for the "
51
+ "Physical Sciences, 3rd ed., McGraw-Hill — §4.2, standard error of the mean "
52
+ "(the significance statistic of the guard).",
53
+ ]
54
+ long_description = (
55
+ "out = flux / nanmean(flux[wave_lo:wave_hi]). The window is "
56
+ "closed at both ends and clamped to the spectrum's wavelength "
57
+ "range. v2.0.0 — uncertainty (rule in place since 1.0.0, now documented ; "
58
+ "no numerical change) : σ_out = σ_in / |region_mean| — the "
59
+ "region mean is treated as noise-free (its standard error, "
60
+ "σ/√N over the window, is not propagated). "
61
+ "If the region mean is zero or non-finite the algorithm fails "
62
+ "with a clear message rather than emitting NaN. v3.0.0: masked "
63
+ "samples (Spectrum1D.mask) are ignored by the region mean and "
64
+ "n_samples counts the usable samples ; the output keeps their flux, "
65
+ "divided like every other sample, and carries the mask through. "
66
+ "v3.1.0 — window_selection: 'nearest' (default, the nearest-sample "
67
+ "indices of wave_lo and wave_hi) or 'within' (every sample with wave_lo ≤ "
68
+ "λ ≤ wave_hi ; fewer than 3 usable samples is a configuration error and "
69
+ "fails). Significance guard, active when min_snr > 0 or on_invalid = "
70
+ "'skip': mean and standard error of the mean (std with ddof = 1 over √n) "
71
+ "of the usable window samples, S/N = mean / sem (±1e6 when sem = 0) ; the "
72
+ "window is invalid when mean ≤ 0 or S/N < min_snr, and then on_invalid "
73
+ "decides: 'fail' (default) fails, 'skip' succeeds with applied = 0, "
74
+ "region_mean, region_snr, wave_lo, wave_hi, n_samples and the spectrum "
75
+ "untouched. A valid window is divided exactly as before ; with the guard "
76
+ "active the metrics gain applied = 1 and region_snr. With the defaults "
77
+ "the brick is byte-identical to 3.0.0, metrics included."
78
+ )
79
+ default_params = {
80
+ "wave_lo": 0.0,
81
+ "wave_hi": 0.0,
82
+ "window_selection": "nearest",
83
+ "min_snr": 0.0,
84
+ "on_invalid": "fail",
85
+ }
86
+ required_params = ["wave_lo", "wave_hi"]
87
+ param_descriptions = {
88
+ "wave_lo": "Lower bound of the reference window (same unit as the wavelength axis).",
89
+ "wave_hi": "Upper bound of the reference window.",
90
+ "window_selection": (
91
+ "'nearest': the samples between the nearest-sample indices of wave_lo "
92
+ "and wave_hi (default) ; 'within': the samples with wave_lo ≤ λ ≤ wave_hi."
93
+ ),
94
+ "min_snr": (
95
+ "Guard: the window mean must be positive and at least this many "
96
+ "standard errors above zero. 0 disables the significance test "
97
+ "(the sign test still runs when on_invalid is 'skip')."
98
+ ),
99
+ "on_invalid": (
100
+ "'fail' (default) or 'skip' when the guard rejects the window: skip "
101
+ "leaves the spectrum untouched and reports applied = 0."
102
+ ),
103
+ }
104
+ input_requirements = ["spectrum"]
105
+ output_produces = [
106
+ "spectrum",
107
+ "metrics.region_mean",
108
+ "metrics.applied",
109
+ "metrics.region_snr",
110
+ ]
111
+
112
+ def run(self, ctx: WorkContext, params: dict[str, Any]) -> AlgorithmOutput:
113
+ wave_lo = float(params["wave_lo"])
114
+ wave_hi = float(params["wave_hi"])
115
+ if wave_hi <= wave_lo:
116
+ raise InvalidParameterError("wave_hi must be strictly greater than wave_lo.")
117
+ spec = ctx.spectrum
118
+ if spec is None:
119
+ return AlgorithmOutput.fail("No ctx.spectrum to normalise.")
120
+
121
+ selection = str(params["window_selection"])
122
+ if selection not in ("nearest", "within"):
123
+ raise InvalidParameterError("window_selection must be 'nearest' or 'within'.")
124
+ on_invalid = str(params["on_invalid"])
125
+ if on_invalid not in ("fail", "skip"):
126
+ raise InvalidParameterError("on_invalid must be 'fail' or 'skip'.")
127
+ min_snr = float(params["min_snr"])
128
+ if min_snr < 0.0:
129
+ raise InvalidParameterError("min_snr must be >= 0.")
130
+ guard_active = min_snr > 0.0 or on_invalid == "skip"
131
+
132
+ wave = np.asarray(spec.wavelength, dtype=np.float64)
133
+ flux = np.asarray(spec.flux, dtype=np.float64)
134
+ if selection == "nearest":
135
+ # Nearest-sample window so users don't need to know the grid step.
136
+ lo_idx = int(np.argmin(np.abs(wave - wave_lo)))
137
+ hi_idx = int(np.argmin(np.abs(wave - wave_hi)))
138
+ lo_idx, hi_idx = sorted((lo_idx, hi_idx))
139
+ if hi_idx == lo_idx:
140
+ return AlgorithmOutput.fail(
141
+ f"Reference window [{wave_lo}, {wave_hi}] is narrower than "
142
+ "one sample of the spectrum's wavelength grid."
143
+ )
144
+ # Masked samples enter as NaN and are dropped with the non-finite ones.
145
+ region = spec.masked_flux()[lo_idx : hi_idx + 1]
146
+ else:
147
+ region = spec.masked_flux()[(wave >= wave_lo) & (wave <= wave_hi)]
148
+ region = region[np.isfinite(region)]
149
+ n_used = int(region.size)
150
+ if (selection == "within" or guard_active) and n_used < 3:
151
+ return AlgorithmOutput.fail(
152
+ f"Reference window [{wave_lo:g}, {wave_hi:g}] holds {n_used} usable "
153
+ "sample(s) — widen it."
154
+ )
155
+ region_mean = float(np.mean(region)) if n_used else float("nan")
156
+ region_snr: float | None = None
157
+ if guard_active:
158
+ sem = float(np.std(region, ddof=1) / np.sqrt(region.size))
159
+ if sem > 0:
160
+ region_snr = region_mean / sem
161
+ else:
162
+ region_snr = 1e6 if region_mean > 0 else -1e6
163
+ if region_mean <= 0.0 or region_snr < min_snr:
164
+ reason = (
165
+ f"mean {region_mean:.4g} over [{wave_lo:g}, {wave_hi:g}] "
166
+ f"(S/N {region_snr:.1f}) — dividing would "
167
+ f"{'invert' if region_mean <= 0.0 else 'scale by noise'} the spectrum."
168
+ )
169
+ if on_invalid == "skip":
170
+ return AlgorithmOutput.ok(
171
+ metrics={
172
+ "applied": 0.0,
173
+ "region_mean": region_mean,
174
+ "region_snr": float(region_snr),
175
+ "wave_lo": wave_lo,
176
+ "wave_hi": wave_hi,
177
+ "n_samples": float(n_used),
178
+ },
179
+ message=f"Not normalised: {reason} Spectrum left untouched.",
180
+ )
181
+ return AlgorithmOutput.fail(f"Reference window rejected: {reason}")
182
+ elif not np.isfinite(region_mean) or region_mean == 0.0:
183
+ return AlgorithmOutput.fail(
184
+ f"Reference region mean is {region_mean} — pick a window "
185
+ "with finite, unmasked, non-zero flux."
186
+ )
187
+
188
+ out = spec.with_flux(flux / region_mean, flux_unit="normalized")
189
+ if spec.uncertainty is not None:
190
+ out.uncertainty = spec.uncertainty / abs(region_mean)
191
+ out.meta["normalized_to_region"] = (wave_lo, wave_hi)
192
+ out.meta["normalization_factor"] = region_mean
193
+ ctx.spectrum = out
194
+
195
+ metrics: dict[str, float] = {
196
+ "region_mean": region_mean,
197
+ "wave_lo": wave_lo,
198
+ "wave_hi": wave_hi,
199
+ "n_samples": float(n_used),
200
+ }
201
+ if region_snr is not None:
202
+ metrics["applied"] = 1.0
203
+ metrics["region_snr"] = float(region_snr)
204
+ return AlgorithmOutput.ok(
205
+ metrics=metrics,
206
+ message=(
207
+ f"Normalised to mean {region_mean:.4g} over "
208
+ f"[{wave_lo:g}, {wave_hi:g}] ({n_used} samples)."
209
+ ),
210
+ )