Skip to content
Merged
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: 1 addition & 1 deletion .circleci/bcp_anat_outputs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ logs/CITATION.html
logs/CITATION.md
logs/CITATION.tex
sub-01
sub-01.html
sub-01/ses-1mo
sub-01/ses-1mo/anat
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-aparcaseg_dseg.nii.gz
Expand Down Expand Up @@ -49,3 +48,4 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-CSF_pr
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-GM_probseg.nii.gz
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-WM_probseg.nii.gz
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-T1w_desc-preproc_T2w.nii.gz
sub-01_ses-1mo.html
2 changes: 1 addition & 1 deletion .circleci/bcp_full_outputs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ logs/CITATION.html
logs/CITATION.md
logs/CITATION.tex
sub-01
sub-01.html
sub-01/ses-1mo
sub-01/ses-1mo/anat
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-aparcaseg_dseg.nii.gz
Expand Down Expand Up @@ -72,3 +71,4 @@ sub-01/ses-1mo/func/sub-01_ses-1mo_task-rest_acq-PA_run-001_space-MNIInfant_coho
sub-01/ses-1mo/func/sub-01_ses-1mo_task-rest_acq-PA_run-001_space-MNIInfant_cohort-1_desc-brain_mask.nii.gz
sub-01/ses-1mo/func/sub-01_ses-1mo_task-rest_acq-PA_run-001_space-MNIInfant_cohort-1_desc-preproc_bold.json
sub-01/ses-1mo/func/sub-01_ses-1mo_task-rest_acq-PA_run-001_space-MNIInfant_cohort-1_desc-preproc_bold.nii.gz
sub-01_ses-1mo.html
57 changes: 29 additions & 28 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@ The input dataset is required to be in valid
{abbr}`BIDS (The Brain Imaging Data Structure)` format,
and it must include at least one T1-weighted and
one T2-weighted structural image and
(unless disabled with a flag) a BOLD series.
a BOLD series (unless using the `--anat-only` flag).

