Skip to content

TFCE Calculation Error #14224

Description

@charfisher

Description of the problem

The outputs of mne.stats.permutation_cluster_1samp_test() do not report the correct clusters when TFCE is being used. The returned "t_obs" variable does not appear to be the t statistics, rather the TFCE values (although these are incorrect). Additionally, the reported number of clusters is equal to the number of time samples. I believe I have found part of the issue. However, since this function is being used with other functions, I do not want to push this to git in case it affects other related functions. All of these issues are in _find_clusters() within mne.stats.cluster_level.py

Issue 1: Extent of cluster is incorrectly calculated

Lines 472-482,

for c in clusters:
      if isinstance(c, slice):
          len_c = c.stop - c.start
      elif isinstance(c, tuple):
          len_c = len(c)
      elif c.dtype == np.dtype(bool):
          len_c = np.sum(c)
      else:
          len_c = len(c)
      scores[c] += h * (len_c**e_power)

Each c is a tuple, but the tuple contains a singular slice. Therefore, len_c is 1 regardless of the length of the cluster.

Issue 2: Height of cluster is incorrectly calculated

Lines 465-471,

# the score of each point is the sum of the h^H * e^E for each
# supporting section "rectangle" h x e.
if ti == 0:
    h = abs(thresh)
else:
    h = abs(thresh - thresholds[ti - 1])
h = h**h_power 

I believe this should be:

# the score of each point is the sum of the h^H * e^E for each
# supporting section "rectangle" h x e.
if ti == 0:
    dh = abs(thresh)
else:
    dh = abs(thresh - thresholds[ti - 1])
h = (abs(thresh)**h_power) * dh

Issue 3: Incorrect number of clusters reported

This issue still remains. I didn't look further into this.

Steps to reproduce

import numpy as np
import matplotlib.pyplot as plt
import scipy
import mne
from mne.io import concatenate_raws, read_raw_edf
from mne.datasets import eegbci

subjects = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
runs = [6, 10, 14] 
sfreq = 160
tmin = -1
tmax = 4

# gets an ERP for each subject from -1 to 4s 
erp = np.zeros((len(subjects), int((tmax-tmin)*sfreq) + 1))

for i in range(0, len(subjects)):
    raw_fnames = eegbci.load_data(subjects[i], runs)
    raws = [read_raw_edf(f, preload=True) for f in raw_fnames]
    # concatenate runs from subject
    raw = concatenate_raws(raws)

    raw.annotations.rename(dict(T1="hands", T2="feet"))  # as documented on PhysioNet
    raw.set_eeg_reference(projection=True)

    # Apply band-pass filter
    raw.filter(1., 10.0, fir_design="firwin", skip_by_annotation="edge")

    picks = mne.pick_types(raw.info, meg=False, eeg=True, stim=False, eog=False, exclude="bads")
    
    # Read epochs (train will be done only between 1 and 2s)
    # Testing will be done with a running classifier
    epochs = mne.Epochs(
        raw,
        event_id=["hands", "feet"],
        tmin=tmin,
        tmax=tmax,
        proj=True,
        picks=picks,
        baseline=None,
        preload=True,
    )
    
    erp[i,:] = np.mean(epochs['hands'].get_data(), axis=(0,1)) # average over epochs and channels for "hands" trial

thresh = {'start': 0, 'step': 0.1}

t_obs, clusters, p_vals, h0 = mne.stats.permutation_cluster_1samp_test(erp, threshold=thresh, tail=0, adjacency=None, max_step=1)
time = np.linspace(tmin, tmax, int((tmax-tmin)*sfreq) + 1)

plt.figure()
plt.plot(time, erp.T)
plt.ylabel("Ampltiude")
plt.xlabel("Time (s)")
plt.title("ERP for each subject")

plt.figure()
plt.plot(time, scipy.stats.ttest_1samp(erp, popmean=0).statistic)
plt.ylabel("t-values from scipy")
plt.xlabel("Time (s)")

plt.figure()
plt.plot(time, t_obs)
plt.ylabel("'t-values' from MNE with step = 0.1")
plt.xlabel("Time (s)")

thresh = {'start': 0, 'step': 1}
t_obs, clusters, p_vals, h0 = mne.stats.permutation_cluster_1samp_test(erp, threshold=thresh, tail=0, adjacency=None, max_step=1)

plt.figure()
plt.plot(time, t_obs)
plt.ylabel("'t-values' from MNE with step = 0.5")
plt.xlabel("Time (s)")

Link to data

No response

Expected results

There should be a cluster around 500 ms. The TFCE values should look something like this:

Image

Actual results

This is what the TFCE values (or "t_obs") looks like:

Image

Additional information

Platform macOS-26.6.2-arm64-arm-64bit
Python 3.10.13 | packaged by conda-forge | (main, Dec 23 2023, 15:35:25) [Clang 16.0.6 ]
Executable /Users/charlief/miniconda3/bin/python
CPU Apple M3 Ultra (28 cores)
Memory 96.0 GiB

Core
├☑ mne 1.12.1 (latest release)
├☑ numpy 2.2.6 (unknown linalg bindings (threadpoolctl module not found: No module named 'threadpoolctl'))
├☑ scipy 1.15.3
└☑ matplotlib 3.10.9 (backend=module://matplotlib_inline.backend_inline)

Numerical (optional)
└☐ unavailable sklearn, numba, nibabel, nilearn, dipy, openmeeg, cupy, pandas, h5io, h5py

Visualization (optional)
├☑ qtpy 2.4.3 (PyQt5=5.15.15)
└☐ unavailable pyvista, pyvistaqt, vtk, ipympl, pyqtgraph, mne-qt-browser, ipywidgets, trame_client, trame_server, trame_vtk, trame_vuetify

Ecosystem (optional)
├☑ defusedxml 0.7.1
└☐ unavailable mne-bids, mne-nirs, mne-features, mne-connectivity, mne-icalabel, mne-bids-pipeline, neo, eeglabio, edfio, curryreader, mffpy, pybv, pymef, antio

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions