Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 97 additions & 61 deletions preproc_functions/nuisance_regression
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ motionderiv() {
COMPUTE_NUISANCE_REGRESSORS_GLOBALS=(nuisance_file nuisance_regressors no_warp \
postWarp postDespike postSS \
templateName mprageBet_base ext tr prefix bpLow bpHigh \
gsr_in_prefix)
gsr_in_prefix custom_regprefix)

# give
# - a nifti get basis functions to remove autocorrelation
Expand Down Expand Up @@ -122,7 +122,7 @@ function run_3dremlfit(){

function compute_nuisance_regressors {
# 20170421WF: skipping might be a problem if we change what regressors we want
if [ -r .nuisance_compute_complete ]; then
if [ -r .nuisance_compute_complete${custom_regprefix:-} ]; then
rel "already computed nuisance regression" c
return 0
fi
Expand Down Expand Up @@ -522,13 +522,58 @@ function compute_nuisance_regressors {
fi

# TODO: do multiple passes to build nuisancecols so we can check that would should be redoing?
rel "echo '${nuisancecols[*]}' > .regressors_in_use"
rel "echo '${nuisancecols[*]}' > .regressors_in_use${custom_regprefix:-}"

rel "date > .nuisance_compute_complete"
rel "date > .nuisance_compute_complete${custom_regprefix:-}"

return 0
}

# look into regression options and build todo string
# A b g (r || custom_prefix)
function regression_todo {
local todo=""
[ $rmautocorr -eq 1 ] && todo="A"
[ $bandpass_filter -eq 1 ] && todo="${todo}b"
[ $gsr_in_prefix -eq 1 ] && todo="${todo}g"
[ $nuisance_regression -eq 1 ] && todo="${todo}${custom_regprefix:-r}"
echo "$todo"
}
function reg_already_done {
local todo="${1:?$FUNC_NAME needs regression operations by prefix, eg Abr}"
local reg_prefix=${custom_regprefix:-r}
local complete_file=.nuisance_regression${custom_regprefix:-}_complete # default .nuisance_regression_complete

[[ -f $complete_file && $todo=$reg_prefix ]] && return 0
[[ -f .bandpass_filter_complete && $todo=b ]] && return 0
[[ -f .bandpass_filter_complete && .remove_autocorr_complete && $todo=Ab ]] && return 0
[[ -f $complete_file && -f .bandpass_filter_complete && $todo=b$reg_prefix ]] && return 0
[[ -f $complete_file && -f .remove_autocorr_complete && $todo=A$reg_prefix ]] && return 0
[[ -f $complete_file && -f .bandpass_filter_complete && -f .remove_autocorr_complete && $todo=Ab$reg_prefix ]] && return 0

# not already done
return 1
}
function reg_todo_to_desc {
[[ $# -eq 0 || -z $1 ]] && echo "none" && return

local todo="${1:?$FUNC_NAME first arg is regression operations by prefix/todo. eg. Abgr }"
#bandpass + prewhitening only
if [ "$todo" = "Ab" ]; then
echo "bandpass+prewhite"
#bandpass only
elif [[ "$todo" = "b" ]]; then
echo "bandpass"
# regress only (withour or without 'A'utocorrelation rm'ed (w/reml)
elif [[ $todo =~ "A?${custom_regprefix:-r}" ]]; then
echo "regressonly"
elif [[ "$todo" =~ A?bg?${custom_regprefix:-r} ]]; then
echo "bandpass+regress"
else
echo "none"
fi
}

function nuisance_regression {

#handle nuisance regression and/or bandpass filtering
Expand All @@ -547,77 +592,68 @@ function nuisance_regression {

preNRBP="${prefix}${funcFile}${smoothing_suffix}${ext}"

local todo=
[ $rmautocorr -eq 1 ] && todo="${todo}A"
[ $bandpass_filter -eq 1 ] && todo="${todo}b"
[ $gsr_in_prefix -eq 1 ] && todo="${todo}g"
[ $nuisance_regression -eq 1 ] && todo="${todo}r"

todo=$(regression_todo)
# todo is
# only: A b r
# combined: Ab Abr br
# 'A' alone is not allowed, will throw error -- checked by parseargs or preprocessFunctional
# gsr_in_prefix (-gsr) must have gs in $nuisance_regressors, check by parseargs

#check whether requisite steps are complete
reg_already_done "$todo" && return 0

prefix="${todo}${prefix}"
postNRBP="${prefix}${funcFile}${smoothing_suffix}${ext}"

#check whether requisite steps are complete
[[ -f .nuisance_regression_complete && $todo=r ]] && return 0
[[ -f .bandpass_filter_complete && $todo=b ]] && return 0
[[ -f .bandpass_filter_complete && .remove_autocorr_complete && $todo=Ab ]] && return 0
[[ -f .nuisance_regression_complete && -f .bandpass_filter_complete && $todo=br ]] && return 0
[[ -f .nuisance_regression_complete && -f .remove_autocorr_complete && $todo=Ar ]] && return 0
[[ -f .nuisance_regression_complete && -f .bandpass_filter_complete && -f .remove_autocorr_complete && $todo=Abr ]] && return 0

if [[ ! -f "${nuisance_file}" && ( $nuisance_regression == 1 || $nuisance_compute == 1 ) ]]; then
compute_nuisance_regressors
fi

#bandpass + prewhitening only
if [ "$todo" = "Ab" ]; then
run_3dremlfit $preNRBP $postNRBP ${subjMask}${ext} #function should pick up this circumstance
rel "date > .bandpass_filter_complete"
#bandpass only
elif [[ "$todo" = "b" ]]; then
rel "3dBandpass -input \"$preNRBP\" -mask \"${subjMask}${ext}\" -dt $tr \
-prefix \"$postNRBP\" $bpLow $bpHigh"

rel "date > .bandpass_filter_complete"
# regress only
elif [[ "$todo" = "r" || "$todo" = "Ar" ]]; then
#default to 3dREMLfit here
if [ $arma_nuisance_regression -eq 0 ]; then
rel "3dDetrend -overwrite -verb -polort 2 -vector ${nuisance_file} \
-prefix \"$postNRBP\" \"$preNRBP\""
#need to mask detrend by brain mask as with 3dBandpass (3dDetrend doesn't support -mask)
rel "fslmaths \"$postNRBP\" -mas \"$subjMask\" \"$postNRBP\""
else
#use 3dREMLfit for regression (default)
run_3dremlfit $preNRBP $postNRBP ${subjMask}${ext}
fi

rel "date > .nuisance_regression_complete"
# bandpass and regress
elif [[ "$todo" =~ A?bg?r ]]; then
if [ $arma_nuisance_regression -eq 0 ]; then
rel "3dTproject -overwrite -input \"$preNRBP\" -mask \"${subjMask}${ext}\" -dt $tr \
-prefix \"$postNRBP\" -polort 2 -ort ${nuisance_file} -passband $bpLow $bpHigh"

#Deprecated -- 3dTproject performs true simultaneous approach, not filter all -> bandpass
#rel "3dBandpass -overwrite -input \"$preNRBP\" -mask \"${subjMask}${ext}\" -dt $tr \
# -prefix \"$postNRBP\" -ort ${nuisance_file} $bpLow $bpHigh"
else
#use 3dREMLfit for bandpass + regression (default)
run_3dremlfit $preNRBP $postNRBP ${subjMask}${ext}
fi

rel "date > .bandpass_filter_complete"
rel "date > .nuisance_regression_complete"
else
# we were only computing things
rel "(Regressors computed but no bandpass or nuisance regression applied)" c
fi
case $(reg_todo_to_desc "$todo") in
bandpass+prewhite)
run_3dremlfit $preNRBP $postNRBP ${subjMask}${ext} #function should pick up this circumstance
rel "date > .bandpass_filter_complete"
;;
bandpass)
rel "3dBandpass -input \"$preNRBP\" -mask \"${subjMask}${ext}\" -dt $tr \
-prefix \"$postNRBP\" $bpLow $bpHigh"
rel "date > .bandpass_filter_complete"
;;
regressonly)
if [ $arma_nuisance_regression -eq 0 ]; then
rel "3dDetrend -overwrite -verb -polort 2 -vector ${nuisance_file} \
-prefix \"$postNRBP\" \"$preNRBP\""
#need to mask detrend by brain mask as with 3dBandpass (3dDetrend doesn't support -mask)
rel "fslmaths \"$postNRBP\" -mas \"$subjMask\" \"$postNRBP\""
else
#use 3dREMLfit for regression (default)
run_3dremlfit $preNRBP $postNRBP ${subjMask}${ext}
fi
rel "date > .nuisance_regression${custom_regprefix:-}_complete"
;;
bandpass+regress)
if [ $arma_nuisance_regression -eq 0 ]; then
rel "3dTproject -overwrite -input \"$preNRBP\" -mask \"${subjMask}${ext}\" -dt $tr \
-prefix \"$postNRBP\" -polort 2 -ort ${nuisance_file} -passband $bpLow $bpHigh"

#Deprecated -- 3dTproject performs true simultaneous approach, not filter all -> bandpass
#rel "3dBandpass -overwrite -input \"$preNRBP\" -mask \"${subjMask}${ext}\" -dt $tr \
# -prefix \"$postNRBP\" -ort ${nuisance_file} $bpLow $bpHigh"
else
#use 3dREMLfit for bandpass + regression (default)
run_3dremlfit $preNRBP $postNRBP ${subjMask}${ext}
fi
rel "date > .bandpass_filter_complete"
rel "date > .nuisance_regression${custom_regprefix:-}_complete"
;;
none) rel "(Regressors computed but no bandpass or nuisance regression applied)" c ;;
*)
rel "ERROR: don't know what to do with $todo = '$(reg_todo_to_desc "$todo")'" c
return 1
;;
esac


