package endrov.flowMorphology; /** * Skeletonize3D for Endrov(C) * Copyright (C) 2008 Ignacio Arganda-Carreras * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt ) * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Modified 2009, copyright Johan Henriksson. Modifications under the same license as the original * and under the Endrov license. * */ import java.util.ArrayList; import endrov.flow.EvOpStack1; import endrov.typeImageset.EvStack; import endrov.util.ProgressHandle; /** * Binary 2D and 3D skeletonization for Endrov. * *

* Originally developed for ImageJ. Forked at version 2.0 06/28/2009 * More information at Skeletonize3D homepage: * http://imagejdocu.tudor.lu/doku.php?id=plugin:morphology:skeletonize3d:start * @author Ignacio Arganda-Carreras * *

* This work is an implementation by Ignacio Arganda-Carreras of the * 3D thinning algorithm from Lee et al. "Building skeleton models via 3-D * medial surface/axis thinning algorithms. Computer Vision, Graphics, and * Image Processing, 56(6):462–478, 1994." Based on the ITK version from * Hanno Homann http://hdl.handle.net/1926/1292 * * @author Johan Henriksson * */ public class EvOpMorphSkeletonizeBinary3D extends EvOpStack1 { @Override public EvStack exec1(ProgressHandle ph, EvStack... p) { return apply(ph,p[0]); } public static EvStack apply(ProgressHandle ph, EvStack in) { //Make sure the data fits the type. Is this really needed? //Important: replaces input with a copy EvStack out=new EvOpMorphBinarize().exec1(ph,in); //Still assumes 0,1 in a few places int[][] pixels=out.getArraysIntReadOnly(ph); computeThinImage(pixels, in.getWidth(), in.getHeight(), in.getDepth()); // BUG: outgetarraysint does not store new arrays in stack!!! return out; } /** * Do the actual work */ private static void computeThinImage(int[][] outputImage, int width, int height, int depth) { ArrayList simpleBorderPoints = new ArrayList(); // Prepare Euler LUT [Lee94] int eulerLUT[] = new int[256]; fillEulerLUT( eulerLUT ); // Loop through the image several times until there is no change. int unchangedBorders = 0; while( unchangedBorders < 6 ) // loop until no change for all the six border types { unchangedBorders = 0; for( int currentBorder = 1; currentBorder <= 6; currentBorder++) { // Loop through the image. for (int z = 0; z < depth; z++) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // check if point is foreground if ( getPixel(outputImage, width, height, depth, x, y, z) != 1 ) continue; // current point is already background // check 6-neighbors if point is a border point of type currentBorder boolean isBorderPoint = false; if( currentBorder == 1 && getPixel(outputImage, width, height, depth, x, y-1, z) == 0 ) //N isBorderPoint = true; else if( currentBorder == 2 && getPixel(outputImage, width, height, depth, x, y+1, z) == 0 ) //S isBorderPoint = true; else if( currentBorder == 3 && getPixel(outputImage, width, height, depth, x+1, y, z) == 0 ) //E isBorderPoint = true; else if( currentBorder == 4 && getPixel(outputImage, width, height, depth, x-1, y, z) == 0 ) //W isBorderPoint = true; else if( currentBorder == 5 && getPixel(outputImage, width, height, depth, x, y, z+1) == 0 ) //U isBorderPoint = true; else if( currentBorder == 6 && getPixel(outputImage, width, height, depth, x, y, z-1) == 0 ) //B isBorderPoint = true; if( !isBorderPoint ) continue; // current point is not deletable // check if point is the end of an arc int numberOfNeighbors = -1; // -1 and not 0 because the center pixel will be counted as well byte[] neighbor = getNeighborhood(outputImage, width, height, depth, x, y, z); for( int i = 0; i < 27; i++ ) // i = 0..26 if( neighbor[i] == 1 ) numberOfNeighbors++; if( numberOfNeighbors == 1 ) continue; // current point is not deletable // Check if point is Euler invariant if( !isEulerInvariant( getNeighborhood(outputImage, width, height, depth, x, y, z), eulerLUT ) ) continue; // current point is not deletable // Check if point is simple (deletion does not change connectivity in the 3x3x3 neighborhood) if( !isSimplePoint(getNeighborhood(outputImage, width, height, depth, x, y, z) ) ) continue; // current point is not deletable // add all simple border points to a list for sequential re-checking int[] index = new int[3]; index[0] = x; index[1] = y; index[2] = z; simpleBorderPoints.add(index); } } } // sequential re-checking to preserve connectivity when // deleting in a parallel way boolean noChange = true; int[] index = null; for(int i = 0; i < simpleBorderPoints.size() ; i++) { index = simpleBorderPoints.get(i); // 1. Set simple border point to 0 setPixel( outputImage, width, height, depth, index[0], index[1], index[2], (byte) 0); // 2. Check if neighborhood is still connected if( !isSimplePoint( getNeighborhood(outputImage, width, height, depth, index[0], index[1], index[2]) ) ) { // we cannot delete current point, so reset setPixel( outputImage, width, height, depth, index[0], index[1], index[2], (byte) 1); } else noChange = false; } if( noChange ) unchangedBorders++; simpleBorderPoints.clear(); } } } /** * Get neighborhood of a pixel in a 3D image (0 border conditions) * * @return corresponding 27-pixels neighborhood (0 if out of image) */ private static byte[] getNeighborhood(int[][] image, int width, int height, int depth, int x, int y, int z) { byte[] neighborhood = new byte[27]; if(z>0) { //better alternative: Special code within image, width, height, depth. //also border, In particular when z==0 and depth==1 neighborhood[ 0] = getPixel(image, width, height, depth, x-1, y-1, z-1); neighborhood[ 1] = getPixel(image, width, height, depth, x , y-1, z-1); neighborhood[ 2] = getPixel(image, width, height, depth, x+1, y-1, z-1); neighborhood[ 3] = getPixel(image, width, height, depth, x-1, y, z-1); neighborhood[ 4] = getPixel(image, width, height, depth, x, y, z-1); neighborhood[ 5] = getPixel(image, width, height, depth, x+1, y, z-1); neighborhood[ 6] = getPixel(image, width, height, depth, x-1, y+1, z-1); neighborhood[ 7] = getPixel(image, width, height, depth, x, y+1, z-1); neighborhood[ 8] = getPixel(image, width, height, depth, x+1, y+1, z-1); } neighborhood[ 9] = getPixel(image, width, height, depth, x-1, y-1, z ); neighborhood[10] = getPixel(image, width, height, depth, x, y-1, z ); neighborhood[11] = getPixel(image, width, height, depth, x+1, y-1, z ); neighborhood[12] = getPixel(image, width, height, depth, x-1, y, z ); neighborhood[13] = getPixel(image, width, height, depth, x, y, z ); neighborhood[14] = getPixel(image, width, height, depth, x+1, y, z ); neighborhood[15] = getPixel(image, width, height, depth, x-1, y+1, z ); neighborhood[16] = getPixel(image, width, height, depth, x, y+1, z ); neighborhood[17] = getPixel(image, width, height, depth, x+1, y+1, z ); if(z= 0 && x < width && y >= 0 && y < height && z >= 0 && z < depth) return (byte)image[z][x + y * width]; else return 0; } /** * Set pixel in 3D image * * @param value pixel value */ private static void setPixel(int[][] image, int width, int height, int depth, int x, int y, int z, byte value) { if(x >= 0 && x < width && y >= 0 && y < height && z >= 0 && z < depth) image[z][x + y * width] = value; } ////////////// Code beneath is not Endrov-specific ////////////// /** * Fill Euler LUT * * @param LUT Euler LUT */ private static void fillEulerLUT(int[] LUT) { LUT[1] = 1; LUT[3] = -1; LUT[5] = -1; LUT[7] = 1; LUT[9] = -3; LUT[11] = -1; LUT[13] = -1; LUT[15] = 1; LUT[17] = -1; LUT[19] = 1; LUT[21] = 1; LUT[23] = -1; LUT[25] = 3; LUT[27] = 1; LUT[29] = 1; LUT[31] = -1; LUT[33] = -3; LUT[35] = -1; LUT[37] = 3; LUT[39] = 1; LUT[41] = 1; LUT[43] = -1; LUT[45] = 3; LUT[47] = 1; LUT[49] = -1; LUT[51] = 1; LUT[53] = 1; LUT[55] = -1; LUT[57] = 3; LUT[59] = 1; LUT[61] = 1; LUT[63] = -1; LUT[65] = -3; LUT[67] = 3; LUT[69] = -1; LUT[71] = 1; LUT[73] = 1; LUT[75] = 3; LUT[77] = -1; LUT[79] = 1; LUT[81] = -1; LUT[83] = 1; LUT[85] = 1; LUT[87] = -1; LUT[89] = 3; LUT[91] = 1; LUT[93] = 1; LUT[95] = -1; LUT[97] = 1; LUT[99] = 3; LUT[101] = 3; LUT[103] = 1; LUT[105] = 5; LUT[107] = 3; LUT[109] = 3; LUT[111] = 1; LUT[113] = -1; LUT[115] = 1; LUT[117] = 1; LUT[119] = -1; LUT[121] = 3; LUT[123] = 1; LUT[125] = 1; LUT[127] = -1; LUT[129] = -7; LUT[131] = -1; LUT[133] = -1; LUT[135] = 1; LUT[137] = -3; LUT[139] = -1; LUT[141] = -1; LUT[143] = 1; LUT[145] = -1; LUT[147] = 1; LUT[149] = 1; LUT[151] = -1; LUT[153] = 3; LUT[155] = 1; LUT[157] = 1; LUT[159] = -1; LUT[161] = -3; LUT[163] = -1; LUT[165] = 3; LUT[167] = 1; LUT[169] = 1; LUT[171] = -1; LUT[173] = 3; LUT[175] = 1; LUT[177] = -1; LUT[179] = 1; LUT[181] = 1; LUT[183] = -1; LUT[185] = 3; LUT[187] = 1; LUT[189] = 1; LUT[191] = -1; LUT[193] = -3; LUT[195] = 3; LUT[197] = -1; LUT[199] = 1; LUT[201] = 1; LUT[203] = 3; LUT[205] = -1; LUT[207] = 1; LUT[209] = -1; LUT[211] = 1; LUT[213] = 1; LUT[215] = -1; LUT[217] = 3; LUT[219] = 1; LUT[221] = 1; LUT[223] = -1; LUT[225] = 1; LUT[227] = 3; LUT[229] = 3; LUT[231] = 1; LUT[233] = 5; LUT[235] = 3; LUT[237] = 3; LUT[239] = 1; LUT[241] = -1; LUT[243] = 1; LUT[245] = 1; LUT[247] = -1; LUT[249] = 3; LUT[251] = 1; LUT[253] = 1; LUT[255] = -1; } /** * Check if a point is Euler invariant * * @param neighbors neighbor pixels of the point * @param LUT Euler LUT * @return true or false if the point is Euler invariant or not */ private static boolean isEulerInvariant(byte[] neighbors, int [] LUT) { // Calculate Euler characteristic for each octant and sum up int eulerChar = 0; char n; // Octant SWU n = 1; if( neighbors[24]==1 ) n |= 128; if( neighbors[25]==1 ) n |= 64; if( neighbors[15]==1 ) n |= 32; if( neighbors[16]==1 ) n |= 16; if( neighbors[21]==1 ) n |= 8; if( neighbors[22]==1 ) n |= 4; if( neighbors[12]==1 ) n |= 2; eulerChar += LUT[n]; // Octant SEU n = 1; if( neighbors[26]==1 ) n |= 128; if( neighbors[23]==1 ) n |= 64; if( neighbors[17]==1 ) n |= 32; if( neighbors[14]==1 ) n |= 16; if( neighbors[25]==1 ) n |= 8; if( neighbors[22]==1 ) n |= 4; if( neighbors[16]==1 ) n |= 2; eulerChar += LUT[n]; // Octant NWU n = 1; if( neighbors[18]==1 ) n |= 128; if( neighbors[21]==1 ) n |= 64; if( neighbors[9]==1 ) n |= 32; if( neighbors[12]==1 ) n |= 16; if( neighbors[19]==1 ) n |= 8; if( neighbors[22]==1 ) n |= 4; if( neighbors[10]==1 ) n |= 2; eulerChar += LUT[n]; // Octant NEU n = 1; if( neighbors[20]==1 ) n |= 128; if( neighbors[23]==1 ) n |= 64; if( neighbors[19]==1 ) n |= 32; if( neighbors[22]==1 ) n |= 16; if( neighbors[11]==1 ) n |= 8; if( neighbors[14]==1 ) n |= 4; if( neighbors[10]==1 ) n |= 2; eulerChar += LUT[n]; // Octant SWB n = 1; if( neighbors[6]==1 ) n |= 128; if( neighbors[15]==1 ) n |= 64; if( neighbors[7]==1 ) n |= 32; if( neighbors[16]==1 ) n |= 16; if( neighbors[3]==1 ) n |= 8; if( neighbors[12]==1 ) n |= 4; if( neighbors[4]==1 ) n |= 2; eulerChar += LUT[n]; // Octant SEB n = 1; if( neighbors[8]==1 ) n |= 128; if( neighbors[7]==1 ) n |= 64; if( neighbors[17]==1 ) n |= 32; if( neighbors[16]==1 ) n |= 16; if( neighbors[5]==1 ) n |= 8; if( neighbors[4]==1 ) n |= 4; if( neighbors[14]==1 ) n |= 2; eulerChar += LUT[n]; // Octant NWB n = 1; if( neighbors[0]==1 ) n |= 128; if( neighbors[9]==1 ) n |= 64; if( neighbors[3]==1 ) n |= 32; if( neighbors[12]==1 ) n |= 16; if( neighbors[1]==1 ) n |= 8; if( neighbors[10]==1 ) n |= 4; if( neighbors[4]==1 ) n |= 2; eulerChar += LUT[n]; // Octant NEB n = 1; if( neighbors[2]==1 ) n |= 128; if( neighbors[1]==1 ) n |= 64; if( neighbors[11]==1 ) n |= 32; if( neighbors[10]==1 ) n |= 16; if( neighbors[5]==1 ) n |= 8; if( neighbors[4]==1 ) n |= 4; if( neighbors[14]==1 ) n |= 2; eulerChar += LUT[n]; return eulerChar == 0; } /* -----------------------------------------------------------------------*/ /** * Check if current point is a Simple Point. * This method is named 'N(v)_labeling' in [Lee94]. * Outputs the number of connected objects in a neighborhood of a point * after this point would have been removed. * * @param neighbors neighbor pixels of the point * @return true or false if the point is simple or not */ private static boolean isSimplePoint(byte[] neighbors) { // copy neighbors for labeling int cube[] = new int[26]; int i = 0; for( i = 0; i < 13; i++ ) // i = 0..12 -> cube[0..12] cube[i] = neighbors[i]; // i != 13 : ignore center pixel when counting (see [Lee94]) for( i = 14; i < 27; i++ ) // i = 14..26 -> cube[13..25] cube[i-1] = neighbors[i]; // set initial label int label = 2; // for all points in the neighborhood for( i = 0; i < 26; i++ ) { if( cube[i]==1 ) // voxel has not been labeled yet { // start recursion with any octant that contains the point i switch( i ) { case 0: case 1: case 3: case 4: case 9: case 10: case 12: octreeLabeling(1, label, cube ); break; case 2: case 5: case 11: case 13: octreeLabeling(2, label, cube ); break; case 6: case 7: case 14: case 15: octreeLabeling(3, label, cube ); break; case 8: case 16: octreeLabeling(4, label, cube ); break; case 17: case 18: case 20: case 21: octreeLabeling(5, label, cube ); break; case 19: case 22: octreeLabeling(6, label, cube ); break; case 23: case 24: octreeLabeling(7, label, cube ); break; case 25: octreeLabeling(8, label, cube ); break; } label++; if( label-2 >= 2 ) { return false; } } } //return label-2; in [Lee94] if the number of connected components would be needed return true; } /** * This is a recursive method that calculates the number of connected * components in the 3D neighborhood after the center pixel would * have been removed. */ private static void octreeLabeling(int octant, int label, int[] cube) { // check if there are points in the octant with value 1 if( octant==1 ) { // set points in this octant to current label // and recursive labeling of adjacent octants if( cube[0] == 1 ) cube[0] = label; if( cube[1] == 1 ) { cube[1] = label; octreeLabeling( 2, label, cube); } if( cube[3] == 1 ) { cube[3] = label; octreeLabeling( 3, label, cube); } if( cube[4] == 1 ) { cube[4] = label; octreeLabeling( 2, label, cube); octreeLabeling( 3, label, cube); octreeLabeling( 4, label, cube); } if( cube[9] == 1 ) { cube[9] = label; octreeLabeling( 5, label, cube); } if( cube[10] == 1 ) { cube[10] = label; octreeLabeling( 2, label, cube); octreeLabeling( 5, label, cube); octreeLabeling( 6, label, cube); } if( cube[12] == 1 ) { cube[12] = label; octreeLabeling( 3, label, cube); octreeLabeling( 5, label, cube); octreeLabeling( 7, label, cube); } } if( octant==2 ) { if( cube[1] == 1 ) { cube[1] = label; octreeLabeling( 1, label, cube); } if( cube[4] == 1 ) { cube[4] = label; octreeLabeling( 1, label, cube); octreeLabeling( 3, label, cube); octreeLabeling( 4, label, cube); } if( cube[10] == 1 ) { cube[10] = label; octreeLabeling( 1, label, cube); octreeLabeling( 5, label, cube); octreeLabeling( 6, label, cube); } if( cube[2] == 1 ) cube[2] = label; if( cube[5] == 1 ) { cube[5] = label; octreeLabeling( 4, label, cube); } if( cube[11] == 1 ) { cube[11] = label; octreeLabeling( 6, label, cube); } if( cube[13] == 1 ) { cube[13] = label; octreeLabeling( 4, label, cube); octreeLabeling( 6, label, cube); octreeLabeling( 8, label, cube); } } if( octant==3 ) { if( cube[3] == 1 ) { cube[3] = label; octreeLabeling( 1, label, cube); } if( cube[4] == 1 ) { cube[4] = label; octreeLabeling( 1, label, cube); octreeLabeling( 2, label, cube); octreeLabeling( 4, label, cube); } if( cube[12] == 1 ) { cube[12] = label; octreeLabeling( 1, label, cube); octreeLabeling( 5, label, cube); octreeLabeling( 7, label, cube); } if( cube[6] == 1 ) cube[6] = label; if( cube[7] == 1 ) { cube[7] = label; octreeLabeling( 4, label, cube); } if( cube[14] == 1 ) { cube[14] = label; octreeLabeling( 7, label, cube); } if( cube[15] == 1 ) { cube[15] = label; octreeLabeling( 4, label, cube); octreeLabeling( 7, label, cube); octreeLabeling( 8, label, cube); } } if( octant==4 ) { if( cube[4] == 1 ) { cube[4] = label; octreeLabeling( 1, label, cube); octreeLabeling( 2, label, cube); octreeLabeling( 3, label, cube); } if( cube[5] == 1 ) { cube[5] = label; octreeLabeling( 2, label, cube); } if( cube[13] == 1 ) { cube[13] = label; octreeLabeling( 2, label, cube); octreeLabeling( 6, label, cube); octreeLabeling( 8, label, cube); } if( cube[7] == 1 ) { cube[7] = label; octreeLabeling( 3, label, cube); } if( cube[15] == 1 ) { cube[15] = label; octreeLabeling( 3, label, cube); octreeLabeling( 7, label, cube); octreeLabeling( 8, label, cube); } if( cube[8] == 1 ) cube[8] = label; if( cube[16] == 1 ) { cube[16] = label; octreeLabeling( 8, label, cube); } } if( octant==5 ) { if( cube[9] == 1 ) { cube[9] = label; octreeLabeling( 1, label, cube); } if( cube[10] == 1 ) { cube[10] = label; octreeLabeling( 1, label, cube); octreeLabeling( 2, label, cube); octreeLabeling( 6, label, cube); } if( cube[12] == 1 ) { cube[12] = label; octreeLabeling( 1, label, cube); octreeLabeling( 3, label, cube); octreeLabeling( 7, label, cube); } if( cube[17] == 1 ) cube[17] = label; if( cube[18] == 1 ) { cube[18] = label; octreeLabeling( 6, label, cube); } if( cube[20] == 1 ) { cube[20] = label; octreeLabeling( 7, label, cube); } if( cube[21] == 1 ) { cube[21] = label; octreeLabeling( 6, label, cube); octreeLabeling( 7, label, cube); octreeLabeling( 8, label, cube); } } if( octant==6 ) { if( cube[10] == 1 ) { cube[10] = label; octreeLabeling( 1, label, cube); octreeLabeling( 2, label, cube); octreeLabeling( 5, label, cube); } if( cube[11] == 1 ) { cube[11] = label; octreeLabeling( 2, label, cube); } if( cube[13] == 1 ) { cube[13] = label; octreeLabeling( 2, label, cube); octreeLabeling( 4, label, cube); octreeLabeling( 8, label, cube); } if( cube[18] == 1 ) { cube[18] = label; octreeLabeling( 5, label, cube); } if( cube[21] == 1 ) { cube[21] = label; octreeLabeling( 5, label, cube); octreeLabeling( 7, label, cube); octreeLabeling( 8, label, cube); } if( cube[19] == 1 ) cube[19] = label; if( cube[22] == 1 ) { cube[22] = label; octreeLabeling( 8, label, cube); } } if( octant==7 ) { if( cube[12] == 1 ) { cube[12] = label; octreeLabeling( 1, label, cube); octreeLabeling( 3, label, cube); octreeLabeling( 5, label, cube); } if( cube[14] == 1 ) { cube[14] = label; octreeLabeling( 3, label, cube); } if( cube[15] == 1 ) { cube[15] = label; octreeLabeling( 3, label, cube); octreeLabeling( 4, label, cube); octreeLabeling( 8, label, cube); } if( cube[20] == 1 ) { cube[20] = label; octreeLabeling( 5, label, cube); } if( cube[21] == 1 ) { cube[21] = label; octreeLabeling( 5, label, cube); octreeLabeling( 6, label, cube); octreeLabeling( 8, label, cube); } if( cube[23] == 1 ) cube[23] = label; if( cube[24] == 1 ) { cube[24] = label; octreeLabeling( 8, label, cube); } } if( octant==8 ) { if( cube[13] == 1 ) { cube[13] = label; octreeLabeling( 2, label, cube); octreeLabeling( 4, label, cube); octreeLabeling( 6, label, cube); } if( cube[15] == 1 ) { cube[15] = label; octreeLabeling( 3, label, cube); octreeLabeling( 4, label, cube); octreeLabeling( 7, label, cube); } if( cube[16] == 1 ) { cube[16] = label; octreeLabeling( 4, label, cube); } if( cube[21] == 1 ) { cube[21] = label; octreeLabeling( 5, label, cube); octreeLabeling( 6, label, cube); octreeLabeling( 7, label, cube); } if( cube[22] == 1 ) { cube[22] = label; octreeLabeling( 6, label, cube); } if( cube[24] == 1 ) { cube[24] = label; octreeLabeling( 7, label, cube); } if( cube[25] == 1 ) cube[25] = label; } } }