From da35d17a9815f5ace0d35df4157bd5a0c5e452c0 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Tue, 29 Nov 2022 13:54:10 -0600 Subject: [PATCH 01/17] Initial addition of FreeSurfer longitudinal interfaces. --- nipype/interfaces/freesurfer/longitudinal.py | 203 ++++++++++++++++++- 1 file changed, 201 insertions(+), 2 deletions(-) diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index 5c3f621e87..4e96f32dc9 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -7,8 +7,10 @@ import os from ... import logging -from ..base import TraitedSpec, File, traits, InputMultiPath, OutputMultiPath, isdefined -from .base import FSCommand, FSTraitedSpec, FSCommandOpenMP, FSTraitedSpecOpenMP +from ..base import TraitedSpec, File, traits, InputMultiPath, OutputMultiPath, isdefined, InputMultiObject, Directory +from .base import FSCommand, FSTraitedSpec, FSCommandOpenMP, FSTraitedSpecOpenMP, CommandLine +from .preprocess import ReconAllInputSpec +from ..io import FreeSurferSource __docformat__ = "restructuredtext" iflogger = logging.getLogger("nipype.interface") @@ -251,3 +253,200 @@ def _list_outputs(self): outputs = self.output_spec().get() outputs["out_file"] = os.path.abspath(self.inputs.out_file) return outputs + + +class BaseReconAllInputSpec(ReconAllInputSpec): + subject_id = traits.Str(argstr="-subjid %s", desc="subject name") + base_id = traits.Str(argstr="-base %s", desc="base template name", xor=["subject_id"]) + timepoints = InputMultiObject( + traits.Str(), argstr="-tp %s...", desc="processed time point to use in template" + ) + +class BaseReconAllOutputSpec(FreeSurferSource.output_spec): + subjects_dir = Directory(exists=True, desc="FreeSurfer subjects directory") + subject_id = traits.Str(desc="Subject template name") + +class BaseReconAll(CommandLine): + """Uses the longitudinal pipeline of recon-all to create a template for a given number of subject's sessions. + + Examples + -------- + >>> from nipype.interfaces.freesurfer.longitudinal import BaseReconAll + >>> baserecon = BaseReconAll() + >>> baserecon.inputs.base_id = 'sub-template' + >>> baserecon.inputs.timepoints = ['ses-1','ses-2'] + >>> baserecon.inputs.directive = 'all' + >>> baserecon.inputs.subjects_dir = '.' + >>> baserecon.cmdline + 'recon-all -all -base sub-template -sd . -tp ses-1 -tp ses-2' + """ + + _cmd = "recon-all" + input_spec = BaseReconAllInputSpec + output_spec = BaseReconAllOutputSpec + _can_resume = True + force_run = False + + def _gen_subjects_dir(self): + return os.getcwd() + + + def _gen_filename(self, name): + if name == "subjects_dir": + return self._gen_subjects_dir() + return None + + + def _list_outputs(self): + if isdefined(self.inputs.subjects_dir): + subjects_dir = self.inputs.subjects_dir + else: + subjects_dir = self._gen_subjects_dir() + + if isdefined(self.inputs.hemi): + hemi = self.inputs.hemi + else: + hemi = "both" + + outputs = self._outputs().get() + + outputs.update( + FreeSurferSource(subject_id=self.inputs.base_id, + subjects_dir=subjects_dir, hemi=hemi)._list_outputs() + ) + outputs["subject_id"] = self.inputs.base_id + outputs["subjects_dir"] = subjects_dir + return outputs + + + def _is_resuming(self): + subjects_dir = self.inputs.subjects_dir + if not isdefined(subjects_dir): + subjects_dir = self._gen_subjects_dir() + if os.path.isdir(os.path.join(subjects_dir, self.inputs.base_id, "mri")): + return True + return False + + + def _format_arg(self, name, trait_spec, value): + return super(BaseReconAll, self)._format_arg(name, trait_spec, value) + + + @property + def cmdline(self): + cmd = super(BaseReconAll, self).cmdline + + if not self._is_resuming(): + return cmd + + subjects_dir = self.inputs.subjects_dir + if not isdefined(subjects_dir): + subjects_dir = self._gen_subjects_dir() + + directive = self.inputs.directive + if not isdefined(directive): + steps = [] + + iflogger.info(f"recon-all: {cmd}") + return cmd + + +class LongReconAllInputSpec(ReconAllInputSpec): + subject_id = traits.Str(argstr="-subjid %s", desc="subject name") + long_id = traits.Tuple( + traits.Str(), + traits.Str(), + argstr="-long %s %s", + desc="longitudinal name followed by base template name", + xor=["subject_id"] + ) + + +class LongReconAllOutputSpec(FreeSurferSource.output_spec): + subjects_dir = Directory(exists=True, desc="FreeSurfer subjects directory") + subject_id = traits.Str(desc="Subject template name") + + +class LongReconAll(CommandLine): + """Uses FreeSurfer's longitudinal recon-all to process a subject given + the previously processed base template. + + Examples + --------- + + >>> from nipype.interfaces.freesurfer.longitudinal import LongReconAll + >>> longrecon = LongReconAll() + >>> longrecon.inputs.long_id = ("ses-1","sub-template") + >>> longrecon.inputs.directive = "all" + >>> longrecon.inputs.subjects_dir = "." + >>> longrecon.cmdline + 'recon-all -all -long ses-1 sub-template -sd .' + """ + + _cmd = "recon-all" + input_spec = LongReconAllInputSpec + output_spec = LongReconAllOutputSpec + _can_resume = True + force_run = False + + def _gen_subjects_dir(self): + return os.getcwd() + + def _gen_filename(self, name): + if name == "subjects_dir": + return self._gen_subjects_dir() + return None + + def _list_outputs(self): + subject_id = f"{self.inputs.long_id[0]}.long.{self.inputs.long_id[1]}" + + if isdefined(self.inputs.subjects_dir): + subjects_dir = self.inputs.subjects_dir + else: + subjects_dir = self._gen_subjects_dir() + + if isdefined(self.inputs.hemi): + hemi = self.inputs.hemi + else: + hemi = "both" + + outputs = self._outputs().get() + + outputs.update( + FreeSurferSource( + subject_id=subject_id, subjects_dir=subjects_dir, hemi=hemi + )._list_outputs() + ) + outputs["subject_id"] = subject_id + outputs["subjects_dir"] = subjects_dir + return outputs + + def _is_resuming(self): + subjects_dir = self.inputs.subjects_dir + subject_id = f"{self.inputs.long_id[0]}.long{self.inputs.long_id[1]}" + if not isdefined(subjects_dir): + subjects_dir = self._gen_subjects_dir() + if os.path.isdir(os.path.join(subjects_dir, subject_id, "mri")): + return True + return False + + def _format_arg(self, name, trait_spec, value): + return super(LongReconAll, self)._format_arg(name, trait_spec, value) + + @property + def cmdline(self): + cmd = super(LongReconAll, self).cmdline + + if not self._is_resuming(): + return cmd + + subjects_dir = self.inputs.subjects_dir + if not isdefined(subjects_dir): + subjects_dir = self._gen_subjects_dir() + + directive = self.inputs.directive + if not isdefined(directive): + steps = [] + + iflogger.info(f"recon-all: {cmd}") + return cmd From d6ec3bd87f84f3a119e056b607ef278b93291ad5 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Tue, 29 Nov 2022 15:01:24 -0600 Subject: [PATCH 02/17] Style and testing. --- nipype/interfaces/freesurfer/longitudinal.py | 2 +- .../tests/test_auto_BaseReconAll.py | 329 ++++++++++++++++++ .../tests/test_auto_LongReconAll.py | 326 +++++++++++++++++ nipype/interfaces/tests/test_auto_Dcm2nii.py | 107 ------ 4 files changed, 656 insertions(+), 108 deletions(-) create mode 100644 nipype/interfaces/freesurfer/tests/test_auto_BaseReconAll.py create mode 100644 nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py delete mode 100644 nipype/interfaces/tests/test_auto_Dcm2nii.py diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index 4e96f32dc9..de8a835f9c 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -296,7 +296,7 @@ def _gen_filename(self, name): return self._gen_subjects_dir() return None - + def _list_outputs(self): if isdefined(self.inputs.subjects_dir): subjects_dir = self.inputs.subjects_dir diff --git a/nipype/interfaces/freesurfer/tests/test_auto_BaseReconAll.py b/nipype/interfaces/freesurfer/tests/test_auto_BaseReconAll.py new file mode 100644 index 0000000000..034ea5925f --- /dev/null +++ b/nipype/interfaces/freesurfer/tests/test_auto_BaseReconAll.py @@ -0,0 +1,329 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from ..longitudinal import BaseReconAll + + +def test_BaseReconAll_inputs(): + input_map = dict( + FLAIR_file=dict( + argstr="-FLAIR %s", + extensions=None, + min_ver="5.3.0", + ), + T1_files=dict( + argstr="-i %s...", + ), + T2_file=dict( + argstr="-T2 %s", + extensions=None, + min_ver="5.3.0", + ), + args=dict( + argstr="%s", + ), + base_id=dict( + argstr="-base %s", + xor=["subject_id"], + ), + big_ventricles=dict( + argstr="-bigventricles", + ), + brainstem=dict( + argstr="-brainstem-structures", + ), + directive=dict( + argstr="-%s", + position=0, + usedefault=True, + ), + environ=dict( + nohash=True, + usedefault=True, + ), + expert=dict( + argstr="-expert %s", + extensions=None, + ), + flags=dict( + argstr="%s", + ), + hemi=dict( + argstr="-hemi %s", + ), + hippocampal_subfields_T1=dict( + argstr="-hippocampal-subfields-T1", + min_ver="6.0.0", + ), + hippocampal_subfields_T2=dict( + argstr="-hippocampal-subfields-T2 %s %s", + min_ver="6.0.0", + ), + hires=dict( + argstr="-hires", + min_ver="6.0.0", + ), + mprage=dict( + argstr="-mprage", + ), + mri_aparc2aseg=dict( + xor=["expert"], + ), + mri_ca_label=dict( + xor=["expert"], + ), + mri_ca_normalize=dict( + xor=["expert"], + ), + mri_ca_register=dict( + xor=["expert"], + ), + mri_edit_wm_with_aseg=dict( + xor=["expert"], + ), + mri_em_register=dict( + xor=["expert"], + ), + mri_fill=dict( + xor=["expert"], + ), + mri_mask=dict( + xor=["expert"], + ), + mri_normalize=dict( + xor=["expert"], + ), + mri_pretess=dict( + xor=["expert"], + ), + mri_remove_neck=dict( + xor=["expert"], + ), + mri_segment=dict( + xor=["expert"], + ), + mri_segstats=dict( + xor=["expert"], + ), + mri_tessellate=dict( + xor=["expert"], + ), + mri_watershed=dict( + xor=["expert"], + ), + mris_anatomical_stats=dict( + xor=["expert"], + ), + mris_ca_label=dict( + xor=["expert"], + ), + mris_fix_topology=dict( + xor=["expert"], + ), + mris_inflate=dict( + xor=["expert"], + ), + mris_make_surfaces=dict( + xor=["expert"], + ), + mris_register=dict( + xor=["expert"], + ), + mris_smooth=dict( + xor=["expert"], + ), + mris_sphere=dict( + xor=["expert"], + ), + mris_surf2vol=dict( + xor=["expert"], + ), + mrisp_paint=dict( + xor=["expert"], + ), + openmp=dict( + argstr="-openmp %d", + ), + parallel=dict( + argstr="-parallel", + ), + subject_id=dict( + argstr="-subjid %s", + ), + subjects_dir=dict( + argstr="-sd %s", + genfile=True, + hash_files=False, + ), + talairach=dict( + xor=["expert"], + ), + timepoints=dict( + argstr="-tp %s...", + ), + use_FLAIR=dict( + argstr="-FLAIRpial", + min_ver="5.3.0", + xor=["use_T2"], + ), + use_T2=dict( + argstr="-T2pial", + min_ver="5.3.0", + xor=["use_FLAIR"], + ), + xopts=dict( + argstr="-xopts-%s", + ), + ) + inputs = BaseReconAll.input_spec() + + for key, metadata in list(input_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(inputs.traits()[key], metakey) == value + + +def test_BaseReconAll_outputs(): + output_map = dict( + BA_stats=dict( + altkey="BA", + loc="stats", + ), + T1=dict( + extensions=None, + loc="mri", + ), + annot=dict( + altkey="*annot", + loc="label", + ), + aparc_a2009s_stats=dict( + altkey="aparc.a2009s", + loc="stats", + ), + aparc_aseg=dict( + altkey="aparc*aseg", + loc="mri", + ), + aparc_stats=dict( + altkey="aparc", + loc="stats", + ), + area_pial=dict( + altkey="area.pial", + loc="surf", + ), + aseg=dict( + extensions=None, + loc="mri", + ), + aseg_stats=dict( + altkey="aseg", + loc="stats", + ), + avg_curv=dict( + loc="surf", + ), + brain=dict( + extensions=None, + loc="mri", + ), + brainmask=dict( + extensions=None, + loc="mri", + ), + curv=dict( + loc="surf", + ), + curv_pial=dict( + altkey="curv.pial", + loc="surf", + ), + curv_stats=dict( + altkey="curv", + loc="stats", + ), + entorhinal_exvivo_stats=dict( + altkey="entorhinal_exvivo", + loc="stats", + ), + filled=dict( + extensions=None, + loc="mri", + ), + graymid=dict( + altkey=["graymid", "midthickness"], + loc="surf", + ), + inflated=dict( + loc="surf", + ), + jacobian_white=dict( + loc="surf", + ), + label=dict( + altkey="*label", + loc="label", + ), + norm=dict( + extensions=None, + loc="mri", + ), + nu=dict( + extensions=None, + loc="mri", + ), + orig=dict( + extensions=None, + loc="mri", + ), + pial=dict( + loc="surf", + ), + rawavg=dict( + extensions=None, + loc="mri", + ), + ribbon=dict( + altkey="*ribbon", + loc="mri", + ), + smoothwm=dict( + loc="surf", + ), + sphere=dict( + loc="surf", + ), + sphere_reg=dict( + altkey="sphere.reg", + loc="surf", + ), + subject_id=dict(), + subjects_dir=dict(), + sulc=dict( + loc="surf", + ), + thickness=dict( + loc="surf", + ), + volume=dict( + loc="surf", + ), + white=dict( + loc="surf", + ), + wm=dict( + extensions=None, + loc="mri", + ), + wmparc=dict( + extensions=None, + loc="mri", + ), + wmparc_stats=dict( + altkey="wmparc", + loc="stats", + ), + ) + outputs = BaseReconAll.output_spec() + + for key, metadata in list(output_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(outputs.traits()[key], metakey) == value diff --git a/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py b/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py new file mode 100644 index 0000000000..dac29f1693 --- /dev/null +++ b/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py @@ -0,0 +1,326 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from ..longitudinal import LongReconAll + + +def test_LongReconAll_inputs(): + input_map = dict( + FLAIR_file=dict( + argstr="-FLAIR %s", + extensions=None, + min_ver="5.3.0", + ), + T1_files=dict( + argstr="-i %s...", + ), + T2_file=dict( + argstr="-T2 %s", + extensions=None, + min_ver="5.3.0", + ), + args=dict( + argstr="%s", + ), + big_ventricles=dict( + argstr="-bigventricles", + ), + brainstem=dict( + argstr="-brainstem-structures", + ), + directive=dict( + argstr="-%s", + position=0, + usedefault=True, + ), + environ=dict( + nohash=True, + usedefault=True, + ), + expert=dict( + argstr="-expert %s", + extensions=None, + ), + flags=dict( + argstr="%s", + ), + hemi=dict( + argstr="-hemi %s", + ), + hippocampal_subfields_T1=dict( + argstr="-hippocampal-subfields-T1", + min_ver="6.0.0", + ), + hippocampal_subfields_T2=dict( + argstr="-hippocampal-subfields-T2 %s %s", + min_ver="6.0.0", + ), + hires=dict( + argstr="-hires", + min_ver="6.0.0", + ), + long_id=dict( + argstr="-long %s %s", + xor=["subject_id"], + ), + mprage=dict( + argstr="-mprage", + ), + mri_aparc2aseg=dict( + xor=["expert"], + ), + mri_ca_label=dict( + xor=["expert"], + ), + mri_ca_normalize=dict( + xor=["expert"], + ), + mri_ca_register=dict( + xor=["expert"], + ), + mri_edit_wm_with_aseg=dict( + xor=["expert"], + ), + mri_em_register=dict( + xor=["expert"], + ), + mri_fill=dict( + xor=["expert"], + ), + mri_mask=dict( + xor=["expert"], + ), + mri_normalize=dict( + xor=["expert"], + ), + mri_pretess=dict( + xor=["expert"], + ), + mri_remove_neck=dict( + xor=["expert"], + ), + mri_segment=dict( + xor=["expert"], + ), + mri_segstats=dict( + xor=["expert"], + ), + mri_tessellate=dict( + xor=["expert"], + ), + mri_watershed=dict( + xor=["expert"], + ), + mris_anatomical_stats=dict( + xor=["expert"], + ), + mris_ca_label=dict( + xor=["expert"], + ), + mris_fix_topology=dict( + xor=["expert"], + ), + mris_inflate=dict( + xor=["expert"], + ), + mris_make_surfaces=dict( + xor=["expert"], + ), + mris_register=dict( + xor=["expert"], + ), + mris_smooth=dict( + xor=["expert"], + ), + mris_sphere=dict( + xor=["expert"], + ), + mris_surf2vol=dict( + xor=["expert"], + ), + mrisp_paint=dict( + xor=["expert"], + ), + openmp=dict( + argstr="-openmp %d", + ), + parallel=dict( + argstr="-parallel", + ), + subject_id=dict( + argstr="-subjid %s", + ), + subjects_dir=dict( + argstr="-sd %s", + genfile=True, + hash_files=False, + ), + talairach=dict( + xor=["expert"], + ), + use_FLAIR=dict( + argstr="-FLAIRpial", + min_ver="5.3.0", + xor=["use_T2"], + ), + use_T2=dict( + argstr="-T2pial", + min_ver="5.3.0", + xor=["use_FLAIR"], + ), + xopts=dict( + argstr="-xopts-%s", + ), + ) + inputs = LongReconAll.input_spec() + + for key, metadata in list(input_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(inputs.traits()[key], metakey) == value + + +def test_LongReconAll_outputs(): + output_map = dict( + BA_stats=dict( + altkey="BA", + loc="stats", + ), + T1=dict( + extensions=None, + loc="mri", + ), + annot=dict( + altkey="*annot", + loc="label", + ), + aparc_a2009s_stats=dict( + altkey="aparc.a2009s", + loc="stats", + ), + aparc_aseg=dict( + altkey="aparc*aseg", + loc="mri", + ), + aparc_stats=dict( + altkey="aparc", + loc="stats", + ), + area_pial=dict( + altkey="area.pial", + loc="surf", + ), + aseg=dict( + extensions=None, + loc="mri", + ), + aseg_stats=dict( + altkey="aseg", + loc="stats", + ), + avg_curv=dict( + loc="surf", + ), + brain=dict( + extensions=None, + loc="mri", + ), + brainmask=dict( + extensions=None, + loc="mri", + ), + curv=dict( + loc="surf", + ), + curv_pial=dict( + altkey="curv.pial", + loc="surf", + ), + curv_stats=dict( + altkey="curv", + loc="stats", + ), + entorhinal_exvivo_stats=dict( + altkey="entorhinal_exvivo", + loc="stats", + ), + filled=dict( + extensions=None, + loc="mri", + ), + graymid=dict( + altkey=["graymid", "midthickness"], + loc="surf", + ), + inflated=dict( + loc="surf", + ), + jacobian_white=dict( + loc="surf", + ), + label=dict( + altkey="*label", + loc="label", + ), + norm=dict( + extensions=None, + loc="mri", + ), + nu=dict( + extensions=None, + loc="mri", + ), + orig=dict( + extensions=None, + loc="mri", + ), + pial=dict( + loc="surf", + ), + rawavg=dict( + extensions=None, + loc="mri", + ), + ribbon=dict( + altkey="*ribbon", + loc="mri", + ), + smoothwm=dict( + loc="surf", + ), + sphere=dict( + loc="surf", + ), + sphere_reg=dict( + altkey="sphere.reg", + loc="surf", + ), + subject_id=dict(), + subjects_dir=dict(), + sulc=dict( + loc="surf", + ), + thickness=dict( + loc="surf", + ), + volume=dict( + loc="surf", + ), + white=dict( + loc="surf", + ), + wm=dict( + extensions=None, + loc="mri", + ), + wmparc=dict( + extensions=None, + loc="mri", + ), + wmparc_stats=dict( + altkey="wmparc", + loc="stats", + ), + ) + outputs = LongReconAll.output_spec() + + for key, metadata in list(output_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(outputs.traits()[key], metakey) == value diff --git a/nipype/interfaces/tests/test_auto_Dcm2nii.py b/nipype/interfaces/tests/test_auto_Dcm2nii.py deleted file mode 100644 index 948aafa083..0000000000 --- a/nipype/interfaces/tests/test_auto_Dcm2nii.py +++ /dev/null @@ -1,107 +0,0 @@ -# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT -from ..dcm2nii import Dcm2nii - - -def test_Dcm2nii_inputs(): - input_map = dict( - anonymize=dict( - argstr="-a", - usedefault=True, - ), - args=dict( - argstr="%s", - ), - collapse_folders=dict( - argstr="-c", - usedefault=True, - ), - config_file=dict( - argstr="-b %s", - extensions=None, - genfile=True, - ), - convert_all_pars=dict( - argstr="-v", - usedefault=True, - ), - date_in_filename=dict( - argstr="-d", - usedefault=True, - ), - environ=dict( - nohash=True, - usedefault=True, - ), - events_in_filename=dict( - argstr="-e", - usedefault=True, - ), - gzip_output=dict( - argstr="-g", - usedefault=True, - ), - id_in_filename=dict( - argstr="-i", - usedefault=True, - ), - nii_output=dict( - argstr="-n", - usedefault=True, - ), - output_dir=dict( - argstr="-o %s", - genfile=True, - ), - protocol_in_filename=dict( - argstr="-p", - usedefault=True, - ), - reorient=dict( - argstr="-r", - ), - reorient_and_crop=dict( - argstr="-x", - usedefault=True, - ), - source_dir=dict( - argstr="%s", - mandatory=True, - position=-1, - xor=["source_names"], - ), - source_in_filename=dict( - argstr="-f", - usedefault=True, - ), - source_names=dict( - argstr="%s", - copyfile=False, - mandatory=True, - position=-1, - xor=["source_dir"], - ), - spm_analyze=dict( - argstr="-s", - xor=["nii_output"], - ), - ) - inputs = Dcm2nii.input_spec() - - for key, metadata in list(input_map.items()): - for metakey, value in list(metadata.items()): - assert getattr(inputs.traits()[key], metakey) == value - - -def test_Dcm2nii_outputs(): - output_map = dict( - bvals=dict(), - bvecs=dict(), - converted_files=dict(), - reoriented_and_cropped_files=dict(), - reoriented_files=dict(), - ) - outputs = Dcm2nii.output_spec() - - for key, metadata in list(output_map.items()): - for metakey, value in list(metadata.items()): - assert getattr(outputs.traits()[key], metakey) == value From 0b51c5445a222599042fe66a17a7bc45394f6ae4 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Fri, 2 Dec 2022 07:36:48 -0600 Subject: [PATCH 03/17] Update LongReconAll to separate base template id. --- nipype/interfaces/freesurfer/longitudinal.py | 23 +++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index de8a835f9c..032dded3df 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -353,12 +353,18 @@ def cmdline(self): class LongReconAllInputSpec(ReconAllInputSpec): subject_id = traits.Str(argstr="-subjid %s", desc="subject name") - long_id = traits.Tuple( - traits.Str(), - traits.Str(), - argstr="-long %s %s", - desc="longitudinal name followed by base template name", - xor=["subject_id"] + long_id = traits.Str( + argstr="-long %s", + desc="longitudinal session/timepoint id", + xor=["subject_id"], + requires=["base_id"], + position=1 + ) + base_id = traits.Str( + argstr="%s", + desc="longitudinal base template id", + requires=["long_id"], + position=2 ) @@ -376,11 +382,12 @@ class LongReconAll(CommandLine): >>> from nipype.interfaces.freesurfer.longitudinal import LongReconAll >>> longrecon = LongReconAll() - >>> longrecon.inputs.long_id = ("ses-1","sub-template") + >>> longrecon.inputs.long_id = "ses-1" + >>> longrecon.inputs.base_id = "sub-template" >>> longrecon.inputs.directive = "all" >>> longrecon.inputs.subjects_dir = "." >>> longrecon.cmdline - 'recon-all -all -long ses-1 sub-template -sd .' + 'recon-all -all -long ses-1 -base sub-template -sd .' """ _cmd = "recon-all" From 6a294befa426dd9d570197a44cb6cfa0c365378c Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Fri, 2 Dec 2022 09:10:57 -0600 Subject: [PATCH 04/17] Forgot check-before-commit. Update LongReconAll docstring. --- nipype/interfaces/freesurfer/longitudinal.py | 2 +- .../freesurfer/tests/test_auto_LongReconAll.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index 032dded3df..ac76850a6a 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -387,7 +387,7 @@ class LongReconAll(CommandLine): >>> longrecon.inputs.directive = "all" >>> longrecon.inputs.subjects_dir = "." >>> longrecon.cmdline - 'recon-all -all -long ses-1 -base sub-template -sd .' + 'recon-all -all -long ses-1 sub-template -sd .' """ _cmd = "recon-all" diff --git a/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py b/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py index dac29f1693..61c15f310c 100644 --- a/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py +++ b/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py @@ -20,6 +20,11 @@ def test_LongReconAll_inputs(): args=dict( argstr="%s", ), + base_id=dict( + argstr="%s", + position=2, + requires=["long_id"], + ), big_ventricles=dict( argstr="-bigventricles", ), @@ -58,7 +63,9 @@ def test_LongReconAll_inputs(): min_ver="6.0.0", ), long_id=dict( - argstr="-long %s %s", + argstr="-long %s", + position=1, + requires=["base_id"], xor=["subject_id"], ), mprage=dict( From 6971865a5971a08ce7a07ee6c27fa1b34621b0db Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Wed, 14 Dec 2022 08:15:24 -0600 Subject: [PATCH 05/17] Update longitudinal outputs to match separation of ids. --- .zenodo.json | 5 +++++ nipype/interfaces/freesurfer/longitudinal.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index fd9e5e9658..5101a34f22 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -890,6 +890,11 @@ "affiliation": "MIT, HMS", "name": "Ghosh, Satrajit", "orcid": "0000-0002-5312-6729" + }, + { + "affiliation": "Department of Neurosurgery, Medical College of Wisconsin", + "name": "Espana, Lezlie", + "orcid": "0000-0002-6466-4653" } ], "keywords": [ diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index ac76850a6a..f757c41f08 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -405,7 +405,7 @@ def _gen_filename(self, name): return None def _list_outputs(self): - subject_id = f"{self.inputs.long_id[0]}.long.{self.inputs.long_id[1]}" + subject_id = f"{self.inputs.long_id}.long.{self.inputs.base_id}" if isdefined(self.inputs.subjects_dir): subjects_dir = self.inputs.subjects_dir @@ -430,7 +430,7 @@ def _list_outputs(self): def _is_resuming(self): subjects_dir = self.inputs.subjects_dir - subject_id = f"{self.inputs.long_id[0]}.long{self.inputs.long_id[1]}" + subject_id = f"{self.inputs.long_id}.long{self.inputs.base_id}" if not isdefined(subjects_dir): subjects_dir = self._gen_subjects_dir() if os.path.isdir(os.path.join(subjects_dir, subject_id, "mri")): From 2d2072f7ac3efc8976a8d0ef3bb8dbc64c9e9d8e Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Wed, 14 Dec 2022 09:22:56 -0600 Subject: [PATCH 06/17] Update/fix zenodo. --- .zenodo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 5101a34f22..0d9f5a7d82 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -374,6 +374,11 @@ { "name": "Schwartz, Yannick" }, + { + "affiliation": "Medical College of Wisconsin", + "name": "Espana, Lezlie", + "orcid": "0000-0002-6466-4653" + }, { "affiliation": "The University of Iowa", "name": "Ghayoor, Ali", @@ -890,11 +895,6 @@ "affiliation": "MIT, HMS", "name": "Ghosh, Satrajit", "orcid": "0000-0002-5312-6729" - }, - { - "affiliation": "Department of Neurosurgery, Medical College of Wisconsin", - "name": "Espana, Lezlie", - "orcid": "0000-0002-6466-4653" } ], "keywords": [ From d38e98f456940526aa6d35c144b327652ce11173 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Thu, 15 Dec 2022 07:21:49 -0600 Subject: [PATCH 07/17] Remove subject_id input per code review Co-authored-by: Ghislain Vaillant --- nipype/interfaces/freesurfer/longitudinal.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index f757c41f08..9c8bc8fa3d 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -256,7 +256,6 @@ def _list_outputs(self): class BaseReconAllInputSpec(ReconAllInputSpec): - subject_id = traits.Str(argstr="-subjid %s", desc="subject name") base_id = traits.Str(argstr="-base %s", desc="base template name", xor=["subject_id"]) timepoints = InputMultiObject( traits.Str(), argstr="-tp %s...", desc="processed time point to use in template" @@ -352,7 +351,6 @@ def cmdline(self): class LongReconAllInputSpec(ReconAllInputSpec): - subject_id = traits.Str(argstr="-subjid %s", desc="subject name") long_id = traits.Str( argstr="-long %s", desc="longitudinal session/timepoint id", From 725cd8209cccea4c29fad7bee742cf08d1fd3754 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Thu, 15 Dec 2022 07:23:14 -0600 Subject: [PATCH 08/17] Make base recon inputs more explicit per code review Co-authored-by: Ghislain Vaillant --- nipype/interfaces/freesurfer/longitudinal.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index 9c8bc8fa3d..dbf6dc9654 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -256,9 +256,16 @@ def _list_outputs(self): class BaseReconAllInputSpec(ReconAllInputSpec): - base_id = traits.Str(argstr="-base %s", desc="base template name", xor=["subject_id"]) - timepoints = InputMultiObject( - traits.Str(), argstr="-tp %s...", desc="processed time point to use in template" + base_template_id = traits.Str( + argstr="-base %s", + desc="base template name", + mandatory=True, + ) + base_timepoint_ids = InputMultiObject( + traits.Str(), + argstr="-base-tp %s...", + desc="processed time point to use in template", + mandatory=True, ) class BaseReconAllOutputSpec(FreeSurferSource.output_spec): From 7d5253e6f94d19f2853b5aea311238ee96b97d0d Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Thu, 15 Dec 2022 07:24:03 -0600 Subject: [PATCH 09/17] Make longitudinal recon more explicit per code review Co-authored-by: Ghislain Vaillant --- nipype/interfaces/freesurfer/longitudinal.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index dbf6dc9654..5d015b02fe 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -358,17 +358,16 @@ def cmdline(self): class LongReconAllInputSpec(ReconAllInputSpec): - long_id = traits.Str( + longitudinal_timepoint_id = traits.Str( argstr="-long %s", desc="longitudinal session/timepoint id", - xor=["subject_id"], - requires=["base_id"], + mandatory=True position=1 ) - base_id = traits.Str( + longitudinal_template_id = traits.Str( argstr="%s", desc="longitudinal base template id", - requires=["long_id"], + mandatory=True, position=2 ) From bdc39fc3806880c879ad226cd4b698afe5cd0e79 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Thu, 15 Dec 2022 07:24:20 -0600 Subject: [PATCH 10/17] Fix typo Co-authored-by: Ghislain Vaillant --- nipype/interfaces/freesurfer/longitudinal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index 5d015b02fe..6da744917a 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -434,7 +434,7 @@ def _list_outputs(self): def _is_resuming(self): subjects_dir = self.inputs.subjects_dir - subject_id = f"{self.inputs.long_id}.long{self.inputs.base_id}" + subject_id = f"{self.inputs.long_id}.long.{self.inputs.base_id}" if not isdefined(subjects_dir): subjects_dir = self._gen_subjects_dir() if os.path.isdir(os.path.join(subjects_dir, subject_id, "mri")): From 78a5feb22f0d1ce6421fe1481bf0ced132cf4685 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Wed, 28 Dec 2022 13:16:18 -0600 Subject: [PATCH 11/17] Refactor longitudinal pipeline to live within ReconAll interface. --- nipype/interfaces/freesurfer/longitudinal.py | 208 ----------- nipype/interfaces/freesurfer/preprocess.py | 98 +++++- .../tests/test_auto_BaseReconAll.py | 329 ----------------- .../tests/test_auto_LongReconAll.py | 333 ------------------ .../freesurfer/tests/test_auto_ReconAll.py | 19 +- 5 files changed, 105 insertions(+), 882 deletions(-) delete mode 100644 nipype/interfaces/freesurfer/tests/test_auto_BaseReconAll.py delete mode 100644 nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index 6da744917a..bac9e5875d 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -253,211 +253,3 @@ def _list_outputs(self): outputs = self.output_spec().get() outputs["out_file"] = os.path.abspath(self.inputs.out_file) return outputs - - -class BaseReconAllInputSpec(ReconAllInputSpec): - base_template_id = traits.Str( - argstr="-base %s", - desc="base template name", - mandatory=True, - ) - base_timepoint_ids = InputMultiObject( - traits.Str(), - argstr="-base-tp %s...", - desc="processed time point to use in template", - mandatory=True, - ) - -class BaseReconAllOutputSpec(FreeSurferSource.output_spec): - subjects_dir = Directory(exists=True, desc="FreeSurfer subjects directory") - subject_id = traits.Str(desc="Subject template name") - -class BaseReconAll(CommandLine): - """Uses the longitudinal pipeline of recon-all to create a template for a given number of subject's sessions. - - Examples - -------- - >>> from nipype.interfaces.freesurfer.longitudinal import BaseReconAll - >>> baserecon = BaseReconAll() - >>> baserecon.inputs.base_id = 'sub-template' - >>> baserecon.inputs.timepoints = ['ses-1','ses-2'] - >>> baserecon.inputs.directive = 'all' - >>> baserecon.inputs.subjects_dir = '.' - >>> baserecon.cmdline - 'recon-all -all -base sub-template -sd . -tp ses-1 -tp ses-2' - """ - - _cmd = "recon-all" - input_spec = BaseReconAllInputSpec - output_spec = BaseReconAllOutputSpec - _can_resume = True - force_run = False - - def _gen_subjects_dir(self): - return os.getcwd() - - - def _gen_filename(self, name): - if name == "subjects_dir": - return self._gen_subjects_dir() - return None - - - def _list_outputs(self): - if isdefined(self.inputs.subjects_dir): - subjects_dir = self.inputs.subjects_dir - else: - subjects_dir = self._gen_subjects_dir() - - if isdefined(self.inputs.hemi): - hemi = self.inputs.hemi - else: - hemi = "both" - - outputs = self._outputs().get() - - outputs.update( - FreeSurferSource(subject_id=self.inputs.base_id, - subjects_dir=subjects_dir, hemi=hemi)._list_outputs() - ) - outputs["subject_id"] = self.inputs.base_id - outputs["subjects_dir"] = subjects_dir - return outputs - - - def _is_resuming(self): - subjects_dir = self.inputs.subjects_dir - if not isdefined(subjects_dir): - subjects_dir = self._gen_subjects_dir() - if os.path.isdir(os.path.join(subjects_dir, self.inputs.base_id, "mri")): - return True - return False - - - def _format_arg(self, name, trait_spec, value): - return super(BaseReconAll, self)._format_arg(name, trait_spec, value) - - - @property - def cmdline(self): - cmd = super(BaseReconAll, self).cmdline - - if not self._is_resuming(): - return cmd - - subjects_dir = self.inputs.subjects_dir - if not isdefined(subjects_dir): - subjects_dir = self._gen_subjects_dir() - - directive = self.inputs.directive - if not isdefined(directive): - steps = [] - - iflogger.info(f"recon-all: {cmd}") - return cmd - - -class LongReconAllInputSpec(ReconAllInputSpec): - longitudinal_timepoint_id = traits.Str( - argstr="-long %s", - desc="longitudinal session/timepoint id", - mandatory=True - position=1 - ) - longitudinal_template_id = traits.Str( - argstr="%s", - desc="longitudinal base template id", - mandatory=True, - position=2 - ) - - -class LongReconAllOutputSpec(FreeSurferSource.output_spec): - subjects_dir = Directory(exists=True, desc="FreeSurfer subjects directory") - subject_id = traits.Str(desc="Subject template name") - - -class LongReconAll(CommandLine): - """Uses FreeSurfer's longitudinal recon-all to process a subject given - the previously processed base template. - - Examples - --------- - - >>> from nipype.interfaces.freesurfer.longitudinal import LongReconAll - >>> longrecon = LongReconAll() - >>> longrecon.inputs.long_id = "ses-1" - >>> longrecon.inputs.base_id = "sub-template" - >>> longrecon.inputs.directive = "all" - >>> longrecon.inputs.subjects_dir = "." - >>> longrecon.cmdline - 'recon-all -all -long ses-1 sub-template -sd .' - """ - - _cmd = "recon-all" - input_spec = LongReconAllInputSpec - output_spec = LongReconAllOutputSpec - _can_resume = True - force_run = False - - def _gen_subjects_dir(self): - return os.getcwd() - - def _gen_filename(self, name): - if name == "subjects_dir": - return self._gen_subjects_dir() - return None - - def _list_outputs(self): - subject_id = f"{self.inputs.long_id}.long.{self.inputs.base_id}" - - if isdefined(self.inputs.subjects_dir): - subjects_dir = self.inputs.subjects_dir - else: - subjects_dir = self._gen_subjects_dir() - - if isdefined(self.inputs.hemi): - hemi = self.inputs.hemi - else: - hemi = "both" - - outputs = self._outputs().get() - - outputs.update( - FreeSurferSource( - subject_id=subject_id, subjects_dir=subjects_dir, hemi=hemi - )._list_outputs() - ) - outputs["subject_id"] = subject_id - outputs["subjects_dir"] = subjects_dir - return outputs - - def _is_resuming(self): - subjects_dir = self.inputs.subjects_dir - subject_id = f"{self.inputs.long_id}.long.{self.inputs.base_id}" - if not isdefined(subjects_dir): - subjects_dir = self._gen_subjects_dir() - if os.path.isdir(os.path.join(subjects_dir, subject_id, "mri")): - return True - return False - - def _format_arg(self, name, trait_spec, value): - return super(LongReconAll, self)._format_arg(name, trait_spec, value) - - @property - def cmdline(self): - cmd = super(LongReconAll, self).cmdline - - if not self._is_resuming(): - return cmd - - subjects_dir = self.inputs.subjects_dir - if not isdefined(subjects_dir): - subjects_dir = self._gen_subjects_dir() - - directive = self.inputs.directive - if not isdefined(directive): - steps = [] - - iflogger.info(f"recon-all: {cmd}") - return cmd diff --git a/nipype/interfaces/freesurfer/preprocess.py b/nipype/interfaces/freesurfer/preprocess.py index 4c9009cd10..8999799843 100644 --- a/nipype/interfaces/freesurfer/preprocess.py +++ b/nipype/interfaces/freesurfer/preprocess.py @@ -25,6 +25,7 @@ CommandLine, CommandLineInputSpec, isdefined, + InputMultiObject, ) from .base import FSCommand, FSTraitedSpec, FSTraitedSpecOpenMP, FSCommandOpenMP, Info from .utils import copy2subjdir @@ -816,7 +817,7 @@ def _gen_filename(self, name): class ReconAllInputSpec(CommandLineInputSpec): subject_id = traits.Str( - "recon_all", argstr="-subjid %s", desc="subject name", usedefault=True + "recon_all", argstr="-subjid %s", desc="subject name", ) directive = traits.Enum( "all", @@ -927,6 +928,31 @@ class ReconAllInputSpec(CommandLineInputSpec): ) flags = InputMultiPath(traits.Str, argstr="%s", desc="additional parameters") + # Longitudinal runs + base_template_id = traits.Str( + argstr="-base %s", + desc="base template id", + xor=["subject_id","longitudinal_timepoint_id"], + requires=["base_timepoint_ids"], + ) + base_timepoint_ids = InputMultiObject( + traits.Str(), + argstr="-base-tp %s...", + desc="processed timepoint to use in template", + ) + longitudinal_timepoint_id = traits.Str( + argstr="-long %s", + desc="longitudinal session/timepoint id", + xor=["subject_id","base_template_id"], + requires=["longitudinal_template_id"], + position=1 + ) + longitudinal_template_id = traits.Str( + argstr="%s", + desc="longitudinal base tempalte id", + position=2 + ) + # Expert options talairach = traits.Str(desc="Flags to pass to talairach commands", xor=["expert"]) mri_normalize = traits.Str( @@ -1019,7 +1045,7 @@ class ReconAll(CommandLine): >>> reconall.inputs.subject_id = 'foo' >>> reconall.inputs.directive = 'all' >>> reconall.inputs.subjects_dir = '.' - >>> reconall.inputs.T1_files = 'structural.nii' + >>> reconall.inputs.T1_files = ['structural.nii'] >>> reconall.cmdline 'recon-all -all -i structural.nii -subjid foo -sd .' >>> reconall.inputs.flags = "-qcache" @@ -1049,7 +1075,7 @@ class ReconAll(CommandLine): >>> reconall_subfields.inputs.subject_id = 'foo' >>> reconall_subfields.inputs.directive = 'all' >>> reconall_subfields.inputs.subjects_dir = '.' - >>> reconall_subfields.inputs.T1_files = 'structural.nii' + >>> reconall_subfields.inputs.T1_files = ['structural.nii'] >>> reconall_subfields.inputs.hippocampal_subfields_T1 = True >>> reconall_subfields.cmdline 'recon-all -all -i structural.nii -hippocampal-subfields-T1 -subjid foo -sd .' @@ -1060,6 +1086,24 @@ class ReconAll(CommandLine): >>> reconall_subfields.inputs.hippocampal_subfields_T1 = False >>> reconall_subfields.cmdline 'recon-all -all -i structural.nii -hippocampal-subfields-T2 structural.nii test -subjid foo -sd .' + + Base template creation for longitudinal pipeline: + >>> baserecon = ReconAll() + >>> baserecon.inputs.base_template_id = 'sub-template' + >>> baserecon.inputs.base_timepoint_ids = ['ses-1','ses-2'] + >>> baserecon.inputs.directive = 'all' + >>> baserecon.inputs.subjects_dir = '.' + >>> baserecon.cmdline + 'recon-all -all -base sub-template -base-tp ses-1 -base-tp ses-2 -sd .' + + Longitudinal timepoint run: + >>> longrecon = ReconAll() + >>> longrecon.inputs.longitudinal_timepoint_id = 'ses-1' + >>> longrecon.inputs.longitudinal_template_id = 'sub-template' + >>> longrecon.inputs.directive = 'all' + >>> longrecon.inputs.subjects_dir = '.' + >>> longrecon.cmdline + 'recon-all -all -long ses-1 sub-template -sd .' """ _cmd = "recon-all" @@ -1523,12 +1567,32 @@ def _list_outputs(self): outputs = self._outputs().get() - outputs.update( - FreeSurferSource( - subject_id=self.inputs.subject_id, subjects_dir=subjects_dir, hemi=hemi - )._list_outputs() - ) - outputs["subject_id"] = self.inputs.subject_id + # If using longitudinal pipeline, update subject id accordingly, + # otherwise use original/default subject_id + if isdefined(self.inputs.base_template_id): + outputs.update( + FreeSurferSource( + subject_id=self.inputs.base_template_id, subjects_dir=subjects_dir, + hemi=hemi + )._list_outputs() + ) + outputs["subject_id"] = self.inputs.base_template_id + elif isdefined(self.inputs.longitudinal_timepoint_id): + subject_id=f"{self.inputs.longitudinal_timepoint_id}.long.{self.inputs.longitudinal_template_id}" + outputs.update( + FreeSurferSource( + subject_id=subject_id, subjects_id=subjects_dir, hemi=hemi + )._list_outputs() + ) + outputs["subject_id"] = subject_id + else: + outputs.update( + FreeSurferSource( + subject_id=self.inputs.subject_id, subjects_dir=subjects_dir, hemi=hemi + )._list_outputs() + ) + outputs["subject_id"] = self.inputs.subject_id + outputs["subjects_dir"] = subjects_dir return outputs @@ -1536,8 +1600,20 @@ def _is_resuming(self): subjects_dir = self.inputs.subjects_dir if not isdefined(subjects_dir): subjects_dir = self._gen_subjects_dir() - if os.path.isdir(os.path.join(subjects_dir, self.inputs.subject_id, "mri")): - return True + + # Check for longitudinal pipeline + if not isdefined(self.inputs.subject_id): + if isdefined(self.inputs.base_template_id): + if os.path.isdir(os.path.join(subjects_dir, self.inputs.base_template_id, "mri")): + return True + elif isdefined(self.inputs.longitudinal_template_id): + if os.path.isdir(os.path.join(subjects_dir, + f"{self.inputs.longitudinal_timepoint_id}.long.{self.inputs.longitudinal_template_id}", + "mri")): + return True + else: + if os.path.isdir(os.path.join(subjects_dir, self.inputs.subject_id, "mri")): + return True return False def _format_arg(self, name, trait_spec, value): diff --git a/nipype/interfaces/freesurfer/tests/test_auto_BaseReconAll.py b/nipype/interfaces/freesurfer/tests/test_auto_BaseReconAll.py deleted file mode 100644 index 034ea5925f..0000000000 --- a/nipype/interfaces/freesurfer/tests/test_auto_BaseReconAll.py +++ /dev/null @@ -1,329 +0,0 @@ -# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT -from ..longitudinal import BaseReconAll - - -def test_BaseReconAll_inputs(): - input_map = dict( - FLAIR_file=dict( - argstr="-FLAIR %s", - extensions=None, - min_ver="5.3.0", - ), - T1_files=dict( - argstr="-i %s...", - ), - T2_file=dict( - argstr="-T2 %s", - extensions=None, - min_ver="5.3.0", - ), - args=dict( - argstr="%s", - ), - base_id=dict( - argstr="-base %s", - xor=["subject_id"], - ), - big_ventricles=dict( - argstr="-bigventricles", - ), - brainstem=dict( - argstr="-brainstem-structures", - ), - directive=dict( - argstr="-%s", - position=0, - usedefault=True, - ), - environ=dict( - nohash=True, - usedefault=True, - ), - expert=dict( - argstr="-expert %s", - extensions=None, - ), - flags=dict( - argstr="%s", - ), - hemi=dict( - argstr="-hemi %s", - ), - hippocampal_subfields_T1=dict( - argstr="-hippocampal-subfields-T1", - min_ver="6.0.0", - ), - hippocampal_subfields_T2=dict( - argstr="-hippocampal-subfields-T2 %s %s", - min_ver="6.0.0", - ), - hires=dict( - argstr="-hires", - min_ver="6.0.0", - ), - mprage=dict( - argstr="-mprage", - ), - mri_aparc2aseg=dict( - xor=["expert"], - ), - mri_ca_label=dict( - xor=["expert"], - ), - mri_ca_normalize=dict( - xor=["expert"], - ), - mri_ca_register=dict( - xor=["expert"], - ), - mri_edit_wm_with_aseg=dict( - xor=["expert"], - ), - mri_em_register=dict( - xor=["expert"], - ), - mri_fill=dict( - xor=["expert"], - ), - mri_mask=dict( - xor=["expert"], - ), - mri_normalize=dict( - xor=["expert"], - ), - mri_pretess=dict( - xor=["expert"], - ), - mri_remove_neck=dict( - xor=["expert"], - ), - mri_segment=dict( - xor=["expert"], - ), - mri_segstats=dict( - xor=["expert"], - ), - mri_tessellate=dict( - xor=["expert"], - ), - mri_watershed=dict( - xor=["expert"], - ), - mris_anatomical_stats=dict( - xor=["expert"], - ), - mris_ca_label=dict( - xor=["expert"], - ), - mris_fix_topology=dict( - xor=["expert"], - ), - mris_inflate=dict( - xor=["expert"], - ), - mris_make_surfaces=dict( - xor=["expert"], - ), - mris_register=dict( - xor=["expert"], - ), - mris_smooth=dict( - xor=["expert"], - ), - mris_sphere=dict( - xor=["expert"], - ), - mris_surf2vol=dict( - xor=["expert"], - ), - mrisp_paint=dict( - xor=["expert"], - ), - openmp=dict( - argstr="-openmp %d", - ), - parallel=dict( - argstr="-parallel", - ), - subject_id=dict( - argstr="-subjid %s", - ), - subjects_dir=dict( - argstr="-sd %s", - genfile=True, - hash_files=False, - ), - talairach=dict( - xor=["expert"], - ), - timepoints=dict( - argstr="-tp %s...", - ), - use_FLAIR=dict( - argstr="-FLAIRpial", - min_ver="5.3.0", - xor=["use_T2"], - ), - use_T2=dict( - argstr="-T2pial", - min_ver="5.3.0", - xor=["use_FLAIR"], - ), - xopts=dict( - argstr="-xopts-%s", - ), - ) - inputs = BaseReconAll.input_spec() - - for key, metadata in list(input_map.items()): - for metakey, value in list(metadata.items()): - assert getattr(inputs.traits()[key], metakey) == value - - -def test_BaseReconAll_outputs(): - output_map = dict( - BA_stats=dict( - altkey="BA", - loc="stats", - ), - T1=dict( - extensions=None, - loc="mri", - ), - annot=dict( - altkey="*annot", - loc="label", - ), - aparc_a2009s_stats=dict( - altkey="aparc.a2009s", - loc="stats", - ), - aparc_aseg=dict( - altkey="aparc*aseg", - loc="mri", - ), - aparc_stats=dict( - altkey="aparc", - loc="stats", - ), - area_pial=dict( - altkey="area.pial", - loc="surf", - ), - aseg=dict( - extensions=None, - loc="mri", - ), - aseg_stats=dict( - altkey="aseg", - loc="stats", - ), - avg_curv=dict( - loc="surf", - ), - brain=dict( - extensions=None, - loc="mri", - ), - brainmask=dict( - extensions=None, - loc="mri", - ), - curv=dict( - loc="surf", - ), - curv_pial=dict( - altkey="curv.pial", - loc="surf", - ), - curv_stats=dict( - altkey="curv", - loc="stats", - ), - entorhinal_exvivo_stats=dict( - altkey="entorhinal_exvivo", - loc="stats", - ), - filled=dict( - extensions=None, - loc="mri", - ), - graymid=dict( - altkey=["graymid", "midthickness"], - loc="surf", - ), - inflated=dict( - loc="surf", - ), - jacobian_white=dict( - loc="surf", - ), - label=dict( - altkey="*label", - loc="label", - ), - norm=dict( - extensions=None, - loc="mri", - ), - nu=dict( - extensions=None, - loc="mri", - ), - orig=dict( - extensions=None, - loc="mri", - ), - pial=dict( - loc="surf", - ), - rawavg=dict( - extensions=None, - loc="mri", - ), - ribbon=dict( - altkey="*ribbon", - loc="mri", - ), - smoothwm=dict( - loc="surf", - ), - sphere=dict( - loc="surf", - ), - sphere_reg=dict( - altkey="sphere.reg", - loc="surf", - ), - subject_id=dict(), - subjects_dir=dict(), - sulc=dict( - loc="surf", - ), - thickness=dict( - loc="surf", - ), - volume=dict( - loc="surf", - ), - white=dict( - loc="surf", - ), - wm=dict( - extensions=None, - loc="mri", - ), - wmparc=dict( - extensions=None, - loc="mri", - ), - wmparc_stats=dict( - altkey="wmparc", - loc="stats", - ), - ) - outputs = BaseReconAll.output_spec() - - for key, metadata in list(output_map.items()): - for metakey, value in list(metadata.items()): - assert getattr(outputs.traits()[key], metakey) == value diff --git a/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py b/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py deleted file mode 100644 index 61c15f310c..0000000000 --- a/nipype/interfaces/freesurfer/tests/test_auto_LongReconAll.py +++ /dev/null @@ -1,333 +0,0 @@ -# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT -from ..longitudinal import LongReconAll - - -def test_LongReconAll_inputs(): - input_map = dict( - FLAIR_file=dict( - argstr="-FLAIR %s", - extensions=None, - min_ver="5.3.0", - ), - T1_files=dict( - argstr="-i %s...", - ), - T2_file=dict( - argstr="-T2 %s", - extensions=None, - min_ver="5.3.0", - ), - args=dict( - argstr="%s", - ), - base_id=dict( - argstr="%s", - position=2, - requires=["long_id"], - ), - big_ventricles=dict( - argstr="-bigventricles", - ), - brainstem=dict( - argstr="-brainstem-structures", - ), - directive=dict( - argstr="-%s", - position=0, - usedefault=True, - ), - environ=dict( - nohash=True, - usedefault=True, - ), - expert=dict( - argstr="-expert %s", - extensions=None, - ), - flags=dict( - argstr="%s", - ), - hemi=dict( - argstr="-hemi %s", - ), - hippocampal_subfields_T1=dict( - argstr="-hippocampal-subfields-T1", - min_ver="6.0.0", - ), - hippocampal_subfields_T2=dict( - argstr="-hippocampal-subfields-T2 %s %s", - min_ver="6.0.0", - ), - hires=dict( - argstr="-hires", - min_ver="6.0.0", - ), - long_id=dict( - argstr="-long %s", - position=1, - requires=["base_id"], - xor=["subject_id"], - ), - mprage=dict( - argstr="-mprage", - ), - mri_aparc2aseg=dict( - xor=["expert"], - ), - mri_ca_label=dict( - xor=["expert"], - ), - mri_ca_normalize=dict( - xor=["expert"], - ), - mri_ca_register=dict( - xor=["expert"], - ), - mri_edit_wm_with_aseg=dict( - xor=["expert"], - ), - mri_em_register=dict( - xor=["expert"], - ), - mri_fill=dict( - xor=["expert"], - ), - mri_mask=dict( - xor=["expert"], - ), - mri_normalize=dict( - xor=["expert"], - ), - mri_pretess=dict( - xor=["expert"], - ), - mri_remove_neck=dict( - xor=["expert"], - ), - mri_segment=dict( - xor=["expert"], - ), - mri_segstats=dict( - xor=["expert"], - ), - mri_tessellate=dict( - xor=["expert"], - ), - mri_watershed=dict( - xor=["expert"], - ), - mris_anatomical_stats=dict( - xor=["expert"], - ), - mris_ca_label=dict( - xor=["expert"], - ), - mris_fix_topology=dict( - xor=["expert"], - ), - mris_inflate=dict( - xor=["expert"], - ), - mris_make_surfaces=dict( - xor=["expert"], - ), - mris_register=dict( - xor=["expert"], - ), - mris_smooth=dict( - xor=["expert"], - ), - mris_sphere=dict( - xor=["expert"], - ), - mris_surf2vol=dict( - xor=["expert"], - ), - mrisp_paint=dict( - xor=["expert"], - ), - openmp=dict( - argstr="-openmp %d", - ), - parallel=dict( - argstr="-parallel", - ), - subject_id=dict( - argstr="-subjid %s", - ), - subjects_dir=dict( - argstr="-sd %s", - genfile=True, - hash_files=False, - ), - talairach=dict( - xor=["expert"], - ), - use_FLAIR=dict( - argstr="-FLAIRpial", - min_ver="5.3.0", - xor=["use_T2"], - ), - use_T2=dict( - argstr="-T2pial", - min_ver="5.3.0", - xor=["use_FLAIR"], - ), - xopts=dict( - argstr="-xopts-%s", - ), - ) - inputs = LongReconAll.input_spec() - - for key, metadata in list(input_map.items()): - for metakey, value in list(metadata.items()): - assert getattr(inputs.traits()[key], metakey) == value - - -def test_LongReconAll_outputs(): - output_map = dict( - BA_stats=dict( - altkey="BA", - loc="stats", - ), - T1=dict( - extensions=None, - loc="mri", - ), - annot=dict( - altkey="*annot", - loc="label", - ), - aparc_a2009s_stats=dict( - altkey="aparc.a2009s", - loc="stats", - ), - aparc_aseg=dict( - altkey="aparc*aseg", - loc="mri", - ), - aparc_stats=dict( - altkey="aparc", - loc="stats", - ), - area_pial=dict( - altkey="area.pial", - loc="surf", - ), - aseg=dict( - extensions=None, - loc="mri", - ), - aseg_stats=dict( - altkey="aseg", - loc="stats", - ), - avg_curv=dict( - loc="surf", - ), - brain=dict( - extensions=None, - loc="mri", - ), - brainmask=dict( - extensions=None, - loc="mri", - ), - curv=dict( - loc="surf", - ), - curv_pial=dict( - altkey="curv.pial", - loc="surf", - ), - curv_stats=dict( - altkey="curv", - loc="stats", - ), - entorhinal_exvivo_stats=dict( - altkey="entorhinal_exvivo", - loc="stats", - ), - filled=dict( - extensions=None, - loc="mri", - ), - graymid=dict( - altkey=["graymid", "midthickness"], - loc="surf", - ), - inflated=dict( - loc="surf", - ), - jacobian_white=dict( - loc="surf", - ), - label=dict( - altkey="*label", - loc="label", - ), - norm=dict( - extensions=None, - loc="mri", - ), - nu=dict( - extensions=None, - loc="mri", - ), - orig=dict( - extensions=None, - loc="mri", - ), - pial=dict( - loc="surf", - ), - rawavg=dict( - extensions=None, - loc="mri", - ), - ribbon=dict( - altkey="*ribbon", - loc="mri", - ), - smoothwm=dict( - loc="surf", - ), - sphere=dict( - loc="surf", - ), - sphere_reg=dict( - altkey="sphere.reg", - loc="surf", - ), - subject_id=dict(), - subjects_dir=dict(), - sulc=dict( - loc="surf", - ), - thickness=dict( - loc="surf", - ), - volume=dict( - loc="surf", - ), - white=dict( - loc="surf", - ), - wm=dict( - extensions=None, - loc="mri", - ), - wmparc=dict( - extensions=None, - loc="mri", - ), - wmparc_stats=dict( - altkey="wmparc", - loc="stats", - ), - ) - outputs = LongReconAll.output_spec() - - for key, metadata in list(output_map.items()): - for metakey, value in list(metadata.items()): - assert getattr(outputs.traits()[key], metakey) == value diff --git a/nipype/interfaces/freesurfer/tests/test_auto_ReconAll.py b/nipype/interfaces/freesurfer/tests/test_auto_ReconAll.py index aa270f30b3..c2756c9e5c 100644 --- a/nipype/interfaces/freesurfer/tests/test_auto_ReconAll.py +++ b/nipype/interfaces/freesurfer/tests/test_auto_ReconAll.py @@ -20,6 +20,14 @@ def test_ReconAll_inputs(): args=dict( argstr="%s", ), + base_template_id=dict( + argstr="-base %s", + requires=["base_timepoint_ids"], + xor=["subject_id", "longitudinal_timepoint_id"], + ), + base_timepoint_ids=dict( + argstr="-base-tp %s...", + ), big_ventricles=dict( argstr="-bigventricles", ), @@ -57,6 +65,16 @@ def test_ReconAll_inputs(): argstr="-hires", min_ver="6.0.0", ), + longitudinal_template_id=dict( + argstr="%s", + position=2, + ), + longitudinal_timepoint_id=dict( + argstr="-long %s", + position=1, + requires=["longitudinal_template_id"], + xor=["subject_id", "base_template_id"], + ), mprage=dict( argstr="-mprage", ), @@ -143,7 +161,6 @@ def test_ReconAll_inputs(): ), subject_id=dict( argstr="-subjid %s", - usedefault=True, ), subjects_dir=dict( argstr="-sd %s", From c82b27903b81edfdbea2de2d7fa45c17a8aeff28 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Wed, 28 Dec 2022 14:20:48 -0600 Subject: [PATCH 12/17] Black formatting. --- nipype/interfaces/freesurfer/longitudinal.py | 19 +++++++++- nipype/interfaces/freesurfer/preprocess.py | 39 ++++++++++++-------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/nipype/interfaces/freesurfer/longitudinal.py b/nipype/interfaces/freesurfer/longitudinal.py index bac9e5875d..df281e2ecd 100644 --- a/nipype/interfaces/freesurfer/longitudinal.py +++ b/nipype/interfaces/freesurfer/longitudinal.py @@ -7,8 +7,23 @@ import os from ... import logging -from ..base import TraitedSpec, File, traits, InputMultiPath, OutputMultiPath, isdefined, InputMultiObject, Directory -from .base import FSCommand, FSTraitedSpec, FSCommandOpenMP, FSTraitedSpecOpenMP, CommandLine +from ..base import ( + TraitedSpec, + File, + traits, + InputMultiPath, + OutputMultiPath, + isdefined, + InputMultiObject, + Directory, +) +from .base import ( + FSCommand, + FSTraitedSpec, + FSCommandOpenMP, + FSTraitedSpecOpenMP, + CommandLine, +) from .preprocess import ReconAllInputSpec from ..io import FreeSurferSource diff --git a/nipype/interfaces/freesurfer/preprocess.py b/nipype/interfaces/freesurfer/preprocess.py index 8999799843..0f0beaf5e7 100644 --- a/nipype/interfaces/freesurfer/preprocess.py +++ b/nipype/interfaces/freesurfer/preprocess.py @@ -817,7 +817,9 @@ def _gen_filename(self, name): class ReconAllInputSpec(CommandLineInputSpec): subject_id = traits.Str( - "recon_all", argstr="-subjid %s", desc="subject name", + "recon_all", + argstr="-subjid %s", + desc="subject name", ) directive = traits.Enum( "all", @@ -932,7 +934,7 @@ class ReconAllInputSpec(CommandLineInputSpec): base_template_id = traits.Str( argstr="-base %s", desc="base template id", - xor=["subject_id","longitudinal_timepoint_id"], + xor=["subject_id", "longitudinal_timepoint_id"], requires=["base_timepoint_ids"], ) base_timepoint_ids = InputMultiObject( @@ -943,14 +945,12 @@ class ReconAllInputSpec(CommandLineInputSpec): longitudinal_timepoint_id = traits.Str( argstr="-long %s", desc="longitudinal session/timepoint id", - xor=["subject_id","base_template_id"], + xor=["subject_id", "base_template_id"], requires=["longitudinal_template_id"], - position=1 + position=1, ) longitudinal_template_id = traits.Str( - argstr="%s", - desc="longitudinal base tempalte id", - position=2 + argstr="%s", desc="longitudinal base tempalte id", position=2 ) # Expert options @@ -1572,13 +1572,14 @@ def _list_outputs(self): if isdefined(self.inputs.base_template_id): outputs.update( FreeSurferSource( - subject_id=self.inputs.base_template_id, subjects_dir=subjects_dir, - hemi=hemi + subject_id=self.inputs.base_template_id, + subjects_dir=subjects_dir, + hemi=hemi, )._list_outputs() ) outputs["subject_id"] = self.inputs.base_template_id elif isdefined(self.inputs.longitudinal_timepoint_id): - subject_id=f"{self.inputs.longitudinal_timepoint_id}.long.{self.inputs.longitudinal_template_id}" + subject_id = f"{self.inputs.longitudinal_timepoint_id}.long.{self.inputs.longitudinal_template_id}" outputs.update( FreeSurferSource( subject_id=subject_id, subjects_id=subjects_dir, hemi=hemi @@ -1588,7 +1589,9 @@ def _list_outputs(self): else: outputs.update( FreeSurferSource( - subject_id=self.inputs.subject_id, subjects_dir=subjects_dir, hemi=hemi + subject_id=self.inputs.subject_id, + subjects_dir=subjects_dir, + hemi=hemi, )._list_outputs() ) outputs["subject_id"] = self.inputs.subject_id @@ -1604,12 +1607,18 @@ def _is_resuming(self): # Check for longitudinal pipeline if not isdefined(self.inputs.subject_id): if isdefined(self.inputs.base_template_id): - if os.path.isdir(os.path.join(subjects_dir, self.inputs.base_template_id, "mri")): + if os.path.isdir( + os.path.join(subjects_dir, self.inputs.base_template_id, "mri") + ): return True elif isdefined(self.inputs.longitudinal_template_id): - if os.path.isdir(os.path.join(subjects_dir, - f"{self.inputs.longitudinal_timepoint_id}.long.{self.inputs.longitudinal_template_id}", - "mri")): + if os.path.isdir( + os.path.join( + subjects_dir, + f"{self.inputs.longitudinal_timepoint_id}.long.{self.inputs.longitudinal_template_id}", + "mri", + ) + ): return True else: if os.path.isdir(os.path.join(subjects_dir, self.inputs.subject_id, "mri")): From c52cf92c4f0b3f041f82227803f24fb5224ab797 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Wed, 4 Jan 2023 11:07:10 -0600 Subject: [PATCH 13/17] Fix list_outputs typo. --- nipype/interfaces/freesurfer/preprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/freesurfer/preprocess.py b/nipype/interfaces/freesurfer/preprocess.py index 0f0beaf5e7..8412376f1d 100644 --- a/nipype/interfaces/freesurfer/preprocess.py +++ b/nipype/interfaces/freesurfer/preprocess.py @@ -1582,7 +1582,7 @@ def _list_outputs(self): subject_id = f"{self.inputs.longitudinal_timepoint_id}.long.{self.inputs.longitudinal_template_id}" outputs.update( FreeSurferSource( - subject_id=subject_id, subjects_id=subjects_dir, hemi=hemi + subject_id=subject_id, subjects_dir=subjects_dir, hemi=hemi )._list_outputs() ) outputs["subject_id"] = subject_id From 262bfb4354b718bd2fbc91f9113413f20275b78b Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Wed, 12 Apr 2023 14:21:15 -0500 Subject: [PATCH 14/17] Additional xor/requires constraints --- nipype/interfaces/freesurfer/preprocess.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/nipype/interfaces/freesurfer/preprocess.py b/nipype/interfaces/freesurfer/preprocess.py index 8412376f1d..ba8ddf2551 100644 --- a/nipype/interfaces/freesurfer/preprocess.py +++ b/nipype/interfaces/freesurfer/preprocess.py @@ -820,6 +820,7 @@ class ReconAllInputSpec(CommandLineInputSpec): "recon_all", argstr="-subjid %s", desc="subject name", + xor=["base_template_id","longitudinal_timepoint_id"], ) directive = traits.Enum( "all", @@ -845,21 +846,28 @@ class ReconAllInputSpec(CommandLineInputSpec): usedefault=True, position=0, ) - hemi = traits.Enum("lh", "rh", desc="hemisphere to process", argstr="-hemi %s") + hemi = traits.Enum("lh", "rh", + desc="hemisphere to process", + argstr="-hemi %s", + requires=["subject_id"], + ) T1_files = InputMultiPath( - File(exists=True), argstr="-i %s...", desc="name of T1 file to process" + File(exists=True), argstr="-i %s...", desc="name of T1 file to process", + requires=["subject_id"], ) T2_file = File( exists=True, argstr="-T2 %s", min_ver="5.3.0", desc="Convert T2 image to orig directory", + requires=["subject_id"], ) FLAIR_file = File( exists=True, argstr="-FLAIR %s", min_ver="5.3.0", desc="Convert FLAIR image to orig directory", + requires=["subject_id"] ) use_T2 = traits.Bool( argstr="-T2pial", @@ -888,18 +896,21 @@ class ReconAllInputSpec(CommandLineInputSpec): "Assume scan parameters are MGH MP-RAGE " "protocol, which produces darker gray matter" ), + requires=["subject_id"], ) big_ventricles = traits.Bool( argstr="-bigventricles", desc=("For use in subjects with enlarged " "ventricles"), ) brainstem = traits.Bool( - argstr="-brainstem-structures", desc="Segment brainstem structures" + argstr="-brainstem-structures", desc="Segment brainstem structures", + requires=["subject_id"], ) hippocampal_subfields_T1 = traits.Bool( argstr="-hippocampal-subfields-T1", min_ver="6.0.0", desc="segment hippocampal subfields using input T1 scan", + requires=["subject_id"], ) hippocampal_subfields_T2 = traits.Tuple( File(exists=True), @@ -910,6 +921,7 @@ class ReconAllInputSpec(CommandLineInputSpec): "segment hippocampal subfields using T2 scan, identified by " "ID (may be combined with hippocampal_subfields_T1)" ), + requires=["subject_id"], ) expert = File( exists=True, argstr="-expert %s", desc="Set parameters using expert file" From 46e07bb3a976485809421954101fe373836a9ef0 Mon Sep 17 00:00:00 2001 From: Lezlie Espana Date: Fri, 14 Apr 2023 06:56:44 -0500 Subject: [PATCH 15/17] Apply formatting suggestions from code review Co-authored-by: Ghislain Vaillant --- nipype/interfaces/freesurfer/preprocess.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nipype/interfaces/freesurfer/preprocess.py b/nipype/interfaces/freesurfer/preprocess.py index ba8ddf2551..975610dd60 100644 --- a/nipype/interfaces/freesurfer/preprocess.py +++ b/nipype/interfaces/freesurfer/preprocess.py @@ -867,7 +867,7 @@ class ReconAllInputSpec(CommandLineInputSpec): argstr="-FLAIR %s", min_ver="5.3.0", desc="Convert FLAIR image to orig directory", - requires=["subject_id"] + requires=["subject_id"], ) use_T2 = traits.Bool( argstr="-T2pial", @@ -903,7 +903,8 @@ class ReconAllInputSpec(CommandLineInputSpec): desc=("For use in subjects with enlarged " "ventricles"), ) brainstem = traits.Bool( - argstr="-brainstem-structures", desc="Segment brainstem structures", + argstr="-brainstem-structures", + desc="Segment brainstem structures", requires=["subject_id"], ) hippocampal_subfields_T1 = traits.Bool( From 34a4ac6eeff8d4924b40875c45df5d84a97da90b Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Wed, 5 Jul 2023 16:31:45 -0400 Subject: [PATCH 16/17] STY: Run black and fix typo --- nipype/interfaces/freesurfer/preprocess.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/nipype/interfaces/freesurfer/preprocess.py b/nipype/interfaces/freesurfer/preprocess.py index fe5c23fc11..b17249b046 100644 --- a/nipype/interfaces/freesurfer/preprocess.py +++ b/nipype/interfaces/freesurfer/preprocess.py @@ -820,7 +820,7 @@ class ReconAllInputSpec(CommandLineInputSpec): "recon_all", argstr="-subjid %s", desc="subject name", - xor=["base_template_id","longitudinal_timepoint_id"], + xor=["base_template_id", "longitudinal_timepoint_id"], ) directive = traits.Enum( "all", @@ -846,13 +846,17 @@ class ReconAllInputSpec(CommandLineInputSpec): usedefault=True, position=0, ) - hemi = traits.Enum("lh", "rh", + hemi = traits.Enum( + "lh", + "rh", desc="hemisphere to process", argstr="-hemi %s", requires=["subject_id"], ) T1_files = InputMultiPath( - File(exists=True), argstr="-i %s...", desc="name of T1 file to process", + File(exists=True), + argstr="-i %s...", + desc="name of T1 file to process", requires=["subject_id"], ) T2_file = File( @@ -963,7 +967,7 @@ class ReconAllInputSpec(CommandLineInputSpec): position=1, ) longitudinal_template_id = traits.Str( - argstr="%s", desc="longitudinal base tempalte id", position=2 + argstr="%s", desc="longitudinal base template id", position=2 ) # Expert options From 78d580b61da0f71093c1fda9d408f0bda2ede08a Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Wed, 5 Jul 2023 16:31:57 -0400 Subject: [PATCH 17/17] TEST: make specs --- .../freesurfer/tests/test_auto_ReconAll.py | 9 ++ nipype/interfaces/tests/test_auto_Dcm2nii.py | 107 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 nipype/interfaces/tests/test_auto_Dcm2nii.py diff --git a/nipype/interfaces/freesurfer/tests/test_auto_ReconAll.py b/nipype/interfaces/freesurfer/tests/test_auto_ReconAll.py index c2756c9e5c..f31bdb89f4 100644 --- a/nipype/interfaces/freesurfer/tests/test_auto_ReconAll.py +++ b/nipype/interfaces/freesurfer/tests/test_auto_ReconAll.py @@ -8,14 +8,17 @@ def test_ReconAll_inputs(): argstr="-FLAIR %s", extensions=None, min_ver="5.3.0", + requires=["subject_id"], ), T1_files=dict( argstr="-i %s...", + requires=["subject_id"], ), T2_file=dict( argstr="-T2 %s", extensions=None, min_ver="5.3.0", + requires=["subject_id"], ), args=dict( argstr="%s", @@ -33,6 +36,7 @@ def test_ReconAll_inputs(): ), brainstem=dict( argstr="-brainstem-structures", + requires=["subject_id"], ), directive=dict( argstr="-%s", @@ -52,14 +56,17 @@ def test_ReconAll_inputs(): ), hemi=dict( argstr="-hemi %s", + requires=["subject_id"], ), hippocampal_subfields_T1=dict( argstr="-hippocampal-subfields-T1", min_ver="6.0.0", + requires=["subject_id"], ), hippocampal_subfields_T2=dict( argstr="-hippocampal-subfields-T2 %s %s", min_ver="6.0.0", + requires=["subject_id"], ), hires=dict( argstr="-hires", @@ -77,6 +84,7 @@ def test_ReconAll_inputs(): ), mprage=dict( argstr="-mprage", + requires=["subject_id"], ), mri_aparc2aseg=dict( xor=["expert"], @@ -161,6 +169,7 @@ def test_ReconAll_inputs(): ), subject_id=dict( argstr="-subjid %s", + xor=["base_template_id", "longitudinal_timepoint_id"], ), subjects_dir=dict( argstr="-sd %s", diff --git a/nipype/interfaces/tests/test_auto_Dcm2nii.py b/nipype/interfaces/tests/test_auto_Dcm2nii.py new file mode 100644 index 0000000000..948aafa083 --- /dev/null +++ b/nipype/interfaces/tests/test_auto_Dcm2nii.py @@ -0,0 +1,107 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from ..dcm2nii import Dcm2nii + + +def test_Dcm2nii_inputs(): + input_map = dict( + anonymize=dict( + argstr="-a", + usedefault=True, + ), + args=dict( + argstr="%s", + ), + collapse_folders=dict( + argstr="-c", + usedefault=True, + ), + config_file=dict( + argstr="-b %s", + extensions=None, + genfile=True, + ), + convert_all_pars=dict( + argstr="-v", + usedefault=True, + ), + date_in_filename=dict( + argstr="-d", + usedefault=True, + ), + environ=dict( + nohash=True, + usedefault=True, + ), + events_in_filename=dict( + argstr="-e", + usedefault=True, + ), + gzip_output=dict( + argstr="-g", + usedefault=True, + ), + id_in_filename=dict( + argstr="-i", + usedefault=True, + ), + nii_output=dict( + argstr="-n", + usedefault=True, + ), + output_dir=dict( + argstr="-o %s", + genfile=True, + ), + protocol_in_filename=dict( + argstr="-p", + usedefault=True, + ), + reorient=dict( + argstr="-r", + ), + reorient_and_crop=dict( + argstr="-x", + usedefault=True, + ), + source_dir=dict( + argstr="%s", + mandatory=True, + position=-1, + xor=["source_names"], + ), + source_in_filename=dict( + argstr="-f", + usedefault=True, + ), + source_names=dict( + argstr="%s", + copyfile=False, + mandatory=True, + position=-1, + xor=["source_dir"], + ), + spm_analyze=dict( + argstr="-s", + xor=["nii_output"], + ), + ) + inputs = Dcm2nii.input_spec() + + for key, metadata in list(input_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(inputs.traits()[key], metakey) == value + + +def test_Dcm2nii_outputs(): + output_map = dict( + bvals=dict(), + bvecs=dict(), + converted_files=dict(), + reoriented_and_cropped_files=dict(), + reoriented_files=dict(), + ) + outputs = Dcm2nii.output_spec() + + for key, metadata in list(output_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(outputs.traits()[key], metakey) == value