-
Notifications
You must be signed in to change notification settings - Fork 56
Expand file tree
/
Copy pathsulci.py
More file actions
495 lines (435 loc) · 23.7 KB
/
Copy pathsulci.py
File metadata and controls
495 lines (435 loc) · 23.7 KB
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
485
486
487
488
489
490
491
492
493
494
495
#!/usr/bin/env python
"""
Functions to extract sulci from folds.
Authors:
- Arno Klein, 2012-2016 (arno@mindboggle.info) http://binarybottle.com
Copyright 2016, Mindboggle team (http://mindboggle.info), Apache v2.0 License
"""
def extract_sulci(labels_file, folds_or_file, hemi, min_boundary=1,
sulcus_names=[], save_file=False, output_file='',
background_value=-1, verbose=False):
"""
Identify sulci from folds in a brain surface according to a labeling
protocol that includes a list of label pairs defining each sulcus.
Since folds are defined as deep, connected areas of a surface, and since
folds may be connected to each other in ways that differ across brains,
there usually does not exist a one-to-one mapping between folds of one
brain and those of another. To address the correspondence problem then,
we need to find just those portions of the folds that correspond across
brains. To accomplish this, Mindboggle segments folds into sulci, which
do have a one-to-one correspondence across non-pathological brains.
Mindboggle defines a sulcus as a folded portion of cortex whose opposing
banks are labeled with one or more sulcus label pairs in the DKT labeling
protocol, where each label pair is unique to one sulcus and represents
a boundary between two adjacent gyri, and each vertex has one gyrus label.
This function assigns vertices in a fold to a sulcus in one of two cases.
In the first case, vertices whose labels are in only one label pair in
the fold are assigned to the label pair's sulcus if they are connected
through similarly labeled vertices to the boundary between the two labels.
In the second case, the segment_regions function propagates labels from
label borders to vertices whose labels are in multiple label pairs in the
fold.
Steps for each fold ::
1. Remove fold if it has fewer than two labels.
2. Remove fold if its labels do not contain a sulcus label pair.
3. Find vertices with labels that are in only one of the fold's
label boundary pairs. Assign the vertices the sulcus with the label
pair if they are connected to the label boundary for that pair.
4. If there are remaining vertices, segment into sets of vertices
connected to label boundaries, and assign a unique ID to each set.
Parameters
----------
labels_file : string
file name for surface mesh VTK containing labels for all vertices
folds_or_file : numpy array, list or string
fold number for each vertex / name of VTK file containing fold scalars
hemi : string
hemisphere abbreviation in {'lh', 'rh'} for sulcus labels
min_boundary : integer
minimum number of vertices for a sulcus label boundary segment
sulcus_names : list of strings
names of sulci
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
-------
sulci : list of integers
sulcus numbers for all vertices (-1 for non-sulcus vertices)
n_sulci : integers
number of sulci
sulci_file : string
output VTK file with sulcus numbers (-1 for non-sulcus vertices)
Examples
--------
>>> # Example 1: Extract sulcus from a fold with one sulcus label pair:
>>> import numpy as np
>>> from mindboggle.features.sulci import extract_sulci
>>> from mindboggle.mio.vtks import read_scalars
>>> from mindboggle.mio.fetch_data import prep_tests
>>> urls, fetch_data = prep_tests()
>>> # Load labels, folds, neighbor lists, and sulcus names and label pairs
>>> labels_file = fetch_data(urls['left_freesurfer_labels'], '', '.vtk')
>>> folds_file = fetch_data(urls['left_folds'], '', '.vtk')
>>> folds_or_file, name = read_scalars(folds_file, True, True)
>>> save_file = True
>>> output_file = 'extract_sulci_fold4_1sulcus.vtk'
>>> background_value = -1
>>> # Limit number of folds to speed up the test:
>>> limit_folds = True
>>> if limit_folds:
... fold_numbers = [4] #[4, 6]
... i0 = [i for i,x in enumerate(folds_or_file) if x not in fold_numbers]
... folds_or_file[i0] = background_value
>>> hemi = 'lh'
>>> min_boundary = 10
>>> sulcus_names = []
>>> verbose = False
>>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file,
... hemi, min_boundary, sulcus_names, save_file, output_file,
... background_value, verbose)
>>> n_sulci # 23 # (if not limit_folds)
1
>>> lens = [len([x for x in sulci if x==y])
... for y in np.unique(sulci) if y != -1]
>>> lens[0:10] # [6358, 3288, 7612, 5205, 4414, 6251, 3493, 2566, 4436, 739] # (if not limit_folds)
[1151]
View result without background (skip test):
>>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP
>>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP
>>> output = 'extract_sulci_fold4_1sulcus_no_background.vtk'
>>> rewrite_scalars(sulci_file, output, sulci,
... 'sulci', sulci) # doctest: +SKIP
>>> plot_surfaces(output) # doctest: +SKIP
Example 2: Extract sulcus from a fold with multiple sulcus label pairs:
>>> folds_or_file, name = read_scalars(folds_file, True, True)
>>> output_file = 'extract_sulci_fold7_2sulci.vtk'
>>> # Limit number of folds to speed up the test:
>>> limit_folds = True
>>> if limit_folds:
... fold_numbers = [7] #[4, 6]
... i0 = [i for i,x in enumerate(folds_or_file) if x not in fold_numbers]
... folds_or_file[i0] = background_value
>>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file,
... hemi, min_boundary, sulcus_names, save_file, output_file,
... background_value, verbose)
>>> n_sulci # 23 # (if not limit_folds)
2
>>> lens = [len([x for x in sulci if x==y])
... for y in np.unique(sulci) if y != -1]
>>> lens[0:10] # [6358, 3288, 7612, 5205, 4414, 6251, 3493, 2566, 4436, 739] # (if not limit_folds)
[369, 93]
View result without background (skip test):
>>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP
>>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP
>>> output = 'extract_sulci_fold7_2sulci_no_background.vtk'
>>> rewrite_scalars(sulci_file, output, sulci,
... 'sulci', sulci) # doctest: +SKIP
>>> plot_surfaces(output) # doctest: +SKIP
"""
import os
from time import time
import numpy as np
from mindboggle.mio.vtks import read_scalars, read_vtk, rewrite_scalars
from mindboggle.guts.mesh import find_neighbors
from mindboggle.guts.segment import extract_borders, propagate, segment_regions
from mindboggle.mio.labels import DKTprotocol
# Load fold numbers if folds_or_file is a string:
if isinstance(folds_or_file, str):
folds, name = read_scalars(folds_or_file)
elif isinstance(folds_or_file, list):
folds = folds_or_file
elif isinstance(folds_or_file, np.ndarray):
folds = folds_or_file.tolist()
dkt = DKTprotocol()
if hemi == 'lh':
pair_lists = dkt.left_sulcus_label_pair_lists
elif hemi == 'rh':
pair_lists = dkt.right_sulcus_label_pair_lists
else:
raise IOError("Warning: hemisphere not properly specified ('lh' or 'rh').")
# Load points, faces, and neighbors:
points, indices, lines, faces, labels, scalar_names, npoints, \
input_vtk = read_vtk(labels_file)
neighbor_lists = find_neighbors(faces, npoints)
# Array of sulcus IDs for fold vertices, initialized as -1.
# Since we do not touch gyral vertices and vertices whose labels
# are not in the label list, or vertices having only one label,
# their sulcus IDs will remain -1:
sulci = background_value * np.ones(npoints)
# ------------------------------------------------------------------------
# Loop through folds
# ------------------------------------------------------------------------
fold_numbers = [int(x) for x in np.unique(folds) if x != background_value]
n_folds = len(fold_numbers)
if verbose:
print("Extract sulci from {0} folds...".format(n_folds))
t0 = time()
for n_fold in fold_numbers:
fold_indices = [i for i,x in enumerate(folds) if x == n_fold]
len_fold = len(fold_indices)
# List the labels in this fold:
fold_labels = [labels[x] for x in fold_indices]
unique_fold_labels = [int(x) for x in np.unique(fold_labels)
if x != background_value]
# --------------------------------------------------------------------
# NO MATCH -- fold has fewer than two labels
# --------------------------------------------------------------------
if verbose and len(unique_fold_labels) < 2:
# Ignore: sulci already initialized with -1 values:
if not unique_fold_labels:
print(" Fold {0} ({1} vertices): "
"NO MATCH -- fold has no labels".
format(n_fold, len_fold))
else:
print(" Fold {0} ({1} vertices): "
"NO MATCH -- fold has only one label ({2})".
format(n_fold, len_fold, unique_fold_labels[0]))
# Ignore: sulci already initialized with -1 values
else:
# Find all label boundary pairs within the fold:
indices_fold_pairs, fold_pairs, unique_fold_pairs = \
extract_borders(fold_indices, labels, neighbor_lists,
ignore_values=[], return_label_pairs=True)
# Find fold label pairs in the protocol (pairs are already sorted):
fold_pairs_in_protocol = [x for x in unique_fold_pairs
if x in dkt.unique_sulcus_label_pairs]
if verbose and unique_fold_labels:
print(" Fold {0} labels: {1} ({2} vertices)".format(n_fold,
', '.join([str(x) for x in unique_fold_labels]),
len_fold))
# ----------------------------------------------------------------
# NO MATCH -- fold has no sulcus label pair
# ----------------------------------------------------------------
if verbose and not fold_pairs_in_protocol:
print(" Fold {0}: NO MATCH -- fold has no sulcus label pair".
format(n_fold, len_fold))
# ----------------------------------------------------------------
# Possible matches
# ----------------------------------------------------------------
else:
if verbose:
print(" Fold {0} label pairs in protocol: {1}".format(n_fold,
', '.join([str(x) for x in fold_pairs_in_protocol])))
# Labels in the protocol (includes repeats across label pairs):
labels_in_pairs = [x for lst in fold_pairs_in_protocol
for x in lst]
# Labels that appear in one or more sulcus label boundary:
unique_labels = []
nonunique_labels = []
for label in np.unique(labels_in_pairs):
if len([x for x in labels_in_pairs if x == label]) == 1:
unique_labels.append(label)
else:
nonunique_labels.append(label)
# ------------------------------------------------------------
# Vertices whose labels are in only one sulcus label pair
# ------------------------------------------------------------
# Find vertices with a label that is in only one of the fold's
# label pairs (the other label in the pair can exist in other
# pairs). Assign the vertices the sulcus with the label pair
# if they are connected to the label boundary for that pair.
# ------------------------------------------------------------
if unique_labels:
for pair in fold_pairs_in_protocol:
# If one or both labels in label pair is/are unique:
unique_labels_in_pair = [x for x in pair
if x in unique_labels]
n_unique = len(unique_labels_in_pair)
if n_unique:
ID = None
for i, pair_list in enumerate(pair_lists):
if not isinstance(pair_list, list):
pair_list = [pair_list]
if pair in pair_list:
ID = i
break
if ID:
# Seeds from label boundary vertices
# (fold_pairs and pair already sorted):
indices_pair = [x for i,x
in enumerate(indices_fold_pairs)
if fold_pairs[i] == pair]
# Vertices with unique label(s) in pair:
indices_unique_labels = [fold_indices[i]
for i,x in enumerate(fold_labels)
if x in unique_labels_in_pair]
#dkt.unique_sulcus_label_pairs]
# Propagate sulcus ID from seeds to vertices
# with "unique" labels (only exist in one
# label pair in a fold); propagation ensures
# that sulci consist of contiguous vertices
# for each label boundary:
sulci2 = segment_regions(indices_unique_labels,
neighbor_lists,
min_region_size=1,
seed_lists=[indices_pair],
keep_seeding=False,
spread_within_labels=True,
labels=labels,
label_lists=[],
values=[], max_steps='',
background_value=background_value,
verbose=False)
sulci[sulci2 != background_value] = ID
# Print statement:
if verbose:
if n_unique == 1:
ps1 = 'One label'
else:
ps1 = 'Both labels'
if len(sulcus_names):
ps2 = sulcus_names[ID]
else:
ps2 = ''
print(" {0} unique to one fold pair: "
"{1} {2}".
format(ps1, ps2,
unique_labels_in_pair))
# ------------------------------------------------------------
# Vertex labels shared by multiple label pairs
# ------------------------------------------------------------
# Propagate labels from label borders to vertices with labels
# that are shared by multiple label pairs in the fold.
# ------------------------------------------------------------
if len(nonunique_labels):
# For each label shared by different label pairs:
for label in nonunique_labels:
# Print statement:
if verbose:
print(" Propagate sulcus borders with label {0}".
format(int(label)))
# Construct seeds from label boundary vertices:
seeds = background_value * np.ones(npoints)
for ID, pair_list in enumerate(pair_lists):
if not isinstance(pair_list, list):
pair_list = [pair_list]
label_pairs = [x for x in pair_list if label in x]
for label_pair in label_pairs:
indices_pair = [x for i,x
in enumerate(indices_fold_pairs)
if np.sort(fold_pairs[i]).
tolist() == label_pair]
if indices_pair:
# Do not include short boundary segments:
if min_boundary > 1:
indices_pair2 = []
seeds2 = segment_regions(indices_pair,
neighbor_lists, 1, [],
False, False, [], [],
[], '', background_value,
verbose)
useeds2 = [x for x in
np.unique(seeds2)
if x != background_value]
for seed2 in useeds2:
iseed2 = [i for i,x
in enumerate(seeds2)
if x == seed2]
if len(iseed2) >= min_boundary:
indices_pair2.extend(iseed2)
elif verbose:
if len(iseed2) == 1:
print(" Remove "
"assignment "
"of ID {0} from "
"1 vertex".
format(seed2))
else:
print(" Remove "
"assignment "
"of ID {0} from "
"{1} vertices".
format(seed2,
len(iseed2)))
indices_pair = indices_pair2
# Assign sulcus IDs to seeds:
seeds[indices_pair] = ID
# Identify vertices with the label:
indices_label = [fold_indices[i] for i,x
in enumerate(fold_labels)
if x == label]
if len(indices_label):
# Propagate sulcus ID from seeds to vertices
# with a given shared label:
seg_vs_prop = False
if seg_vs_prop:
indices_seeds = []
for seed in [x for x in np.unique(seeds)
if x != background_value]:
indices_seeds.append([i for i,x
in enumerate(seeds)
if x == seed])
sulci2 = segment_regions(indices_label,
neighbor_lists, 50, indices_seeds,
False, True, labels, [], [], '',
background_value, verbose)
else:
label_array = background_value * \
np.ones(npoints)
label_array[indices_label] = 1
sulci2 = propagate(points, faces,
label_array, seeds, sulci,
max_iters=10000,
tol=0.001, sigma=5,
background_value=background_value,
verbose=verbose)
sulci[sulci2 != background_value] = \
sulci2[sulci2 != background_value]
sulcus_numbers = [int(x) for x in np.unique(sulci)
if x != background_value]
n_sulci = len(sulcus_numbers)
# ------------------------------------------------------------------------
# Print statements
# ------------------------------------------------------------------------
if verbose:
if n_sulci == 1:
sulcus_str = 'sulcus'
else:
sulcus_str = 'sulci'
if n_folds == 1:
folds_str = 'fold'
else:
folds_str = 'folds'
print("Extracted {0} {1} from {2} {3} ({4:.1f}s):".
format(n_sulci, sulcus_str, n_folds, folds_str, time()-t0))
if sulcus_names:
for sulcus_number in sulcus_numbers:
print(" {0}: {1}".format(sulcus_number,
sulcus_names[sulcus_number]))
elif sulcus_numbers:
print(" " + ", ".join([str(x) for x in sulcus_numbers]))
unresolved = [i for i in range(len(pair_lists))
if i not in sulcus_numbers]
if len(unresolved) == 1:
print("The following sulcus is unaccounted for:")
else:
print("The following {0} sulci are unaccounted for:".
format(len(unresolved)))
if sulcus_names:
for sulcus_number in unresolved:
print(" {0}: {1}".format(sulcus_number,
sulcus_names[sulcus_number]))
else:
print(" " + ", ".join([str(x) for x in unresolved]))
# ------------------------------------------------------------------------
# Return sulci, number of sulci, and file name
# ------------------------------------------------------------------------
sulci = [int(x) for x in sulci]
sulci_file = os.path.join(os.getcwd(), 'sulci.vtk')
rewrite_scalars(labels_file, sulci_file, sulci, 'sulci', [],
background_value)
if not os.path.exists(sulci_file):
raise IOError(sulci_file + " not found")
return sulci, n_sulci, sulci_file
# ============================================================================
# Doctests
# ============================================================================
if __name__ == "__main__":
import doctest
doctest.testmod(verbose=True) # py.test --doctest-modules