Aluode's picture
Upload folder using huggingface_hub
3bb804c verified
# Authors: The MNE-Python contributors.
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.
import numpy as np
import scipy.linalg
from ..cov import Covariance, _smart_eigh, compute_whitener
def _handle_restr_mat(C_ref, restr_type, info, rank):
"""Get restricting matrix to C_ref rank-dimensional principal subspace.
Returns matrix of shape (rank, n_chs) used to restrict or
restrict+rescale (whiten) covariances matrices.
"""
if C_ref is None or restr_type is None:
return None
if restr_type == "whitening":
C_ref_cov = Covariance(C_ref, info.ch_names, info["bads"], info["projs"], 0)
restr_mat = compute_whitener(
C_ref_cov, info, rank=rank, pca=True, verbose="error"
)[0]
elif restr_type == "restricting":
restr_mat = _get_restr_mat(C_ref, info, rank)
else:
raise ValueError(
"restr_type should either be callable or one of "
"('whitening', 'restricting')"
)
return restr_mat
def _smart_ged(S, R, restr_mat=None, R_func=None):
"""Perform smart generalized eigenvalue decomposition (GED) of S and R.
If restr_mat is provided S and R will be restricted to the principal subspace
of a reference matrix with rank r (see _handle_restr_mat), then GED is performed
on the restricted S and R and then generalized eigenvectors are transformed back
to the original space. The g-eigenvectors matrix is of shape (n_chs, r).
If callable R_func is provided the GED will be performed on (S, R_func(S,R))
"""
if restr_mat is None:
evals, evecs = scipy.linalg.eigh(S, R)
return evals, evecs
S_restr = restr_mat @ S @ restr_mat.T
R_restr = restr_mat @ R @ restr_mat.T
if R_func is not None:
R_restr = R_func([S_restr, R_restr])
evals, evecs_restr = scipy.linalg.eigh(S_restr, R_restr)
evecs = restr_mat.T @ evecs_restr
return evals, evecs
def _is_cov_symm(cov, rtol=1e-7, atol=None):
if atol is None:
atol = 1e-7 * np.max(np.abs(cov))
is_symm = scipy.linalg.issymmetric(cov, rtol=rtol, atol=atol)
return is_symm
def _get_cov_def(cov, eval_tol=None):
"""Get definiteness of symmetric cov matrix.
All evals in (-eval_tol, eval_tol) will be considered zero,
while all evals smaller than -eval_tol will be considered
negative.
"""
evals = scipy.linalg.eigvalsh(cov)
if eval_tol is None:
eval_tol = 1e-7 * np.max(np.abs(evals))
if np.all(evals > eval_tol):
return "pos_def"
elif np.all(evals >= -eval_tol):
return "pos_semidef"
else:
return "indef"
def _is_cov_pos_semidef(cov, eval_tol=None):
cov_def = _get_cov_def(cov, eval_tol=eval_tol)
return cov_def in ("pos_def", "pos_semidef")
def _is_cov_pos_def(cov, eval_tol=None):
cov_def = _get_cov_def(cov, eval_tol=eval_tol)
return cov_def == "pos_def"
def _smart_ajd(covs, restr_mat=None, weights=None):
"""Perform smart approximate joint diagonalization.
If restr_mat is provided all the cov matrices will be restricted to the
principal subspace of a reference matrix with rank r (see _handle_restr_mat),
then GED is performed on the restricted S and R and then generalized eigenvectors
are transformed back to the original space.
The matrix of generalized eigenvectors is of shape (n_chs, r).
"""
from .csp import _ajd_pham
if restr_mat is None:
are_all_pos_def = all([_is_cov_pos_def(cov) for cov in covs])
if not are_all_pos_def:
raise ValueError(
"If C_ref is not provided by covariance estimator, "
"all the covs should be positive definite"
)
evecs, D = _ajd_pham(covs)
return evecs
else:
are_all_pos_semidef = all([_is_cov_pos_semidef(cov) for cov in covs])
if not are_all_pos_semidef:
raise ValueError(
"All the covs should be positive semi-definite for "
"approximate joint diagonalization"
)
covs = np.array([restr_mat @ cov @ restr_mat.T for cov in covs], float)
evecs_restr, D = _ajd_pham(covs)
evecs = _normalize_eigenvectors(evecs_restr.T, covs, weights)
evecs = restr_mat.T @ evecs
return evecs
def _get_restr_mat(C, info, rank):
"""Get matrix restricting covariance to rank-dimensional principal subspace of C."""
_, ref_evecs, mask = _smart_eigh(
C,
info,
rank,
proj_subspace=True,
do_compute_rank=False,
log_ch_type="data",
)
restr_mat = ref_evecs[mask]
return restr_mat
def _normalize_eigenvectors(evecs, covs, sample_weights):
# Here we apply an euclidean mean. See pyRiemann for other metrics
mean_cov = np.average(covs, axis=0, weights=sample_weights)
for ii in range(evecs.shape[1]):
tmp = np.dot(np.dot(evecs[:, ii].T, mean_cov), evecs[:, ii])
evecs[:, ii] /= np.sqrt(tmp)
return evecs