Multi-echo, fmriprep 21.0.2 distortion correction, the -t task selection parameter, and tedana

Hey all,

I finally found some time to test the latest distortion correction implementation in fmriprep in the context of multi-echo data.

My bids data looks like this:

.
├── anat
│   ├── sub-p2452_T1w.json
│   └── sub-p2452_T1w.nii.gz
├── fmap
│   ├── sub-p2452_dir-PA_run-1_epi.json
│   ├── sub-p2452_dir-PA_run-1_epi.nii.gz
│   ├── sub-p2452_dir-PA_run-2_epi.json
│   └── sub-p2452_dir-PA_run-2_epi.nii.gz
└── func
    ├── sub-p2452_task-hands2_echo-1_bold.json
    ├── sub-p2452_task-hands2_echo-1_bold.nii.gz
    ├── sub-p2452_task-hands2_echo-2_bold.json
    ├── sub-p2452_task-hands2_echo-2_bold.nii.gz
    ├── sub-p2452_task-hands2_echo-3_bold.json
    ├── sub-p2452_task-hands2_echo-3_bold.nii.gz
    ├── sub-p2452_task-rest2_bold.json
    └── sub-p2452_task-rest2_bold.nii.gz

sub-p2452_dir-PA_run-1_epi.json:

    "IntendedFor": [
        "func/sub-p2452_task-hands2_echo-1_bold.nii.gz",
        "func/sub-p2452_task-hands2_echo-2_bold.nii.gz",
        "func/sub-p2452_task-hands2_echo-3_bold.nii.gz"
    ]

sub-p2452_dir-PA_run-2_epi.json:

    "IntendedFor": [
        "func/sub-p2452_task-rest2_bold.nii.gz"
    ]

I am using the dockered version with the following fmriprep and call info:

  • fMRIPrep version: 21.0.2
  • fMRIPrep command: /opt/conda/bin/fmriprep /data /out participant --participant-label sub-p1799 --fs-no-reconall --nprocs 4 --low-mem --fs-license-file /opt/freesurfer/license.txt -t hands2 -w /scratch --output-layout legacy

The results I am getting seem to be far better than what I had using 20.2.6, so I’d like to start updating my fmriprep + tedana pipeline.

Now my questions:

Regarding the new distortion correction; I was not able to understand what exactly is happening from the methods boilerplate in the html output.

  1. Is there a reason why fmriprep is processing sub-p2452_dir-PA_run-2_epi.nii.gz? (because it does). Is the -t parameter being ignored? Or is it trying to get a better B0 distortion estimation by combining info from multiple scans? And if the latter, how?
  2. In addition, how exactly is distortion correction applied to the echo’s? In the resulting output files and html, I see that fieldmaps are separately generated per echo. Does that mean that different corrections are applied to different echo’s? iirc, @handwerkerd recommended to me to not do that.
  3. Before I start updating my implementation of collect_fmriprep.py,
    a) which partly preprocessed files are best to start off with?
    b) should I apply SDC from echo 1 manually to 2 and 3, before running the files through tedana? (related to question 2). Or would it be better to apply echo 1 SDC on the result from tedana?

I hope anyone can help me further :smile:

I’m not sure why that would happen. I assume that there are preprocessed distortion maps in the output directory? Are the task-rest2 fMRI files also outputted? If not, then I’m guessing that -t is not being ignored, but perhaps all field maps are preprocessed automatically, regardless of which fMRI runs they’re intended for.

One distortion correction (and STC/motion correction) should be applied to all echoes equally. Could you share a tree of the output directory like you did for your BIDS dataset?

I would recommend using the --me-output-echos parameter, which will output the partially preprocessed echoes for you to use with tedana (or whatever tool you prefer). You won’t need to use a collection script anymore.

The partially preprocessed echoes will be distortion-corrected, using the same distortion correction for all echoes. In the tedana docs, we recommend doing distortion correction after denoising, but in practice we found it simpler to just bundle STC, motion correction, and distortion correction together in the “partial preprocessing”, and we don’t expect the distortion correction (especially when applied equally across echoes) to have a major impact on the denoising results.

