Running afni_proc over runs for multi-echo data

Hi AFNI team again,

I've successfully executed the following preprocessing script for a single run of multi-echo data. Now, I would like to extend this process across various runs. This tcsh script, named 'afni2_preproc.tcsh', is being executed on an HPC server using the Slurm scheduler. In my search for a solution, I came across 'run_26_ap_me_bT.tcsh' in the demo data, but I'm uncertain if it can be adapted for processing multiple runs within each subject. Should I make a run loop in this script?
Any advice or guidance on this matter would be greatly appreciated!

Thanks,
Byeol

AFNI version info (afni -ver): 23.0.04

...
# dataset inputs
set dsets_epi_me  = ( ${sdir_epi}/${subj}_${ses}_*run-05_echo-?_bold.nii* )
set me_times      = ( 13.2 31.45 49.7 )

set epi_forward   = "${sdir_fmap}/${subj}_${ses}_acq-mb3_dir-ap_run-01_epi.nii.gz[0..1]"
set epi_reverse   = "${sdir_fmap}/${subj}_${ses}_acq-mb3_dir-pa_run-01_epi.nii.gz[0..1]"

set anat_cp       = ${sdir_ssw}/anatSS.${subj}.nii
set anat_skull    = ${sdir_ssw}/anatU.${subj}.nii

set dsets_NL_warp = ( ${sdir_ssw}/anatQQ.${subj}.nii         \
                      ${sdir_ssw}/anatQQ.${subj}.aff12.1D    \
                      ${sdir_ssw}/anatQQ.${subj}_WARP.nii )
		      
# control variables
set nt_rm         = 8      # pre-steady state volume (dummy)
set blur_size     = 4      # smaller blur, since ME and NL warp
set final_dxyz    = 3      # can test against inputs
set cen_motion    = 0.3
set cen_outliers  = 0.1

# ---------------------------------------------------------------------------
# run programs
# ---------------------------------------------------------------------------

set ap_cmd = ${sdir_ap}/ap.cmd.${subj}

\mkdir -p ${sdir_ap}

# write AP command to file
cat <<EOF >! ${ap_cmd}

# ME:
#   - -dsets_me_run, -echo_times, -combine_method
#   - mask combine blur

afni_proc.py                                                            \
     -subj_id                  ${subj}                                  \
     -blocks despike tshift align tlrc volreg mask combine blur scale   \
         regress                                                        \
     -radial_correlate_blocks  tcat volreg                              \
     -copy_anat                ${anat_cp}                               \
     -anat_has_skull           no                                      \
     -blip_forward_dset        "${epi_forward}"                         \
     -blip_reverse_dset        "${epi_reverse}"                         \
     -dsets_me_run             ${dsets_epi_me}                          \
     -echo_times               ${me_times}                              \
     -align_unifize_epi        local                                    \
     -combine_method           OC                                       \
     -tcat_remove_first_trs    ${nt_rm}                                 \
     -tshift_interp            -wsinc9                                  \
     -align_opts_aea           -cost lpc+ZZ -giant_move -check_flip     \
     -tlrc_base                ${template}                              \
     -tlrc_NL_warp                                                      \
     -tlrc_NL_warped_dsets     ${dsets_NL_warp}                         \
     -volreg_align_to          MIN_OUTLIER                              \
     -volreg_align_e2a                                                  \
     -volreg_tlrc_warp                                                  \
     -volreg_warp_final_interp  wsinc5                                  \
     -volreg_warp_dxyz         ${final_dxyz}                            \
     -volreg_compute_tsnr      yes                                      \
     -blur_size                ${blur_size}                             \
     -mask_epi_anat            yes                                      \
     -regress_motion_per_run                                            \
     -regress_censor_motion    ${cen_motion}                            \
     -regress_censor_outliers  ${cen_outliers}                          \
     -regress_apply_mot_types  demean deriv                             \
     -regress_est_blur_epits                                            \
     -regress_est_blur_errts                                            \
     -html_review_style        pythonic  

   

EOF

cd ${sdir_ap}

