Source code for specmod.smoothing.base
"""The smoother interface.
Smoothing is deliberately separate from estimation. The pre-refactor code baked
log-binning into ``Spectrum.__init__``, so every spectrum was binned on
construction with parameters no caller could reach, and the unbinned and binned
arrays travelled together as ``amp``/``bamp`` with nothing recording which was
which.
A smoother maps a :class:`~specmod.core.spectrum.Spectrum` to another one. The
kind, motion, duration and sampling rate are carried through unchanged; only the
frequency axis and amplitudes move.
Smoothing is lossy
------------------
None of these preserve energy — that is the point of them. A smoothed spectrum
therefore fails the Parseval contract that :mod:`specmod.transforms` guarantees,
and :meth:`Spectrum.energy` on one is not meaningful. Every smoother records
what it did under ``meta["smoothing"]`` so this is visible downstream rather
than inferred.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Protocol, runtime_checkable
from ..core.spectrum import Spectrum
__all__ = ["NoSmoothing", "Smoother", "record_smoothing"]
[docs]
@runtime_checkable
class Smoother(Protocol):
"""Anything that maps a spectrum to a smoothed spectrum."""
@property
def name(self) -> str:
"""Short identifier, recorded in the spectrum's metadata."""
...
[docs]
def smooth(self, spectrum: Spectrum) -> Spectrum:
"""Return a smoothed copy of ``spectrum``."""
...
[docs]
@dataclass(frozen=True)
class NoSmoothing:
"""Return the spectrum unchanged.
What ``[smoothing] method = "none"`` selects. It records nothing, so
:func:`is_smoothed` stays ``False`` and the Parseval contract still holds.
"""
name: str = "none"
def smooth(self, spectrum: Spectrum) -> Spectrum:
return spectrum
[docs]
def record_smoothing(meta: Any, name: str, **params: Any) -> dict[str, Any]:
"""Merge a smoothing record into metadata.
Appends rather than overwrites, so chaining smoothers leaves a trail. An
energy check downstream can look for this key and refuse rather than
silently comparing a smoothed spectrum against a Parseval expectation.
"""
info = dict(meta)
applied = list(info.get("smoothing", ()))
applied.append({"method": name, **params})
info["smoothing"] = applied
return info