Thanks for the quick reply @tsalo!

Yes, there are preprocessed distortion maps in the output directory. No the rest is not being processed.

That’s indeed what happens.

Sure.

.
├── anat
│   ├── sub-p2452_desc-brain_mask.json
│   ├── sub-p2452_desc-brain_mask.nii.gz
│   ├── sub-p2452_desc-preproc_T1w.json
│   ├── sub-p2452_desc-preproc_T1w.nii.gz
│   ├── sub-p2452_dseg.nii.gz
│   ├── sub-p2452_from-MNI152NLin2009cAsym_to-T1w_mode-image_xfm.h5
│   ├── sub-p2452_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5
│   ├── sub-p2452_label-CSF_probseg.nii.gz
│   ├── sub-p2452_label-GM_probseg.nii.gz
│   ├── sub-p2452_label-WM_probseg.nii.gz
│   ├── sub-p2452_space-MNI152NLin2009cAsym_desc-brain_mask.json
│   ├── sub-p2452_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz
│   ├── sub-p2452_space-MNI152NLin2009cAsym_desc-preproc_T1w.json
│   ├── sub-p2452_space-MNI152NLin2009cAsym_desc-preproc_T1w.nii.gz
│   ├── sub-p2452_space-MNI152NLin2009cAsym_dseg.nii.gz
│   ├── sub-p2452_space-MNI152NLin2009cAsym_label-CSF_probseg.nii.gz
│   ├── sub-p2452_space-MNI152NLin2009cAsym_label-GM_probseg.nii.gz
│   └── sub-p2452_space-MNI152NLin2009cAsym_label-WM_probseg.nii.gz
├── figures
│   ├── sub-p2452_desc-about_T1w.html
│   ├── sub-p2452_desc-conform_T1w.html
│   ├── sub-p2452_desc-summary_T1w.html
│   ├── sub-p2452_dseg.svg
│   ├── sub-p2452_fmapid-auto00000_desc-pepolar_fieldmap.svg
│   ├── sub-p2452_fmapid-auto00001_desc-pepolar_fieldmap.svg
│   ├── sub-p2452_fmapid-auto00002_desc-pepolar_fieldmap.svg
│   ├── sub-p2452_fmapid-auto00003_desc-pepolar_fieldmap.svg
│   ├── sub-p2452_space-MNI152NLin2009cAsym_T1w.svg
│   ├── sub-p2452_task-hands2_desc-carpetplot_bold.svg
│   ├── sub-p2452_task-hands2_desc-compcorvar_bold.svg
│   ├── sub-p2452_task-hands2_desc-confoundcorr_bold.svg
│   ├── sub-p2452_task-hands2_desc-flirtbbr_bold.svg
│   ├── sub-p2452_task-hands2_desc-rois_bold.svg
│   ├── sub-p2452_task-hands2_desc-sdc_bold.svg
│   ├── sub-p2452_task-hands2_desc-summary_bold.html
│   └── sub-p2452_task-hands2_desc-validation_bold.html
├── fmap
│   ├── sub-p2452_fmapid-auto00000_desc-coeff_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00000_desc-epi_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00000_desc-preproc_fieldmap.json
│   ├── sub-p2452_fmapid-auto00000_desc-preproc_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00001_desc-coeff_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00001_desc-epi_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00001_desc-preproc_fieldmap.json
│   ├── sub-p2452_fmapid-auto00001_desc-preproc_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00002_desc-coeff_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00002_desc-epi_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00002_desc-preproc_fieldmap.json
│   ├── sub-p2452_fmapid-auto00002_desc-preproc_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00003_desc-coeff_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00003_desc-epi_fieldmap.nii.gz
│   ├── sub-p2452_fmapid-auto00003_desc-preproc_fieldmap.json
│   └── sub-p2452_fmapid-auto00003_desc-preproc_fieldmap.nii.gz
├── func
│   ├── sub-p2452_task-hands2_desc-confounds_timeseries.json
│   ├── sub-p2452_task-hands2_desc-confounds_timeseries.tsv
│   ├── sub-p2452_task-hands2_from-scanner_to-T1w_mode-image_xfm.txt
│   ├── sub-p2452_task-hands2_from-T1w_to-scanner_mode-image_xfm.txt
│   ├── sub-p2452_task-hands2_space-MNI152NLin2009cAsym_boldref.nii.gz
│   ├── sub-p2452_task-hands2_space-MNI152NLin2009cAsym_desc-brain_mask.json
│   ├── sub-p2452_task-hands2_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz
│   ├── sub-p2452_task-hands2_space-MNI152NLin2009cAsym_desc-preproc_bold.json
│   └── sub-p2452_task-hands2_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz
└── log
    └── 20220715-073039_33df7118-c55d-4a52-9577-5534878c173c
        └── fmriprep.toml

