Dual-Echo Denoising with nilearn

Dual-Echo Denoising with nilearn#

Dual-echo fMRI leverages one of the same principles motivating multi-echo fMRI; namely, that BOLD contrast increases with echo time, so earlier echoes tend to be more affected by non-BOLD noise than later ones. At an early enough echo time (<5ms for 3T scanners), the signal is almost entirely driven by non-BOLD noise. When it comes to denoising, this means that, if you acquire data with both an early echo time and a more typical echo time (~30ms for 3T), you can simply regress the earlier echo’s time series out of the later echo’s time series, which will remove a lot of non-BOLD noise.

Additionally, dual-echo fMRI comes at no real cost in terms of temporal or spatial resolution, unlike multi-echo fMRI. For multi-echo denoising to work, you need to have at least one echo time that is later than the typical echo time, which means decreasing your temporal resolution, all else remaining equal. In the case of dual-echo fMRI, you only need a shorter echo time, which occurs in what is essentially “dead time” in the pulse sequence.

Dual-echo denoising was originally proposed in Bright & Murphy (2013).

import json
import os
from glob import glob

import matplotlib.pyplot as plt
import nibabel as nb
from book_utils import glue_figure, regress_one_image_out_of_another
from nilearn import plotting

data_path = os.path.abspath('../data')
/home/tsalo/micromamba/envs/meda/lib/python3.12/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.6.3) or chardet (7.2.0)/charset_normalizer (3.4.6) doesn't match a supported version!
  warnings.warn(
func_dir = os.path.join(data_path, "ds006185/sub-24053/ses-1/func/")
te1_img = os.path.join(
    func_dir,
    "sub-24053_ses-1_task-rat_dir-PA_run-01_echo-1_part-mag_desc-preproc_bold.nii.gz",
)
te2_img = os.path.join(
    func_dir,
    "sub-24053_ses-1_task-rat_dir-PA_run-01_echo-2_part-mag_desc-preproc_bold.nii.gz",
)
mask_img = os.path.join(
    func_dir,
    "sub-24053_ses-1_task-rat_dir-PA_run-01_part-mag_desc-brain_mask.nii.gz"
)
denoised_img = regress_one_image_out_of_another(te2_img, te1_img, mask_img)

Hide code cell output

/mnt/c/Users/tsalo/Documents/ME-ICA/multi-echo-data-analysis/content/book_utils.py:59: FutureWarning: From release 0.13.0 onwards, this function will, by default, copy the header of the input image to the output. Currently, the header is reset to the default Nifti1Header. To suppress this warning and use the new behavior, set `copy_header=True`.
  mean_data_img = image.mean_img(data_img)
/mnt/c/Users/tsalo/Documents/ME-ICA/multi-echo-data-analysis/content/book_utils.py:60: FutureWarning: From release 0.13.0 onwards, this function will, by default, copy the header of the input image to the output. Currently, the header is reset to the default Nifti1Header. To suppress this warning and use the new behavior, set `copy_header=True`.
  mean_nuis_img = image.mean_img(nuis_img)
fig, axes = plt.subplots(figsize=(16, 16), nrows=3)

plotting.plot_carpet(te2_img, axes=axes[0], figure=fig)
axes[0].set_title("First Echo (BAD)", fontsize=20)
plotting.plot_carpet(te1_img, axes=axes[1], figure=fig)
axes[1].set_title("Second Echo (GOOD)", fontsize=20)
plotting.plot_carpet(denoised_img, axes=axes[2], figure=fig)
axes[2].set_title("Denoised Data (GREAT)", fontsize=20)
axes[0].xaxis.set_visible(False)
axes[1].xaxis.set_visible(False)
axes[0].spines["bottom"].set_visible(False)
axes[1].spines["bottom"].set_visible(False)
fig.tight_layout()
glue_figure("figure_dual_echo_results", fig, display=False)

Hide code cell output

/tmp/ipykernel_6925/2413803516.py:3: DeprecationWarning: The default strategy for standardize is currently 'zscore' which incorrectly uses population std to calculate sample zscores. The new strategy 'zscore_sample' corrects this behavior by using the sample std. In release 0.13, the default strategy will be replaced by the new strategy and the 'zscore' option will be removed. Please use 'zscore_sample' instead.
  plotting.plot_carpet(te2_img, axes=axes[0], figure=fig)
/tmp/ipykernel_6925/2413803516.py:5: DeprecationWarning: The default strategy for standardize is currently 'zscore' which incorrectly uses population std to calculate sample zscores. The new strategy 'zscore_sample' corrects this behavior by using the sample std. In release 0.13, the default strategy will be replaced by the new strategy and the 'zscore' option will be removed. Please use 'zscore_sample' instead.
  plotting.plot_carpet(te1_img, axes=axes[1], figure=fig)
/tmp/ipykernel_6925/2413803516.py:7: DeprecationWarning: The default strategy for standardize is currently 'zscore' which incorrectly uses population std to calculate sample zscores. The new strategy 'zscore_sample' corrects this behavior by using the sample std. In release 0.13, the default strategy will be replaced by the new strategy and the 'zscore' option will be removed. Please use 'zscore_sample' instead.
  plotting.plot_carpet(denoised_img, axes=axes[2], figure=fig)
../_images/cb1eeec63987cf6938effdccf7557072320adf3fbf153ddd3e15cc4d22730d53.png
../_images/ba123c7756d92114bc0eda4255c882f850042735d0903a868419fc38b5030636.png

Fig. 35 Results of dual-echo regression.#