Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

ENH: Separate surfaces and morphometrics into standalone outputs #359

Merged
merged 4 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions smriprep/interfaces/surf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
SimpleInterface,
File,
isdefined,
InputMultiObject,
traits,
)


Expand Down Expand Up @@ -114,6 +116,45 @@
return runtime


class AggregateSurfacesInputSpec(TraitedSpec):
surfaces = InputMultiObject(File(exists=True), desc="Input surfaces")
morphometrics = InputMultiObject(File(exists=True), desc="Input morphometrics")


class AggregateSurfacesOutputSpec(TraitedSpec):
pial = traits.List(File(), maxlen=2, desc="Pial surfaces")
white = traits.List(File(), maxlen=2, desc="White surfaces")
inflated = traits.List(File(), maxlen=2, desc="Inflated surfaces")
midthickness = traits.List(File(), maxlen=2, desc="Midthickness (or graymid) surfaces")
thickness = traits.List(File(), maxlen=2, desc="Cortical thickness maps")
sulc = traits.List(File(), maxlen=2, desc="Sulcal depth maps")
curv = traits.List(File(), maxlen=2, desc="Curvature maps")


class AggregateSurfaces(SimpleInterface):
"""Aggregate and group surfaces & morphometrics into left/right pairs."""
input_spec = AggregateSurfacesInputSpec
output_spec = AggregateSurfacesOutputSpec

def _run_interface(self, runtime):
from collections import defaultdict
import os
import re

Check warning on line 142 in smriprep/interfaces/surf.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/surf.py#L140-L142

Added lines #L140 - L142 were not covered by tests

container = defaultdict(list)
inputs = (self.inputs.surfaces or []) + (self.inputs.morphometrics or [])
findre = re.compile(

Check warning on line 146 in smriprep/interfaces/surf.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/surf.py#L144-L146

Added lines #L144 - L146 were not covered by tests
r'(?:^|[^d])(?P<name>white|pial|inflated|midthickness|thickness|sulc|curv)'
)
for surface in sorted(inputs, key=os.path.basename):
match = findre.search(os.path.basename(surface))

Check warning on line 150 in smriprep/interfaces/surf.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/surf.py#L150

Added line #L150 was not covered by tests
if match:
container[match.group('name')].append(surface)

Check warning on line 152 in smriprep/interfaces/surf.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/surf.py#L152

Added line #L152 was not covered by tests
for name, files in container.items():
self._results[name] = files
return runtime

Check warning on line 155 in smriprep/interfaces/surf.py

View check run for this annotation

Codecov / codecov/patch

smriprep/interfaces/surf.py#L154-L155

Added lines #L154 - L155 were not covered by tests


def normalize_surfs(in_file: str, transform_file: str, newpath: Optional[str] = None) -> str:
"""
Update GIFTI metadata and apply rigid coordinate correction.
Expand Down
60 changes: 57 additions & 3 deletions smriprep/workflows/surfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@
"out_aseg",
"out_aparc",
"morphometrics",
"midthickness",
"pial",
"white",
"inflated",
"thickness",
"sulc",
"curv",
]
),
name="outputnode",
Expand Down Expand Up @@ -294,7 +301,14 @@
(autorecon_resume_wf, outputnode, [('outputnode.subjects_dir', 'subjects_dir'),
('outputnode.subject_id', 'subject_id')]),
(gifti_surface_wf, outputnode, [('outputnode.surfaces', 'surfaces'),
('outputnode.morphometrics', 'morphometrics')]),
('outputnode.morphometrics', 'morphometrics'),
('outputnode.midthickness', 'midthickness'),
('outputnode.pial', 'pial'),
('outputnode.white', 'white'),
('outputnode.inflated', 'inflated'),
('outputnode.thickness', 'thickness'),
('outputnode.sulc', 'sulc'),
('outputnode.curv', 'curv')]),
(t1w2fsnative_xfm, outputnode, [('out_lta', 't1w2fsnative_xfm')]),
(fsnative2t1w_xfm, outputnode, [('out_reg_file', 'fsnative2t1w_xfm')]),
(refine, outputnode, [('out_file', 'out_brainmask')]),
Expand Down Expand Up @@ -574,23 +588,51 @@

Outputs
-------
midthickness
Left and right midthickness (or graymid) surface GIFTIs
pial
Left and right pial surface GIFTIs
white
Left and right white surface GIFTIs
inflated
Left and right inflated surface GIFTIs
surfaces
GIFTI surfaces for gray/white matter boundary, pial surface,
midthickness (or graymid) surface, and inflated surfaces
thickness
Left and right cortical thickness GIFTIs
sulc
Left and right sulcal depth map GIFTIs
curv
Left and right curvature map GIFTIs
morphometrics
GIFTIs of cortical thickness, curvature, and sulcal depth

"""
from ..interfaces.freesurfer import MRIsConvertData
from ..interfaces.surf import NormalizeSurf
from ..interfaces.surf import NormalizeSurf, AggregateSurfaces

Check warning on line 613 in smriprep/workflows/surfaces.py

View check run for this annotation

Codecov / codecov/patch

smriprep/workflows/surfaces.py#L613

Added line #L613 was not covered by tests

workflow = Workflow(name=name)

inputnode = pe.Node(
niu.IdentityInterface(["subjects_dir", "subject_id", "fsnative2t1w_xfm"]),
name="inputnode",
)
outputnode = pe.Node(niu.IdentityInterface(["surfaces", "morphometrics"]), name="outputnode")
outputnode = pe.Node(

Check warning on line 621 in smriprep/workflows/surfaces.py

View check run for this annotation

Codecov / codecov/patch

smriprep/workflows/surfaces.py#L621

Added line #L621 was not covered by tests
niu.IdentityInterface([
"pial",
"white",
"inflated",
"midthickness",
"thickness",
"sulc",
"curv",
# Preserve grouping
"surfaces",
"morphometrics",
]),
name="outputnode"
)

get_surfaces = pe.Node(nio.FreeSurferSource(), name="get_surfaces")

Expand Down Expand Up @@ -624,6 +666,8 @@
name="morphs2gii",
)

agg_surfaces = pe.Node(AggregateSurfaces(), name="agg_surfaces")

Check warning on line 669 in smriprep/workflows/surfaces.py

View check run for this annotation

Codecov / codecov/patch

smriprep/workflows/surfaces.py#L669

Added line #L669 was not covered by tests

# fmt:off
workflow.connect([
(inputnode, get_surfaces, [('subjects_dir', 'subjects_dir'),
Expand All @@ -648,6 +692,16 @@
('curv', 'in3')]),
(surfmorph_list, morphs2gii, [('out', 'scalarcurv_file')]),
(morphs2gii, outputnode, [('converted', 'morphometrics')]),
# Output individual surfaces as well
(fix_surfs, agg_surfaces, [('out_file', 'surfaces')]),
(morphs2gii, agg_surfaces, [('converted', 'morphometrics')]),
(agg_surfaces, outputnode, [('pial', 'pial'),
('white', 'white'),
('inflated', 'inflated'),
('midthickness', 'midthickness'),
('thickness', 'thickness'),
('sulc', 'sulc'),
('curv', 'curv')]),
])
# fmt:on
return workflow
Expand Down
Loading