How to apply nonlinear warp parameters to existing preprocessed files?

AFNI version info (afni -ver): Precompiled binary macos_13_ARM: Apr 7 2025 (Version AFNI_25.1.00 'Maximinus')

Dear AFNI experts,

Is there a way to apply nonlinear warp parameters to existing preprocessed files (so that the warp is applied to all output data), without the need to reprocess it all again?

I've used the following preprocessing pipeline for my subjects (example script for one subject):

set subj = '241113LB'
set tdir = '/Volumes/Data/fMRI_ToneLanguage/data/timing_models/241113LB'

cd /Volumes/Data/fMRI_ToneLanguage/data/241113LB

ln -s 241113LB_prosody_run1_20241113162218_3.nii ${subj}_MB_prosody_run1.nii
ln -s 241113LB_prosody_run2_20241113162218_7.nii ${subj}_MB_prosody_run2.nii
ln -s 241113LB_prosody_run3_20241113162218_11.nii ${subj}_MB_prosody_run3.nii
ln -s 241113LB_prosody_run4_20241113162218_15.nii ${subj}_MB_prosody_run4.nii

ln -s 241113LB_MPRAGE-GRAPPA2-1x1x1-208sl-100pcFOV_20241113162218_18.nii ${subj}_MB_anat.nii

ln -s 241113LB_prosody_run4_PE_REV_20241113162218_17.nii ${subj}_MB_prosody_prev4.nii

	afni_proc.py -subj_id ${subj} \
		-script afni_proc_${subj}_prosody_task.tcsh \
		-out_dir afni_proc_${subj}_prosody_task.results \
		-dsets ${subj}_MB_prosody_run1.nii ${subj}_MB_prosody_run2.nii \
			${subj}_MB_prosody_run3.nii ${subj}_MB_prosody_run4.nii \
		-copy_anat ${subj}_MB_anat.nii \
		-anat_has_skull yes \
		-blocks align tlrc volreg mask blur scale regress \
		-blip_reverse_dset ${subj}_MB_prosody_prev4.nii \
		-blip_forward_dset ${subj}_MB_prosody_run4.nii \
		-radial_correlate_blocks tcat volreg regress \
		-tcat_remove_first_trs 6 \
		-align_unifize_epi local \
		-align_opts_aea -cost lpc+ZZ \
						-giant_move \
						-check_flip \
		-tlrc_base MNI152_2009_template.nii.gz \
		-volreg_align_to MIN_OUTLIER \
		-volreg_align_e2a \
		-volreg_tlrc_warp \
		-volreg_warp_dxyz 3 \
		-volreg_compute_tsnr yes \
		-blur_size 4 \
		-blur_in_automask \
		-regress_reml_exec \
		-regress_motion_per_run \
		-regress_local_times \
		-regress_censor_motion 0.3 \
		-regress_stim_times $tdir/241113LB_consistent75_exact.txt \
							$tdir/241113LB_consistent100_exact.txt \
							$tdir/241113LB_conflicting75_exact.txt \
							$tdir/241113LB_conflicting100_exact.txt \
		-regress_stim_labels consistent75 consistent100 conflicting75 conflicting100 \
		-regress_stim_types AM1 \
		-regress_basis 'dmUBLOCK(-1)' \
		-regress_opts_3dD \
			-gltsym 'SYM: +consistent75 + consistent100 -conflicting75 -conflicting100' \
			-glt_label 1 'ConsistentConflicting' \
			-jobs 4 \
		-regress_make_ideal_sum sum_ideal.1D \
		-regress_est_blur_epits \
		-regress_est_blur_errts \
		-regress_run_clustsim no \
		-html_review_style pythonic \
		-execute

As far as I understand, I missed the -tlrc_NL_warp \ option in my preprocessing script. It would be great if I could apply it to the already processed data rather than having to reprocess everything. Could you please help? Thanks!

Howdy-

This is possible. Indeed, we normally recommend people to calculate nonlinear alignment prior to running AP, so that it is faster to run and re-run AP itself. This would typically be done for human data by running sswarper2 to calculate both skullstripping (SS) and nonlinear alignment (warping) of the subject anatomical to a reference template; for nonhuman datasets, the @animal_warper program accomplishes both goals, and its results can be used in exactly the same way.

