Skip to content

Commit 0ee3eed

Browse files
author
Arno Klein
committed
Revise thickinthehead algorithm to fix issue #149
1 parent b35205f commit 0ee3eed

File tree

2 files changed

+66
-147
lines changed

2 files changed

+66
-147
lines changed

mindboggle/mindboggle

-2
Original file line numberDiff line numberDiff line change
@@ -2253,7 +2253,6 @@ if do_label and not args.no_volumes:
22532253
'noncortex_value',
22542254
'labels',
22552255
'names',
2256-
'resize',
22572256
'propagate',
22582257
'output_dir',
22592258
'save_table',
@@ -2293,7 +2292,6 @@ if do_label and not args.no_volumes:
22932292
FSthicknesses.inputs.noncortex_value = 3
22942293
FSthicknesses.inputs.labels = dkt.cerebrum_cortex_numbers
22952294
FSthicknesses.inputs.names = dkt.cerebrum_cortex_names
2296-
FSthicknesses.inputs.resize = True
22972295
FSthicknesses.inputs.propagate = False
22982296
FSthicknesses.inputs.output_dir = ''
22992297
FSthicknesses.inputs.save_table = True

mindboggle/shapes/volume_shapes.py

+66-145
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)