Using noise volumes in Nordic/fMRIPrep

Summary of what happened:

Before running fmriprep, I run nordic on my data. This uses the last 3 (noise) volumes (NEX=305). In order to run fmriprep on my data, I have copied over the corresponding *.json files, but these include the 3 noise volumes, even though the final image has only 302 volumes. Does that create a problem for fmriprep?

Command used (and if a helper script was used, a link to the helper script or the command generated):

fmriprep-docker \
--skip_bids_validation \
--omp-nthreads=8 --nthreads=4 --notrack --mem=12 \
--fs-license-file=/home/software/freesurfer/license.txt \
--ignore=sbref --ignore=slicetiming  \
-vv \
`pwd`/test `pwd`/fmriprep_out participant \
--output-space=MNI152NLin2009cAsym

Version:

nipreps/fmriprep:25.0.0

Environment (Docker, Singularity / Apptainer, custom installation):

docker wrapper (fmriprep-docker)

Data formatted according to a validatable standard? Please provide the output of the validator:

PASTE VALIDATOR OUTPUT HERE

Relevant log outputs (up to 20 lines):

PASTE LOG OUTPUT HERE

Screenshots / relevant information:


1 Like

Yes. In BIDS, noise volumes must be split out of the BOLD files as separate files with the noRF suffix. Not doing that will affect fMRIPrep processing.

1 Like

Hi Taylor,

Thanks. I did not realize that. Is there any suggestion on how to do that? I am using dcm2bids to convert my raw data into BIDS format, and I don’t really know how I’d parse my time series within the Dicoms to read the data differently for the _noRF vs. normal volumes. I suppose I could create a new directory for the noRF viles, move the last 3 volumes worth of DICOMs into it, and then add that to my dcm2bids config file, but that is not particularly elegant.

I have another set of data that are self-paced, so the noise volumes are not acquired, and I am using a different Nordic approach for those. I assume for those scans I don’t face the same issue, except that sometimes I have one more volume in one (or more) of the 4 echoes I’m collecting when the scanner is terminated. Is there any best practice when the number of volumes differ from what is in the *.json file (as in such cases when the number of volumes for each echo need to be the same)?

Best wishes,
Matt

Hi @hoptman,

You might have better look scripting something to work directly with the niftis. The following is an AI-generated example, but something like it might work for you. Definitely validate this script on example / copied data before applying it to your original dataset.

#!/usr/bin/env bash
set -euo pipefail

BIDS_DIR="${1:?Usage: bash split_noRF.sh /path/to/bids}"
EXPECTED_VOLS=305
N_NOISE=3

for cmd in fslnvols fslroi jq; do
    command -v "$cmd" >/dev/null || {
        echo "Missing dependency: $cmd" >&2
        exit 1
    }
done

tmpdir=""
trap 'if [[ -n "$tmpdir" ]]; then rm -rf -- "$tmpdir"; fi' EXIT

while IFS= read -r -d '' bold; do

    prefix="${bold%_bold.nii.gz}"
    bold_json="${prefix}_bold.json"
    noise="${prefix}_noRF.nii.gz"
    noise_json="${prefix}_noRF.json"

    nvols=$(fslnvols "$bold")

    if (( nvols != EXPECTED_VOLS )); then
        echo "Skipping ($nvols volumes): $bold"
        continue
    fi

    if [[ -e "$noise" || -e "$noise_json" ]]; then
        echo "Skipping (noRF exists): $bold"
        continue
    fi

    if [[ ! -f "$bold_json" ]]; then
        echo "Missing JSON: $bold_json" >&2
        exit 1
    fi

    n_bold=$((nvols - N_NOISE))

    # Ensure any VolumeTiming array has the correct original length
    if ! jq -e --argjson n "$nvols" \
        '(.VolumeTiming == null) or
         ((.VolumeTiming | type) == "array" and
          (.VolumeTiming | length) == $n)' \
        "$bold_json" >/dev/null; then
        echo "Invalid VolumeTiming: $bold_json" >&2
        exit 1
    fi

    echo "Splitting: $bold"

    tmpdir=$(mktemp -d "$(dirname "$bold")/.split-noRF.XXXXXX")

    # First 302 volumes: functional BOLD
    fslroi "$bold" "$tmpdir/bold.nii.gz" 0 "$n_bold"

    # Last 3 volumes: RF-off noise
    fslroi "$bold" "$tmpdir/noRF.nii.gz" "$n_bold" "$N_NOISE"

    # Update BOLD JSON if VolumeTiming is present
    jq --argjson k "$n_bold" '
        if (.VolumeTiming | type) == "array"
        then .VolumeTiming |= .[:$k]
        else .
        end
    ' "$bold_json" > "$tmpdir/bold.json"

    # Copy acquisition metadata, adjusting noise-specific fields
    jq --argjson k "$n_bold" '
        (if (.VolumeTiming | type) == "array"
         then .VolumeTiming |= .[$k:]
         else .
         end)
        | del(.FlipAngle,
              .NumberOfVolumesDiscardedByScanner,
              .NumberOfVolumesDiscardedByUser)
    ' "$bold_json" > "$tmpdir/noRF.json"

    # Verify extracted dimensions before replacing files
    [[ "$(fslnvols "$tmpdir/bold.nii.gz")" == "$n_bold" ]]
    [[ "$(fslnvols "$tmpdir/noRF.nii.gz")" == "$N_NOISE" ]]

    mv "$tmpdir/noRF.nii.gz" "$noise"
    mv "$tmpdir/noRF.json" "$noise_json"
    mv "$tmpdir/bold.nii.gz" "$bold"
    mv "$tmpdir/bold.json" "$bold_json"

    rmdir "$tmpdir"
    tmpdir=""

    echo "Done: $n_bold BOLD + $N_NOISE noRF volumes"

done < <(
    find "$BIDS_DIR" \
        -type d -name derivatives -prune -o \
        -type f -path '*/func/*' \
        -name '*_bold.nii.gz' -print0
)

Best,
Steven

Hi Steve,

Thanks, that’s helpful. Unfortunately, it’s crashing, though, without any messages. Is there a way to turn off the automatic checks so I can troubleshoot? It seems to run through some of my directories, but not others.

Best wishes,

Matt

Hi @hoptman,

You might have better luck iterating with AI on your end to cut out the middleman. It would be inefficient for me to debug without having access to your filesystem.

Best,
Steven

That makes sense. Thanks!

1 Like