When telling AP that you want to be using nonlinear alignment in the tlrc block you add this opt:

-tlrc_NL_warp

... otherwise, it will use affine registration only (which might be useful for quick assessments, but not more polished processing).

We would typically also then add this (which I see you already have in your AP command):

-volreg_tlrc_warp

... which does:

With this option, the EPI data will be warped to standard space
in the volreg processing block.  All further processing through
regression will be done in standard space.

The sswarper2 and @animal_warper programs each output the full anatomical->template warp in 2 pieces: an initial affine alignment (*.1D text file) and a nonlinear warp refinement (*.nii.gz dataset with 3 volumes, because we live in a 3D world). That information is provided, along with a version of warped anatomical in standard space, in afni_proc.py with the following kind of template:

-tlrc_NL_warped_dsets                         \
    DSET_ANAT_IN_TEMPLATE                     \
    1D_AFFINE_FILE                            \
    DSET_WARP

If you had run sswarper2 specifically, this would look like:

-tlrc_NL_warped_dsets                         \
    anatQQ.${subj}.nii                        \
    anatQQ.${subj}.aff12.1D                   \
    anatQQ.${subj}_WARP.nii

So, what did you use to calculate your nonlinear alignment, but didn't apply? Did you happen to use sswarper2 (or its precursor, @SSwarper)? Then you can just add that to your existing AP command, along with the above two options.

(If you just used 3dQwarp on its own or auto_warp.py, you can still apply those results here, but those programs include the affine parameters in the warp dataset, rather than leaving the pieces separate; so, instead of a *.1D file to add separately, you could make your own identity-affine-matrix file, which you could call "mat_identity.aff12.1D", made up of these 12 numbers in a row: 1 0 0 0 1 0 0 0 1 0 0 0; you would provide that along with your full warp.)

--pt

ps: here is your AP command from above, with vertical alignment of opts+args, which I find helpful for reading:

afni_proc.py                                                                 \
    -subj_id                  ${subj}                                        \
    -script                   afni_proc_${subj}_prosody_task.tcsh            \
    -out_dir                  afni_proc_${subj}_prosody_task.results         \
    -dsets                    ${subj}_MB_prosody_run1.nii                    \
                              ${subj}_MB_prosody_run2.nii                    \
                              ${subj}_MB_prosody_run3.nii                    \
                              ${subj}_MB_prosody_run4.nii                    \
    -copy_anat                ${subj}_MB_anat.nii                            \
    -anat_has_skull           yes                                            \
    -blocks                   align tlrc volreg mask blur scale regress      \
    -blip_reverse_dset        ${subj}_MB_prosody_prev4.nii                   \
    -blip_forward_dset        ${subj}_MB_prosody_run4.nii                    \
    -radial_correlate_blocks  tcat volreg regress                            \
    -tcat_remove_first_trs    6                                              \
    -align_unifize_epi        local                                          \
    -align_opts_aea           -cost lpc+ZZ                                   \
                              -giant_move                                    \
                              -check_flip                                    \
    -tlrc_base                MNI152_2009_template.nii.gz                    \
    -volreg_align_to          MIN_OUTLIER                                    \
    -volreg_align_e2a                                                        \
    -volreg_tlrc_warp                                                        \
    -volreg_warp_dxyz         3                                              \
    -volreg_compute_tsnr      yes                                            \
    -blur_size                4                                              \
    -blur_in_automask                                                        \
    -regress_reml_exec                                                       \
    -regress_motion_per_run                                                  \
    -regress_local_times                                                     \
    -regress_censor_motion    0.3                                            \
    -regress_stim_times       $tdir/241113LB_consistent75_exact.txt          \
                              $tdir/241113LB_consistent100_exact.txt         \
                              $tdir/241113LB_conflicting75_exact.txt         \
                              $tdir/241113LB_conflicting100_exact.txt        \
    -regress_stim_labels      consistent75 consistent100 conflicting75       \
                              conflicting100                                 \
    -regress_stim_types       AM1                                            \
    -regress_basis            'dmUBLOCK(-1)'                                 \
    -regress_opts_3dD         -gltsym                                        \
                              'SYM: +consistent75 + consistent100 -conflicting75 -conflicting100' \
                              -glt_label 1 'ConsistentConflicting'           \
                              -jobs 4                                        \
    -regress_make_ideal_sum   sum_ideal.1D                                   \
    -regress_est_blur_epits                                                  \
    -regress_est_blur_errts                                                  \
    -regress_run_clustsim     no                                             \
    -html_review_style        pythonic                                       \
    -execute


