Mean-centred analysis of volume source data#

This example demonstrates how we to use mean-centred PLS to identify condition-wise differences in source time courses in a volumetric source space.

Loading data#

We will begin by loading the sample data and extracting epochs corresponding to auditory stimuli presented to the left versus right ear.

[1]:
import mne
from mne.datasets import sample
from mne.minimum_norm import apply_inverse, apply_inverse_epochs, read_inverse_operator
import mne_plsc

data_path = sample.data_path()
meg_path = data_path / "MEG" / "sample"
fname_raw = meg_path / "sample_audvis_filt-0-40_raw.fif"
fname_event = meg_path / "sample_audvis_filt-0-40_raw-eve.fif"

# Load data
raw = mne.io.read_raw_fif(fname_raw)
events = mne.read_events(fname_event)

# pick MEG channels
picks = mne.pick_types(
    raw.info, meg=True, eeg=False, stim=False, eog=True, exclude="bads"
)
# Read epochs
epochs = mne.Epochs(
    raw,
    events,
    event_id={'left': 1, 'right': 2},
    tmin=-0.2, tmax=0.5,
    picks=picks,
    baseline=(None, 0),
    reject=dict(mag=4e-12, grad=4000e-13, eog=150e-6),
    preload=True,
    verbose=False
)
# Crop to a small window for speed
epochs = epochs.crop(tmin=0.05, tmax=0.1)
Opening raw data file /home/docs/mne_data/MNE-sample-data/MEG/sample/sample_audvis_filt-0-40_raw.fif...
    Read a total of 4 projection items:
        PCA-v1 (1 x 102)  idle
        PCA-v2 (1 x 102)  idle
        PCA-v3 (1 x 102)  idle
        Average EEG reference (1 x 60)  idle
    Range : 6450 ... 48149 =     42.956 ...   320.665 secs
Ready.

Computing volume source estimate#

Next, we will load the pre-computed inverse operator for the sample dataset and apply it to the epochs. This yields a list of source time courses (STCs).

[2]:
fname_inv = meg_path / "sample_audvis-meg-vol-7-meg-inv.fif"
inverse_operator = read_inverse_operator(fname_inv, verbose=False)

snr = 3.0
lambda2 = 1.0 / snr**2
stcs = apply_inverse_epochs(
    epochs=epochs,
    inverse_operator=inverse_operator,
    lambda2=lambda2,
    method='dSPM',
    verbose=False
)

Fitting and assessing mean-centred PLS model#

Next, we will get the labels for the epochs and use them to fit a mean-centred PLS model. In order to render a 4D image of the brain saliences, we will attach info about the source space to the model. For a volumetric source space, this means adding the VolSourceSpaces object corresponding to the inverse operator.

[3]:
labels = mne_plsc.utils.get_epoch_labels(epochs)
res = mne_plsc.fit_mc(stcs,
                      between=labels,
                      random_state=123)
res.add_source_info(src=inverse_operator['src'])
res.plot_lv(0)
Assuming time-domain source-space data. If data is actually freq- or time-freq-domain, specify this explicitly
[3]:
(<Figure size 640x480 with 3 Axes>,
 array([<Axes: xlabel='Time (s)', ylabel='Axial slice'>,
        <Axes: ylabel='Brain score'>], dtype=object))
../_images/notebooks_mc-vol-stc_6_2.png

We can assess the significance of the model using permutation testing. We will do a small number of permutations here for speed, but for real data it would be best to do many more.

[4]:
res.permute(100)
print(res.summary())
Getting permutations: 100%|██████████| 100/100 [00:00<00:00, 122712.23it/s]
Permuting: 100%|██████████| 100/100 [00:00<00:00, 238.41it/s]
   LV index  singular value  variance explained   p value
0         0       22.784547                 1.0  0.009901
1         1        0.000000                 0.0       NaN

Thus, the model has identified a significant contrast between the conditions.

Cluster analysis#

To characterize the pattern that differentiates the two conditions, we can perform cluster analysis. First, we will perform bootstrap resampling to estimate the \(z\) scores of the brain saliences.

[5]:
res.bootstrap(100)
Getting resamples: 100%|██████████| 100/100 [00:00<00:00, 6954.00it/s]
Resampling: 100%|██████████| 100/100 [00:00<00:00, 108.04it/s]

Next, we can add an adjacency matrix and compute clusters. Computing adjacency requires adding source info (via add_source_info), which we did before. As the plot below shows, there are two major clusters in the data—each other cluster accounts for less than 10% of the strong saliences.

[6]:
res.add_adjacency()
res.cluster(threshold=3)
f, ax = res.plot_cluster_sizes(lv_idx=0)
ax.axhline(y=10, c='k', ls=':')
-- number of adjacent vertices : 3757
Clustering z-scores
Defaulting to unsigned clustering
Computing clusters for lv_idx 0...
Threshold: 3
57 clusters
[6]:
<matplotlib.lines.Line2D at 0x7312e9fa8f50>
../_images/notebooks_mc-vol-stc_13_2.png

To visualize clusters, we will attach the subject’s MRI to the model (which will then be used as a background image). Visualizing these first two clusters, we can see that they overlap with the left and right auditory cortex.

[7]:
res.add_source_info(mri=data_path / "subjects" / "sample" / "mri" / "T1.mgz")
res.plot_cluster_spatial(lv_idx=0, cluster_idx=0, highlight='extent')
res.plot_cluster_spatial(lv_idx=0, cluster_idx=1, highlight='extent')
[7]:
(<Figure size 640x480 with 5 Axes>, <Axes: >)
../_images/notebooks_mc-vol-stc_15_1.png
../_images/notebooks_mc-vol-stc_15_2.png