diff --git a/.gitignore b/.gitignore index 9f0b75f..b0dd0dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ output/ logs/ .qbatch/ +.vscode +*.csv diff --git a/.gitmodules b/.gitmodules index 8a76792..8fcfdac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "antsRegistration_affine_SyN"] path = antsRegistration_affine_SyN url = https://github.com/CoBrALab/antsRegistration_affine_SyN.git +[submodule "libBIDS.sh"] + path = libBIDS.sh + url = https://github.com/CoBrALab/libBIDS.sh.git diff --git a/helpers.sh b/helpers.sh index c7d8a1c..d10b861 100644 --- a/helpers.sh +++ b/helpers.sh @@ -9,7 +9,7 @@ __datetime=$(date -u +%F_%H-%M-%S-UTC) ### BASH HELPER FUNCTIONS ### # Stolen from https://github.com/kvz/bash3boilerplate" -if [[ ${_arg_dry_run} == "on" || ${_arg_debug} == "on" ]]; then +if [[ ${_arg_dry_run:-off} == "on" || ${_arg_debug:-off} == "on" ]]; then LOG_LEVEL=7 else LOG_LEVEL=6 diff --git a/libBIDS.sh b/libBIDS.sh new file mode 160000 index 0000000..4683940 --- /dev/null +++ b/libBIDS.sh @@ -0,0 +1 @@ +Subproject commit 468394004f4c8823871f4c8379e4538eda8edd70 diff --git a/modelbuild_input_from_bids.sh b/modelbuild_input_from_bids.sh new file mode 100755 index 0000000..6792326 --- /dev/null +++ b/modelbuild_input_from_bids.sh @@ -0,0 +1,181 @@ +#!/bin/bash +# +# ARG_OPTIONAL_SINGLE([suffix],[],[BIDS file suffix to construct inputs with],[T1w]) +# ARG_OPTIONAL_SINGLE([derivatives],[],[Derivatives sub-path to use (for preprocessed data)],[NA]) + +# ARG_OPTIONAL_REPEATED([column-filter],[],[Use to filter other BIDS entity types]) + +# ARG_OPTIONAL_SINGLE([masks],[],[Generate brain masks file],[]) + +# ARG_POSITIONAL_SINGLE([input_bids],[Path to BIDS dataset top-level directory],[]) +# ARG_POSITIONAL_SINGLE([output],[CSV filename to save],[]) +# ARG_HELP([Helper script to parse a BIDS-formatted dataset and generate an input.csv for modelbuild.sh]) +# ARGBASH_SET_INDENT([ ]) +# ARGBASH_GO() +# needed because of Argbash --> m4_ignore([ +### START OF CODE GENERATED BY Argbash v2.11.0 one line above ### +# Argbash is a bash code generator used to get arguments parsing right. +# Argbash is FREE SOFTWARE, see https://argbash.dev for more info + +die() { + local _ret="${2:-1}" + test "${_PRINT_HELP:-no}" = yes && print_help >&2 + echo "$1" >&2 + exit "${_ret}" +} + +begins_with_short_option() { + local first_option all_short_options='h' + first_option="${1:0:1}" + test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0 +} + +# THE DEFAULTS INITIALIZATION - POSITIONALS +_positionals=() +# THE DEFAULTS INITIALIZATION - OPTIONALS +_arg_suffix="T1w" +_arg_derivatives="NA" +_arg_column_filter=() +_arg_masks= + +print_help() { + printf '%s\n' "Helper script to parse a BIDS-formatted dataset and generate an input.csv for modelbuild.sh" + printf 'Usage: %s [--suffix ] [--derivatives ] [--column-filter ] [--masks ] [-h|--help] \n' "$0" + printf '\t%s\n' ": Path to BIDS dataset top-level directory" + printf '\t%s\n' ": CSV filename to save" + printf '\t%s\n' "--suffix: BIDS file suffix to construct inputs with (default: 'T1w')" + printf '\t%s\n' "--derivatives: Derivatives sub-path to use (for preprocessed data) (default: 'NA')" + printf '\t%s\n' "--column-filter: Use to filter other BIDS entity types (empty by default)" + printf '\t%s\n' "--masks: Generate brain masks file (no default)" + printf '\t%s\n' "-h, --help: Prints help" +} + +parse_commandline() { + _positionals_count=0 + local _key + while test $# -gt 0; do + _key="$1" + case "$_key" in + --suffix) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_suffix="$2" + shift + ;; + --suffix=*) + _arg_suffix="${_key##--suffix=}" + ;; + --derivatives) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_derivatives="$2" + shift + ;; + --derivatives=*) + _arg_derivatives="${_key##--derivatives=}" + ;; + --column-filter) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_column_filter+=("$2") + shift + ;; + --column-filter=*) + _arg_column_filter+=("${_key##--column-filter=}") + ;; + --masks) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_masks="$2" + shift + ;; + --masks=*) + _arg_masks="${_key##--masks=}" + ;; + -h | --help) + print_help + exit 0 + ;; + -h*) + print_help + exit 0 + ;; + *) + _last_positional="$1" + _positionals+=("$_last_positional") + _positionals_count=$((_positionals_count + 1)) + ;; + esac + shift + done +} + +handle_passed_args_count() { + local _required_args_string="'input_bids' and 'output'" + test "${_positionals_count}" -ge 2 || _PRINT_HELP=yes die "FATAL ERROR: Not enough positional arguments - we require exactly 2 (namely: $_required_args_string), but got only ${_positionals_count}." 1 + test "${_positionals_count}" -le 2 || _PRINT_HELP=yes die "FATAL ERROR: There were spurious positional arguments --- we expect exactly 2 (namely: $_required_args_string), but got ${_positionals_count} (the last one was: '${_last_positional}')." 1 +} + +assign_positional_args() { + local _positional_name _shift_for=$1 + _positional_names="_arg_input_bids _arg_output " + + shift "$_shift_for" + for _positional_name in ${_positional_names}; do + test $# -gt 0 || break + eval "$_positional_name=\${1}" || die "Error during argument parsing, possibly an Argbash bug." 1 + shift + done +} + +parse_commandline "$@" +handle_passed_args_count +assign_positional_args 1 "${_positionals[@]}" + +# OTHER STUFF GENERATED BY Argbash + +### END OF CODE GENERATED BY Argbash (sortof) ### ]) +# [ <-- needed because of Argbash + +set -uo pipefail +set -eE -o functrace + +# Load up helper scripts and define helper variables +# shellcheck source=helpers.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/helpers.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/libBIDS.sh/libBIDS.sh" + +# Set magic variables for current file, directory, os, etc. +__file="${__dir}/$(basename "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" +__base="$(basename "${__file}" .sh)" +# shellcheck disable=SC2034,SC2015 +__invocation="$(printf %q "${__file}")$( (($#)) && printf ' %q' "$@" || true)" + +bids_table=$(libBIDSsh_parse_bids_to_csv "${_arg_input_bids}") + +# Prefix filters with the proper option +_column_filter_args=() +for filter in "${_arg_column_filter[@]}"; do + _column_filter_args+=("-r" "$filter") +done + +filtered_image_table=$(libBIDSsh_csv_filter "${bids_table}" \ + -r "suffix:${_arg_suffix}" \ + -r "extension:(nii|nii.gz)" \ + -r "derivatives:${_arg_derivatives}" \ + "${_column_filter_args[@]}") + +declare -A row +while libBIDSsh_csv_iterator "$filtered_image_table" row "subject" "session"; do + printf "%s\n" "${row[path]}" +done >"${_arg_output}" + +if [[ -n "${_arg_masks}" ]]; then + filtered_mask_table=$(libBIDSsh_csv_filter "${bids_table}" \ + -r "suffix:mask" \ + -r "extension:(nii|nii.gz)" \ + -r "derivatives:${_arg_derivatives}" \ + "${_column_filter_args[@]}") + declare -A row + while libBIDS_csv_iterator "$filtered_mask_table" row "subject" "session"; do + printf "%s\n" "${row[path]}" + done >"${_arg_masks}" +fi + +# ] <-- needed because of Argbash diff --git a/twolevel_modelbuild_input_from_bids.sh b/twolevel_modelbuild_input_from_bids.sh new file mode 100755 index 0000000..3543c2f --- /dev/null +++ b/twolevel_modelbuild_input_from_bids.sh @@ -0,0 +1,192 @@ +#!/bin/bash +# +# ARG_OPTIONAL_SINGLE([suffix],[],[BIDS file suffix to construct inputs with],[T1w]) +# ARG_OPTIONAL_SINGLE([derivatives],[],[Derivatives sub-path to use (for preprocessed data)],[NA]) + +# ARG_OPTIONAL_REPEATED([column-filter],[],[Use to filter other BIDS entity types]) + +# ARG_OPTIONAL_SINGLE([masks],[],[Generate brain masks file],[]) + +# ARG_POSITIONAL_SINGLE([input_bids],[Path to BIDS dataset top-level directory],[]) +# ARG_POSITIONAL_SINGLE([output],[CSV filename to save],[]) +# ARG_HELP([Helper script to parse a BIDS-formatted dataset and generate an input.csv for modelbuild.sh]) +# ARGBASH_SET_INDENT([ ]) +# ARGBASH_GO() +# needed because of Argbash --> m4_ignore([ +### START OF CODE GENERATED BY Argbash v2.11.0 one line above ### +# Argbash is a bash code generator used to get arguments parsing right. +# Argbash is FREE SOFTWARE, see https://argbash.dev for more info + +die() { + local _ret="${2:-1}" + test "${_PRINT_HELP:-no}" = yes && print_help >&2 + echo "$1" >&2 + exit "${_ret}" +} + +begins_with_short_option() { + local first_option all_short_options='h' + first_option="${1:0:1}" + test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0 +} + +# THE DEFAULTS INITIALIZATION - POSITIONALS +_positionals=() +# THE DEFAULTS INITIALIZATION - OPTIONALS +_arg_suffix="T1w" +_arg_derivatives="NA" +_arg_column_filter=() +_arg_masks= + +print_help() { + printf '%s\n' "Helper script to parse a BIDS-formatted dataset and generate an input.csv for modelbuild.sh" + printf 'Usage: %s [--suffix ] [--derivatives ] [--column-filter ] [--masks ] [-h|--help] \n' "$0" + printf '\t%s\n' ": Path to BIDS dataset top-level directory" + printf '\t%s\n' ": CSV filename to save" + printf '\t%s\n' "--suffix: BIDS file suffix to construct inputs with (default: 'T1w')" + printf '\t%s\n' "--derivatives: Derivatives sub-path to use (for preprocessed data) (default: 'NA')" + printf '\t%s\n' "--column-filter: Use to filter other BIDS entity types (empty by default)" + printf '\t%s\n' "--masks: Generate brain masks file (no default)" + printf '\t%s\n' "-h, --help: Prints help" +} + +parse_commandline() { + _positionals_count=0 + local _key + while test $# -gt 0; do + _key="$1" + case "$_key" in + --suffix) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_suffix="$2" + shift + ;; + --suffix=*) + _arg_suffix="${_key##--suffix=}" + ;; + --derivatives) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_derivatives="$2" + shift + ;; + --derivatives=*) + _arg_derivatives="${_key##--derivatives=}" + ;; + --column-filter) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_column_filter+=("$2") + shift + ;; + --column-filter=*) + _arg_column_filter+=("${_key##--column-filter=}") + ;; + --masks) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_masks="$2" + shift + ;; + --masks=*) + _arg_masks="${_key##--masks=}" + ;; + -h | --help) + print_help + exit 0 + ;; + -h*) + print_help + exit 0 + ;; + *) + _last_positional="$1" + _positionals+=("$_last_positional") + _positionals_count=$((_positionals_count + 1)) + ;; + esac + shift + done +} + +handle_passed_args_count() { + local _required_args_string="'input_bids' and 'output'" + test "${_positionals_count}" -ge 2 || _PRINT_HELP=yes die "FATAL ERROR: Not enough positional arguments - we require exactly 2 (namely: $_required_args_string), but got only ${_positionals_count}." 1 + test "${_positionals_count}" -le 2 || _PRINT_HELP=yes die "FATAL ERROR: There were spurious positional arguments --- we expect exactly 2 (namely: $_required_args_string), but got ${_positionals_count} (the last one was: '${_last_positional}')." 1 +} + +assign_positional_args() { + local _positional_name _shift_for=$1 + _positional_names="_arg_input_bids _arg_output " + + shift "$_shift_for" + for _positional_name in ${_positional_names}; do + test $# -gt 0 || break + eval "$_positional_name=\${1}" || die "Error during argument parsing, possibly an Argbash bug." 1 + shift + done +} + +parse_commandline "$@" +handle_passed_args_count +assign_positional_args 1 "${_positionals[@]}" + +# OTHER STUFF GENERATED BY Argbash + +### END OF CODE GENERATED BY Argbash (sortof) ### ]) +# [ <-- needed because of Argbash + +set -uo pipefail +set -eE -o functrace + +# Load up helper scripts and define helper variables +# shellcheck source=helpers.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/helpers.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/libBIDS.sh/libBIDS.sh" + +# Set magic variables for current file, directory, os, etc. +__file="${__dir}/$(basename "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" +__base="$(basename "${__file}" .sh)" +# shellcheck disable=SC2034,SC2015 +__invocation="$(printf %q "${__file}")$( (($#)) && printf ' %q' "$@" || true)" + +bids_table=$(libBIDSsh_parse_bids_to_csv "${_arg_input_bids}") + +# Prefix filters with the proper option +_column_filter_args=() +for filter in "${_arg_column_filter[@]}"; do + _column_filter_args+=("-r" "$filter") +done + +filtered_image_table=$(libBIDSsh_csv_filter "${bids_table}" \ + -r "suffix:${_arg_suffix}" \ + -r "extension:(nii|nii.gz)" \ + -r "derivatives:${_arg_derivatives}" \ + "${_column_filter_args[@]}") + +declare -a subjects +declare -A row +libBIDSsh_csv_column_to_array "$filtered_image_table" "subject" subjects true true +for subject in "${subjects[@]}"; do + while libBIDSsh_csv_iterator "$(libBIDSsh_csv_filter "${filtered_image_table}" -r "subject:${subject}")" row "subject" "session"; do + printf "%s," "${row[path]}" + done + printf "\n" +done >"${_arg_output}" + +if [[ -n "${_arg_masks}" ]]; then + filtered_mask_table=$(libBIDSsh_csv_filter "${bids_table}" \ + -r "suffix:mask" \ + -r "extension:(nii|nii.gz)" \ + -r "derivatives:${_arg_derivatives}" \ + "${_column_filter_args[@]}") + + declare -a subjects + declare -A row + libBIDSsh_csv_column_to_array "$filtered_mask_table" "subject" subjects true true + for subject in "${subjects[@]}"; do + while libBIDSsh_csv_iterator "$(libBIDSsh_csv_filter "${filtered_mask_table}" -r "subject:${subject}")" row "subject" "session"; do + printf "%s," "${row[path]}" + done + printf "\n" + done >"${_arg_masks}" +fi + +# ] <-- needed because of Argbash