Skip to content

Commit

Permalink
Merge tag '24.1.0'
Browse files Browse the repository at this point in the history
24.1.0 (October 02, 2024)

This new minor release includes a few bug fixes, such as excluding MCRIBS from surface reconstruction without a precomputed segmentation and ensuring generated derivatives are not masked, as well as improvements to reporting.

  * ENH: Add boilerplate, errors to report (#403)
  * ENH: Add age to session report (#402)
  * ENH: Improvements to age parsing (#395, #398)

  * FIX: MCRIBS auto surface reconstruction logic (#399)
  * FIX: Do not force masking of anatomicals when using `--derivatives` (#400)

  * MAINT: Revisit warnings filter (#396)
  * MAINT: Automate testing with tox (#404)
  * MAINT: Port over parser arguments and tests from fmriprep (#401)
  • Loading branch information
mgxd committed Oct 3, 2024
2 parents 915fb6f + 27c0a55 commit 45655b8
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 116 deletions.
40 changes: 23 additions & 17 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
exclude: ".*/data/.*"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
exclude: '.*\.svg'
- id: end-of-file-fixer
exclude: '.*\.svg'
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-added-large-files
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.5
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
exclude: '.*\.svg'
- id: end-of-file-fixer
exclude: '.*\.svg'
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-added-large-files
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.5
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
additional_dependencies:
- tomli
19 changes: 19 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
24.1.0 (October 02, 2024)
=========================
This new minor release includes a few bug fixes, such as excluding MCRIBS from surface reconstruction without a precomputed segmentation and ensuring generated derivatives are not masked, as well as improvements to reporting.

### Enhancements
* ENH: Add boilerplate, errors to report (#403)
* ENH: Add age to session report (#402)
* ENH: Improvements to age parsing (#395, #398)

### Bug Fixes
* FIX: MCRIBS auto surface reconstruction logic (#399)
* FIX: Do not force masking of anatomicals when using `--derivatives` (#400)

### Internals / Maintenance
* MAINT: Revisit warnings filter (#396)
* MAINT: Automate testing with tox (#404)
* MAINT: Port over parser arguments and tests from fmriprep (#401)


24.0.1 (August 31, 2024)
========================
A patch release with a fix for the BOLD T2\* workflow. The command line argument `--me-t2s-fit-method` was added for finer control when processing multi-echo datasets.
Expand Down
48 changes: 20 additions & 28 deletions nibabies/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,36 @@

import json
from pathlib import Path
from tempfile import TemporaryDirectory
from shutil import copytree

import nibabel as nb
import numpy as np
import pytest

from nibabies.data import load as load_data

FILES = (
'functional.nii',
'anatomical.nii',
'func.dlabel.nii',
'func.dtseries.nii',
'epi.nii',
'T1w.nii',
'func_to_struct.mat',
'atlas.nii',
'label_list.txt',
'sub-01_run-01_echo-1_bold.nii.gz',
'sub-01_run-01_echo-2_bold.nii.gz',
'sub-01_run-01_echo-3_bold.nii.gz',
)


@pytest.fixture(scope='package')
def data_dir():
with TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
for fname in FILES:
Path.touch(tmp_path / fname)
yield tmp_path
try:
from importlib.resources import files as ir_files
except ImportError: # PY<3.9
from importlib_resources import files as ir_files


def copytree_or_skip(source, target):
data_dir = ir_files('nibabies') / source
if not data_dir.exists():
pytest.skip(f'Cannot chdir into {data_dir!r}. Probably in a zipped distribution.')

try:
copytree(data_dir, target / data_dir.name)
except Exception: # noqa: BLE001
pytest.skip(f'Cannot copy {data_dir!r} into {target / data_dir.name}. Probably in a zip.')


@pytest.fixture(autouse=True)
def _populate_namespace(doctest_namespace, data_dir):
doctest_namespace['data_dir'] = data_dir
doctest_namespace['test_data'] = load_data.cached('../tests/data')
doctest_namespace['Path'] = Path
def _populate_namespace(doctest_namespace, tmp_path):
doctest_namespace['copytree_or_skip'] = copytree_or_skip
doctest_namespace['testdir'] = tmp_path
doctest_namespace['datadir'] = load_data()


@pytest.fixture
Expand Down
26 changes: 26 additions & 0 deletions nibabies/data/boilerplate.bib
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,29 @@ @article{mcribs
title = {Parcellation of the neonatal cortex using Surface-based Melbourne Children's Regional Infant Brain atlases (M-{CRIB}-S)},
journal = {Scientific Reports}
}

@article{patriat_improved_2017,
title = {An improved model of motion-related signal changes in {fMRI}},
volume = {144, Part A},
issn = {1053-8119},
url = {https://www.sciencedirect.com/science/article/pii/S1053811916304360},
doi = {10.1016/j.neuroimage.2016.08.051},
abstract = {Head motion is a significant source of noise in the estimation of functional connectivity from resting-state functional MRI (rs-fMRI). Current strategies to reduce this noise include image realignment, censoring time points corrupted by motion, and including motion realignment parameters and their derivatives as additional nuisance regressors in the general linear model. However, this nuisance regression approach assumes that the motion-induced signal changes are linearly related to the estimated realignment parameters, which is not always the case. In this study we develop an improved model of motion-related signal changes, where nuisance regressors are formed by first rotating and translating a single brain volume according to the estimated motion, re-registering the data, and then performing a principal components analysis (PCA) on the resultant time series of both moved and re-registered data. We show that these “Motion Simulated (MotSim)” regressors account for significantly greater fraction of variance, result in higher temporal signal-to-noise, and lead to functional connectivity estimates that are less affected by motion compared to the most common current approach of using the realignment parameters and their derivatives as nuisance regressors. This improvement should lead to more accurate estimates of functional connectivity, particularly in populations where motion is prevalent, such as patients and young children.},
urldate = {2017-01-18},
journal = {NeuroImage},
author = {Patriat, Rémi and Reynolds, Richard C. and Birn, Rasmus M.},
month = jan,
year = {2017},
keywords = {Motion, Correction, Methods, Rs-fMRI},
pages = {74--82},
}

@article{templateflow,
author = {Ciric, R. and Thompson, William H. and Lorenz, R. and Goncalves, M. and MacNicol, E. and Markiewicz, C. J. and Halchenko, Y. O. and Ghosh, S. S. and Gorgolewski, K. J. and Poldrack, R. A. and Esteban, O.},
title = {{TemplateFlow}: {FAIR}-sharing of multi-scale, multi-species brain models},
volume = {19},
pages = {1568--1571},
year = {2022},
doi = {10.1038/s41592-022-01681-2},
journal = {Nature Methods}
}
Empty file.
Empty file.
Empty file.
61 changes: 61 additions & 0 deletions nibabies/interfaces/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from pathlib import Path
from shutil import copytree
from tempfile import TemporaryDirectory

import pytest

try:
from contextlib import chdir as _chdir
except ImportError: # PY310
import os
from contextlib import contextmanager

@contextmanager # type: ignore
def _chdir(path):
cwd = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(cwd)


DATA_FILES = (
'functional.nii',
'anatomical.nii',
'func.dlabel.nii',
'func.dtseries.nii',
'epi.nii',
'T1w.nii',
'func_to_struct.mat',
'atlas.nii',
'label_list.txt',
'sub-01_run-01_echo-1_bold.nii.gz',
'sub-01_run-01_echo-2_bold.nii.gz',
'sub-01_run-01_echo-3_bold.nii.gz',
)


@pytest.fixture(scope='package')
def data_dir():
with TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
for fname in DATA_FILES:
Path.touch(tmp_path / fname)
yield tmp_path


@pytest.fixture(autouse=True)
def _docdir(data_dir, request, tmp_path):
# Trigger ONLY for the doctests.
doctest_plugin = request.config.pluginmanager.getplugin('doctest')
if isinstance(request.node, doctest_plugin.DoctestItem):
copytree(data_dir, tmp_path, dirs_exist_ok=True)

# Chdir only for the duration of the test.
with _chdir(tmp_path):
yield

else:
# For normal tests, we have to yield, since this is a yield-fixture.
yield
38 changes: 19 additions & 19 deletions nibabies/interfaces/workbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,16 @@ class CiftiCreateDenseFromTemplate(WBCommand):
>>> from nibabies.interfaces import workbench as wb
>>> frmtpl = wb.CiftiCreateDenseFromTemplate()
>>> frmtpl.inputs.in_file = data_dir / "func.dtseries.nii"
>>> frmtpl.inputs.in_file = testdir / "func.dtseries.nii"
>>> frmtpl.inputs.series = True
>>> frmtpl.inputs.series_step = 0.8
>>> frmtpl.inputs.series_start = 0
>>> frmtpl.cmdline # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
'wb_command -cifti-create-dense-from-template .../func.dtseries.nii \
template_func.dtseries.nii -series 0.8 0.0'
>>> frmtpl.inputs.volume = [("OTHER", data_dir / 'functional.nii', True), \
("PUTAMEN_LEFT", data_dir / 'functional.nii')]
>>> frmtpl.inputs.volume = [("OTHER", testdir / 'functional.nii', True), \
("PUTAMEN_LEFT", testdir / 'functional.nii')]
>>> frmtpl.cmdline # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
'wb_command -cifti-create-dense-from-template .../func.dtseries.nii \
template_func.dtseries.nii -series 0.8 0.0 \
Expand Down Expand Up @@ -330,8 +330,8 @@ class CiftiCreateDenseTimeseries(WBCommand):
>>> from nibabies.interfaces.workbench import CiftiCreateDenseTimeseries
>>> createdts = CiftiCreateDenseTimeseries()
>>> createdts.inputs.volume_data = data_dir /'functional.nii'
>>> createdts.inputs.volume_structure_labels = data_dir / 'atlas.nii'
>>> createdts.inputs.volume_data = testdir /'functional.nii'
>>> createdts.inputs.volume_structure_labels = testdir / 'atlas.nii'
>>> createdts.cmdline # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
'wb_command -cifti-create-dense-timeseries out.dtseries.nii \
-volume .../functional.nii .../atlas.nii'
Expand Down Expand Up @@ -467,8 +467,8 @@ class CiftiCreateLabel(WBCommand):
>>> from nibabies.interfaces import workbench as wb
>>> lab = wb.CiftiCreateLabel()
>>> lab.inputs.volume_label = data_dir / "functional.nii"
>>> lab.inputs.structure_label_volume = data_dir / "functional.nii"
>>> lab.inputs.volume_label = testdir / "functional.nii"
>>> lab.inputs.structure_label_volume = testdir / "functional.nii"
>>> lab.cmdline # doctest: +ELLIPSIS
'wb_command -cifti-create-label out.dlabel.nii -volume .../functional.nii .../functional.nii'
"""
Expand Down Expand Up @@ -940,9 +940,9 @@ class CiftiResample(WBCommand):
>>> from nibabies.interfaces import workbench as wb
>>> res = wb.CiftiResample()
>>> res.inputs.in_file = data_dir / "func.dtseries.nii"
>>> res.inputs.in_file = testdir / "func.dtseries.nii"
>>> res.inputs.direction = "COLUMN"
>>> res.inputs.template = data_dir / "func.dlabel.nii"
>>> res.inputs.template = testdir / "func.dlabel.nii"
>>> res.inputs.template_direction = "COLUMN"
>>> res.inputs.surface_method = "ADAP_BARY_AREA"
>>> res.inputs.volume_method = "CUBIC"
Expand Down Expand Up @@ -1051,7 +1051,7 @@ class CiftiSeparate(WBCommand):
dimension, columns for .dtseries.
>>> separate = CiftiSeparate()
>>> separate.inputs.in_file = data_dir / "func.dtseries.nii"
>>> separate.inputs.in_file = testdir / "func.dtseries.nii"
>>> separate.inputs.direction = "COLUMN"
>>> separate.inputs.volume_all_file = "volume_all.nii.gz"
>>> separate.cmdline # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Expand Down Expand Up @@ -1216,10 +1216,10 @@ class VolumeAffineResample(WBCommand):
>>> from nibabies.interfaces.workbench import VolumeAffineResample
>>> resample = VolumeAffineResample()
>>> resample.inputs.in_file = data_dir /'functional.nii'
>>> resample.inputs.volume_space = data_dir /'anatomical.nii'
>>> resample.inputs.in_file = testdir /'functional.nii'
>>> resample.inputs.volume_space = testdir /'anatomical.nii'
>>> resample.inputs.method = 'CUBIC'
>>> resample.inputs.affine = data_dir / 'func_to_struct.mat'
>>> resample.inputs.affine = testdir / 'func_to_struct.mat'
>>> resample.cmdline # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
'wb_command -volume-resample .../functional.nii .../anatomical.nii CUBIC \
resampled_functional.nii.gz -affine .../func_to_struct.mat'
Expand All @@ -1237,8 +1237,8 @@ class VolumeAffineResample(WBCommand):
However, if other volumes were used to calculate the affine, they can
be provided:
>>> resample.inputs.flirt_source_volume = data_dir / 'epi.nii'
>>> resample.inputs.flirt_target_volume = data_dir /'T1w.nii'
>>> resample.inputs.flirt_source_volume = testdir / 'epi.nii'
>>> resample.inputs.flirt_target_volume = testdir /'T1w.nii'
>>> resample.cmdline # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
'wb_command -volume-resample .../functional.nii .../anatomical.nii CUBIC \
resampled_functional.nii.gz -affine .../func_to_struct.mat \
Expand Down Expand Up @@ -1297,7 +1297,7 @@ class VolumeAllLabelsToROIs(WBCommand):
>>> from nibabies.interfaces.workbench import VolumeAllLabelsToROIs
>>> rois = VolumeAllLabelsToROIs()
>>> rois.inputs.in_file = data_dir / 'atlas.nii'
>>> rois.inputs.in_file = testdir / 'atlas.nii'
>>> rois.inputs.label_map = 1
>>> rois.cmdline # doctest: +ELLIPSIS
'wb_command -volume-all-labels-to-rois .../atlas.nii 1 atlas_rois.nii.gz'
Expand Down Expand Up @@ -1346,7 +1346,7 @@ class VolumeLabelExportTable(WBCommand):
>>> from nibabies.interfaces.workbench import VolumeLabelExportTable
>>> label_export = VolumeLabelExportTable()
>>> label_export.inputs.in_file = data_dir / 'atlas.nii'
>>> label_export.inputs.in_file = testdir / 'atlas.nii'
>>> label_export.inputs.label_map = 1
>>> label_export.cmdline # doctest: +ELLIPSIS
'wb_command -volume-label-export-table .../atlas.nii 1 atlas_labels.txt'
Expand Down Expand Up @@ -1434,8 +1434,8 @@ class VolumeLabelImport(WBCommand):
>>> from nibabies.interfaces.workbench import VolumeLabelImport
>>> label_import = VolumeLabelImport()
>>> label_import.inputs.in_file = data_dir / 'atlas.nii'
>>> label_import.inputs.label_list_file = data_dir / 'label_list.txt'
>>> label_import.inputs.in_file = testdir / 'atlas.nii'
>>> label_import.inputs.label_list_file = testdir / 'label_list.txt'
>>> label_import.cmdline # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
'wb_command -volume-label-import .../atlas.nii .../label_list.txt \
atlas_labels.nii.gz'
Expand Down
6 changes: 3 additions & 3 deletions nibabies/workflows/bold/alignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,11 @@ def parse_roi_labels(label_file: str):
Return a list of structure names and label keys.
>>> structs, ids = parse_roi_labels(test_data / "labelfile.txt")
>>> structs, ids = parse_roi_labels(datadir / "FreeSurferSubcorticalLabelTableLut.txt")
>>> structs
['CEREBELLUM_LEFT', 'THALAMUS_LEFT', 'CAUDATE_LEFT']
['ACCUMBENS_LEFT', 'ACCUMBENS_RIGHT', 'AMYGDALA_LEFT', ...]
>>> ids
[8, 10, 11]
[26, 58, 18, ...]
"""

with open(label_file) as fp:
Expand Down
Loading

0 comments on commit 45655b8

Please sign in to comment.