Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of Block Covariance estimation #154

Merged
merged 27 commits into from Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d27e99e
fix for new sklearn version
Mar 29, 2021
5ca7137
fetch upstream
gabelstein Nov 19, 2021
53cf78e
Added Tests and all for block covariance estimation.
gabelstein Dec 8, 2021
93a3398
Merge branch 'master' of https://github.com/pyRiemann/pyRiemann into …
gabelstein Dec 8, 2021
111baaf
Apply suggestions from code review
gabelstein Dec 10, 2021
0716fdb
flake8
gabelstein Dec 10, 2021
3ec030b
Update whatsnew.rst
gabelstein Dec 10, 2021
e571aba
Merge branch 'block_cov' of https://github.com/gabelstein/pyRiemann i…
gabelstein Dec 10, 2021
081eb80
Update plot_classify_ssvep_mdm.py
gabelstein Dec 10, 2021
0f1abae
Merge branch 'master' into block_cov
gabelstein Dec 13, 2021
7d035dd
CI
gabelstein Dec 13, 2021
fd6d793
Apply suggestions from code review
gabelstein Dec 13, 2021
8ecafac
Fix Docstrings
gabelstein Dec 13, 2021
0b189b8
flake8
gabelstein Dec 13, 2021
7505318
Extended Docstrings
gabelstein Dec 14, 2021
b8945dd
set whatsnew in the chronological order
qbarthelemy Dec 14, 2021
5e41262
Apply suggestions from code review
gabelstein Dec 15, 2021
713fbb3
Move valid block size checking to block_covariances
gabelstein Dec 15, 2021
7f6fea5
Delete workspace.xml
gabelstein Dec 15, 2021
ebf00fc
suggestions
gabelstein Dec 16, 2021
006edfa
flake8
gabelstein Dec 17, 2021
455e040
Update pyriemann/utils/covariance.py
gabelstein Dec 17, 2021
75821a9
Merge branch 'block_cov' of https://github.com/gabelstein/pyRiemann i…
gabelstein Dec 17, 2021
c836d1e
Update test_estimation.py
gabelstein Dec 17, 2021
7be9818
move contribution and tests
qbarthelemy Dec 17, 2021
9a59c11
complete doc
qbarthelemy Dec 17, 2021
857e6df
correct flake8
qbarthelemy Dec 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/api.rst
Expand Up @@ -16,6 +16,7 @@ Covariance Estimation
Covariances
ERPCovariances
XdawnCovariances
BlockCovariances
CospCovariances
Coherences
HankelCovariances
Expand Down Expand Up @@ -153,6 +154,7 @@ Covariance preprocessing
covariances
covariances_EP
covariances_X
block_covariances
cross_spectrum
cospectrum
coherence
Expand Down
2 changes: 2 additions & 0 deletions doc/whatsnew.rst
Expand Up @@ -38,6 +38,8 @@ v0.2.8.dev

- Add ``corr`` option in :func:`pyriemann.utils.covariance.normalize`, to normalize covariance into correlation matrices

- Add block covariance matrix: :class:`pyriemann.estimation.BlockCovariances` and :func:`pyriemann.utils.covariance.block_covariances`

v0.2.7 (June 2021)
------------------

Expand Down
5 changes: 3 additions & 2 deletions examples/SSVEP/plot_classify_ssvep_mdm.py
Expand Up @@ -23,7 +23,7 @@
from mne.datasets import fetch_dataset

# pyriemann import
from pyriemann.estimation import Covariances
from pyriemann.estimation import BlockCovariances
from pyriemann.utils.mean import mean_riemann
from pyriemann.classification import MDM

Expand Down Expand Up @@ -190,7 +190,8 @@ def _bandpass_filter(signal, lowcut, highcut):
# The covariance matrices will be estimated using the Ledoit-Wolf shrinkage
# estimator on the extended signal.

cov_ext_trials = Covariances(estimator='lwf').transform(epochs.get_data())
cov_ext_trials = BlockCovariances(estimator='lwf',
block_size=8).transform(epochs.get_data())

# This plot shows an example of a covariance matrix observed for each class:

Expand Down
196 changes: 139 additions & 57 deletions pyriemann/estimation.py
Expand Up @@ -3,7 +3,7 @@

