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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
output/
logs/
.qbatch/
.vscode
*.csv
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions libBIDS.sh
Submodule libBIDS.sh added at 468394
181 changes: 181 additions & 0 deletions modelbuild_input_from_bids.sh
Original file line number Diff line number Diff line change
@@ -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 <arg>] [--derivatives <arg>] [--column-filter <arg>] [--masks <arg>] [-h|--help] <input_bids> <output>\n' "$0"
printf '\t%s\n' "<input_bids>: Path to BIDS dataset top-level directory"
printf '\t%s\n' "<output>: 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
Comment thread
gdevenyi marked this conversation as resolved.

filtered_image_table=$(libBIDSsh_csv_filter "${bids_table}" \
-r "suffix:${_arg_suffix}" \
-r "extension:(nii|nii.gz)" \
-r "derivatives:${_arg_derivatives}" \
"${_column_filter_args[@]}")
Comment on lines +158 to +162

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script defines --session and --run CLI options (and documents them in print_help), but they are never applied to the filter. As a result, those flags have no effect. Consider adding conditional -r \"session:${_arg_session}\" and -r \"run:${_arg_run}\" filters when the user supplies them (e.g., non-empty session and run != 'NA'), or folding them into _column_filter_args similarly to --column-filter.

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +162

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

--session and --run options are parsed but never applied as filters.

_arg_session and _arg_run are accepted via CLI (lines 73-88) but are not passed to libBIDSsh_csv_filter here or in the mask filter block (lines 192-196). Users specifying --session or --run will get no filtering effect, which is silently incorrect.

🐛 Proposed fix — add session/run filters when set
 filtered_image_table=$(libBIDSsh_csv_filter "${bids_table}" \
   -r "suffix:${_arg_suffix}" \
   -r "extension:(nii|nii.gz)" \
   -r "derivatives:${_arg_derivatives}" \
+  ${_arg_session:+-r "session:${_arg_session}"} \
+  ${_arg_run:+-r "run:${_arg_run}"} \
   "${_column_filter_args[@]}")

Apply the same addition to the mask filter block (lines 192-196).

🤖 Prompt for AI Agents
In `@modelbuild_input_from_bids.sh` around lines 180 - 184, The CLI options
_arg_session and _arg_run are parsed but not passed to libBIDSsh_csv_filter, so
add session/run filters when building the filter calls: update the
filtered_image_table libBIDSsh_csv_filter invocation and the mask filter
invocation to include -r "session:${_arg_session}" and -r "run:${_arg_run}"
(only when those variables are non-empty) so that filtering honors --session and
--run; reference the existing calls that populate filtered_image_table and the
mask filter block that uses libBIDSsh_csv_filter to apply the same conditional
additions.


declare -A row
while libBIDSsh_csv_iterator "$filtered_image_table" row "subject" "session"; do
printf "%s\n" "${row[path]}"
done >"${_arg_output}"
Comment on lines +164 to +167

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script mixes libBIDSsh_* function names (e.g., libBIDSsh_parse_bids_to_csv, libBIDSsh_csv_filter) with libBIDS_csv_iterator. If the iterator function in the submodule uses the same libBIDSsh_ prefix, this will fail at runtime with command not found. Align the iterator call to the actual exported function name from libBIDS.sh and keep the naming consistent within this script.

Copilot uses AI. Check for mistakes.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: libBIDS_csv_iterator should be libBIDSsh_csv_iterator.

Line 165 correctly calls libBIDSsh_csv_iterator, but line 176 drops the sh — calling libBIDS_csv_iterator instead. This will fail at runtime with "command not found" whenever --masks is used.

🐛 Proposed fix
-  while libBIDS_csv_iterator "$filtered_mask_table" row "subject" "session"; do
+  while libBIDSsh_csv_iterator "$filtered_mask_table" row "subject" "session"; do
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while libBIDS_csv_iterator "$filtered_mask_table" row "subject" "session"; do
while libBIDSsh_csv_iterator "$filtered_mask_table" row "subject" "session"; do
🤖 Prompt for AI Agents
In `@modelbuild_input_from_bids.sh` at line 176, The loop mistakenly calls
libBIDS_csv_iterator instead of the correct libBIDSsh_csv_iterator; update the
while invocation that currently references libBIDS_csv_iterator to call
libBIDSsh_csv_iterator (the same helper used earlier) so the loop over
"$filtered_mask_table" with variables row, subject, session uses the correct
function and avoids the "command not found" runtime error.

printf "%s\n" "${row[path]}"
done >"${_arg_masks}"
fi

# ] <-- needed because of Argbash
192 changes: 192 additions & 0 deletions twolevel_modelbuild_input_from_bids.sh
Original file line number Diff line number Diff line change
@@ -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 <arg>] [--derivatives <arg>] [--column-filter <arg>] [--masks <arg>] [-h|--help] <input_bids> <output>\n' "$0"
printf '\t%s\n' "<input_bids>: Path to BIDS dataset top-level directory"
printf '\t%s\n' "<output>: 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