Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply transforms to raw data when MNI6 derivatives aren't available #58

Merged
merged 12 commits into from
Sep 3, 2024
Prev Previous commit
Next Next commit
Get normalization figure working.
tsalo committed Sep 2, 2024
commit 138a918b80c69302eefaa4bb5600cbdc7a738534
2 changes: 2 additions & 0 deletions src/fmripost_aroma/data/io_spec.json
Original file line number Diff line number Diff line change
@@ -81,6 +81,8 @@
},
"anat_dseg": {
"datatype": "anat",
"task": null,
"run": null,
"space": null,
"res": null,
"den": null,
16 changes: 7 additions & 9 deletions src/fmripost_aroma/data/reports-spec-func.yml
Original file line number Diff line number Diff line change
@@ -10,17 +10,15 @@ sections:
reportlets:
- bids: {datatype: figures, desc: summary, suffix: bold}
- bids: {datatype: figures, desc: validation, suffix: bold}
- bids: {datatype: figures, desc: aroma, suffix: bold}
- bids: {datatype: figures, desc: metrics, suffix: bold}
- bids: {datatype: figures, desc: coreg, suffix: bold}
- bids: {datatype: figures, desc: normalization, suffix: bold}
caption: This panel shows the alignment of the reference EPI (BOLD) image to the
anatomical (T1-weighted) image.
The reference EPI has been contrast enhanced and susceptibility-distortion
corrected (if applicable) for improved anatomical fidelity.
The anatomical image has been resampled into EPI space, as well as the
anatomical white matter mask, which appears as a red contour.
MNI152NLin6Asym template.
The anatomical white matter mask has been warped to MNI152NLin6Asym space
and appears as a red contour.
static: false
subtitle: Alignment of functional and anatomical MRI data (coregistration)
subtitle: Alignment of functional and template MRI data (normalization)
- bids: {datatype: figures, desc: aroma, suffix: bold}
- bids: {datatype: figures, desc: metrics, suffix: bold}
- bids: {datatype: figures, desc: preprocCarpetplot, suffix: bold}
title: Preprocessed BOLD
- bids: {datatype: figures, desc: nonaggrCarpetplot, suffix: bold}
16 changes: 7 additions & 9 deletions src/fmripost_aroma/data/reports-spec.yml
Original file line number Diff line number Diff line change
@@ -10,17 +10,15 @@ sections:
reportlets:
- bids: {datatype: figures, desc: summary, suffix: bold}
- bids: {datatype: figures, desc: validation, suffix: bold}
- bids: {datatype: figures, desc: aroma, suffix: bold}
- bids: {datatype: figures, desc: metrics, suffix: bold}
- bids: {datatype: figures, desc: coreg, suffix: bold}
- bids: {datatype: figures, desc: normalization, suffix: bold}
caption: This panel shows the alignment of the reference EPI (BOLD) image to the
anatomical (T1-weighted) image.
The reference EPI has been contrast enhanced and susceptibility-distortion
corrected (if applicable) for improved anatomical fidelity.
The anatomical image has been resampled into EPI space, as well as the
anatomical white matter mask, which appears as a red contour.
MNI152NLin6Asym template.
The anatomical white matter mask has been warped to MNI152NLin6Asym space
and appears as a red contour.
static: false
subtitle: Alignment of functional and anatomical MRI data (coregistration)
subtitle: Alignment of functional and template MRI data (normalization)
- bids: {datatype: figures, desc: aroma, suffix: bold}
- bids: {datatype: figures, desc: metrics, suffix: bold}
- bids: {datatype: figures, desc: preprocCarpetplot, suffix: bold}
title: Preprocessed BOLD
- bids: {datatype: figures, desc: nonaggrCarpetplot, suffix: bold}
16 changes: 12 additions & 4 deletions src/fmripost_aroma/interfaces/nilearn.py
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@
)
mask_file = File(
exists=True,
mandatory=True,
mandatory=False,
desc='A binary brain mask.',
)
out_file = File(
@@ -46,11 +46,19 @@
output_spec = _MeanImageOutputSpec

def _run_interface(self, runtime):
import nibabel as nb

Check warning on line 49 in src/fmripost_aroma/interfaces/nilearn.py

Codecov / codecov/patch

src/fmripost_aroma/interfaces/nilearn.py#L49

Added line #L49 was not covered by tests
from nilearn.masking import apply_mask, unmask
from nipype.interfaces.base import isdefined

Check warning on line 51 in src/fmripost_aroma/interfaces/nilearn.py

Codecov / codecov/patch

src/fmripost_aroma/interfaces/nilearn.py#L51

Added line #L51 was not covered by tests

if isdefined(self.inputs.mask_file):
data = apply_mask(self.inputs.bold_file, self.inputs.mask_file)
mean_data = data.mean(axis=0)
mean_img = unmask(mean_data, self.inputs.mask_file)

Check warning on line 56 in src/fmripost_aroma/interfaces/nilearn.py

Codecov / codecov/patch

src/fmripost_aroma/interfaces/nilearn.py#L54-L56

Added lines #L54 - L56 were not covered by tests
else:
in_img = nb.load(self.inputs.bold_file)
mean_data = in_img.get_fdata().mean(axis=3)
mean_img = nb.Nifti1Image(mean_data, in_img.affine, in_img.header)

Check warning on line 60 in src/fmripost_aroma/interfaces/nilearn.py

Codecov / codecov/patch

src/fmripost_aroma/interfaces/nilearn.py#L58-L60

Added lines #L58 - L60 were not covered by tests

data = apply_mask(self.inputs.bold_file, self.inputs.mask_file)
mean_data = data.mean(axis=0)
mean_img = unmask(mean_data, self.inputs.mask_file)
self._results['out_file'] = os.path.join(runtime.cwd, self.inputs.out_file)
mean_img.to_filename(self._results['out_file'])

21 changes: 15 additions & 6 deletions src/fmripost_aroma/workflows/outputs.py
Original file line number Diff line number Diff line change
@@ -55,37 +55,38 @@
source_file
Input BOLD images
"""
from nireports.interfaces.reporting.base import (

Check warning on line 58 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L58

Added line #L58 was not covered by tests
SimpleBeforeAfterRPT as SimpleBeforeAfter,
)
from templateflow.api import get as get_template

Check warning on line 61 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L61

Added line #L61 was not covered by tests

from fmripost_aroma.interfaces.nilearn import MeanImage

Check warning on line 63 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L63

Added line #L63 was not covered by tests

workflow = pe.Workflow(name=name)

Check warning on line 65 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L65

Added line #L65 was not covered by tests

inputfields = [

Check warning on line 67 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L67

Added line #L67 was not covered by tests
'source_file',
'bold_mni6',
'anat_dseg',
'anat2std_xfm',
]
inputnode = pe.Node(niu.IdentityInterface(fields=inputfields), name='inputnode')

Check warning on line 73 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L73

Added line #L73 was not covered by tests

# Average the BOLD image over time
calculate_mean_bold = pe.Node(

Check warning on line 76 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L76

Added line #L76 was not covered by tests
MeanImage(),
name='calculate_mean_bold',
mem_gb=1,
)
workflow.connect([(inputnode, calculate_mean_bold, [('bold_mni6', 'bold_file')])])

Check warning on line 81 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L81

Added line #L81 was not covered by tests

# Warp the tissue segmentation to MNI
dseg_to_mni6 = pe.Node(

Check warning on line 84 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L84

Added line #L84 was not covered by tests
ApplyTransforms(interpolation='MultiLabel'),
name='dseg_to_mni6',
mem_gb=1,
)
workflow.connect([

Check warning on line 89 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L89

Added line #L89 was not covered by tests
(inputnode, dseg_to_mni6, [
('anat_dseg', 'input_image'),
('anat2std_xfm', 'transforms'),
@@ -93,43 +94,51 @@
]),
]) # fmt:skip

mni6_wm = pe.Node(

Check warning on line 97 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L97

Added line #L97 was not covered by tests
niu.Function(function=dseg_label),
name='mni6_wm',
mem_gb=DEFAULT_MEMORY_MIN_GB,
)
mni6_wm.inputs.label = 2 # BIDS default is WM=2
workflow.connect([(dseg_to_mni6, mni6_wm, [('output_image', 'in_file')])])
workflow.connect([(dseg_to_mni6, mni6_wm, [('output_image', 'in_seg')])])

Check warning on line 103 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L102-L103

Added lines #L102 - L103 were not covered by tests

# EPI-MNI registration
epi_mni_report = pe.Node(

Check warning on line 106 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L106

Added line #L106 was not covered by tests
SimpleBeforeAfter(
before_label='T1w',
after_label='EPI',
after=str(
get_template(
'MNI152NLin6Asym',
resolution=2,
desc='brain',
suffix='T1w',
extension=['.nii', '.nii.gz'],
)
),
before_label='EPI',
after_label='MNI152NLin6Asym',
dismiss_affine=True,
),
name='epi_mni_report',
mem_gb=0.1,
)
workflow.connect([

Check warning on line 124 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L124

Added line #L124 was not covered by tests
(inputnode, epi_mni_report, [('coreg_boldref', 'after')]),
(calculate_mean_bold, epi_mni_report, [('out_file', 'before')]),
(mni6_wm, epi_mni_report, [('output_image', 'wm_seg')]),
(mni6_wm, epi_mni_report, [('out', 'wm_seg')]),
]) # fmt:skip

ds_epi_mni_report = pe.Node(

Check warning on line 129 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L129

Added line #L129 was not covered by tests
DerivativesDataSink(
base_directory=output_dir,
desc='coreg',
desc='normalization',
suffix='bold',
datatype='figures',
dismiss_entities=dismiss_echo(),
),
name='ds_epi_mni_report',
)
workflow.connect([

Check warning on line 139 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L139

Added line #L139 was not covered by tests
(inputnode, ds_epi_mni_report, [('source_file', 'source_file')]),
(epi_mni_report, ds_epi_mni_report, [('out_report', 'in_file')]),
]) # fmt:skip

return workflow

Check warning on line 144 in src/fmripost_aroma/workflows/outputs.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/outputs.py#L144

Added line #L144 was not covered by tests

Unchanged files with check annotations Beta

def init_single_run_wf(bold_file):
"""Set up a single-run workflow for fMRIPost-AROMA."""
from fmriprep.utils.misc import estimate_bold_mem_usage

Check warning on line 282 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L282

Added line #L282 was not covered by tests
from nipype.interfaces import utility as niu
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
from fmripost_aroma.utils.bids import collect_derivatives, extract_entities
from fmripost_aroma.workflows.aroma import init_denoise_wf, init_ica_aroma_wf
from fmripost_aroma.workflows.outputs import init_func_fit_reports_wf

Check warning on line 288 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L288

Added line #L288 was not covered by tests
spaces = config.workflow.spaces
omp_nthreads = config.nipype.omp_nthreads

Check warning on line 291 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L291

Added line #L291 was not covered by tests
workflow = Workflow(name=_get_wf_name(bold_file, 'single_run'))
workflow.__desc__ = ''

Check warning on line 294 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L294

Added line #L294 was not covered by tests
bold_metadata = config.execution.layout.get_metadata(bold_file)
mem_gb = estimate_bold_mem_usage(bold_file)[1]

Check warning on line 297 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L297

Added line #L297 was not covered by tests
entities = extract_entities(bold_file)
functional_cache = defaultdict(list, {})
if config.execution.derivatives:
# Collect native-space derivatives and transforms
functional_cache = collect_derivatives(

Check warning on line 304 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L304

Added line #L304 was not covered by tests
raw_dataset=config.execution.layout,
derivatives_dataset=None,
entities=entities,
skip_vols = get_nss(functional_cache['confounds'])
# Run ICA-AROMA
ica_aroma_wf = init_ica_aroma_wf(bold_file=bold_file, metadata=bold_metadata, mem_gb=mem_gb)

Check warning on line 369 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L369

Added line #L369 was not covered by tests
ica_aroma_wf.inputs.inputnode.confounds = functional_cache['confounds']
ica_aroma_wf.inputs.inputnode.skip_vols = skip_vols
mni6_buffer = pe.Node(niu.IdentityInterface(fields=['bold', 'bold_mask']), name='mni6_buffer')

Check warning on line 373 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L373

Added line #L373 was not covered by tests
if ('bold_mni152nlin6asym' not in functional_cache) and ('bold_raw' in functional_cache):
# Resample to MNI152NLin6Asym:res-2, for ICA-AROMA classification
from fmriprep.workflows.bold.apply import init_bold_volumetric_resample_wf
from fmriprep.workflows.bold.stc import init_bold_stc_wf
from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms
from niworkflows.interfaces.header import ValidateImage
from templateflow.api import get as get_template

Check warning on line 381 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L377-L381

Added lines #L377 - L381 were not covered by tests
workflow.__desc__ += """\

Check warning on line 383 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L383

Added line #L383 was not covered by tests
Raw BOLD series were resampled to MNI152NLin6Asym:res-2, for ICA-AROMA classification.
"""
validate_bold = pe.Node(

Check warning on line 387 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L387

Added line #L387 was not covered by tests
ValidateImage(in_file=functional_cache['bold_raw']),
name='validate_bold',
)
stc_buffer = pe.Node(

Check warning on line 392 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L392

Added line #L392 was not covered by tests
niu.IdentityInterface(fields=['bold_file']),
name='stc_buffer',
)
run_stc = ('SliceTiming' in bold_metadata) and 'slicetiming' not in config.workflow.ignore

Check warning on line 396 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L396

Added line #L396 was not covered by tests
if run_stc:
bold_stc_wf = init_bold_stc_wf(

Check warning on line 398 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L398

Added line #L398 was not covered by tests
mem_gb=mem_gb,
metadata=bold_metadata,
name='bold_stc_wf',
)
bold_stc_wf.inputs.inputnode.skip_vols = skip_vols
workflow.connect([

Check warning on line 404 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L403-L404

Added lines #L403 - L404 were not covered by tests
(validate_bold, bold_stc_wf, [('out_file', 'inputnode.bold_file')]),
(bold_stc_wf, stc_buffer, [('outputnode.stc_file', 'bold_file')]),
]) # fmt:skip
else:
workflow.connect([(validate_bold, stc_buffer, [('out_file', 'bold_file')])])

Check warning on line 409 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L409

Added line #L409 was not covered by tests
mni6_mask = str(

Check warning on line 411 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L411

Added line #L411 was not covered by tests
get_template(
'MNI152NLin6Asym',
resolution=2,
)
)
bold_MNI6_wf = init_bold_volumetric_resample_wf(

Check warning on line 421 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L421

Added line #L421 was not covered by tests
metadata=bold_metadata,
fieldmap_id=None, # XXX: Ignoring the field map for now
omp_nthreads=omp_nthreads,
jacobian='fmap-jacobian' not in config.workflow.ignore,
name='bold_MNI6_wf',
)
bold_MNI6_wf.inputs.inputnode.motion_xfm = functional_cache['hmc']
bold_MNI6_wf.inputs.inputnode.boldref2fmap_xfm = functional_cache['boldref2fmap']
bold_MNI6_wf.inputs.inputnode.boldref2anat_xfm = functional_cache['boldref2anat']
bold_MNI6_wf.inputs.inputnode.anat2std_xfm = functional_cache['anat2mni152nlin6asym']
bold_MNI6_wf.inputs.inputnode.resolution = '02'

Check warning on line 433 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L429-L433

Added lines #L429 - L433 were not covered by tests
# use mask as boldref?
bold_MNI6_wf.inputs.inputnode.bold_ref_file = functional_cache['bold_mask_native']
bold_MNI6_wf.inputs.inputnode.target_mask = mni6_mask
bold_MNI6_wf.inputs.inputnode.target_ref_file = mni6_mask

Check warning on line 437 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L435-L437

Added lines #L435 - L437 were not covered by tests
workflow.connect([
# Resample BOLD to MNI152NLin6Asym, may duplicate bold_std_wf above
]) # fmt:skip
# Warp the mask as well
mask_to_mni6 = pe.Node(

Check warning on line 452 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L452

Added line #L452 was not covered by tests
ApplyTransforms(
interpolation='MultiLabel',
input_image=functional_cache['bold_mask_native'],
),
name='mask_to_mni6',
)
workflow.connect([(mask_to_mni6, mni6_buffer, [('output_image', 'bold_mask')])])

Check warning on line 464 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L464

Added line #L464 was not covered by tests
elif 'bold_mni152nlin6asym' in functional_cache:
workflow.__desc__ += """\

Check warning on line 467 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L467

Added line #L467 was not covered by tests
Preprocessed BOLD series in MNI152NLin6Asym:res-2 space were collected for ICA-AROMA
classification.
"""
mni6_buffer.inputs.bold = functional_cache['bold_mni152nlin6asym']
mni6_buffer.inputs.bold_mask = functional_cache['bold_mask_mni152nlin6asym']

Check warning on line 472 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L471-L472

Added lines #L471 - L472 were not covered by tests
else:
raise ValueError('No valid BOLD series found for ICA-AROMA classification.')

Check warning on line 475 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L475

Added line #L475 was not covered by tests
workflow.connect([
(mni6_buffer, ica_aroma_wf, [
]) # fmt:skip
# Generate reportlets
func_fit_reports_wf = init_func_fit_reports_wf(output_dir=config.execution.output_dir)
func_fit_reports_wf.inputs.inputnode.source_file = bold_file
func_fit_reports_wf.inputs.inputnode.anat2std_xfm = functional_cache['anat2mni152nlin6asym']
func_fit_reports_wf.inputs.inputnode.anat_dseg = functional_cache['anat_dseg']
workflow.connect([(mni6_buffer, func_fit_reports_wf, [('bold', 'inputnode.bold_mni6')])])

Check warning on line 489 in src/fmripost_aroma/workflows/base.py

Codecov / codecov/patch

src/fmripost_aroma/workflows/base.py#L485-L489

Added lines #L485 - L489 were not covered by tests
if config.workflow.denoise_method:
# Now denoise the output-space BOLD data using ICA-AROMA