Hi Paul,

Thank you so much for your detailed response!

It took me a while to reply because I needed to consult with my collaborators about how we want to proceed. Being concerned about “hacking” into the pipeline, we decided to go ahead and reprocess the data.

What I did before was try to apply nonlinear warping directly in AP without running sswarper2 first, but it seems I missed some steps. This is my first fMRI project, so I do get lost sometimes.

Do I understand your suggestion correctly that your recommended approach would be to (1) run sswarper2 to perform skull stripping and warping, and then (2) run AP separately, using the outputs from sswarper2 as inputs?

Would you be so kind as to check if the code below is the correct way to do this?

Are there any other considerations we should be aware of?

# Step 1
sswarper2                                \
    -input  ${subj}_MB_anat.nii          \
    -base   MNI152_2009_template.nii.gz  \
    -subid  ${subj}
# Step 2
afni_proc.py                                                                 \
    -subj_id                  ${subj}                                        \
    -script                   afni_proc_${subj}_prosody_task.tcsh            \
    -out_dir                  afni_proc_${subj}_prosody_task.results         \
    -dsets                    ${subj}_MB_prosody_run1.nii                    \
                              ${subj}_MB_prosody_run2.nii                    \
                              ${subj}_MB_prosody_run3.nii                    \
                              ${subj}_MB_prosody_run4.nii                    \
    -copy_anat                ${subj}_MB_anat.nii                            \
    -anat_has_skull           no                                             \
    -blocks                   align tlrc volreg mask blur scale regress      \
    -blip_reverse_dset        ${subj}_MB_prosody_prev4.nii                   \
    -blip_forward_dset        ${subj}_MB_prosody_run4.nii                    \
    -radial_correlate_blocks  tcat volreg regress                            \
    -tcat_remove_first_trs    6                                              \
    -align_unifize_epi        local                                          \
    -align_opts_aea           -cost lpc+ZZ                                   \
                              -giant_move                                    \
                              -check_flip                                    \
    -tlrc_base                MNI152_2009_template.nii.gz                    \
    -tlrc_NL_warp                                                            \
    -tlrc_NL_warped_dsets     anatQQ.${subj}.nii                             \
                              anatQQ.${subj}.aff12.1D                        \
                              anatQQ.${subj}_WARP.nii                        \
    -volreg_align_to          MIN_OUTLIER                                    \
    -volreg_align_e2a                                                        \
    -volreg_tlrc_warp                                                        \
    -volreg_warp_dxyz         3                                              \
    -volreg_compute_tsnr      yes                                            \
    -blur_size                4                                              \
    -blur_in_automask                                                        \
    -regress_reml_exec                                                       \
    -regress_motion_per_run                                                  \
    -regress_local_times                                                     \
    -regress_censor_motion    0.3                                            \
    -regress_stim_times       $tdir/241113LB_consistent75_exact.txt          \
                              $tdir/241113LB_consistent100_exact.txt         \
                              $tdir/241113LB_conflicting75_exact.txt         \
                              $tdir/241113LB_conflicting100_exact.txt        \
    -regress_stim_labels      consistent75 consistent100 conflicting75       \
                              conflicting100                                 \
    -regress_stim_types       AM1                                            \
    -regress_basis            'dmUBLOCK(-1)'                                 \
    -regress_opts_3dD         -gltsym                                        \
                              'SYM: +consistent75 + consistent100 -conflicting75 -conflicting100' \
                              -glt_label 1 'ConsistentConflicting'           \
                              -jobs 4                                        \
    -regress_make_ideal_sum   sum_ideal.1D                                   \
    -regress_est_blur_epits                                                  \
    -regress_est_blur_errts                                                  \
    -regress_run_clustsim     no                                             \
    -html_review_style        pythonic                                       \
    -execute

