#ifndef RTSSKELETONIZER_H #define RTSSKELETONIZER_H #include "rts_itkVolume.h" #include "objJedi.h" #include #include using namespace std; typedef vector> filament; typedef unsigned int indextype; #define BACKGROUND_NODE 0 #define SKELETON_NODE 255 #define ENDPOINT_NODE 128 #define VISITED_NODE 64 class rtsSkeletonizer { private: rts_itkVolume implicit_skeleton; /*Key: 0 - background node 255 - skeleton node 128 - endpoint node */ vector explicit_filaments; vector> explicit_endpoints; public: rtsSkeletonizer(rts_itkVolume input); unsigned int BackgroundComponents6(rts_itkVolume* function, indextype x, indextype y, indextype z, unsigned char threshold); unsigned int rtsSkeletonizer::Peel(unsigned char isovalue); int Neighbors26(rts_itkVolume* function, indextype x, indextype y, indextype z, unsigned char isovalue); vector> FloodFill6(rts_itkVolume* function, indextype x, indextype y, indextype z, unsigned char target_value); void FloodFill26(rts_itkVolume* function, int x, int y, int z, unsigned char target_value); unsigned int Neighbors6(rts_itkVolume* function, indextype x, indextype y, indextype z, unsigned char threshold); rts_itkVolume getImplicitResult(){return implicit_skeleton;} unsigned int NumConnectedComponents(indextype x, indextype y, indextype z, unsigned char threshold); void ComputeEndPoints(); //find all of the filament end points void TraceFilament(point3D p0, point3D p1); rts_itkVolume getLocalRegion(unsigned int x, unsigned int y, unsigned int z); void ConstructExplicitSkeleton(); void SmoothExplicitSkeleton(int iterations); void SaveExplicitSkeleton(const char* filename); }; void rtsSkeletonizer::SmoothExplicitSkeleton(int iterations) { /*This function smooths the vessels by repeatedly taking the dual of the skeleton. Branch points and end points are constrained to their initial positions. */ vector::iterator f; int num_verts, end_vert; int v; point3D new_vert; for(int i = 0; isize(); end_vert = num_verts - 1; //take care of the first vertex new_filament.push_back((*f)[0]); for(v=1; v::iterator i; filament::iterator f; for(i=explicit_filaments.begin(); i!=explicit_filaments.end(); i++) { output.objBegin(OBJ_LINE_STRIP); //cout<<"start filament"<begin(); f!=i->end(); f++) { output.objVertex3f((*f).x, (*f).y, (*f).z); //f->print(); } //cout<<"end filament"< rtsSkeletonizer::getLocalRegion(unsigned int x, unsigned int y, unsigned int z) { rts_itkVolume local(3, 3, 3); point3D corner; indextype u, v, w; corner = point3D(x-1, y-1, z-1); for(u=0; u<3; u++) for(v=0; v<3; v++) for(w=0; w<3; w++) local.SetPixel(u, v, w, implicit_skeleton.GetPixel(corner.x + u, corner.y + v, corner.z + w)); return local; } void rtsSkeletonizer::TraceFilament(point3D p0, point3D p1) { //create the new filament and start it with the two given points filament result; result.push_back(p0); result.push_back(p1); //if(p0 == point3D(156, 433, 254)) // cout<<"Stop here"< local; point3D current = p1; point3D next; point3D possible_end; int x, y, z; int c; //possible connections while(1) //continue tracing until the next endpoint { c=0; local = getLocalRegion(current.x, current.y, current.z); //cout<<"Local Region at ("<(1, 1, 1) + vector3D(x, y, z); } //if it is an endpoint node, check for a termination if(local(x, y, z) == ENDPOINT_NODE) { //if we are not backtracking to the start node possible_end = current - vector3D(1, 1, 1) + vector3D(x, y, z); if(possible_end != p0 || result.size() > 2) { //terminate the fiber result.push_back(current - vector3D(1, 1, 1) + vector3D(x, y, z)); explicit_filaments.push_back(result); implicit_skeleton.SetPixel(current.x, current.y, current.z, VISITED_NODE); return; } } } //return when there is nowhere to go if(c == 0) return; //now we have the next node in the filament, go ahead and label the current //one as traversed implicit_skeleton.SetPixel(current.x, current.y, current.z, VISITED_NODE); //now insert it into the fiber and move to the next node result.push_back(next); if(current == next) { //cout<<"I've been waiting for you Obi-Wan"<>::iterator i; rts_itkVolume local; unsigned int x, y, z; for(i = explicit_endpoints.begin(); i != explicit_endpoints.end(); i++) { //cout<<"Tracing Filament..."<print(); local = getLocalRegion(i->x, i->y, i->z); local.SetPixel(1, 1, 1, 0); //local.toConsole(); for(x=0; x<3; x++) for(y=0; y<3; y++) for(z=0; z<3; z++) if(local.GetPixel(x, y, z) > VISITED_NODE) { //cout<<(int)local.xyz(x, y, z)<x - 1 + x, // i->y - 1 + y, // i->z - 1 + z)<(i->x - 1 + x, i->y - 1 + y, i->z - 1 + z)); } //since we have tracked all filaments attached to an endpoint //set the endpoint as visited implicit_skeleton.SetPixel(i->x, i->y, i->z, VISITED_NODE); } //cout<<"Tracing complete"< input) { implicit_skeleton = input; } void rtsSkeletonizer::ComputeEndPoints() { /*This function finds all of the filament end points in the implicit skeleton. The end points are stored in the explicit_endpoints vector. An end point is defined as a node with (n == 1) or (n > 2) connected components. */ unsigned int max_x = implicit_skeleton.DimX(); unsigned int max_y = implicit_skeleton.DimY(); unsigned int max_z = implicit_skeleton.DimZ(); unsigned int conn; indextype x, y, z; for(x=0; x 2) //if the number of connected components meets the //criteria, store the node in the explicit_endpoints vector. { explicit_endpoints.push_back(point3D(x, y, z)); } } } //tag the endpoints in the implicit function vector>::iterator i; for(i=explicit_endpoints.begin(); i!=explicit_endpoints.end(); i++) implicit_skeleton.SetPixel(i->x, i->y, i->z, ENDPOINT_NODE); //cout<<"Number of end points: "< local(3, 3, 3); local = getLocalRegion(x, y, z); // local.toConsole(); local.SetPixel(1, 1, 1, 0); // local.toConsole(); indextype xi, yi, zi; //iterate through each voxel for(xi=0; xi<3; xi++) for(yi=0; yi<3; yi++) for(zi=0; zi<3; zi++) if(local(xi, yi, zi) >= threshold) { FloodFill6(&local, xi, yi, zi, 0); result++; } /* if(result == 1 || result > 2) { cout<* function, indextype x, indextype y, indextype z, unsigned char threshold) { /** This function computes the number of 6-connected background components in the local region of (x, y, z). This computation is performed by testing all 6 possible voxels that can connect to the specified node. If a background node is found, the entire background component associated with that node is filled and the counter is incremented by 1. The value n specifies the connectivity domain for the flood fill. The definition of background components is that specified by He, Kischell, Rioult and Holmes. **/ //see if there is at least one BG component if(Neighbors6(function, x, y, z, threshold) == 6) return 0; //retrieve the local region of the function rts_itkVolume local(3, 3, 3); point3D corner(x-1, y-1, z-1); indextype u, v, w; for(u=0; u<3; u++) for(v=0; v<3; v++) for(w=0; w<3; w++) local.SetPixel(u, v, w, function->GetPixel(corner.x + u, corner.y + v, corner.z + w)); //threshold the background to find inside/outside points local.Binary(threshold, 1); //fill points that are not in the connectivity domain /*if(n == 18) { local(0, 0, 0) = 1; local(0, 0, 2) = 1; local(0, 2, 0) = 1; local(0, 2, 2) = 1; local(2, 0, 0) = 1; local(2, 0, 2) = 1; local(2, 2, 0) = 1; local(2, 2, 2) = 1; }*/ //local.toConsole(); //search all 6 possible connected points. If a background node is found, fill the component unsigned int components = 0; if(local(0, 1, 1) == 0) { components++; FloodFill6(&local, 0, 1, 1, 1); } if(local(2, 1, 1) == 0) { components++; FloodFill6(&local, 2, 1, 1, 1); } if(local(1, 0, 1) == 0) { components++; FloodFill6(&local, 1, 0, 1, 1); } if(local(1, 2, 1) == 0) { components++; FloodFill6(&local, 1, 2, 1, 1); } if(local(1, 1, 0) == 0) { components++; FloodFill6(&local, 1, 1, 0, 1); } if(local(1, 1, 2) == 0) { components++; FloodFill6(&local, 1, 1, 2, 1); } return components; } unsigned int rtsSkeletonizer::Neighbors6(rts_itkVolume* function, indextype x, indextype y, indextype z, unsigned char threshold) { unsigned int neighbors = 0; if(function->GetPixel(x+1, y, z) >= threshold) neighbors++; if(function->GetPixel(x-1, y, z) >= threshold) neighbors++; if(function->GetPixel(x, y+1, z) >= threshold) neighbors++; if(function->GetPixel(x, y-1, z) >= threshold) neighbors++; if(function->GetPixel(x, y, z+1) >= threshold) neighbors++; if(function->GetPixel(x, y, z-1) >= threshold) neighbors++; return neighbors; } int rtsSkeletonizer::Neighbors26(rts_itkVolume* function, indextype x, indextype y, indextype z, unsigned char isovalue) { int neighbors = 0; int u,v,w; for(u=-1; u<=1; u++) for(v=-1; v<=1; v++) for(w=-1; w<=1; w++) if(function->GetPixel(x+u, y+v, z+w) >= isovalue) neighbors++; if(function->GetPixel(x, y, z) > isovalue) neighbors--; return neighbors; } unsigned int rtsSkeletonizer::Peel(unsigned char isovalue) { /** Removes one layer of pixels from the specified isosurface. **/ unsigned int res_x = implicit_skeleton.DimX(); unsigned int res_y = implicit_skeleton.DimY(); unsigned int res_z = implicit_skeleton.DimZ(); vector> border_nodes; //get the border nodes for the image indextype x, y, z; int condition; for(x=0; x= isovalue && BackgroundComponents6(&implicit_skeleton, x, y, z, isovalue) == 1 && Neighbors26(&implicit_skeleton, x, y, z, isovalue) != 1) { condition = 0; //now find the border pairs. A border point must meet two of the following conditions to be a border pair. //south border: s(p) is background if(implicit_skeleton(x, y-1, z) < isovalue) condition++; //north border: n(p) is background, s(p) and s(s(p)) are foreground if(implicit_skeleton(x, y+1, z) < isovalue && implicit_skeleton(x, y-1, z) >= isovalue && implicit_skeleton(x, y-2, z) >= isovalue) condition++; //west border: w(p) is background if(implicit_skeleton(x-1, y, z) < isovalue) condition++; //east border: e(p) is background, w(p) and w(w(p)) are foreground if(implicit_skeleton(x+1, y, z) < isovalue && implicit_skeleton(x-1, y, z) >= isovalue && implicit_skeleton(x-2, y, z) >= isovalue) condition++; //up border: u(p) is background if(implicit_skeleton(x, y, z-1) < isovalue) condition++; //down border: d(p) is background, u(p) and u(u(p)) are foreground if(implicit_skeleton(x, y, z+1) < isovalue && implicit_skeleton(x, y, z-1) >= isovalue && implicit_skeleton(x, y, z-2) >= isovalue) condition++; if(condition > 1) border_nodes.push_back(point3D(x, y, z)); } //cout<<"Number of border nodes: "< local(3, 3, 3); //store the region local to the current voxel int nodes_before, nodes_after; //number of neighboring nodes before and after the filling operation point3D fill_start; vector>::iterator i; /* Here we determine if a point can be removed by looking at the number of foreground connected components in the local region. If there is more than one connected component */ unsigned int removed = 0; for(i=border_nodes.begin(); i 0) fill_start = point3D(x, y, z); //fill the local region FloodFill26(&local, fill_start.x, fill_start.y, fill_start.z, 2); //get the number of filled neighbors nodes_after = Neighbors26(&local, 1, 1, 1, 2); if(nodes_after == nodes_before) { implicit_skeleton.SetPixel((*i).x, (*i).y, (*i).z, 0); removed++; } } return removed; } vector> rtsSkeletonizer::FloodFill6(rts_itkVolume* function, indextype x, indextype y, indextype z, unsigned char target_value) { //create a vector containing all of the filled nodes vector> result; unsigned char old_value = function->GetPixel(x, y, z); //find the old value (the value being flood-filled) if(target_value == old_value) //if the target value is the same as the old value, nothing to do return result; queue> Q; //create a queue for neighboring points point3D current(x, y, z); //start with the current point function->SetPixel(current.x, current.y, current.z, target_value); point3D next; Q.push(current); indextype u, v, w; while(!Q.empty()) //continue until the queue is empty { current = Q.front(); //get the first element from the queue Q.pop(); if(current.x != function->DimY() - 1) if(function->GetPixel(current.x + 1, current.y, current.z) == old_value) { function->SetPixel(current.x + 1, current.y, current.z, target_value); result.push_back(point3D(current.x + 1, current.y, current.z)); Q.push(point3D(current.x + 1, current.y, current.z)); } if(current.x != 0) if(function->GetPixel(current.x - 1, current.y, current.z) == old_value) { function->SetPixel(current.x - 1, current.y, current.z, target_value); result.push_back(point3D(current.x - 1, current.y, current.z)); Q.push(point3D(current.x - 1, current.y, current.z)); } if(current.y != function->DimY() - 1) if(function->GetPixel(current.x, current.y +1, current.z) == old_value) { function->SetPixel(current.x, current.y+1, current.z, target_value); result.push_back(point3D(current.x, current.y+1, current.z)); Q.push(point3D(current.x, current.y+1, current.z)); } if(current.y != 0) if(function->GetPixel(current.x, current.y-1, current.z) == old_value) { function->SetPixel(current.x, current.y-1, current.z, target_value); result.push_back(point3D(current.x, current.y-1, current.z)); Q.push(point3D(current.x, current.y-1, current.z)); } if(current.z != function->DimZ() - 1) if(function->GetPixel(current.x, current.y, current.z+1) == old_value) { function->SetPixel(current.x, current.y, current.z+1, target_value); result.push_back(point3D(current.x, current.y, current.z+1)); Q.push(point3D(current.x, current.y, current.z+1)); } if(current.z != 0) if(function->GetPixel(current.x, current.y, current.z-1) == old_value) { function->SetPixel(current.x, current.y, current.z-1, target_value); result.push_back(point3D(current.x, current.y, current.z-1)); Q.push(point3D(current.x, current.y, current.z-1)); } } return result; } void rtsSkeletonizer::FloodFill26(rts_itkVolume* function, int x, int y, int z, unsigned char target_value) { unsigned char old_value = function->GetPixel(x, y, z); if(target_value == old_value) return; queue> Q; point3D current(x, y, z); point3D next; Q.push(current); indextype u, v, w; while(!Q.empty()) { current = Q.front(); if(function->GetPixel(current.x, current.y, current.z) == old_value) function->SetPixel(current.x, current.y, current.z, target_value); Q.pop(); unsigned int res_x = function->DimX(); unsigned int res_y = function->DimY(); unsigned int res_z = function->DimZ(); for(u=-1; u<=1; u++) for(v=-1; v<=1; v++) for(w=-1; w<=1; w++) { next.x = current.x + u; next.y = current.y + v; next.z = current.z + w; if(next.x >= 0 && next.x < res_x && next.y >= 0 && next.y < res_y && next.z >= 0 && next.z < res_z && function->GetPixel(next.x, next.y, next.z) == old_value) { function->SetPixel(next.x, next.y, next.z, target_value); Q.push(next); } } } } #endif