# execute AP command to make processing script
tcsh -xef ${ap_cmd} |& tee output.ap.cmd.${subj}

set ecode = ${status}

if ( ! ${ecode} ) then
   # execute the proc script, saving text info
   time tcsh -xef proc.${subj} |& tee output.proc.${subj}
endif

set ecode = ${status}
if ( ${ecode} ) then
    echo "++ FAILED AP: ${ap_label}"
else
    echo "++ FINISHED AP: ${ap_label}"
endif

# ---------------------------------------------------------------------------

exit ${ecode}

Hi, Byeol-

Yes, afni_proc.py processes a single subject's data. In your script that is shown, I expect probably everything will stay the same, except subject ID. That can be true even of your path structures, if you are including only pieces that vary in subject ID there, which is increasingly common.

Therefore, the top of your script DO_SCRIPT.tcsh could take in a variable from the command line to run the script, like:

#!/bin/tcsh

set subj = $1

...

... so that if you execute it as:

# run for subject "sub-101"
tcsh DO_SCRIPT.tcsh sub-101

# run for subject "sub-102"
tcsh DO_SCRIPT.tcsh sub-102

... then within the script the variable "$subj" will be assigned "sub-101" in the first case, "sub-102" in the second, etc.

As a schematic description, to execute all the subjects, you can essentially make a script that contains the lines to run processing over all of them, in a group level run script RUN_SCRIPT.tcsh:

#!/bin/tcsh

# list of subject IDs, can be arbitrarily long
set all_subj = ( sub-101 sub-102 sub-103 )  

# ----- setup script that contains all calls to do_*.tcsh script 
set my_script = "all_ap_runs.tcsh"
# ... and make sure that script is empty/null to start
printf "" > ${my_script}

# loop over all subj to populate the script,
#  including a line to log terminal output
foreach subj ( ${all_subj} )
    echo "tcsh DO_SCRIPT.tcsh ${subj} |& tee log.ap_${subj}.txt" >> ${my_script}
end

After running tcsh RUN_SCRIPT.tcsh, you should have a new script called "all_ap_runs.tcsh" that contains a sequence of execution commands for the DO_SCRIPT.tcsh.

On a normal desktop/laptop computer, that script can be executed (tcsh all_ap_runs.tcsh) and each dataset will be processed in sequence. But, if you are luckier, you have access to a cluster/HPC system, and you can execute that script in a special way that each line is executed in parallel, running each subject simultaneously on different nodes. That would involve including extra information in your RUN_SCRIPT.tcsh above, but that would be a good way to go. The details for doing that depend on your HPC/cluster system.

At NIMH, we are lucky enough to have the "Biowulf" computing cluster, which uses slurm to execute scripts like that in parallel. We have example of how to do that for slurm here in the multiecho demo scripts.

--pt

Just to add on to @ptaylor's response - afni_proc can easily process all of the runs within a given subject. That line at the beginning: set dsets_epi_me has wildcards available to allow it to set multiple runs. You could do something like:

set dsets_epi_me  = ( 
${sdir_epi}/${subj}_${ses}_*run-01_echo-*_bold.nii* \
${sdir_epi}/${subj}_${ses}_*run-02_echo-*_bold.nii* \
${sdir_epi}/${subj}_${ses}_*run-03_echo-*_bold.nii* \
)

Notice that there is the option in the actual script -dsets_me_run that specifies that you have the order run1 with all echos, then run 2 with all echos, etc. The above syntax will give that to you.

Hi Peter,
Thank you for your reply. I've tried that and dsets_epi_me is correctly selecting 6 images across two runs. However, it gave me an error saying "have 6 echoes, but 3 echo times." Is there anything else I need to consider in this script?

Thanks, Byeol

Hi Taylor,

Thank you for your thorough response; it's greatly beneficial for managing the script across different subjects and sessions.
Just to clarify my question, I wish to execute the afni_proc script for seven runs within a single session, all through a singular tcsh script. Would this be feasible?

Byeol

Hi, Byeol-

