Poor Fieldmap <-> Functional Registration in fMRIPrep (highly variable step)

Summary of what happened:

I’m testing the newest version of fMRIPrep on a small dataset, 1 participant scanned on 9 scanners. For one of the sessions, the EPI ↔ functional registration is quite bad.

In that run, an anatomical mask was provided through the --derivatives argument (it’s a mask calculated by synthstrip and is very good). However, when I don’t provide the mask, the registration looks fine.

(The exclusion of cerebellum is real and not an artifact of figure cropping)

Note that the registration ends up bad when using or not using synthstrip’s --no-csf flag.

Are there gotcha’s when providing pre-specified masks? For example, when providing anatomical masks, do I also need to provide masks for the functionals (and/or fmaps)? Or, do I need to ensure that the mask covers or doesn’t cover certain structures (brainstem, cerebellum, etc)?

(FWIW, if this seems like a possible bug, I’d be happy/motivated to dig around)

Command used (and if a helper script was used, a link to the helper script or the command generated):

docker run \
  --rm -it \
  -v $PWD:$PWD \
  nipreps/fmriprep:24.0.1 \
  --notrack \
  --fs-license-file $PWD/license  \
  --work-dir $PWD/work2 \
  --task-id rest \
  --derivatives synthstrip=$PWD/sourcedata/synthstrip \
  --dummy-scans 15 \
  $PWD/rawdata \
  $PWD/derivatives/fmriprep-synthstrip \
  participant

vs

docker run \
  --rm -it \
  -v $PWD:$PWD \
  nipreps/fmriprep:24.0.1 \
  --notrack \
  --fs-license-file $PWD/license \
  --dummy-scans 15 \
  --work-dir $PWD/work \
  --task-id rest \
  $PWD/rawdata \
  $PWD/derivatives \
  participant

Version:

24.0.1

Environment (Docker, Singularity / Apptainer, custom installation):

Docker

Data formatted according to a validatable standard? Please provide the output of the validator:

bids-validator@1.14.6
(node:9) Warning: Closing directory handle on garbage collection
(Use `node --trace-warnings ...` to show where the warning was created)
	1: [WARN] The recommended file /README is very small. Please consider expanding it with additional information about the dataset. (code: 213 - README_FILE_SMALL)
		./README

	Please visit https://neurostars.org/search?q=README_FILE_SMALL for existing conversations about this issue.

        Summary:                  Available Tasks:        Available Modalities: 
        18 Files, 771.33MB        cuff                    MRI                   
        1 - Subject               rest                                          
        1 - Session                                                             


	If you have any questions, please post on https://neurostars.org/tags/bids.

Relevant log outputs (up to 20 lines):

240905-21:54:38,271 nipype.workflow IMPORTANT:
	 Building fMRIPrep's workflow:
           * BIDS dataset path: /Users/psadil/Desktop/NStravel2/rawdata.
           * Participant list: ['travel2'].
           * Run identifier: 20240905-215427_aff96021-2fa0-443f-8faf-7280522b1da7.
           * Output spaces: MNI152NLin2009cAsym:res-native.
           * Searching for derivatives: [PosixPath('/Users/psadil/Desktop/NStravel2/sourcedata/synthstrip')].
           * Pre-run FreeSurfer's SUBJECTS_DIR: /Users/psadil/Desktop/NStravel2/derivatives/fmriprep-synthstrip/sourcedata/freesurfer.
240905-21:54:38,814 nipype.workflow INFO:
	 ANAT Stage 1: Adding template workflow
240905-21:54:39,171 nipype.workflow INFO:
	 ANAT Found brain mask
240905-21:54:39,171 nipype.workflow INFO:
	 ANAT Skipping skull-strip, INU-correction only
240905-21:54:39,186 nipype.workflow INFO:
	 ANAT Stage 3: Preparing segmentation workflow
240905-21:54:39,190 nipype.workflow INFO:
	 ANAT Stage 4: Preparing normalization workflow for ['MNI152NLin2009cAsym']
