A short demo of the surface images & maskers

copied from the nilearn sandbox discussion, to be transformed into tests & examples

Note

this example is meant to support discussion around a tentative API for surface images in nilearn. This functionality is provided by the nilearn.experimental.surface module; it is still incomplete and subject to change without a deprecation cycle. Please participate in the discussion on GitHub!

try:
    import matplotlib.pyplot as plt
except ImportError:
    raise RuntimeError("This script needs the matplotlib library")
import numpy as np

from nilearn.experimental import plotting
from nilearn.experimental.surface import SurfaceMasker, fetch_nki
from nilearn.plotting import plot_matrix

img = fetch_nki()[0]
print(f"NKI image: {img}")

masker = SurfaceMasker()
masked_data = masker.fit_transform(img)
print(f"Masked data shape: {masked_data.shape}")

mean_data = masked_data.mean(axis=0)
mean_img = masker.inverse_transform(mean_data)
print(f"Image mean: {mean_img}")

# let's create a figure with all the views for both hemispheres
views = ["lateral", "medial", "dorsal", "ventral", "anterior", "posterior"]
hemispheres = ["left", "right"]

fig, axes = plt.subplots(
    len(views),
    len(hemispheres),
    subplot_kw={"projection": "3d"},
    figsize=(4 * len(hemispheres), 4),
)
axes = np.atleast_2d(axes)

for view, ax_row in zip(views, axes):
    for ax, hemi in zip(ax_row, hemispheres):
        plotting.plot_surf(
            surf_map=mean_img,
            hemi=hemi,
            view=view,
            figure=fig,
            axes=ax,
            title=f"mean image - {hemi} - {view}",
            colorbar=False,
            cmap="bwr",
            symmetric_cmap=True,
            bg_on_data=True,
        )
fig.set_size_inches(6, 8)

