Spaces:
Running
Running
| # 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 | |