from .spatialfilters import Xdawn
from .utils.covariance import (covariances, covariances_EP, cospectrum,
coherence)
coherence, block_covariances)
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.covariance import shrunk_covariance

Expand All @@ -19,7 +19,7 @@ def _nextpow2(i):
class Covariances(BaseEstimator, TransformerMixin):
"""Estimation of covariance matrix.

Perform a simple covariance matrix estimation for each given trial.
Perform a simple covariance matrix estimation for each given input.

Parameters
----------
Expand Down Expand Up @@ -47,10 +47,10 @@ def fit(self, X, y=None):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
y : ndarray shape (n_trials,)
labels corresponding to each trial, not used.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series
y : ndarray shape (n_matrices,)
Labels corresponding to each matrix, not used.

Returns
-------
Expand All @@ -64,13 +64,13 @@ def transform(self, X):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series

Returns
-------
covmats : ndarray, shape (n_trials, n_channels, n_channels)
ndarray of covariance matrices for each trials.
covmats : ndarray, shape (n_matrices, n_channels, n_channels)
Covariance matrices.
"""
covmats = covariances(X, estimator=self.estimator)
return covmats
Expand Down Expand Up @@ -151,10 +151,10 @@ def fit(self, X, y):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
y : ndarray shape (n_trials,)
labels corresponding to each trial.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.
y : ndarray shape (n_matrices,)
Labels corresponding to each matrix.

Returns
-------
Expand Down Expand Up @@ -186,13 +186,13 @@ def transform(self, X):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.

Returns
-------
covmats : ndarray, shape (n_trials, n_c, n_c)
ndarray of covariance matrices for each trials, with n_c the size
covmats : ndarray, shape (n_matrices, n_c, n_c)
Covariance matrices for each input, with n_c the size
of covmats equal to n_channels * (n_classes + 1) in case svd is
None and equal to n_channels + n_classes * svd otherwise.
"""
Expand Down Expand Up @@ -267,10 +267,10 @@ def fit(self, X, y):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
y : ndarray shape (n_trials,)
labels corresponding to each trial.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.
y : ndarray shape (n_matrices,)
Labels corresponding to each matrix.

Returns
-------
Expand All @@ -291,13 +291,13 @@ def transform(self, X):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.

Returns
-------
covmats : ndarray, shape (n_trials, n_c, n_c)
ndarray of covariance matrices for each trials.
covmats : ndarray, shape (n_matrices, n_c, n_c)
Covariance matrices.
"""
if self.applyfilters:
X = self.Xd_.transform(X)
Expand All @@ -306,6 +306,89 @@ def transform(self, X):
return covmats


class BlockCovariances(BaseEstimator, TransformerMixin):
"""Estimation of block covariance matrix.

Perform a block covariance estimation for each given matrix. The
resulting matrices are block diagonal matrices.

The blocks on the diagonal are calculated as individual covariance
matrices for a subset of channels using the given the estimator.
Varying block sized possible by passing a list to allow incorporation
of different modalities with different number of channels (e.g. EEG,
ECoG, LFP, EMG) with their own respective covariance matrices.

Parameters
----------
estimator : {'cov', 'scm', 'lwf', 'oas', 'mcd', 'sch', 'corr'} \
(default: 'scm')
Covariance matrix estimator, see
:func:`pyriemann.utils.covariance.covariances`.
block_size : int | list
Sizes of individual blocks given as int for same-size block, or list
for varying block sizes.

Notes
-----
.. versionadded:: 0.2.8

See Also
--------
Covariances
"""

def __init__(self, block_size, estimator='scm'):
"""Init."""
self.estimator = estimator
self.block_size = block_size

def fit(self, X, y=None):
"""Fit.

Do nothing. For compatibility purpose.

Parameters
----------
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.
y : ndarray shape (n_matrices,)
Labels corresponding to each matrix, not used.

Returns
-------
self : BlockCovariances instance
The BlockCovariances instance.
"""
return self

def transform(self, X):
"""Estimate block covariance matrices.

Parameters
----------
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.

Returns
-------
covmats : ndarray, shape (n_matrices, n_channels, n_channels)
Covariance matrices.
"""
n_matrices, n_channels, n_times = X.shape

