@@ -128,9 +128,9 @@ def volume_per_brain_region(input_file, include_labels=[], exclude_labels=[],
128128 return unique_labels , volumes , output_table
129129
130130
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 ,
134134 output_table = '' , verbose = False ):
135135 """
136136 Compute a simple thickness measure for each labeled cortex region volume.
@@ -141,89 +141,45 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
141141 produce favorable results. For example, we found that at least a quarter
142142 of the over one hundred EMBARC brain images we processed through
143143 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
145145 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).
169171
170172 Note::
171173
172174 - Cortex, noncortex, & label files are from the same coregistered brain.
173- - Calls ANTs functions: ImageMath, Threshold, ResampleImageBySpacing
175+ - Calls ANTs functions: ImageMath and Threshold
174176 - There may be slight discrepancies between volumes computed by
175177 thickinthehead() and volumes computed by volume_per_label();
176178 in 31 of 600+ ADNI 1.5T images, some volume_per_label() volumes
177179 were slightly larger (in the third decimal place), presumably due to
178180 label propagation through the cortex in thickinthehead().
179181 This is more pronounced in ANTs vs. FreeSurfer-labeled volumes.
180182
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-
227183 Parameters
228184 ----------
229185 segmented_file : string
@@ -238,10 +194,8 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
238194 label indices
239195 names : list of strings
240196 label names
241- resize : bool
242- resize (2x) segmented_file for more accurate thickness estimates?
243197 propagate : bool
244- propagate labels through cortex?
198+ propagate labels through cortex (or mask labels with cortex) ?
245199 output_dir : string
246200 output directory
247201 save_table : bool
@@ -260,7 +214,7 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
260214
261215 Examples
262216 --------
263- >>> # Example simply using ants segmentation and labels:
217+ >>> # Example simply using ants segmentation and labels vs. hybrid segmentation :
264218 >>> import os
265219 >>> import numpy as np
266220 >>> from mindboggle.shapes.volume_shapes import thickinthehead
@@ -279,7 +233,6 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
279233 >>> labels.remove(1033)
280234 >>> labels.remove(2033)
281235 >>> names = []
282- >>> resize = True
283236 >>> propagate = False
284237 >>> output_dir = ''
285238 >>> save_table = True
@@ -290,13 +243,10 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
290243
291244 >>> label_volume_thickness, output_table = thickinthehead(segmented_file,
292245 ... 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
294247 >>> [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]
296248 >>> [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]
298249 >>> [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]
300250
301251 """
302252 import os
@@ -336,92 +286,61 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
336286 else :
337287 output_table = ''
338288
339- # ------------------------------------------------------------------------
340- # ants command paths:
341- # ------------------------------------------------------------------------
342- ants_thresh = 'ThresholdImage'
343- ants_math = 'ImageMath'
344- ants_resample = 'ResampleImageBySpacing'
345-
346289 # ------------------------------------------------------------------------
347290 # Extract noncortex and cortex:
348291 # ------------------------------------------------------------------------
349- cmd = [ants_thresh , '3' , segmented_file , noncortex ,
292+ cmd = ['ThresholdImage' , '3' , segmented_file , noncortex ,
350293 str (noncortex_value ), str (noncortex_value ), '1 0' ]
351294 execute (cmd , 'os' )
352- cmd = [ants_thresh , '3' , segmented_file , cortex ,
295+ cmd = ['ThresholdImage' , '3' , segmented_file , cortex ,
353296 str (cortex_value ), str (cortex_value ), '1 0' ]
354297 execute (cmd , 'os' )
355298
356299 # ------------------------------------------------------------------------
357300 # Either mask labels with cortex or fill cortex with labels:
358301 # ------------------------------------------------------------------------
359302 if propagate :
360- cmd = [ants_math , '3' , cortex , 'PropagateLabelsThroughMask' ,
303+ cmd = ['ImageMath' , '3' , cortex , 'PropagateLabelsThroughMask' ,
361304 cortex , labeled_file ]
362305 execute (cmd , 'os' )
363306 else :
364- cmd = [ants_math , '3' , cortex , 'm' , cortex , labeled_file ]
307+ cmd = ['ImageMath' , '3' , cortex , 'm' , cortex , labeled_file ]
365308 execute (cmd , 'os' )
366309
367310 # ------------------------------------------------------------------------
368311 # Load data and dimensions:
369312 # ------------------------------------------------------------------------
370- if resize :
371- rescale = 2.0
372- maxdim = 257
373- else :
374- rescale = 1.0
375- maxdim = 514
376313 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 )
388314 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
401320
402321 # ------------------------------------------------------------------------
403322 # 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):
406325 # ------------------------------------------------------------------------
407- cmd = [ants_math , '3' , inner_edge , 'MD' , noncortex , '1' ]
326+ cmd = ['ImageMath' , '3' , inner_edge , 'MD' , noncortex , '1' ]
408327 execute (cmd , 'os' )
409- cmd = [ants_math , '3' , inner_edge , 'm' , cortex , inner_edge ]
328+ cmd = ['ImageMath' , '3' , inner_edge , 'm' , cortex , inner_edge ]
410329 execute (cmd , 'os' )
411330 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' ]
413332 execute (cmd , 'os' )
414- cmd = [ants_math , '3' , outer_edge , 'ME' , outer_edge , '1' ]
333+ cmd = ['ImageMath' , '3' , outer_edge , 'ME' , outer_edge , '1' ]
415334 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' ]
417336 execute (cmd , 'os' )
418- cmd = [ants_math , '3' , outer_edge , 'm' , cortex , outer_edge ]
337+ cmd = ['ImageMath' , '3' , outer_edge , 'm' , cortex , outer_edge ]
419338 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' ]
421340 execute (cmd , 'os' )
422- cmd = [ants_thresh , '3' , temp , temp , '1 1 0 1' ]
341+ cmd = ['ThresholdImage' , '3' , temp , temp , '1 1 0 1' ]
423342 execute (cmd , 'os' )
424- cmd = [ants_math , '3' , outer_edge , 'm' , temp , outer_edge ]
343+ cmd = ['ImageMath' , '3' , outer_edge , 'm' , temp , outer_edge ]
425344 execute (cmd , 'os' )
426345
427346 # ------------------------------------------------------------------------
@@ -445,24 +364,26 @@ def thickinthehead(segmented_file, labeled_file, cortex_value=2,
445364 name = names [ilabel ]
446365
447366 # --------------------------------------------------------------------
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
450369 # 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.
451372 # - Compute the volume of a labeled region of cortex.
452373 # - 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.
454375 # --------------------------------------------------------------------
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 * \
457378 len (np .where (inner_edge_data == label )[0 ])
458- if label_inner_edge_volume :
379+ if label_inner_edge_area :
459380 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
464385 else :
465- label_area = label_inner_edge_volume
386+ label_area = label_inner_edge_area
466387 thickness = label_cortex_volume / label_area
467388 label_volume_thickness [ilabel , 1 ] = label_cortex_volume
468389 label_volume_thickness [ilabel , 2 ] = thickness
0 commit comments