We highly recommend that you validate your dataset with the free, online
[BIDS Validator](http://bids-standard.github.io/bids-validator/).

The exact command to run *NiBabies* depends on the [Installation](./installation.md) method.
The common parts of the command follow the
[BIDS-Apps](https://github.com/BIDS-Apps) definition.
Example:

```Shell
$ nibabies data/bids_root/ out/ participant -w work/ --participant-id 01 --age-months 12
```
### Participant Ages
*NiBabies* will attempt to automatically extract participant ages (in months) from the BIDS layout.
Specifically, these two files will be checked:
- [Sessions file](https://bids-specification.readthedocs.io/en/stable/03-modality-agnostic-files.html#sessions-file): `<bids-root>/<subject>/subject_sessions.tsv`
- [Participants file](https://bids-specification.readthedocs.io/en/stable/03-modality-agnostic-files.html#participants-file): `<bids-root>/participants.tsv`

Further information about BIDS and BIDS-Apps can be found at the
[NiPreps portal](https://www.nipreps.org/apps/framework/).
Either file should include `age` (or if you wish to be more explicit: `age_months`) columns, and it is
recommended to have an accompanying JSON file to further describe these fields, and explicitly state the values are in months.

## The FreeSurfer license

Expand All @@ -33,6 +31,21 @@ To obtain a FreeSurfer license, simply register for free at https://surfer.nmr.m
FreeSurfer will search for a license key file first using the `$FS_LICENSE` environment variable and then in the default path to the license key file (`$FREESURFER_HOME`/license.txt). If `$FS_LICENSE` is set, the [`nibabies-wrapper`](#using-the-nibabies-wrapper) will automatically handle setting the license within the container.
Otherwise, you will need to use the `--fs-license-file` flag to ensure the license is available.


## Example command

The exact command to run *NiBabies* depends on the [Installation](./installation.md) method.
The common parts of the command follow the
[BIDS-Apps](https://github.com/BIDS-Apps) definition.
Example:

```Shell
$ nibabies data/bids_root/ out/ participant -w work/ --participant-id 01
```

Further information about BIDS and BIDS-Apps can be found at the
[NiPreps portal](https://www.nipreps.org/apps/framework/).

## Command-Line Arguments
```{argparse}
:ref: nibabies.cli.parser._build_parser
Expand All @@ -50,21 +63,9 @@ At minimum, the following *positional* arguments are required.

However, as infant brains can vastly differ depending on age, providing the following arguments is highly recommended:

- **`--age-months`** - participant age in months

:::{admonition} Warning
:class: warning

This is required if FreeSurfer is not disabled (`--fs-no-reconall`)
:::

- **`--participant-id`** - participant ID

:::{admonition} Tip
:class: tip

This is recommended when using `--age-months` if age varies across participants.
:::
- **`--session-id`** - session ID

- **`--segmentation-atlases-dir`** - directory containing pre-labeled segmentations to use for Joint Label Fusion.

Expand All @@ -85,11 +86,11 @@ For installation instructions, please see [](installation.md#installing-the-niba
### Sample Docker usage

```
$ nibabies-wrapper docker /path/to/data /path/to/output participant --age-months 12 --fs-license-file /usr/freesurfer/license.txt
$ nibabies-wrapper docker /path/to/data /path/to/output participant --fs-license-file /usr/freesurfer/license.txt

RUNNING: docker run --rm -e DOCKER_VERSION_8395080871=20.10.6 -it -v /path/to/data:/data:ro \
-v /path/to/output:/out -v /usr/freesurfer/license.txt:/opt/freesurfer/license.txt:ro \
nipreps/nibabies:21.0.0 /data /out participant --age-months 12
nipreps/nibabies:23.0.0 /data /out participant
...
```

Expand All @@ -103,11 +104,11 @@ This can be overridden by using the `-i` flag to specify a particular Docker ima
### Sample Singularity usage

```
$ nibabies-wrapper singularity /path/to/data /path/to/output participant --age-months 12 -i nibabies-21.0.0.sif --fs-license-file /usr/freesurfer/license.txt
$ nibabies-wrapper singularity /path/to/data /path/to/output participant -i nibabies-23.0.0.sif --fs-license-file /usr/freesurfer/license.txt

RUNNING: singularity run --cleanenv -B /path/to/data:/data:ro \
-B /path/to/output:/out -B /usr/freesurfer/license.txt:/opt/freesurfer/license.txt:ro \
nibabies-21.0.0.sif /data /out participant --age-months 12
nibabies-23.0.0.sif /data /out participant
...
```

Expand Down
47 changes: 33 additions & 14 deletions nibabies/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,20 +696,6 @@ def parse_args(args=None, namespace=None):
config.execution.log_level = int(max(25 - 5 * opts.verbose_count, logging.DEBUG))
config.from_dict(vars(opts))

# Initialize --output-spaces if not defined
if config.execution.output_spaces is None:
from niworkflows.utils.spaces import Reference, SpatialReferences

from ..utils.misc import cohort_by_months

if config.workflow.age_months is None:
parser.error("--age-months must be provided if --output-spaces is not set.")

cohort = cohort_by_months("MNIInfant", config.workflow.age_months)
config.execution.output_spaces = SpatialReferences(
[Reference("MNIInfant", {"res": "native", "cohort": cohort})]
)

# Retrieve logging level
build_log = config.loggers.cli

Expand Down Expand Up @@ -831,8 +817,41 @@ def parse_args(args=None, namespace=None):

config.execution.participant_label = sorted(participant_label)
config.workflow.skull_strip_template = config.workflow.skull_strip_template[0]
config.execution.unique_labels = compute_subworkflows()

# finally, write config to file
config_file = config.execution.work_dir / config.execution.run_uuid / "config.toml"
config_file.parent.mkdir(exist_ok=True, parents=True)
config.to_filename(config_file)


def compute_subworkflows() -> list:
"""
Query all available participants and sessions, and construct the combinations of the
subworkflows needed.
"""
from niworkflows.utils.bids import collect_participants

from nibabies import config

# consists of (subject_id, session_id) tuples
subworkflows = []

subject_list = collect_participants(
config.execution.layout,
participant_label=config.execution.participant_label,
strict=True,
)

for subject in subject_list:
# Due to rapidly changing morphometry of the population
# Ensure each subject session is processed individually
sessions = (
config.execution.session_id
or config.execution.layout.get_sessions(scope='raw', subject=subject)
or [None]
)
# grab participant age per session
for session in sessions:
subworkflows.append((subject, session))
return subworkflows
3 changes: 1 addition & 2 deletions nibabies/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ def main():

# Generate reports phase
generate_reports(
config.execution.participant_label,
config.execution.session_id,
config.execution.unique_labels,
config.execution.nibabies_dir,
config.execution.run_uuid,
config=pkgrf("nibabies", "data/reports-spec.yml"),
Expand Down
27 changes: 10 additions & 17 deletions nibabies/cli/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

def build_workflow(config_file):
"""Create the Nipype Workflow that supports the whole execution graph."""
from niworkflows.utils.bids import check_pipeline_version, collect_participants
from niworkflows.utils.bids import check_pipeline_version
from niworkflows.utils.misc import check_valid_fs_license

from .. import config
Expand Down Expand Up @@ -42,24 +42,17 @@ def build_workflow(config_file):
desc_content = dset_desc_path.read_bytes()
config.execution.bids_description_hash = sha256(desc_content).hexdigest()

# First check that bids_dir looks like a BIDS folder
subject_list = collect_participants(
config.execution.layout, participant_label=config.execution.participant_label
)
subjects_sessions = {
subject: config.execution.session_id
or config.execution.layout.get_sessions(scope='raw', subject=subject)
or [None]
for subject in subject_list
}

# Called with reports only
if config.execution.reports_only:
from pkg_resources import resource_filename as pkgrf

build_logger.log(25, "Running --reports-only on participants %s", ", ".join(subject_list))
build_logger.log(
25,
"Running --reports-only on participants %s",
", ".join(config.execution.unique_labels),
)
retval["return_code"] = generate_reports(
subject_list,
config.execution.unique_labels,
nibabies_dir,
config.execution.run_uuid,
config=pkgrf("nibabies", "data/reports-spec.yml"),
Expand All @@ -71,9 +64,9 @@ def build_workflow(config_file):
init_msg = f"""
Running nibabies version {config.environment.version}:
* BIDS dataset path: {config.execution.bids_dir}.
* Participant list: {subject_list}.
* Participant list: {config.execution.unique_labels}.
* Run identifier: {config.execution.run_uuid}.
* Output spaces: {config.execution.output_spaces}."""
* Output spaces: {config.execution.output_spaces or 'MNIInfant'}."""

if config.execution.anat_derivatives:
init_msg += f"""
Expand All @@ -84,7 +77,7 @@ def build_workflow(config_file):
* Pre-run FreeSurfer's SUBJECTS_DIR: {config.execution.fs_subjects_dir}."""
build_logger.log(25, init_msg)

retval["workflow"] = init_nibabies_wf(subjects_sessions)
retval["workflow"] = init_nibabies_wf(config.execution.unique_labels)

# Check for FS license after building the workflow
if not check_valid_fs_license():
Expand Down
41 changes: 2 additions & 39 deletions nibabies/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ class execution(_Config):
"""Select a particular task from all available in the dataset."""
templateflow_home = _templateflow_home
"""The root folder of the TemplateFlow client."""
unique_labels = None
"""Combinations of subject + session identifiers to be preprocessed."""
work_dir = Path("work").absolute()
"""Path to a working directory where intermediate results will be available."""
write_graph = False
Expand Down Expand Up @@ -581,8 +583,6 @@ class workflow(_Config):
instance keeping standard and nonstandard spaces."""
surface_recon_method = "infantfs"
"""Method to use for surface reconstruction."""
topup_max_vols = 5
"""Maximum number of volumes to use with TOPUP, per-series (EPI or BOLD)."""
use_aroma = None
"""Run ICA-:abbr:`AROMA (automatic removal of motion artifacts)`."""
use_bbr = False
Expand Down Expand Up @@ -694,7 +694,6 @@ def load(filename, skip=None):
section = getattr(sys.modules[__name__], sectionname)
ignore = skip.get(sectionname)
section.load(configs, ignore=ignore)
init_spaces()


def get(flat=False):
Expand Down Expand Up @@ -729,42 +728,6 @@ def to_filename(filename):
filename.write_text(dumps())


def init_spaces(checkpoint=True):
"""Initialize the :attr:`~workflow.spaces` setting."""
from niworkflows.utils.spaces import Reference, SpatialReferences

spaces = execution.output_spaces or SpatialReferences()
if not isinstance(spaces, SpatialReferences):
spaces = SpatialReferences(
[ref for s in spaces.split(" ") for ref in Reference.from_string(s)]
)

if checkpoint and not spaces.is_cached():
spaces.checkpoint()

# Ensure user-defined spatial references for outputs are correctly parsed.
# Certain options require normalization to a space not explicitly defined by users.
# These spaces will not be included in the final outputs.
if workflow.use_aroma:
# Make sure there's a normalization to FSL for AROMA to use.
spaces.add(Reference("MNI152NLin6Asym", {"res": "2"}))

if workflow.cifti_output:
# CIFTI grayordinates to corresponding FSL-MNI resolutions.
vol_res = "2" if workflow.cifti_output == "91k" else "1"
spaces.add(Reference("fsaverage", {"den": "164k"}))
spaces.add(Reference("MNI152NLin6Asym", {"res": vol_res}))
# Ensure a non-native version of MNIInfant is added as a target
if workflow.age_months is not None:
from .utils.misc import cohort_by_months

cohort = cohort_by_months("MNIInfant", workflow.age_months)
spaces.add(Reference("MNIInfant", {"cohort": cohort}))

# Make the SpatialReferences object available
workflow.spaces = spaces


def _process_initializer(cwd, omp_nthreads):
"""Initialize the environment of the child process."""
os.chdir(cwd)
Expand Down
17 changes: 6 additions & 11 deletions nibabies/reports/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ def run_reports(


def generate_reports(
subject_list,
sessions_list,
sub_ses_list,
output_dir,
run_uuid,
config=None,
Expand All @@ -100,15 +99,11 @@ def generate_reports(
if work_dir is not None:
reportlets_dir = Path(work_dir) / "reportlets"

if sessions_list is None:
sessions_list = [None]

report_errors = []
for subject_label, session in product(subject_list, sessions_list):
html_report = f"sub-{subject_label}"
if session:
html_report += f"_ses-{session}"
html_report += ".html"
for subject_label, session in sub_ses_list:
html_report = ''.join(
[f"sub-{subject_label}", f"_ses-{session}" if session else "", ".html"]
)
report_errors.append(
run_reports(
output_dir,
Expand All @@ -127,7 +122,7 @@ def generate_reports(

logger = logging.getLogger("cli")
error_list = ", ".join(
"%s (%d)" % (subid, err) for subid, err in zip(subject_list, report_errors) if err
"%s (%d)" % (subid, err) for subid, err in zip(sub_ses_list, report_errors) if err
)
logger.error(
"Preprocessing did not finish successfully. Errors occurred while processing "
Expand Down
Loading