if isinstance(self.block_size, int):
n_blocks = n_channels // self.block_size
blocks = [self.block_size for b in range(n_blocks)]

elif isinstance(self.block_size, (list, np.ndarray)):
blocks = self.block_size

else:
raise ValueError("Parameter block_size must be int or list.")

return block_covariances(X, blocks, self.estimator)


###############################################################################


Expand All @@ -315,7 +398,7 @@ class CospCovariances(BaseEstimator, TransformerMixin):
Co-spectral matrices are the real part of complex cross-spectral matrices
(see :func:`pyriemann.utils.covariance.cross_spectrum`), estimated as the
spectrum covariance in the frequency domain. This method returns a 4-d
array with a cospectral covariance matrice for each trial and in each
array with a cospectral covariance matrix for each input and in each
frequency bin of the FFT.

Parameters
Expand Down Expand Up @@ -360,10 +443,10 @@ def fit(self, X, y=None):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
y : ndarray, shape (n_trials,)
labels corresponding to each trial, not used.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.
y : ndarray, shape (n_matrices,)
Labels corresponding to each matrix, not used.

Returns
-------
Expand All @@ -377,14 +460,13 @@ def transform(self, X):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.

Returns
-------
covmats : ndarray, shape (n_trials, n_channels, n_channels, n_freqs)
ndarray of covariance matrices for each trials and for each
frequency bin.
covmats : ndarray, shape (n_matrices, n_channels, n_channels, n_freqs)
Covariance matrices for each input and for each frequency bin.
"""
Nt = len(X)
out = []
Expand All @@ -407,7 +489,7 @@ class Coherences(CospCovariances):
"""Estimation of squared coherence matrices.

Squared coherence matrices estimation [1]_. This method will return a 4-d
array with a squared coherence matrix estimation for each trial and in
array with a squared coherence matrix estimation for each input and in
each frequency bin of the FFT.

Parameters
Expand Down Expand Up @@ -483,13 +565,13 @@ def transform(self, X):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.

Returns
-------
covmats : ndarray, shape (n_trials, n_channels, n_channels, n_freqs)
Squared coherence matrices for each trial and for each frequency
covmats : ndarray, shape (n_matrices, n_channels, n_channels, n_freqs)
Squared coherence matrices for each input and for each frequency
bin.
"""
Nt = len(X)
Expand Down Expand Up @@ -547,10 +629,10 @@ def fit(self, X, y=None):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
y : ndarray shape (n_trials,)
labels corresponding to each trial, not used.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.
y : ndarray shape (n_matrices,)
Labels corresponding to each matrix, not used.

Returns
-------
Expand All @@ -564,13 +646,13 @@ def transform(self, X):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
X : ndarray, shape (n_matrices, n_channels, n_times)
Multi-channel time-series.

Returns
-------
covmats : ndarray, shape (n_trials, n_channels, n_channels)
ndarray of covariance matrices for each trials.
covmats : ndarray, shape (n_matrices, n_channels, n_channels)
Covariance matrices.
"""

if isinstance(self.delays, int):
Expand All @@ -595,7 +677,7 @@ class Shrinkage(BaseEstimator, TransformerMixin):

This transformer apply a shrinkage regularization to any covariance matrix.
It directly use the `shrunk_covariance` function from scikit learn, applied
on each trial.
on each input.

Parameters
----------
Expand All @@ -619,10 +701,10 @@ def fit(self, X, y=None):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
X : ndarray, shape (n_matrices, n_channels, n_times)
ndarray of Target data.
y : ndarray shape (n_trials,)
Labels corresponding to each trial, not used.
y : ndarray shape (n_matrices,)
Labels corresponding to each matrix, not used.

Returns
-------
Expand All @@ -636,13 +718,13 @@ def transform(self, X):

Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_channels)
ndarray of covariances matrices
X : ndarray, shape (n_matrices, n_channels, n_channels)
Covariances matrices

Returns
-------
covmats : ndarray, shape (n_trials, n_channels, n_channels)
ndarray of covariance matrices for each trials.
covmats : ndarray, shape (n_matrices, n_channels, n_channels)
Shrunk covariances matrices.
"""

covmats = np.zeros_like(X)
Expand Down