Skip to content

Signal Quality

Metrics for assessing spectral data quality.

spectrakit.quality.quality_snr

quality_snr(
    intensities: ndarray,
    *,
    signal_range: tuple[int, int] | None = None,
    noise_range: tuple[int, int] | None = None,
) -> float | np.ndarray

Estimate the signal-to-noise ratio of spectral data.

Two modes of operation:

  1. Explicit regions — provide both signal_range and noise_range as (start_idx, end_idx) index slices. SNR = mean(signal_region) / std(noise_region).
  2. Automatic (2nd derivative) — omit both ranges. Noise is estimated from std(diff(intensities, n=2)) / sqrt(6) (white-noise correction). Signal is peak-to-peak amplitude.

Parameters:

Name Type Description Default
intensities ndarray

Spectral intensities, shape (W,) or (N, W).

required
signal_range tuple[int, int] | None

(start_idx, end_idx) index slice for the signal region. Must be provided together with noise_range.

None
noise_range tuple[int, int] | None

(start_idx, end_idx) index slice for the noise region. Must be provided together with signal_range.

None

Returns:

Type Description
float | ndarray

SNR value. Scalar float for 1-D input, array of shape

float | ndarray

(N,) for a 2-D batch.

Raises:

Type Description
SpectrumShapeError

If intensities is not 1-D or 2-D.

EmptySpectrumError

If intensities has zero elements.

ValueError

If only one of signal_range / noise_range is given.

ValueError

If ranges produce empty slices or are out of bounds.

ValueError

If spectrum has fewer than 3 points in automatic mode.

Examples:

>>> import numpy as np
>>> from spectrakit import quality_snr
>>> clean = np.sin(np.linspace(0, 2 * np.pi, 200))
>>> quality_snr(clean)
32.1...
Source code in src/spectrakit/quality/snr.py
def quality_snr(
    intensities: np.ndarray,
    *,
    signal_range: tuple[int, int] | None = None,
    noise_range: tuple[int, int] | None = None,
) -> float | np.ndarray:
    """Estimate the signal-to-noise ratio of spectral data.

    Two modes of operation:

    1. **Explicit regions** — provide both *signal_range* and *noise_range*
       as ``(start_idx, end_idx)`` index slices.
       ``SNR = mean(signal_region) / std(noise_region)``.
    2. **Automatic (2nd derivative)** — omit both ranges.  Noise is
       estimated from ``std(diff(intensities, n=2)) / sqrt(6)``
       (white-noise correction).  Signal is peak-to-peak amplitude.

    Args:
        intensities: Spectral intensities, shape ``(W,)`` or ``(N, W)``.
        signal_range: ``(start_idx, end_idx)`` index slice for the signal
            region.  Must be provided together with *noise_range*.
        noise_range: ``(start_idx, end_idx)`` index slice for the noise
            region.  Must be provided together with *signal_range*.

    Returns:
        SNR value.  Scalar ``float`` for 1-D input, array of shape
        ``(N,)`` for a 2-D batch.

    Raises:
        SpectrumShapeError: If *intensities* is not 1-D or 2-D.
        EmptySpectrumError: If *intensities* has zero elements.
        ValueError: If only one of *signal_range* / *noise_range* is given.
        ValueError: If ranges produce empty slices or are out of bounds.
        ValueError: If spectrum has fewer than 3 points in automatic mode.

    Examples:
        >>> import numpy as np
        >>> from spectrakit import quality_snr
        >>> clean = np.sin(np.linspace(0, 2 * np.pi, 200))
        >>> quality_snr(clean)  # doctest: +SKIP
        32.1...
    """
    intensities = ensure_float64(intensities)
    validate_1d_or_2d(intensities)
    warn_if_not_finite(intensities)

    has_signal = signal_range is not None
    has_noise = noise_range is not None
    if has_signal != has_noise:
        raise ValueError("signal_range and noise_range must both be provided or both omitted")

    if has_signal and has_noise:
        assert signal_range is not None  # for type narrowing
        assert noise_range is not None
        return _snr_explicit(intensities, signal_range, noise_range)

    return _snr_auto(intensities)

spectrakit.quality.quality_roughness

quality_roughness(
    intensities: ndarray, *, order: int = DEFAULT_ORDER
) -> float | np.ndarray

Compute spectral roughness as the RMS of finite differences.

Lower values indicate smoother spectra; higher values indicate noisier or spikier data. Useful for comparing preprocessing outcomes or flagging low-quality acquisitions.

Parameters:

Name Type Description Default
intensities ndarray

Spectral intensities, shape (W,) or (N, W).

required
order int

Finite difference order (1 = first difference, 2 = second, etc.). Default is 1.

DEFAULT_ORDER

Returns:

Type Description
float | ndarray

Roughness value. Scalar float for 1-D input, array of shape

float | ndarray

(N,) for a 2-D batch.

Raises:

Type Description
SpectrumShapeError

If intensities is not 1-D or 2-D.

EmptySpectrumError

If intensities has zero elements.

ValueError

If order < 1 or order >= spectrum length.

Examples:

>>> import numpy as np
>>> from spectrakit import quality_roughness
>>> smooth = np.sin(np.linspace(0, 2 * np.pi, 100))
>>> quality_roughness(smooth)
0.044...
Source code in src/spectrakit/quality/roughness.py
def quality_roughness(
    intensities: np.ndarray,
    *,
    order: int = DEFAULT_ORDER,
) -> float | np.ndarray:
    """Compute spectral roughness as the RMS of finite differences.

    Lower values indicate smoother spectra; higher values indicate
    noisier or spikier data.  Useful for comparing preprocessing
    outcomes or flagging low-quality acquisitions.

    Args:
        intensities: Spectral intensities, shape ``(W,)`` or ``(N, W)``.
        order: Finite difference order (1 = first difference, 2 = second,
            etc.).  Default is 1.

    Returns:
        Roughness value.  Scalar ``float`` for 1-D input, array of shape
        ``(N,)`` for a 2-D batch.

    Raises:
        SpectrumShapeError: If *intensities* is not 1-D or 2-D.
        EmptySpectrumError: If *intensities* has zero elements.
        ValueError: If *order* < 1 or *order* >= spectrum length.

    Examples:
        >>> import numpy as np
        >>> from spectrakit import quality_roughness
        >>> smooth = np.sin(np.linspace(0, 2 * np.pi, 100))
        >>> quality_roughness(smooth)  # doctest: +SKIP
        0.044...
    """
    intensities = ensure_float64(intensities)
    validate_1d_or_2d(intensities)
    warn_if_not_finite(intensities)

    if order < 1:
        raise ValueError(f"order must be >= 1, got {order}")

    width = intensities.shape[-1]
    if order >= width:
        raise ValueError(f"order ({order}) must be less than spectrum length ({width})")

    if intensities.ndim == 1:
        diffs = np.diff(intensities, n=order)
        return float(np.sqrt(np.mean(diffs**2)))

    # 2-D: vectorized along axis=1
    diffs = np.diff(intensities, n=order, axis=1)
    return np.sqrt(np.mean(diffs**2, axis=1))  # type: ignore[return-value]