Skip to content

Commit

Permalink
ENH: Restore resampling to FreeSurfer surfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
effigies committed Nov 3, 2023
1 parent ce7cbfa commit 1ebc6c3
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 105 deletions.
41 changes: 20 additions & 21 deletions fmriprep/workflows/bold/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(

Check warning on line 447 in fmriprep/workflows/bold/base.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/workflows/bold/base.py#L445-L447

Added lines #L445 - L447 were not covered by tests
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([

Check warning on line 456 in fmriprep/workflows/bold/base.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/workflows/bold/base.py#L455-L456

Added lines #L455 - L456 were not covered by tests
(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],
Expand Down Expand Up @@ -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:
Expand Down
54 changes: 0 additions & 54 deletions fmriprep/workflows/bold/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[
Expand Down Expand Up @@ -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<hemi>[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(
Expand Down
64 changes: 34 additions & 30 deletions fmriprep/workflows/bold/resampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
):
"""
Expand Down Expand Up @@ -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
Expand All @@ -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

Check warning on line 117 in fmriprep/workflows/bold/resampling.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/workflows/bold/resampling.py#L117

Added line #L117 was not covered by tests

timing_parameters = prepare_timing_parameters(metadata)

Check warning on line 119 in fmriprep/workflows/bold/resampling.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/workflows/bold/resampling.py#L119

Added line #L119 was not covered by tests

workflow = Workflow(name=name)
workflow.__desc__ = """\
The BOLD time-series were resampled onto the following surfaces
Expand All @@ -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",
)
Expand All @@ -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
)
Expand All @@ -172,47 +180,43 @@ 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(

Check warning on line 183 in fmriprep/workflows/bold/resampling.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/workflows/bold/resampling.py#L183

Added line #L183 was not covered by tests
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"]

Check warning on line 195 in fmriprep/workflows/bold/resampling.py

View check run for this annotation

Codecov / codecov/patch

fmriprep/workflows/bold/resampling.py#L195

Added line #L195 was not covered by tests

# fmt: off
workflow.connect([
(inputnode, get_fsnative, [
("subject_id", "subject_id"),
("subjects_dir", "subjects_dir")
]),
(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(
Expand Down

0 comments on commit 1ebc6c3

Please sign in to comment.