From 4237da24567170d93804bb13087b3ea588728c74 Mon Sep 17 00:00:00 2001 From: Ma Feilong Date: Thu, 1 May 2025 15:25:27 -0400 Subject: [PATCH 01/11] Add a reference for the onavg template space. --- fmriprep/data/boilerplate.bib | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fmriprep/data/boilerplate.bib b/fmriprep/data/boilerplate.bib index 4cc802f25..66b9c540d 100644 --- a/fmriprep/data/boilerplate.bib +++ b/fmriprep/data/boilerplate.bib @@ -365,3 +365,17 @@ @article{patriat_improved_2017 keywords = {Motion, Correction, Methods, Rs-fMRI}, pages = {74--82}, } + +@article{onavg, + author = {Feilong, Ma and Jiahui, Guo and Gobbini, Maria Ida and Haxby, James V.}, + title = {A cortical surface template for human neuroscience}, + url = {https://www.nature.com/articles/s41592-024-02346-y}, + journal = {Nature Methods}, + issn = {1548-7105}, + number = {9}, + volume = {21}, + year = {2024}, + month = sep, + pages = {1736--1742}, + doi = {10.1038/s41592-024-02346-y}, +} From 283269a2106a0437a7095fa3f6f3b3833b412afc Mon Sep 17 00:00:00 2001 From: Ma Feilong Date: Thu, 1 May 2025 15:31:23 -0400 Subject: [PATCH 02/11] Add init_bold_surf_wb_wf, which resamples data to surfaces using connectome workbench. --- fmriprep/workflows/bold/resampling.py | 206 ++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index e83b476ba..3db4cae22 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -514,6 +514,212 @@ def _calc_lower_thr(in_stats): return workflow +def init_bold_surf_wb_wf( + space: str, + density: ty.Literal['10k', '32k', '41k'], + omp_nthreads: int, + mem_gb: float, + name: str = 'bold_surf_wb_wf', +): + """Resample BOLD time series to surface using the Connectome Workbench. + + This workflow is modified from ``init_bold_fsLR_resampling_wf``. + + Workflow Graph + .. workflow:: + :graph2use: colored + :simple_form: yes + + from fmriprep.workflows.bold.resampling import init_bold_surf_wb_wf + wf = init_bold_surf_wb_wf( + space='onavg', + density='10k', + omp_nthreads=1, + mem_gb=1, + ) + Parameters + ---------- + space : :class:`str` + Surface template space, such as ``"onavg"`` or ``"fsLR"``. + density : :class:`str` + Either ``"10k"``, ``"32k"``, or ``"41k"``, representing the number of vertices + per hemisphere. + omp_nthreads : :class:`int` + Maximum number of threads an individual process may use. + mem_gb : :class:`float` + Size of BOLD file in GB. + name : :class:`str` + Name of workflow (default: ``bold_surf_wb_wf``). + + Inputs + ------ + bold_file : :class:`str` + Path to BOLD file resampled into T1 space + white : :class:`list` of :class:`str` + Path to left and right hemisphere white matter GIFTI surfaces. + pial : :class:`list` of :class:`str` + Path to left and right hemisphere pial GIFTI surfaces. + midthickness : :class:`list` of :class:`str` + Path to left and right hemisphere midthickness GIFTI surfaces. + midthickness_resampled : :class:`list` of :class:`str` + Path to left and right hemisphere midthickness GIFTI surfaces resampled into the output + space. + sphere_reg_fsLR : :class:`list` of :class:`str` + Path to left and right hemisphere sphere.reg GIFTI surfaces, mapping from subject to fsLR. + volume_roi : :class:`str` or Undefined + Pre-calculated goodvoxels mask. Not required. + + Outputs + ------- + bold_resampled : :class:`list` of :class:`str` + Path to BOLD series resampled as functional GIFTI files in template space. + + """ + import templateflow.api as tf + from niworkflows.engine.workflows import LiterateWorkflow as Workflow + from niworkflows.interfaces.utility import KeySelect + + from fmriprep.interfaces.workbench import VolumeToSurfaceMapping + + workflow = Workflow(name=name) + + space_str = 'onavg [@onavg]' if space == 'onavg' else space + workflow.__desc__ = f"""\ +The BOLD time-series were resampled onto the {space_str} space +using the Connectome Workbench [@hcppipelines]. +""" + + inputnode = pe.Node( + niu.IdentityInterface( + fields=[ + 'bold_file', + 'white', + 'pial', + 'midthickness', + 'midthickness_resampled', + 'sphere_reg_fsLR', + # 'cortex_mask', + 'volume_roi', + ] + ), + name='inputnode', + ) + + hemisource = pe.Node( + niu.IdentityInterface(fields=['hemi']), + name='hemisource', + iterables=[('hemi', ['L', 'R'])], + ) + + joinnode = pe.JoinNode( + niu.IdentityInterface(fields=['bold_resampled']), + name='joinnode', + joinsource='hemisource', + ) + + outputnode = pe.Node( + niu.IdentityInterface(fields=['bold_resampled']), + name='outputnode', + ) + + # select white, midthickness and pial surfaces based on hemi + select_surfaces = pe.Node( + KeySelect( + fields=[ + 'white', + 'pial', + 'midthickness', + 'midthickness_resampled', + 'sphere_reg_fsLR', + 'template_sphere', + # 'cortex_mask', + # 'template_roi', + ], + keys=['L', 'R'], + ), + name='select_surfaces', + run_without_submitting=True, + ) + select_surfaces.inputs.template_sphere = [ + str(sphere) + for sphere in tf.get( + template=space, + space=('fsLR' if space != 'fsLR' else None), + density=density, + suffix='sphere', + extension='.surf.gii', + ) + ] + + # RibbonVolumeToSurfaceMapping.sh + # Line 85 thru ... + volume_to_surface = pe.Node( + VolumeToSurfaceMapping(method='ribbon-constrained'), + name='volume_to_surface', + mem_gb=mem_gb * 3, + n_procs=omp_nthreads, + ) + metric_dilate = pe.Node( + MetricDilate(distance=10, nearest=True), + name='metric_dilate', + mem_gb=1, + n_procs=omp_nthreads, + ) + # mask_native = pe.Node(MetricMask(), name='mask_native') + resample_to_template = pe.Node( + MetricResample(method='ADAP_BARY_AREA', area_surfs=True), + name='resample_to_template', + mem_gb=1, + n_procs=omp_nthreads, + ) + # ... line 89 + # mask_fsLR = pe.Node(MetricMask(), name='mask_fsLR') + + workflow.connect([ + (inputnode, select_surfaces, [ + ('white', 'white'), + ('pial', 'pial'), + ('midthickness', 'midthickness'), + ('midthickness_resampled', 'midthickness_resampled'), + ('sphere_reg_fsLR', 'sphere_reg_fsLR'), + # ('cortex_mask', 'cortex_mask'), + ]), + (hemisource, select_surfaces, [('hemi', 'key')]), + # Resample BOLD to native surface, dilate and mask + (inputnode, volume_to_surface, [ + ('bold_file', 'volume_file'), + ('volume_roi', 'volume_roi'), + ]), + (select_surfaces, volume_to_surface, [ + ('midthickness', 'surface_file'), + ('white', 'inner_surface'), + ('pial', 'outer_surface'), + ]), + (select_surfaces, metric_dilate, [('midthickness', 'surf_file')]), + # (select_surfaces, mask_native, [('cortex_mask', 'mask')]), + (volume_to_surface, metric_dilate, [('out_file', 'in_file')]), + # (metric_dilate, mask_native, [('out_file', 'in_file')]), + # Resample BOLD to fsLR and mask + (select_surfaces, resample_to_template, [ + ('sphere_reg_fsLR', 'current_sphere'), + ('template_sphere', 'new_sphere'), + ('midthickness', 'current_area'), + ('midthickness_resampled', 'new_area'), + # ('cortex_mask', 'roi_metric'), + ]), + (metric_dilate, resample_to_template, [('out_file', 'in_file')]), + # (mask_native, resample_to_template, [('out_file', 'in_file')]), + # (select_surfaces, mask_fsLR, [('template_roi', 'mask')]), + # (resample_to_fsLR, mask_fsLR, [('out_file', 'in_file')]), + # Output + # (mask_fsLR, joinnode, [('out_file', 'bold_fsLR')]), + (resample_to_template, joinnode, [('out_file', 'bold_resampled')]), + (joinnode, outputnode, [('bold_resampled', 'bold_resampled')]), + ]) # fmt:skip + + return workflow + + def init_bold_fsLR_resampling_wf( grayord_density: ty.Literal['91k', '170k'], omp_nthreads: int, From 3cda02cb6c04e18e85d1589d8d223dc630d385f3 Mon Sep 17 00:00:00 2001 From: Ma Feilong Date: Thu, 1 May 2025 15:38:55 -0400 Subject: [PATCH 03/11] Clean up code related to cortical masking. --- fmriprep/workflows/bold/resampling.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index 3db4cae22..a305caa8b 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -598,7 +598,6 @@ def init_bold_surf_wb_wf( 'midthickness', 'midthickness_resampled', 'sphere_reg_fsLR', - # 'cortex_mask', 'volume_roi', ] ), @@ -632,8 +631,6 @@ def init_bold_surf_wb_wf( 'midthickness_resampled', 'sphere_reg_fsLR', 'template_sphere', - # 'cortex_mask', - # 'template_roi', ], keys=['L', 'R'], ), @@ -665,7 +662,6 @@ def init_bold_surf_wb_wf( mem_gb=1, n_procs=omp_nthreads, ) - # mask_native = pe.Node(MetricMask(), name='mask_native') resample_to_template = pe.Node( MetricResample(method='ADAP_BARY_AREA', area_surfs=True), name='resample_to_template', @@ -673,7 +669,6 @@ def init_bold_surf_wb_wf( n_procs=omp_nthreads, ) # ... line 89 - # mask_fsLR = pe.Node(MetricMask(), name='mask_fsLR') workflow.connect([ (inputnode, select_surfaces, [ @@ -682,7 +677,6 @@ def init_bold_surf_wb_wf( ('midthickness', 'midthickness'), ('midthickness_resampled', 'midthickness_resampled'), ('sphere_reg_fsLR', 'sphere_reg_fsLR'), - # ('cortex_mask', 'cortex_mask'), ]), (hemisource, select_surfaces, [('hemi', 'key')]), # Resample BOLD to native surface, dilate and mask @@ -696,23 +690,16 @@ def init_bold_surf_wb_wf( ('pial', 'outer_surface'), ]), (select_surfaces, metric_dilate, [('midthickness', 'surf_file')]), - # (select_surfaces, mask_native, [('cortex_mask', 'mask')]), (volume_to_surface, metric_dilate, [('out_file', 'in_file')]), - # (metric_dilate, mask_native, [('out_file', 'in_file')]), # Resample BOLD to fsLR and mask (select_surfaces, resample_to_template, [ ('sphere_reg_fsLR', 'current_sphere'), ('template_sphere', 'new_sphere'), ('midthickness', 'current_area'), ('midthickness_resampled', 'new_area'), - # ('cortex_mask', 'roi_metric'), ]), (metric_dilate, resample_to_template, [('out_file', 'in_file')]), - # (mask_native, resample_to_template, [('out_file', 'in_file')]), - # (select_surfaces, mask_fsLR, [('template_roi', 'mask')]), - # (resample_to_fsLR, mask_fsLR, [('out_file', 'in_file')]), # Output - # (mask_fsLR, joinnode, [('out_file', 'bold_fsLR')]), (resample_to_template, joinnode, [('out_file', 'bold_resampled')]), (joinnode, outputnode, [('bold_resampled', 'bold_resampled')]), ]) # fmt:skip From 8c54936438969bdde2613917a5233879d2a4cdb2 Mon Sep 17 00:00:00 2001 From: Ma Feilong Date: Fri, 2 May 2025 10:56:49 -0400 Subject: [PATCH 04/11] feat: Reusable volume-to-native-surface resampling workflow. --- fmriprep/workflows/bold/resampling.py | 170 +++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index a305caa8b..d7d9528d9 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -25,6 +25,8 @@ ++++++++++++++++++++ .. autofunction:: init_bold_surf_wf +.. autofunction:: init_wb_vol_surf_wf +.. autofunction:: init_bold_surf_wb_wf .. autofunction:: init_bold_fsLR_resampling_wf .. autofunction:: init_bold_grayords_wf .. autofunction:: init_goodvoxels_bold_mask_wf @@ -514,6 +516,172 @@ def _calc_lower_thr(in_stats): return workflow +def init_wb_vol_surf_wf( + omp_nthreads: int, + mem_gb: float, + name: str = 'wb_vol_surf_wf', + dilate: bool = True, +): + """Resample volume to native surface and dilate it using the Workbench. + + This workflow performs the first two steps of surface resampling: + 1. Resample volume to native surface using "ribbon-constrained" method + 2. Dilate the resampled surface to fix small holes using nearest neighbors + + The output of this workflow can be reused to resample to multiple template + spaces and resolutions. + + Workflow Graph + .. workflow:: + :graph2use: colored + :simple_form: yes + + from fmriprep.workflows.bold.resampling import init_wb_vol_surf_wf + wf = init_wb_vol_surf_wf(omp_nthreads=1, mem_gb=1) + + + Parameters + ---------- + omp_nthreads : :class:`int` + Maximum number of threads an individual process may use. + mem_gb : :class:`float` + Size of BOLD file in GB. + name : :class:`str` + Name of workflow (default: ``wb_vol_surf_wf``). + + Inputs + ------ + bold_file : :class:`str` + Path to BOLD file resampled into T1 space + white : :class:`list` of :class:`str` + Path to left and right hemisphere white matter GIFTI surfaces. + pial : :class:`list` of :class:`str` + Path to left and right hemisphere pial GIFTI surfaces. + midthickness : :class:`list` of :class:`str` + Path to left and right hemisphere midthickness GIFTI surfaces. + volume_roi : :class:`str` or Undefined + Pre-calculated goodvoxels mask. Not required. + + Outputs + ------- + bold_fsnative : :class:`list` of :class:`str` + Path to BOLD series resampled as functional GIFTI files in native + surface space. + """ + from niworkflows.engine.workflows import LiterateWorkflow as Workflow + from niworkflows.interfaces.utility import KeySelect + + from fmriprep.interfaces.workbench import VolumeToSurfaceMapping + + workflow = Workflow(name=name) + workflow.__desc__ = """\ +The BOLD time-series were resampled onto the native surface of the subject +using the "ribbon-constrained" method +""" + workflow.__desc__ += ' and then dilated by 10 mm.' if dilate else '.' + + inputnode = pe.Node( + niu.IdentityInterface( + fields=[ + 'bold_file', + 'white', + 'pial', + 'midthickness', + 'volume_roi', + ] + ), + name='inputnode', + ) + + hemisource = pe.Node( + niu.IdentityInterface(fields=['hemi']), + name='hemisource', + iterables=[('hemi', ['L', 'R'])], + ) + + joinnode = pe.JoinNode( + niu.IdentityInterface(fields=['bold_fsnative']), + name='joinnode', + joinsource='hemisource', + ) + + outputnode = pe.Node( + niu.IdentityInterface(fields=['bold_fsnative']), + name='outputnode', + ) + + select_surfaces = pe.Node( + KeySelect( + fields=[ + 'white', + 'pial', + 'midthickness', + ], + keys=['L', 'R'], + ), + name='select_surfaces', + run_without_submitting=True, + ) + + volume_to_surface = pe.Node( + VolumeToSurfaceMapping(method='ribbon-constrained'), + name='volume_to_surface', + mem_gb=mem_gb * 3, + n_procs=omp_nthreads, + ) + if dilate: + metric_dilate = pe.Node( + MetricDilate(distance=10, nearest=True), + name='metric_dilate', + mem_gb=1, + n_procs=omp_nthreads, + ) + + workflow.connect([ + (inputnode, select_surfaces, [ + ('white', 'white'), + ('pial', 'pial'), + ('midthickness', 'midthickness'), + ]), + (hemisource, select_surfaces, [('hemi', 'key')]), + (inputnode, volume_to_surface, [ + ('bold_file', 'volume_file'), + ('volume_roi', 'volume_roi'), + ]), + (select_surfaces, volume_to_surface, [ + ('midthickness', 'surface_file'), + ('white', 'inner_surface'), + ('pial', 'outer_surface'), + ]), + ]) # fmt:skip + if dilate: + workflow.connect([ + (select_surfaces, metric_dilate, [ + ('midthickness', 'surf_file'), + ]), + (volume_to_surface, metric_dilate, [ + ('out_file', 'in_file'), + ]), + (metric_dilate, joinnode, [ + ('out_file', 'bold_fsnative'), + ]), + (joinnode, outputnode, [ + ('bold_fsnative', 'bold_fsnative'), + ]), + ]) # fmt:skip + else: + workflow.connect([ + (volume_to_surface, joinnode, [ + ('out_file', 'bold_fsnative'), + ]), + (joinnode, outputnode, [ + ('bold_fsnative', 'bold_fsnative'), + ]), + ]) # fmt:skip + + return workflow + + def init_bold_surf_wb_wf( space: str, density: ty.Literal['10k', '32k', '41k'], @@ -691,7 +859,7 @@ def init_bold_surf_wb_wf( ]), (select_surfaces, metric_dilate, [('midthickness', 'surf_file')]), (volume_to_surface, metric_dilate, [('out_file', 'in_file')]), - # Resample BOLD to fsLR and mask + # Resample BOLD to output space and mask (select_surfaces, resample_to_template, [ ('sphere_reg_fsLR', 'current_sphere'), ('template_sphere', 'new_sphere'), From 00c89448770e044c617afdaeb5059a6251a5bf23 Mon Sep 17 00:00:00 2001 From: Ma Feilong Date: Fri, 2 May 2025 12:39:32 -0400 Subject: [PATCH 05/11] feat: add init_wb_surf_surf_wf for native surface to template resampling. --- fmriprep/workflows/bold/resampling.py | 160 +++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 3 deletions(-) diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index d7d9528d9..b8ac8be34 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -26,6 +26,7 @@ .. autofunction:: init_bold_surf_wf .. autofunction:: init_wb_vol_surf_wf +.. autofunction:: init_wb_surf_surf_wf .. autofunction:: init_bold_surf_wb_wf .. autofunction:: init_bold_fsLR_resampling_wf .. autofunction:: init_bold_grayords_wf @@ -595,14 +596,14 @@ def init_wb_vol_surf_wf( hemisource = pe.Node( niu.IdentityInterface(fields=['hemi']), - name='hemisource', + name='hemisource_vol_surf', iterables=[('hemi', ['L', 'R'])], ) joinnode = pe.JoinNode( niu.IdentityInterface(fields=['bold_fsnative']), - name='joinnode', - joinsource='hemisource', + name='joinnode_vol_surf', + joinsource='hemisource_vol_surf', ) outputnode = pe.Node( @@ -682,6 +683,159 @@ def init_wb_vol_surf_wf( return workflow +def init_wb_surf_surf_wf( + space: str, + density: ty.Literal['10k', '32k', '41k'], + omp_nthreads: int, + mem_gb: float, + name: str = 'wb_surf_surf_wf', +): + """Resample BOLD time series from native surface to template surface. + + This workflow performs the third step of surface resampling: + 3. Resample the native surface to the template surface using the + Connectome Workbench + + Workflow Graph + .. workflow:: + :graph2use: colored + :simple_form: yes + + from fmriprep.workflows.bold.resampling import init_wb_surf_surf_wf + wf = init_wb_surf_surf_wf( + space='fsLR', + density='32k', + omp_nthreads=1, + mem_gb=1, + ) + + Parameters + ---------- + space : :class:`str` + Surface template space, such as ``"onavg"`` or ``"fsLR"``. + density : :class:`str` + Either ``"10k"``, ``"32k"``, or ``"41k"``, representing the number of + vertices per hemisphere. + omp_nthreads : :class:`int` + Maximum number of threads an individual process may use. + mem_gb : :class:`float` + Size of BOLD file in GB. + name : :class:`str` + Name of workflow (default: ``wb_surf_surf_wf``). + + Inputs + ------ + bold_fsnative : :class:`list` of :class:`str` + Path to BOLD series resampled as functional GIFTI files in native + surface space. + midthickness : :class:`list` of :class:`str` + Path to left and right hemisphere midthickness GIFTI surfaces. + midthickness_resampled : :class:`list` of :class:`str` + Path to left and right hemisphere midthickness GIFTI surfaces resampled + into the output space. + sphere_reg_fsLR : :class:`list` of :class:`str` + Path to left and right hemisphere sphere.reg GIFTI surfaces, mapping + from subject to fsLR. + + Outputs + ------- + bold_resampled : :class:`list` of :class:`str` + Path to BOLD series resampled as functional GIFTI files in the output + template space. + """ + import templateflow.api as tf + from niworkflows.engine.workflows import LiterateWorkflow as Workflow + from niworkflows.interfaces.utility import KeySelect + + workflow = Workflow(name=name) + + inputnode = pe.Node( + niu.IdentityInterface( + fields=[ + 'bold_fsnative', + 'midthickness', + 'midthickness_resampled', + 'sphere_reg_fsLR', + ] + ), + name='inputnode', + ) + + hemisource = pe.Node( + niu.IdentityInterface(fields=['hemi']), + name=name + '_hemisource_surf_surf', + iterables=[('hemi', ['L', 'R'])], + ) + + joinnode = pe.JoinNode( + niu.IdentityInterface(fields=['bold_resampled']), + name=name + '_joinnode_surf_surf', + joinsource=name + '_hemisource_surf_surf', + ) + + outputnode = pe.Node( + niu.IdentityInterface(fields=['bold_resampled']), + name='outputnode', + ) + + select_surfaces = pe.Node( + KeySelect( + fields=[ + 'bold_fsnative', + 'midthickness', + 'midthickness_resampled', + 'sphere_reg_fsLR', + 'template_sphere', + ], + keys=['L', 'R'], + ), + name=name + '_select_surfaces', + run_without_submitting=True, + ) + select_surfaces.inputs.template_sphere = [ + str(sphere) + for sphere in tf.get( + template=space, + space=('fsLR' if space != 'fsLR' else None), + density=density, + suffix='sphere', + extension='.surf.gii', + ) + ] + + resample_to_template = pe.Node( + MetricResample(method='ADAP_BARY_AREA', area_surfs=True), + name=name + '_resample_to_template', + mem_gb=1, + n_procs=omp_nthreads, + ) + + workflow.connect([ + (inputnode, select_surfaces, [ + ('bold_fsnative', 'bold_fsnative'), + ('midthickness', 'midthickness'), + ('midthickness_resampled', 'midthickness_resampled'), + ('sphere_reg_fsLR', 'sphere_reg_fsLR'), + ]), + (hemisource, select_surfaces, [('hemi', 'key')]), + (select_surfaces, resample_to_template, [ + ('bold_fsnative', 'in_file'), + ('sphere_reg_fsLR', 'current_sphere'), + ('template_sphere', 'new_sphere'), + ('midthickness', 'current_area'), + ('midthickness_resampled', 'new_area'), + ]), + (resample_to_template, joinnode, [ + ('out_file', 'bold_resampled'), + ]), + (joinnode, outputnode, [ + ('bold_resampled', 'bold_resampled'), + ]), + ]) # fmt:skip + + return workflow + + def init_bold_surf_wb_wf( space: str, density: ty.Literal['10k', '32k', '41k'], From 2c140eaa2775264ba414423ede59df45306b62ac Mon Sep 17 00:00:00 2001 From: Ma Feilong Date: Fri, 2 May 2025 12:41:50 -0400 Subject: [PATCH 06/11] Remove init_bold_surf_wb_wf as it's replaced by init_wb_vol_surf_wf and init_wb_surf_surf_wf. --- fmriprep/workflows/bold/resampling.py | 194 -------------------------- 1 file changed, 194 deletions(-) diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index b8ac8be34..6ff8a6d11 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -27,7 +27,6 @@ .. autofunction:: init_bold_surf_wf .. autofunction:: init_wb_vol_surf_wf .. autofunction:: init_wb_surf_surf_wf -.. autofunction:: init_bold_surf_wb_wf .. autofunction:: init_bold_fsLR_resampling_wf .. autofunction:: init_bold_grayords_wf .. autofunction:: init_goodvoxels_bold_mask_wf @@ -836,199 +835,6 @@ def init_wb_surf_surf_wf( return workflow -def init_bold_surf_wb_wf( - space: str, - density: ty.Literal['10k', '32k', '41k'], - omp_nthreads: int, - mem_gb: float, - name: str = 'bold_surf_wb_wf', -): - """Resample BOLD time series to surface using the Connectome Workbench. - - This workflow is modified from ``init_bold_fsLR_resampling_wf``. - - Workflow Graph - .. workflow:: - :graph2use: colored - :simple_form: yes - - from fmriprep.workflows.bold.resampling import init_bold_surf_wb_wf - wf = init_bold_surf_wb_wf( - space='onavg', - density='10k', - omp_nthreads=1, - mem_gb=1, - ) - Parameters - ---------- - space : :class:`str` - Surface template space, such as ``"onavg"`` or ``"fsLR"``. - density : :class:`str` - Either ``"10k"``, ``"32k"``, or ``"41k"``, representing the number of vertices - per hemisphere. - omp_nthreads : :class:`int` - Maximum number of threads an individual process may use. - mem_gb : :class:`float` - Size of BOLD file in GB. - name : :class:`str` - Name of workflow (default: ``bold_surf_wb_wf``). - - Inputs - ------ - bold_file : :class:`str` - Path to BOLD file resampled into T1 space - white : :class:`list` of :class:`str` - Path to left and right hemisphere white matter GIFTI surfaces. - pial : :class:`list` of :class:`str` - Path to left and right hemisphere pial GIFTI surfaces. - midthickness : :class:`list` of :class:`str` - Path to left and right hemisphere midthickness GIFTI surfaces. - midthickness_resampled : :class:`list` of :class:`str` - Path to left and right hemisphere midthickness GIFTI surfaces resampled into the output - space. - sphere_reg_fsLR : :class:`list` of :class:`str` - Path to left and right hemisphere sphere.reg GIFTI surfaces, mapping from subject to fsLR. - volume_roi : :class:`str` or Undefined - Pre-calculated goodvoxels mask. Not required. - - Outputs - ------- - bold_resampled : :class:`list` of :class:`str` - Path to BOLD series resampled as functional GIFTI files in template space. - - """ - import templateflow.api as tf - from niworkflows.engine.workflows import LiterateWorkflow as Workflow - from niworkflows.interfaces.utility import KeySelect - - from fmriprep.interfaces.workbench import VolumeToSurfaceMapping - - workflow = Workflow(name=name) - - space_str = 'onavg [@onavg]' if space == 'onavg' else space - workflow.__desc__ = f"""\ -The BOLD time-series were resampled onto the {space_str} space -using the Connectome Workbench [@hcppipelines]. -""" - - inputnode = pe.Node( - niu.IdentityInterface( - fields=[ - 'bold_file', - 'white', - 'pial', - 'midthickness', - 'midthickness_resampled', - 'sphere_reg_fsLR', - 'volume_roi', - ] - ), - name='inputnode', - ) - - hemisource = pe.Node( - niu.IdentityInterface(fields=['hemi']), - name='hemisource', - iterables=[('hemi', ['L', 'R'])], - ) - - joinnode = pe.JoinNode( - niu.IdentityInterface(fields=['bold_resampled']), - name='joinnode', - joinsource='hemisource', - ) - - outputnode = pe.Node( - niu.IdentityInterface(fields=['bold_resampled']), - name='outputnode', - ) - - # select white, midthickness and pial surfaces based on hemi - select_surfaces = pe.Node( - KeySelect( - fields=[ - 'white', - 'pial', - 'midthickness', - 'midthickness_resampled', - 'sphere_reg_fsLR', - 'template_sphere', - ], - keys=['L', 'R'], - ), - name='select_surfaces', - run_without_submitting=True, - ) - select_surfaces.inputs.template_sphere = [ - str(sphere) - for sphere in tf.get( - template=space, - space=('fsLR' if space != 'fsLR' else None), - density=density, - suffix='sphere', - extension='.surf.gii', - ) - ] - - # RibbonVolumeToSurfaceMapping.sh - # Line 85 thru ... - volume_to_surface = pe.Node( - VolumeToSurfaceMapping(method='ribbon-constrained'), - name='volume_to_surface', - mem_gb=mem_gb * 3, - n_procs=omp_nthreads, - ) - metric_dilate = pe.Node( - MetricDilate(distance=10, nearest=True), - name='metric_dilate', - mem_gb=1, - n_procs=omp_nthreads, - ) - resample_to_template = pe.Node( - MetricResample(method='ADAP_BARY_AREA', area_surfs=True), - name='resample_to_template', - mem_gb=1, - n_procs=omp_nthreads, - ) - # ... line 89 - - workflow.connect([ - (inputnode, select_surfaces, [ - ('white', 'white'), - ('pial', 'pial'), - ('midthickness', 'midthickness'), - ('midthickness_resampled', 'midthickness_resampled'), - ('sphere_reg_fsLR', 'sphere_reg_fsLR'), - ]), - (hemisource, select_surfaces, [('hemi', 'key')]), - # Resample BOLD to native surface, dilate and mask - (inputnode, volume_to_surface, [ - ('bold_file', 'volume_file'), - ('volume_roi', 'volume_roi'), - ]), - (select_surfaces, volume_to_surface, [ - ('midthickness', 'surface_file'), - ('white', 'inner_surface'), - ('pial', 'outer_surface'), - ]), - (select_surfaces, metric_dilate, [('midthickness', 'surf_file')]), - (volume_to_surface, metric_dilate, [('out_file', 'in_file')]), - # Resample BOLD to output space and mask - (select_surfaces, resample_to_template, [ - ('sphere_reg_fsLR', 'current_sphere'), - ('template_sphere', 'new_sphere'), - ('midthickness', 'current_area'), - ('midthickness_resampled', 'new_area'), - ]), - (metric_dilate, resample_to_template, [('out_file', 'in_file')]), - # Output - (resample_to_template, joinnode, [('out_file', 'bold_resampled')]), - (joinnode, outputnode, [('bold_resampled', 'bold_resampled')]), - ]) # fmt:skip - - return workflow - - def init_bold_fsLR_resampling_wf( grayord_density: ty.Literal['91k', '170k'], omp_nthreads: int, From 834bdf60e1b82c32460c9faec4dd9902067e68de Mon Sep 17 00:00:00 2001 From: Ma Feilong Date: Fri, 2 May 2025 14:19:23 -0400 Subject: [PATCH 07/11] feat: Resample to surface template spaces using the Connectome Workbench. --- fmriprep/workflows/bold/base.py | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index 18eba4426..10c50a328 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -494,6 +494,115 @@ def init_bold_wf( ]), ]) # fmt:skip + surf_std = spaces.get_standard(dim=(2,)) + if surf_std: # Probably ensure reconall and msmsulc are run + workflow.__postdesc__ += """\ +Non-gridded (surface) resamplings were performed using the Connectome +Workbench. +""" + config.loggers.workflow.debug('Creating BOLD surface workbench resampling workflow.') + from smriprep.workflows.surfaces import init_resample_surfaces_wb_wf + + from .resampling import ( + init_goodvoxels_bold_mask_wf, + init_wb_surf_surf_wf, + init_wb_vol_surf_wf, + ) + + wb_vol_surf_wf = init_wb_vol_surf_wf( + name='wb_vol_surf_wf', + omp_nthreads=omp_nthreads, + mem_gb=mem_gb['resampled'], + dilate=True, + ) + workflow.connect([ + (inputnode, wb_vol_surf_wf,[ + ('white', 'inputnode.white'), + ('pial', 'inputnode.pial'), + ('midthickness', 'inputnode.midthickness'), + ]), + (bold_anat_wf, wb_vol_surf_wf, [ + ('outputnode.bold_file', 'inputnode.bold_file'), + ]), + ]) # fmt:skip + + if config.workflow.project_goodvoxels: + goodvoxels_bold_mask_wf = init_goodvoxels_bold_mask_wf(mem_gb['resampled']) + + workflow.connect([ + (inputnode, goodvoxels_bold_mask_wf, [('anat_ribbon', 'inputnode.anat_ribbon')]), + (bold_anat_wf, goodvoxels_bold_mask_wf, [ + ('outputnode.bold_file', 'inputnode.bold_file'), + ]), + (goodvoxels_bold_mask_wf, wb_vol_surf_wf, [ + ('outputnode.goodvoxels_mask', 'inputnode.volume_roi'), + ]), + ]) # fmt:skip + workflow.__desc__ += """\ +A "goodvoxels" mask was applied during volume-to-surface sampling, excluding +voxels whose time-series have a locally high coefficient of variation. +""" + + for ref_ in surf_std: + space, den = ref_.space, ref_.spec['den'] + + resample_surfaces_wb_wf = init_resample_surfaces_wb_wf( + name=f'resample_surfaces_wb_wf_{space}_{den}', + surfaces=['midthickness'], + space=space, + density=den, + ) + + wb_surf_surf_wf = init_wb_surf_surf_wf( + space=space, + density=den, + name=f'wb_surf_surf_wf_{space}_{den}', + omp_nthreads=omp_nthreads, + mem_gb=mem_gb['resampled'], + ) + + ds_bold_surf_wb = pe.Node( + DerivativesDataSink( + base_directory=fmriprep_dir, + hemi=['L', 'R'], + dismiss_entities=dismiss_echo(), + space=space, + density=den, + suffix='bold', + # compress=False, # not sure if needed for gii. + TaskName=all_metadata[0].get('TaskName'), + extension='.func.gii', + **prepare_timing_parameters(all_metadata[0]), + ), + iterfield=('in_file', 'hemi'), + name=f'ds_bold_surf_wb_{space}_{den}', + run_without_submitting=True, + ) + ds_bold_surf_wb.inputs.source_file = bold_file + + workflow.connect([ + (inputnode, resample_surfaces_wb_wf, [ + ('midthickness', 'inputnode.midthickness'), + ('sphere_reg_fsLR', 'inputnode.sphere_reg_fsLR'), + ]), + (wb_vol_surf_wf, wb_surf_surf_wf, [ + ('outputnode.bold_fsnative', 'inputnode.bold_fsnative'), + ]), + (inputnode, wb_surf_surf_wf, [ + ('midthickness', 'inputnode.midthickness'), + # # TODO: check inputnode.midthickness_resampled + # ('midthickness_resampled', 'inputnode.midthickness_resampled'), + ('sphere_reg_fsLR', 'inputnode.sphere_reg_fsLR'), + ]), + (resample_surfaces_wb_wf, wb_surf_surf_wf, [ + ('outputnode.midthickness_resampled', 'inputnode.midthickness_resampled'), + ]), + (wb_surf_surf_wf, ds_bold_surf_wb, [ + ('outputnode.bold_resampled', 'in_file'), + # TODO: json metadata? + ]), + ]) # fmt:skip + if config.workflow.run_reconall and freesurfer_spaces: workflow.__postdesc__ += """\ Non-gridded (surface) resamplings were performed using `mri_vol2surf` From 2758c819d3471dfe83acf5a450542f12ce41afaf Mon Sep 17 00:00:00 2001 From: mathiasg Date: Fri, 12 Sep 2025 17:12:49 -0400 Subject: [PATCH 08/11] enh: update following smriprep changes --- fmriprep/workflows/base.py | 2 +- fmriprep/workflows/bold/base.py | 117 ++++++++++++-------------- fmriprep/workflows/bold/resampling.py | 71 ++++++++-------- 3 files changed, 89 insertions(+), 101 deletions(-) diff --git a/fmriprep/workflows/base.py b/fmriprep/workflows/base.py index 5c18496d2..bda11647f 100644 --- a/fmriprep/workflows/base.py +++ b/fmriprep/workflows/base.py @@ -534,7 +534,7 @@ def init_single_subject_wf( ) resample_surfaces_wf = init_resample_surfaces_wf( surfaces=['white', 'pial', 'midthickness'], - grayord_density=config.workflow.cifti_output, + density=config.workflow.cifti_output, ) ds_grayord_metrics_wf = init_ds_grayord_metrics_wf( bids_root=bids_root, diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index 10c50a328..aa656ccef 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -184,8 +184,9 @@ def init_bold_wf( return config.loggers.workflow.debug( - f'Creating bold processing workflow for <{bold_file}> ({mem_gb["filesize"]:.2f} GB / {nvols} TRs). ' - f'Memory resampled/largemem={mem_gb["resampled"]:.2f}/{mem_gb["largemem"]:.2f} GB.' + f'Creating bold processing workflow for <{bold_file}> ({mem_gb["filesize"]:.2f} GB / ' + f'{nvols} TRs). Memory resampled/largemem={mem_gb["resampled"]:.2f}/' + f'{mem_gb["largemem"]:.2f} GB.' ) workflow = Workflow(name=_get_wf_name(bold_file, 'bold')) @@ -494,23 +495,54 @@ def init_bold_wf( ]), ]) # fmt:skip - surf_std = spaces.get_standard(dim=(2,)) - if surf_std: # Probably ensure reconall and msmsulc are run + # Goodvoxels mask might be needed in any surface resampling + if config.workflow.project_goodvoxels: + from .resampling import init_goodvoxels_bold_mask_wf + + goodvoxels_bold_mask_wf = init_goodvoxels_bold_mask_wf(mem_gb['resampled']) + ds_goodvoxels_mask = pe.Node( + DerivativesDataSink( + base_directory=fmriprep_dir, + dismiss_entities=dismiss_echo(), + compress=True, + space='T1w', + desc='goodvoxels', + suffix='mask', + ), + name='ds_goodvoxels_mask', + run_without_submitting=True, + ) + ds_goodvoxels_mask.inputs.source_file = bold_file + + workflow.__desc__ += """\ +A "goodvoxels" mask was applied during volume-to-surface sampling, excluding +voxels whose time-series have a locally high coefficient of variation. +""" + workflow.connect([ + (inputnode, goodvoxels_bold_mask_wf, [('anat_ribbon', 'inputnode.anat_ribbon')]), + (bold_anat_wf, goodvoxels_bold_mask_wf, [ + ('outputnode.bold_file', 'inputnode.bold_file'), + ]), + (goodvoxels_bold_mask_wf, ds_goodvoxels_mask, [ + ('outputnode.goodvoxels_mask', 'in_file'), + ]), + ]) # fmt:skip + + surf_std = spaces.get_nonstandard(dim=(2,)) + if surf_std and config.workflow.run_reconall and config.workflow.cifti_output: workflow.__postdesc__ += """\ Non-gridded (surface) resamplings were performed using the Connectome Workbench. """ config.loggers.workflow.debug('Creating BOLD surface workbench resampling workflow.') - from smriprep.workflows.surfaces import init_resample_surfaces_wb_wf + from smriprep.workflows.surfaces import init_resample_surfaces_wf from .resampling import ( - init_goodvoxels_bold_mask_wf, init_wb_surf_surf_wf, init_wb_vol_surf_wf, ) wb_vol_surf_wf = init_wb_vol_surf_wf( - name='wb_vol_surf_wf', omp_nthreads=omp_nthreads, mem_gb=mem_gb['resampled'], dilate=True, @@ -530,33 +562,28 @@ def init_bold_wf( goodvoxels_bold_mask_wf = init_goodvoxels_bold_mask_wf(mem_gb['resampled']) workflow.connect([ - (inputnode, goodvoxels_bold_mask_wf, [('anat_ribbon', 'inputnode.anat_ribbon')]), - (bold_anat_wf, goodvoxels_bold_mask_wf, [ - ('outputnode.bold_file', 'inputnode.bold_file'), - ]), (goodvoxels_bold_mask_wf, wb_vol_surf_wf, [ ('outputnode.goodvoxels_mask', 'inputnode.volume_roi'), ]), ]) # fmt:skip - workflow.__desc__ += """\ -A "goodvoxels" mask was applied during volume-to-surface sampling, excluding -voxels whose time-series have a locally high coefficient of variation. -""" for ref_ in surf_std: - space, den = ref_.space, ref_.spec['den'] - - resample_surfaces_wb_wf = init_resample_surfaces_wb_wf( - name=f'resample_surfaces_wb_wf_{space}_{den}', + template = ref_.space + density = ref_.spec.get('density') or ref_.spec.get('den') or None + if density is None: + config.loggers.warning(f'Cannot resample {ref_} without density specified.') + continue + + resample_surfaces_wb_wf = init_resample_surfaces_wf( + name=f'resample_surfaces_wb_wf_{template}_{density}', surfaces=['midthickness'], - space=space, - density=den, + template=template, + density=density, ) wb_surf_surf_wf = init_wb_surf_surf_wf( - space=space, - density=den, - name=f'wb_surf_surf_wf_{space}_{den}', + template=template, + density=density, omp_nthreads=omp_nthreads, mem_gb=mem_gb['resampled'], ) @@ -566,16 +593,15 @@ def init_bold_wf( base_directory=fmriprep_dir, hemi=['L', 'R'], dismiss_entities=dismiss_echo(), - space=space, - density=den, + space=template, + density=density, suffix='bold', - # compress=False, # not sure if needed for gii. TaskName=all_metadata[0].get('TaskName'), extension='.func.gii', **prepare_timing_parameters(all_metadata[0]), ), iterfield=('in_file', 'hemi'), - name=f'ds_bold_surf_wb_{space}_{den}', + name=f'ds_bold_surf_wb_{template}_{density}', run_without_submitting=True, ) ds_bold_surf_wb.inputs.source_file = bold_file @@ -590,12 +616,10 @@ def init_bold_wf( ]), (inputnode, wb_surf_surf_wf, [ ('midthickness', 'inputnode.midthickness'), - # # TODO: check inputnode.midthickness_resampled - # ('midthickness_resampled', 'inputnode.midthickness_resampled'), ('sphere_reg_fsLR', 'inputnode.sphere_reg_fsLR'), ]), (resample_surfaces_wb_wf, wb_surf_surf_wf, [ - ('outputnode.midthickness_resampled', 'inputnode.midthickness_resampled'), + ('outputnode.midthickness', 'inputnode.midthickness_resampled'), ]), (wb_surf_surf_wf, ds_bold_surf_wb, [ ('outputnode.bold_resampled', 'in_file'), @@ -650,7 +674,6 @@ def init_bold_wf( from .resampling import ( init_bold_fsLR_resampling_wf, init_bold_grayords_wf, - init_goodvoxels_bold_mask_wf, ) bold_MNI6_wf = init_bold_volumetric_resample_wf( @@ -669,42 +692,12 @@ def init_bold_wf( ) if config.workflow.project_goodvoxels: - goodvoxels_bold_mask_wf = init_goodvoxels_bold_mask_wf(mem_gb['resampled']) - workflow.connect([ - (inputnode, goodvoxels_bold_mask_wf, [('anat_ribbon', 'inputnode.anat_ribbon')]), - (bold_anat_wf, goodvoxels_bold_mask_wf, [ - ('outputnode.bold_file', 'inputnode.bold_file'), - ]), - ]) # fmt:skip - - ds_goodvoxels_mask = pe.Node( - DerivativesDataSink( - base_directory=fmriprep_dir, - dismiss_entities=dismiss_echo(), - compress=True, - space='T1w', - desc='goodvoxels', - suffix='mask', - ), - name='ds_goodvoxels_mask', - run_without_submitting=True, - ) - ds_goodvoxels_mask.inputs.source_file = bold_file - workflow.connect([ - (goodvoxels_bold_mask_wf, ds_goodvoxels_mask, [ - ('outputnode.goodvoxels_mask', 'in_file'), - ]), (goodvoxels_bold_mask_wf, bold_fsLR_resampling_wf, [ ('outputnode.goodvoxels_mask', 'inputnode.volume_roi'), ]), ]) # fmt:skip - bold_fsLR_resampling_wf.__desc__ += """\ -A "goodvoxels" mask was applied during volume-to-surface sampling in fsLR space, -excluding voxels whose time-series have a locally high coefficient of variation. -""" - bold_grayords_wf = init_bold_grayords_wf( grayord_density=config.workflow.cifti_output, mem_gb=1, diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index 6ff8a6d11..1e95e4e20 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -629,13 +629,13 @@ def init_wb_vol_surf_wf( mem_gb=mem_gb * 3, n_procs=omp_nthreads, ) - if dilate: - metric_dilate = pe.Node( - MetricDilate(distance=10, nearest=True), - name='metric_dilate', - mem_gb=1, - n_procs=omp_nthreads, - ) + + metric_dilate = pe.Node( + MetricDilate(distance=10, nearest=True), + name='metric_dilate', + mem_gb=1, + n_procs=omp_nthreads, + ) workflow.connect([ (inputnode, select_surfaces, [ @@ -653,41 +653,29 @@ def init_wb_vol_surf_wf( ('white', 'inner_surface'), ('pial', 'outer_surface'), ]), + (joinnode, outputnode, [('bold_fsnative', 'bold_fsnative')]), ]) # fmt:skip + if dilate: workflow.connect([ - (select_surfaces, metric_dilate, [ - ('midthickness', 'surf_file'), - ]), - (volume_to_surface, metric_dilate, [ - ('out_file', 'in_file'), - ]), - (metric_dilate, joinnode, [ - ('out_file', 'bold_fsnative'), - ]), - (joinnode, outputnode, [ - ('bold_fsnative', 'bold_fsnative'), - ]), + (select_surfaces, metric_dilate, [('midthickness', 'surf_file')]), + (volume_to_surface, metric_dilate, [('out_file', 'in_file')]), + (metric_dilate, joinnode, [('out_file', 'bold_fsnative')]), ]) # fmt:skip else: - workflow.connect([ - (volume_to_surface, joinnode, [ - ('out_file', 'bold_fsnative'), - ]), - (joinnode, outputnode, [ - ('bold_fsnative', 'bold_fsnative'), - ]), - ]) # fmt:skip + workflow.connect(volume_to_surface, 'out_file', joinnode, 'bold_fsnative') return workflow def init_wb_surf_surf_wf( - space: str, - density: ty.Literal['10k', '32k', '41k'], + *, + space: str | None = 'fsLR', + template: str, + density: str, omp_nthreads: int, mem_gb: float, - name: str = 'wb_surf_surf_wf', + name: str | None = None, ): """Resample BOLD time series from native surface to template surface. @@ -710,7 +698,11 @@ def init_wb_surf_surf_wf( Parameters ---------- - space : :class:`str` + space : :class:`str` or :obj:`None` + The registration space for which there are both subject and template + registration spheres. + If ``None``, the template space is used. + template : :class:`str` Surface template space, such as ``"onavg"`` or ``"fsLR"``. density : :class:`str` Either ``"10k"``, ``"32k"``, or ``"41k"``, representing the number of @@ -746,6 +738,8 @@ def init_wb_surf_surf_wf( from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.utility import KeySelect + if name is None: + name = f'wb_surf_native_{template}_{density}_wf' workflow = Workflow(name=name) inputnode = pe.Node( @@ -760,16 +754,17 @@ def init_wb_surf_surf_wf( name='inputnode', ) + # Iterables / JoinNode should be unique to avoid overloading hemisource = pe.Node( niu.IdentityInterface(fields=['hemi']), - name=name + '_hemisource_surf_surf', + name=f'hemisource_surf_surf_{template}_{density}', iterables=[('hemi', ['L', 'R'])], ) joinnode = pe.JoinNode( niu.IdentityInterface(fields=['bold_resampled']), - name=name + '_joinnode_surf_surf', - joinsource=name + '_hemisource_surf_surf', + name=f'joinnode_surf_surf_{template}_{density}', + joinsource=f'hemisource_surf_surf_{template}_{density}', ) outputnode = pe.Node( @@ -788,14 +783,14 @@ def init_wb_surf_surf_wf( ], keys=['L', 'R'], ), - name=name + '_select_surfaces', + name='select_surfaces', run_without_submitting=True, ) select_surfaces.inputs.template_sphere = [ str(sphere) for sphere in tf.get( - template=space, - space=('fsLR' if space != 'fsLR' else None), + template=template, + space=space if space != template else None, density=density, suffix='sphere', extension='.surf.gii', @@ -804,7 +799,7 @@ def init_wb_surf_surf_wf( resample_to_template = pe.Node( MetricResample(method='ADAP_BARY_AREA', area_surfs=True), - name=name + '_resample_to_template', + name='resample_to_template', mem_gb=1, n_procs=omp_nthreads, ) From ae177e6892d58a4d4334521fb0bf0877b34d576c Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Mon, 15 Sep 2025 14:33:26 -0400 Subject: [PATCH 09/11] fix: use fslr density over grayord --- fmriprep/workflows/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fmriprep/workflows/base.py b/fmriprep/workflows/base.py index bda11647f..5264df25a 100644 --- a/fmriprep/workflows/base.py +++ b/fmriprep/workflows/base.py @@ -532,9 +532,10 @@ def init_single_subject_wf( grayord_density=config.workflow.cifti_output, omp_nthreads=omp_nthreads, ) + fslr_density = '32k' if config.workflow.cifti_output == '91k' else '59k' resample_surfaces_wf = init_resample_surfaces_wf( surfaces=['white', 'pial', 'midthickness'], - density=config.workflow.cifti_output, + density=fslr_density, ) ds_grayord_metrics_wf = init_ds_grayord_metrics_wf( bids_root=bids_root, @@ -547,7 +548,7 @@ def init_single_subject_wf( surfaces=['white', 'pial', 'midthickness'], entities={ 'space': 'fsLR', - 'density': '32k' if config.workflow.cifti_output == '91k' else '59k', + 'density': fslr_density, }, name='ds_fsLR_surfaces_wf', ) From 8d21c00fcc886421c7bf8fdb7115731283aa9fd3 Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Mon, 15 Sep 2025 17:07:48 -0400 Subject: [PATCH 10/11] fix: add to postdesc --- fmriprep/workflows/bold/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index aa656ccef..1470c79f6 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -514,7 +514,7 @@ def init_bold_wf( ) ds_goodvoxels_mask.inputs.source_file = bold_file - workflow.__desc__ += """\ + workflow.__postdesc__ += """\ A "goodvoxels" mask was applied during volume-to-surface sampling, excluding voxels whose time-series have a locally high coefficient of variation. """ From d20a8d17ff9aafb3a92c853077a8e02e6d00bfe3 Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Wed, 17 Sep 2025 11:08:11 -0400 Subject: [PATCH 11/11] fix: remove duplicated workflow, fix space query --- fmriprep/workflows/bold/base.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index 1470c79f6..521041247 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -528,7 +528,7 @@ def init_bold_wf( ]), ]) # fmt:skip - surf_std = spaces.get_nonstandard(dim=(2,)) + surf_std = spaces.get_standard(dim=(2,)) if surf_std and config.workflow.run_reconall and config.workflow.cifti_output: workflow.__postdesc__ += """\ Non-gridded (surface) resamplings were performed using the Connectome @@ -559,8 +559,6 @@ def init_bold_wf( ]) # fmt:skip if config.workflow.project_goodvoxels: - goodvoxels_bold_mask_wf = init_goodvoxels_bold_mask_wf(mem_gb['resampled']) - workflow.connect([ (goodvoxels_bold_mask_wf, wb_vol_surf_wf, [ ('outputnode.goodvoxels_mask', 'inputnode.volume_roi'),