Howdy-

That sounds like a good way forward. Indeed, there are a lot of details to manage with processing, and it usually takes me a couple goes-through of datasets in a new collection to figure out what is most appropriate and set it up.

Yes, your setup of running sswarper2 (SSW) and then afni_proc.py (AP) is what I would do in general for human data processing (if it were nonhuman data, then just swapping in @animal_warper for the former program.

For SSW, you have to a specific multi-volume reference dataset. These are described here. The one that corresponds exactly to the volume you have listed there is called MNI152_2009_template_SSW.nii.gz, so you can just cahnge line 4 there to be:

-base MNI152_2009_template_SSW.nii.gz

In your AP, you could technically then use either of those datasets for the -tlrc_base .. dset, but I think it probably is just clearer to use the same filename:

-tlrc_base MNI152_2009_template_SSW.nii.gz

One other thing about your AP command: I suspect you will need to prepend a path to your SSW output directories, so like you have

-tlrc_NL_warped_dsets     anatQQ.${subj}.nii

... that will probably need to be something like this, defining a variable with the path:

-tlrc_NL_warped_dsets     ${ssw_dir}/anatQQ.${subj}.nii

When people provide full AP commands and ask our advice (which is great! this is one reason we like having a scriptable command, so we can see the full set of opts all at once), we often use some functionality Rick wrote to compare an AP commands set of options against ones we are familiar with from Bootcamp teaching or previous studies. This is because (as you might have noticed), there are lots of choices to be made when processing.

So, I ran your command with the additional option:

-compare_opts 'publish 3b'

This goes through and reports what is similar and different for option usage (and secondarily, their arguments) between your current command and the reference "target" command. In this case, "publish 3b" means the second example in publication 3:

  • Reynolds RC, Glen DR, Chen G, Saad ZS, Cox RW, Taylor PA (2024). Processing, evaluating and understanding FMRI data with afni_proc.py.
    Imaging Neuroscience 2:1-52.

Now, your command is definitely going to have differences, and this doesn't mean they are errors, but they are things to maybe consider. Filenames will also typically be different, too. By doing this, and checking first the "missing options" section (which means options in the publish 3b example that don't appear in your command), I would probably consider adding these opts, and I put comments above each about why, briefly, but you can see the above paper for more description:

# mask is made by intersecting EPI and anatomical masks
-mask_epi_anat            yes

# let AP use the *non*skullstripped data and send it to final space,
# which can be useful for verifying alignment and/or CSF sticking 
# out of the EPI, etc. So, mainly just for some QC; this dset is output
# by SSW.
-anat_follower            anat_w_skull anat ${ssw_dir}/anatU.${subj}.nii

# in addition to motion-based censoring, this is a useful secondary
# option to catch potential badness in a volume
-regress_censor_outliers  0.05

# can be useful to compute+output the "fit" time series
-regress_compute_fitts

In the "Extra options" section, it totally makes sense for you to use the -blip_* options like you have; the publish 3b task data just didn't happen to have those to use. You also have a different regress basis, so fine. If you have your timing files set up for "local times", then fine, too. Choosing to blur within an automask can be fine.

I guess you don't have slice timing info, to apply "tshift"? Such is life.

For the blur size: if you will be doing ROI-based analysis, you should not blur. For voxelwise analysis with single echo data, we often recommend using a FWHM value that is 1.5-2 times the minimum voxel size; for multiecho FMRI data (which you don't have here), we would blur just minimally above voxel size. But again, you may have reasons to choose something outside those ranges, those are just "vanilla mode" guides to start with. Some of this, and other processing choice tidbits, are discussed in detail in the above paper's Discussion section, and also in this soon-to-be-presented-at-OHBM poster.

Note that when you are removing initial TRs, which is common, your timing files should refer to timing where zero is the first volume *after the removal of TRs has taken place:

-tcat_remove_first_trs    6 

So, I just mention this so you are sure your timing files are set for that.

On questions of regression modeling, I will let @rickr and @Gang voice any opinions.

--pt