From c4d1a00527b6b7b88d3f29a1304aa454cd0c2e23 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Fri, 3 Nov 2023 14:17:30 -0400 Subject: [PATCH 1/3] RF: Remove datasinks already reimplemented elsewhere --- fmriprep/workflows/bold/outputs.py | 339 ----------------------------- 1 file changed, 339 deletions(-) diff --git a/fmriprep/workflows/bold/outputs.py b/fmriprep/workflows/bold/outputs.py index d31cafc0b..e83026636 100644 --- a/fmriprep/workflows/bold/outputs.py +++ b/fmriprep/workflows/bold/outputs.py @@ -961,351 +961,12 @@ def init_func_derivatives_wf( raw_sources = pe.Node(niu.Function(function=_bids_relative), name='raw_sources') raw_sources.inputs.bids_root = bids_root - ds_ref_t1w_xfm = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - to='T1w', - mode='image', - suffix='xfm', - extension='.txt', - dismiss_entities=('echo',), - **{'from': 'scanner'}, - ), - name='ds_ref_t1w_xfm', - run_without_submitting=True, - ) - ds_ref_t1w_inv_xfm = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - to='scanner', - mode='image', - suffix='xfm', - extension='.txt', - dismiss_entities=('echo',), - **{'from': 'T1w'}, - ), - name='ds_t1w_tpl_inv_xfm', - run_without_submitting=True, - ) # fmt:off workflow.connect([ (inputnode, raw_sources, [('all_source_files', 'in_files')]), - (inputnode, ds_ref_t1w_xfm, [('source_file', 'source_file'), - ('bold2anat_xfm', 'in_file')]), - (inputnode, ds_ref_t1w_inv_xfm, [('source_file', 'source_file'), - ('anat2bold_xfm', 'in_file')]), - ]) - # fmt:on - - # Output HMC and reference volume - ds_bold_hmc_xfm = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - to='boldref', - mode='image', - suffix='xfm', - extension='.txt', - dismiss_entities=('echo',), - **{'from': 'scanner'}, - ), - name='ds_bold_hmc_xfm', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - - ds_bold_native_ref = pe.Node( - DerivativesDataSink( - base_directory=output_dir, suffix='boldref', compress=True, dismiss_entities=("echo",) - ), - name='ds_bold_native_ref', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - - # fmt:off - workflow.connect([ - (inputnode, ds_bold_hmc_xfm, [('source_file', 'source_file'), - ('hmc_xforms', 'in_file')]), - (inputnode, ds_bold_native_ref, [('source_file', 'source_file'), - ('bold_native_ref', 'in_file')]) ]) # fmt:on - # Resample to T1w space - if nonstd_spaces.intersection(('T1w', 'anat')): - ds_bold_t1 = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - space='T1w', - desc='preproc', - compress=True, - SkullStripped=masked, - TaskName=metadata.get('TaskName'), - **timing_parameters, - ), - name='ds_bold_t1', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - ds_bold_t1_ref = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - space='T1w', - suffix='boldref', - compress=True, - dismiss_entities=("echo",), - ), - name='ds_bold_t1_ref', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - ds_bold_mask_t1 = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - space='T1w', - desc='brain', - suffix='mask', - compress=True, - dismiss_entities=("echo",), - ), - name='ds_bold_mask_t1', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - # fmt:off - workflow.connect([ - (inputnode, ds_bold_t1, [('source_file', 'source_file'), - ('bold_t1', 'in_file')]), - (inputnode, ds_bold_t1_ref, [('source_file', 'source_file'), - ('bold_t1_ref', 'in_file')]), - (inputnode, ds_bold_mask_t1, [('source_file', 'source_file'), - ('bold_mask_t1', 'in_file')]), - (raw_sources, ds_bold_mask_t1, [('out', 'RawSources')]), - ]) - # fmt:on - if freesurfer: - ds_bold_aseg_t1 = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - space='T1w', - desc='aseg', - suffix='dseg', - compress=True, - dismiss_entities=("echo",), - ), - name='ds_bold_aseg_t1', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - ds_bold_aparc_t1 = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - space='T1w', - desc='aparcaseg', - suffix='dseg', - compress=True, - dismiss_entities=("echo",), - ), - name='ds_bold_aparc_t1', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - # fmt:off - workflow.connect([ - (inputnode, ds_bold_aseg_t1, [('source_file', 'source_file'), - ('bold_aseg_t1', 'in_file')]), - (inputnode, ds_bold_aparc_t1, [('source_file', 'source_file'), - ('bold_aparc_t1', 'in_file')]), - ]) - # fmt:on - if multiecho: - ds_t2star_t1 = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - space='T1w', - suffix='T2starmap', - compress=True, - dismiss_entities=("echo",), - **t2star_meta, - ), - name='ds_t2star_t1', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - # fmt:off - workflow.connect([ - (inputnode, ds_t2star_t1, [('source_file', 'source_file'), - ('t2star_t1', 'in_file')]), - (raw_sources, ds_t2star_t1, [('out', 'RawSources')]), - ]) - # fmt:on - - if getattr(spaces, '_cached') is None: - return workflow - - # Store resamplings in standard spaces when listed in --output-spaces - if spaces.cached.references: - from niworkflows.interfaces.space import SpaceDataSource - - spacesource = pe.Node(SpaceDataSource(), name='spacesource', run_without_submitting=True) - spacesource.iterables = ( - 'in_tuple', - [(s.fullname, s.spec) for s in spaces.cached.get_standard(dim=(3,))], - ) - - fields = ['template', 'bold_std', 'bold_std_ref', 'bold_mask_std'] - if multiecho: - fields.append('t2star_std') - select_std = pe.Node( - KeySelect(fields=fields), - name='select_std', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - - ds_bold_std = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - desc='preproc', - compress=True, - SkullStripped=masked, - TaskName=metadata.get('TaskName'), - **timing_parameters, - ), - name='ds_bold_std', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - ds_bold_std_ref = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - suffix='boldref', - compress=True, - dismiss_entities=("echo",), - ), - name='ds_bold_std_ref', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - ds_bold_mask_std = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - desc='brain', - suffix='mask', - compress=True, - dismiss_entities=("echo",), - ), - name='ds_bold_mask_std', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - # fmt:off - workflow.connect([ - (inputnode, ds_bold_std, [('source_file', 'source_file')]), - (inputnode, ds_bold_std_ref, [('source_file', 'source_file')]), - (inputnode, ds_bold_mask_std, [('source_file', 'source_file')]), - (inputnode, select_std, [('bold_std', 'bold_std'), - ('bold_std_ref', 'bold_std_ref'), - ('bold_mask_std', 'bold_mask_std'), - ('t2star_std', 't2star_std'), - ('template', 'template'), - ('spatial_reference', 'keys')]), - (spacesource, select_std, [('uid', 'key')]), - (select_std, ds_bold_std, [('bold_std', 'in_file')]), - (spacesource, ds_bold_std, [('space', 'space'), - ('cohort', 'cohort'), - ('resolution', 'resolution'), - ('density', 'density')]), - (select_std, ds_bold_std_ref, [('bold_std_ref', 'in_file')]), - (spacesource, ds_bold_std_ref, [('space', 'space'), - ('cohort', 'cohort'), - ('resolution', 'resolution'), - ('density', 'density')]), - (select_std, ds_bold_mask_std, [('bold_mask_std', 'in_file')]), - (spacesource, ds_bold_mask_std, [('space', 'space'), - ('cohort', 'cohort'), - ('resolution', 'resolution'), - ('density', 'density')]), - (raw_sources, ds_bold_mask_std, [('out', 'RawSources')]), - ]) - # fmt:on - if freesurfer: - select_fs_std = pe.Node( - KeySelect(fields=['bold_aseg_std', 'bold_aparc_std', 'template']), - name='select_fs_std', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - ds_bold_aseg_std = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - desc='aseg', - suffix='dseg', - compress=True, - dismiss_entities=("echo",), - ), - name='ds_bold_aseg_std', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - ds_bold_aparc_std = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - desc='aparcaseg', - suffix='dseg', - compress=True, - dismiss_entities=("echo",), - ), - name='ds_bold_aparc_std', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - # fmt:off - workflow.connect([ - (spacesource, select_fs_std, [('uid', 'key')]), - (inputnode, select_fs_std, [('bold_aseg_std', 'bold_aseg_std'), - ('bold_aparc_std', 'bold_aparc_std'), - ('template', 'template'), - ('spatial_reference', 'keys')]), - (select_fs_std, ds_bold_aseg_std, [('bold_aseg_std', 'in_file')]), - (spacesource, ds_bold_aseg_std, [('space', 'space'), - ('cohort', 'cohort'), - ('resolution', 'resolution'), - ('density', 'density')]), - (select_fs_std, ds_bold_aparc_std, [('bold_aparc_std', 'in_file')]), - (spacesource, ds_bold_aparc_std, [('space', 'space'), - ('cohort', 'cohort'), - ('resolution', 'resolution'), - ('density', 'density')]), - (inputnode, ds_bold_aseg_std, [('source_file', 'source_file')]), - (inputnode, ds_bold_aparc_std, [('source_file', 'source_file')]) - ]) - # fmt:on - if multiecho: - ds_t2star_std = pe.Node( - DerivativesDataSink( - base_directory=output_dir, - suffix='T2starmap', - compress=True, - dismiss_entities=("echo",), - **t2star_meta, - ), - name='ds_t2star_std', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - # fmt:off - workflow.connect([ - (inputnode, ds_t2star_std, [('source_file', 'source_file')]), - (select_std, ds_t2star_std, [('t2star_std', 'in_file')]), - (spacesource, ds_t2star_std, [('space', 'space'), - ('cohort', 'cohort'), - ('resolution', 'resolution'), - ('density', 'density')]), - (raw_sources, ds_t2star_std, [('out', 'RawSources')]), - ]) - # fmt:on - fs_outputs = spaces.cached.get_fs_spaces() if freesurfer and fs_outputs: from niworkflows.interfaces.surf import Path2BIDS From d378af37524973cceedc46799f0ff75a0f7f3977 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Fri, 3 Nov 2023 16:37:40 -0400 Subject: [PATCH 2/3] ENH: Restore resampling to FreeSurfer surfaces --- fmriprep/workflows/bold/base.py | 41 +++++++++-------- fmriprep/workflows/bold/outputs.py | 54 ---------------------- fmriprep/workflows/bold/resampling.py | 64 ++++++++++++++------------- 3 files changed, 54 insertions(+), 105 deletions(-) diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index e9fcca638..1722df70e 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -442,6 +442,26 @@ def init_bold_wf( (bold_std_wf, ds_bold_std_wf, [('outputnode.bold_file', 'inputnode.bold')]), ]) # fmt:skip + if config.workflow.run_reconall and freesurfer_spaces: + config.loggers.workflow.debug("Creating BOLD surface-sampling workflow.") + bold_surf_wf = init_bold_surf_wf( + mem_gb=mem_gb["resampled"], + surface_spaces=freesurfer_spaces, + medial_surface_nan=config.workflow.medial_surface_nan, + metadata=all_metadata[0], + output_dir=fmriprep_dir, + name="bold_surf_wf", + ) + bold_surf_wf.inputs.inputnode.source_file = bold_file + workflow.connect([ + (inputnode, bold_surf_wf, [ + ("subjects_dir", "inputnode.subjects_dir"), + ("subject_id", "inputnode.subject_id"), + ("fsnative2t1w_xfm", "inputnode.fsnative2t1w_xfm"), + ]), + (bold_anat_wf, bold_surf_wf, [("outputnode.bold_file", "inputnode.bold_t1w")]), + ]) # fmt:skip + bold_confounds_wf = init_bold_confs_wf( mem_gb=mem_gb["largemem"], metadata=all_metadata[0], @@ -723,27 +743,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False): ) # SURFACES ################################################################################## - # Freesurfer - if freesurfer and freesurfer_spaces: - config.loggers.workflow.debug("Creating BOLD surface-sampling workflow.") - bold_surf_wf = init_bold_surf_wf( - mem_gb=mem_gb["resampled"], - surface_spaces=freesurfer_spaces, - medial_surface_nan=config.workflow.medial_surface_nan, - name="bold_surf_wf", - ) - # fmt:off - workflow.connect([ - (inputnode, bold_surf_wf, [ - ("subjects_dir", "inputnode.subjects_dir"), - ("subject_id", "inputnode.subject_id"), - ("fsnative2t1w_xfm", "inputnode.fsnative2t1w_xfm"), - ]), - (bold_t1_trans_wf, bold_surf_wf, [("outputnode.bold_t1", "inputnode.source_file")]), - (bold_surf_wf, outputnode, [("outputnode.surfaces", "surfaces")]), - (bold_surf_wf, func_derivatives_wf, [("outputnode.target", "inputnode.surf_refs")]), - ]) - # fmt:on # CIFTI output if config.workflow.cifti_output: diff --git a/fmriprep/workflows/bold/outputs.py b/fmriprep/workflows/bold/outputs.py index e83026636..40b746700 100644 --- a/fmriprep/workflows/bold/outputs.py +++ b/fmriprep/workflows/bold/outputs.py @@ -907,15 +907,6 @@ def init_func_derivatives_wf( nonstd_spaces = set(spaces.get_nonstandard()) workflow = Workflow(name=name) - # BOLD series will generally be unmasked unless multiecho, - # as the optimal combination is undefined outside a bounded mask - masked = multiecho - t2star_meta = { - 'Units': 's', - 'EstimationReference': 'doi:10.1002/mrm.20900', - 'EstimationAlgorithm': 'monoexponential decay model', - } - inputnode = pe.Node( niu.IdentityInterface( fields=[ @@ -967,51 +958,6 @@ def init_func_derivatives_wf( ]) # fmt:on - fs_outputs = spaces.cached.get_fs_spaces() - if freesurfer and fs_outputs: - from niworkflows.interfaces.surf import Path2BIDS - - select_fs_surf = pe.Node( - KeySelect(fields=['surfaces', 'surf_kwargs']), - name='select_fs_surf', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - select_fs_surf.iterables = [('key', fs_outputs)] - select_fs_surf.inputs.surf_kwargs = [{'space': s} for s in fs_outputs] - - name_surfs = pe.MapNode( - Path2BIDS(pattern=r'(?P[lr])h.\w+'), - iterfield='in_file', - name='name_surfs', - run_without_submitting=True, - ) - - ds_bold_surfs = pe.MapNode( - DerivativesDataSink( - base_directory=output_dir, - extension=".func.gii", - TaskName=metadata.get('TaskName'), - **timing_parameters, - ), - iterfield=['in_file', 'hemi'], - name='ds_bold_surfs', - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) - # fmt:off - workflow.connect([ - (inputnode, select_fs_surf, [ - ('surf_files', 'surfaces'), - ('surf_refs', 'keys')]), - (select_fs_surf, name_surfs, [('surfaces', 'in_file')]), - (inputnode, ds_bold_surfs, [('source_file', 'source_file')]), - (select_fs_surf, ds_bold_surfs, [('surfaces', 'in_file'), - ('key', 'space')]), - (name_surfs, ds_bold_surfs, [('hemi', 'hemi')]), - ]) - # fmt:on - if freesurfer and project_goodvoxels: ds_goodvoxels_mask = pe.Node( DerivativesDataSink( diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index 1649cd0d2..30069a323 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -46,6 +46,7 @@ from ...config import DEFAULT_MEMORY_MIN_GB from ...interfaces.workbench import MetricDilate, MetricMask, MetricResample +from .outputs import prepare_timing_parameters if ty.TYPE_CHECKING: from niworkflows.utils.spaces import SpatialReferences @@ -56,6 +57,8 @@ def init_bold_surf_wf( mem_gb: float, surface_spaces: ty.List[str], medial_surface_nan: bool, + metadata: dict, + output_dir: str, name: str = "bold_surf_wf", ): """ @@ -90,6 +93,8 @@ def init_bold_surf_wf( Inputs ------ source_file + Original BOLD series + bold_t1w Motion-corrected BOLD series in T1 space subjects_dir FreeSurfer SUBJECTS_DIR @@ -109,6 +114,10 @@ def init_bold_surf_wf( from niworkflows.interfaces.nitransforms import ConcatenateXFMs from niworkflows.interfaces.surf import GiftiSetAnatomicalStructure + from fmriprep.interfaces import DerivativesDataSink + + timing_parameters = prepare_timing_parameters(metadata) + workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD time-series were resampled onto the following surfaces @@ -120,7 +129,13 @@ def init_bold_surf_wf( inputnode = pe.Node( niu.IdentityInterface( - fields=["source_file", "subject_id", "subjects_dir", "fsnative2t1w_xfm"] + fields=[ + "source_file", + "bold_t1w", + "subject_id", + "subjects_dir", + "fsnative2t1w_xfm", + ] ), name="inputnode", ) @@ -140,13 +155,6 @@ def select_target(subject_id, space): mem_gb=DEFAULT_MEMORY_MIN_GB, ) - # Rename the source file to the output space to simplify naming later - rename_src = pe.Node( - niu.Rename(format_string="%(subject)s", keep_ext=True), - name="rename_src", - run_without_submitting=True, - mem_gb=DEFAULT_MEMORY_MIN_GB, - ) itk2lta = pe.Node( ConcatenateXFMs(out_fmt="fs", inverse=True), name="itk2lta", run_without_submitting=True ) @@ -172,18 +180,20 @@ def select_target(subject_id, space): mem_gb=DEFAULT_MEMORY_MIN_GB, ) - joinnode = pe.JoinNode( - niu.IdentityInterface(fields=["surfaces", "target"]), - joinsource="itersource", - name="joinnode", - ) - - outputnode = pe.Node( - niu.IdentityInterface(fields=["surfaces", "target"]), - name="outputnode", + ds_bold_surfs = pe.MapNode( + DerivativesDataSink( + base_directory=output_dir, + extension=".func.gii", + TaskName=metadata.get('TaskName'), + **timing_parameters, + ), + iterfield=['in_file', 'hemi'], + name='ds_bold_surfs', + run_without_submitting=True, + mem_gb=DEFAULT_MEMORY_MIN_GB, ) + ds_bold_surfs.inputs.hemi = ["L", "R"] - # fmt: off workflow.connect([ (inputnode, get_fsnative, [ ("subject_id", "subject_id"), @@ -191,28 +201,22 @@ def select_target(subject_id, space): ]), (inputnode, targets, [("subject_id", "subject_id")]), (inputnode, itk2lta, [ - ("source_file", "reference"), + ("bold_t1w", "reference"), ("fsnative2t1w_xfm", "in_xfms"), ]), (get_fsnative, itk2lta, [("T1", "moving")]), (inputnode, sampler, [ ("subjects_dir", "subjects_dir"), ("subject_id", "subject_id"), + ("bold_t1w", "source_file"), ]), (itersource, targets, [("target", "space")]), - (inputnode, rename_src, [("source_file", "in_file")]), - (itersource, rename_src, [("target", "subject")]), - (rename_src, sampler, [("out_file", "source_file")]), (itk2lta, sampler, [("out_inv", "reg_file")]), (targets, sampler, [("out", "target_subject")]), - (update_metadata, joinnode, [("out_file", "surfaces")]), - (itersource, joinnode, [("target", "target")]), - (joinnode, outputnode, [ - ("surfaces", "surfaces"), - ("target", "target"), - ]), - ]) - # fmt: on + (inputnode, ds_bold_surfs, [("source_file", "source_file")]), + (itersource, ds_bold_surfs, [("target", "space")]), + (update_metadata, ds_bold_surfs, [("out_file", "in_file")]), + ]) # fmt:skip # Refine if medial vertices should be NaNs medial_nans = pe.MapNode( From c79464e9347eb8ff87047af6e4b7d74c14dd116b Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 7 Nov 2023 14:19:13 -0500 Subject: [PATCH 3/3] FIX: Moving and reference apply to the forward transform, not the final --- fmriprep/workflows/bold/resampling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index 30069a323..c51098121 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -201,10 +201,10 @@ def select_target(subject_id, space): ]), (inputnode, targets, [("subject_id", "subject_id")]), (inputnode, itk2lta, [ - ("bold_t1w", "reference"), + ("bold_t1w", "moving"), ("fsnative2t1w_xfm", "in_xfms"), ]), - (get_fsnative, itk2lta, [("T1", "moving")]), + (get_fsnative, itk2lta, [("T1", "reference")]), (inputnode, sampler, [ ("subjects_dir", "subjects_dir"), ("subject_id", "subject_id"),