Problem with smoothing in first-level analysis

Dear Nilearn Experts,

I’m starting to work with Nilearn and I’m currently stuck with a problem in the first-level analysis. When I smooth the data in the first level analysis function (as you can see below) or try to smooth it before running the first level the analysis, the process is aborted. However, if I do not smooth the data, it works without problems. Additionally, smoothing the data after the first-level analysis using image.smooth_img also works fine.

I’m working on a new Linux laptop with 32GB of RAM, but during the process, the memory usages reaches 99.9%, and then a message appears saying: “The Kernel crashed while executing code in the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure.” I’m using the latest version of python 3.11.9

Below is is the code for 2 runs using a 7T scanner:

TR = 2
frame_times = np.arange(confounds.shape[0]) * TR 


design_matrix = make_first_level_design_matrix(
    frame_times,
    events,
    hrf_model="spm",
    drift_model="Cosine",
    high_pass=1 / 128,
    add_regs=confounds,
)

plot_design_matrix(design_matrix)
plt.show()

# put the design matrices in a list
design_matrices.append(design_matrix)
print(design_matrices)

design_matrix.to_csv(f"/home/daniela/fMRI_analyses/Comparison/design_matrix_sub-{subject_number:02}_run{run:02}.xlsx")

print("Design Matrix was created")

Estimate GLM

flm = FirstLevelModel(
t_r=2,
hrf_model=‘spm’,
mask_img=masked_data,
smoothing_fwhm=2.4,
n_jobs=len(RUNS),

    )

flm.fit(
fmri_imgs,
design_matrices=design_matrices,
)

Do you have any idea why this is happening?

Many thanks in advance for your help,

Daniela Valerio

Hello Daniela,

Thanks for your question!

The problem is that since we don’t have access to your data, we can’t really test and reproduce the issue on our end.

So, I wrote a similar script but using one of the example datasets that can be downloaded via nilearn.

Could you please try running the following and report back if you run into the same issue?

from nilearn.datasets import fetch_spm_multimodal_fmri
from nilearn.glm.first_level import make_first_level_design_matrix
from nilearn.plotting import plot_design_matrix
import numpy as np
import matplotlib.pyplot as plt
from nilearn.glm.first_level import FirstLevelModel
import pandas as pd
from nilearn.image import concat_imgs, resample_img
import warnings

data = fetch_spm_multimodal_fmri()
tr = 2
n_runs = 2

# this is specific to this dataset because it is quite old and needs to be resampled
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    fmri_img = [
        concat_imgs(data.func1, auto_resample=True),
        concat_imgs(data.func2, auto_resample=True),
    ]
affine, shape = fmri_img[0].affine, fmri_img[0].shape
print("Resampling the second image (this takes time)...")
fmri_img[1] = resample_img(fmri_img[1], affine, shape[:3], copy_header=True)

# Calculate the design matrices for each run
design_matrices = []
for run in range(n_runs):
    events = pd.read_csv(data[f"events{run+1}"], sep="\t")
    frame_times = np.arange(fmri_img[run].shape[-1]) * tr
    design_matrix = make_first_level_design_matrix(
        frame_times,
        events,
        hrf_model="spm",
        drift_model="Cosine",
        high_pass=1 / 128,
    )
    # put the design matrices in a list
    design_matrices.append(design_matrix)
    print(design_matrix)

    plot_design_matrix(design_matrix)
    plt.show()

    design_matrix.to_csv(f"design_matrix_run{run}.xlsx")
    print("Design Matrix was created")

flm = FirstLevelModel(
    t_r=tr,
    hrf_model="spm",
    smoothing_fwhm=2.4,
    n_jobs=n_runs,
)
flm.fit(
    fmri_img,
    design_matrices=design_matrices,
)

Best,
Himanshu

Hi Himanshu,

Thank you very much for your prompt reply and help.

I apologize for not sharing the data initially. You can access it using the following link: nilearn_testing_data - pCloud

I successfully ran your script, and it also worked when I ran it in my data using the native space functional files. However, when I attempt to use the MNI space (specifically the fmriprep output named “space-MNI152NLin2009cAsym_desc-preproc_bold”), the process crashes as described in my previous message.

Do you have any insights into why this might be happening?

Best,
Daniela

Thanks for sharing the data, Daniela! This indeed made the troubleshooting easier.

I think the main issue is that your fMRI images are high resolution (1.2 mm iso), and fitting the GLM on them is quite memory-intensive. There might also be some resampling happening depending on the resolution of the mask you provide in your initial code. This resampling is again memory intensive.

The RAM usage indeed reached just over 32 GBs when I tried.

But I am unsure why this behavior differs between native space and MNI space images.

