25
25
CommandLine ,
26
26
CommandLineInputSpec ,
27
27
isdefined ,
28
+ InputMultiObject ,
28
29
)
29
30
from .base import FSCommand , FSTraitedSpec , FSTraitedSpecOpenMP , FSCommandOpenMP , Info
30
31
from .utils import copy2subjdir
@@ -816,7 +817,10 @@ def _gen_filename(self, name):
816
817
817
818
class ReconAllInputSpec (CommandLineInputSpec ):
818
819
subject_id = traits .Str (
819
- "recon_all" , argstr = "-subjid %s" , desc = "subject name" , usedefault = True
820
+ "recon_all" ,
821
+ argstr = "-subjid %s" ,
822
+ desc = "subject name" ,
823
+ xor = ["base_template_id" , "longitudinal_timepoint_id" ],
820
824
)
821
825
directive = traits .Enum (
822
826
"all" ,
@@ -842,21 +846,32 @@ class ReconAllInputSpec(CommandLineInputSpec):
842
846
usedefault = True ,
843
847
position = 0 ,
844
848
)
845
- hemi = traits .Enum ("lh" , "rh" , desc = "hemisphere to process" , argstr = "-hemi %s" )
849
+ hemi = traits .Enum (
850
+ "lh" ,
851
+ "rh" ,
852
+ desc = "hemisphere to process" ,
853
+ argstr = "-hemi %s" ,
854
+ requires = ["subject_id" ],
855
+ )
846
856
T1_files = InputMultiPath (
847
- File (exists = True ), argstr = "-i %s..." , desc = "name of T1 file to process"
857
+ File (exists = True ),
858
+ argstr = "-i %s..." ,
859
+ desc = "name of T1 file to process" ,
860
+ requires = ["subject_id" ],
848
861
)
849
862
T2_file = File (
850
863
exists = True ,
851
864
argstr = "-T2 %s" ,
852
865
min_ver = "5.3.0" ,
853
866
desc = "Convert T2 image to orig directory" ,
867
+ requires = ["subject_id" ],
854
868
)
855
869
FLAIR_file = File (
856
870
exists = True ,
857
871
argstr = "-FLAIR %s" ,
858
872
min_ver = "5.3.0" ,
859
873
desc = "Convert FLAIR image to orig directory" ,
874
+ requires = ["subject_id" ],
860
875
)
861
876
use_T2 = traits .Bool (
862
877
argstr = "-T2pial" ,
@@ -885,18 +900,22 @@ class ReconAllInputSpec(CommandLineInputSpec):
885
900
"Assume scan parameters are MGH MP-RAGE "
886
901
"protocol, which produces darker gray matter"
887
902
),
903
+ requires = ["subject_id" ],
888
904
)
889
905
big_ventricles = traits .Bool (
890
906
argstr = "-bigventricles" ,
891
907
desc = ("For use in subjects with enlarged ventricles" ),
892
908
)
893
909
brainstem = traits .Bool (
894
- argstr = "-brainstem-structures" , desc = "Segment brainstem structures"
910
+ argstr = "-brainstem-structures" ,
911
+ desc = "Segment brainstem structures" ,
912
+ requires = ["subject_id" ],
895
913
)
896
914
hippocampal_subfields_T1 = traits .Bool (
897
915
argstr = "-hippocampal-subfields-T1" ,
898
916
min_ver = "6.0.0" ,
899
917
desc = "segment hippocampal subfields using input T1 scan" ,
918
+ requires = ["subject_id" ],
900
919
)
901
920
hippocampal_subfields_T2 = traits .Tuple (
902
921
File (exists = True ),
@@ -907,6 +926,7 @@ class ReconAllInputSpec(CommandLineInputSpec):
907
926
"segment hippocampal subfields using T2 scan, identified by "
908
927
"ID (may be combined with hippocampal_subfields_T1)"
909
928
),
929
+ requires = ["subject_id" ],
910
930
)
911
931
expert = File (
912
932
exists = True , argstr = "-expert %s" , desc = "Set parameters using expert file"
@@ -927,6 +947,29 @@ class ReconAllInputSpec(CommandLineInputSpec):
927
947
)
928
948
flags = InputMultiPath (traits .Str , argstr = "%s" , desc = "additional parameters" )
929
949
950
+ # Longitudinal runs
951
+ base_template_id = traits .Str (
952
+ argstr = "-base %s" ,
953
+ desc = "base template id" ,
954
+ xor = ["subject_id" , "longitudinal_timepoint_id" ],
955
+ requires = ["base_timepoint_ids" ],
956
+ )
957
+ base_timepoint_ids = InputMultiObject (
958
+ traits .Str (),
959
+ argstr = "-base-tp %s..." ,
960
+ desc = "processed timepoint to use in template" ,
961
+ )
962
+ longitudinal_timepoint_id = traits .Str (
963
+ argstr = "-long %s" ,
964
+ desc = "longitudinal session/timepoint id" ,
965
+ xor = ["subject_id" , "base_template_id" ],
966
+ requires = ["longitudinal_template_id" ],
967
+ position = 1 ,
968
+ )
969
+ longitudinal_template_id = traits .Str (
970
+ argstr = "%s" , desc = "longitudinal base template id" , position = 2
971
+ )
972
+
930
973
# Expert options
931
974
talairach = traits .Str (desc = "Flags to pass to talairach commands" , xor = ["expert" ])
932
975
mri_normalize = traits .Str (
@@ -1019,7 +1062,7 @@ class ReconAll(CommandLine):
1019
1062
>>> reconall.inputs.subject_id = 'foo'
1020
1063
>>> reconall.inputs.directive = 'all'
1021
1064
>>> reconall.inputs.subjects_dir = '.'
1022
- >>> reconall.inputs.T1_files = 'structural.nii'
1065
+ >>> reconall.inputs.T1_files = [ 'structural.nii']
1023
1066
>>> reconall.cmdline
1024
1067
'recon-all -all -i structural.nii -subjid foo -sd .'
1025
1068
>>> reconall.inputs.flags = "-qcache"
@@ -1049,7 +1092,7 @@ class ReconAll(CommandLine):
1049
1092
>>> reconall_subfields.inputs.subject_id = 'foo'
1050
1093
>>> reconall_subfields.inputs.directive = 'all'
1051
1094
>>> reconall_subfields.inputs.subjects_dir = '.'
1052
- >>> reconall_subfields.inputs.T1_files = 'structural.nii'
1095
+ >>> reconall_subfields.inputs.T1_files = [ 'structural.nii']
1053
1096
>>> reconall_subfields.inputs.hippocampal_subfields_T1 = True
1054
1097
>>> reconall_subfields.cmdline
1055
1098
'recon-all -all -i structural.nii -hippocampal-subfields-T1 -subjid foo -sd .'
@@ -1060,6 +1103,24 @@ class ReconAll(CommandLine):
1060
1103
>>> reconall_subfields.inputs.hippocampal_subfields_T1 = False
1061
1104
>>> reconall_subfields.cmdline
1062
1105
'recon-all -all -i structural.nii -hippocampal-subfields-T2 structural.nii test -subjid foo -sd .'
1106
+
1107
+ Base template creation for longitudinal pipeline:
1108
+ >>> baserecon = ReconAll()
1109
+ >>> baserecon.inputs.base_template_id = 'sub-template'
1110
+ >>> baserecon.inputs.base_timepoint_ids = ['ses-1','ses-2']
1111
+ >>> baserecon.inputs.directive = 'all'
1112
+ >>> baserecon.inputs.subjects_dir = '.'
1113
+ >>> baserecon.cmdline
1114
+ 'recon-all -all -base sub-template -base-tp ses-1 -base-tp ses-2 -sd .'
1115
+
1116
+ Longitudinal timepoint run:
1117
+ >>> longrecon = ReconAll()
1118
+ >>> longrecon.inputs.longitudinal_timepoint_id = 'ses-1'
1119
+ >>> longrecon.inputs.longitudinal_template_id = 'sub-template'
1120
+ >>> longrecon.inputs.directive = 'all'
1121
+ >>> longrecon.inputs.subjects_dir = '.'
1122
+ >>> longrecon.cmdline
1123
+ 'recon-all -all -long ses-1 sub-template -sd .'
1063
1124
"""
1064
1125
1065
1126
_cmd = "recon-all"
@@ -1523,21 +1584,62 @@ def _list_outputs(self):
1523
1584
1524
1585
outputs = self ._outputs ().get ()
1525
1586
1526
- outputs .update (
1527
- FreeSurferSource (
1528
- subject_id = self .inputs .subject_id , subjects_dir = subjects_dir , hemi = hemi
1529
- )._list_outputs ()
1530
- )
1531
- outputs ["subject_id" ] = self .inputs .subject_id
1587
+ # If using longitudinal pipeline, update subject id accordingly,
1588
+ # otherwise use original/default subject_id
1589
+ if isdefined (self .inputs .base_template_id ):
1590
+ outputs .update (
1591
+ FreeSurferSource (
1592
+ subject_id = self .inputs .base_template_id ,
1593
+ subjects_dir = subjects_dir ,
1594
+ hemi = hemi ,
1595
+ )._list_outputs ()
1596
+ )
1597
+ outputs ["subject_id" ] = self .inputs .base_template_id
1598
+ elif isdefined (self .inputs .longitudinal_timepoint_id ):
1599
+ subject_id = f"{ self .inputs .longitudinal_timepoint_id } .long.{ self .inputs .longitudinal_template_id } "
1600
+ outputs .update (
1601
+ FreeSurferSource (
1602
+ subject_id = subject_id , subjects_dir = subjects_dir , hemi = hemi
1603
+ )._list_outputs ()
1604
+ )
1605
+ outputs ["subject_id" ] = subject_id
1606
+ else :
1607
+ outputs .update (
1608
+ FreeSurferSource (
1609
+ subject_id = self .inputs .subject_id ,
1610
+ subjects_dir = subjects_dir ,
1611
+ hemi = hemi ,
1612
+ )._list_outputs ()
1613
+ )
1614
+ outputs ["subject_id" ] = self .inputs .subject_id
1615
+
1532
1616
outputs ["subjects_dir" ] = subjects_dir
1533
1617
return outputs
1534
1618
1535
1619
def _is_resuming (self ):
1536
1620
subjects_dir = self .inputs .subjects_dir
1537
1621
if not isdefined (subjects_dir ):
1538
1622
subjects_dir = self ._gen_subjects_dir ()
1539
- if os .path .isdir (os .path .join (subjects_dir , self .inputs .subject_id , "mri" )):
1540
- return True
1623
+
1624
+ # Check for longitudinal pipeline
1625
+ if not isdefined (self .inputs .subject_id ):
1626
+ if isdefined (self .inputs .base_template_id ):
1627
+ if os .path .isdir (
1628
+ os .path .join (subjects_dir , self .inputs .base_template_id , "mri" )
1629
+ ):
1630
+ return True
1631
+ elif isdefined (self .inputs .longitudinal_template_id ):
1632
+ if os .path .isdir (
1633
+ os .path .join (
1634
+ subjects_dir ,
1635
+ f"{ self .inputs .longitudinal_timepoint_id } .long.{ self .inputs .longitudinal_template_id } " ,
1636
+ "mri" ,
1637
+ )
1638
+ ):
1639
+ return True
1640
+ else :
1641
+ if os .path .isdir (os .path .join (subjects_dir , self .inputs .subject_id , "mri" )):
1642
+ return True
1541
1643
return False
1542
1644
1543
1645
def _format_arg (self , name , trait_spec , value ):
0 commit comments