Source code for mne_denoise.overcorrection
"""Overcorrection metrics for linear denoisers."""
from __future__ import annotations
import numpy as np
__all__ = ["quantify_overcorrection"]
[docs]
def quantify_overcorrection(
operator: np.ndarray, leadfield: np.ndarray
) -> dict[str, np.ndarray]:
"""Compute source-topography distortion metrics for a spatial operator.
Parameters
----------
operator : ndarray, shape (n_channels, n_channels)
Linear spatial filter.
leadfield : ndarray, shape (n_channels, n_sources)
Lead field in the same channel order and reference as operator.
Returns
-------
dict of ndarray
Per-source arrays for:
amplitude_change
(norm(operator @ l) - norm(l)) / norm(l).
correlation
(operator @ l) dot l / (norm(operator @ l) * norm(l)).
relative_error
norm(operator @ l - l) / norm(l).
goodness_of_fit
1 - relative_error**2.
Notes
-----
These are mne-denoise evaluation definitions. Sources with zero input or output
topography produce NaN where the corresponding ratio is undefined.
"""
operator = np.asarray(operator, dtype=float)
leadfield = np.asarray(leadfield, dtype=float)
if operator.ndim != 2 or operator.shape[0] != operator.shape[1]:
raise ValueError(
f"operator must be square (n_channels, n_channels), got {operator.shape}."
)
if leadfield.ndim != 2 or leadfield.shape[0] != operator.shape[0]:
raise ValueError(
f"leadfield has {leadfield.shape[0]} channels but operator has "
f"{operator.shape[0]}."
)
filtered = operator @ leadfield
norm_before = np.linalg.norm(leadfield, axis=0)
norm_after = np.linalg.norm(filtered, axis=0)
# A source with no topography, or one deleted outright, has no meaningful
# relative change; report NaN instead of dividing by zero.
scale = np.where(norm_before > 0, norm_before, np.nan)
cosine_scale = np.where(
(norm_before > 0) & (norm_after > 0), norm_before * norm_after, np.nan
)
relative_error = np.linalg.norm(filtered - leadfield, axis=0) / scale
return {
"amplitude_change": (norm_after - norm_before) / scale,
"correlation": np.einsum("ij,ij->j", filtered, leadfield) / cosine_scale,
"relative_error": relative_error,
"goodness_of_fit": 1.0 - relative_error**2,
}