The naming is somewhat vague but the json files are helpful to understand which is which:

auto00000 = hands2_echo-1
auto00001 = hands2_echo-2
auto00002 = hands2_echo-3
auto00003 = rest2

The coeff_fieldmap-files for the seperate echo’s differ from each other. So, now I am afraid that separate fieldmaps are applied to the echo’s.

Oh sweet!!! I’ll go try it out.

Also, did the docker call change? (I did not see anything obvious in the changelog).

If I run the following script without --output-layout legacy, I don’t get any output at all in {fmriprep_folder}.

cmd =   f"docker run -it --rm \
        -v {source_folder}:/data:ro \
        -v {fmriprep_folder}:/out/fmriprep/ \
        -v {license_file}:/opt/freesurfer/license.txt \
        nipreps/fmriprep:21.0.2 \
        /data /out participant --participant-label sub-{subject} \
        --fs-no-reconall --nprocs 4 --fs-license-file /opt/freesurfer/license.txt --output-layout legacy"

os.system(cmd)

That’s odd… It seems like there should only be one, but I could be wrong. @effigies do you know why each of the echoes would have a different preprocessed field map file?

I’m sorry, I haven’t ever compared the output layouts. It should default to the new, more BIDS-compliant output layout, which should mean new files in {fmriprep_folder}, unless fMRIPrep crashed.

The log and scratch folder seem to indicate that the whole analysis finished correctly. There’s just no files in the linked output folder. I’ll try to find some time next week to dig into it a little more to figure out what’s happening there.

Very likely a bug. The many variations of PEpolar fieldmaps are a logistical pain, and I’m not sure if we tested on a multiecho reverse-PE EPI dataset. The bug will be here:

I suspect that if you just drop the echo-2 and echo-3 from your IntendedFor, you’ll get the right result, but I would check…


If you want your outputs in {fmriprep_folder}, you need to do the following:

 cmd =   f"docker run -it --rm \
         -v {source_folder}:/data:ro \
-        -v {fmriprep_folder}:/out/fmriprep/ \
+        -v {fmriprep_folder}:/out \
         -v {license_file}:/opt/freesurfer/license.txt \
         nipreps/fmriprep:21.0.2 \
         /data /out participant --participant-label sub-{subject} \
         --fs-no-reconall --nprocs 4 --fs-license-file /opt/freesurfer/license.txt --output-layout legacy"
 
 os.system(cmd)

Otherwise what’s happening is that {fmriprep_folder} is being mounted in /out/fmriprep while a bunch of outputs are being dumped in /out.

1 Like

Thanks so much! I’ll go test these recommendations asap!

When removing echo-2 and echo-3 from IntendedFor, two fieldmaps are now generated. One for task-hands2_echo-1, and one for task-rest2. So the -t hands2 parameter is still being ignored for fieldmaps.

It might be related to that issue IntendedFor is ignored by wrangler.find_estimators · Issue #266 · nipreps/sdcflows · GitHub

1 Like