-
Notifications
You must be signed in to change notification settings - Fork 53
/
Copy pathfolds.py
484 lines (433 loc) · 20 KB
/
folds.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
#!/usr/bin/env python
"""
Functions to extract surface folds.
Authors:
- Arno Klein, 2012-2016 ([email protected]) http://binarybottle.com
Copyright 2016, Mindboggle team (http://mindboggle.info), Apache v2.0 License
"""
def find_depth_threshold(depth_file, min_vertices=10000, verbose=False):
"""
Find depth threshold to extract folds from a triangular surface mesh.
Steps ::
1. Compute histogram of depth measures.
2. Define a depth threshold and find the deepest vertices.
To extract an initial set of deep vertices from the surface mesh,
we anticipate that there will be a rapidly decreasing distribution
of low depth values (on the outer surface) with a long tail
of higher depth values (in the folds), so we smooth the histogram's
bin values, convolve to compute slopes, and find the depth value
for the first bin with slope = 0. This is our threshold.
Parameters
----------
depth_file : string
surface mesh file in VTK format with faces and depth scalar values
min_vertices : integer
minimum number of vertices
verbose : bool
print statements?
Returns
-------
depth_threshold : float
threshold defining the minimum depth for vertices to be in a fold
bins : list of integers
histogram bins: each is the number of vertices within a range of depth values
bin_edges : list of floats
histogram bin edge values defining the bin ranges of depth values
Examples
--------
>>> import numpy as np
>>> from mindboggle.features.folds import find_depth_threshold
>>> from mindboggle.mio.fetch_data import prep_tests
>>> urls, fetch_data = prep_tests()
>>> depth_file = fetch_data(urls['left_travel_depth'], '', '.vtk')
>>> min_vertices = 10000
>>> verbose = False
>>> depth_threshold, bins, bin_edges = find_depth_threshold(depth_file,
... min_vertices, verbose)
>>> np.float("{0:.{1}f}".format(depth_threshold, 5))
2.36089
View threshold histogram plots (skip test):
>>> def vis():
... import numpy as np
... import pylab
... from scipy.ndimage.filters import gaussian_filter1d
... from mindboggle.mio.vtks import read_scalars
... # Plot histogram and depth threshold:
... depths, name = read_scalars(depth_file)
... nbins = np.round(len(depths) / 100.0)
... a,b,c = pylab.hist(depths, bins=nbins)
... pylab.plot(depth_threshold * np.ones((100,1)),
... np.linspace(0, max(bins), 100), 'r.')
... pylab.title('Histogram of depth values with threshold')
... pylab.xlabel('Depth')
... pylab.ylabel('Number of vertices')
... pylab.show()
... # Plot smoothed histogram:
... bins_smooth = gaussian_filter1d(bins.tolist(), 5)
... pylab.plot(list(range(len(bins))), bins, '.',
... list(range(len(bins))), bins_smooth,'-')
... pylab.title('Smoothed histogram of depth values')
... pylab.show()
>>> vis() # doctest: +SKIP
"""
import numpy as np
from scipy.ndimage.filters import gaussian_filter1d
from mindboggle.mio.vtks import read_vtk
# ------------------------------------------------------------------------
# Load depth values for all vertices:
# ------------------------------------------------------------------------
points, indices, lines, faces, depths, scalar_names, npoints, \
input_vtk = read_vtk(depth_file, return_first=True, return_array=True)
# ------------------------------------------------------------------------
# Compute histogram of depth measures:
# ------------------------------------------------------------------------
if npoints > min_vertices:
nbins = np.int(np.round(npoints / 100.0))
else:
raise IOError(" Expecting at least {0} vertices to create "
"depth histogram".format(min_vertices))
bins, bin_edges = np.histogram(depths, bins=nbins)
# ------------------------------------------------------------------------
# Anticipating that there will be a rapidly decreasing distribution
# of low depth values (on the outer surface) with a long tail of higher
# depth values (in the folds), smooth the bin values (Gaussian), convolve
# to compute slopes, and find the depth for the first bin with slope = 0.
# ------------------------------------------------------------------------
bins_smooth = gaussian_filter1d(bins.tolist(), 5)
window = [-1, 0, 1]
bin_slopes = np.convolve(bins_smooth, window, mode='same') / \
(len(window) - 1)
ibins0 = np.where(bin_slopes == 0)[0]
if ibins0.shape:
depth_threshold = bin_edges[ibins0[0]]
else:
depth_threshold = np.median(depths)
# Print statement:
if verbose:
print(' Depth threshold: {0}'.format(depth_threshold))
return depth_threshold, bins, bin_edges
def extract_folds(depth_file, depth_threshold=2, min_fold_size=50,
save_file=False, output_file='', background_value=-1,
verbose=False):
"""
Use depth threshold to extract folds from a triangular surface mesh.
A fold is a group of connected, deep vertices. To extract folds,
a depth threshold is used to segment deep vertices of the surface mesh.
We have observed in the histograms of travel depth measures of cortical
surfaces that there is a rapidly decreasing distribution of low depth
values (corresponding to the outer surface, or gyral crowns) with a
long tail of higher depth values (corresponding to the folds).
The find_depth_threshold function therefore computes a histogram of
travel depth measures, smooths the histogram's bin values, convolves
to compute slopes, and finds the depth value for the first bin with
zero slope. The extract_folds function uses this depth value, segments
deep vertices, and removes extremely small folds (empirically set at 50
vertices or fewer out of a total mesh size of over 100,000 vertices).
Steps ::
1. Segment deep vertices as an initial set of folds.
2. Remove small folds.
3. Renumber folds.
Note ::
Removed option: Find and fill holes in the folds:
Folds could have holes in areas shallower than the depth threshold.
Calling fill_holes() could accidentally include very shallow areas
(in an annulus-shaped fold, for example).
However, we could include the argument exclude_range to check for
any values from zero to min_hole_depth; holes would not be filled
if they were to contain values within this range.
Parameters
----------
depth_file : string
surface mesh file in VTK format with faces and depth scalar values
depth_threshold : float
threshold defining the minimum depth for vertices to be in a fold
min_fold_size : integer
minimum fold size (number of vertices)
save_file : bool
save output VTK file?
output_file : string
name of output file in VTK format
background_value : integer or float
background value
verbose : bool
print statements?
Returns
-------
folds : list of integers
fold numbers for all vertices (-1 for non-fold vertices)
n_folds : int
number of folds
folds_file : string (if save_file)
name of output VTK file with fold IDs (-1 for non-fold vertices)
Examples
--------
>>> from mindboggle.features.folds import extract_folds
>>> from mindboggle.mio.fetch_data import prep_tests
>>> urls, fetch_data = prep_tests()
>>> depth_file = fetch_data(urls['left_travel_depth'], '', '.vtk')
>>> depth_threshold = 2.36089
>>> min_fold_size = 50
>>> save_file = True
>>> output_file = 'extract_folds.vtk'
>>> background_value = -1
>>> verbose = False
>>> folds, n_folds, folds_file = extract_folds(depth_file,
... depth_threshold, min_fold_size, save_file, output_file,
... background_value, verbose)
>>> n_folds
33
>>> lens = [len([x for x in folds if x == y]) for y in range(n_folds)]
>>> lens[0:10]
[726, 67241, 2750, 5799, 1151, 6360, 1001, 505, 228, 198]
View folds (skip test):
>>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP
>>> plot_surfaces('extract_folds.vtk') # doctest: +SKIP
View folds without background (skip test):
>>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP
>>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP
>>> rewrite_scalars(depth_file, 'extract_folds_no_background.vtk', folds,
... 'just_folds', folds, -1) # doctest: +SKIP
>>> plot_surfaces('extract_folds_no_background.vtk') # doctest: +SKIP
"""
import os
import numpy as np
from time import time
from mindboggle.mio.vtks import rewrite_scalars, read_vtk
from mindboggle.guts.mesh import find_neighbors
from mindboggle.guts.segment import segment_regions
if verbose:
print("Extract folds in surface mesh")
t0 = time()
# ------------------------------------------------------------------------
# Load depth values for all vertices
# ------------------------------------------------------------------------
points, indices, lines, faces, depths, scalar_names, npoints, \
input_vtk = read_vtk(depth_file, return_first=True, return_array=True)
# ------------------------------------------------------------------------
# Find the deepest vertices
# ------------------------------------------------------------------------
indices_deep = [i for i,x in enumerate(depths) if x >= depth_threshold]
if indices_deep:
# --------------------------------------------------------------------
# Find neighbors for each vertex
# --------------------------------------------------------------------
neighbor_lists = find_neighbors(faces, npoints)
# --------------------------------------------------------------------
# Segment deep vertices as an initial set of folds
# --------------------------------------------------------------------
if verbose:
print(" Segment vertices deeper than {0:.2f} as folds".format(depth_threshold))
t1 = time()
folds = segment_regions(indices_deep, neighbor_lists, 1, [], False,
False, [], [], [], '', background_value, False)
if verbose:
print(' ...Segmented folds ({0:.2f} seconds)'.format(time() - t1))
# --------------------------------------------------------------------
# Remove small folds
# --------------------------------------------------------------------
if min_fold_size > 1:
if verbose:
print(' Remove folds smaller than {0}'.format(min_fold_size))
unique_folds = [x for x in np.unique(folds)
if x != background_value]
for nfold in unique_folds:
indices_fold = [i for i,x in enumerate(folds) if x == nfold]
if len(indices_fold) < min_fold_size:
folds[indices_fold] = background_value
# --------------------------------------------------------------------
# Find and fill holes in the folds
# Note: Surfaces surrounded by folds can be mistaken for holes,
# so exclude_range includes outer surface values close to zero.
# --------------------------------------------------------------------
# folds = fill_holes(folds, neighbor_lists, values=depths,
# exclude_range=[0, min_hole_depth])
# --------------------------------------------------------------------
# Renumber folds so they are sequential.
# NOTE: All vertices are included (-1 for non-fold vertices).
# --------------------------------------------------------------------
renumber_folds = background_value * np.ones(npoints)
fold_numbers = [x for x in np.unique(folds) if x != background_value]
for i_fold, n_fold in enumerate(fold_numbers):
fold_indices = [i for i,x in enumerate(folds) if x == n_fold]
renumber_folds[fold_indices] = i_fold
folds = renumber_folds
folds = [int(x) for x in folds]
n_folds = i_fold + 1
# Print statement
if verbose:
print(' ...Extracted {0} folds ({1:.2f} seconds)'.
format(n_folds, time() - t0))
else:
if verbose:
print(' No deep vertices')
# ------------------------------------------------------------------------
# Return folds, number of folds, file name
# ------------------------------------------------------------------------
if save_file:
if output_file:
folds_file = output_file
else:
folds_file = os.path.join(os.getcwd(), 'folds.vtk')
rewrite_scalars(depth_file, folds_file, folds, 'folds', [],
background_value)
if not os.path.exists(folds_file):
raise IOError(folds_file + " not found")
else:
folds_file = None
return folds, n_folds, folds_file
# def extract_subfolds(depth_file, folds, min_size=10, depth_factor=0.25,
# depth_ratio=0.1, tolerance=0.01, save_file=False,
# background_value=-1, verbose=False):
# """
# Use depth to segment folds into subfolds in a triangular surface mesh.
#
# Note ::
#
# The function extract_sulci() performs about the same whether folds
# or subfolds are used as input. The latter leads to some loss of
# small subfolds and possibly holes for small subfolds in the middle
# of other subfolds.
#
# Note about the watershed() function:
# The watershed() function performs individual seed growing from deep seeds,
# repeats segmentation from the resulting seeds until each seed's segment
# touches a boundary. The function segment() fills in the rest. Finally
# segments are joined if their seeds are too close to each other.
# Despite these precautions, the order of seed selection in segment() could
# possibly influence the resulting borders between adjoining segments.
# [The propagate() function is slower and insensitive to depth,
# but is not biased by seed order.]
#
# Parameters
# ----------
# depth_file : string
# surface mesh file in VTK format with faces and depth scalar values
# folds : list of integers
# fold numbers for all vertices (-1 for non-fold vertices)
# min_size : integer
# minimum number of vertices for a subfold
# depth_factor : float
# watershed() depth_factor:
# factor to determine whether to merge two neighboring watershed
# catchment basins -- they are merged if the Euclidean distance between
# their basin seeds is less than this fraction of the maximum Euclidean
# distance between points having minimum and maximum depths
# depth_ratio : float
# watershed() depth_ratio:
# the minimum fraction of depth for a neighboring shallower
# watershed catchment basin (otherwise merged with the deeper basin)
# tolerance : float
# watershed() tolerance:
# tolerance for detecting differences in depth between vertices
# save_file : bool
# save output VTK file?
# background_value : integer or float
# background value
# verbose : bool
# verbose output?
#
# Returns
# -------
# subfolds : list of integers
# fold numbers for all vertices (-1 for non-fold vertices)
# n_subfolds : int
# number of subfolds
# subfolds_file : string (if save_file)
# name of output VTK file with fold IDs (-1 for non-fold vertices)
#
# Examples
# --------
# >>> import os
# >>> from mindboggle.mio.vtks import read_scalars, rewrite_scalars
# >>> from mindboggle.guts.mesh import find_neighbors_from_file
# >>> from mindboggle.features.folds import extract_subfolds
# >>> from mindboggle.mio.plots import plot_surfaces
# >>> path = os.environ['MINDBOGGLE_DATA']
# >>> depth_file = os.path.join(path, 'shapes', 'left_cortical_surface',
# ... 'travel_depth.vtk')
# >>> folds_file = os.path.join(path, 'features', 'left_cortical_surface',
# ... 'folds.vtk')
# >>> folds, name = read_scalars(folds_file)
# >>> min_size = 10
# >>> depth_factor = 0.5
# >>> depth_ratio = 0.1
# >>> tolerance = 0.01
# >>> save_file = False
# >>> background_value = -1
# >>> verbose = False
# >>> subfolds, n_subfolds, subfolds_file = extract_subfolds(depth_file,
# ... folds, min_size, depth_factor, depth_ratio, tolerance, save_file,
# ... background_value, verbose)
# >>> n_subfolds
# 288
# >>> [len([x for x in subfolds if x == y]) for y in range(n_subfolds)] # doctest: +ELLIPSIS
# [3406, 31, 678, 1208, 1241, 30,, ..., 93, 107, 68, 90, 131, 71]
# >>>
# >>> # View:
# >>> def vis():
# ... rewrite_scalars(depth_file, 'subfolds.vtk', subfolds, 'subfolds',
# ... subfolds)
# ... plot_surfaces('subfolds.vtk')
# >>> vis() # doctest: +SKIP
#
# """
# import os
# import numpy as np
# from time import time
# from mindboggle.mio.vtks import rewrite_scalars, read_vtk
# from mindboggle.guts.mesh import find_neighbors
# from mindboggle.guts.segment import watershed
#
# if verbose:
# print("Segment folds into subfolds")
# t0 = time()
#
# # ------------------------------------------------------------------------
# # Load depth values for all vertices
# # ------------------------------------------------------------------------
# points, indices, lines, faces, depths, scalar_names, npoints, \
# input_vtk = read_vtk(depth_file, return_first=True, return_array=True)
#
# # ------------------------------------------------------------------------
# # Find neighbors for each vertex
# # ------------------------------------------------------------------------
# neighbor_lists = find_neighbors(faces, npoints)
#
# # ------------------------------------------------------------------------
# # Segment folds into "watershed basins"
# # ------------------------------------------------------------------------
# indices_folds = [i for i,x in enumerate(folds) if x != -1]
# subfolds, seed_indices = watershed(depths, points, indices_folds,
# neighbor_lists, min_size,
# depth_factor=0.25, depth_ratio=0.1,
# tolerance=0.01, regrow=True,
# background_value=background_value,
# verbose=False)
#
# # Print statement
# n_subfolds = len([x for x in np.unique(subfolds) if x != -1])
# if verbose:
# print(' Extracted {0} subfolds ({1:.2f} seconds)'.
# format(n_subfolds, time() - t0))
#
# # ------------------------------------------------------------------------
# # Return subfolds, number of subfolds, file name
# # ------------------------------------------------------------------------
# if save_file:
# subfolds_file = os.path.join(os.getcwd(), 'subfolds.vtk')
# rewrite_scalars(depth_file, subfolds_file,
# subfolds, 'subfolds', subfolds)
#
# if not os.path.exists(subfolds_file):
# raise IOError(subfolds_file + " not found")
#
# else:
# subfolds_file = None
#
# return subfolds, n_subfolds, subfolds_file
# ============================================================================
# Doctests
# ============================================================================
if __name__ == "__main__":
import doctest
doctest.testmod(verbose=True) # py.test --doctest-modules