Note
Go to the end to download the full example code.
Template-based prediction.¶
In this tutorial, we show how to improve inter-subject similarity using a template computed across multiple source subjects. For this purpose, we leverage GroupAlignment to create a template, using Procrustes alignment. Leveraging the learned template, we align the target subject using shared information. We then compare the voxelwise similarity between the target subject and the template to the similarity between the target subject and the anatomical Euclidean average of the source subjects.
We mostly rely on Python common packages and on nilearn to handle functional data in a clean fashion.
To run this example, you must launch IPython via ipython
--matplotlib
in a terminal, or use jupyter-notebook
.
Retrieve the data¶
In this example we use the IBC dataset, which includes a large number of different contrasts maps for 12 subjects. We download the images for subjects sub-01, sub-02, sub-04, sub-05, sub-06 and sub-07 (or retrieve them if they were already downloaded). imgs is the list of paths to available statistical images for each subjects. df is a dataframe with metadata about each of them. mask is a binary image used to extract grey matter regions.
[get_dataset_dir] Added README.md to /home/runner/nilearn_data
[get_dataset_dir] Dataset created in /home/runner/nilearn_data/ibc
[fetch_files] Downloading data from https://osf.io/pcvje/download ...
[fetch_files] ...done. (2 seconds, 0 min)
[fetch_files] Extracting data from
/home/runner/nilearn_data/ibc/8e275a34345802c5c273312d85957d6c/download...
[fetch_files] .. done.
[fetch_files] Downloading data from https://osf.io/yvju3/download ...
[fetch_files] ...done. (2 seconds, 0 min)
[fetch_files] Extracting data from
/home/runner/nilearn_data/ibc/fe06df963fcb3fd454f63a33f0864e8d/download...
[fetch_files] .. done.
[fetch_files] Downloading data from https://osf.io/8z23h/download ...
[fetch_files] ...done. (3 seconds, 0 min)
[fetch_files] Extracting data from
/home/runner/nilearn_data/ibc/100352739b7501f0ed04920933b4be36/download...
[fetch_files] .. done.
[fetch_files] Downloading data from https://osf.io/e9kbm/download ...
[fetch_files] ...done. (3 seconds, 0 min)
[fetch_files] Extracting data from
/home/runner/nilearn_data/ibc/331a0a579c6e46c0911502a96215b358/download...
[fetch_files] .. done.
[fetch_files] Downloading data from https://osf.io/qn5b6/download ...
[fetch_files] Downloaded 9764864 of 21197218 bytes (46.1%%, 1.2s remaining)
[fetch_files] ...done. (4 seconds, 0 min)
[fetch_files] Extracting data from
/home/runner/nilearn_data/ibc/cd396fed594eb866baecd48b70ddf7e7/download...
[fetch_files] .. done.
[fetch_files] Downloading data from https://osf.io/u74a3/download ...
[fetch_files] Downloaded 14229504 of 21185350 bytes (67.2%%, 0.5s remaining)
[fetch_files] ...done. (4 seconds, 0 min)
[fetch_files] Extracting data from
/home/runner/nilearn_data/ibc/fc5556cc3678df4f4ab566414382180a/download...
[fetch_files] .. done.
[fetch_files] Downloading data from https://osf.io/83bje/download ...
[fetch_files] ...done. (3 seconds, 0 min)
[fetch_files] Extracting data from
/home/runner/nilearn_data/ibc/1beaa1b5a1734a1afbf1c844e1f7a60e/download...
[fetch_files] .. done.
[fetch_files] Downloading data from https://osf.io/43j69/download ...
[fetch_files] Downloaded 9707520 of 21187400 bytes (45.8%%, 1.2s remaining)
[fetch_files] Downloaded 18071552 of 21187400 bytes (85.3%%, 0.3s remaining)
[fetch_files] ...done. (5 seconds, 0 min)
[fetch_files] Extracting data from
/home/runner/nilearn_data/ibc/75e62c44985852e000c2b2865badf72d/download...
[fetch_files] .. done.
Define a masker¶
We define a nilearn masker that will be used to handle relevant data. For more information, consult Nilearn’s documentation on masker objects.
from nilearn.maskers import NiftiMasker
masker = NiftiMasker(mask_img=mask_img).fit()
Prepare the data¶
For each subject, we will use two series of contrasts acquired during two independent sessions with a different phase encoding: Antero-posterior(AP) or Postero-anterior(PA).
# To infer a template for subjects sub-01 to sub-06 for both AP and PA data,
# we make a list of 4D niimgs from our list of list of files containing 3D images
from nilearn.image import concat_imgs
template_train = []
for i in range(5):
template_train.append(concat_imgs(imgs[i]))
# sub-07 (that is 5th in the list) will be our left-out subject.
# We make a single 4D Niimg from our list of 3D filenames.
left_out_subject = concat_imgs(imgs[5])
Compute a baseline (average of subjects)¶
We create an image with as many contrasts as any subject representing for each contrast the average of all train subjects maps.
import numpy as np
masked_imgs = [masker.transform(img) for img in template_train]
euclidean_avg = np.mean(masked_imgs, axis=0)
Create a template from the training subjects.¶
- We define an estimator using
GroupAlignment
: We align the whole brain through multiple local alignments.
These alignments are calculated on a parcellation of the brain in 150 pieces, this parcellation creates group of functionally similar voxels.
The template is created iteratively, aligning all subjects data into a common space, from which the template is inferred and aligning again to this new template space.
from fmralign import GroupAlignment
from fmralign.embeddings.parcellation import get_labels
# Use only the first image to speed up the computation of the labels
labels = get_labels(imgs[0], n_pieces=150, masker=masker)
# We create a dictionary with the subject names as keys and the subjects data as values
dict_alignment = dict(zip(subjects, masked_imgs))
# We use Procrustes/scaled orthogonal alignment method
template_estim = GroupAlignment(method="procrustes", labels=labels)
template_estim.fit(X=dict_alignment, y="template")
procrustes_template = template_estim.template
/home/runner/work/fmralign/fmralign/fmralign/embeddings/parcellation.py:82: UserWarning: Overriding provided-default estimator parameters with provided masker parameters :
Parameter mask_strategy :
Masker parameter background - overriding estimator parameter epi
Parameter smoothing_fwhm :
Masker parameter None - overriding estimator parameter 4.0
parcellation.fit(images_to_parcel)
/home/runner/work/fmralign/fmralign/.venv/lib/python3.12/site-packages/sklearn/cluster/_agglomerative.py:321: UserWarning: the number of connected components of the connectivity matrix is 35 > 1. Completing it to avoid stopping the tree early.
connectivity, n_connected_components = _fix_connectivity(
/home/runner/work/fmralign/fmralign/fmralign/alignment/utils.py:208: UserWarning:
Some parcels are more than 1000 voxels wide it can slow down alignment,especially optimal_transport :
parcel 11 : 1026 voxels
parcel 37 : 1370 voxels
parcel 42 : 2251 voxels
warnings.warn(warning)
Predict new data for left-out subject¶
We predict the contrasts of the left-out subject using the template we just created. This method takes the left-out subject as input, computes a pairwise alignment with the template and returns the aligned data.
from fmralign import PairwiseAlignment
left_out_data = masker.transform(left_out_subject)
pairwise_estim = PairwiseAlignment(method="procrustes", labels=labels).fit(
left_out_data, procrustes_template
)
predictions_from_template = pairwise_estim.transform(left_out_data)
Score the baseline and the prediction¶
We use a utility scoring function to measure the voxelwise correlation between the images. That is, for each voxel, we measure the correlation between its profile of activation without and with alignment, to see if template-based alignment was able to improve inter-subject similarity.
from fmralign.metrics import score_voxelwise
average_score = score_voxelwise(left_out_data, euclidean_avg, loss="corr")
template_score = score_voxelwise(
predictions_from_template, procrustes_template, loss="corr"
)
Plotting the measures¶
Finally we plot both scores
from nilearn import plotting
average_score_img = masker.inverse_transform(average_score)
template_score_img = masker.inverse_transform(template_score)
baseline_display = plotting.plot_stat_map(
average_score_img, display_mode="z", vmax=1, cut_coords=[-15, -5]
)
baseline_display.title("Left-out subject correlation with group average")
display = plotting.plot_stat_map(
template_score_img, display_mode="z", cut_coords=[-15, -5], vmax=1
)
display.title("Aligned subject correlation with Procrustes template")
We observe that creating a template and aligning a new subject to it yields better inter-subject similarity than regular euclidean averaging.
Total running time of the script: (4 minutes 40.934 seconds)