PTdecode/CImg-1.3.0/plugins/skeleton.h

Mon, 03 Aug 2009 23:41:04 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Mon, 03 Aug 2009 23:41:04 +0100
changeset 11
69416826d18c
parent 5
1204ebf9340d
permissions
-rwxr-xr-x

added dep/*.d and obj/*.o to hgignore

philpem@5 1 /*
philpem@5 2 #
philpem@5 3 # File : skeleton.h
philpem@5 4 # ( C++ header file - CImg plug-in )
philpem@5 5 #
philpem@5 6 # Description : CImg plugin that implements the computation of the Hamilton-Jacobi skeletons
philpem@5 7 # using Siddiqi algorithm with the correction proposed by Torsello,
philpem@5 8 # as described in :
philpem@5 9 #
philpem@5 10 # [SBTZ02] K. Siddiqi, S. Bouix, A. Tannenbaum and S.W. Zucker. Hamilton-Jacobi Skeletons
philpem@5 11 # International Journal of Computer Vision, 48(3):215-231, 2002
philpem@5 12 #
philpem@5 13 # [TH03] A. Torsello and E. R. Hancock. Curvature Correction of the Hamilton-Jacobi Skeleton
philpem@5 14 # IEEE Computer Vision and Pattern Recognition, 2003
philpem@5 15 #
philpem@5 16 # [BST05] S. Bouix, K. Siddiqi and A. Tannenbaum. Flux driven automatic centerline
philpem@5 17 # extraction. Medical Image Analysis, 9:209-221, 2005
philpem@5 18 #
philpem@5 19 # IMPORTANT WARNING : You must include STL's <queue> before plugin inclusion to make it working !
philpem@5 20 #
philpem@5 21 # Copyright : Francois-Xavier Dupe
philpem@5 22 # ( http://www.greyc.ensicaen.fr/~fdupe/ )
philpem@5 23 #
philpem@5 24 # This software is governed by the CeCILL license under French law and
philpem@5 25 # abiding by the rules of distribution of free software. You can use,
philpem@5 26 # modify and/or redistribute the software under the terms of the CeCILL
philpem@5 27 # license as circulated by CEA, CNRS and INRIA at the following URL
philpem@5 28 # "http://www.cecill.info".
philpem@5 29 #
philpem@5 30 # As a counterpart to the access to the source code and rights to copy,
philpem@5 31 # modify and redistribute granted by the license, users are provided only
philpem@5 32 # with a limited warranty and the software's author, the holder of the
philpem@5 33 # economic rights, and the successive licensors have only limited
philpem@5 34 # liability.
philpem@5 35 #
philpem@5 36 # In this respect, the user's attention is drawn to the risks associated
philpem@5 37 # with loading, using, modifying and/or developing or reproducing the
philpem@5 38 # software by the user in light of its specific status of free software,
philpem@5 39 # that may mean that it is complicated to manipulate, and that also
philpem@5 40 # therefore means that it is reserved for developers and experienced
philpem@5 41 # professionals having in-depth computer knowledge. Users are therefore
philpem@5 42 # encouraged to load and test the software's suitability as regards their
philpem@5 43 # requirements in conditions enabling the security of their systems and/or
philpem@5 44 # data to be ensured and, more generally, to use and operate it in the
philpem@5 45 # same conditions as regards security.
philpem@5 46 #
philpem@5 47 # The fact that you are presently reading this means that you have had
philpem@5 48 # knowledge of the CeCILL license and that you accept its terms.
philpem@5 49 #
philpem@5 50 */
philpem@5 51 #ifndef cimg_plugin_skeleton
philpem@5 52 #define cimg_plugin_skeleton
philpem@5 53
philpem@5 54 /**
philpem@5 55 * Compute the flux of the gradient
philpem@5 56 * @param grad the gradient of the distance function
philpem@5 57 * @param sY the sampling size in Y
philpem@5 58 * @param sZ the sampling size in Z
philpem@5 59 * @return the flux
philpem@5 60 */
philpem@5 61 CImg<floatT> get_flux ( const CImgList<floatT> & grad,
philpem@5 62 float sY = 1.0f, float sZ = 1.0f ) const
philpem@5 63 {
philpem@5 64 int stop = 0; // Stop flag
philpem@5 65 float f = 0; // The current flux
philpem@5 66 int count = 0; // Counter
philpem@5 67 CImg<floatT> flux ((*this).dimx(), (*this).dimy(), (*this).dimz());
philpem@5 68 flux.fill(0);
philpem@5 69
philpem@5 70 cimg_forXYZ((*this),x,y,z)
philpem@5 71 {
philpem@5 72 // If the point is the backgroung continue
philpem@5 73 if ( (*this)(x,y,z) == 0 )
philpem@5 74 continue;
philpem@5 75 // Look at the neigthboorhound and compute the flux
philpem@5 76 stop = 0;
philpem@5 77 f = 0;
philpem@5 78 count = 0;
philpem@5 79
philpem@5 80 for ( int k = -1; k <= 1; ++k )
philpem@5 81 for ( int l = -1; l <= 1; ++l )
philpem@5 82 for ( int m = -1; m <= 1; ++m )
philpem@5 83 {
philpem@5 84 if ( stop == 1 )
philpem@5 85 continue;
philpem@5 86
philpem@5 87 // Protection
philpem@5 88 if (( x+k < 0 ) || ( x+k >= (*this).dimx() ) || ( y+l < 0 ) || ( y+l >= (*this).dimy() ) ||
philpem@5 89 ( z+m < 0 ) || ( z+m >= (*this).dimz() ) || ( k==0 && l==0 && m==0 ))
philpem@5 90 continue;
philpem@5 91
philpem@5 92 ++count;
philpem@5 93
philpem@5 94 //Test if the point is in the interior
philpem@5 95 if ( (*this)(x+k,y+l,z+m) == 0 )
philpem@5 96 {
philpem@5 97 stop = 1;
philpem@5 98 continue;
philpem@5 99 }
philpem@5 100
philpem@5 101 // Compute the flux
philpem@5 102 f += ( grad(0,x+k,y+l,z+m)*k + grad(1,x+k,y+l,z+m)*l/sY + grad(2,x+k,y+l,z+m)*m/sZ ) / cimg_std::sqrt((float)(k*k+l*l+m*m));
philpem@5 103 }
philpem@5 104
philpem@5 105 //Update
philpem@5 106 if ( stop == 1 || count == 0 )
philpem@5 107 flux(x,y,z) = 0;
philpem@5 108 else
philpem@5 109 flux(x,y,z) = f / count;
philpem@5 110
philpem@5 111 }
philpem@5 112
philpem@5 113 return flux;
philpem@5 114 }
philpem@5 115
philpem@5 116 /**
philpem@5 117 * Definition of a point with his flux value
philpem@5 118 */
philpem@5 119 struct _PointFlux
philpem@5 120 {
philpem@5 121 int pos [3];
philpem@5 122 float flux;
philpem@5 123 float dist;
philpem@5 124 };
philpem@5 125
philpem@5 126 /**
philpem@5 127 * Class for the priority queue
philpem@5 128 */
philpem@5 129 class _compare_point
philpem@5 130 {
philpem@5 131 /**
philpem@5 132 * Create medial curves
philpem@5 133 */
philpem@5 134 bool curve;
philpem@5 135
philpem@5 136 public:
philpem@5 137 _compare_point ( bool curve = false )
philpem@5 138 {
philpem@5 139 this->curve = curve;
philpem@5 140 }
philpem@5 141
philpem@5 142 bool operator() ( const _PointFlux & p1, const _PointFlux & p2 ) const
philpem@5 143 {
philpem@5 144 if ( curve )
philpem@5 145 {
philpem@5 146 if ( p1.dist > p2.dist )
philpem@5 147 return true;
philpem@5 148 else if ( p1.dist == p2.dist && p1.flux < p2.flux )
philpem@5 149 return true;
philpem@5 150 }
philpem@5 151 else
philpem@5 152 {
philpem@5 153 if ( p1.flux < p2.flux )
philpem@5 154 return true;
philpem@5 155 else if ( p1.flux == p2.flux && p1.dist > p2.dist )
philpem@5 156 return true;
philpem@5 157 }
philpem@5 158
philpem@5 159 return false;
philpem@5 160 }
philpem@5 161 };
philpem@5 162
philpem@5 163 /**
philpem@5 164 * Compute the log-density using the algorithm from Torsello
philpem@5 165 * @param dist the distance map
philpem@5 166 * @param grad the gradient of the distance map, e.g. the flux
philpem@5 167 * @param flux the divergence map
philpem@5 168 * @param delta the threshold for the division
philpem@5 169 * @return the logdensity \rho
philpem@5 170 */
philpem@5 171 CImg<floatT> get_logdensity ( const CImg<floatT> & dist,
philpem@5 172 const CImgList<floatT> & grad,
philpem@5 173 const CImg<floatT> & flux, float delta = 0.1 ) const
philpem@5 174 {
philpem@5 175 std::priority_queue< _PointFlux, std::vector<_PointFlux>, _compare_point > pqueue(true);
philpem@5 176 CImg<floatT> logdensity ((*this).dimx(), (*this).dimy(), (*this).dimz(), 1, 0);
philpem@5 177
philpem@5 178 // 1 - Put all the pixel inside the priority queue
philpem@5 179 cimg_forXYZ(dist,x,y,z)
philpem@5 180 if ( dist(x,y,z) != 0 )
philpem@5 181 {
philpem@5 182 _PointFlux p;
philpem@5 183 p.pos[0] = x;
philpem@5 184 p.pos[1] = y;
philpem@5 185 p.pos[2] = z;
philpem@5 186 p.flux = 0;
philpem@5 187 p.dist = dist(x,y,z);
philpem@5 188 pqueue.push(p);
philpem@5 189 }
philpem@5 190
philpem@5 191 // 2 - Compute the logdensity
philpem@5 192 while ( ! pqueue.empty() )
philpem@5 193 {
philpem@5 194 _PointFlux p = pqueue.top();
philpem@5 195 pqueue.pop();
philpem@5 196
philpem@5 197 float Fx = grad(0,p.pos[0],p.pos[1],p.pos[2]);
philpem@5 198 float Fy = grad(1,p.pos[0],p.pos[1],p.pos[2]);
philpem@5 199 float Fz = grad(2,p.pos[0],p.pos[1],p.pos[2]);
philpem@5 200
philpem@5 201 logdensity(p.pos[0],p.pos[1],p.pos[2]) = logdensity.linear_atXYZ(p.pos[0]-Fx,p.pos[1]-Fy,p.pos[2]-Fz)
philpem@5 202 - 0.5f * (flux(p.pos[0],p.pos[1],p.pos[2])+flux.linear_atXYZ(p.pos[0]-Fx,p.pos[1]-Fy,p.pos[2]-Fz));
philpem@5 203
philpem@5 204 float tmp = 1.0f - (1.0f-fabs(Fx)) * (1.0f-fabs(Fy)) * (1.0f-fabs(Fz));
philpem@5 205 if ( tmp > delta )
philpem@5 206 logdensity(p.pos[0],p.pos[1],p.pos[2]) /= tmp;
philpem@5 207 else if ( delta < 1 )
philpem@5 208 logdensity(p.pos[0],p.pos[1],p.pos[2]) = 0;
philpem@5 209 }
philpem@5 210
philpem@5 211 return logdensity;
philpem@5 212 }
philpem@5 213
philpem@5 214 /**
philpem@5 215 * Computed the corrected divergence map using Torsello formula and idea
philpem@5 216 * @param logdensity the log density map
philpem@5 217 * @param grad the gradient of the distance map
philpem@5 218 * @param flux the flux using siddiqi formula
philpem@5 219 * @param delta the discrete step
philpem@5 220 * @return the corrected divergence map
philpem@5 221 */
philpem@5 222 CImg<floatT> get_corrected_flux ( const CImg<floatT> & logdensity,
philpem@5 223 const CImgList<floatT> & grad,
philpem@5 224 const CImg<floatT> & flux,
philpem@5 225 float delta = 1.0 ) const
philpem@5 226 {
philpem@5 227 CImg<floatT> corr_map ((*this).dimx(), (*this).dimy(), (*this).dimz(), 1, 0);
philpem@5 228
philpem@5 229 cimg_forXYZ(corr_map,x,y,z)
philpem@5 230 {
philpem@5 231 float Fx = grad(0,x,y,z);
philpem@5 232 float Fy = grad(1,x,y,z);
philpem@5 233 float Fz = grad(2,x,y,z);
philpem@5 234
philpem@5 235 corr_map(x,y,z) = (logdensity(x,y,z) - logdensity.linear_atXYZ(x-Fx,y-Fy,z-Fz)) * expf(logdensity(x,y,z) - 0.5f * delta) +
philpem@5 236 0.5f * ( flux.linear_atXYZ(x-Fx,y-Fy,z-Fz)*expf(logdensity.linear_atXYZ(x-Fx,y-Fy,z-Fz)) + flux(x,y,z)*expf(logdensity(x,y,z)));
philpem@5 237 }
philpem@5 238
philpem@5 239 return corr_map;
philpem@5 240 }
philpem@5 241
philpem@5 242 /**
philpem@5 243 * Test if a point is simple using Euler number for 2D case
philpem@5 244 * or using Malandain criterion for 3D case
philpem@5 245 * @param img the image
philpem@5 246 * @param x the x coordinate
philpem@5 247 * @param y the y coordinate
philpem@5 248 * @param z the z coordinate
philpem@5 249 * @return true if simple
philpem@5 250 */
philpem@5 251 bool _isSimple ( const CImg<T> & img, int x, int y, int z ) const
philpem@5 252 {
philpem@5 253 if ( img.dimz() == 1 ) // 2D case
philpem@5 254 {
philpem@5 255 int V = 0; // Number of vertices
philpem@5 256 int E = 0; // Number of edges
philpem@5 257
philpem@5 258 for ( int k = -1; k <= 1; ++k )
philpem@5 259 for ( int l = -1; l <= 1; ++l )
philpem@5 260 {
philpem@5 261 // Protection
philpem@5 262 if ( x+k < 0 || x+k >= img.dimx() || y+l < 0 || y+l >= img.dimy() )
philpem@5 263 continue;
philpem@5 264
philpem@5 265 // Count the number of vertices
philpem@5 266 if ( img(x+k,y+l) != 0 && ! ( k == 0 && l == 0 ))
philpem@5 267 {
philpem@5 268 ++V;
philpem@5 269
philpem@5 270 // Count the number of edges
philpem@5 271 for ( int k1 = -1; k1 <= 1; ++k1 )
philpem@5 272 for ( int l1 = -1; l1 <= 1; ++l1 )
philpem@5 273 {
philpem@5 274 // Protection
philpem@5 275 if ( x+k+k1 < 0 || x+k+k1 >= img.dimx() || y+l+l1 < 0 || y+l+l1 >= img.dimy() )
philpem@5 276 continue;
philpem@5 277
philpem@5 278 if ( !(k1 == 0 && l1 == 0) && img(x+k+k1,y+l+l1) != 0 && k+k1 > -2 && l+l1 > -2
philpem@5 279 && k+k1 < 2 && l+l1 < 2 && !( k+k1 == 0 && l+l1 == 0 ))
philpem@5 280 ++E;
philpem@5 281 }
philpem@5 282 }
philpem@5 283 }
philpem@5 284
philpem@5 285 // Remove the corner if exists
philpem@5 286 if ( x-1 >= 0 && y-1 >= 0 && img(x-1,y-1) != 0 && img(x,y-1) != 0 && img(x-1,y) != 0 )
philpem@5 287 E -= 2;
philpem@5 288
philpem@5 289 if ( x-1 >= 0 && y+1 < img.dimy() && img(x-1,y+1) != 0 && img(x,y+1) != 0 && img(x-1,y) != 0 )
philpem@5 290 E -= 2;
philpem@5 291
philpem@5 292 if ( x+1 < img.dimx() && y-1 >= 0 && img(x+1,y-1) != 0 && img(x,y-1) != 0 && img(x+1,y) != 0 )
philpem@5 293 E -= 2;
philpem@5 294
philpem@5 295 if ( x+1 < img.dimx() && y+1 < img.dimy() && img(x+1,y+1) != 0 && img(x,y+1) != 0 && img(x+1,y) != 0 )
philpem@5 296 E -= 2;
philpem@5 297
philpem@5 298 // Final return true if it is a tree (eg euler number equal to 1)
philpem@5 299 if (( V - E/2 ) == 1 )
philpem@5 300 return true;
philpem@5 301 }
philpem@5 302 else // 3D case
philpem@5 303 {
philpem@5 304 int C_asterix = 0;
philpem@5 305 int C_bar = 0;
philpem@5 306 CImg<intT> visit ( 3, 3, 3, 1, 0 ); // Visitor table
philpem@5 307 int count = 0;
philpem@5 308
philpem@5 309 visit(1,1,1) = -1;
philpem@5 310
philpem@5 311 // Compute C^*
philpem@5 312
philpem@5 313 // Seeking for a component
philpem@5 314 for ( int k = -1; k <= 1; ++k )
philpem@5 315 for ( int l = -1; l <= 1; ++l )
philpem@5 316 for ( int m = -1; m <= 1; ++m )
philpem@5 317 {
philpem@5 318 int label = 0;
philpem@5 319
philpem@5 320 // Protection
philpem@5 321 if ( x+k < 0 || x+k >= img.dimx() ||
philpem@5 322 y+l < 0 || y+l >= img.dimy() ||
philpem@5 323 z+m < 0 || z+m >= img.dimz() ||
philpem@5 324 ( k==0 && l==0 && m==0 ))
philpem@5 325 continue;
philpem@5 326
philpem@5 327 if ( visit(1+k,1+l,1+m) == 0 && img(x+k,y+l,z+m) != 0 )
philpem@5 328 {
philpem@5 329 // Look after the neightbor
philpem@5 330 for ( int k1 = -1; k1 <= 1; ++k1 )
philpem@5 331 for ( int l1 = -1; l1 <= 1; ++l1 )
philpem@5 332 for ( int m1 = -1; m1 <= 1; ++m1 )
philpem@5 333 {
philpem@5 334 // Protection
philpem@5 335 if ( x+k+k1 < 0 || x+k+k1 >= img.dimx() ||
philpem@5 336 y+l+l1 < 0 || y+l+l1 >= img.dimy() ||
philpem@5 337 z+m+m1 < 0 || z+m+m1 >= img.dimz() ||
philpem@5 338 k+k1 > 1 || k+k1 < -1 ||
philpem@5 339 l+l1 > 1 || l+l1 < -1 ||
philpem@5 340 m+m1 > 1 || m+m1 < -1 )
philpem@5 341 continue;
philpem@5 342
philpem@5 343 // Search for a already knew component
philpem@5 344 if ( visit(1+k+k1,1+l+l1,1+m+m1) > 0 &&
philpem@5 345 img(x+k+k1,y+l+l1,z+m+m1) != 0 )
philpem@5 346 {
philpem@5 347 if ( label == 0 )
philpem@5 348 label = visit(1+k+k1,1+l+l1,1+m+m1);
philpem@5 349 else if ( label != visit(1+k+k1,1+l+l1,1+m+m1) )
philpem@5 350 {
philpem@5 351 // Meld component
philpem@5 352 --C_asterix;
philpem@5 353
philpem@5 354 int C = visit(1+k+k1,1+l+l1,1+m+m1);
philpem@5 355 cimg_forXYZ(visit,a,b,c)
philpem@5 356 if ( visit(a,b,c) == C )
philpem@5 357 visit(a,b,c) = label;
philpem@5 358 }
philpem@5 359 }
philpem@5 360 }
philpem@5 361
philpem@5 362 // Label the point
philpem@5 363 if ( label == 0 )
philpem@5 364 {
philpem@5 365 // Find a new component
philpem@5 366 ++C_asterix;
philpem@5 367 ++count;
philpem@5 368 visit(1+k,1+l,1+m) = count;
philpem@5 369 }
philpem@5 370 else
philpem@5 371 {
philpem@5 372 visit(1+k,1+l,1+m) = label;
philpem@5 373 }
philpem@5 374 }
philpem@5 375 }
philpem@5 376
philpem@5 377 if ( C_asterix != 1 )
philpem@5 378 return false;
philpem@5 379
philpem@5 380 // Compute \bar{C}
philpem@5 381
philpem@5 382 // Reinit visit
philpem@5 383 visit.fill(0);
philpem@5 384 visit(1,1,1) = -1;
philpem@5 385
philpem@5 386 // Seeking for a component
philpem@5 387
philpem@5 388 // Look at X-axis
philpem@5 389 { for ( int k = -1; k <= 1; ++k )
philpem@5 390 {
philpem@5 391 if ( x+k < 0 || x+k >= img.dimx() )
philpem@5 392 continue;
philpem@5 393
philpem@5 394 if ( img(x+k,y,z) == 0 && visit(1+k,1,1) == 0 )
philpem@5 395 {
philpem@5 396 ++C_bar;
philpem@5 397 ++count;
philpem@5 398 visit(1+k,1,1) = count;
philpem@5 399
philpem@5 400 // Follow component
philpem@5 401 for ( int l = -1; l <= 1; ++l )
philpem@5 402 {
philpem@5 403 if ( y+l < img.dimy() && y+l >= 0 && img(x+k,y+l,z) == 0 && visit(1+k,1+l,1) == 0 )
philpem@5 404 visit(1+k,1+l,1) = count;
philpem@5 405 if ( z+l < img.dimz() && z+l >= 0 && img(x+k,y,z+l) == 0 && visit(1+k,1,1+l) == 0 )
philpem@5 406 visit(1+k,1,1+l) = count;
philpem@5 407 }
philpem@5 408 }
philpem@5 409 }
philpem@5 410 }
philpem@5 411
philpem@5 412 // Look at Y-axis
philpem@5 413 { for ( int k = -1; k <= 1; ++k )
philpem@5 414 {
philpem@5 415 if ( y+k < 0 || y+k >= img.dimy() )
philpem@5 416 continue;
philpem@5 417
philpem@5 418 if ( img(x,y+k,z) == 0 && visit(1,1+k,1) == 0 )
philpem@5 419 {
philpem@5 420 int label = 0;
philpem@5 421 ++C_bar;
philpem@5 422 ++count;
philpem@5 423 visit(1,1+k,1) = count;
philpem@5 424 label = count;
philpem@5 425
philpem@5 426 // Follow component
philpem@5 427 for ( int l = -1; l <= 1; ++l )
philpem@5 428 {
philpem@5 429 if ( l == 0 )
philpem@5 430 continue;
philpem@5 431
philpem@5 432 if ( x+l < img.dimx() && x+l >= 0 && img(x+l,y+k,z) == 0 )
philpem@5 433 {
philpem@5 434 if ( visit(1+l,1+k,1) != 0 )
philpem@5 435 {
philpem@5 436 if ( label != visit(1+l,1+k,1) )
philpem@5 437 {
philpem@5 438 // Meld component
philpem@5 439 --C_bar;
philpem@5 440
philpem@5 441 int C = visit(1+l,1+k,1);
philpem@5 442 cimg_forXYZ(visit,a,b,c)
philpem@5 443 if ( visit(a,b,c) == C )
philpem@5 444 visit(a,b,c) = label;
philpem@5 445 }
philpem@5 446 }
philpem@5 447 else
philpem@5 448 visit(1+l,1+k,1) = label;
philpem@5 449 }
philpem@5 450
philpem@5 451 if ( z+l < img.dimz() && z+l >= 0 && img(x,y+k,z+l) == 0 )
philpem@5 452 {
philpem@5 453 if ( visit(1,1+k,1+l) != 0 )
philpem@5 454 {
philpem@5 455 if ( label != visit(1,1+k,1+l) )
philpem@5 456 {
philpem@5 457 // Meld component
philpem@5 458 --C_bar;
philpem@5 459
philpem@5 460 int C = visit(1,1+k,1+l);
philpem@5 461 cimg_forXYZ(visit,a,b,c)
philpem@5 462 if ( visit(a,b,c) == C )
philpem@5 463 visit(a,b,c) = label;
philpem@5 464 }
philpem@5 465 }
philpem@5 466 else
philpem@5 467 visit(1,1+k,1+l) = label;
philpem@5 468 }
philpem@5 469 }
philpem@5 470 }
philpem@5 471 }
philpem@5 472 }
philpem@5 473
philpem@5 474 // Look at Z-axis
philpem@5 475 { for ( int k = -1; k <= 1; ++k )
philpem@5 476 {
philpem@5 477 if ( z+k < 0 || z+k >= img.dimz() )
philpem@5 478 continue;
philpem@5 479
philpem@5 480 if ( img(x,y,z+k) == 0 && visit(1,1,1+k) == 0 )
philpem@5 481 {
philpem@5 482 int label = 0;
philpem@5 483 ++C_bar;
philpem@5 484 ++count;
philpem@5 485 visit(1,1,1+k) = count;
philpem@5 486 label = count;
philpem@5 487
philpem@5 488 // Follow component
philpem@5 489 for ( int l = -1; l <= 1; ++l )
philpem@5 490 {
philpem@5 491 if ( l == 0 )
philpem@5 492 continue;
philpem@5 493
philpem@5 494 if ( x+l < img.dimx() && x+l >= 0 && img(x+l,y,z+k) == 0 )
philpem@5 495 {
philpem@5 496 if ( visit(1+l,1,1+k) != 0 )
philpem@5 497 {
philpem@5 498 if ( label != visit(1+l,1,1+k) )
philpem@5 499 {
philpem@5 500 // Meld component
philpem@5 501 --C_bar;
philpem@5 502
philpem@5 503 int C = visit(1+l,1,1+k);
philpem@5 504 cimg_forXYZ(visit,a,b,c)
philpem@5 505 if ( visit(a,b,c) == C )
philpem@5 506 visit(a,b,c) = label;
philpem@5 507 }
philpem@5 508 }
philpem@5 509 else
philpem@5 510 visit(1+l,1,1+k) = label;
philpem@5 511 }
philpem@5 512
philpem@5 513 if ( y+l < img.dimy() && y+l >= 0 && img(x,y+l,z+k) == 0 )
philpem@5 514 {
philpem@5 515 if ( visit(1,1+l,1+k) != 0 )
philpem@5 516 {
philpem@5 517 if ( label != visit(1,1+l,1+k) )
philpem@5 518 {
philpem@5 519 // Meld component
philpem@5 520 --C_bar;
philpem@5 521
philpem@5 522 int C = visit(1,1+l,1+k);
philpem@5 523 cimg_forXYZ(visit,a,b,c)
philpem@5 524 if ( visit(a,b,c) == C )
philpem@5 525 visit(a,b,c) = label;
philpem@5 526 }
philpem@5 527 }
philpem@5 528 else
philpem@5 529 visit(1,1+l,1+k) = label;
philpem@5 530 }
philpem@5 531 }
philpem@5 532 }
philpem@5 533 }
philpem@5 534 }
philpem@5 535
philpem@5 536 if ( C_bar == 1 )
philpem@5 537 return true;
philpem@5 538 }
philpem@5 539
philpem@5 540 return false;
philpem@5 541 }
philpem@5 542
philpem@5 543 /**
philpem@5 544 * Test if a point is a end point
philpem@5 545 * @param img the image
philpem@5 546 * @param label the table of labels
philpem@5 547 * @param curve set it to true for having medial curve
philpem@5 548 * @param x the x coordinate
philpem@5 549 * @param y the y coordinate
philpem@5 550 * @param z the z coordinate
philpem@5 551 * @return true if simple
philpem@5 552 */
philpem@5 553 bool _isEndPoint ( const CImg<T> & img, const CImg<T> & label,
philpem@5 554 bool curve, int x, int y, int z ) const
philpem@5 555 {
philpem@5 556 if ( label(x,y,z) == 1 )
philpem@5 557 return true;
philpem@5 558
philpem@5 559 if (( ! curve ) && ( img.dimz() != 1 )) // 3D case with medial surface
philpem@5 560 {
philpem@5 561 // Use Pudney specification with the 9 plans
philpem@5 562 int plan9 [9][8][3] = { { {-1,0,-1}, {0,0,-1}, {1,0,-1}, {-1,0,0}, {1,0,0}, {-1,0,1}, {0,0,1}, {1,0,1} }, // Plan 1
philpem@5 563 { {-1,1,0}, {0,1,0}, {1,1,0}, {-1,0,0}, {1,0,0}, {-1,-1,0}, {0,-1,0}, {1,-1,0} }, // Plan 2
philpem@5 564 { {0,-1,-1}, {0,0,-1}, {0,1,-1}, {0,-1,0}, {0,1,0}, {0,-1,1}, {0,0,1}, {0,1,1} }, // Plan 3
philpem@5 565 { {1,1,1}, {0,1,0}, {-1,1,-1}, {1,0,1}, {-1,0,-1}, {-1,-1,-1}, {0,-1,0}, {1,-1,1} }, // Plan 4
philpem@5 566 { {-1,1,1}, {0,1,0}, {1,1,-1}, {-1,0,1}, {1,0,-1}, {-1,-1,1}, {0,-1,0}, {1,-1,-1} }, // Plan 5
philpem@5 567 { {-1,1,1}, {0,1,1}, {1,1,1}, {-1,0,0}, {1,0,0}, {-1,-1,-1}, {0,-1,-1}, {1,-1,-1} }, // Plan 6
philpem@5 568 { {-1,1,-1}, {0,1,-1}, {1,1,-1}, {-1,0,0}, {1,0,0}, {-1,-1,1}, {0,-1,1}, {1,-1,1} }, // Plan 7
philpem@5 569 { {-1,1,-1}, {-1,1,0}, {-1,1,1}, {0,0,-1}, {0,0,1}, {1,-1,-1}, {1,-1,0}, {1,-1,1} }, // Plan 8
philpem@5 570 { {1,1,-1}, {1,1,0}, {1,1,1}, {0,0,-1}, {0,0,1}, {-1,-1,-1}, {-1,-1,0}, {-1,-1,1} } // Plan 9
philpem@5 571 };
philpem@5 572
philpem@5 573 // Count the number of neighbors on each plan
philpem@5 574 for ( int k = 0; k < 9; ++k )
philpem@5 575 {
philpem@5 576 int count = 0;
philpem@5 577
philpem@5 578 for ( int l = 0; l < 8; ++l )
philpem@5 579 {
philpem@5 580 if ( x+plan9[k][l][0] < 0 || x+plan9[k][l][0] >= img.dimx() ||
philpem@5 581 y+plan9[k][l][1] < 0 || y+plan9[k][l][1] >= img.dimy() ||
philpem@5 582 z+plan9[k][l][2] < 0 || z+plan9[k][l][2] >= img.dimz() )
philpem@5 583 continue;
philpem@5 584
philpem@5 585 if ( img(x+plan9[k][l][0],y+plan9[k][l][1],z+plan9[k][l][2]) != 0 )
philpem@5 586 ++count;
philpem@5 587 }
philpem@5 588
philpem@5 589 if ( count < 2 )
philpem@5 590 return true;
philpem@5 591 }
philpem@5 592 }
philpem@5 593 else // 2D or 3D case with medial curve
philpem@5 594 {
philpem@5 595 int isb = 0;
philpem@5 596
philpem@5 597 for ( int k = -1; k <= 1; ++k )
philpem@5 598 for ( int l = -1; l <= 1; ++l )
philpem@5 599 for ( int m = -1; m <= 1; ++m )
philpem@5 600 {
philpem@5 601 // Protection
philpem@5 602 if ( x+k < 0 || x+k >= img.dimx() || y+l < 0 || y+l >= img.dimy() ||
philpem@5 603 z+m < 0 || z+m >= img.dimz() )
philpem@5 604 continue;
philpem@5 605
philpem@5 606 if ( img(x+k,y+l,z+m) != 0 )
philpem@5 607 ++isb;
philpem@5 608 }
philpem@5 609
philpem@5 610 if ( isb == 2 ) // The pixel with one neighbor
philpem@5 611 return true;
philpem@5 612 }
philpem@5 613
philpem@5 614 // Else it's not...
philpem@5 615 return false;
philpem@5 616 }
philpem@5 617
philpem@5 618 /**
philpem@5 619 * Compute the skeleton of the shape using Hamilton-Jacobi scheme
philpem@5 620 * @param flux the flux of the distance gradient
philpem@5 621 * @param dist the euclidean distance of the object
philpem@5 622 * @param curve create or not medial curve
philpem@5 623 * @param thres the threshold on the flux
philpem@5 624 * @return the skeleton
philpem@5 625 */
philpem@5 626 CImg<T> get_skeleton ( const CImg<floatT> & flux,
philpem@5 627 const CImg<floatT> & dist, bool curve ,float thres ) const
philpem@5 628 {
philpem@5 629 CImg<T> skeleton ( *this ); // The skeleton
philpem@5 630 CImg<T> label ( (*this).dimx(), (*this).dimy(), (*this).dimz(), 1, 0 ); // Save label
philpem@5 631 CImg<T> count ( (*this).dimx(), (*this).dimy(), (*this).dimz(), 1, 0 ); // A counter for the queue
philpem@5 632 std::priority_queue< _PointFlux, std::vector<_PointFlux>, _compare_point > pqueue(curve);
philpem@5 633 int isb = 0;
philpem@5 634
philpem@5 635 // 1 - Init get the bound points
philpem@5 636 cimg_forXYZ(*this,x,y,z)
philpem@5 637 {
philpem@5 638 if ( skeleton(x,y,z) == 0 )
philpem@5 639 continue;
philpem@5 640
philpem@5 641 // Test bound condition
philpem@5 642 isb = 0;
philpem@5 643 for ( int k = -1; k <= 1; ++k )
philpem@5 644 for ( int l = -1; l <= 1; ++l )
philpem@5 645 for ( int m = -1; m <= 1; ++m )
philpem@5 646 {
philpem@5 647 // Protection
philpem@5 648 if ( x+k < 0 || x+k >= (*this).dimx() || y+l < 0 || y+l >= (*this).dimy() ||
philpem@5 649 z+m < 0 || z+m >= (*this).dimz() )
philpem@5 650 continue;
philpem@5 651
philpem@5 652 if ( skeleton(x+k,y+l,z+m) == 0 )
philpem@5 653 isb = 1;
philpem@5 654 }
philpem@5 655
philpem@5 656 if ( isb == 1 && _isSimple(skeleton,x,y,z) )
philpem@5 657 {
philpem@5 658 _PointFlux p;
philpem@5 659 p.pos[0] = x;
philpem@5 660 p.pos[1] = y;
philpem@5 661 p.pos[2] = z;
philpem@5 662 p.flux = flux(x,y,z);
philpem@5 663 p.dist = dist(x,y,z);
philpem@5 664 pqueue.push(p);
philpem@5 665 count(x,y,z) = 1;
philpem@5 666 }
philpem@5 667
philpem@5 668 }
philpem@5 669
philpem@5 670 // 2 - Compute the skeleton
philpem@5 671 while ( ! pqueue.empty() )
philpem@5 672 {
philpem@5 673 _PointFlux p = pqueue.top(); // Get the point with the max flux
philpem@5 674 pqueue.pop(); // Remove the point from the queue
philpem@5 675 count(p.pos[0],p.pos[1],p.pos[2]) = 0; // Reinit counter
philpem@5 676
philpem@5 677 // Test if the point is simple
philpem@5 678 if ( _isSimple(skeleton,p.pos[0],p.pos[1],p.pos[2]) )
philpem@5 679 {
philpem@5 680 if (( ! _isEndPoint(skeleton,label,curve,p.pos[0],p.pos[1],p.pos[2]) ) || p.flux > thres )
philpem@5 681 {
philpem@5 682 skeleton(p.pos[0],p.pos[1],p.pos[2]) = 0; // Remove the point
philpem@5 683
philpem@5 684 for ( int k = -1; k <= 1; ++k )
philpem@5 685 for ( int l = -1; l <= 1; ++l )
philpem@5 686 for ( int m = -1; m <= 1; ++m )
philpem@5 687 {
philpem@5 688 // Protection
philpem@5 689 if ( p.pos[0]+k < 0 || p.pos[0]+k >= (*this).dimx() ||
philpem@5 690 p.pos[1]+l < 0 || p.pos[1]+l >= (*this).dimy() ||
philpem@5 691 p.pos[2]+m < 0 || p.pos[2]+m >= (*this).dimz() )
philpem@5 692 continue;
philpem@5 693
philpem@5 694 if ( skeleton(p.pos[0]+k,p.pos[1]+l,p.pos[2]+m) != 0 &&
philpem@5 695 count(p.pos[0]+k,p.pos[1]+l,p.pos[2]+m) < 1 &&
philpem@5 696 _isSimple(skeleton,p.pos[0]+k,p.pos[1]+l,p.pos[2]+m) )
philpem@5 697 {
philpem@5 698 _PointFlux p1;
philpem@5 699 p1.pos[0] = p.pos[0]+k;
philpem@5 700 p1.pos[1] = p.pos[1]+l;
philpem@5 701 p1.pos[2] = p.pos[2]+m;
philpem@5 702 p1.flux = flux(p.pos[0]+k,p.pos[1]+l,p.pos[2]+m);
philpem@5 703 p1.dist = dist(p.pos[0]+k,p.pos[1]+l,p.pos[2]+m);
philpem@5 704 pqueue.push(p1);
philpem@5 705 count(p.pos[0]+k,p.pos[1]+l,p.pos[2]+m) = 1;
philpem@5 706 }
philpem@5 707 }
philpem@5 708 }
philpem@5 709 else
philpem@5 710 {
philpem@5 711 label(p.pos[0],p.pos[1],p.pos[2]) = 1; // Mark the point as skeletal
philpem@5 712 }
philpem@5 713 }
philpem@5 714 }
philpem@5 715
philpem@5 716 return skeleton;
philpem@5 717 }
philpem@5 718
philpem@5 719 /**
philpem@5 720 * In place version of get_skeleton
philpem@5 721 */
philpem@5 722 CImg<T> skeleton ( const CImg<floatT> & flux,
philpem@5 723 const CImg<floatT> & dist, bool curve ,float thres )
philpem@5 724 {
philpem@5 725 return get_skeleton(flux,dist,curve,thres).transfer_to(*this);
philpem@5 726 }
philpem@5 727
philpem@5 728 #endif /* cimg_skeleton_plugin */