240905-21:54:39,199 nipype.workflow INFO:
	 ANAT Stage 5: Preparing surface reconstruction workflow
240905-21:54:39,215 nipype.workflow INFO:
	 ANAT Found brain mask - skipping Stage 6

Screenshots / relevant information:

See above


From digging around, I’m pretty sure that this coregistration failure is unrelated to the mask. Instead, it seems like the misregistration is from an unstable step. The node’s working directory is

work/fmriprep_24_0_wf/sub_[sub]_wf/bold_ses_[ses]_task_[task]_run_[run]_wf/bold_fit_wf/fmapreg_wf/coregister

underneath which is a command like


antsRegistration \
    --collapse-output-transforms 1 \
    --dimensionality 3 \
    --initial-moving-transform \
    \[ /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/fmap_preproc_wf/wf_auto_00000/brainextraction_wf/clipper_post/clipped.nii.gz, /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/bold_ses_NS_task_rest_run_01_wf/bold_fit_wf/enhance_and_skullstrip_bold_wf/n4_correct/sub-travel2_ses-NS_task-rest_run-01_bold_average_corrected.nii.gz, 1 \] \
    --initialize-transforms-per-stage 0 \
    --interpolation LanczosWindowedSinc \
    --output transform8 \
    --transform Rigid\[ 0.1 \] \
    --metric Mattes\[ /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/fmap_preproc_wf/wf_auto_00000/brainextraction_wf/clipper_post/clipped.nii.gz, /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/bold_ses_NS_task_rest_run_01_wf/bold_fit_wf/enhance_and_skullstrip_bold_wf/n4_correct/sub-travel2_ses-NS_task-rest_run-01_bold_average_corrected.nii.gz, 1, 32, Random, 0.25 \] \
    --convergence \[ 50, 1e-07, 5 \] \
    --smoothing-sigmas 8.0mm \
    --shrink-factors 2 \
    --use-histogram-matching 1 \
    --masks \[ /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/fmap_preproc_wf/wf_auto_00000/brainextraction_wf/masker/clipped_mask.nii.gz, /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/bold_ses_NS_task_rest_run_01_wf/bold_fit_wf/enhance_and_skullstrip_bold_wf/combine_masks/sub-travel2_ses-NS_task-rest_run-01_bold_average_corrected_brain_mask_maths.nii.gz \] \
    --transform Rigid\[ 0.1 \] \
    --metric Mattes\[ /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/fmap_preproc_wf/wf_auto_00000/brainextraction_wf/clipper_post/clipped.nii.gz, /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/bold_ses_NS_task_rest_run_01_wf/bold_fit_wf/enhance_and_skullstrip_bold_wf/n4_correct/sub-travel2_ses-NS_task-rest_run-01_bold_average_corrected.nii.gz, 1, 32, Random, 0.5 \] \
    --convergence \[ 20, 1e-08, 3 \] \
    --smoothing-sigmas 2.0mm \
    --shrink-factors 1 \
    --use-histogram-matching 1 \
    --masks \[ /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/fmap_preproc_wf/wf_auto_00000/brainextraction_wf/masker/clipped_mask.nii.gz, /Users/psadil/Desktop/NStravel2/work2/fmriprep_24_0_wf/sub_travel2_wf/bold_ses_NS_task_rest_run_01_wf/bold_fit_wf/enhance_and_skullstrip_bold_wf/combine_masks/sub-travel2_ses-NS_task-rest_run-01_bold_average_corrected_brain_mask_maths.nii.gz \] \
    --winsorize-image-intensities \[ 0.001, 0.999 \]  \
    --write-composite-transform 0

Running this command (and converting the result with ConcatenateXFMs) several times produces pretty variable transformation matrices. Here are 6

