@@ -18,19 +18,31 @@ censor_rel="" # 3dTcat: truncate input
1818MAXVOLSTOTAL=-1 # " truncate how many volumes are ultimately averaged.
1919IDX_SAMPLE_METHOD=" first" # how to sample indexes (first, last, random) when MAXVOLS*
2020tnorm_opt=" -nzmean" # 3dTstat: final output (time normalized)
21+ FD_THRES=${FD_THRES:- 0.3} # framewise displacement applied to confounds_timeseries.tsv
22+
23+ # GNU Parallel default options for per func/ files (added for -fmriprep, 20260102)
24+ PARALEL_OPTS=${PARALEL_OPTS:- " --eta --progress --total-jobs -P50%" }
2125
2226OUTPUT=tat2star.nii.gz
2327TMPLOCATION=/tmp
2428CLEAN=1
2529TMPD=" " # will be folder specific to single invocation of tat2. removed at the end
2630WRITE_JSON_LOG=1
2731
32+ # run through fmriprep. -fmriprep option added 20260102
33+ FMRIPREP_DERIV_ROOT=" "
34+ FMRIPREP_TASK_PATTERN=${FMRIPREP_TASK_PATTERN:- " *_task-*desc-preproc_bold.nii.gz" }
35+
2836# all the inputs (possibly globs) we want to work on
2937declare -a GLOBFILES
3038
3139filedesc_whichvol=" "
3240
33- TAT2_VER=" 20240719-calc_ln+BUGFIX-no_vol"
41+ TAT2_VER=" 1.0.0.20260102-fmriprep"
42+ # 1.0.0.20260102
43+ # add -fmriprep option
44+ # 20240719-calc_ln+BUGFIX-no_vol
45+ # novolume was 'x*1', fixed to be '(x/m)*m'
3446# 20201116
3547# back to mean default
3648# add
@@ -67,6 +79,20 @@ SYNOPSIS
6779 final '$OUTPUT ' will be in the cwd
6880
6981OPTIONS (in order of relevance)
82+ -fmriprep /path/to/fmriprep/
83+ Specify path to fmriprep root. Will run for all func directories within provided path
84+
85+ Will overwrite -censor_rel and -mask_rel options to new defaults.
86+
87+ To change new defaults, specify modifications after -fmriprep option
88+ NB. without -fmriprep, tat2 creates a single file from all inputs
89+ with -fmriprep, tat2start.nii.gz file PER func/ directory
90+
91+ GNU Parallel will be used when avalaible to parallelize.
92+ Parameters set with environment variable
93+ export PARALEL_OPTS='$PARALEL_OPTS '
94+
95+
7096 -censor_rel FILE
7197 specify the motion censor file. single column no header file. nLines = nVols. 0=exclude, 1=keep.
7298 either a filename or regexp
@@ -197,7 +223,8 @@ collapse_seq_idx(){
197223
198224parse_args (){
199225 # keep args around for 3dNotes history
200- allargs=" $* "
226+ allargs=" $* " # single long string, into json/log
227+ ALLARGS_=(" $@ " ) # used to forward tat2 when -fmriprep
201228
202229
203230 # read in any arguments/paramaters
@@ -223,6 +250,15 @@ parse_args(){
223250 -inverse) t2_inv=1; shift ;;
224251 -no_voxscale)vox_scale=0; shift;;
225252 -noclean) CLEAN=0; shift ;;
253+ -fmriprep)
254+ FMRIPREP_DERIV_ROOT=" ${2:? expect fmriprep deriviate directory} " ; shift 2
255+ [[ " $MASK_REL " != " subject_mask.nii.gz" ]] &&
256+ echo " WARNING: -mask_rel '$MASK_REL ' defined before -fmriprep, overwriting" >&2
257+ MASK_REL=' s/_desc-preproc_bold/_desc-brain_mask/'
258+ [ -n " $censor_rel " ] &&
259+ echo " WARNING: -censor_rel '$censor_rel ' defined before -fmriprep, overwriting" >&2
260+ censor_rel=' s/(task-[^_]+(_run-[^_]+)?)_.*/\1_desc-confounds_timeseries.tsv/'
261+ ;;
226262 -verbose) set -x; shift ;;
227263 -h* ) usage;;
228264 -* ) echo " unknown option '$1 '" ; usage;;
@@ -251,30 +287,65 @@ args_are_sane(){
251287 err " must use -no_voxscale with -calc_zscore or -calc_ln"
252288 fi
253289
254- echo " #files/globs: ${# GLOBFILES[@]} "
255- # need to have at least one file to average
256- [ -z " ${GLOBFILES[*]} " ] && usage
290+ # only check globs/inputs if not running as fmriprep
291+ if [ -z " $FMRIPREP_DERIV_ROOT " ]; then
292+ echo " #files/globs: ${# GLOBFILES[@]} "
293+ # need to have at least one file to average
294+ [ -z " ${GLOBFILES[*]} " ] && usage
295+
296+ # how many files do we have
297+ nfiles=$( find -L ${GLOBFILES[@]} -maxdepth 0 | wc -l)
298+ [ $nfiles -eq 0 ] && err " no files match input GLOBFILESs: ${GLOBFILES[@]} "
299+ [ $nfiles -eq 1 ] && echo " WARNING: only one file matches '${GLOBFILES[@]} '. expected all (>1) runs"
300+ [ $nfiles -gt 10 ] && echo " WARNING: running on $nfiles epi files! Are you sure you don't want to run one visit at a time?"
301+
302+ [ -r " $OUTPUT " ] && echo " # have $OUTPUT ; rm $OUTPUT # to redo" && exit 0
303+ fi
304+
257305
258- # how many files do we have
259- nfiles=$( find -L ${GLOBFILES[@]} -maxdepth 0 | wc -l)
260- [ $nfiles -eq 0 ] && err " no files match input GLOBFILESs: ${GLOBFILES[@]} "
261- [ $nfiles -eq 1 ] && echo " WARNING: only one file matches '${GLOBFILES[@]} '. expected all (>1) runs"
262- [ $nfiles -gt 10 ] && echo " WARNING: running on $nfiles epi files! Are you sure you don't want to run one visit at a time?"
263306
264- [ -r " $OUTPUT " ] && echo " # have $OUTPUT ; rm $OUTPUT # to redo" && exit 0
265307 [ " $MAXVOLS " == " 1" ] && echo " -maxvols cannot be 1. 3dcalc doesn't like applying a single value 1D file to a 4D dataset" && exit 1
266308 return 0
267309}
268310
311+ fmriprep_to_fd_censor (){
312+ local tsvfile=${1:? expect framewise displacement file}
313+ ! test -r $tsvfile &&
314+ echo " ERROR: could not find expected file '$tsvfile '" >&2 &&
315+ return 1
316+
317+ ! perl -slane ' BEGIN{
318+ $i=-1;
319+ @c=split/\t/,<>;
320+ foreach (@c){
321+ ++$i;
322+ last if /framewise_displacement/;
323+ exit 100 if $i >= $#c;
324+ }}
325+ print $F[$i]>$FD_THRES?0:1;' -- -FD_THRES=" $FD_THRES " < $tsvfile &&
326+ echo " ERROR: no 'framewise_displacement' in $tsvfile " >&2 &&
327+ return 2
328+ return 0
329+ }
330+
269331update_with_censor (){
270332 declare -g input filedesc_whichvol censor_file
333+ local tmpd=${1:- $TMPLOCATION } # only used if fmrirep/rewriting
271334 filedesc_whichvol=" "
272335 readonly censor_rel
273336 local idxs nkeep tat2inputfile
274337 # update 'input' and 'runoutput'
275338 # censor input
276339 if [ -n " $censor_rel " ]; then
277340 censor_file=$( find_rel_file " $input " " $censor_rel " )
341+ # SPECIAL CASE (20260102): fmriprep input is not
342+ if [[ $censor_file =~ desc-confounds_timeseries.tsv$ ]]; then
343+ new_censor=" $tmpd /$( basename $censor_file _timeseries.tsv) _fd-$FD_THRES .1D"
344+ msg " apply fd<$FD_THRES from '$censor_file ' to '$new_censor '"
345+ test -r " $new_censor " && echo " WARNING: overwriting an existing censor file $new_censor " >&2
346+ fmriprep_to_fd_censor " $censor_file " > $new_censor
347+ censor_file=$new_censor
348+ fi
278349 # if MAXVOLS is default "-1", firstn_cvs does nothing
279350 idxs=$( where1csv $censor_file | idx_shuffle $IDX_SAMPLE_METHOD | firstn_csv $MAXVOLS )
280351 nkeep=$( echo $idxs | tr ' ,' ' \n' | wc -l)
@@ -364,7 +435,7 @@ one_ta(){
364435
365436 # change $input and $filedesc_whichvol if $cesnor_rel exist
366437 censor_file=" " # updated version of '$censor_rel' if used
367- update_with_censor
438+ update_with_censor " $tmpd "
368439
369440 # must match *_tat2.nii.gz to be picked up by 3dTstat at the end
370441 runoutput=" $tmpd /${cnt}${filedesc_whichvol}${filedesc_volnorm} _tat2.nii.gz"
@@ -431,6 +502,11 @@ json_list(){
431502}
432503
433504_tat2 (){
505+ # would see this message on arg check unless running w/-fmriprep
506+ test -r " $OUTPUT " &&
507+ msg " # have $OUTPUT . rm '$OUTPUT ' # to redo" &&
508+ return 0
509+
434510 TMPD=$( mktemp -d $TMPLOCATION /tat2star_XXXX)
435511 cnt=0
436512 volcount=0
@@ -528,7 +604,7 @@ _tat2(){
528604 [ " ${WRITE_JSON_LOG:- 1} " -eq 1 ] &&
529605 cat << - HEREDOC > "${OUTPUT/ .nii.gz/ .log.json} "
530606 {
531- "cmd": "$0 $allargs ",
607+ "cmd": "$0 $( sed ' s:\\:\\\\:g ' <<< " $ allargs" ) ",
532608 "roistats_cmds": "${all_roistats} ",
533609 "volume_norm_cmds": "${all_calc_cmd} ",
534610 "collapse_cmd": "${Tstat_cmd} ",
@@ -561,11 +637,63 @@ _tat2(){
561637 return 0
562638}
563639
640+ _fmriprep_loop (){
641+ # run all of fmripre func/ files
642+ # 'deriv/sub-x/ses-y/func' or 'deriv/sub-x/func/'
643+ mapfile -t bold_dirs < <( find " $FMRIPREP_DERIV_ROOT " -name ' func' -type d)
644+ msg " found ${# bold_dirs[@]} 'func' folders (fmriprep sessions)"
645+ [ ${# bold_dirs[@]} -eq 0 ] &&
646+ err " No $FMRIPREP_DERIV_ROOT /**/func folders to work with!"
647+
648+ # ALLARGS_ includes two we want to remove: -fmriprep /path/to/deriv
649+ noprepargs=(); i=0
650+ while [ $i -lt ${# ALLARGS_[@]} ]; do
651+ arg=${ALLARGS_[$i]}
652+ [[ $arg == -f mriprep ]] && let i=i+2 && continue
653+ noprepargs+=(" $arg " )
654+ let ++i
655+ done
656+
657+ if command -v env_parallel > /dev/null && [ -n " $PARALEL_OPTS " ]; then
658+ export FMRIPREP_TASK_PATTERN FMRIPREP_DERIV_ROOT
659+ export -f _tat2_fmriprep
660+ parallel $PARALEL_OPTS -N1 _tat2_fmriprep {1} -censor_rel " '$censor_rel '" -mask_rel " '$MASK_REL '" " ${noprepargs[@]} " ::: " ${bold_dirs[@]} "
661+ else
662+ echo " WARNING: not parallelizing. install GNU Parallel to use mutliple cores." >&2
663+ for session_func in " ${bold_dirs[@]} " ; do
664+ msg " starting $session_func "
665+ # continue across failures -- don't want one bad session to bring it all down
666+ _tat2_fmriprep " $session_func " " ${noprepargs[@]} " || :
667+ done
668+ fi
669+ }
670+ _tat2_fmriprep (){
671+ # expect to be called by _fmriprep_loop after parse_args sets all bug GLOBFILES
672+ local session_func=${1:? expect fmriprep func/ directory} ; shift ;
673+ mapfile -t GLOBFILES < <( find $session_func -iname " $FMRIPREP_TASK_PATTERN " -not -iname ' *echo-*' )
674+ [ ${# GLOBFILES[@]} -eq 0 ] &&
675+ echo " WARNING: no non-echo task files in $session_func " >&2 &&
676+ return 1
677+
678+ ! [[ ${GLOBFILES[0]} =~ sub-[^_/]+ (_ses-[^_/]+)? _ ]] &&
679+ echo " ERROR: no 'sub-*' in first input of $session_func : '${GLOBFILES[0]} '" &&
680+ return 2
681+
682+ # TODO: option to save outside of fmrirep func dir
683+ tat2 -output " $session_func /${BASH_REMATCH} desc-preproc_tat2star.nii.gz" " $@ " ${GLOBFILES[@]}
684+ }
685+
564686# run if not sourced
565687if ! [[ " $( caller) " != " 0 " * ]]; then
566688 set -eou pipefail
567689 # 20210218WF - hygenic file usage -- tmp files after crash clog fileystem
568690 trap ' [ -n "$TMPD" -a -d "$TMPD" -a $CLEAN -eq 1 ] && [[ "$TMPD" =~ $TMPLOCATION ]] && rm -r "$TMPD"' EXIT
569691 args_are_sane " $@ "
570- _tat2
692+
693+ # running on many at once!
694+ if [ -n " $FMRIPREP_DERIV_ROOT " ]; then
695+ _fmriprep_loop
696+ else
697+ _tat2
698+ fi
571699fi
0 commit comments