Nevertheless, depending on what you want to do with these GLM parameters, you have the following options:

  • If you want high-resolution maps, you should probably consider using a high-performance cluster (you might already have access to them at Neurospin).

  • If you are fine with downsampling your data, then you can probably use your laptop. I have updated the code to do so.

Let me know if you have any further questions.

Best,
Himanshu

data = {
    "func1_path": "nilearn_testing_data/sub-02_task-comparison_dir-ap_run-01_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz",
    "func2_path": "nilearn_testing_data/sub-02_task-comparison_dir-ap_run-02_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz",
    "events1": "nilearn_testing_data/sub-02_task-comparison_run-1_events_button_left-right.tsv",
    "events2": "nilearn_testing_data/sub-02_task-comparison_run-2_events_button_left-right.tsv",
    "confounds1": "nilearn_testing_data/sub-02_task-comparison_dir-ap_run-01_desc-confounds_timeseries.tsv",
    "confounds2": "nilearn_testing_data/sub-02_task-comparison_dir-pa_run-02_desc-confounds_timeseries.tsv",
}
tr = 2
n_runs = 2
# Get a default MNI152 grey matter mask
mask = load_mni152_gm_mask(resolution=3)

# downsample both the functional images and the mask to 3mm isotropic resolution
# this is done to speed up the processing and reduce memory usage
for run in range(n_runs):
    print(f"Downsampling run {run+1}...")
    fmri_img = data[f"func{run+1}_path"]
    # Resample the functional image to the mask's resolution
    fmri_img = resample_img(fmri_img, target_affine=np.diag([3, 3, 3]))
    data[f"func{run+1}"] = fmri_img
# Resample the mask to the functional images' resolution
mask = resample_img(
    mask,
    target_affine=fmri_img.affine,
    target_shape=fmri_img.shape[:3],
    interpolation="nearest",
)

# Calculate the design matrices for each run
design_matrices = []
for run in range(n_runs):
    print(f"Processing run {run+1}...")
    fmri_img = data[f"func{run+1}"]
    print(f"Loading {run+1} image...")
    fmri_img = load_img(fmri_img)

    # Load the events file
    events = pd.read_csv(data[f"events{run+1}"], sep="\t")

    # I saw that there were some NaNs in `global_signal` variables in the confounds
    # load_confounds only loads motion, high_pass and wm_csf variables
    # this selection is based on best practices from the literature
    # you can add more variables if you think they are important
    # see docs https://nilearn.github.io/stable/modules/generated/nilearn.interfaces.fmriprep.load_confounds.html
    confounds, _ = load_confounds(data[f"func{run+1}_path"])
    frame_times = np.arange(fmri_img.shape[-1]) * tr
    print("Creating design matrix...")
    design_matrix = make_first_level_design_matrix(
        frame_times,
        events,
        hrf_model="spm",
        drift_model="Cosine",
        high_pass=1 / 128,
        add_regs=confounds,
    )
    # put the design matrices in a list
    design_matrices.append(design_matrix)
    print(design_matrix)

    plot_design_matrix(design_matrix)
    plt.show()

    design_matrix.to_csv(f"design_matrix_run{run+1}.xlsx")
    print(f"Created design matrix for run{run+1}")


flm = FirstLevelModel(
    t_r=tr,
    hrf_model="spm",
    mask_img=mask,
    smoothing_fwhm=2.4,
    n_jobs=1,
)
# fitting the FirstLevelModel
flm.fit(
    [data[f"func{run+1}"] for run in range(n_runs)],
    design_matrices=design_matrices,
)

@nilearn devs:

FirstLevelModel.fit() seems to require a extraordinary amount of RAM to analyse a single 1.2GB fmri filewhen smoothing (the following code (also available as a notebook at https://colab.research.google.com/drive/1TiiJUGGtXsrJH12PylwI-qfez56Igi1I)
crashes because of lack of RAM on a 32 GB computer)


!pip install nilearn
!wget https://www.pallier.org/nilearn_data_daniela_valerio.zip
!unzip nilearn_data_daniela_valerio.zip

fmri_img = "nilearn_data_daniela_valerio/sub-02_task-comparison_dir-ap_run-01_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz",
events = "nilearn_data_daniela_valerio/sub-02_task-comparison_run-1_events_button_left-right.tsv",
confounds =  "nilearn__data_daniela_valerio/sub-02_task-comparison_dir-ap_run-01_desc-confounds_timeseries.tsv",

from nilearn.glm.first_level import FirstLevelModel

flm = FirstLevelModel(
    t_r=2,
    hrf_model="spm",
    #smoothing_fwhm=2,  # <- crashes when uncommented on a machine with 32GB RAM
    n_jobs=1,
)

flm.fit(fmri_img, events)

design_matrix = flm.design_matrices_[0]
import matplotlib.pyplot as plt
from nilearn.plotting import plot_design_matrix
plot_design_matrix(design_matrix)
plt.show()