@@ -36,13 +36,14 @@ class MCRIBReconAllInputSpec(CommandLineInputSpec):
36
36
)
37
37
t2w_file = File (
38
38
exists = True ,
39
- required = True ,
40
39
copyfile = True ,
41
40
desc = 'T2w (Isotropic + N4 corrected)' ,
42
41
)
43
42
segmentation_file = File (
43
+ exists = True ,
44
44
desc = 'Segmentation file (skips tissue segmentation)' ,
45
45
)
46
+ mask_file = File (exists = True , desc = 'T2w mask' )
46
47
47
48
# MCRIBS options
48
49
conform = traits .Bool (
@@ -54,39 +55,31 @@ class MCRIBReconAllInputSpec(CommandLineInputSpec):
54
55
desc = 'Perform tissue type segmentation' ,
55
56
)
56
57
surfrecon = traits .Bool (
57
- True ,
58
- usedefault = True ,
59
58
argstr = '--surfrecon' ,
60
59
desc = 'Reconstruct surfaces' ,
61
60
)
62
61
surfrecon_method = traits .Enum (
63
62
'Deformable' ,
64
63
argstr = '--surfreconmethod %s' ,
65
- usedefault = True ,
64
+ requires = [ 'surfrecon' ] ,
66
65
desc = 'Surface reconstruction method' ,
67
66
)
68
67
join_thresh = traits .Float (
69
- 1.0 ,
70
68
argstr = '--deformablejointhresh %f' ,
71
- usedefault = True ,
69
+ requires = [ 'surfrecon' ] ,
72
70
desc = 'Join threshold parameter for Deformable' ,
73
71
)
74
72
fast_collision = traits .Bool (
75
- True ,
76
73
argstr = '--deformablefastcollision' ,
77
- usedefault = True ,
74
+ requires = [ 'surfrecon' ] ,
78
75
desc = 'Use Deformable fast collision test' ,
79
76
)
80
77
autorecon_after_surf = traits .Bool (
81
- True ,
82
78
argstr = '--autoreconaftersurf' ,
83
- usedefault = True ,
84
79
desc = 'Do all steps after surface reconstruction' ,
85
80
)
86
81
segstats = traits .Bool (
87
- True ,
88
82
argstr = '--segstats' ,
89
- usedefault = True ,
90
83
desc = 'Compute statistics on segmented volumes' ,
91
84
)
92
85
nthreads = traits .Int (
@@ -97,6 +90,7 @@ class MCRIBReconAllInputSpec(CommandLineInputSpec):
97
90
98
91
class MCRIBReconAllOutputSpec (TraitedSpec ):
99
92
mcribs_dir = Directory (desc = 'MCRIBS output directory' )
93
+ subjects_dir = Directory (desc = 'FreeSurfer output directory' )
100
94
101
95
102
96
class MCRIBReconAll (CommandLine ):
@@ -111,10 +105,17 @@ def cmdline(self):
111
105
# Avoid processing if valid
112
106
if self .inputs .outdir :
113
107
sid = self .inputs .subject_id
114
- logf = Path (self .inputs .outdir ) / sid / 'logs' / f'{ sid } .log'
115
- if logf .exists ():
116
- logtxt = logf .read_text ().splitlines ()[- 3 :]
117
- self ._no_run = 'Finished without error' in logtxt
108
+ # Check MIRTK surface recon deformable
109
+ if self .inputs .surfrecon :
110
+ surfrecon_dir = Path (self .inputs .outdir ) / sid / 'SurfReconDeformable' / sid
111
+ if self ._verify_surfrecon_outputs (surfrecon_dir , error = False ):
112
+ self ._no_run = True
113
+ # Check FS directory population
114
+ elif self .inputs .autorecon_after_surf :
115
+ fs_dir = Path (self .inputs .outdir ) / sid / 'freesurfer' / sid
116
+ if self ._verify_autorecon_outputs (fs_dir , error = False ):
117
+ self ._no_run = True
118
+
118
119
if self ._no_run :
119
120
return "echo MCRIBSReconAll: nothing to do"
120
121
return cmd
@@ -147,21 +148,22 @@ def _setup_directory_structure(self, mcribs_dir: Path) -> None:
147
148
root .mkdir (** mkdir_kw )
148
149
149
150
# T2w operations
150
- t2w = root / 'RawT2' / f'{ sid } .nii.gz'
151
- t2w .parent .mkdir (** mkdir_kw )
152
- if not t2w .exists ():
153
- shutil .copy (self .inputs .t2w_file , str (t2w ))
154
-
155
- if not self .inputs .conform :
156
- t2wiso = root / 'RawT2RadiologicalIsotropic' / f'{ sid } .nii.gz'
157
- t2wiso .parent .mkdir (** mkdir_kw )
158
- if not t2wiso .exists ():
159
- t2wiso .symlink_to (f'../RawT2/{ sid } .nii.gz' )
160
-
161
- n4 = root / 'TissueSegDrawEM' / sid / 'N4' / f'{ sid } .nii.gz'
162
- n4 .parent .mkdir (** mkdir_kw )
163
- if not n4 .exists ():
164
- n4 .symlink_to (f'../../../RawT2/{ sid } .nii.gz' )
151
+ if self .inputs .t2w_file :
152
+ t2w = root / 'RawT2' / f'{ sid } .nii.gz'
153
+ t2w .parent .mkdir (** mkdir_kw )
154
+ if not t2w .exists ():
155
+ shutil .copy (self .inputs .t2w_file , str (t2w ))
156
+
157
+ if not self .inputs .conform :
158
+ t2wiso = root / 'RawT2RadiologicalIsotropic' / f'{ sid } .nii.gz'
159
+ t2wiso .parent .mkdir (** mkdir_kw )
160
+ if not t2wiso .exists ():
161
+ t2wiso .symlink_to (f'../RawT2/{ sid } .nii.gz' )
162
+
163
+ n4 = root / 'TissueSegDrawEM' / sid / 'N4' / f'{ sid } .nii.gz'
164
+ n4 .parent .mkdir (** mkdir_kw )
165
+ if not n4 .exists ():
166
+ n4 .symlink_to (f'../../../RawT2/{ sid } .nii.gz' )
165
167
166
168
# Segmentation
167
169
if self .inputs .segmentation_file :
@@ -184,6 +186,11 @@ def _setup_directory_structure(self, mcribs_dir: Path) -> None:
184
186
if not surfrec .exists ():
185
187
surfrec .symlink_to (f'../../../RawT2/{ sid } .nii.gz' )
186
188
189
+ if self .inputs .mask_file :
190
+ surfrec_mask = surfrec .parent / 'brain-mask.nii.gz'
191
+ if not surfrec_mask .exists ():
192
+ shutil .copy (self .inputs .mask_file , str (surfrec_mask ))
193
+
187
194
if self .inputs .surfrecon :
188
195
# Create FreeSurfer layout to safeguard against cd-ing into missing directories
189
196
for d in ('surf' , 'mri' , 'label' , 'scripts' , 'stats' ):
@@ -196,21 +203,82 @@ def _run_interface(self, runtime):
196
203
# if users wish to preserve their runs
197
204
mcribs_dir = self .inputs .outdir or Path (runtime .cwd ) / 'mcribs'
198
205
self ._mcribs_dir = Path (mcribs_dir )
199
- self ._setup_directory_structure (self ._mcribs_dir )
206
+ if self .inputs .surfrecon :
207
+ assert self .inputs .t2w_file , "Missing T2w input"
208
+ self ._setup_directory_structure (self ._mcribs_dir )
200
209
# overwrite CWD to be in MCRIB subject's directory
201
210
runtime .cwd = str (self ._mcribs_dir / self .inputs .subject_id )
202
211
return super ()._run_interface (runtime )
203
212
204
213
def _list_outputs (self ):
205
214
outputs = self ._outputs ().get ()
206
- outputs ['mcribs_dir' ] = str (self ._mcribs_dir )
207
-
208
- # Copy freesurfer directory into FS subjects dir
209
215
sid = self .inputs .subject_id
210
- mcribs_fs = self ._mcribs_dir / sid / 'freesurfer' / sid
211
- if mcribs_fs .exists () and self .inputs .subjects_dir :
216
+ if self .inputs .surfrecon :
217
+ # verify surface reconstruction was successful
218
+ surfrecon_dir = self ._mcribs_dir / sid / 'SurfReconDeformable' / sid
219
+ self ._verify_surfrecon_outputs (surfrecon_dir , error = True )
220
+
221
+ outputs ['mcribs_dir' ] = str (self ._mcribs_dir )
222
+ if self .inputs .autorecon_after_surf and self .inputs .subjects_dir :
223
+ mcribs_fs = self ._mcribs_dir / sid / 'freesurfer' / sid
224
+ self ._verify_autorecon_outputs (mcribs_fs , error = True )
212
225
dst = Path (self .inputs .subjects_dir ) / self .inputs .subject_id
213
226
if not dst .exists ():
214
227
shutil .copytree (mcribs_fs , dst )
228
+ outputs ['subjects_dir' ] = self .inputs .subjects_dir
215
229
216
230
return outputs
231
+
232
+ @staticmethod
233
+ def _verify_surfrecon_outputs (surfrecon_dir : Path , error : bool ) -> bool :
234
+ """
235
+ Sanity check to ensure the surface reconstruction was successful.
236
+
237
+ MCRIBReconAll does not return a failing exit code if a step failed, which leads
238
+ this interface to be marked as completed without error in such cases.
239
+ """
240
+ # fmt:off
241
+ surfrecon_files = {
242
+ 'meshes' : (
243
+ 'pial-lh-reordered.vtp' ,
244
+ 'pial-rh-reordered.vtp' ,
245
+ 'white-rh.vtp' ,
246
+ 'white-lh.vtp' ,
247
+ )
248
+ }
249
+ # fmt:on
250
+ for d , fls in surfrecon_files .items ():
251
+ for fl in fls :
252
+ if not (surfrecon_dir / d / fl ).exists ():
253
+ if error :
254
+ raise FileNotFoundError (f"SurfReconDeformable missing: { fl } " )
255
+ return False
256
+ return True
257
+
258
+ @staticmethod
259
+ def _verify_autorecon_outputs (fs_dir : Path , error : bool ) -> bool :
260
+ """
261
+ Sanity check to ensure the necessary FreeSurfer files have been created.
262
+
263
+ MCRIBReconAll does not return a failing exit code if a step failed, which leads
264
+ this interface to be marked as completed without error in such cases.
265
+ """
266
+ # fmt:off
267
+ fs_files = {
268
+ 'mri' : ('T2.mgz' , 'aseg.presurf.mgz' , 'ribbon.mgz' , 'brain.mgz' ),
269
+ 'label' : ('lh.cortex.label' , 'rh.cortex.label' ),
270
+ 'stats' : ('aseg.stats' , 'brainvol.stats' , 'lh.aparc.stats' , 'rh.curv.stats' ),
271
+ 'surf' : (
272
+ 'lh.pial' , 'rh.pial' ,
273
+ 'lh.white' , 'rh.white' ,
274
+ 'lh.curv' , 'rh.curv' ,
275
+ 'lh.thickness' , 'rh.thickness' ),
276
+ }
277
+ # fmt:on
278
+ for d , fls in fs_files .items ():
279
+ for fl in fls :
280
+ if not (fs_dir / d / fl ).exists ():
281
+ if error :
282
+ raise FileNotFoundError (f"FreeSurfer directory missing: { fl } " )
283
+ return False
284
+ return True
0 commit comments