plt.show()
mean image - left - lateral, mean image - right - lateral, mean image - left - medial, mean image - right - medial, mean image - left - dorsal, mean image - right - dorsal, mean image - left - ventral, mean image - right - ventral, mean image - left - anterior, mean image - right - anterior, mean image - left - posterior, mean image - right - posterior
Downloading data from https://www.nitrc.org/frs/download.php/8263/A00033747_rh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 16367616 of 42437884 bytes (38.6%,    1.6s remaining) ...done. (3 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8262/A00033747_lh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 12697600 of 42406508 bytes (29.9%,    2.4s remaining)
Downloaded 38510592 of 42406508 bytes (90.8%,    0.2s remaining) ...done. (3 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8265/A00035072_rh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 12165120 of 42448924 bytes (28.7%,    2.5s remaining)
Downloaded 33275904 of 42448924 bytes (78.4%,    0.6s remaining) ...done. (3 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8264/A00035072_lh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 18333696 of 42407636 bytes (43.2%,    1.3s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8267/A00035827_rh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 17768448 of 42421928 bytes (41.9%,    1.4s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8266/A00035827_lh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 18456576 of 42388496 bytes (43.5%,    1.3s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8269/A00035840_rh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 28499968 of 42192604 bytes (67.5%,    0.5s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8268/A00035840_lh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 30515200 of 42210360 bytes (72.3%,    0.4s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8271/A00037112_rh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 23707648 of 42398300 bytes (55.9%,    0.8s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8270/A00037112_lh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 29335552 of 42390948 bytes (69.2%,    0.4s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8273/A00037511_rh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 34709504 of 42308452 bytes (82.0%,    0.2s remaining) ...done. (1 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8272/A00037511_lh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 31670272 of 42342372 bytes (74.8%,    0.3s remaining) ...done. (1 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8275/A00038998_rh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 32702464 of 42322216 bytes (77.3%,    0.3s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8274/A00038998_lh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 25649152 of 42300384 bytes (60.6%,    0.7s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8277/A00039391_rh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 27836416 of 42289608 bytes (65.8%,    0.5s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8276/A00039391_lh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 35340288 of 42265584 bytes (83.6%,    0.2s remaining) ...done. (1 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8279/A00039431_rh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 30285824 of 42379532 bytes (71.5%,    0.4s remaining) ...done. (2 seconds, 0 min)
Downloading data from https://www.nitrc.org/frs/download.php/8278/A00039431_lh_preprocessed_fsaverage5_fwhm6.gii ...

Downloaded 33071104 of 42365696 bytes (78.1%,    0.3s remaining) ...done. (1 seconds, 0 min)
NKI image: <SurfaceImage (895, 20484)>
Masked data shape: (895, 20484)
Image mean: <SurfaceImage (20484,)>

Connectivity with a surface atlas and SurfaceLabelsMasker

from nilearn import connectome
from nilearn.experimental.surface import (
    SurfaceLabelsMasker,
    fetch_destrieux,
    load_fsaverage_data,
)

# for our plots we will be using the fsaverage sulcal data as background map
fsaverage_sulcal = load_fsaverage_data(data_type="sulcal")

img = fetch_nki()[0]
print(f"NKI image: {img}")

labels_img, label_names = fetch_destrieux()
print(f"Destrieux image: {labels_img}")
plotting.plot_surf_roi(
    roi_map=labels_img,
    avg_method="median",
    view="lateral",
    bg_on_data=True,
    bg_map=fsaverage_sulcal,
    darkness=0.5,
    title="Destrieux atlas",
)

labels_masker = SurfaceLabelsMasker(labels_img, label_names).fit()
masked_data = labels_masker.transform(img)
print(f"Masked data shape: {masked_data.shape}")

connectome = (
    connectome.ConnectivityMeasure(kind="correlation").fit([masked_data]).mean_
)
plot_matrix(connectome, labels=labels_masker.label_names_)

plt.show()
  • Destrieux atlas
  • plot surface image and maskers
NKI image: <SurfaceImage (895, 20484)>
Destrieux image: <SurfaceImage (20484,)>
Masked data shape: (895, 75)

Using the Decoder

from nilearn import decoding
from nilearn._utils import param_validation

The following is just disabling a couple of checks performed by the decoder that would force us to use a NiftiMasker.

def monkeypatch_masker_checks():
    def adjust_screening_percentile(screening_percentile, *args, **kwargs):
        return screening_percentile

    param_validation.adjust_screening_percentile = adjust_screening_percentile


monkeypatch_masker_checks()

Now using the appropriate masker we can use a Decoder on surface data just as we do for volume images.

img = fetch_nki()[0]
y = np.random.RandomState(0).choice([0, 1], replace=True, size=img.shape[0])

decoder = decoding.Decoder(
    mask=SurfaceMasker(),
    param_grid={"C": [0.01, 0.1]},
    cv=3,
    screening_percentile=1,
)
decoder.fit(img, y)
print("CV scores:", decoder.cv_scores_)

plotting.plot_surf(
    decoder.coef_img_[0],
    threshold=1e-6,
    bg_map=fsaverage_sulcal,
    bg_on_data=True,
    colorbar=True,
    cmap="black_red",
    vmin=0,
)
plt.show()
plot surface image and maskers
/opt/hostedtoolcache/Python/3.12.4/x64/lib/python3.12/site-packages/nilearn/_utils/masker_validation.py:113: UserWarning:

Overriding provided-default estimator parameters with provided masker parameters :
Parameter standardize :
    Masker parameter False - overriding estimator parameter True


/opt/hostedtoolcache/Python/3.12.4/x64/lib/python3.12/site-packages/sklearn/feature_selection/_univariate_selection.py:112: UserWarning:

Features [    8    36    38 ... 20206 20207 20208] are constant.

/opt/hostedtoolcache/Python/3.12.4/x64/lib/python3.12/site-packages/sklearn/feature_selection/_univariate_selection.py:113: RuntimeWarning:

invalid value encountered in divide

/opt/hostedtoolcache/Python/3.12.4/x64/lib/python3.12/site-packages/sklearn/feature_selection/_univariate_selection.py:112: UserWarning:

Features [    8    36    38 ... 20206 20207 20208] are constant.

/opt/hostedtoolcache/Python/3.12.4/x64/lib/python3.12/site-packages/sklearn/feature_selection/_univariate_selection.py:113: RuntimeWarning:

invalid value encountered in divide

/opt/hostedtoolcache/Python/3.12.4/x64/lib/python3.12/site-packages/sklearn/feature_selection/_univariate_selection.py:112: UserWarning:

Features [    8    36    38 ... 20206 20207 20208] are constant.

/opt/hostedtoolcache/Python/3.12.4/x64/lib/python3.12/site-packages/sklearn/feature_selection/_univariate_selection.py:113: RuntimeWarning:

invalid value encountered in divide

CV scores: {np.int64(0): [np.float64(0.4991939095387371), np.float64(0.5115891053391053), np.float64(0.4847132034632034)], np.int64(1): [np.float64(0.4991939095387371), np.float64(0.5115891053391053), np.float64(0.4847132034632034)]}

Decoding with a scikit-learn Pipeline

from sklearn import feature_selection, linear_model, pipeline, preprocessing

img = fetch_nki()[0]
y = np.random.RandomState(0).normal(size=img.shape[0])

decoder = pipeline.make_pipeline(
    SurfaceMasker(),
    preprocessing.StandardScaler(),
    feature_selection.SelectKBest(
        score_func=feature_selection.f_regression, k=500
    ),
    linear_model.Ridge(),
)
decoder.fit(img, y)

coef_img = decoder[:-1].inverse_transform(np.atleast_2d(decoder[-1].coef_))

vmax = max([np.absolute(dp).max() for dp in coef_img.data.parts.values()])
plotting.plot_surf(
    coef_img,
    cmap="cold_hot",
    vmin=-vmax,
    vmax=vmax,
    threshold=1e-6,
    bg_map=fsaverage_sulcal,
    bg_on_data=True,
    colorbar=True,
)
plt.show()
plot surface image and maskers

Total running time of the script: (1 minutes 49.100 seconds)

Estimated memory usage: 7926 MB

Gallery generated by Sphinx-Gallery