From a223a2fba3a5abf6bbdb91cf00d08fecdf3a8f78 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Tue, 22 Aug 2023 15:26:10 -0400 Subject: [PATCH 1/4] ENH: Add interface to aggregate / sort surface files --- smriprep/interfaces/surf.py | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/smriprep/interfaces/surf.py b/smriprep/interfaces/surf.py index 8b378f8925..bd91db0966 100644 --- a/smriprep/interfaces/surf.py +++ b/smriprep/interfaces/surf.py @@ -33,6 +33,8 @@ SimpleInterface, File, isdefined, + InputMultiObject, + traits, ) @@ -114,6 +116,45 @@ def _run_interface(self, runtime): 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 + + container = defaultdict(list) + inputs = (self.inputs.surfaces or []) + (self.inputs.morphometrics or []) + findre = re.compile( + r'(?:^|[^d])(?Pwhite|pial|inflated|midthickness|thickness|sulc|curv)' + ) + for surface in sorted(inputs, key=os.path.basename): + match = findre.search(os.path.basename(surface)) + if match: + container[match.group('name')].append(surface) + for name, files in container.items(): + self._results[name] = files + return runtime + + def normalize_surfs(in_file: str, transform_file: str, newpath: Optional[str] = None) -> str: """ Update GIFTI metadata and apply rigid coordinate correction. From 0a88fd993f1dc0b22a8d427b5b753d9cea58b333 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Tue, 22 Aug 2023 15:26:47 -0400 Subject: [PATCH 2/4] ENH: Add each surface / morphometric as a standalone output --- smriprep/workflows/surfaces.py | 44 ++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/smriprep/workflows/surfaces.py b/smriprep/workflows/surfaces.py index b074c3d741..9b26517e20 100644 --- a/smriprep/workflows/surfaces.py +++ b/smriprep/workflows/surfaces.py @@ -574,15 +574,29 @@ def init_gifti_surface_wf(*, name="gifti_surface_wf"): 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 workflow = Workflow(name=name) @@ -590,7 +604,21 @@ def init_gifti_surface_wf(*, name="gifti_surface_wf"): niu.IdentityInterface(["subjects_dir", "subject_id", "fsnative2t1w_xfm"]), name="inputnode", ) - outputnode = pe.Node(niu.IdentityInterface(["surfaces", "morphometrics"]), name="outputnode") + outputnode = pe.Node( + niu.IdentityInterface([ + "pial", + "white", + "inflated", + "midthickness", + "thickness", + "sulc", + "curv", + # Preserve grouping + "surfaces", + "morphometrics", + ]), + name="outputnode" + ) get_surfaces = pe.Node(nio.FreeSurferSource(), name="get_surfaces") @@ -624,6 +652,8 @@ def init_gifti_surface_wf(*, name="gifti_surface_wf"): name="morphs2gii", ) + agg_surfaces = pe.Node(AggregateSurfaces(), name="agg_surfaces") + # fmt:off workflow.connect([ (inputnode, get_surfaces, [('subjects_dir', 'subjects_dir'), @@ -648,6 +678,16 @@ def init_gifti_surface_wf(*, name="gifti_surface_wf"): ('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 From dbb5dc74837125d157e224e780b361dbe22b9d90 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 23 Aug 2023 14:34:33 -0400 Subject: [PATCH 3/4] ENH: Pass along individual outputs to surface workflow --- smriprep/workflows/surfaces.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/smriprep/workflows/surfaces.py b/smriprep/workflows/surfaces.py index 9b26517e20..021b6c4773 100644 --- a/smriprep/workflows/surfaces.py +++ b/smriprep/workflows/surfaces.py @@ -209,6 +209,13 @@ def init_surface_recon_wf(*, omp_nthreads, hires, name="surface_recon_wf"): "out_aseg", "out_aparc", "morphometrics", + "midthickness", + "pial", + "white", + "inflated", + "thickness", + "sulc", + "curv", ] ), name="outputnode", @@ -294,7 +301,15 @@ def init_surface_recon_wf(*, omp_nthreads, hires, name="surface_recon_wf"): (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.morphometrics', 'morphometrics'), + ('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')]), From cf49b5b27dddce1ed985d7a679812ea5fb9a0f48 Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Wed, 23 Aug 2023 15:20:07 -0400 Subject: [PATCH 4/4] Update smriprep/workflows/surfaces.py Co-authored-by: Chris Markiewicz --- smriprep/workflows/surfaces.py | 1 - 1 file changed, 1 deletion(-) diff --git a/smriprep/workflows/surfaces.py b/smriprep/workflows/surfaces.py index 021b6c4773..71b4dbecad 100644 --- a/smriprep/workflows/surfaces.py +++ b/smriprep/workflows/surfaces.py @@ -306,7 +306,6 @@ def init_surface_recon_wf(*, omp_nthreads, hires, name="surface_recon_wf"): ('outputnode.pial', 'pial'), ('outputnode.white', 'white'), ('outputnode.inflated', 'inflated'), - ('outputnode.morphometrics', 'morphometrics'), ('outputnode.thickness', 'thickness'), ('outputnode.sulc', 'sulc'), ('outputnode.curv', 'curv')]),