==> out1_fwd.tfm <==
#Insight Transform File V1.0
#Transform 0
Transform: AffineTransform_float_3_3
Parameters: 0.999672 -0.0016924 -0.0255665 0.00277344 0.999101 0.0423075 0.0254719 -0.0423645 0.998777 1.10469 -2.04965 0.734481
FixedParameters: 0 0 0
==> out2_fwd.tfm <==
#Insight Transform File V1.0
#Transform 0
Transform: AffineTransform_float_3_3
Parameters: 0.999889 0.00556549 -0.0138201 -0.00529245 0.999792 0.0197156 0.013927 -0.0196402 0.99971 0.274105 -1.77395 -0.445344
FixedParameters: 0 0 0
==> out3_fwd.tfm <==
#Insight Transform File V1.0
#Transform 0
Transform: AffineTransform_float_3_3
Parameters: 0.999479 -0.00549332 -0.0317923 0.00689586 0.999 0.0441753 0.0315179 -0.0443716 0.998518 1.41946 -2.57649 1.31726
FixedParameters: 0 0 0
==> out4_fwd.tfm <==
#Insight Transform File V1.0
#Transform 0
Transform: AffineTransform_float_3_3
Parameters: 0.987015 -0.0158937 -0.15984 0.037051 0.990786 0.130272 0.156297 -0.134503 0.978509 -12.1044 14.0549 -4.84802
FixedParameters: 0 0 0
==> out5_fwd.tfm <==
#Insight Transform File V1.0
#Transform 0
Transform: AffineTransform_float_3_3
Parameters: 0.96765 0.0557279 -0.246066 -0.0187037 0.988462 0.15031 0.251604 -0.140845 0.957527 -4.86533 4.64465 -8.38042
FixedParameters: 0 0 0
==> out6_fwd.tfm <==
#Insight Transform File V1.0
#Transform 0
Transform: AffineTransform_float_3_3
Parameters: 0.999198 -0.00150606 -0.0400227 0.00331347 0.998976 0.045132 0.0399137 -0.0452284 0.998179 2.26726 -2.61363 1.85205
FixedParameters: 0 0 0

This is discouraging because the images start out well-aligned and the masks look fine.

boldref

fmapref

masks

So, the issue is at least easy to detect automatically (large movement within the transformation matrices indicates some failure). And it seems like I could make this step at least reproducible by, e.g., setting the environment variable ANTS_RANDOM_SEED. But, are there suggestions for making this more robust?

Also, FWIW, the files can be shared

It seems like a mask may be key, but not the one from synthstrip. The current call to antsRegistration includes a mask for both images. Both masks look fine (see above), but if I either dilate the masks by a few mm or remove the masks entirely, the coregistration is reliably accurate.

I’m noticing that there are warnings about masks in the ANTs documentation, here

A mask restricts computation of the metric to a certain region. The mask should be 1 where similarity should be evaluated, and 0 otherwise. Anything outside the mask does not contribute to the similarity metric. This can help but also hinder registration. It can help by ignoring things that are leading to problems (eg, if we’re trying to align brains within a head image, we might not care about the neck so much). But it can also destabilize registration as points moving into and out of the mask can change the metric sharply. Masking out smaller regions to deal with specific problems (eg, lesions) is less likely to cause problems than attempting to draw a mask around the entire structure of interest.

and here

  • A registration mask can help with some problems such as structured background noise or anatomy of no interest, or inconsistent anatomical fields of view. Masks can also reduce registration quality as only the voxels inside the mask are considered by the registration. If possible, there should be some background voxels in the mask so that edges can be properly aligned. You may also apply masks at each stage of registration, using a wider mask (or none at all) for the rigid or affine stages, and a more restrictive mask for deformable stages.

This isn’t quite clear to me, but what I’m getting is that masking can be tricky. The fmap epi has very bright voxels near the edge of the mask, and so this is a case where small shifts lead to big changes in the cost function (high-intensity voxels moving in and out of the mask). But also there are a few large patches of high-intensity voxels near the edge, so the relatively tight mask (that doesn’t include much from outside the brain) might make it hard to pin the two images together; a lot of information may be missing because the tight masks restrict use of the boundary between brain and not brain

Discussion (including possible solution!) moved to Dilate fmap and epi masks before coregistration · Issue #461 · nipreps/sdcflows · GitHub