From 477f17cd2e67901fc82dbb358b38fefec019b192 Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Tue, 20 Aug 2024 13:51:46 -0400 Subject: [PATCH 1/5] FIX: Select function in segmentation resampling workflow --- smriprep/workflows/surfaces.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/smriprep/workflows/surfaces.py b/smriprep/workflows/surfaces.py index ec44155b7f..cddba93cc5 100644 --- a/smriprep/workflows/surfaces.py +++ b/smriprep/workflows/surfaces.py @@ -1170,11 +1170,11 @@ def init_hcp_morphometrics_wf( def init_segs_to_native_wf( *, image_type: ty.Literal['T1w', 'T2w'] = 'T1w', - segmentation: ty.Literal['aseg', 'aparc_aseg', 'wmparc'] = 'aseg', + segmentation: ty.Literal['aseg', 'aparc_aseg', 'aparc_a2009s', 'aparc_dkt'] | str = 'aseg', name: str = 'segs_to_native_wf', ) -> Workflow: """ - Get a segmentation from FreeSurfer conformed space into native T1w space. + Get a segmentation from FreeSurfer conformed space into native anatomical space. Workflow Graph .. workflow:: @@ -1219,30 +1219,27 @@ def init_segs_to_native_wf( lta = pe.Node(ConcatenateXFMs(out_fmt='fs'), name='lta', run_without_submitting=True) - # Resample from T1.mgz to T1w.nii.gz, applying any offset in fsnative2anat_xfm, + # Resample from Freesurfer anat to native anat, applying any offset in fsnative2anat_xfm, # and convert to NIfTI while we're at it resample = pe.Node( fs.ApplyVolTransform(transformed_file='seg.nii.gz', interp='nearest'), name='resample', ) - if segmentation.startswith('aparc'): - if segmentation == 'aparc_aseg': + seg_mapping = {'aparc_aseg': 'aparc+', 'aparc_a2009s': 'a2009s+', 'aparc_dkt': 'DKTatlas+'} + if segmentation in seg_mapping: + name = seg_mapping[segmentation] - def _sel(x): - return [parc for parc in x if 'aparc+' in parc][0] # noqa + def _sel(files, name=name): + if isinstance(files, str): + return files - elif segmentation == 'aparc_a2009s': + for fl in files: + if name in fl: + return fl + raise FileNotFoundError - def _sel(x): - return [parc for parc in x if 'a2009s+' in parc][0] # noqa - - elif segmentation == 'aparc_dkt': - - def _sel(x): - return [parc for parc in x if 'DKTatlas+' in parc][0] # noqa - - segmentation = (segmentation, _sel) + segmentation = (segmentation, _sel) # type: ignore anat = 'T2' if image_type == 'T2w' else 'T1' From f2ea08da8d16fa79b86afdb47942f223349e7600 Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Tue, 20 Aug 2024 14:50:00 -0400 Subject: [PATCH 2/5] RF: Move filtering into standalone custom function --- smriprep/workflows/surfaces.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/smriprep/workflows/surfaces.py b/smriprep/workflows/surfaces.py index cddba93cc5..2ef9cb9b16 100644 --- a/smriprep/workflows/surfaces.py +++ b/smriprep/workflows/surfaces.py @@ -1226,20 +1226,8 @@ def init_segs_to_native_wf( name='resample', ) - seg_mapping = {'aparc_aseg': 'aparc+', 'aparc_a2009s': 'a2009s+', 'aparc_dkt': 'DKTatlas+'} - if segmentation in seg_mapping: - name = seg_mapping[segmentation] - - def _sel(files, name=name): - if isinstance(files, str): - return files - - for fl in files: - if name in fl: - return fl - raise FileNotFoundError - - segmentation = (segmentation, _sel) # type: ignore + select_seg = pe.Node(niu.Function(function=_select_seg), name='select_seg') + select_seg.inputs.segmentation = segmentation anat = 'T2' if image_type == 'T2w' else 'T1' @@ -1251,7 +1239,8 @@ def _sel(files, name=name): ('fsnative2anat_xfm', 'in_xfms')]), (fssource, lta, [(anat, 'moving')]), (inputnode, resample, [('in_file', 'target_file')]), - (fssource, resample, [(segmentation, 'source_file')]), + (fssource, select_seg, [(segmentation, 'in_files')]), + (select_seg, resample, [('out_file', 'source_file')]), (lta, resample, [('out_xfm', 'lta_file')]), (resample, outputnode, [('transformed_file', 'out_file')]), ]) # fmt:skip @@ -1675,3 +1664,17 @@ def _get_surfaces(subjects_dir: str, subject_id: str, surfaces: list[str]) -> tu ret = tuple(all_surfs[surface] for surface in surfaces) return ret if len(ret) > 1 else ret[0] + + +def _select_seg(in_files, segmentation): + if isinstance(in_files, str): + return in_files + + seg_mapping = {'aparc_aseg': 'aparc+', 'aparc_a2009s': 'a2009s+', 'aparc_dkt': 'DKTatlas+'} + if segmentation in seg_mapping: + segmentation = seg_mapping[segmentation] + + for fl in in_files: + if segmentation in fl: + return fl + raise FileNotFoundError(f'No segmentation containing "{segmentation}" was found.') From 0cfde86aedba36f55f18f114721f7242faae6eb3 Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Tue, 20 Aug 2024 15:19:32 -0400 Subject: [PATCH 3/5] TST: Add test for standalone function --- smriprep/workflows/tests/test_surfaces.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/smriprep/workflows/tests/test_surfaces.py b/smriprep/workflows/tests/test_surfaces.py index 95076da759..5a716548c1 100644 --- a/smriprep/workflows/tests/test_surfaces.py +++ b/smriprep/workflows/tests/test_surfaces.py @@ -9,7 +9,7 @@ from smriprep.interfaces.tests.data import load as load_test_data -from ..surfaces import init_anat_ribbon_wf, init_gifti_surfaces_wf +from ..surfaces import _select_seg, init_anat_ribbon_wf, init_gifti_surfaces_wf def test_ribbon_workflow(tmp_path: Path): @@ -53,3 +53,16 @@ def test_ribbon_workflow(tmp_path: Path): assert np.allclose(ribbon.affine, expected.affine) # Mask data is binary, so we can use np.array_equal assert np.array_equal(ribbon.dataobj, expected.dataobj) + + +@pytest.mark.parametrize( + ('in_files', 'segmentation', 'expected'), + [ + ('aparc+aseg.mgz', 'aparc_aseg', 'aparc+aseg.mgz'), + (['a2009s+aseg.mgz', 'aparc+aseg.mgz'], 'aparc_aseg', 'aparc+aseg.mgz'), + (['a2009s+aseg.mgz', 'aparc+aseg.mgz'], 'aparc_2009s', 'a2009s+aseg.mgz'), + ('wmparc.mgz', 'wmparc.mgz', 'wmparc.mgz'), + ], +) +def test_select_seg(in_files, segmentation, expected): + assert _select_seg(in_files, segmentation) == expected From 530ce3793d53f06020201a42287fe6807e9d12bd Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Tue, 20 Aug 2024 16:17:12 -0400 Subject: [PATCH 4/5] FIX: Output name --- smriprep/workflows/surfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smriprep/workflows/surfaces.py b/smriprep/workflows/surfaces.py index 2ef9cb9b16..48895377a3 100644 --- a/smriprep/workflows/surfaces.py +++ b/smriprep/workflows/surfaces.py @@ -1240,7 +1240,7 @@ def init_segs_to_native_wf( (fssource, lta, [(anat, 'moving')]), (inputnode, resample, [('in_file', 'target_file')]), (fssource, select_seg, [(segmentation, 'in_files')]), - (select_seg, resample, [('out_file', 'source_file')]), + (select_seg, resample, [('out', 'source_file')]), (lta, resample, [('out_xfm', 'lta_file')]), (resample, outputnode, [('transformed_file', 'out_file')]), ]) # fmt:skip From 4738420bb428b564f189dfe8b0d6767b9b413c86 Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Tue, 20 Aug 2024 17:10:27 -0400 Subject: [PATCH 5/5] TST: Fix test input --- smriprep/workflows/tests/test_surfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smriprep/workflows/tests/test_surfaces.py b/smriprep/workflows/tests/test_surfaces.py index 5a716548c1..da28ba1914 100644 --- a/smriprep/workflows/tests/test_surfaces.py +++ b/smriprep/workflows/tests/test_surfaces.py @@ -60,7 +60,7 @@ def test_ribbon_workflow(tmp_path: Path): [ ('aparc+aseg.mgz', 'aparc_aseg', 'aparc+aseg.mgz'), (['a2009s+aseg.mgz', 'aparc+aseg.mgz'], 'aparc_aseg', 'aparc+aseg.mgz'), - (['a2009s+aseg.mgz', 'aparc+aseg.mgz'], 'aparc_2009s', 'a2009s+aseg.mgz'), + (['a2009s+aseg.mgz', 'aparc+aseg.mgz'], 'aparc_a2009s', 'a2009s+aseg.mgz'), ('wmparc.mgz', 'wmparc.mgz', 'wmparc.mgz'), ], )