Yes, I think you should be able to have multiple runs of multi-echo EPI, namely by including multiple "-dsets_me_run ..` options. In the AP help:

    -dsets_me_run dset1 dset2 ...   : specify ME datasets for one run
                                      (all echoes with each option)

       These examples might correspond to 4 echoes across 2 runs.

            e.g. -dsets_me_run epi_run1.echo_*+orig.HEAD
                 -dsets_me_run epi_run2.echo_*+orig.HEAD

            e.g. -dsets_me_run r1.e*.nii
                 -dsets_me_run r2.e*.nii

            e.g. -dsets_me_run r1.e1.nii r1.e2.nii r1.e3.nii r1.e4.nii
                 -dsets_me_run r2.e1.nii r2.e2.nii r2.e3.nii r2.e4.nii

        This option is convenient when there are more echoes than runs.

Can you try that?

--------- separate note on mechanics of scripting-----

If I understand correctly, you might want to loop over both subjects and session? You could have 2 variables at the top of your file:

#!/bin/tcsh

set subj = $1
set ses  = $2
...

Actually, on a scripting note, I might put in a guard to make sure the user uses 2 options:

#!/bin/tcsh

set subj = $1
set ses  = $2

if ( "${ses}" == "" ) then
    echo "** ERROR: hey, you need to provide 2 command line args:"
    echo "    subj_id  ses_id"
    exit 1
endif
...

And then have a double loop within the run*tcsh script, looping over the separate sessions per subjects. This implies you know how many sessions there are per subject. If there are always 2 and they are called ses-01 and ses-02, for example, you could have RUN_SCRIPT.tcsh look like:

#!/bin/tcsh

# list of subject IDs, can be arbitrarily long
set all_subj = ( sub-101 sub-102 sub-103 )  
set all_ses  = ( ses-01 ses-02 )

# ----- setup script that contains all calls to do_*.tcsh script 
set my_script = "all_ap_runs.tcsh"
# ... and make sure that script is empty/null to start
printf "" > ${my_script}

# loop over all subj to populate the script,
#  including a line to log terminal output
foreach subj ( ${all_subj} )
    foreach ses ( ${all_ses} )
        echo "tcsh DO_SCRIPT.tcsh ${subj} ${ses} |& tee log.ap_${subj}_${ses}.txt" >> ${my_script}
    end
end

However, if the number/names of sessions is variable, then you might need to check on that explicitly to build an $all_ses list for each subject. And example of doing so is included in this run*tcsh script, from our processing contribution to the FMRI Open QC Project. Basically, we rely on each session ID being the name of a directory within the session-level directory.

--pt

Awesome, it's working!! Got it now.
I just need to include multiple -dsets_me_run options in the afni_proc.py command, like the below. That was the bit I found confusing, so thanks a bunch for pointing me in the right direction!

set dsets_epi_me_1  = ( ${sdir_epi}/${subj}_${ses}_*run-06_echo-*_bold.nii* )
set dsets_epi_me_2  = ( ${sdir_epi}/${subj}_${ses}_*run-07_echo-*_bold.nii* )
...
afni_proc.py                                                            \
     -subj_id                  ${subj}                                  \
     -blocks despike tshift align tlrc volreg mask combine blur scale   \
         regress                                                        \
...
     -dsets_me_run             ${dsets_epi_me_1}                          \
     -dsets_me_run             ${dsets_epi_me_2}                          \
...

Regarding the separate note, yes, each subject has two sessions. I'll give it a go following your advice and might come back with some follow-up questions later on. Thanks a ton for your help!

Byeol

1 Like

Yes, I wasn't sure how to write that up. Multiple -dsets_me_run options sounds great, that is until you have 3 echoes and 20 runs...

To exemplify, given 10 runs of 3 echoes, one can use:

a) 10 -dsets_me_run options, each with the 3 echo datasets for that run
or
b) 3 -dsets_me_echo options, each with the 10 runs for that echo

Either way one would specify all 30 datasets. But which seems more desirable might depend on the number of echoes vs runs. Soooo... you get to choose. :)

  • rick