#explicit return code needed to avoid implicit status of prior command
return 0
Expand Down
15 changes: 14 additions & 1 deletion preproc_functions/parse_args
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function parse_args {
cleanup_only=0
compute_warp=0 #used for -compute_warp_only. Specifies to compute warp to template without applying it to preprocessed data.
constrainToTemplate="y"
custom_regprefix="" # added 20250925 - used instead of 'r' for regression prefix
deoblique_all=0
despike=0
despikeThresh="2.5 4.0"
Expand Down Expand Up @@ -251,6 +252,9 @@ function parse_args {
sliceTimesFile="$2"
sliceMotion4D=1 #for now, this option is only relevant for sliceMotion4d correction
shift 2
elif [ $1 = -custom_regression_prefix ]; then
custom_regprefix="$2"
shift 2
elif [ $1 = -constrain_to_template ]; then
if [[ $2 = [NnYy] ]]; then
constrainToTemplate="${2}"
Expand Down Expand Up @@ -650,6 +654,15 @@ function parse_args {
exit 1
fi

if [[ "$nuisance_regression" -eq 0 && -n $custom_regprefix ]]; then
echo "Cannot request -custom_regression_prefix without also specifying -nuisance_regression"
exit 1
fi
if [[ -n $custom_regprefix && "$nuisance_file" == .nuisance_regressors ]]; then
echo "Cannot request -custom_regression_prefix without also changing default -nuisance_file"
exit 1
fi

# Not legal to use -rm_completefiles without warping
if [[ -n "$rmgroup_component_1d" && $no_warp -ge 1 ]]; then
echo "Cannot request -rmgroup_component and not preform a warp!"
Expand Down Expand Up @@ -693,7 +706,7 @@ function parse_args {
#if we have v6.0+, then incorporate fieldmap unwarping into BBR func2struct
#and use resulting unwarping information for EPI -> unwarp -> struct -> MNI transform.
#for older versions, need to use EPI-registered fieldmap for unwarping
flirtVersion=$( flirt | head -1 | perl -pe 's/^FLIRT version ([0-9\\.]+)$/\1/' )
command -v flirt && flirtVersion=$( flirt | head -1 | perl -pe 's/^FLIRT version ([0-9\\.]+)$/\1/' ) || flirtVersion=0
bbrCapable=$( echo "${flirtVersion} >= 6" | bc )

#The smoothing kernel parameter is expected to be the full width at half
Expand Down
7 changes: 6 additions & 1 deletion preprocessFunctional
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ Command line options:
-custom_slice_times: For use with 4d slice timing + motion correction.
A csv file containing the times (in seconds summing to TR) of each slice (bottom to top).
Example: -custom_slice_times "../SPECCMBSliceTimes.csv"
-custom_regression_prefix
Use a specified letter or letters instead of 'r' for regression. Also see -gsr
Approprate for tracking eg '-custom_regression_prefix M -nuisance_regression motion6 -nuisance_file motion_reg.txt' seperatly from default
-delete_dicom: if converting dicom to nifti, whether delete or archive DICOM files. If not
passed, user will be prompted for action. Options are:
-delete_dicom no. (leaves DICOM untouched)
Expand Down Expand Up @@ -204,7 +207,7 @@ The major steps in the processing pipeline are, in order:
10) ICA-AROMA (optional) - a
11) High-pass filtering - f
12) Intensity normalization - n
13) Nuisance regression (optional) - r
13) Nuisance regression (optional) - r (also see -custom_regression_prefix)
13.5) Global signal regression (optional, label optional) - g
14) Bandpass filtering (optional) - b
15) Auto Correlation removal (optional) - A
Expand Down Expand Up @@ -235,6 +238,8 @@ EndOfHelp
#
#Changelog:
#
# 09/25/2025 (WF)
# - add '-custom_regression_prefix' (refactor preproc_functions/nuisance_regression)
#04/30/2024 (WF)
# - added '-ignore_bids' option and handle slicetiming missing errors
#08/26/2022 (WF)
Expand Down
3 changes: 3 additions & 0 deletions test/ROI_TempCorr.R.bats
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,13 @@ teardown() {

## Semi
@test "semi cor" {

Rscript -e "installed.packages()['ppcor','Version']" || skip "R missing ppcor"
ROI_TempCorr.R -ts $shortrestfile -brainmask $mask -rois $roi -njobs 1 -pcorr_method semi:pearson
last_rowcol corr_rois_pearson_semipartial.txt "33 102"
}
@test "semi+partial+full cor" {
Rscript -e "installed.packages()['ppcor','Version']" || skip "R missing ppcor"
ROI_TempCorr.R -ts $shortrestfile -brainmask $mask -rois $roi -njobs 1 -pcorr_method semi:pearson,pearson -corr_method pearson
last_rowcol corr_rois_pearson.txt "33 33"
last_rowcol corr_rois_pearson_semipartial.txt "33 102"
Expand Down
13 changes: 13 additions & 0 deletions test/fmripp_parseargs.bats
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,21 @@ teardown() {
parse_args -4d fake.nii.gz -gsr -nuisance_regression dwm,gs,wm
[ $gsr_in_prefix -eq 1 ]
[ $nuisance_regressors = "dwm,gs,wm" ]

}

@test "custom reg" {
run parse_args -4d fake.nii.gz -custom_regression_prefix R
[ $status -eq 1 ]
[[ $output =~ Cannot.*without.*nuisance_reg ]]

run parse_args -4d fake.nii.gz -custom_regression_prefix R -nuisance_regression 6motion
[ $status -eq 1 ]
[[ $output =~ Cannot.*without.*nuisance_file ]]

parse_args -4d fake.nii.gz -custom_regression_prefix Mr -nuisance_regression 6motion -nuisance_file reg_motion.txt
[[ $custom_regprefix = "Mr" ]]
}

@test "-rmgroup_component" {
parse_args -4d fake.nii.gz -rmgroup_component test.1d -tr 1
Expand Down
Loading