Aligning PET data to MNI space using FMRIPREP transform

Summary of what happened:

I have PET-MR data (dynamic PET frames) and I am trying to compute registration from PET native space to MNI space to look at group level binding potential results. I have run fmriprep on the BOLD and anatomical data, which generated good registrations, but I am having some trouble creating a composite registration from PET native space to MNI space.

Specifics:

I used ants to create an affine registration between the PET native space (using the mean PET image as a template) and the preprocessed T1w FMRIPREP output (T1w space). This registration works really well, ie an output pet_template_space-T1w.nii.gz is very well aligned to the preproc_T1w file. If I then apply the T1w_to_MNI .h5 transform file from FMRIPREP to this T1w space PET template, the resulting pet_template_space-MNI.nii.gz file looks well aligned to the MNI space output.

Based on this I thought I would be able to combine the affine I computed and the fmriprep composite transform into a single step to avoid resampling to T1w space in the middle, but the resulting warped pet_template is somewhat off from the MNI template. I generated the combined PET to MNI transform by calling ants.apply_transform(fixed,moving,[PET_to_T1w, T1w_to_MNI],compose=PET_to_MNI_file) to generate an output PET_to_MNI.nii.gz warp file and then apply this to the pet_template.nii.gz file. I also tried just calling
ants.apply_transform(fixed,moving,[PET_to_T1w, T1w_to_MNI]) to just save the output. Both of these result in the exact same MNI space pet template image that is not properly aligned.

I tried this on multiple subjects and achieved the same result: if I actually transform the PET_template into the T1w space, and then apply the fmriprep transform, I get a good result. If I try to go directly from PET to MNI space by combining the transform I computed and the one from FMRIPREP, it doesn’t align.

Happy to hear any suggestions! I’m wondering if it has to do better performance from actually resampling to the 1mm T1w space before registering to MNI (I’m using 2mm MNI2009cAsym template). PET native space is ~ 2mm.

Below is the relevant code:

 # load images for registration
    template = ants.image_read(template_file)
    T1w = ants.image_read(T1w_file)

    # Perform affine registration between template and anatomical
    registration = ants.registration(fixed=T1w, moving=template, type_of_transform='Affine',metric='MI',outprefix=REGDIR+'/')
    
    # save T1w space PET template
    template_T1w_file = template_file.split('.')[0] +'_T1w.nii.gz'
    ants.image_write(registration['warpedmovout'],template_T1w_file)
    
    # save PET to T1w registration file
    PET_to_T1w_affine_file = os.path.join(REGDIR,'PET_to_T1w_Affine.mat')
    shutil.move(registration['fwdtransforms'][0], PET_to_T1w_affine_file)
    
    # load composite transforms from fmriprep
    T1w_to_MNI_file=glob.glob(os.path.join(bidsdir,'derivatives','fmriprep','sub-'+subname,'anat','sub-'+subname+'*_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5'))[0]
    
    # load MNI space T1w image
    T1w_MNI_file = glob.glob(os.path.join(bidsdir,'derivatives','fmriprep','sub-'+subname,'anat','sub-'+subname+'*_space-MNI152NLin2009cAsym*desc-preproc_T1w.nii.gz'))[0]
    T1w_MNI = ants.image_read(T1w_MNI_file)
    
    # combine registrations and save combined transform file
    PET_to_MNI_file = os.path.join(datadir,subname,'pet','reg','PET_to_MNI_')
    comptx_file = ants.apply_transforms(T1w_MNI,template,[PET_to_T1w_affine_file,T1w_to_MNI_file],compose=PET_to_MNI_file)

    # apply PET to MNI transform to dynamic_template
    template_MNIspace = ants.apply_transforms(T1w_MNI,template,[comptx_file])
    ants.image_write(template_MNIspace,os.path.join(REGDIR,'dynamic_template_MNI.nii.gz'))

    # try registering to T1w then to MNI
    template_MNIspace_fromT1w = ants.apply_transforms(T1w_MNI,ants.image_read(template_T1w_file),[T1w_to_MNI_file])
    ants.image_write(template_MNIspace_fromT1w,os.path.join(REGDIR,'dynamic_template_MNI_fromT1w.nii.gz'))

What do you mean it doesn’t align: it does nothing or it does something but the result is off?

Here are a few suggestions:

  • Did you try to invert the order in which you apply your transforms?
  • Here is an example of the fmriprep command to project a functional image into the template space using the affine transform bold-> T1w and a composition transform T1w-> MNI. What you are trying to do is indeed the same with only one transform in addition to the composite transform in your case against 3 transformation in the example below (motion correction, SDC correction and affine bold-> T1w transformation).
antsApplyTransforms --default-value 0 --float 1 \
 --input bold.nii.gz \
 --interpolation LanczosWindowedSinc \
 --output bold_in_MNI.nii.gz \
 --reference-image tpl-MNI152NLin2009cAsym_res-02_T1w_reference.nii.gz \
 --transform ants_t1_to_mniComposite.h5 \
 --transform bold_reg_wf/bbreg_wf/concat_xfm/out_fwd.tfm \
 --transform unwarp_wf/resample/vol0000_xfm.nii.gz \
 --transform mat2itk_pos-003_xfm-00000.txt

If you do a one-step resampling to your template, you don’t have to worry about resampling in the T1w space.

Hi @jsein, sorry if that wasn’t clear. When I say it doesn’t align, I mean that doing a single combined transformation from PET native space to MNI space generates a an output that is close to looking good, but is not as good as the output I get when I first register PET to T1w space, and then register that image to MNI space.

You are correct that I want to do a one-step resampling. My point is that the registration looks better if I have an intermediate step of resampling to T1w space, even though I’m using all the same transformations.

I’m going to try computing the intermediate PET to T1w registration using a T1w image resampled to the PET voxel size.

I can also try switching the order of the transforms, but it was my understanding that in the ants.apply_transforms() call that the transforms were applied in the order that they are listed in the transform_list argument. Ie in this case transform_list=[PET_to_T1w, T1w_to_MNI], but I can definitely try transform_list=T1w_to_MNI,PET_to_T1w]

UPDATE: FIXED.

The solution turned out to be switching the order of the transforms in the call to ants.apply_transforms() so that transform_list=[T1w_to_MNI,PET_to_T1w], thanks for the suggestion @jsein !