@@ -128,9 +128,9 @@ def volume_per_brain_region(input_file, include_labels=[], exclude_labels=[],
128
128
return unique_labels , volumes , output_table
129
129
130
130
131
- def thickinthehead (segmented_file , labeled_file , cortex_value = 2 ,
132
- noncortex_value = 3 , labels = [], names = [], resize = True ,
133
- propagate = True , output_dir = '' , save_table = False ,
131
+ def thickinthehead (segmented_file , labeled_file ,
132
+ cortex_value = 2 , noncortex_value = 3 , labels = [], names = [],
133
+ propagate = False , output_dir = '' , save_table = False ,
134
134
output_table = '' , verbose = False ):
135
135
"""
136
136
Compute a simple thickness measure for each labeled cortex region volume.
@@ -141,89 +141,45 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
141
141
produce favorable results. For example, we found that at least a quarter
142
142
of the over one hundred EMBARC brain images we processed through
143
143
FreeSurfer clipped ventral cortical regions, resulting in bad surface
144
- patches in those regions. For comparison, we built a function called
144
+ patches in those regions. For comparison, we built this function called
145
145
thickinthehead which computes a simple thickness measure for each
146
- cortical region using the hybrid segmentation volume rather than surfaces.
147
-
148
- The thickinthehead function first saves a brain volume that has been
149
- segmented into cortex and non-cortex voxels into separate binary files,
150
- then resamples these cortex and non-cortex files from, for example,
151
- 1mm^3 to 0.5mm^3 voxel dimensions to better represent the contours
152
- of the cortex, then extracts outer and inner boundary voxels of the cortex
153
- by morphologically eroding the cortex by one (resampled) voxel bordering
154
- the outside of the brain and bordering the inside of the brain
155
- (non-cortex). Then it estimates the middle cortical surface area by the
156
- average volume of the outer and inner boundary voxels of the cortex.
157
- Finally, it estimates the thickness of a labeled cortical region as the
158
- volume of the labeled region divided by the surface area of that region.
159
-
160
- We compared thickinthehead and FreeSurfer cortical thickness estimates
161
- for 16 cortical regions in 40 EMBARC control subjects (unpublished
162
- results) with published estimates based on manual delineations of MR
163
- images (Kabani, 2001). Forty percent of FreeSurfer estimates for the 640
164
- labels were in the range of the published values, whereas almost ninety
165
- percent of thickinthehead’s estimates were within range. ANTs values
166
- deviated further from the published estimates and were less reliable
167
- (greater inter-subject ranges) than the FreeSurfer or thickinthehead
168
- values.
146
+ cortical region using a segmentation volume rather than surfaces.
147
+
148
+ We have revised this algorithm from the original published version.
149
+ We removed upsampling to reduce memory issues for large image volumes,
150
+ and replaced the estimated volume of middle cortical layer
151
+ with an estimate of its surface area. We made these revisions to be less
152
+ susceptible to deviations in voxel size from isometric 1mm^3 voxels
153
+ for which thickinthehead was originally built.
154
+
155
+ Steps ::
156
+
157
+ 1. Extract noncortex and cortex into separate files.
158
+ 2. Either mask labels with cortex or fill cortex with labels.
159
+ 3. Extract outer and inner boundary voxels of the cortex,
160
+ by morphologically eroding the cortex (=2) by one voxel bordering
161
+ the outside of the brain (=0) and bordering the inside of the brain
162
+ (non-cortex=3).
163
+ 4. Estimate middle cortical layer's surface area by the average
164
+ surface area of the outer and inner boundary voxels of the cortex,
165
+ where surface area is roughly estimated as the average face area
166
+ of a voxel times the number of voxels.
167
+ 5. Compute the volume of a labeled region of cortex.
168
+ 6. Estimate the thickness of the labeled cortical region as the
169
+ volume of the labeled region (#5) divided by the
170
+ estimate of the middle cortical surface area of that region (#4).
169
171
170
172
Note::
171
173
172
174
- Cortex, noncortex, & label files are from the same coregistered brain.
173
- - Calls ANTs functions: ImageMath, Threshold, ResampleImageBySpacing
175
+ - Calls ANTs functions: ImageMath and Threshold
174
176
- There may be slight discrepancies between volumes computed by
175
177
thickinthehead() and volumes computed by volume_per_label();
176
178
in 31 of 600+ ADNI 1.5T images, some volume_per_label() volumes
177
179
were slightly larger (in the third decimal place), presumably due to
178
180
label propagation through the cortex in thickinthehead().
179
181
This is more pronounced in ANTs vs. FreeSurfer-labeled volumes.
180
182
181
- Example preprocessing steps ::
182
-
183
- 1. Run Freesurfer and antsCorticalThickness.sh on T1-weighted image.
184
- 2. Convert FreeSurfer volume labels (e.g., wmparc.mgz or aparc+aseg.mgz)
185
- to cortex (2) and noncortex (3) segments using relabel_volume()
186
- function [refer to labels.rst or FreeSurferColorLUT labels file].
187
- 3. Convert ANTs Atropos-segmented volume (tmpBrainSegmentation.nii.gz)
188
- to cortex and noncortex segments, by converting 1-labels to 0 and
189
- 4-labels to 3 with the relabel_volume() function
190
- (the latter is to include deep-gray matter with noncortical tissues).
191
- 4. Combine FreeSurfer and ANTs segmentation volumes to obtain a single
192
- cortex (2) and noncortex (3) segmentation file using the function
193
- combine_2labels_in_2volumes(). This function takes the union of
194
- cortex voxels from the segmentations, the union of the noncortex
195
- voxels from the segmentations, and overwrites intersecting cortex
196
- and noncortex voxels with noncortex (3) labels.
197
- ANTs tends to include more cortical gray matter at the periphery of
198
- the brain than Freesurfer, and FreeSurfer tends to include more white
199
- matter that extends deep into gyral folds than ANTs, so the above
200
- attempts to remedy their differences by overlaying ANTs cortical gray
201
- with FreeSurfer white matter.
202
- 5. Optional, see Step 2 below:
203
- Fill segmented cortex with cortex labels and noncortex with
204
- noncortex labels using the PropagateLabelsThroughMask() function
205
- (which calls ImageMath ... PropagateLabelsThroughMask in ANTs).
206
- The labels can be initialized using FreeSurfer (e.g. wmparc.mgz)
207
- or ANTs (by applying the nonlinear inverse transform generated by
208
- antsCorticalThickness.sh to labels in the Atropos template space).
209
- [Note: Any further labeling steps may be applied, such as
210
- overwriting cerebrum with intersecting cerebellum labels.]
211
-
212
- Steps ::
213
-
214
- 1. Extract noncortex and cortex.
215
- 2. Either mask labels with cortex or fill cortex with labels.
216
- 3. Resample cortex and noncortex files from 1x1x1 to 0.5x0.5x0.5
217
- to better represent the contours of the boundaries of the cortex.
218
- 4. Extract outer and inner boundary voxels of the cortex,
219
- by eroding 1 (resampled) voxel for cortex voxels (2) bordering
220
- the outside of the brain (0) and bordering noncortex (3).
221
- 5. Estimate middle cortical surface area by the average volume
222
- of the outer and inner boundary voxels of the cortex.
223
- 6. Compute the volume of a labeled region of cortex.
224
- 7. Estimate the thickness of the labeled cortical region as the
225
- volume of the labeled region (#6) divided by the surface area (#5).
226
-
227
183
Parameters
228
184
----------
229
185
segmented_file : string
@@ -238,10 +194,8 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
238
194
label indices
239
195
names : list of strings
240
196
label names
241
- resize : bool
242
- resize (2x) segmented_file for more accurate thickness estimates?
243
197
propagate : bool
244
- propagate labels through cortex?
198
+ propagate labels through cortex (or mask labels with cortex) ?
245
199
output_dir : string
246
200
output directory
247
201
save_table : bool
@@ -260,7 +214,7 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
260
214
261
215
Examples
262
216
--------
263
- >>> # Example simply using ants segmentation and labels:
217
+ >>> # Example simply using ants segmentation and labels vs. hybrid segmentation :
264
218
>>> import os
265
219
>>> import numpy as np
266
220
>>> from mindboggle.shapes.volume_shapes import thickinthehead
@@ -279,7 +233,6 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
279
233
>>> labels.remove(1033)
280
234
>>> labels.remove(2033)
281
235
>>> names = []
282
- >>> resize = True
283
236
>>> propagate = False
284
237
>>> output_dir = ''
285
238
>>> save_table = True
@@ -290,13 +243,10 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
290
243
291
244
>>> label_volume_thickness, output_table = thickinthehead(segmented_file,
292
245
... labeled_file, cortex_value, noncortex_value, labels, names,
293
- ... resize, propagate, output_dir, save_table, output_table, verbose) # doctest: +SKIP
246
+ ... propagate, output_dir, save_table, output_table, verbose) # doctest: +SKIP
294
247
>>> [np.int("{0:.{1}f}".format(x, 5)) label_volume_thickness[0][0:10]] # doctest: +SKIP
295
- [1002, 1003, 1005, 1006, 1007, 1008, 1009, 1010, 1011 1012]
296
248
>>> [np.float("{0:.{1}f}".format(x, 5)) for x in label_volume_thickness[1][0:5]] # doctest: +SKIP
297
- [3136.99383, 7206.98582, 3257.99359, 1950.99616, 12458.97549]
298
249
>>> [np.float("{0:.{1}f}".format(x, 5)) for x in label_volume_thickness[2][0:5]] # doctest: +SKIP
299
- [3.8639, 3.69637, 2.56334, 4.09336, 4.52592]
300
250
301
251
"""
302
252
import os
@@ -336,92 +286,61 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
336
286
else :
337
287
output_table = ''
338
288
339
- # ------------------------------------------------------------------------
340
- # ants command paths:
341
- # ------------------------------------------------------------------------
342
- ants_thresh = 'ThresholdImage'
343
- ants_math = 'ImageMath'
344
- ants_resample = 'ResampleImageBySpacing'
345
-
346
289
# ------------------------------------------------------------------------
347
290
# Extract noncortex and cortex:
348
291
# ------------------------------------------------------------------------
349
- cmd = [ants_thresh , '3' , segmented_file , noncortex ,
292
+ cmd = ['ThresholdImage' , '3' , segmented_file , noncortex ,
350
293
str (noncortex_value ), str (noncortex_value ), '1 0' ]
351
294
execute (cmd , 'os' )
352
- cmd = [ants_thresh , '3' , segmented_file , cortex ,
295
+ cmd = ['ThresholdImage' , '3' , segmented_file , cortex ,
353
296
str (cortex_value ), str (cortex_value ), '1 0' ]
354
297
execute (cmd , 'os' )
355
298
356
299
# ------------------------------------------------------------------------
357
300
# Either mask labels with cortex or fill cortex with labels:
358
301
# ------------------------------------------------------------------------
359
302
if propagate :
360
- cmd = [ants_math , '3' , cortex , 'PropagateLabelsThroughMask' ,
303
+ cmd = ['ImageMath' , '3' , cortex , 'PropagateLabelsThroughMask' ,
361
304
cortex , labeled_file ]
362
305
execute (cmd , 'os' )
363
306
else :
364
- cmd = [ants_math , '3' , cortex , 'm' , cortex , labeled_file ]
307
+ cmd = ['ImageMath' , '3' , cortex , 'm' , cortex , labeled_file ]
365
308
execute (cmd , 'os' )
366
309
367
310
# ------------------------------------------------------------------------
368
311
# Load data and dimensions:
369
312
# ------------------------------------------------------------------------
370
- if resize :
371
- rescale = 2.0
372
- maxdim = 257
373
- else :
374
- rescale = 1.0
375
- maxdim = 514
376
313
img = nb .load (cortex )
377
- dims = img .header .get_data_shape ()
378
-
379
- if any ([x > maxdim for x in dims ]) and resize :
380
- raise IOError ("Image dimensions greater than " + str (maxdim ) +
381
- " voxels, which may be too large for thickinthehead "
382
- "to resample." )
383
-
384
- voxdims0 = img .header .get_zooms ()
385
- voxdims = [x / rescale for x in voxdims0 ]
386
- voxvol0 = np .prod (voxdims0 )
387
- voxvol = np .prod (voxdims )
388
314
cortex_data = img .get_data ().ravel ()
389
-
390
- # ------------------------------------------------------------------------
391
- # Resample cortex and noncortex files from 1x1x1 to 0.5x0.5x0.5
392
- # to better represent the contours of the boundaries of the cortex:
393
- # ------------------------------------------------------------------------
394
- if resize :
395
- #voxdims_str = ' '.join([str(1/rescale), str(1/rescale), str(1/rescale)])
396
- voxdims_str = ' ' .join ([str (x ) for x in voxdims ])
397
- cmd = [ants_resample , '3' , cortex , cortex , voxdims_str , '0 0 1' ]
398
- execute (cmd , 'os' )
399
- cmd = [ants_resample , '3' , noncortex , noncortex , voxdims_str , '0 0 1' ]
400
- execute (cmd , 'os' )
315
+ voxsize = img .header .get_zooms ()
316
+ voxvol = np .prod (voxsize )
317
+ voxarea = (voxsize [0 ] * voxsize [1 ] + \
318
+ voxsize [0 ] * voxsize [2 ] + \
319
+ voxsize [1 ] * voxsize [2 ]) / 3
401
320
402
321
# ------------------------------------------------------------------------
403
322
# Extract outer and inner boundary voxels of the cortex,
404
- # by eroding 1 (resampled) voxel for cortex voxels (2) bordering
405
- # the outside of the brain (0) and bordering noncortex (3):
323
+ # by eroding 1 voxel for cortex voxels (= 2) bordering
324
+ # the outside of the brain (= 0) and bordering noncortex (= 3):
406
325
# ------------------------------------------------------------------------
407
- cmd = [ants_math , '3' , inner_edge , 'MD' , noncortex , '1' ]
326
+ cmd = ['ImageMath' , '3' , inner_edge , 'MD' , noncortex , '1' ]
408
327
execute (cmd , 'os' )
409
- cmd = [ants_math , '3' , inner_edge , 'm' , cortex , inner_edge ]
328
+ cmd = ['ImageMath' , '3' , inner_edge , 'm' , cortex , inner_edge ]
410
329
execute (cmd , 'os' )
411
330
if use_outer_edge :
412
- cmd = [ants_thresh , '3' , cortex , outer_edge , '1 10000 1 0' ]
331
+ cmd = ['ThresholdImage' , '3' , cortex , outer_edge , '1 10000 1 0' ]
413
332
execute (cmd , 'os' )
414
- cmd = [ants_math , '3' , outer_edge , 'ME' , outer_edge , '1' ]
333
+ cmd = ['ImageMath' , '3' , outer_edge , 'ME' , outer_edge , '1' ]
415
334
execute (cmd , 'os' )
416
- cmd = [ants_thresh , '3' , outer_edge , outer_edge , '1 1 0 1' ]
335
+ cmd = ['ThresholdImage' , '3' , outer_edge , outer_edge , '1 1 0 1' ]
417
336
execute (cmd , 'os' )
418
- cmd = [ants_math , '3' , outer_edge , 'm' , cortex , outer_edge ]
337
+ cmd = ['ImageMath' , '3' , outer_edge , 'm' , cortex , outer_edge ]
419
338
execute (cmd , 'os' )
420
- cmd = [ants_thresh , '3' , inner_edge , temp , '1 10000 1 0' ]
339
+ cmd = ['ThresholdImage' , '3' , inner_edge , temp , '1 10000 1 0' ]
421
340
execute (cmd , 'os' )
422
- cmd = [ants_thresh , '3' , temp , temp , '1 1 0 1' ]
341
+ cmd = ['ThresholdImage' , '3' , temp , temp , '1 1 0 1' ]
423
342
execute (cmd , 'os' )
424
- cmd = [ants_math , '3' , outer_edge , 'm' , temp , outer_edge ]
343
+ cmd = ['ImageMath' , '3' , outer_edge , 'm' , temp , outer_edge ]
425
344
execute (cmd , 'os' )
426
345
427
346
# ------------------------------------------------------------------------
@@ -445,24 +364,26 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
445
364
name = names [ilabel ]
446
365
447
366
# --------------------------------------------------------------------
448
- # Compute thickness as a ratio of label volume and edge volume :
449
- # - Estimate middle cortical surface area by the average volume
367
+ # Compute thickness as a ratio of label volume and layer surface area :
368
+ # - Estimate middle cortical surface area by the average area
450
369
# of the outer and inner boundary voxels of the cortex.
370
+ # - Surface area is roughly estimated as the average face area
371
+ # of a voxel times the number of voxels.
451
372
# - Compute the volume of a labeled region of cortex.
452
373
# - Estimate the thickness of the labeled cortical region as the
453
- # volume of the labeled region divided by the surface area.
374
+ # volume of the labeled region divided by the middle surface area.
454
375
# --------------------------------------------------------------------
455
- label_cortex_volume = voxvol0 * len (np .where (cortex_data == label )[0 ])
456
- label_inner_edge_volume = voxvol * \
376
+ label_cortex_volume = voxvol * len (np .where (cortex_data == label )[0 ])
377
+ label_inner_edge_area = voxarea * \
457
378
len (np .where (inner_edge_data == label )[0 ])
458
- if label_inner_edge_volume :
379
+ if label_inner_edge_area :
459
380
if use_outer_edge :
460
- label_outer_edge_volume = \
461
- voxvol * len (np .where (outer_edge_data == label )[0 ])
462
- label_area = (label_inner_edge_volume +
463
- label_outer_edge_volume ) / 2.0
381
+ label_outer_edge_area = \
382
+ voxarea * len (np .where (outer_edge_data == label )[0 ])
383
+ label_area = (label_inner_edge_area +
384
+ label_outer_edge_area ) / 2.0
464
385
else :
465
- label_area = label_inner_edge_volume
386
+ label_area = label_inner_edge_area
466
387
thickness = label_cortex_volume / label_area
467
388
label_volume_thickness [ilabel , 1 ] = label_cortex_volume
468
389
label_volume_thickness [ilabel , 2 ] = thickness
0 commit comments