/ add-intended-for
add-intended-for
  1  #!/usr/bin/env bash
  2  #
  3  # 20221020WF - init. lncd "habit" project
  4  # 20231110WF - into lncdtools
  5  # 20231221WF - takes multiple patterns
  6  #
  7  
  8  usage(){
  9     cat <<HEREDOC
 10  'add-intended-for' adds an element like
 11    'IntendedFor': ['sub-1/ses-1/func/sub-1_ses-1_task-rest_bold.nii.gz']
 12   to the json files matching \`-fmap 'pattern.json'\`
 13  
 14   IntendedFor is used by fmriprep's susceptibility distortion correction (SDC).
 15  
 16  USAGE:
 17    $(basename "$0") -fmap '*PA_run-1_epi.json' [-for '*_bold.nii.gz'] [-for '*dwi.nii.gz']  subj-1/ses-1/ [subj-2/ses-1/ ...]
 18  
 19  OPTIONS:
 20    -help    this message
 21  
 22    -fmap    pattern to find fmap json file. where to insert IntendedFor
 23  
 24    -for     pattern to find bold or dwi file(s).
 25             default is '*_bold.nii.gz'. An alternative: '*dwi.nii.gz'
 26             Can repeat "-for 'pattern.nii.gz'" 
 27             to put multiple patterns into the 'IntendedFor' array
 28  
 29    -me_okay include multiecho files in search.
 30             default is to exclude all files matching '*echo-*'
 31  
 32    sesdir   any number of session dirs 
 33             each should be session root dir with fmap/, func/ +/- dwi/
 34             repeat for as many sessions as needed. Consider using a glob
 35  
 36  NOTE:
 37    * skips files where 'IntendedFor' already exists
 38    * test with 'DRYRUN=1 add-intended-for ...'
 39  
 40  HEREDOC
 41  }
 42  
 43  #shellcheck disable=SC2089 # intentionally not an array
 44  FIND_EXCLUDE=" -not -name *echo-*"
 45  
 46  csv_niifiles(){
 47     local sesdir="$1"; shift
 48     #local boldpat="${1:?need at least one bold or dwi file pattern}"
 49     local find_names
 50     # build a list of acceptable names
 51     find_names=$(printf " -iname %s -or" "$@"|sed 's/-or$//')
 52     # shellcheck disable=SC2086,SC2090 # sending multiple args in ea. var
 53     (cd "$sesdir" &&
 54      find func/ dwi/ \( $find_names \) $FIND_EXCLUDE ) |
 55      sed 's/^\|$/"/g'|
 56      paste -sd,
 57  }
 58  find_se_file(){
 59     local sesdir="${1:?find_se_file requires session dir}"
 60     local pattern="${2:?find_se_file requires a json pattern}"
 61     mapfile -t sefiles < <(find "$sesdir/fmap" -name "$pattern") 
 62     if [[ -z "${sefiles[*]}" || ! -r "${sefiles[0]}" ]]; then
 63        warn "no like '$sesdir/fmap/$pattern'?" 
 64        exit 1
 65     fi
 66     [ ${#sefiles[@]} -gt 1 ] && warn ">1 match for '$pattern' found: ${sefiles[*]}"
 67     printf "%s\n" "${sefiles[@]}"
 68  }
 69  
 70  add_intended_for(){
 71     local sefile forfilescsv str
 72     sefile="$1"; shift
 73     forfilescsv="$1"; shift
 74     # (DANGER) inline replace on
 75     # matching files without an 'IntendedFor' line
 76     str="\"IntendedFor\": [$forfilescsv],"
 77     grep -L IntendedFor "$sefile" |
 78        xargs -r dryrun sed "s;{;{\n$str;" -i || :
 79     return 0
 80  }
 81  
 82  _intendedFor() {
 83    local boldpatt=()
 84    local sepatt=""
 85    local sesdirs=()
 86    [ $# -eq 0 ] && usage && exit 1
 87    while [ $# -gt 0 ]; do
 88       case "$1" in
 89          -for|-bold|-dwi) boldpatt=("${boldpatt[@]}" "$2"); shift 2;;
 90          -fmap) sepatt="$2"; shift 2;;
 91          -me_okay) FIND_EXCLUDE=""; shift 1;;
 92          -help) usage; exit 0;;
 93          *) sesdirs+=("$1"); shift 1;;
 94       esac
 95    done
 96    [ -z "${boldpatt[*]}" ] && boldpatt=('*_bold.nii.gz')
 97  
 98    # check inputs
 99    [ -z "$sepatt" ] &&
100       echo "ERROR: no -fmap pattern; see -help" &&
101       exit 1
102    ! [[ "$sepatt" =~ .json$ ]] &&
103       echo "-fmap pattern must end with .json" &&
104       exit 2
105  
106    for sesdir in "${sesdirs[@]}"; do
107       mapfile -t sefiles < <(find_se_file "$sesdir" "$sepatt")
108       for sefile in "${sefiles[@]}"; do
109          forfilescsv=$(csv_niifiles "$sesdir" "${boldpatt[@]}")
110          [ -z "$forfilescsv" ] &&
111             warn "no matching files in $sesdir/{func,dwi}/ matching ${boldpatt[*]}" && return 1
112          add_intended_for "$sefile" "$forfilescsv" || :
113       done
114    done
115    return 0
116  }
117  
118  # if not sourced (testing), run as command
119  eval "$(iffmain "_intendedFor")"