Skip to content

Conversation

shristibaral
Copy link
Contributor

@shristibaral shristibaral commented Aug 10, 2025

What does this implement/fix?

I improved the existing statistical cluster plot by adding a new function that plots the cluster boundary to its spatial extent on top of the brain.

@shristibaral shristibaral changed the title ENH: Add function to plot statistical clusters on brain surface ENH: Add function to plot statistical clusters on brain surface (#13366) Aug 10, 2025
@shristibaral shristibaral changed the title ENH: Add function to plot statistical clusters on brain surface (#13366) ENH: Add function to plot statistical clusters on brain surface Aug 10, 2025
@shristibaral shristibaral marked this pull request as ready for review August 11, 2025 18:54
@drammock
Copy link
Member

Thanks for the contribution! For a big change like this, it's usually best to open an issue first to discuss the API. We'll discuss this one at our next maintainer meeting and report back. Meanwhile: if I view the last image in the rendered tutorial (https://output.circle-artifacts.com/output/job/77fa0002-c504-4110-a95d-0a57460ffe05/artifacts/0/html/auto_tutorials/stats-source-space/20_cluster_1samp_spatiotemporal.html#visualize-the-clusters), there doesn't appear to be any magenta-colored boundary on the brain? So not clear that this function is actually working as intended.

@shristibaral
Copy link
Contributor Author

Thanks for the contribution! For a big change like this, it's usually best to open an issue first to discuss the API. We'll discuss this one at our next maintainer meeting and report back.

Apologies. I will open issue next time. :)

Meanwhile: if I view the last image in the rendered tutorial (https://output.circle-artifacts.com/output/job/77fa0002-c504-4110-a95d-0a57460ffe05/artifacts/0/html/auto_tutorials/stats-source-space/20_cluster_1samp_spatiotemporal.html#visualize-the-clusters), there doesn't appear to be any magenta-colored boundary on the brain? So not clear that this function is actually working as intended.

I have updated this part with a new cluster index, the boundary is visible now.

Copy link
Contributor

@wmvanvliet wmvanvliet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good start! Now we need the unit test to test different scenarios:

  • Explicitly check that the proper vertices are included in the label
  • Plotting multiple clusters and checking the auto-generated names
  • Selecting different time points and checking that the cluster boundaries change

@@ -0,0 +1 @@
Make :func:`~mne.viz.plot_stat_cluster` that plots spatial extent of a cluster on top of a brain by `Shristi Baral`_.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Make :func:`~mne.viz.plot_stat_cluster` that plots spatial extent of a cluster on top of a brain by `Shristi Baral`_.
Add :func:`~mne.viz.plot_stat_cluster` that plots the spatial extent of a cluster on top of a brain by `Shristi Baral`_.

Comment on lines +4344 to +4345
"A cluster is a tuple of two elements, a list time indices "
"and list of vertex indices."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"A cluster is a tuple of two elements, a list time indices "
"and list of vertex indices."
"A cluster is a tuple of two elements: an array of time indices "
"and an array of vertex indices."

mne/viz/_3d.py Outdated
Comment on lines 4371 to 4374
# Let's create an anatomical label containing these vertex indices.
# Problem 1): a label must be defined for either the left or right hemisphere. It
# cannot span both hemispheres. So we must filter the vertices based on their
# hemisphere.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Let's create an anatomical label containing these vertex indices.
# Problem 1): a label must be defined for either the left or right hemisphere. It
# cannot span both hemispheres. So we must filter the vertices based on their
# hemisphere.
# Create the anatomical label containing the vertex indices belonging to the cluster.
# A label cannot span both hemispheres. So we must filter the vertices based on their
# hemisphere.

mne/viz/_3d.py Outdated
Comment on lines 4403 to 4406
# Problem 2): We have vertex *indices* that need to be transformed into proper
# vertex numbers. Not every vertex in the original high-resolution brain mesh is a
# source point in the source estimate. Do draw nice smooth curves, we need to
# interpolate the vertex indices.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Problem 2): We have vertex *indices* that need to be transformed into proper
# vertex numbers. Not every vertex in the original high-resolution brain mesh is a
# source point in the source estimate. Do draw nice smooth curves, we need to
# interpolate the vertex indices.
# Transform vertex indices into proper vertex numbers.
# Not every vertex in the original high-resolution brain mesh is a
# source point in the source estimate. Do draw nice smooth curves, we need to
# interpolate the vertex indices.

@testing.requires_testing_data
def test_plot_stat_cluster(renderer_interactive):
"""Test plotting clusters on brain in static and interactive mode."""
pytest.importorskip("nibabel")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is nibabel necessary?

Comment on lines 1468 to 1481
for key in ("lh", "rh"):
for attr, desc in [
("labels", "brain.labels"),
("_hemis", "brain._hemis"),
("_layered_meshes", "brain._layered_meshes"),
]:
if key not in getattr(brain, attr):
missing.append(f"{key} is missing from '{desc}'")
if not brain._subject:
missing.append("Subject name is missing from brain._subject")
if not brain._subjects_dir:
missing.append("Subject directory path is missing from brain._subjects_dir")
if brain._times is None or brain._times.size == 0:
missing.append("Time is missing from brain._times")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for key in ("lh", "rh"):
for attr, desc in [
("labels", "brain.labels"),
("_hemis", "brain._hemis"),
("_layered_meshes", "brain._layered_meshes"),
]:
if key not in getattr(brain, attr):
missing.append(f"{key} is missing from '{desc}'")
if not brain._subject:
missing.append("Subject name is missing from brain._subject")
if not brain._subjects_dir:
missing.append("Subject directory path is missing from brain._subjects_dir")
if brain._times is None or brain._times.size == 0:
missing.append("Time is missing from brain._times")
# Check that the proper anatomical label has been constructed.
assert len(brain.labels["lh"] == 1
assert len(brain.labels["rh"] == 0
assert brain.labels["lh"][0].name == "cluster-0"

In unit tests, it is not necessary to produce user-friendly error messages and you can just assert. Also, I think we can get away with just verifying the label is there and not bother with checking other Brain related functionality such as the presence of _hemis and _layered_meshes as those things are tested in other unit tests.

n_time = 5
n_verts = sum(len(v) for v in vertices)

# simulate stc data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this test, I don't think it's actually needed to have STC data, we could just do:

brain = Brain("sample", subjects_dir=subjects_dir, surface="white")

Comment on lines 269 to 272
# Alternatively, you may wish to observe clusters are considered statistically
# significant under the permutation distribution with resect all the source estimates.
# This can easily be done by plotting the cluster boundary on top of the source
# estimates using the code snippet below.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Alternatively, you may wish to observe clusters are considered statistically
# significant under the permutation distribution with resect all the source estimates.
# This can easily be done by plotting the cluster boundary on top of the source
# estimates using the code snippet below.
# Alternatively, you may wish to observe the spatial and temporal extent of
# single clusters. The code below demonstrates how to plot the cluster
# boundary on top of an existing source estimate.

initial_time=0.1,
)

# We are plotting only one clusters here for illustration purpose.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# We are plotting only one clusters here for illustration purpose.
# Plot one cluster at the time of maximal spatial extent of that cluster.

good_clusters[2], src, difference_plot, time="max-extent", color="magenta", width=1
)

# Plotting the same cluster on the interactive mode for illustration purpose.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Plotting the same cluster on the interactive mode for illustration purpose.
# %%
Plotting the cluster in interactive mode allows scrolling through time.

@wmvanvliet
Copy link
Contributor

Apologies. I will open issue next time. :)

Why wait? You can do so now :)

@shristibaral
Copy link
Contributor Author

A good start! Now we need the unit test to test different scenarios:

  • Explicitly check that the proper vertices are included in the label
  • Plotting multiple clusters and checking the auto-generated names
  • Selecting different time points and checking that the cluster boundaries change

WIP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants