EMAN2
skeletonizer.cpp
Go to the documentation of this file.
1// Copyright (C) 2005-2008 Washington University in St Louis, Baylor College of Medicine. All rights reserved
2// Author: Sasakthi S. Abeysinghe (sasakthi@gmail.com)
3// Description: Performs skeletonization on a grayscale volume
4
5#include "skeletonizer.h"
6#include <list>
7using std::list;
8
9using namespace wustl_mm::GraySkeletonCPP;
10
11 const char VolumeSkeletonizer::THINNING_CLASS_SURFACE_PRESERVATION = 4;
12 const char VolumeSkeletonizer::THINNING_CLASS_CURVE_PRESERVATION_2D = 3;
13 const char VolumeSkeletonizer::THINNING_CLASS_CURVE_PRESERVATION = 2;
14 const char VolumeSkeletonizer::THINNING_CLASS_POINT_PRESERVATION = 1;
15 const char VolumeSkeletonizer::THINNING_CLASS_TOPOLOGY_PRESERVATION = 0;
16 const char VolumeSkeletonizer::PRUNING_CLASS_PRUNE_SURFACES = 5;
17 const char VolumeSkeletonizer::PRUNING_CLASS_PRUNE_CURVES = 6;
18 const char VolumeSkeletonizer::PRUNING_CLASS_PRUNE_POINTS = 7;
19
20 VolumeSkeletonizer::VolumeSkeletonizer(int pointRadius, int curveRadius, int surfaceRadius, int skeletonDirectionRadius) {
21// math = new MathLib();
22// surfaceNormalFinder = new NormalFinder();
23 this->pointRadius = pointRadius;
24 this->curveRadius = curveRadius;
25 this->surfaceRadius = surfaceRadius;
26 this->skeletonDirectionRadius = skeletonDirectionRadius;
27
28// gaussianFilterPointRadius.radius = pointRadius;
29// math->GetBinomialDistribution(gaussianFilterPointRadius);
30//
31// gaussianFilterCurveRadius.radius = curveRadius;
32// math->GetBinomialDistribution(gaussianFilterCurveRadius);
33//
34// gaussianFilterSurfaceRadius.radius = surfaceRadius;
35// math->GetBinomialDistribution(gaussianFilterSurfaceRadius);
36//
37// gaussianFilterMaxRadius.radius = MAX_GAUSSIAN_FILTER_RADIUS;
38// math->GetBinomialDistribution(gaussianFilterMaxRadius);
39//
40// uniformFilterSkeletonDirectionRadius.radius = skeletonDirectionRadius;
41// math->GetUniformDistribution(uniformFilterSkeletonDirectionRadius);
42 }
43
45 //delete math;
46 //delete surfaceNormalFinder;
47 }
48 // **************************** Added by Ross ****************************************
49
50const float CURVE_VAL = 1.0f;
51const float SURFACE_VAL = 2.0f;
52const float MAP_ERR_VAL = 100.0f;
53
55 if ( u==v || abs(u[0]-v[0])>1 || abs(u[1]-v[1])>1 || abs(u[2]-v[2])>1) {
56 return false;
57 } else {
58 return true;
59 }
60 }
61
62 // Based on NonManifoldMesh<TVertex, TEdge, TFace>::NonManifoldMesh(Volume * sourceVol)
64
65 int faceNeighbors[3][3][3] = { {{1,0,0}, {1,0,1}, {0,0,1}},
66 {{1,0,0}, {1,1,0}, {0,1,0}},
67 {{0,1,0}, {0,1,1}, {0,0,1}} };
68 int indices[4];
69 bool faceFound;
70
71 for (int z = 0; z < skeleton->getSizeZ(); z++) {
72 for (int y = 0; y < skeleton->getSizeY(); y++) {
73 for (int x = 0; x < skeleton->getSizeX(); x++) {
74
75 indices[0] = skeleton->getIndex(x,y,z);
76 for (int n = 0; n < 3; n++) {
77 faceFound = true;
78 for (int m = 0; m < 3; m++) {
79 indices[m+1] = skeleton->getIndex(x+faceNeighbors[n][m][0], y+faceNeighbors[n][m][1], z+faceNeighbors[n][m][2]);
80 faceFound = faceFound && (skeleton->getDataAt(indices[m+1]) > 0);
81 }
82 if (faceFound) {
83 for (int m = 0; m < 4; m++) {
84 skeleton->setDataAt(indices[m], SURFACE_VAL);
85 }
86 }
87 }
88
89 }
90 }
91 }
92 }
93
94 void VolumeSkeletonizer::CleanUpSkeleton(Volume * skeleton, int minNumVoxels, float valueThreshold) {
95
96 //Get the indices of voxels that are above the threshold, i.e. part of the skeleton
97 list< Vec3<int> > skel_indices;
98 Vec3<int> voxel_indices;
99 for (int k=0; k < skeleton->getSizeZ(); k++) {
100 for (int j=0; j < skeleton->getSizeY(); j++) {
101 for (int i=0; i < skeleton->getSizeX(); i++) {
102 if (skeleton->getDataAt(i,j,k) > valueThreshold) {
103 voxel_indices.set_value(i,j,k);
104 skel_indices.push_front(voxel_indices);
105 }
106 }
107 }
108 }
109
110 vector< Vec3<int> > segment; //the coordinates for a set of connected skeleton voxels
111 list< Vec3<int> >::iterator itr;
112
113 /*
114 1. Group the connected voxels together
115 2. Check the number of voxels in that group
116 3. If below the minimum number of voxels, remove them from the skeleton
117 4. Repeat until all the voxels in the skeleton have been grouped (possibly alone if not connected)
118 */
119 while (skel_indices.size() > 0) {
120 segment.clear();
121 segment.push_back(skel_indices.front());
122 skel_indices.pop_front();
123 // group connected voxels -- each member of segment neighbors at least one other member of segment
124 //For each voxel in segment, we test if each voxel in skel_indices is a neighbor
125 for (unsigned int seg_ix=0; seg_ix < segment.size(); seg_ix++) {
126 for (itr = skel_indices.begin(); itr != skel_indices.end(); itr++) {
127
128 if (Are26Neighbors(segment[seg_ix], *itr)) {
129 segment.push_back(*itr);
130 skel_indices.erase(itr);
131 }
132 }
133 } //Now, a segment is complete
134
135
136 //If the region of connected voxels is too small, remove them from the map
137 if (segment.size() < static_cast<unsigned int>(minNumVoxels)) {
138 for (unsigned int ix=0; ix<segment.size(); ix++) {
139 skeleton->setDataAt(segment[ix][0], segment[ix][1], segment[ix][2],0.0f);
140 }
141 }
142
143 }
144 }
145 // **************************** End: added by Ross ****************************************
146
147
148 Volume * VolumeSkeletonizer::PerformPureJuSkeletonization(Volume * imageVol, string, double threshold, int minCurveWidth, int minSurfaceWidth) {
149 imageVol->pad(MAX_GAUSSIAN_FILTER_RADIUS, 0);
150 Volume * preservedVol = new Volume(imageVol->getSizeX(), imageVol->getSizeY(), imageVol->getSizeZ());
151 Volume * surfaceVol;
152 Volume * curveVol;
153 Volume * topologyVol;
154 //printf("\t\t\tUSING THRESHOLD : %f\n", threshold);
155 // Skeletonizing while preserving surface features curve features and topology
156 surfaceVol = GetJuSurfaceSkeleton(imageVol, preservedVol, threshold);
157 PruneSurfaces(surfaceVol, minSurfaceWidth);
158 VoxelOr(preservedVol, surfaceVol);
159 curveVol = VolumeSkeletonizer::GetJuCurveSkeleton(imageVol, preservedVol, threshold, true);
160 VolumeSkeletonizer::PruneCurves(curveVol, minCurveWidth);
161 VoxelOr(preservedVol, curveVol);
162
163 topologyVol = VolumeSkeletonizer::GetJuTopologySkeleton(imageVol, preservedVol, threshold);
164
165 //Code below by Ross as a test -- to replace GetJuTopologySkeleton return value
166// int curveVolMax = curveVol->getVolumeData()->GetMaxIndex();
167// int surfaceVolMax = curveVol->getVolumeData()->GetMaxIndex();
168// int maximum = curveVolMax <= surfaceVolMax? curveVolMax : surfaceVolMax;
169// if (curveVolMax != surfaceVolMax)
170// cout << "Curve Skeleton: " << curveVolMax << '\n' << "Surface Skeleton" << surfaceVolMax << endl;
171// topologyVol = new Volume(curveVol->getSizeX(), curveVol->getSizeY(), curveVol->getSizeZ());
172// float val, cval, sval;
173// for (int i = 0; i < maximum; i++)
174// {
175// cval = float(curveVol->getDataAt(i));
176// sval = float(surfaceVol->getDataAt(i));
177// if (cval && sval)
178// val = 100; //Something went wrong!
179// else if (cval)
180// val = 1;
181// else if (sval)
182// val = -1;
183// else
184// val = 0;
185// topologyVol->setDataAt(i, val);
186// }
187
188
189
190
191
192 imageVol->pad(-MAX_GAUSSIAN_FILTER_RADIUS, 0);
193 topologyVol->pad(-MAX_GAUSSIAN_FILTER_RADIUS, 0);
194 delete preservedVol;
195 delete surfaceVol;
196 delete curveVol;
197 return topologyVol;
198 }
199
200 Volume * VolumeSkeletonizer::GetJuCurveSkeleton(Volume * sourceVolume, Volume * preserve, double threshold, bool is3D){
202 return GetJuThinning(sourceVolume, preserve, threshold, thinningClass);
203 }
204
205 Volume * VolumeSkeletonizer::GetJuSurfaceSkeleton(Volume * sourceVolume, Volume * preserve, double threshold){
206 return GetJuThinning(sourceVolume, preserve, threshold, THINNING_CLASS_SURFACE_PRESERVATION);
207 }
208
209 Volume * VolumeSkeletonizer::GetJuTopologySkeleton(Volume * sourceVolume, Volume * preserve, double threshold){
210 return GetJuThinning(sourceVolume, preserve, threshold, THINNING_CLASS_TOPOLOGY_PRESERVATION);
211 }
212
213 Volume * VolumeSkeletonizer::GetJuThinning(Volume * sourceVolume, Volume * preserve, double threshold, char thinningClass) {
214 Volume * thinnedVolume = new Volume(sourceVolume->getSizeX(), sourceVolume->getSizeY(), sourceVolume->getSizeZ(), 0, 0, 0, sourceVolume);
215 switch(thinningClass) {
217 thinnedVolume->surfaceSkeletonPres((float)threshold, preserve);
218 break;
220 thinnedVolume->curveSkeleton((float)threshold, preserve);
221 break;
223 thinnedVolume->curveSkeleton2D((float)threshold, preserve);
224 break;
226 thinnedVolume->skeleton((float)threshold, preserve, preserve);
227 }
228 return thinnedVolume;
229 }
230
231 void VolumeSkeletonizer::PruneCurves(Volume * sourceVolume, int pruneLength) {
232 sourceVolume->erodeHelix(pruneLength);
233 }
234 void VolumeSkeletonizer::PruneSurfaces(Volume * sourceVolume, int pruneLength) {
235 sourceVolume->erodeSheet(pruneLength);
236 }
237
238 void VolumeSkeletonizer::VoxelOr(Volume * sourceAndDestVolume1, Volume * sourceVolume2){
239 if(sourceVolume2 != NULL) {
240 for(int x = 0; x < sourceAndDestVolume1->getSizeX(); x++) {
241 for(int y = 0; y < sourceAndDestVolume1->getSizeY(); y++) {
242 for(int z = 0; z < sourceAndDestVolume1->getSizeZ(); z++) {
243 sourceAndDestVolume1->setDataAt(x, y, z, max(sourceAndDestVolume1->getDataAt(x, y, z), sourceVolume2->getDataAt(x, y, z)));
244 }
245 }
246 }
247 }
248 }
249
250
251
252
253
254
255
256
257
258
259
260
261
262 //Volume * VolumeSkeletonizer::PerformImmersionSkeletonizationAndPruning(Volume * sourceVol, Volume * preserveVol, double startGray, double endGray, double stepSize, int smoothingIterations, int smoothingRadius, int minCurveSize, int minSurfaceSize, int maxCurveHole, int maxSurfaceHole, string outputPath, bool doPruning, double pointThreshold, double curveThreshold, double surfaceThreshold) {
263 //appTimeManager.PushCurrentTime();
264 //for(int i = 0; i < smoothingIterations; i++) {
265 //SmoothenVolume(sourceVol, startGray, endGray, smoothingRadius);
266 //}
267 //appTimeManager.PopAndDisplayTime("Smoothing : %f seconds!\n");
268 //Vector3DFloat * volumeGradient = NULL;
269 //EigenResults3D * volumeEigens;
270 //sourceVol->pad(MAX_GAUSSIAN_FILTER_RADIUS, 0);
271 //if(preserveVol != NULL) {
272 //preserveVol->pad(MAX_GAUSSIAN_FILTER_RADIUS, 0);
273 //}
274
275 //if(doPruning) {
276 //volumeGradient = GetVolumeGradient(sourceVol);
277 //}
278
279 //Volume * nullVol = new Volume(sourceVol->getSizeX(), sourceVol->getSizeY(), sourceVol->getSizeZ());
280 //appTimeManager.PushCurrentTime();
281 //Volume * surfaceVol = GetImmersionThinning(sourceVol, preserveVol, startGray, endGray, stepSize, THINNING_CLASS_SURFACE_PRESERVATION);
282 //appTimeManager.PopAndDisplayTime("Surface Thinning : %f seconds!\n");
283
284 //#ifdef SAVE_INTERMEDIATE_RESULTS
285 //surfaceVol->toMRCFile((char *)(outputPath + "-S-Pre-Prune-Pre-Erode.mrc").c_str());
286 //#endif
287
288 //PruneSurfaces(surfaceVol, minSurfaceSize);
289
290 //appTimeManager.PushCurrentTime();
291 //if(doPruning) {
292 //#ifdef SAVE_INTERMEDIATE_RESULTS
293 //surfaceVol->toMRCFile((char *)(outputPath + "-S-Pre-Prune.mrc").c_str());
294 //WriteVolumeToVRMLFile(surfaceVol, outputPath + "-S-Pre-Prune.wrl");
295 //#endif
296 //appTimeManager.PushCurrentTime();
297 //volumeEigens = GetEigenResults(surfaceVol, volumeGradient, gaussianFilterSurfaceRadius, surfaceRadius, true);
298 //appTimeManager.PopAndDisplayTime(" Getting Eigens : %f seconds!\n");
299
300 //appTimeManager.PushCurrentTime();
301 //Volume * prunedSurfaceVol = new Volume(surfaceVol->getSizeX(), surfaceVol->getSizeY(), surfaceVol->getSizeZ(), 0, 0, 0, surfaceVol);
302 //appTimeManager.PopAndDisplayTime(" Getting Copy of surface : %f seconds!\n");
303
304
305 //appTimeManager.PushCurrentTime();
306 //PruneUsingStructureTensor(prunedSurfaceVol, sourceVol, preserveVol, volumeGradient, volumeEigens, gaussianFilterSurfaceRadius, surfaceThreshold, PRUNING_CLASS_PRUNE_SURFACES, outputPath + "-S");
307 //appTimeManager.PopAndDisplayTime(" Pruning: %f seconds!\n");
308
309 //appTimeManager.PushCurrentTime();
310 //delete [] volumeEigens;
311 //#ifdef SAVE_INTERMEDIATE_RESULTS
312 //prunedSurfaceVol->toMRCFile((char *)(outputPath + "-S-Post-Prune.mrc").c_str());
313 //#endif
314
315 //delete surfaceVol;
316 //surfaceVol = prunedSurfaceVol;
317 //appTimeManager.PopAndDisplayTime(" Memory Cleanup: %f seconds!\n");
318
319 //}
320
321 //PruneSurfaces(surfaceVol, minSurfaceSize);
322 //appTimeManager.PopAndDisplayTime("Surface Pruning : %f seconds!\n");
323
324 //#ifdef SAVE_INTERMEDIATE_RESULTS
325 //surfaceVol->toMRCFile((char *)(outputPath + "-S-Post-Erosion.mrc").c_str());
326 //#endif
327
328 //Volume * cleanedSurfaceVol = GetJuSurfaceSkeleton(surfaceVol, nullVol, 0.5);
329 //PruneSurfaces(cleanedSurfaceVol, minSurfaceSize);
330 //#ifdef SAVE_INTERMEDIATE_RESULTS
331 //cleanedSurfaceVol->toMRCFile((char *)(outputPath + "-S-Cleaned.mrc").c_str());
332 //#endif
333
334 //delete surfaceVol;
335 //surfaceVol = cleanedSurfaceVol;
336 //VoxelOr(surfaceVol, preserveVol);
337
338 //appTimeManager.PushCurrentTime();
339
340 //Volume * curveVol = GetImmersionThinning(sourceVol, surfaceVol, startGray, endGray, stepSize, THINNING_CLASS_CURVE_PRESERVATION);
341 //appTimeManager.PopAndDisplayTime("Curve Thinning : %f seconds!\n");
342
343 //#ifdef SAVE_INTERMEDIATE_RESULTS
344 //curveVol->toMRCFile((char *)(outputPath + "-C-Pre-Prune_Pre-Erode.mrc").c_str());
345 //#endif
346
347 //PruneCurves(curveVol, minCurveSize);
348 //VoxelBinarySubtract(curveVol, surfaceVol);
349
350 //appTimeManager.PushCurrentTime();
351 //if(doPruning) {
352 //#ifdef SAVE_INTERMEDIATE_RESULTS
353 //curveVol->toMRCFile((char *)(outputPath + "-C-Pre-Prune.mrc").c_str());
354 //#endif
355
356 //volumeEigens = GetEigenResults(curveVol, volumeGradient, gaussianFilterCurveRadius, curveRadius, true);
357 //Volume * prunedCurveVol = new Volume(curveVol->getSizeX(), curveVol->getSizeY(), curveVol->getSizeZ(), 0, 0, 0, curveVol);
358 //PruneUsingStructureTensor(prunedCurveVol, sourceVol, preserveVol, volumeGradient, volumeEigens, gaussianFilterCurveRadius, curveThreshold, PRUNING_CLASS_PRUNE_CURVES, outputPath + "-C");
359 //delete [] volumeEigens;
360 //#ifdef SAVE_INTERMEDIATE_RESULTS
361 //prunedCurveVol->toMRCFile((char *)(outputPath + "-C-Post-Prune.mrc").c_str());
362 //#endif
363
364 //Volume * filledCurveVol = FillCurveHoles(prunedCurveVol, curveVol, maxCurveHole);
365 //#ifdef SAVE_INTERMEDIATE_RESULTS
366 //filledCurveVol->toMRCFile((char *)(outputPath + "-C-Post-Fill.mrc").c_str());
367 //#endif
368 //delete curveVol;
369 //delete prunedCurveVol;
370 //curveVol = filledCurveVol;
371
372
373 //}
374
375 //VoxelOr(curveVol, surfaceVol);
376 //PruneCurves(curveVol, minCurveSize);
377 //appTimeManager.PopAndDisplayTime("Curve Pruning : %f seconds!\n");
378 //#ifdef SAVE_INTERMEDIATE_RESULTS
379 //curveVol->toMRCFile((char *)(outputPath + "-C-Post-Erosion.mrc").c_str());
380 //#endif
381
382 //Volume * cleanedCurveVol = GetJuCurveSkeleton(curveVol, surfaceVol, 0.5, true);
383 //PruneCurves(cleanedCurveVol, minCurveSize);
384 //#ifdef SAVE_INTERMEDIATE_RESULTS
385 //cleanedCurveVol->toMRCFile((char *)(outputPath + "-C-Cleaned.mrc").c_str());
386 //#endif
387
388 //delete curveVol;
389 //curveVol = cleanedCurveVol;
390
391 //VoxelOr(curveVol, surfaceVol);
392 //#ifdef SAVE_INTERMEDIATE_RESULTS
393 //curveVol->toMRCFile((char *)(outputPath + "-SC.mrc").c_str());
394 //#endif
395
405
407
408 //delete surfaceVol;
410 //delete nullVol;
411 //delete [] volumeGradient;
412
413 //#ifdef SAVE_INTERMEDIATE_RESULTS
414 //curveVol->toOFFCells2((char *)(outputPath + "-SC.off").c_str());
415 //#endif
416
417 //sourceVol->pad(-MAX_GAUSSIAN_FILTER_RADIUS, 0);
418 //curveVol->pad(-MAX_GAUSSIAN_FILTER_RADIUS, 0);
419 //return curveVol;
420 //}
void set_value(const vector< Type2 > &v)
Set new values using a std::vector object.
Definition: vec3.h:408
static bool Are26Neighbors(Vec3< int > u, Vec3< int > v)
static void MarkSurfaces(Volume *skeleton)
static Volume * GetJuTopologySkeleton(Volume *sourceVolume, Volume *preserve, double threshold)
static void PruneSurfaces(Volume *sourceVolume, int pruneLength)
static Volume * GetJuThinning(Volume *sourceVolume, Volume *preserve, double threshold, char thinningClass)
static void VoxelOr(Volume *sourceAndDestVolume1, Volume *sourceVolume2)
static Volume * GetJuCurveSkeleton(Volume *sourceVolume, Volume *preserve, double threshold, bool is3D)
static void CleanUpSkeleton(Volume *skeleton, int minNumVoxels=4, float valueThreshold=0.5)
static Volume * PerformPureJuSkeletonization(Volume *imageVol, string outputPath, double threshold, int minCurveWidth, int minSurfaceWidth)
static Volume * GetJuSurfaceSkeleton(Volume *sourceVolume, Volume *preserve, double threshold)
static void PruneCurves(Volume *sourceVolume, int pruneLength)
void skeleton(float thr, int off)
Definition: volume.cpp:5089
int getIndex(int x, int y, int z)
Definition: volume.cpp:48
void pad(int padBy, double padValue)
Definition: volume.cpp:273
void curveSkeleton(Volume *grayvol, float lowthr, float highthr, Volume *svol)
insert them back into priority queue
Definition: volume.cpp:3812
void surfaceSkeletonPres(float thr, Volume *preserve)
for ( int m = 0 ; m < 6 ; m ++ )
Definition: volume.cpp:8672
void curveSkeleton2D(float thr, Volume *svol)
Definition: volume.cpp:4678
double getDataAt(int x, int y, int z)
Definition: volume.cpp:60
void setDataAt(int x, int y, int z, double d)
Definition: volume.cpp:52
#define y(i, j)
Definition: projector.cpp:1516
#define x(i)
Definition: projector.cpp:1517
const float SURFACE_VAL
const float CURVE_VAL
const float MAP_ERR_VAL
const int MAX_GAUSSIAN_FILTER_RADIUS
Definition: skeletonizer.h:11