/
fundi.py
257 lines (224 loc) · 11 KB
/
fundi.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
#!/usr/bin/env python
"""
Extract fundus curves from surface mesh patches (folds).
Authors:
Arno Klein, 2013-2016 . arno@mindboggle.info . www.binarybottle.com
Copyright 2016, Mindboggle team (http://mindboggle.info), Apache v2.0 License
"""
def extract_fundi(folds, curv_file, depth_file, min_separation=10,
erode_ratio=0.1, erode_min_size=1, save_file=False,
output_file='', background_value=-1, verbose=False):
"""
Extract fundi from folds.
A fundus is a branching curve that runs along the deepest and most highly
curved portions of a fold. This function extracts one fundus from each
fold by finding the deepest vertices inside the fold, finding endpoints
along the edge of the fold, and connecting the former to the latter with
tracks that run along deep and curved paths (through vertices with high
values of travel depth multiplied by curvature), and a final filtration
step.
The deepest vertices are those with values at least two median
absolute deviations above the median (non-zero) value, with the higher
value chosen if two of the vertices are within (a default of) 10 edges
from each other (to reduce the number of possible fundus paths as well
as computation time).
To find the endpoints, the find_outer_endpoints function propagates
multiple tracks from seed vertices at median depth in the fold through
concentric rings toward the fold’s edge, selecting maximal values within
each ring, and terminating at candidate endpoints. The final endpoints
are those candidates at the end of tracks that have a high median value,
with the higher value chosen if two candidate endpoints are within
(a default of) 10 edges from each other (otherwise, the resulting fundi
can have spurious branching at the fold’s edge).
The connect_points_erosion function connects the deepest fold vertices
to the endpoints with a skeleton of 1-vertex-thick curves by erosion.
It erodes by iteratively removing simple topological points and endpoints
in order of lowest to highest values, where a simple topological point
is a vertex that when added to or removed from an object on a surface
mesh (such as a fundus curve) does not alter the object's topology.
Steps ::
1. Find fundus endpoints (outer anchors) with find_outer_endpoints().
2. Include inner anchor points.
3. Connect anchor points using connect_points_erosion();
inner anchors are removed if they result in endpoints.
Note ::
Follow this with segment_by_region() to segment fundi by sulci.
Parameters
----------
folds : numpy array or list of integers
fold number for each vertex
curv_file : string
surface mesh file in VTK format with mean curvature values
depth_file : string
surface mesh file in VTK format with rescaled depth values
likelihoods : list of integers
fundus likelihood value for each vertex
min_separation : integer
minimum number of edges between inner/outer anchor points
erode_ratio : float
fraction of indices to test for removal at each iteration
in connect_points_erosion()
save_file : bool
save output VTK file?
output_file : string
output VTK file
background_value : integer or float
background value
verbose : bool
print statements?
Returns
-------
fundus_per_fold : list of integers
fundus numbers for all vertices, labeled by fold
(-1 for non-fundus vertices)
n_fundi_in_folds : integer
number of fundi
fundus_per_fold_file : string (if save_file)
output VTK file with fundus numbers (-1 for non-fundus vertices)
Examples
--------
>>> # Extract fundus from one or more folds:
>>> import numpy as np
>>> from mindboggle.mio.vtks import read_scalars
>>> from mindboggle.features.fundi import extract_fundi
>>> from mindboggle.mio.fetch_data import prep_tests
>>> urls, fetch_data = prep_tests()
>>> curv_file = fetch_data(urls['left_mean_curvature'], '', '.vtk')
>>> depth_file = fetch_data(urls['left_travel_depth'], '', '.vtk')
>>> folds_file = fetch_data(urls['left_folds'], '', '.vtk')
>>> folds, name = read_scalars(folds_file, True, True)
>>> # 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) if x not in fold_numbers]
... folds[i0] = -1
>>> min_separation = 10
>>> erode_ratio = 0.10
>>> erode_min_size = 10
>>> save_file = True
>>> output_file = 'extract_fundi_fold4.vtk'
>>> background_value = -1
>>> verbose = False
>>> o1, o2, fundus_per_fold_file = extract_fundi(folds, curv_file,
... depth_file, min_separation, erode_ratio, erode_min_size,
... save_file, output_file, background_value, verbose)
>>> lens = [len([x for x in o1 if x == y])
... for y in np.unique(o1) if y != background_value]
>>> lens[0:10] # [66, 2914, 100, 363, 73, 331, 59, 30, 1, 14] # (if not limit_folds)
[73]
View result without background (skip test):
>>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP
>>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP
>>> rewrite_scalars(fundus_per_fold_file,
... 'extract_fundi_fold4_no_background.vtk', o1,
... 'fundus_per_fold', folds) # doctest: +SKIP
>>> plot_surfaces('extract_fundi_fold4_no_background.vtk') # doctest: +SKIP
"""
# Extract a skeleton to connect endpoints in a fold:
import os
import numpy as np
from time import time
from mindboggle.mio.vtks import read_scalars, read_vtk, rewrite_scalars
from mindboggle.guts.compute import median_abs_dev
from mindboggle.guts.paths import find_max_values
from mindboggle.guts.mesh import find_neighbors_from_file
#from mindboggle.guts.mesh import find_complete_faces
from mindboggle.guts.paths import find_outer_endpoints
from mindboggle.guts.paths import connect_points_erosion
if isinstance(folds, list):
folds = np.array(folds)
# Load values, inner anchor threshold, and neighbors:
if os.path.isfile(curv_file):
points, indices, lines, faces, curvs, scalar_names, npoints, \
input_vtk = read_vtk(curv_file, True, True)
else:
raise IOError("{0} doesn't exist!".format(curv_file))
if os.path.isfile(curv_file):
depths, name = read_scalars(depth_file, True, True)
else:
raise IOError("{0} doesn't exist!".format(depth_file))
values = curvs * depths
values0 = [x for x in values if x > 0]
thr = np.median(values0) + 2 * median_abs_dev(values0)
neighbor_lists = find_neighbors_from_file(curv_file)
# ------------------------------------------------------------------------
# Loop through folds:
# ------------------------------------------------------------------------
t1 = time()
skeletons = []
unique_fold_IDs = [x for x in np.unique(folds) if x != background_value]
if verbose:
if len(unique_fold_IDs) == 1:
print("Extract a fundus from 1 fold...")
else:
print("Extract a fundus from each of {0} folds...".
format(len(unique_fold_IDs)))
for fold_ID in unique_fold_IDs:
indices_fold = [i for i,x in enumerate(folds) if x == fold_ID]
if indices_fold:
if verbose:
print(' Fold {0}:'.format(int(fold_ID)))
# ----------------------------------------------------------------
# Find outer anchor points on the boundary of the surface region,
# to serve as fundus endpoints:
# ----------------------------------------------------------------
outer_anchors, tracks = find_outer_endpoints(indices_fold,
neighbor_lists, values, depths, min_separation,
background_value, verbose)
# ----------------------------------------------------------------
# Find inner anchor points:
# ----------------------------------------------------------------
inner_anchors = find_max_values(points, values, min_separation,
thr)
# ----------------------------------------------------------------
# Connect anchor points to create skeleton:
# ----------------------------------------------------------------
B = background_value * np.ones(npoints)
B[indices_fold] = 1
skeleton = connect_points_erosion(B, neighbor_lists,
outer_anchors, inner_anchors, values, erode_ratio,
erode_min_size, [], '', background_value, verbose)
if skeleton:
skeletons.extend(skeleton)
## ---------------------------------------------------------------
## Remove fundus vertices if they make complete triangle faces:
## ---------------------------------------------------------------
#Iremove = find_complete_faces(skeletons, faces)
#if Iremove:
# skeletons = list(frozenset(skeletons).difference(Iremove))
indices_skel = [x for x in skeletons if folds[x] != background_value]
fundus_per_fold = background_value * np.ones(npoints)
fundus_per_fold[indices_skel] = folds[indices_skel]
n_fundi_in_folds = len([x for x in np.unique(fundus_per_fold)
if x != background_value])
if n_fundi_in_folds == 1:
sdum = 'fold fundus'
else:
sdum = 'fold fundi'
if verbose:
print(' ...Extracted {0} {1}; {2} total ({3:.2f} seconds)'.
format(n_fundi_in_folds, sdum, n_fundi_in_folds, time() - t1))
# ------------------------------------------------------------------------
# Return fundi, number of fundi, and file name:
# ------------------------------------------------------------------------
fundus_per_fold_file = None
if n_fundi_in_folds > 0:
fundus_per_fold = [int(x) for x in fundus_per_fold]
if save_file:
if output_file:
fundus_per_fold_file = output_file
else:
fundus_per_fold_file = os.path.join(os.getcwd(),
'fundus_per_fold.vtk')
rewrite_scalars(curv_file, fundus_per_fold_file, fundus_per_fold,
'fundi', [], background_value)
if not os.path.exists(fundus_per_fold_file):
raise IOError(fundus_per_fold_file + " not found")
return fundus_per_fold, n_fundi_in_folds, fundus_per_fold_file
# ============================================================================
# Doctests
# ============================================================================
if __name__ == "__main__":
import doctest
doctest.testmod(verbose=True) # py.test --doctest-modules