Source code for sails.orthogonalise

#!/usr/bin/python

import numpy as np
from scipy import linalg


def closest_orthogonal(A, verbose=False):
    """Implementation of the closest-orthonormal orthogonalisation method
    defined in [Colclough2015]_

    Parameters
    ----------
    A : ndarray
        Data matrix to orthogonalise, of size [channels, samples]
    verbose : bool
        Flag indicating whether to show detailed output (Default value = False)

    Returns
    -------

    data : numpy.ndarray [channels, samples] of orthogonalised data

    d : numpy.ndarray

    rho : numpy.ndarray

    W : numpy.ndarray

    """

    A = A.T

    U, S, V = linalg.svd(A, full_matrices=False)
    tol = S.max() * max(A.shape) * np.finfo(S.dtype).eps

    # Settings
    max_iter = 300
    it = 0

    # Initialise d to ones, we start with the closest orthonormal matrix
    d = np.ones((A.shape[1],))

    rho = np.zeros((max_iter, 1)) * np.nan

    while it < max_iter:

        # Find orthonormal polar factor
        V, _ = symmetric_orthonormal(A.dot(np.diag(d)).T)

        # Minimise rho w.r.t d
        d = np.diag(np.conj(A).T.dot(V.T))

        # New best estimre
        L = V.T.dot(np.diag(d))

        # Error term
        E = A - L
        rho[it] = np.sum(np.diag(E.T.dot(E)))

        if verbose:
            print('iter: %4d\trho: %g' % (it, rho[it]))

        if it > 1 and np.abs(rho[it] - rho[it-1]) <= tol:
            break

        it += 1

    # Finally calculate the linear operator
    _, W = symmetric_orthonormal(A.dot(np.diag(d)).T)
    W = np.diag(d).dot(W).dot(np.diag(d))

    if it == max_iter:  # pragma: nocover
        # ? Turn this into an exception?
        print("MaxIterationsHit: the results my not be optimal")

    # Tidy up the rho vector
    rho = rho[~np.isnan(rho)]

    return L.T, d, rho, W


[docs] def symmetric_orthonormal(A, maintain_mag=False): """Implement the [Colclough2015]_ algorithm for multivariate leakage correction in MEG. Also see the OHBA toolbox (https://github.com/OHBA-analysis/MEG-ROI-nets) and http://empslocal.ex.ac.uk/people/staff/reverson/uploads/Site/procrustes.pdf Parameters ---------- A : ndarray Data matrix to orthogonalise, of size [channels, samples] verbose : bool Flag indicating whether to show detailed output (Default value = False) maintain_mag : bool Flag indicating whether to maintain magnitudes (Default value = False) Returns ------- data : numpy.ndarray [channels, samples] of orthogonalised data W : numpy.ndarray """ Adecom = A.T # Do we need to adjust for magnitude? if maintain_mag: D = np.diag(np.sqrt(np.diag(A.dot(A.T)))) Adecom = A.T.dot(D) # Compute svd U, S, V = linalg.svd(Adecom, full_matrices=False) # This is equivalent to np.linalg.matrix_rank tol = S.max() * max(A.shape) * np.finfo(S.dtype).eps rank = sum(S > tol) if rank < A.shape[0]: raise Exception('Input is not full rank!') # Not conj(transpose(V)) in python as V is already transposed Lnorm = U.dot(V.conj()) W = V.T.dot(np.diag(1.0 / S)).dot(V.conj()) if maintain_mag: Lnorm = Lnorm.dot(D) W = D.dot(W).dot(D) # Ensure that we return in the same order as we were input return Lnorm.T, W
[docs] def innovations(A, order, mvar_class=None, verbose=False): """Implementation of the innovations-orthogonalisation defined in [Pascual-Marqui2017]_ . This creates the mixing matrix on the input data after variance explained by an MVAR model is removed. The orthogonalisation is defined on the residuals but applied to the raw data. Parameters ---------- A : ndarray Data matrix to orthogonalise, of size [channels, samples] order : int Model order used during MVAR model fit mvar_class : sails LinearModel class Model class to do model fitting (Default value = None) verbose : bool Flag indicating whether to show detailed output (Default value = False) Returns ------- ndarray : Orthogonalised data matrix, of size [channels, samples] """ if mvar_class is None: from .modelfit import VieiraMorfLinearModel mvar_class = VieiraMorfLinearModel() # Fit AR model to raw data delay_vect = np.arange(order) m = mvar_class.fit_model(A[:, :, None], delay_vect) # Get innovations time-series (aka residuals) from sails.modelfit import get_residuals innovs = get_residuals(A[:, :, None], m.parameters, delay_vect) # Orthogonalise innovations orth_innovs, _, _, _ = closest_orthogonal(innovs[:, :, 0]) # Channels x Channels mixing matrix M = np.linalg.pinv(orth_innovs.T).dot(innovs[:, :, 0].T) # Apply (un)mixing matrix and return orthogonalised data return A.T.dot(M).T