From 8c084a6e6539b32f1af4b40dccc9e8fbfffe1f34 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Mon, 13 Nov 2023 11:38:35 -0500 Subject: [PATCH 01/10] ENH: Restore carpetplot workflow --- fmriprep/workflows/base.py | 21 +++++++ fmriprep/workflows/bold/base.py | 87 ++++++++++++---------------- fmriprep/workflows/bold/confounds.py | 18 +++--- 3 files changed, 68 insertions(+), 58 deletions(-) diff --git a/fmriprep/workflows/base.py b/fmriprep/workflows/base.py index 60043a96b..1e32e4e8a 100644 --- a/fmriprep/workflows/base.py +++ b/fmriprep/workflows/base.py @@ -334,6 +334,7 @@ def init_single_subject_wf(subject_id: str): # Set up the template iterator once, if used template_iterator_wf = None + select_MNI2009c_xfm = None if config.workflow.level == "full": if spaces.cached.get_spaces(nonstandard=False, dim=(3,)): template_iterator_wf = init_template_iterator_wf(spaces=spaces) @@ -344,6 +345,19 @@ def init_single_subject_wf(subject_id: str): ]), ]) # fmt:skip + if 'MNI152NLin2009cAsym' in spaces.get_spaces(): + select_MNI2009c_xfm = pe.Node( + KeySelect(fields=["std2anat_xfm"], key="MNI152NLin2009cAsym"), + name="select_MNI2009c_xfm", + run_without_submitting=True, + ) + workflow.connect([ + (anat_fit_wf, select_MNI2009c_xfm, [ + ("outputnode.std2anat_xfm", "std2anat_xfm"), + ("outputnode.template", "keys"), + ]), + ]) # fmt:skip + # Thread MNI152NLin6Asym standard outputs to CIFTI subworkflow, skipping # the iterator, which targets only output spaces. # This can lead to duplication in the working directory if people actually @@ -569,6 +583,13 @@ def init_single_subject_wf(subject_id: str): ]), ]) # fmt:skip + if select_MNI2009c_xfm is not None: + workflow.connect([ + (select_MNI2009c_xfm, bold_wf, [ + ("std2anat_xfm", "inputnode.mni2009c2anat_xfm"), + ]), + ]) # fmt:skip + # Thread MNI152NLin6Asym standard outputs to CIFTI subworkflow, skipping # the iterator, which targets only output spaces. # This can lead to duplication in the working directory if people actually diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index 8e7bb79a0..08bbaf2ee 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -217,6 +217,8 @@ def init_bold_wf( # MNI152NLin6Asym warp, for CIFTI use "anat2mni6_xfm", "mni6_mask", + # MNI152NLin2009cAsym inverse warp, for carpetplotting + "mni2009c2anat_xfm", ], ), name="inputnode", @@ -598,6 +600,41 @@ def init_bold_wf( ]), ]) # fmt:skip + if spaces.get_spaces(nonstandard=False, dim=(3,)): + carpetplot_wf = init_carpetplot_wf( + mem_gb=mem_gb["resampled"], + metadata=all_metadata[0], + cifti_output=config.workflow.cifti_output, + name="carpetplot_wf", + ) + + if config.workflow.cifti_output: + workflow.connect( + bold_grayords_wf, "outputnode.cifti_bold", carpetplot_wf, "inputnode.cifti_bold", + ) # fmt:skip + + def _last(inlist): + return inlist[-1] + + workflow.connect([ + (inputnode, carpetplot_wf, [ + ("mni2009c2anat_xfm", "inputnode.std2anat_xfm"), + ]), + (bold_fit_wf, carpetplot_wf, [ + ("outputnode.dummy_scans", "inputnode.dummy_scans"), + ("outputnode.bold_mask", "inputnode.bold_mask"), + ('outputnode.boldref2anat_xfm', 'inputnode.boldref2anat_xfm'), + ]), + (bold_native_wf, carpetplot_wf, [ + ("outputnode.bold_native", "inputnode.bold"), + ]), + (bold_confounds_wf, carpetplot_wf, [ + ("outputnode.confounds_file", "inputnode.confounds_file"), + ("outputnode.crown_mask", "inputnode.crown_mask"), + (("outputnode.acompcor_masks", _last), "inputnode.acompcor_mask"), + ]), + ]) # fmt:skip + # Fill-in datasinks of reportlets seen so far for node in workflow.list_node_names(): if node.split(".")[-1].startswith("ds_report"): @@ -837,56 +874,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False): # CIFTI output - if spaces.get_spaces(nonstandard=False, dim=(3,)): - carpetplot_wf = init_carpetplot_wf( - mem_gb=mem_gb["resampled"], - metadata=metadata, - cifti_output=config.workflow.cifti_output, - name="carpetplot_wf", - ) - - # Xform to "MNI152NLin2009cAsym" is always computed. - carpetplot_select_std = pe.Node( - KeySelect(fields=["std2anat_xfm"], key="MNI152NLin2009cAsym"), - name="carpetplot_select_std", - run_without_submitting=True, - ) - - if config.workflow.cifti_output: - # fmt:off - workflow.connect( - bold_grayords_wf, "outputnode.cifti_bold", carpetplot_wf, "inputnode.cifti_bold", - ) - # fmt:on - - def _last(inlist): - return inlist[-1] - - # fmt:off - workflow.connect([ - (initial_boldref_wf, carpetplot_wf, [ - ("outputnode.skip_vols", "inputnode.dummy_scans"), - ]), - (inputnode, carpetplot_select_std, [("std2anat_xfm", "std2anat_xfm"), - ("template", "keys")]), - (carpetplot_select_std, carpetplot_wf, [ - ("std2anat_xfm", "inputnode.std2anat_xfm"), - ]), - (bold_final, carpetplot_wf, [ - ("bold", "inputnode.bold"), - ("mask", "inputnode.bold_mask"), - ]), - (bold_reg_wf, carpetplot_wf, [ - ("outputnode.itk_t1_to_bold", "inputnode.t1_bold_xform"), - ]), - (bold_confounds_wf, carpetplot_wf, [ - ("outputnode.confounds_file", "inputnode.confounds_file"), - ("outputnode.crown_mask", "inputnode.crown_mask"), - (("outputnode.acompcor_masks", _last), "inputnode.acompcor_mask"), - ]), - ]) - # fmt:on - # REPORTING ############################################################ ds_report_summary = pe.Node( DerivativesDataSink(desc="summary", datatype="figures", dismiss_entities=("echo",)), diff --git a/fmriprep/workflows/bold/confounds.py b/fmriprep/workflows/bold/confounds.py index 1bcf091e1..ff876d01d 100644 --- a/fmriprep/workflows/bold/confounds.py +++ b/fmriprep/workflows/bold/confounds.py @@ -719,23 +719,25 @@ def init_carpetplot_wf( if cifti_output: workflow.connect(inputnode, "cifti_bold", conf_plot, "in_cifti") - # fmt:off workflow.connect([ - (inputnode, mrg_xfms, [("boldref2anat_xfm", "in1"), - ("std2anat_xfm", "in2")]), + (inputnode, mrg_xfms, [ + ("boldref2anat_xfm", "in1"), + ("std2anat_xfm", "in2"), + ]), (inputnode, resample_parc, [("bold_mask", "reference_image")]), (inputnode, parcels, [("crown_mask", "crown_mask")]), (inputnode, parcels, [("acompcor_mask", "acompcor_mask")]), - (inputnode, conf_plot, [("bold", "in_nifti"), - ("confounds_file", "confounds_file"), - ("dummy_scans", "drop_trs")]), + (inputnode, conf_plot, [ + ("bold", "in_nifti"), + ("confounds_file", "confounds_file"), + ("dummy_scans", "drop_trs"), + ]), (mrg_xfms, resample_parc, [("out", "transforms")]), (resample_parc, parcels, [("output_image", "segmentation")]), (parcels, conf_plot, [("out", "in_segm")]), (conf_plot, ds_report_bold_conf, [("out_file", "in_file")]), (conf_plot, outputnode, [("out_file", "out_carpetplot")]), - ]) - # fmt:on + ]) # fmt:skip return workflow From 5cdcedac0b93cffbf13a0e271527e7c3236cd5b4 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 13 Nov 2023 16:51:43 -0500 Subject: [PATCH 02/10] RPT: Update boilerplate on new bold_wf --- fmriprep/workflows/bold/base.py | 12 ++++++++++++ fmriprep/workflows/bold/resampling.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index 08bbaf2ee..e3aeee6d1 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -181,6 +181,14 @@ def init_bold_wf( ) workflow = Workflow(name=_get_wf_name(bold_file, "bold")) + workflow.__postdesc__ = """\ +All resamplings can be performed with *a single interpolation +step* by composing all the pertinent transformations (i.e. head-motion +transform matrices, susceptibility distortion correction when available, +and co-registrations to anatomical and output spaces). +Gridded (volumetric) resamplings were performed using `nitransforms`, +configured with cubic B-spline interpolation. +""" inputnode = pe.Node( niu.IdentityInterface( @@ -456,6 +464,10 @@ def init_bold_wf( ]) # fmt:skip if config.workflow.run_reconall and freesurfer_spaces: + workflow.__postdesc__ += """\ +Non-gridded (surface) resamplings were performed using `mri_vol2surf` +(FreeSurfer). +""" config.loggers.workflow.debug("Creating BOLD surface-sampling workflow.") bold_surf_wf = init_bold_surf_wf( mem_gb=mem_gb["resampled"], diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index f91495d50..252bb6e0a 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -583,7 +583,7 @@ def init_bold_fsLR_resampling_wf( workflow.__desc__ = """\ The BOLD time-series were resampled onto the left/right-symmetric template -"fsLR" [@hcppipelines]. +"fsLR" using the Connectome Workbench [@hcppipelines]. """ inputnode = pe.Node( From 71e57b804336a921d0b433a483031e132f2f22b1 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 13 Nov 2023 20:28:32 -0500 Subject: [PATCH 03/10] DOC: Update init_bold_wf docstring --- fmriprep/workflows/bold/base.py | 52 +++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index e3aeee6d1..1b0ad265f 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -110,14 +110,26 @@ def init_bold_wf( t1w_dseg Segmentation of preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) - anat2std_xfm - List of transform files, collated with templates + t1w_tpms + List of tissue probability maps in T1w space subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID fsnative2t1w_xfm LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w + white + FreeSurfer white matter surfaces, in T1w space, collated left, then right + midthickness + FreeSurfer mid-thickness surfaces, in T1w space, collated left, then right + pial + FreeSurfer pial surfaces, in T1w space, collated left, then right + sphere_reg_fsLR + Registration spheres from fsnative to fsLR space, collated left, then right + thickness + FreeSurfer thickness metrics, collated left, then right + anat_ribbon + Binary cortical ribbon mask in T1w space fmap_id Unique identifiers to select fieldmap files fmap @@ -130,14 +142,44 @@ def init_bold_wf( List of fieldmap masks (collated with fmap_id) sdc_method List of fieldmap correction method names (collated with fmap_id) + anat2std_xfm + Transform from anatomical space to standard space + std_t1w + T1w reference image in standard space + std_mask + Brain (binary) mask of the standard reference image + std_space + Value of space entity to be used in standard space output filenames + std_resolution + Value of resolution entity to be used in standard space output filenames + std_cohort + Value of cohort entity to be used in standard space output filenames + anat2mni6_xfm + Transform from anatomical space to MNI152NLin6Asym space + mni6_mask + Brain (binary) mask of the MNI152NLin6Asym reference image + mni2009c2anat_xfm + Transform from MNI152NLin2009cAsym to anatomical space + + Note that ``anat2std_xfm``, ``std_space``, ``std_resolution``, + ``std_cohort``, ``std_t1w`` and ``std_mask`` are treated as single + inputs. In order to resample to multiple target spaces, connect + these fields to an iterable. See Also -------- * :func:`~fmriprep.workflows.bold.fit.init_bold_fit_wf` * :func:`~fmriprep.workflows.bold.fit.init_bold_native_wf` + * :func:`~fmriprep.workflows.bold.apply.init_bold_volumetric_resample_wf` * :func:`~fmriprep.workflows.bold.outputs.init_ds_bold_native_wf` + * :func:`~fmriprep.workflows.bold.outputs.init_ds_volumes_wf` * :func:`~fmriprep.workflows.bold.t2s.init_t2s_reporting_wf` + * :func:`~fmriprep.workflows.bold.resampling.init_bold_surf_wf` + * :func:`~fmriprep.workflows.bold.resampling.init_bold_fsLR_resampling_wf` + * :func:`~fmriprep.workflows.bold.resampling.init_bold_grayords_wf` + * :func:`~fmriprep.workflows.bold.confounds.init_bold_confs_wf` + * :func:`~fmriprep.workflows.bold.confounds.init_carpetplot_wf` """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow @@ -205,8 +247,8 @@ def init_bold_wf( "white", "midthickness", "pial", - "thickness", "sphere_reg_fsLR", + "thickness", "anat_ribbon", # Fieldmap registration "fmap", @@ -217,11 +259,11 @@ def init_bold_wf( "sdc_method", # Volumetric templates "anat2std_xfm", + "std_t1w", + "std_mask", "std_space", "std_resolution", "std_cohort", - "std_t1w", - "std_mask", # MNI152NLin6Asym warp, for CIFTI use "anat2mni6_xfm", "mni6_mask", From ffb2bbd044c5551475f333e575e26fc7b47e33a6 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 13 Nov 2023 20:28:45 -0500 Subject: [PATCH 04/10] RF: Remove dead code --- fmriprep/workflows/bold/__init__.py | 13 +- fmriprep/workflows/bold/apply.py | 40 -- fmriprep/workflows/bold/base.py | 292 +-------------- fmriprep/workflows/bold/outputs.py | 170 --------- fmriprep/workflows/bold/resampling.py | 506 -------------------------- fmriprep/workflows/tests/__init__.py | 2 - 6 files changed, 5 insertions(+), 1018 deletions(-) diff --git a/fmriprep/workflows/bold/__init__.py b/fmriprep/workflows/bold/__init__.py index c7b1ce70a..5440395c3 100644 --- a/fmriprep/workflows/bold/__init__.py +++ b/fmriprep/workflows/bold/__init__.py @@ -16,27 +16,18 @@ """ -from .base import init_func_preproc_wf from .confounds import init_bold_confs_wf from .hmc import init_bold_hmc_wf -from .registration import init_bold_reg_wf, init_bold_t1_trans_wf -from .resampling import ( - init_bold_preproc_trans_wf, - init_bold_std_trans_wf, - init_bold_surf_wf, -) +from .registration import init_bold_reg_wf +from .resampling import init_bold_surf_wf from .stc import init_bold_stc_wf from .t2s import init_bold_t2s_wf __all__ = [ 'init_bold_confs_wf', 'init_bold_hmc_wf', - 'init_bold_std_trans_wf', - 'init_bold_preproc_trans_wf', 'init_bold_reg_wf', 'init_bold_stc_wf', 'init_bold_surf_wf', - 'init_bold_t1_trans_wf', 'init_bold_t2s_wf', - 'init_func_preproc_wf', ] diff --git a/fmriprep/workflows/bold/apply.py b/fmriprep/workflows/bold/apply.py index 6024a73d0..1e7d04380 100644 --- a/fmriprep/workflows/bold/apply.py +++ b/fmriprep/workflows/bold/apply.py @@ -1,29 +1,15 @@ from __future__ import annotations -import os -import typing as ty - -import nibabel as nb import nipype.interfaces.utility as niu import nipype.pipeline.engine as pe -from niworkflows.interfaces.header import ValidateImage from niworkflows.interfaces.nibabel import GenerateSamplingReference from niworkflows.interfaces.utility import KeySelect -from niworkflows.utils.connections import listify - -from fmriprep import config from ...interfaces.resampling import ( DistortionParameters, ReconstructFieldmap, ResampleSeries, ) -from ...utils.misc import estimate_bold_mem_usage -from .stc import init_bold_stc_wf -from .t2s import init_bold_t2s_wf, init_t2s_reporting_wf - -if ty.TYPE_CHECKING: - from niworkflows.utils.spaces import SpatialReferences def init_bold_volumetric_resample_wf( @@ -130,32 +116,6 @@ def init_bold_volumetric_resample_wf( return workflow -def init_bold_apply_wf( - *, - spaces: SpatialReferences, - name: str = 'bold_apply_wf', -) -> pe.Workflow: - """TODO: Docstring""" - from smriprep.workflows.outputs import init_template_iterator_wf - - workflow = pe.Workflow(name=name) - - if spaces.is_cached() and spaces.cached.references: - template_iterator_wf = init_template_iterator_wf(spaces=spaces) - # TODO: Refactor bold_std_trans_wf - # bold_std_trans_wf = init_bold_std_trans_wf( - # freesurfer=config.workflow.run_reconall, - # mem_gb=mem_gb["resampled"], - # omp_nthreads=config.nipype.omp_nthreads, - # spaces=spaces, - # multiecho=multiecho, - # use_compression=not config.execution.low_mem, - # name="bold_std_trans_wf", - # ) - - return workflow - - def _gen_inverses(inlist: list) -> list[bool]: """Create a list indicating the first transform should be inverted. diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index 1b0ad265f..e6ccc0f4e 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -29,41 +29,28 @@ .. autofunction:: init_bold_native_wf """ -import os import typing as ty import nibabel as nb import numpy as np from nipype.interfaces import utility as niu -from nipype.interfaces.fsl import Split as FSLSplit from nipype.pipeline import engine as pe -from niworkflows.utils.connections import listify, pop_file +from niworkflows.utils.connections import listify from ... import config from ...interfaces import DerivativesDataSink -from ...interfaces.reports import FunctionalSummary -from ...utils.meepi import combine_meepi_source # BOLD workflows from .apply import init_bold_volumetric_resample_wf from .confounds import init_bold_confs_wf, init_carpetplot_wf from .fit import init_bold_fit_wf, init_bold_native_wf -from .hmc import init_bold_hmc_wf from .outputs import ( init_ds_bold_native_wf, - init_ds_registration_wf, init_ds_volumes_wf, - init_func_derivatives_wf, prepare_timing_parameters, ) -from .registration import init_bold_reg_wf, init_bold_t1_trans_wf -from .resampling import ( - init_bold_preproc_trans_wf, - init_bold_std_trans_wf, - init_bold_surf_wf, -) -from .stc import init_bold_stc_wf -from .t2s import init_bold_t2s_wf, init_t2s_reporting_wf +from .resampling import init_bold_surf_wf +from .t2s import init_t2s_reporting_wf def init_bold_wf( @@ -71,7 +58,6 @@ def init_bold_wf( bold_series: ty.List[str], precomputed: dict = {}, fieldmap_id: ty.Optional[str] = None, - name: str = "bold_wf", ) -> pe.Workflow: """ This workflow controls the functional preprocessing stages of *fMRIPrep*. @@ -310,7 +296,6 @@ def init_bold_wf( spaces = config.workflow.spaces nonstd_spaces = set(spaces.get_nonstandard()) - template_spaces = spaces.get_spaces(nonstandard=False, dim=(3,)) freesurfer_spaces = spaces.get_fs_spaces() # @@ -698,261 +683,6 @@ def _last(inlist): return workflow -def init_func_preproc_wf(bold_file, has_fieldmap=False): - """ - This workflow controls the functional preprocessing stages of *fMRIPrep*. - - Workflow Graph - .. workflow:: - :graph2use: orig - :simple_form: yes - - from fmriprep.workflows.tests import mock_config - from fmriprep import config - from fmriprep.workflows.bold.base import init_func_preproc_wf - with mock_config(): - bold_file = config.execution.bids_dir / "sub-01" / "func" \ - / "sub-01_task-mixedgamblestask_run-01_bold.nii.gz" - wf = init_func_preproc_wf(str(bold_file)) - - Parameters - ---------- - bold_file - Path to NIfTI file (single echo) or list of paths to NIfTI files (multi-echo) - has_fieldmap : :obj:`bool` - Signals the workflow to use inputnode fieldmap files - - Inputs - ------ - bold_file - BOLD series NIfTI file - t1w_preproc - Bias-corrected structural template image - t1w_mask - Mask of the skull-stripped template image - t1w_dseg - Segmentation of preprocessed structural image, including - gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) - t1w_aseg - Segmentation of structural image, done with FreeSurfer. - t1w_aparc - Parcellation of structural image, done with FreeSurfer. - t1w_tpms - List of tissue probability maps in T1w space - template - List of templates to target - anat2std_xfm - List of transform files, collated with templates - std2anat_xfm - List of inverse transform files, collated with templates - subjects_dir - FreeSurfer SUBJECTS_DIR - subject_id - FreeSurfer subject ID - fsnative2t1w_xfm - LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w - - Outputs - ------- - bold_t1 - BOLD series, resampled to T1w space - bold_t1_ref - BOLD reference image, resampled to T1w space - bold2anat_xfm - Affine transform from BOLD reference space to T1w space - anat2bold_xfm - Affine transform from T1w space to BOLD reference space - hmc_xforms - Affine transforms for each BOLD volume to the BOLD reference - bold_mask_t1 - BOLD series mask in T1w space - bold_aseg_t1 - FreeSurfer ``aseg`` resampled to match ``bold_t1`` - bold_aparc_t1 - FreeSurfer ``aparc+aseg`` resampled to match ``bold_t1`` - bold_std - BOLD series, resampled to template space - bold_std_ref - BOLD reference image, resampled to template space - bold_mask_std - BOLD series mask in template space - bold_aseg_std - FreeSurfer ``aseg`` resampled to match ``bold_std`` - bold_aparc_std - FreeSurfer ``aparc+aseg`` resampled to match ``bold_std`` - bold_native - BOLD series, with distortion corrections applied (native space) - bold_native_ref - BOLD reference image in native space - bold_mask_native - BOLD series mask in native space - bold_echos_native - Per-echo BOLD series, with distortion corrections applied - bold_cifti - BOLD CIFTI image - cifti_metadata - Path of metadata files corresponding to ``bold_cifti``. - surfaces - BOLD series, resampled to FreeSurfer surfaces - t2star_bold - Estimated T2\\* map in BOLD native space - t2star_t1 - Estimated T2\\* map in T1w space - t2star_std - Estimated T2\\* map in template space - confounds - TSV of confounds - confounds_metadata - Confounds metadata dictionary - - See Also - -------- - - * :py:func:`~niworkflows.func.util.init_bold_reference_wf` - * :py:func:`~fmriprep.workflows.bold.stc.init_bold_stc_wf` - * :py:func:`~fmriprep.workflows.bold.hmc.init_bold_hmc_wf` - * :py:func:`~fmriprep.workflows.bold.t2s.init_bold_t2s_wf` - * :py:func:`~fmriprep.workflows.bold.t2s.init_t2s_reporting_wf` - * :py:func:`~fmriprep.workflows.bold.registration.init_bold_t1_trans_wf` - * :py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf` - * :py:func:`~fmriprep.workflows.bold.confounds.init_bold_confs_wf` - * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_std_trans_wf` - * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_preproc_trans_wf` - * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_surf_wf` - * :py:func:`~sdcflows.workflows.fmap.init_fmap_wf` - * :py:func:`~sdcflows.workflows.pepolar.init_pepolar_unwarp_wf` - * :py:func:`~sdcflows.workflows.phdiff.init_phdiff_wf` - * :py:func:`~sdcflows.workflows.syn.init_syn_sdc_wf` - * :py:func:`~sdcflows.workflows.unwarp.init_sdc_unwarp_wf` - - """ - from niworkflows.engine.workflows import LiterateWorkflow as Workflow - from niworkflows.func.util import init_bold_reference_wf - from niworkflows.interfaces.nibabel import ApplyMask - from niworkflows.interfaces.reportlets.registration import ( - SimpleBeforeAfterRPT as SimpleBeforeAfter, - ) - from niworkflows.interfaces.utility import KeySelect - - # Have some options handy - omp_nthreads = config.nipype.omp_nthreads - freesurfer = config.workflow.run_reconall - spaces = config.workflow.spaces - fmriprep_dir = str(config.execution.fmriprep_dir) - freesurfer_spaces = spaces.get_fs_spaces() - - ref_file = bold_file - wf_name = _get_wf_name(ref_file, "func_preproc") - - # Build workflow - workflow = Workflow(name=wf_name) - workflow.__postdesc__ = """\ -All resamplings can be performed with *a single interpolation -step* by composing all the pertinent transformations (i.e. head-motion -transform matrices, susceptibility distortion correction when available, -and co-registrations to anatomical and output spaces). -Gridded (volumetric) resamplings were performed using `antsApplyTransforms` (ANTs), -configured with Lanczos interpolation to minimize the smoothing -effects of other kernels [@lanczos]. -Non-gridded (surface) resamplings were performed using `mri_vol2surf` -(FreeSurfer). -""" - - inputnode = pe.Node( - niu.IdentityInterface( - fields=[ - "bold_file", - "subjects_dir", - "subject_id", - "t1w_preproc", - "t1w_mask", - "t1w_dseg", - "t1w_tpms", - "t1w_aseg", - "t1w_aparc", - "anat2std_xfm", - "std2anat_xfm", - "template", - "anat_ribbon", - "fsnative2t1w_xfm", - "surfaces", - "morphometrics", - "sphere_reg_fsLR", - "fmap", - "fmap_ref", - "fmap_coeff", - "fmap_mask", - "fmap_id", - "sdc_method", - ] - ), - name="inputnode", - ) - inputnode.inputs.bold_file = bold_file - - outputnode = pe.Node( - niu.IdentityInterface( - fields=[ - "bold_t1", - "bold_t1_ref", - "bold2anat_xfm", - "anat2bold_xfm", - "hmc_xforms", - "bold_mask_t1", - "bold_aseg_t1", - "bold_aparc_t1", - "bold_std", - "bold_std_ref", - "bold_mask_std", - "bold_aseg_std", - "bold_aparc_std", - "bold_native", - "bold_native_ref", - "bold_mask_native", - "bold_echos_native", - "bold_cifti", - "cifti_metadata", - "surfaces", - "t2star_bold", - "t2star_t1", - "t2star_std", - "confounds", - "confounds_metadata", - "weights_text", - ] - ), - name="outputnode", - ) - - # SURFACES ################################################################################## - - # CIFTI output - - # REPORTING ############################################################ - ds_report_summary = pe.Node( - DerivativesDataSink(desc="summary", datatype="figures", dismiss_entities=("echo",)), - name="ds_report_summary", - run_without_submitting=True, - mem_gb=config.DEFAULT_MEMORY_MIN_GB, - ) - - ds_report_validation = pe.Node( - DerivativesDataSink(desc="validation", datatype="figures", dismiss_entities=("echo",)), - name="ds_report_validation", - run_without_submitting=True, - mem_gb=config.DEFAULT_MEMORY_MIN_GB, - ) - - # fmt:off - workflow.connect([ - (summary, ds_report_summary, [("out_report", "in_file")]), - (initial_boldref_wf, ds_report_validation, [("outputnode.validation_report", "in_file")]), - ]) - # fmt:on - - return workflow - - def _create_mem_gb(bold_fname): img = nb.load(bold_fname) nvox = int(np.prod(img.shape, dtype='u8')) @@ -988,16 +718,6 @@ def _get_wf_name(bold_fname, prefix): return f'{prefix}_{fname_nosub.replace("-", "_")}_wf' -def _to_join(in_file, join_file): - """Join two tsv files if the join_file is not ``None``.""" - from niworkflows.interfaces.utility import JoinTSVColumns - - if join_file is None: - return in_file - res = JoinTSVColumns(in_file=in_file, join_file=join_file).run() - return res.outputs.out_file - - def extract_entities(file_list): """ Return a dictionary of common entities given a list of files. @@ -1032,12 +752,6 @@ def _unique(inlist): return {k: _unique(v) for k, v in entities.items()} -def get_img_orientation(imgf): - """Return the image orientation as a string""" - img = nb.load(imgf) - return "".join(nb.aff2axcodes(img.affine)) - - def _read_json(in_file): from json import loads from pathlib import Path diff --git a/fmriprep/workflows/bold/outputs.py b/fmriprep/workflows/bold/outputs.py index 9b56daf71..01ae00013 100644 --- a/fmriprep/workflows/bold/outputs.py +++ b/fmriprep/workflows/bold/outputs.py @@ -36,9 +36,6 @@ from fmriprep.config import DEFAULT_MEMORY_MIN_GB from fmriprep.interfaces import DerivativesDataSink -if ty.TYPE_CHECKING: - from niworkflows.utils.spaces import SpatialReferences - def prepare_timing_parameters(metadata: dict): """Convert initial timing metadata to post-realignment timing metadata @@ -849,167 +846,6 @@ def init_ds_volumes_wf( return workflow -def init_func_derivatives_wf( - bids_root: str, - cifti_output: bool, - freesurfer: bool, - project_goodvoxels: bool, - all_metadata: ty.List[dict], - multiecho: bool, - output_dir: str, - spaces: SpatialReferences, - name='func_derivatives_wf', -): - """ - Set up a battery of datasinks to store derivatives in the right location. - - Parameters - ---------- - bids_root : :obj:`str` - Original BIDS dataset path. - cifti_output : :obj:`bool` - Whether the ``--cifti-output`` flag was set. - freesurfer : :obj:`bool` - Whether FreeSurfer anatomical processing was run. - project_goodvoxels : :obj:`bool` - Whether the option was used to exclude voxels with - locally high coefficient of variation, or that lie outside the - cortical surfaces, from the surface projection. - metadata : :obj:`dict` - Metadata dictionary associated to the BOLD run. - multiecho : :obj:`bool` - Derivatives were generated from multi-echo time series. - output_dir : :obj:`str` - Where derivatives should be written out to. - spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences` - A container for storing, organizing, and parsing spatial normalizations. Composed of - :py:class:`~niworkflows.utils.spaces.Reference` objects representing spatial references. - Each ``Reference`` contains a space, which is a string of either TemplateFlow template IDs - (e.g., ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNIPediatricAsym``), nonstandard references - (e.g., ``T1w`` or ``anat``, ``sbref``, ``run``, etc.), or a custom template located in - the TemplateFlow root directory. Each ``Reference`` may also contain a spec, which is a - dictionary with template specifications (e.g., a specification of ``{'resolution': 2}`` - would lead to resampling on a 2mm resolution of the space). - name : :obj:`str` - This workflow's identifier (default: ``func_derivatives_wf``). - - """ - from niworkflows.engine.workflows import LiterateWorkflow as Workflow - from niworkflows.interfaces.utility import KeySelect - from smriprep.workflows.outputs import _bids_relative - - metadata = all_metadata[0] - - timing_parameters = prepare_timing_parameters(metadata) - - nonstd_spaces = set(spaces.get_nonstandard()) - workflow = Workflow(name=name) - - inputnode = pe.Node( - niu.IdentityInterface( - fields=[ - 'bold_aparc_std', - 'bold_aparc_t1', - 'bold_aseg_std', - 'bold_aseg_t1', - 'bold_cifti', - 'bold_mask_std', - 'bold_mask_t1', - 'bold_std', - 'bold_std_ref', - 'bold_t1', - 'bold_t1_ref', - 'bold_native', - 'bold_native_ref', - 'bold_mask_native', - 'bold_echos_native', - 'cifti_metadata', - 'cifti_density', - 'confounds', - 'confounds_metadata', - 'goodvoxels_mask', - 'source_file', - 'all_source_files', - 'surf_files', - 'surf_refs', - 'template', - 'spatial_reference', - 't2star_bold', - 't2star_t1', - 't2star_std', - 'bold2anat_xfm', - 'anat2bold_xfm', - 'hmc_xforms', - 'acompcor_masks', - 'tcompcor_mask', - ] - ), - name='inputnode', - ) - - raw_sources = pe.Node(niu.Function(function=_bids_relative), name='raw_sources') - raw_sources.inputs.bids_root = bids_root - - # fmt:off - workflow.connect([ - (inputnode, raw_sources, [('all_source_files', 'in_files')]), - ]) - # fmt:on - - if freesurfer and project_goodvoxels: - ds_goodvoxels_mask = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - space='T1w', - desc='goodvoxels', - suffix='mask', - Type='ROI', # Metadata - compress=True, - dismiss_entities=("echo",), - ), - name='ds_goodvoxels_mask', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - # fmt:off - workflow.connect([ - (inputnode, ds_goodvoxels_mask, [ - ('source_file', 'source_file'), - ('goodvoxels_mask', 'in_file'), - ]), - ]) - # fmt:on - - if "compcor" in config.execution.debug: - ds_acompcor_masks = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - desc=[f"CompCor{_}" for _ in "CWA"], - suffix="mask", - compress=True, - ), - name="ds_acompcor_masks", - run_without_submitting=True, - ) - ds_tcompcor_mask = pe.Node( - DerivativesDataSink( - base_directory=output_dir, desc="CompCorT", suffix="mask", compress=True - ), - name="ds_tcompcor_mask", - run_without_submitting=True, - ) - # fmt:off - workflow.connect([ - (inputnode, ds_acompcor_masks, [("acompcor_masks", "in_file"), - ("source_file", "source_file")]), - (inputnode, ds_tcompcor_mask, [("tcompcor_mask", "in_file"), - ("source_file", "source_file")]), - ]) - # fmt:on - - return workflow - - def init_bold_preproc_report_wf( mem_gb: float, reportlets_dir: str, @@ -1088,9 +924,3 @@ def init_bold_preproc_report_wf( # fmt:on return workflow - - -def _unlist(in_file): - while isinstance(in_file, (list, tuple)) and len(in_file) == 1: - in_file = in_file[0] - return in_file diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index 252bb6e0a..9629d6a04 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -48,9 +48,6 @@ from ...interfaces.workbench import MetricDilate, MetricMask, MetricResample from .outputs import prepare_timing_parameters -if ty.TYPE_CHECKING: - from niworkflows.utils.spaces import SpatialReferences - def init_bold_surf_wf( *, @@ -769,464 +766,6 @@ def init_bold_fsLR_resampling_wf( return workflow -def init_bold_std_trans_wf( - freesurfer: bool, - mem_gb: float, - omp_nthreads: int, - spaces: SpatialReferences, - multiecho: bool, - name: str = "bold_std_trans_wf", - use_compression: bool = True, -): - """ - Sample fMRI into standard space with a single-step resampling of the original BOLD series. - - .. important:: - This workflow provides two outputnodes. - One output node (with name ``poutputnode``) will be parameterized in a Nipype sense - (see `Nipype iterables - `__), and a - second node (``outputnode``) will collapse the parameterized outputs into synchronous - lists of the output fields listed below. - - Workflow Graph - .. workflow:: - :graph2use: colored - :simple_form: yes - - from niworkflows.utils.spaces import SpatialReferences - from fmriprep.workflows.bold import init_bold_std_trans_wf - wf = init_bold_std_trans_wf( - freesurfer=True, - mem_gb=3, - omp_nthreads=1, - spaces=SpatialReferences( - spaces=["MNI152Lin", - ("MNIPediatricAsym", {"cohort": "6"})], - checkpoint=True), - multiecho=False, - ) - - Parameters - ---------- - freesurfer : :obj:`bool` - Whether to generate FreeSurfer's aseg/aparc segmentations on BOLD space. - mem_gb : :obj:`float` - Size of BOLD file in GB - omp_nthreads : :obj:`int` - Maximum number of threads an individual process may use - spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences` - A container for storing, organizing, and parsing spatial normalizations. Composed of - :py:class:`~niworkflows.utils.spaces.Reference` objects representing spatial references. - Each ``Reference`` contains a space, which is a string of either TemplateFlow template IDs - (e.g., ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNIPediatricAsym``), nonstandard references - (e.g., ``T1w`` or ``anat``, ``sbref``, ``run``, etc.), or a custom template located in - the TemplateFlow root directory. Each ``Reference`` may also contain a spec, which is a - dictionary with template specifications (e.g., a specification of ``{"resolution": 2}`` - would lead to resampling on a 2mm resolution of the space). - name : :obj:`str` - Name of workflow (default: ``bold_std_trans_wf``) - use_compression : :obj:`bool` - Save registered BOLD series as ``.nii.gz`` - - Inputs - ------ - anat2std_xfm - List of anatomical-to-standard space transforms generated during - spatial normalization. - bold_aparc - FreeSurfer's ``aparc+aseg.mgz`` atlas projected into the T1w reference - (only if ``recon-all`` was run). - bold_aseg - FreeSurfer's ``aseg.mgz`` atlas projected into the T1w reference - (only if ``recon-all`` was run). - bold_mask - Skull-stripping mask of reference image - bold_split - Individual 3D volumes, not motion corrected - t2star - Estimated T2\\* map in BOLD native space - fieldwarp - a :abbr:`DFM (displacements field map)` in ITK format - hmc_xforms - List of affine transforms aligning each volume to ``ref_image`` in ITK format - itk_bold_to_t1 - Affine transform from ``ref_bold_brain`` to T1 space (ITK format) - name_source - BOLD series NIfTI file - Used to recover original information lost during processing - templates - List of templates that were applied as targets during - spatial normalization. - - Outputs - ------- - bold_std - BOLD series, resampled to template space - bold_std_ref - Reference, contrast-enhanced summary of the BOLD series, resampled to template space - bold_mask_std - BOLD series mask in template space - bold_aseg_std - FreeSurfer's ``aseg.mgz`` atlas, in template space at the BOLD resolution - (only if ``recon-all`` was run) - bold_aparc_std - FreeSurfer's ``aparc+aseg.mgz`` atlas, in template space at the BOLD resolution - (only if ``recon-all`` was run) - t2star_std - Estimated T2\\* map in template space - template - Template identifiers synchronized correspondingly to previously - described outputs. - - """ - from niworkflows.engine.workflows import LiterateWorkflow as Workflow - from niworkflows.func.util import init_bold_reference_wf - from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms - from niworkflows.interfaces.itk import MultiApplyTransforms - from niworkflows.interfaces.nibabel import GenerateSamplingReference - from niworkflows.interfaces.nilearn import Merge - from niworkflows.interfaces.utility import KeySelect - from niworkflows.utils.spaces import format_reference - - from fmriprep.interfaces.maths import Clip - - workflow = Workflow(name=name) - output_references = spaces.cached.get_spaces(nonstandard=False, dim=(3,)) - std_vol_references = [ - (s.fullname, s.spec) for s in spaces.references if s.standard and s.dim == 3 - ] - - if len(output_references) == 1: - workflow.__desc__ = """\ -The BOLD time-series were resampled into standard space, -generating a *preprocessed BOLD run in {tpl} space*. -""".format( - tpl=output_references[0] - ) - elif len(output_references) > 1: - workflow.__desc__ = """\ -The BOLD time-series were resampled into several standard spaces, -correspondingly generating the following *spatially-normalized, -preprocessed BOLD runs*: {tpl}. -""".format( - tpl=", ".join(output_references) - ) - - inputnode = pe.Node( - niu.IdentityInterface( - fields=[ - "anat2std_xfm", - "bold_aparc", - "bold_aseg", - "bold_mask", - "bold_split", - "t2star", - "fieldwarp", - "hmc_xforms", - "itk_bold_to_t1", - "name_source", - "templates", - ] - ), - name="inputnode", - ) - - iterablesource = pe.Node(niu.IdentityInterface(fields=["std_target"]), name="iterablesource") - # Generate conversions for every template+spec at the input - iterablesource.iterables = [("std_target", std_vol_references)] - - split_target = pe.Node( - niu.Function( - function=_split_spec, - input_names=["in_target"], - output_names=["space", "template", "spec"], - ), - run_without_submitting=True, - name="split_target", - ) - - select_std = pe.Node( - KeySelect(fields=["anat2std_xfm"]), - name="select_std", - run_without_submitting=True, - ) - - select_tpl = pe.Node( - niu.Function(function=_select_template), - name="select_tpl", - run_without_submitting=True, - ) - - gen_ref = pe.Node( - GenerateSamplingReference(), name="gen_ref", mem_gb=0.3 - ) # 256x256x256 * 64 / 8 ~ 150MB) - - mask_std_tfm = pe.Node( - ApplyTransforms(interpolation="MultiLabel"), name="mask_std_tfm", mem_gb=1 - ) - - # Write corrected file in the designated output dir - mask_merge_tfms = pe.Node( - niu.Merge(2), - name="mask_merge_tfms", - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - - merge_xforms = pe.Node( - niu.Merge(4), - name="merge_xforms", - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - - bold_to_std_transform = pe.Node( - MultiApplyTransforms(interpolation="LanczosWindowedSinc", float=True, copy_dtype=True), - name="bold_to_std_transform", - mem_gb=mem_gb * 3 * omp_nthreads, - n_procs=omp_nthreads, - ) - - # Interpolation can occasionally produce below-zero values as an artifact - threshold = pe.MapNode( - Clip(minimum=0), name="threshold", iterfield=['in_file'], mem_gb=DEFAULT_MEMORY_MIN_GB - ) - - merge = pe.Node(Merge(compress=use_compression), name="merge", mem_gb=mem_gb * 3) - - # Generate a reference on the target standard space - gen_final_ref = init_bold_reference_wf(omp_nthreads=omp_nthreads, pre_mask=True) - # fmt:off - workflow.connect([ - (iterablesource, split_target, [("std_target", "in_target")]), - (iterablesource, select_tpl, [("std_target", "template")]), - (inputnode, select_std, [("anat2std_xfm", "anat2std_xfm"), - ("templates", "keys")]), - (inputnode, mask_std_tfm, [("bold_mask", "input_image")]), - (inputnode, gen_ref, [(("bold_split", _first), "moving_image")]), - (inputnode, merge_xforms, [("hmc_xforms", "in4"), - ("fieldwarp", "in3"), - (("itk_bold_to_t1", _aslist), "in2")]), - (inputnode, merge, [("name_source", "header_source")]), - (inputnode, mask_merge_tfms, [(("itk_bold_to_t1", _aslist), "in2")]), - (inputnode, bold_to_std_transform, [("bold_split", "input_image")]), - (split_target, select_std, [("space", "key")]), - (select_std, merge_xforms, [("anat2std_xfm", "in1")]), - (select_std, mask_merge_tfms, [("anat2std_xfm", "in1")]), - (split_target, gen_ref, [(("spec", _is_native), "keep_native")]), - (select_tpl, gen_ref, [("out", "fixed_image")]), - (merge_xforms, bold_to_std_transform, [("out", "transforms")]), - (gen_ref, bold_to_std_transform, [("out_file", "reference_image")]), - (gen_ref, mask_std_tfm, [("out_file", "reference_image")]), - (mask_merge_tfms, mask_std_tfm, [("out", "transforms")]), - (mask_std_tfm, gen_final_ref, [("output_image", "inputnode.bold_mask")]), - (bold_to_std_transform, threshold, [("out_files", "in_file")]), - (threshold, merge, [("out_file", "in_files")]), - (merge, gen_final_ref, [("out_file", "inputnode.bold_file")]), - ]) - # fmt:on - - output_names = [ - "bold_mask_std", - "bold_std", - "bold_std_ref", - "spatial_reference", - "template", - ] - if freesurfer: - output_names.extend(["bold_aseg_std", "bold_aparc_std"]) - if multiecho: - output_names.append("t2star_std") - - poutputnode = pe.Node(niu.IdentityInterface(fields=output_names), name="poutputnode") - # fmt:off - workflow.connect([ - # Connecting outputnode - (iterablesource, poutputnode, [ - (("std_target", format_reference), "spatial_reference")]), - (merge, poutputnode, [("out_file", "bold_std")]), - (gen_final_ref, poutputnode, [("outputnode.ref_image", "bold_std_ref")]), - (mask_std_tfm, poutputnode, [("output_image", "bold_mask_std")]), - (select_std, poutputnode, [("key", "template")]), - ]) - # fmt:on - - if freesurfer: - # Sample the parcellation files to functional space - aseg_std_tfm = pe.Node( - ApplyTransforms(interpolation="MultiLabel"), name="aseg_std_tfm", mem_gb=1 - ) - aparc_std_tfm = pe.Node( - ApplyTransforms(interpolation="MultiLabel"), name="aparc_std_tfm", mem_gb=1 - ) - # fmt:off - workflow.connect([ - (inputnode, aseg_std_tfm, [("bold_aseg", "input_image")]), - (inputnode, aparc_std_tfm, [("bold_aparc", "input_image")]), - (select_std, aseg_std_tfm, [("anat2std_xfm", "transforms")]), - (select_std, aparc_std_tfm, [("anat2std_xfm", "transforms")]), - (gen_ref, aseg_std_tfm, [("out_file", "reference_image")]), - (gen_ref, aparc_std_tfm, [("out_file", "reference_image")]), - (aseg_std_tfm, poutputnode, [("output_image", "bold_aseg_std")]), - (aparc_std_tfm, poutputnode, [("output_image", "bold_aparc_std")]), - ]) - # fmt:on - - if multiecho: - t2star_std_tfm = pe.Node( - ApplyTransforms(interpolation="LanczosWindowedSinc", float=True), - name="t2star_std_tfm", - mem_gb=1, - ) - # fmt:off - workflow.connect([ - (inputnode, t2star_std_tfm, [("t2star", "input_image")]), - (select_std, t2star_std_tfm, [("anat2std_xfm", "transforms")]), - (gen_ref, t2star_std_tfm, [("out_file", "reference_image")]), - (t2star_std_tfm, poutputnode, [("output_image", "t2star_std")]), - ]) - # fmt:on - - # Connect parametric outputs to a Join outputnode - outputnode = pe.JoinNode( - niu.IdentityInterface(fields=output_names), - name="outputnode", - joinsource="iterablesource", - ) - # fmt:off - workflow.connect([ - (poutputnode, outputnode, [(f, f) for f in output_names]), - ]) - # fmt:on - return workflow - - -def init_bold_preproc_trans_wf( - mem_gb: float, - omp_nthreads: int, - name: str = "bold_preproc_trans_wf", - use_compression: bool = True, - use_fieldwarp: bool = False, - interpolation: str = "LanczosWindowedSinc", -): - """ - Resample in native (original) space. - - This workflow resamples the input fMRI in its native (original) - space in a "single shot" from the original BOLD series. - - Workflow Graph - .. workflow:: - :graph2use: colored - :simple_form: yes - - from fmriprep.workflows.bold import init_bold_preproc_trans_wf - wf = init_bold_preproc_trans_wf(mem_gb=3, omp_nthreads=1) - - Parameters - ---------- - mem_gb : :obj:`float` - Size of BOLD file in GB - omp_nthreads : :obj:`int` - Maximum number of threads an individual process may use - name : :obj:`str` - Name of workflow (default: ``bold_std_trans_wf``) - use_compression : :obj:`bool` - Save registered BOLD series as ``.nii.gz`` - use_fieldwarp : :obj:`bool` - Include SDC warp in single-shot transform from BOLD to MNI - interpolation : :obj:`str` - Interpolation type to be used by ANTs' ``applyTransforms`` - (default ``"LanczosWindowedSinc"``) - - Inputs - ------ - bold_file - Individual 3D volumes, not motion corrected - name_source - BOLD series NIfTI file - Used to recover original information lost during processing - hmc_xforms - List of affine transforms aligning each volume to ``ref_image`` in ITK format - fieldwarp - a :abbr:`DFM (displacements field map)` in ITK format - - Outputs - ------- - bold - BOLD series, resampled in native space, including all preprocessing - - """ - from niworkflows.engine.workflows import LiterateWorkflow as Workflow - from niworkflows.interfaces.itk import MultiApplyTransforms - from niworkflows.interfaces.nilearn import Merge - - from fmriprep.interfaces.maths import Clip - - workflow = Workflow(name=name) - workflow.__desc__ = """\ -The BOLD time-series (including slice-timing correction when applied) -were resampled onto their original, native space by applying -{transforms}. -These resampled BOLD time-series will be referred to as *preprocessed -BOLD in original space*, or just *preprocessed BOLD*. -""".format( - transforms="""\ -a single, composite transform to correct for head-motion and -susceptibility distortions""" - if use_fieldwarp - else """\ -the transforms to correct for head-motion""" - ) - - inputnode = pe.Node( - niu.IdentityInterface(fields=["name_source", "bold_file", "hmc_xforms", "fieldwarp"]), - name="inputnode", - ) - - outputnode = pe.Node( - niu.IdentityInterface(fields=["bold", "bold_ref", "bold_ref_brain"]), - name="outputnode", - ) - - merge_xforms = pe.Node( - niu.Merge(2), - name="merge_xforms", - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - - bold_transform = pe.Node( - MultiApplyTransforms(interpolation=interpolation, copy_dtype=True), - name="bold_transform", - mem_gb=mem_gb * 3 * omp_nthreads, - n_procs=omp_nthreads, - ) - - # Interpolation can occasionally produce below-zero values as an artifact - threshold = pe.MapNode( - Clip(minimum=0), name="threshold", iterfield=['in_file'], mem_gb=DEFAULT_MEMORY_MIN_GB - ) - - merge = pe.Node(Merge(compress=use_compression), name="merge", mem_gb=mem_gb * 3) - - # fmt:off - workflow.connect([ - (inputnode, merge_xforms, [("fieldwarp", "in1"), - ("hmc_xforms", "in2")]), - (inputnode, bold_transform, [("bold_file", "input_image"), - (("bold_file", _first), "reference_image")]), - (inputnode, merge, [("name_source", "header_source")]), - (merge_xforms, bold_transform, [("out", "transforms")]), - (bold_transform, threshold, [("out_files", "in_file")]), - (threshold, merge, [("out_file", "in_files")]), - (merge, outputnode, [("out_file", "bold")]), - ]) - # fmt:on - return workflow - - def init_bold_grayords_wf( grayord_density: ty.Literal['91k', '170k'], mem_gb: float, @@ -1317,48 +856,3 @@ def init_bold_grayords_wf( ]), ]) # fmt:skip return workflow - - -def _split_spec(in_target): - space, spec = in_target - template = space.split(":")[0] - return space, template, spec - - -def _select_template(template): - from niworkflows.utils.misc import get_template_specs - - template, specs = template - template = template.split(":")[0] # Drop any cohort modifier if present - specs = specs.copy() - specs["suffix"] = specs.get("suffix", "T1w") - - # Sanitize resolution - res = specs.pop("res", None) or specs.pop("resolution", None) or "native" - if res != "native": - specs["resolution"] = res - return get_template_specs(template, template_spec=specs)[0] - - # Map nonstandard resolutions to existing resolutions - specs["resolution"] = 2 - try: - out = get_template_specs(template, template_spec=specs) - except RuntimeError: - specs["resolution"] = 1 - out = get_template_specs(template, template_spec=specs) - - return out[0] - - -def _first(inlist): - return inlist[0] - - -def _aslist(in_value): - if isinstance(in_value, list): - return in_value - return [in_value] - - -def _is_native(in_value): - return in_value.get("resolution") == "native" or in_value.get("res") == "native" diff --git a/fmriprep/workflows/tests/__init__.py b/fmriprep/workflows/tests/__init__.py index 2861e70da..bf1ce32d4 100644 --- a/fmriprep/workflows/tests/__init__.py +++ b/fmriprep/workflows/tests/__init__.py @@ -30,8 +30,6 @@ from pkg_resources import resource_filename as pkgrf from toml import loads -from ..base import get_estimator - @contextmanager def mock_config(): From d8eee6a5659affc35b6408aa762b1f138b318a22 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 13 Nov 2023 20:59:07 -0500 Subject: [PATCH 05/10] STY: Pacify flake8 a bit --- fmriprep/utils/telemetry.py | 6 +++--- fmriprep/workflows/bold/outputs.py | 9 +++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/fmriprep/utils/telemetry.py b/fmriprep/utils/telemetry.py index 46418e615..e51c5e82a 100644 --- a/fmriprep/utils/telemetry.py +++ b/fmriprep/utils/telemetry.py @@ -110,7 +110,7 @@ def process_crashfile(crashfile): # Extract any other possible metadata in the crash file for k, v in crash_info.items(): - strv = list(_chunks(str(v))) + strv = _chunks(str(v)) if len(strv) == 1: scope.set_extra(k, strv[0]) else: @@ -129,7 +129,7 @@ def process_crashfile(crashfile): break message = issue_title + '\n\n' - message += exception_text[-(8192 - len(message)) :] + message += exception_text[-8192:] if fingerprint: sentry_sdk.add_breadcrumb(message=fingerprint, level='fatal') else: @@ -182,7 +182,7 @@ def _chunks(string, length=CHUNK_SIZE): ['som', 'e l', 'ong', 'er ', 'str', 'ing', '.'] """ - return (string[i : i + length] for i in range(0, len(string), length)) + return [string[i : i + length] for i in range(0, len(string), length)] def setup_migas(init_ping: bool = True) -> None: diff --git a/fmriprep/workflows/bold/outputs.py b/fmriprep/workflows/bold/outputs.py index 01ae00013..bc9ac8043 100644 --- a/fmriprep/workflows/bold/outputs.py +++ b/fmriprep/workflows/bold/outputs.py @@ -823,12 +823,10 @@ def init_ds_volumes_wf( [ (inputnode, resampler, [('ref_file', 'reference_image')]) for resampler in resamplers - ] - + [ + ] + [ (boldref2target, resampler, [('out', 'transforms')]) for resampler in resamplers - ] - + [ + ] + [ (inputnode, datasink, [ ('source_files', 'source_file'), ('space', 'space'), @@ -836,8 +834,7 @@ def init_ds_volumes_wf( ('resolution', 'resolution'), ]) for datasink in datasinks - ] - + [ + ] + [ (resampler, datasink, [("output_image", "in_file")]) for resampler, datasink in zip(resamplers, datasinks) ] From 85f30efb8e4d5847d92056592f0b2df32a9ac567 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 14 Nov 2023 00:22:02 -0500 Subject: [PATCH 06/10] DOC: Update installation guide --- docs/installation.rst | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 631bb056a..5420d1427 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -6,7 +6,7 @@ Installation There are two ways to install *fMRIPrep*: * using container technologies (RECOMMENDED); or -* within a `Manually Prepared Environment (Python 3.8+)`_, also known as +* within a `Manually Prepared Environment (Python 3.10+)`_, also known as *bare-metal installation*. The ``fmriprep`` command-line adheres to the `BIDS-Apps recommendations @@ -42,8 +42,18 @@ or `Singularity `__ subsections. The *NiPreps* portal also contains `extended details of execution with the Docker wrapper `__. -Manually Prepared Environment (Python 3.8+) -=========================================== +In short, install the ``fmriprep-docker`` wrapper with pip:: + + $ python -m pip install fmriprep-docker + +Then run the ``fmriprep-docker`` command-line as if you were running +``fmriprep`` on a *bare-metal* installation:: + + $ fmriprep-docker + + +Manually Prepared Environment (Python 3.10+) +============================================ .. warning:: @@ -56,7 +66,7 @@ A relatively interpretable description of how your environment can be set-up is found in the `Dockerfile `_. As an additional installation setting, FreeSurfer requires a license file (see :ref:`fs_license`). -On a functional Python 3.8 (or above) environment with ``pip`` installed, +On a functional Python 3.10 (or above) environment with ``pip`` installed, *fMRIPrep* can be installed using the habitual command :: $ python -m pip install fmriprep From 5cdcde755de2e239b2eee33264336620100a5799 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 14 Nov 2023 01:23:54 -0500 Subject: [PATCH 07/10] DOC: Update usage.rst with notes on reusing derivatives --- docs/usage.rst | 80 +++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index fc2481ab0..905e63531 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -128,10 +128,13 @@ would be equivalent to the latest example: :: Reusing precomputed derivatives ------------------------------- + Reusing a previous, partial execution of *fMRIPrep* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *fMRIPrep* will pick up where it left off a previous execution, so long as the work directory points to the same location, and this directory has not been changed/manipulated. +Some workflow nodes will rerun unconditionally, so there will always be some amount of +reprocessing. Using a previous run of *FreeSurfer* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -143,48 +146,39 @@ You can use the ``--fs-subjects-dir`` flag to specify a different location to sa FreeSurfer outputs. If precomputed results are found, they will be reused. -The *anatomical fast-track* -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Starting with version 20.1.0, *fMRIPrep* has a command-line argument (``--anat-derivatives ``) -to indicate a path from which the preprocessed information derived from the T1w, T2w (if present) and -FLAIR (if present) images. -This feature was envisioned to help process very large multi-session datasets where the anatomical -images can be averaged (i.e., anatomy is not expected to vary substantially across sessions). -An example where this kind of processing would be useful is -`My Connectome `__, a dataset that contains -107 sessions for a single-subject. -Most of these sessions contain anatomical information which, given the design of the dataset, can be averaged -across sessions as no substantial changes should happen. -In other words, the anatomical information of the dataset can be considered as *cross-sectional*. -Before version 20.1.0, preprocessing this dataset would be hard for two limitations: - - * if the dataset were to be processed in just one enormous job (be it in a commercial Cloud or - :abbr:`HPC (high-performance computing)` resources), the amount of data to be processed surely - would exceed the time limitations per job (and/or related issues, such as restarting from where - it left before); or - * if the processing were `split in sessions `__, - then *fMRIPrep* would attempt to re-process the anatomical information for every session. - -Because processing this emerging type of datasets (*densely sampled neuroimaging*) was impractical with -*fMRIPrep*, the option ``--anat-derivatives`` will shortcut the whole anatomical processing. - -.. danger:: - Using the *anatomical fast-track* (the ``--anat-derivatives`` argument) has important side-effects - that risk the reproducibility and reliability of *fMRIPrep*. - This flag breaks *fMRIPrep*'s internal tracing of provenance, and it trusts whatever input *fMRIPrep* - is given (so long it is BIDS-Derivatives compliant and contains all the necessary files). - - When reporting results obtained with ``--anat-derivatives``, please make sure you highlight this - particular deviation from *fMRIPrep*, and clearly describe the alternative preprocessing of - anatomical data. - -.. attention:: - When the intention is to combine the *anatomical fast-track* with some advanced options that involve - standard spaces (e.g., ``--cifti-output``), please make sure you include the - ``MNI152NLin6Asym`` space to the ``--output-spaces`` list in the first invocation of *fMRIPrep* - (or *sMRIPrep*) from which the results are to be reused. - Otherwise, a warning message indicating that *fMRIPrep*'s expectations were not met will be issued, - and the pre-computed anatomical derivatives will not be reused. +BIDS Derivatives reuse +~~~~~~~~~~~~~~~~~~~~~~ +As of version 23.2.0, *fMRIPrep* can reuse precomputed derivatives that follow BIDS Derivatives +conventions. To provide derivatives to *fMRIPrep*, use the ``--derivatives`` (``-d``) flag one +or more times. + +This mechanism replaces the earlier, more limited ``--anat-derivatives`` flag. + +.. note:: + Derivatives reuse is considered *experimental*. + +This feature has several intended use-cases: + + * To enable fMRIPrep to be run in a "minimal" mode, where only the most essential + derivatives are generated. This can be useful for large datasets where disk space + is a concern, or for users who only need a subset of the derivatives. Further + derivatives may be generated later, or by a different tool. + * To enable fMRIPrep to be integrated into a larger processing pipeline, where + other tools may generate derivatives that fMRIPrep can use in place of its own + steps. + * To enable users to substitute their own custom derivatives for those generated + by fMRIPrep. For example, a user may wish to use a different brain extraction + tool, or a different registration tool, and then use fMRIPrep to generate the + remaining derivatives. + * To enable complicated meta-workflows, where fMRIPrep is run multiple times + with different options, and the results are combined. For example, the + `My Connectome `__ dataset contains + 107 sessions for a single-subject. Processing of all sessions simultaneously + would be impractical, but the anatomical processing can be done once, and + then the functional processing can be done separately for each session. + +See also the ``--level`` flag, which can be used to control which derivatives are +generated. Troubleshooting --------------- @@ -195,7 +189,7 @@ Information on how to customize and understand these files can be found on the page. **Support and communication**. -The documentation of this project is found here: https://fmriprep.readthedocs.org/en/latest/. +The documentation of this project is found here: https://fmriprep.org/en/latest/. All bugs, concerns and enhancement requests for this software can be submitted here: https://github.com/nipreps/fmriprep/issues. From 399184637ae534aabe58f2bfcc7468ca1266910b Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 14 Nov 2023 02:27:23 -0500 Subject: [PATCH 08/10] DOC: Update workflow graph generators --- docs/workflows.rst | 110 ++++++++++++++++---------- fmriprep/workflows/bold/resampling.py | 4 +- 2 files changed, 72 insertions(+), 42 deletions(-) diff --git a/docs/workflows.rst b/docs/workflows.rst index 12351d5cc..2f9549c99 100644 --- a/docs/workflows.rst +++ b/docs/workflows.rst @@ -37,7 +37,7 @@ is presented below: For example, ``anat_preproc_wf`` is a sub-workflow that is generated by the :func:`~smriprep.workflows.anatomical.init_anat_preproc_wf` (see below). Because each task and run of functional data is processed separately, - :func:`~fmriprep.workflows.bold.base.init_func_preproc_wf` names the + :func:`~fmriprep.workflows.bold.base.init_bold_wf` names the resulting workflows using input parameters, resulting in ``func_preproc_task_{task}_run_{run}_wf``. * Datasinks begin with ``ds_``, and save files to the output directory. @@ -287,7 +287,13 @@ from the ``aseg.mgz`` file as described in BOLD preprocessing ------------------ -:py:func:`~fmriprep.workflows.bold.base.init_func_preproc_wf` +*fMRIPrep* performs a series of steps to preprocess :abbr:`BOLD (blood-oxygen level-dependent)` +data. Broadly, these are split into fit and transform stages. + +The following figures show the overall workflow graph and the ``bold_fit_wf`` +subgraph: + +:py:func:`~fmriprep.workflows.bold.base.init_bold_wf` .. workflow:: :graph2use: orig @@ -295,11 +301,27 @@ BOLD preprocessing from fmriprep.workflows.tests import mock_config from fmriprep import config - from fmriprep.workflows.bold.base import init_func_preproc_wf + from fmriprep.workflows.bold.base import init_bold_wf with mock_config(): bold_file = config.execution.bids_dir / 'sub-01' / 'func' \ / 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz' - wf = init_func_preproc_wf(str(bold_file)) + wf = init_bold_wf(bold_series=[str(bold_file)]) + +.. _bold_fit: + +:py:func:`~fmriprep.workflows.bold.fit.init_bold_fit_wf` + +.. workflow:: + :graph2use: orig + :simple_form: yes + + from fmriprep.workflows.tests import mock_config + from fmriprep import config + from fmriprep.workflows.bold.fit import init_bold_fit_wf + with mock_config(): + bold_file = config.execution.bids_dir / 'sub-01' / 'func' \ + / 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz' + wf = init_bold_fit_wf(bold_series=[str(bold_file)], fieldmap_id="fmap") Preprocessing of :abbr:`BOLD (blood-oxygen level-dependent)` files is split into multiple sub-workflows described below. @@ -308,31 +330,31 @@ split into multiple sub-workflows described below. BOLD reference image estimation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:py:func:`~niworkflows.func.util.init_bold_reference_wf` +:py:func:`~fmriprep.workflows.bold.reference.init_raw_boldref_wf` .. workflow:: :graph2use: orig :simple_form: yes - from niworkflows.func.util import init_bold_reference_wf - wf = init_bold_reference_wf(omp_nthreads=1) + from fmriprep.workflows.bold.reference import init_raw_boldref_wf + wf = init_raw_boldref_wf() This workflow estimates a reference image for a -:abbr:`BOLD (blood-oxygen level-dependent)` series. -If a single-band reference ("sbref") image associated with the BOLD series is -available, then it is used directly. -If not, a reference image is estimated from the BOLD series as follows: +:abbr:`BOLD (blood-oxygen level-dependent)` series as follows: When T1-saturation effects ("dummy scans" or non-steady state volumes) are detected, they are averaged and used as reference due to their superior tissue contrast. Otherwise, a median of motion corrected subset of volumes is used. -The reference image is then used to calculate a brain mask for the -:abbr:`BOLD (blood-oxygen level-dependent)` signal using *NiWorkflows*' -:py:func:`~niworkflows.func.util.init_enhance_and_skullstrip_bold_wf`. -Further, the reference is fed to the :ref:`head-motion estimation -workflow ` and the :ref:`registration workflow to map -BOLD series into the T1w image of the same subject `. +This reference is used for :ref:`head-motion estimation `. + +For the :ref:`registration workflow `, the reference image is +either the above described reference image or a single-band reference, +if one is found in the input dataset. +In either case, this image is contrast-enhanced and skull-stripped +(see :py:func:`~niworkflows.func.util.init_enhance_and_skullstrip_bold_wf`). +If fieldmaps are present, the skull-stripped reference is corrected +prior to registration. .. figure:: _static/sub-01_task-balloonanalogrisktask_run-1_desc-rois_bold.svg @@ -418,14 +440,19 @@ Theory, methods and references are found within the Pre-processed BOLD in native space ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:py:func:`~fmriprep.workflows.bold.resampling.init_bold_preproc_trans_wf` +:py:func:`~fmriprep.workflows.bold.fit.init_bold_native_wf` .. workflow:: :graph2use: orig :simple_form: yes - from fmriprep.workflows.bold import init_bold_preproc_trans_wf - wf = init_bold_preproc_trans_wf(mem_gb=3, omp_nthreads=1) + from fmriprep.workflows.tests import mock_config + from fmriprep import config + from fmriprep.workflows.bold.fit import init_bold_native_wf + with mock_config(): + bold_file = config.execution.bids_dir / 'sub-01' / 'func' \ + / 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz' + wf = init_bold_native_wf(bold_series=[str(bold_file)], fieldmap_id='fmap') A new *preproc* :abbr:`BOLD (blood-oxygen level-dependent)` series is generated from the slice-timing corrected or the original data (if @@ -442,16 +469,14 @@ Interpolation uses a Lanczos kernel. EPI to T1w registration ~~~~~~~~~~~~~~~~~~~~~~~ -:py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf` +:py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf` .. workflow:: - :graph2use: orig + :graph2use: hierarchical :simple_form: yes - from fmriprep.workflows.bold import init_bold_reg_wf - wf = init_bold_reg_wf( - freesurfer=True, - mem_gb=1, + from fmriprep.workflows.bold.registration import init_bbreg_wf + wf = init_bbreg_wf( omp_nthreads=1, use_bbr=True, bold2t1w_dof=9, @@ -478,23 +503,14 @@ original, affine registration. Resampling BOLD runs onto standard spaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:py:func:`~fmriprep.workflows.bold.resampling.init_bold_std_trans_wf` +:py:func:`~fmriprep.workflows.bold.apply.init_bold_volumetric_resample_wf` .. workflow:: :graph2use: colored :simple_form: yes - from niworkflows.utils.spaces import SpatialReferences - from fmriprep.workflows.bold import init_bold_std_trans_wf - wf = init_bold_std_trans_wf( - freesurfer=True, - mem_gb=3, - omp_nthreads=1, - spaces=SpatialReferences( - spaces=[('MNI152Lin', {}), ('MNIPediatricAsym', {'cohort': '6'})], - checkpoint=True), - multiecho=False, - ) + from fmriprep.workflows.bold.apply import init_bold_volumetric_resample_wf + wf = init_bold_volumetric_resample_wf(metadata={}, fieldmap_id='fmap') This sub-workflow concatenates the transforms calculated upstream (see `Head-motion estimation`_, `Susceptibility Distortion Correction (SDC)`_ --if @@ -523,7 +539,10 @@ EPI sampled to FreeSurfer surfaces wf = init_bold_surf_wf( mem_gb=1, surface_spaces=['fsnative', 'fsaverage5'], - medial_surface_nan=False) + medial_surface_nan=False, + metadata={}, + output_dir='.', + ) If FreeSurfer processing is enabled, the motion-corrected functional series (after single shot resampling to T1w space) is sampled to the @@ -540,7 +559,7 @@ HCP Grayordinates :py:func:`~fmriprep.workflows.bold.resampling.init_bold_fsLR_resampling_wf` .. workflow:: - :graph2use: colored + :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.resampling import init_bold_fsLR_resampling_wf @@ -595,6 +614,17 @@ T2*-driven echo combination ~~~~~~~~~~~~~~~~~~~~~~~~~~~ :py:func:`~fmriprep.workflows.bold.t2s.init_bold_t2s_wf` +.. workflow:: + :graph2use: colored + :simple_form: yes + + from fmriprep.workflows.bold.t2s import init_bold_t2s_wf + wf = init_bold_t2s_wf( + echo_times=[0.015, 0.030, 0.045], + mem_gb=1, + omp_nthreads=1, + ) + If multi-echo :abbr:`BOLD (blood-oxygen level-dependent)` data is supplied, this workflow uses the `tedana`_ `T2* workflow`_ to generate an adaptive T2* map and optimally weighted combination of all supplied single echo time series. diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index 9629d6a04..b4a52d9e1 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -25,8 +25,6 @@ ++++++++++++++++++++ .. autofunction:: init_bold_surf_wf -.. autofunction:: init_bold_std_trans_wf -.. autofunction:: init_bold_preproc_trans_wf .. autofunction:: init_bold_fsLR_resampling_wf .. autofunction:: init_bold_grayords_wf .. autofunction:: init_goodvoxels_bold_mask_wf @@ -75,6 +73,8 @@ def init_bold_surf_wf( wf = init_bold_surf_wf(mem_gb=0.1, surface_spaces=["fsnative", "fsaverage5"], medial_surface_nan=False, + metadata={}, + output_dir='.', ) Parameters From 921caf9ffeaa23ebd4e56eeffe6f14f695781978 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 13 Nov 2023 20:53:11 -0500 Subject: [PATCH 09/10] FIX: Incomplete transition to an HMC-free path --- fmriprep/interfaces/resampling.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/fmriprep/interfaces/resampling.py b/fmriprep/interfaces/resampling.py index d3febb00c..f997765ea 100644 --- a/fmriprep/interfaces/resampling.py +++ b/fmriprep/interfaces/resampling.py @@ -539,7 +539,7 @@ def resample_image( classes = [xfm.__class__.__name__ for xfm in transforms] raise ValueError(f"HMC transforms must come last. Found sequence: {classes}") transform_list: list = transforms.transforms - hmc = None + hmc = [] # Retrieve the RAS coordinates of the target space coordinates = nt.base.SpatialReference.factory(target).ndcoords.astype('f4').T @@ -548,15 +548,12 @@ def resample_image( vox2ras = source.affine ras2vox = np.linalg.inv(vox2ras) # Transform RAS2RAS head motion transforms to VOX2VOX - if hmc is not None: - hmc_xfms = [ras2vox @ xfm.matrix @ vox2ras for xfm in transforms[-1]] - else: - hmc_xfms = None + hmc_xfms = [ras2vox @ xfm.matrix @ vox2ras for xfm in hmc] - # Remove the head-motion transforms and add a mapping from boldref + # After removing the head-motion transforms, add a mapping from boldref # world space to voxels. This new transform maps from world coordinates # in the target space to voxel coordinates in the source space. - ref2vox = nt.TransformChain(transforms[:-1] + [nt.Affine(ras2vox)]) + ref2vox = nt.TransformChain(transform_list + [nt.Affine(ras2vox)]) mapped_coordinates = ref2vox.map(coordinates) # Some identities to reduce special casing downstream From e55f185d488491c4c3abfd0b1e21ffe79773d4e2 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 14 Nov 2023 13:58:57 -0500 Subject: [PATCH 10/10] DOC: Add notes on processing level to outputs.rst --- docs/outputs.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/outputs.rst b/docs/outputs.rst index c7b63ef3c..d3c241535 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -62,6 +62,28 @@ This layout, now the default, may be explicitly specified with the For compatibility with versions of fMRIPrep prior to 21.0, the `legacy layout`_ is available via ``--output-layout legacy``. +Processing level +---------------- +As of version 23.2.0, fMRIPrep supports three levels of derivatives: + +* ``--level minimal``: This processing mode aims to produce the smallest + working directory and output dataset possible, while enabling all further + processing results to be deterministically generated. Most components of + the `visual reports`_ can be generated at this level, so the quality of + preprocessing can be assessed. Because no resampling is done, confounds + and carpetplots will be missing from the reports. +* ``--level resampling``: This processing mode aims to produce additional + derivatives that enable third-party resampling, resampling BOLD series + in the working directory as needed, but these are not saved to the output + directory. + The ``--me-output-echos`` flag will be enabled at this level, in which + case the individual echos will be saved to the working directory after + slice-timing correction, head-motion correction, and susceptibility + distortion correction. +* ``--level full``: This processing mode aims to produce all derivatives + that have previously been a part of the fMRIPrep output dataset. + This is the default processing level. + Visual Reports -------------- *fMRIPrep* outputs summary reports, written to ``/fmriprep/sub-.html``.