Skip to content

Commit

Permalink
✅ add-intended-for: change args, allow multiple -for, add tests
Browse files Browse the repository at this point in the history
 * -me_okay is temporary kludge. need to read up on what fmriprep
   wants from these. only in first echo?
 * testing two functions: csv_niifiles and find_se_file
 * switched from '-bold' to '-for' (-bold and -dwi are both aliases of -for)
 * allow multiple -for patterns
  • Loading branch information
WillForan committed Dec 22, 2023
1 parent b7ab68d commit 6998e4d
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 22 deletions.
72 changes: 50 additions & 22 deletions add-intended-for
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,58 @@
#
# 20221020WF - init. lncd "habit" project
# 20231110WF - into lncdtools
# 20231221WF - takes multiple patterns
#
# add fieldmap annotations to task where fmriprep can find them

usage(){
cat <<HEREDOC
'add-intended-for' adds an element like
'IntendedFor': ['sub-1/ses-1/func/sub-1_ses-1_task-rest_bold.nii.gz']
to the json files matching \`-fmap 'pattern.json'\`
IntendedFor is used by fmriprep's susceptibility distortion correction (SDC).
USAGE:
$(basename "$0") -fmap '*PA_run-1_epi.json' subj-1/ses-1/ [subj-2/ses-1/ ...]
$(basename "$0") -fmap '*PA_run-1_epi.json' [-for '*_bold.nii.gz'] [-for '*dwi.nii.gz'] subj-1/ses-1/ [subj-2/ses-1/ ...]
OPTIONS:
-fmap pattern to find fmap json file. where to insert IntendedFor
-bold pattern to find bold file. default '*_bold.nii.gz' (can be *dwi.nii.gz)
sesdir any number of session dirs (dir that contains fmap/ and func/ or dwi/)
-help this message
-fmap pattern to find fmap json file. where to insert IntendedFor
-for pattern to find bold or dwi file(s).
default is '*_bold.nii.gz'. An alternative: '*dwi.nii.gz'
Can repeat "-for 'pattern.nii.gz'"
to put multiple patterns into the 'IntendedFor' array
-me_okay include multiecho files in search.
default is to exclude all files matching '*echo-*'
sesdir any number of session dirs
each should be session root dir with fmap/, func/ +/- dwi/
repeat for as many sessions as needed. Consider using a glob
NOTE:
* skips files where 'IntendedFor' already exists
* test with 'DRYRUN=1 add-intended-for ...'
HEREDOC
}

csv_bolds(){
#shellcheck disable=SC2089 # intentionally not an array
FIND_EXCLUDE=" -not -name *echo-*"

csv_niifiles(){
local sesdir="$1"; shift
local boldpat="${1}"
#local boldpat="${1:?need at least one bold or dwi file pattern}"
local find_names
# build a list of acceptable names
find_names=$(printf " -iname %s -or" "$@"|sed 's/-or$//')
# shellcheck disable=SC2086,SC2090 # sending multiple args in ea. var
(cd "$sesdir" &&
find func/ dwi/\
-name "$boldpat" \
-not -name '*echo-*'|
sed 's/^\|$/"/g'|
paste -sd,)
find func/ dwi/ \( $find_names \) $FIND_EXCLUDE ) |
sed 's/^\|$/"/g'|
paste -sd,
}
find_se_file(){
local sesdir="${1:?find_se_file requires session dir}"
Expand All @@ -42,31 +68,32 @@ find_se_file(){
}

add_intended_for(){
local sefile boldfile str
local sefile forfilescsv str
sefile="$1"; shift
boldfile="$1"; shift
forfilescsv="$1"; shift
# (DANGER) inline replace on
# matching files without an 'IntendedFor' line
str="\"IntendedFor\": [$boldfile],"
str="\"IntendedFor\": [$forfilescsv],"
grep -L IntendedFor "$sefile" |
xargs -r dryrun sed "s;{;{\n$str;" -i || :
return 0
}

_intendedFor() {
warn "# WARNING: $0 is untested"
local boldpatt='*_bold.nii.gz'
local boldpatt=()
local sepatt=""
sesdirs=()
local sesdirs=()
[ $# -eq 0 ] && usage && exit 1
while [ $# -gt 0 ]; do
case "$1" in
-bold) boldpatt="$2"; shift 2;;
-for|-bold|-dwi) boldpatt=("${boldpatt[@]}" "$2"); shift 2;;
-fmap) sepatt="$2"; shift 2;;
-me_okay) FIND_EXCLUDE=""; shift 1;;
-help) usage; exit 0;;
*) sesdirs+=("$1"); shift 1;;
esac
done
[ -z "${boldpatt[*]}" ] && boldpatt=('*_bold.nii.gz')

# check inputs
[ -z "$sepatt" ] &&
Expand All @@ -79,9 +106,10 @@ _intendedFor() {
for sesdir in "${sesdirs[@]}"; do
mapfile -t sefiles < <(find_se_file "$sesdir" "$sepatt")
for sefile in "${sefiles[@]}"; do
boldfile=$(csv_bolds "$sesdir" "$boldpatt")
[ -z "$boldpatt" ] && warn "no bold in $sesdir/func matching $boldpatt" && return 1
add_intended_for "$sefile" "$boldfile" || :
forfilescsv=$(csv_niifiles "$sesdir" "${boldpatt[@]}")
[ -z "$forfilescsv" ] &&
warn "no matching files in $sesdir/{func,dwi}/ matching ${boldpatt[*]}" && return 1
add_intended_for "$sefile" "$forfilescsv" || :
done
done
return 0
Expand Down
51 changes: 51 additions & 0 deletions t/add-intended-for.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
setup(){
SUBJ1=$BATS_TEST_TMPDIR/sub-1
mkdir -p $SUBJ1/{fmap,func,dwi}/

echo -e '{\n}' > $SUBJ1/fmap/sub-1_epi.json
echo -e '{\n}' > $SUBJ1/fmap/sub-1_decoy__epi.json

touch $SUBJ1/func/sub-1_task-rest_bold.nii.gz
touch $SUBJ1/dwi/sub-1_dwi.nii.gz

touch $SUBJ1/func/sub-1_task-me-echo-1_bold.nii.gz

touch $SUBJ1/func/sub-1_task-decoy_bold.nii.gz
touch $SUBJ1/dwi/sub-1_acq-decoy_dwi.nii.gz

source add-intended-for
}

AIF_csv_niifiles-1() { #@test
run csv_niifiles $SUBJ1/ "*_task-rest*nii.gz"
[[ $status -eq 0 ]]
[[ $output == '"func/sub-1_task-rest_bold.nii.gz"' ]]

run csv_niifiles $SUBJ1/ "*[0-9]_dwi.nii.gz"
[[ $output == '"dwi/sub-1_dwi.nii.gz"' ]]
}
AIF_csv_niifiles-2() { #@test
run csv_niifiles $SUBJ1/ "*_task-rest*nii.gz" "*1_dwi.nii.gz"
[[ $output =~ '"func/sub-1_task-rest_bold.nii.gz","dwi/sub-1_dwi.nii.gz"' ]]
}


AIF_find_se_file() { #@test
run find_se_file $BATS_TEST_TMPDIR/sub-1/ '*1_epi.json'
[[ "$output" =~ sub-1_epi.json ]]
! [[ "$output" =~ decoy ]]
}

add-intended-for-full() { #@test
run ./add-intended-for \
-fmap '*1_epi.json' \
-for '*[0-9]_dwi.nii.gz' \
-for '*task-rest_bold.nii.gz' \
$BATS_TEST_TMPDIR/sub-1/

[ $status -eq 0 ]

run grep -R IntendedFor $BATS_TEST_TMPDIR/
[ $status -eq 0 ]
[[ $output =~ '"func/sub-1_task-rest_bold.nii.gz","dwi/sub-1_dwi.nii.gz"' ]]
}

0 comments on commit 6998e4d

Please sign in to comment.