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.
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/CHANGELOG.md +127 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/PKG-INFO +2 -1
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/usage/library.md +29 -0
- spectro_kernel-0.9.0/examples/longslit_selfcalibrated.py +59 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/pyproject.toml +1 -0
- spectro_kernel-0.9.0/src/spectro_kernel/algorithms/continuum/normalize_region.py +210 -0
- spectro_kernel-0.9.0/src/spectro_kernel/algorithms/corrections/convert_flux_to_rayleigh.py +219 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/boxcar.py +100 -4
- spectro_kernel-0.9.0/src/spectro_kernel/algorithms/quality/measure_line_offsets.py +132 -0
- spectro_kernel-0.9.0/src/spectro_kernel/algorithms/reduction/correct_smile_xcorr.py +370 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_bias.py +6 -1
- spectro_kernel-0.9.0/src/spectro_kernel/algorithms/reduction/flat_normalize_response.py +271 -0
- spectro_kernel-0.9.0/src/spectro_kernel/algorithms/reduction/subtract_bias.py +325 -0
- spectro_kernel-0.9.0/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +226 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/resample.py +30 -5
- spectro_kernel-0.9.0/src/spectro_kernel/algorithms/wavelength_calibration/_line_fit.py +76 -0
- spectro_kernel-0.9.0/src/spectro_kernel/algorithms/wavelength_calibration/refine_zeropoint.py +168 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/version.py +1 -1
- spectro_kernel-0.9.0/tests/unit/test_convert_flux_to_rayleigh.py +134 -0
- spectro_kernel-0.9.0/tests/unit/test_correct_smile_xcorr.py +162 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_examples_run.py +1 -0
- spectro_kernel-0.9.0/tests/unit/test_extract_boxcar_fixed.py +141 -0
- spectro_kernel-0.9.0/tests/unit/test_flat_normalize_response.py +147 -0
- spectro_kernel-0.9.0/tests/unit/test_measure_line_offsets.py +86 -0
- spectro_kernel-0.9.0/tests/unit/test_normalize_to_region_guard.py +114 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_presets.py +4 -2
- spectro_kernel-0.9.0/tests/unit/test_refine_wavelength_zeropoint.py +142 -0
- spectro_kernel-0.9.0/tests/unit/test_resample_oversampling.py +60 -0
- spectro_kernel-0.9.0/tests/unit/test_subtract_bias.py +172 -0
- spectro_kernel-0.9.0/tests/unit/test_subtract_bias_margin.py +176 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_uncertainty_propagation.py +2 -1
- spectro_kernel-0.9.0/tests/unit/test_wavelength_calibrate_apply.py +140 -0
- spectro_kernel-0.8.0/src/spectro_kernel/algorithms/continuum/normalize_region.py +0 -112
- spectro_kernel-0.8.0/src/spectro_kernel/algorithms/reduction/wavelength_calibrate.py +0 -106
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/.gitignore +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/AGENTS.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/LICENSE +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/README.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/.drafts/README.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/.drafts/notebooks/aurora-line-monitor.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/concepts/algorithms.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/concepts/architecture.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/concepts/data-types.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/concepts/pipelines.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/contributing.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/cookbook/multi-star-viewer.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/cookbook/web-playground.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/getting-started.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_ew_timeseries.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_halpha_phase_stack.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_halpha_timeseries.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_overlay.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_pc1_vs_phase.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_pca_2d.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_pca_3d.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_periodogram.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_phase_folded.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_rv_timeseries.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_similarity.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_cyg_similarity_date.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_halpha_phase_stack.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_pc1_vs_phase.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_pca_2d.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_pca_3d.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_periodogram.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_phase_folded.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_rv_timeseries.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_similarity_date.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/images/notebooks/alpha_dra_similarity_phase.png +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/index.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/alpha-cyg-time-series.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/alpha-dra-binary-period.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/be-star-variability.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/claude-mcp-end-to-end.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/exoplanet-transit-rv.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/full-reduction-walkthrough.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/native-vs-easyspec-showdown.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/notebooks/your-first-sb2.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/recipes/format.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/recipes/index.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/recipes/share.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/reference/python.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/add-an-algorithm.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/analyse-a-spectrum.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/discover-the-catalogue.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/first-spectrum.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/tutorials/long-slit-reduction.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/usage/cli.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/usage/mcp.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/validation/.nav.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/validation/index.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/docs/why.md +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/examples/mcp_client.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/examples/quickstart.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/examples/recipe_with_profile.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/examples/wavelength_calibration.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/adapters/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/adapters/easyspec.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/_common.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/_bands.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/activity_index_caii_hk.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/activity_index_halpha.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/activity/ccf_bisector.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/advanced/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/advanced/aperture_photometry.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/advanced/disentangle_sb2.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/advanced/doppler_tomogram.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/gaia.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/simbad.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/standard_star.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/catalogs/vizier.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/classification/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/classification/classify_template_chi2.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/classification/pickles_atlas.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/compare_normalisations.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_edges.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_max.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_percentile.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_polynomial.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/normalize_spline.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/continuum/subtract_continuum.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/air_vacuum.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/atmospheric_extinction.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/barycentric.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/deredden_interstellar.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/doppler_shift.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/extinction_correct_easyspec.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/fit_telluric_scaling.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/flux_calibrate_easyspec.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/remove_telluric.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/response_from_standard.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/corrections/synth_telluric.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_band_power.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_continuum_subtracted.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_lick_indices.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_log_lambda.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_pretrained.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_remote.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_spectrum.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/embedding/embed_wavelets.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/_bess_contract.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_csv.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_fits.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_fits_bess.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_hdf5.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/exports/export_votable.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/_aperture.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/detect_trace.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/easyspec_extract.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/optimal.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/extraction/sky_lateral_bands.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_ascii.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_echelle.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_fits.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_sdss.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/io/read_votable.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/kinematics/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/kinematics/rotation_curve.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/kinematics/vsini_fourier.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/_line_flux.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/_profiles.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/catalogs.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/compare_line_fits.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/detect.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/equivalent_width.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/fit_gaussian.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/fit_lorentzian.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/fit_voigt.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/lines/vr_ratio.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/balmer_decrement_extinction.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/bpt_line_ratios.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/oiii_electron_temperature.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/nebular/sii_electron_density.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/compare_snr_methods.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/measure_resolving_power.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/snr_der.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/snr_edge.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/snr_linear_fit.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/quality/validate_bess_header.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/_easyspec_apply.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/_easyspec_helpers.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/bias_combine.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/clip_cosmic_rays.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/dark_combine.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/dark_subtract.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/denoise_2d.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_bias.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_cosmic_ray.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_dark.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_flat_normalize.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/easyspec_subtract_dark.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/extract_spectrum_sum.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/flat_combine.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/flat_normalize.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/geometry.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/reduction/subtract_sky_2d.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/cross_correlate.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/fit_keplerian_orbit.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/measure.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/precision_bouchy.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/rv/redshift_lines.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/_kernel_uncertainty.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/_nan_bridge.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/compare_smoothings.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/smooth_gaussian.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/smoothing/smooth_savgol.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/stacking/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/stacking/merge_echelle_orders.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/stacking/stack.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/_light_curve.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/box_least_squares.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/lomb_scargle.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/phase_dispersion_minimization.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/phase_fold.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/timeseries/temporal_variance_spectrum.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/clip_sigma.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/combine_arithmetic.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/extract_region.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/mask_range.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/transforms/resample_flux_conserving.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/plot_3d_surface.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/plot_animation.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/plot_dynamic_spectrum.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/viz/plot_plotly.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/arc_geometry.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/easyspec_wavelength.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/in_situ.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/lamp_atlas.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/match_lamp.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/reidentify.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/algorithms/wavelength_calibration/solar.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/base.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/cli.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/embeddings.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/errors.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/io/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/io/ascii.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/io/fits.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/io/votable.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/pipeline.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/balmer_quick.yaml +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/embed_quick.yaml +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/quality_report.yaml +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/rv_quick.yaml +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/snr_check.yaml +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/analysis/time_series_overview.yaml +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/catalog/reduction/full_reduction_easyspec.yaml +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/loader.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/preset.schema.json +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/presets/variables.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/py.typed +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/registry.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/registry_export.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/catalog.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/context.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/enums.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/history.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/image.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/line.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/spectrum.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/types/timeseries.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_kernel/url_safety.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/__main__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/auth.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/auto_tools.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/observability.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/py.typed +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/resources.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/server.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/session.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/src/spectro_mcp/url_safety.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/conftest.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/cases.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/conftest.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/test_known_answers.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/test_sun.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/test_validation_cases.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/reference/test_vega.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_activity_bisector.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_activity_caii_hk.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_activity_halpha.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_aperture_photometry.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_barycentric_bjd.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_base.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_catalog_queries_mocked.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_classify_template_chi2.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_cli.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_combine.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_combine_arithmetic.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_continuum.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_corrections.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_cross_correlate_extension.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_dark_flat_combine.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_denoise_2d.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_detect_lines_blind.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_detect_trace.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_disentangle_sb2.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_docs_generators.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_doppler_tomogram.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_easyspec_apply_staging.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_easyspec_sessions.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_easyspec_wrappers.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_embedding.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_embedding_pretrained.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_embedding_remote_lick.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_embedding_tier1.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_export_fits_bess.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_export_hdf5.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_extract_boxcar.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_extract_optimal.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_extract_spectrum_sum.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_extraction_centring.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_atmospheric_extinction.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_balmer_decrement.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_calibrate_easyspec.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_deredden_interstellar.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_flux_standard_star_reference.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_geometry_corrections.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_io.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_io_fits_zoo.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_lamp_atlas_match.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_line_profiles.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_lines.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mask_honoured.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_http_entry.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_param_guard.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_production.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_resources.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_security.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_mcp_tools_extra.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_measure_arc_geometry.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_misc_algorithms.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_nebular_diagnostics.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_no_circular_imports.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_normalize_spline.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_normalize_to_region.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_output_produces_contract.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_pipeline.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_quality.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_read_sdss.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_redshift_lines.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_registry.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_reidentify.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_resample_flux_conserving.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_resolving_power.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_response_from_standard.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_rotation_curve.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_rv_precision_bouchy.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_sky_lateral_bands.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_smoothing.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_timeseries.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_timeseries_bls.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_timeseries_pdm.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_timeseries_tvs.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_transforms.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_types.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_url_safety_kernel.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_validate_bess_header.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_viz.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_vr_ratio.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_vsini_fourier.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_wavelength_calibration_in_situ.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tests/unit/test_wavelength_calibration_solar.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/__init__.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/build.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_catalogue.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_cli.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_llms.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_mcp.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_recipes.py +0 -0
- {spectro_kernel-0.8.0 → spectro_kernel-0.9.0}/tools/docs/generate_validation.py +0 -0
- {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.
|
|
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
|
+
)
|