-
Notifications
You must be signed in to change notification settings - Fork 75
/
Copy pathvol2surf.m
270 lines (243 loc) · 10.2 KB
/
vol2surf.m
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
function [no, el, regions, holes] = vol2surf(img, ix, iy, iz, opt, dofix, method, isovalues)
%
% [no,el,regions,holes]=vol2surf(img,ix,iy,iz,opt,dofix,method,isovalues)
%
% converting a 3D volumetric image to surfaces
%
% author: Qianqian Fang (q.fang at neu.edu)
%
% input:
% img: a volumetric binary image; if img is empty, vol2surf will
% return user defined surfaces via opt.surf if it exists
% ix,iy,iz: subvolume selection indices in x,y,z directions
% opt: function parameters
% if method is 'cgalsurf' or 'cgalpoly':
% opt=a float number>1: max radius of the Delaunay sphere(element size)
% opt.radbound: same as above, max radius of the Delaunay sphere
% opt.distbound: maximum deviation from the specified isosurfaces
% opt(1,2,...).radbound: same as above, for each levelset
% if method is 'simplify':
% opt=a float number<1: compression rate for surf. simplification
% opt.keepratio=a float less than 1: same as above, same for all surf.
% opt(1,2,..).keepratio: setting compression rate for each levelset
% opt(1,2,..).maxsurf: 1 - only use the largest disjointed surface
% 0 - use all surfaces for that levelset
% opt(1,2,..).side: - 'upper': threshold at upper interface
% 'lower': threshold at lower interface
% opt(1,2,..).maxnode: - the maximum number of surface node per levelset
% opt(1,2,..).holes: user specified holes interior pt list
% opt(1,2,..).regions: user specified regions interior pt list
% opt(1,2,..).surf.{node,elem}: add additional surfaces
% opt(1,2,..).{A,B}: linear transformation for each surface
% opt.autoregion: if set to 1, vol2surf will try to determine
% the interior points for each closed surface automatically
% dofix: 1: perform mesh validation&repair, 0: skip repairing
% method: - if method is 'simplify', iso2mesh will first call
% binsurface to generate a voxel-based surface mesh and then
% use meshresample/meshcheckrepair to create a coarser mesh;
% - if method is 'cgalsurf', iso2mesh will call the surface
% extraction program from CGAL to make surface mesh
% - if method is not specified, 'cgalsurf' is assumed by default
% isovalues: a list of isovalues where the levelset is defined
%
% output:
% no: list of nodes on the resulting suface mesh, 3 columns for x,y,z
% el: list of trianglular elements on the surface, [n1,n2,n3,region_id]
% regions: list of interior points for all sub-region, [x,y,z]
% holes: list of interior points for all holes, [x,y,z]
%
% -- this function is part of iso2mesh toolbox (http://iso2mesh.sf.net)
%
fprintf(1, 'extracting surfaces from a volume ...\n');
el = [];
no = [];
if (isstruct(opt) && isfield(opt, 'holes'))
holes = opt.holes;
else
holes = [];
end
if (isstruct(opt) && isfield(opt, 'regions'))
regions = opt.regions;
else
regions = [];
end
maxlevel = 0;
if (~isempty(img))
img = img(ix, iy, iz);
dim = size(img);
newdim = dim + [2 2 2];
newimg = zeros(newdim);
newimg(2:end - 1, 2:end - 1, 2:end - 1) = img;
if (nargin < 8)
maxlevel = max(newimg(:));
isovalues = 1:maxlevel;
else
isovalues = unique(sort(isovalues));
maxlevel = length(isovalues);
end
% compute the region seed for each region
for i = 1:maxlevel
if (i < maxlevel)
levelmask = int8(newimg >= isovalues(i) & newimg < isovalues(i + 1));
else
levelmask = int8(newimg >= isovalues(i));
end
[isoct, octver] = isoctavemesh;
if (~isoct || sscanf(octver, '%d') > 5)
[levelno, levelel] = binsurface(levelmask, 'iso');
else
[levelno, levelel] = binsurface(levelmask);
end
if (~isempty(levelel))
if (isstruct(opt) && isfield(opt, 'autoregion'))
if (opt.autoregion)
seeds = surfseeds(levelno, levelel);
else
seeds = surfinterior(levelno, levelel);
end
else
seeds = surfinterior(levelno, levelel);
end
if (~isempty(seeds))
disp([sprintf('region %d centroid :', i) sprintf('\t%f %f %f\n', seeds')]);
if (~isempty(regions))
regions(end + 1:end + size(seeds, 1), :) = seeds;
else
regions = seeds;
end
else
regions(end + 1, :) = mean(levelno);
end
end
end
for i = 1:maxlevel
fprintf(1, 'processing threshold level %d...\n', i);
if (nargin >= 7 && strcmp(method, 'simplify'))
[v0, f0] = binsurface(newimg >= isovalues(i)); % not sure if binsurface works for multi-value arrays
% with binsurface, I think the following line is not needed anymore
% v0(:,[1 2])=v0(:,[2 1]); % isosurface(V,th) assumes x/y transposed
if (dofix)
[v0, f0] = meshcheckrepair(v0, f0);
end
if (isstruct(opt) && length(opt) == maxlevel)
keepratio = opt(i).keepratio;
elseif (isstruct(opt) && length(opt) == 1)
keepratio = opt.keepratio;
else
keepratio = opt;
end
% first, resample the surface mesh with cgal
fprintf(1, 'resampling surface mesh for level %d...\n', i);
[v0, f0] = meshresample(v0, f0, keepratio);
% iso2mesh is not stable for meshing small islands,remove them (max 3x3x3 voxels)
f0 = removeisolatedsurf(v0, f0, 3);
if (dofix)
[v0, f0] = meshcheckrepair(v0, f0);
end
elseif (nargin < 7 || strcmp(method, 'cgalsurf') || strcmp(method, 'cgalpoly'))
if (isstruct(opt) && length(opt) == maxlevel)
radbound = opt(i).radbound;
elseif (isstruct(opt) && length(opt) == 1)
radbound = opt.radbound;
else
radbound = opt;
end
maxsurfnode = 40000; % maximum node numbers for each level
if (isstruct(opt) && length(opt) == maxlevel)
if (isfield(opt(i), 'maxnode'))
maxsurfnode = opt(i).maxnode;
end
elseif (isstruct(opt) && length(opt) == 1)
if (isfield(opt(1), 'maxnode'))
maxsurfnode = opt.maxnode;
end
end
distbound = radbound;
if (isstruct(opt) && length(opt) == maxlevel)
if (isfield(opt(i), 'distbound'))
distbound = opt(i).distbound;
end
elseif (isstruct(opt) && length(opt) == 1)
if (isfield(opt(1), 'distbound'))
distbound = opt.distbound;
end
end
surfside = '';
if (isstruct(opt) && length(opt) == maxlevel)
if (isfield(opt(i), 'side'))
surfside = opt(i).side;
end
elseif (isstruct(opt) && length(opt) == 1)
if (isfield(opt(1), 'side'))
surfside = opt(1).side;
end
end
if (~isempty(surfside))
newimg0 = newimg;
end
if (strcmp(surfside, 'upper'))
newimg(find(newimg <= isovalues(i) - 1e-9)) = isovalues(i) - 1e-9;
elseif (strcmp(surfside, 'lower'))
newimg(find(newimg >= isovalues(i) + 1e-9)) = isovalues(i) + 1e-9;
end
perturb = 1e-4 * abs(max(isovalues));
if (all(newimg > isovalues(i) - perturb))
perturb = -perturb;
end
[v0, f0] = vol2restrictedtri(newimg, isovalues(i) - perturb, regions(i, :), ...
sum(newdim .* newdim) * 2, 30, radbound, distbound, maxsurfnode);
if (~isempty(surfside))
newimg = newimg0;
clear newimg0;
end
else
error('method can only be one of "cgalsurf", "cgalpoly" or "simplify".');
end
% if use defines maxsurf=1, take only the largest closed surface
if (isstruct(opt))
if ((isfield(opt, 'maxsurf') && length(opt) == 1 && opt.maxsurf == 1) || ...
(length(opt) == maxlevel && isfield(opt(i), 'maxsurf') && opt(i).maxsurf == 1))
f0 = maxsurf(finddisconnsurf(f0));
end
end
% if a transformation matrix/offset vector supplied, apply them
if (isstruct(opt) && length(opt) == maxlevel)
if (isfield(opt(i), 'A') && isfield(opt(i), 'B'))
v0 = (opt(i).A * v0' + repmat(opt(i).B(:), 1, size(v0, 1)))';
end
elseif (isstruct(opt) && length(opt) == 1)
if (isfield(opt, 'A') && isfield(opt, 'B'))
v0 = (opt.A * v0' + repmat(opt.B(:), 1, size(v0, 1)))';
end
end
% if user specified holelist and regionlist, append them
if (isstruct(opt) && length(opt) == maxlevel)
if (isfield(opt(i), 'hole'))
holes = [holes; opt(i).hole];
end
if (isfield(opt(i), 'region'))
regions = [regions; opt(i).region];
end
end
if (i == 0)
el = [f0 (i + 1) * ones(size(f0, 1), 1)];
no = v0;
else
el = [el; f0 + length(no) (i + 1) * ones(size(f0, 1), 1)];
no = [no; v0];
end
end
% some final fix and scaling
no(:, 1:3) = no(:, 1:3) - 1; % because we padded the image with a 1 voxel thick null layer in newimg
no(:, 1) = no(:, 1) * (max(ix) - min(ix) + 1) / dim(1) + (min(ix) - 1);
no(:, 2) = no(:, 2) * (max(iy) - min(iy) + 1) / dim(2) + (min(iy) - 1);
no(:, 3) = no(:, 3) * (max(iz) - min(iz) + 1) / dim(3) + (min(iz) - 1);
end % if not isempty(img)
if (isstruct(opt) && isfield(opt, 'surf'))
for i = 1:length(opt.surf)
opt.surf(i).elem(:, 4) = maxlevel + i;
el = [el; opt.surf(i).elem + length(no)];
no = [no; opt.surf(i).node];
end
end
fprintf(1, 'surface mesh generation is complete\n');