aboutsummaryrefslogtreecommitdiff
#include <device_functions.h>
#include "skelft.h"
#include <cstdio>
#include <iostream>


#include <thrust/host_vector.h> 
#include <thrust/device_vector.h> 
#include <thrust/sort.h>



using namespace std;


// Parameters for CUDA kernel executions; more or less optimized for a 1024x1024 image.
#define BLOCKX     16
#define BLOCKY     16
#define BLOCKSIZE  64
#define TILE_DIM   32
#define BLOCK_ROWS 16



/****** Global Variables *******/
const int NB = 7;                        // Nr buffers we use and store in the entire framework
short2 **pbaTextures;                    // Work buffers used to compute and store resident images
// 0: work buffer
// 1: FT
// 2: thresholded DT or exact floating-point DT
// 3: thresholded skeleton
// 4: topology analysis
// 5: work buffer for topology
// 6: skeleton FT
//

float*  pbaTexSiteParam;    // Stores boundary parameterization
float2* pbaTexAdvectSites;
float*  pbaTexDensityData;  // The density map of the sites
float2* pbaTexGradientData; // The gradient of the density map

int     pbaTexSize;     // Texture size (squared) actually used in all computations
int     floodBand  = 4, // Various FT computation parameters; defaults are good for an 1024x1024 image.    
        maurerBand = 4,
        colorBand  = 4;        

texture<short2> pbaTexColor;  // 2D textures (bound to various buffers defined above as needed)
texture<short2> pbaTexColor2;
texture<short2> pbaTexLinks;
texture<float>  pbaTexParam;  // 1D site parameterization texture (bound to pbaTexSiteParam)
texture<unsigned char>
                pbaTexGray;   // 2D texture of unsigned char values, e.g. the binary skeleton
texture<float2> pbaTexAdvect;
texture<float,cudaTextureType2D,cudaReadModeElementType>  pbaTexDensity;
texture<float2,cudaTextureType2D,cudaReadModeElementType> pbaTexGradient;
texture<float,cudaTextureType2D,cudaReadModeElementType>  pbaTexDT;


__device__  bool fill_gc; //Indicates if a fill-sweep did fill anything or not


#if __CUDA_ARCH__ < 110   // We cannot use atomic intrinsics on SM10 or below. Thus, we define these as nop.
#define atomicInc(a,b) 0  // The default will be that some code e.g. endpoint detection will thus not do anything.
#endif



/********* Kernels ********/
#include "skelftkernel.h"



// Initialize necessary memory (CPU/GPU sides)
// - textureSize: The max size of any image we will process until re-initialization
void skelft2DInitialization(int maxTexSize)
{
    int pbaMemSize = maxTexSize * maxTexSize * sizeof(short2); // A buffer has 2 shorts / pixel
    pbaTextures  = (short2**) malloc(NB * sizeof(short2*));    // We will use NB buffers

    for(int i=0;i<NB;++i)
       cudaMalloc((void **) &pbaTextures[i], pbaMemSize); // Allocate work buffer 'i' (on GPU)

    cudaMalloc((void**) &pbaTexSiteParam, maxTexSize * maxTexSize * sizeof(float));    // Sites texture (for FT)
    cudaMalloc((void**) &pbaTexAdvectSites, maxTexSize * maxTexSize * sizeof(float2)); // Sites 2D-coords
    cudaMalloc((void**) &pbaTexDensityData, maxTexSize * maxTexSize * sizeof(float));
    cudaMalloc((void**) &pbaTexGradientData, maxTexSize * maxTexSize * sizeof(float2));
}




// Deallocate all allocated memory
void skelft2DDeinitialization()
{
    for(int i=0;i<NB;++i) cudaFree(pbaTextures[i]); 
    cudaFree(pbaTexSiteParam);
    cudaFree(pbaTexAdvectSites);
    cudaFree(pbaTexDensityData);
    cudaFree(pbaTexGradientData);
    free(pbaTextures);
}


// Initialize the Voronoi textures from the sites' encoding texture (parameterization)
// REMARK: we interpret 'inputVoro' as a 2D texture, as it's much easier/faster like this
__global__ void kernelSiteParamInit(short2* inputVoro, int size)
{
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;

    if (tx<size && ty<size) // Careful not to go outside the image..
    {
        int i = TOID(tx,ty,size);
        // The sites-param has non-zero (parameter) values precisely on boundary points
        float param = tex1Dfetch(pbaTexParam,i); 

        short2& v = inputVoro[i];
        // Non-boundary points are marked as 0 in the parameterization. Here we will compute the FT.
        v.x = v.y = MARKER;
        // These are points which define the 'sites' to compute the FT/skeleton (thus, have FT==identity)
        // We could use an if-then-else here, but it's faster with an if-then
        if (param>0) 
        {            
            v.x = tx;
            v.y = ty;
        }
    }
}



void skelft2DInitializeInput(float* sites, int size)                                        // Copy input sites from CPU to GPU; Also set up site param initialization in pbaTextures[0]
{
    pbaTexSize = size;                                                                        // Size of the actual texture being used in this run; can be smaller than the max-tex-size
                                                                                            // which was used in skelft2DInitialization()

    cudaMemcpy(pbaTexSiteParam, sites, pbaTexSize * pbaTexSize * sizeof(float), cudaMemcpyHostToDevice);
                                                                                            // Pass sites parameterization to CUDA.  Must be done before calling the initialization
                                                                                            // kernel, since we use the sites-param as a texture in that kernel
    cudaBindTexture(0, pbaTexParam, pbaTexSiteParam);                                        // Bind the sites-param as a 1D texture so we can quickly index it next
    dim3 block = dim3(BLOCKX,BLOCKY);
    dim3 grid  = dim3(pbaTexSize/block.x,pbaTexSize/block.y);
    
    kernelSiteParamInit<<<grid,block>>>(pbaTextures[0],pbaTexSize);                            // Do the site param initialization. This sets up pbaTextures[0]
    cudaUnbindTexture(pbaTexParam);
}





// In-place transpose a squared texture. 
// Block orders are modified to optimize memory access. 
// Point coordinates are also swapped. 
void pba2DTranspose(short2 *texture)
{
    dim3 block(TILE_DIM, BLOCK_ROWS); 
    dim3 grid(pbaTexSize / TILE_DIM, pbaTexSize / TILE_DIM); 

    cudaBindTexture(0, pbaTexColor, texture); 
    kernelTranspose<<< grid, block >>>(texture, pbaTexSize); 
    cudaUnbindTexture(pbaTexColor); 
}

// Phase 1 of PBA. m1 must divides texture size
void pba2DPhase1(int m1, short xm, short ym, short xM, short yM) 
{
    dim3 block = dim3(BLOCKSIZE);   
    dim3 grid = dim3(pbaTexSize / block.x, m1); 

    // Flood vertically in their own bands
    cudaBindTexture(0, pbaTexColor, pbaTextures[0]); 
    kernelFloodDown<<< grid, block >>>(pbaTextures[1], pbaTexSize, pbaTexSize / m1); 
    cudaUnbindTexture(pbaTexColor); 

    cudaBindTexture(0, pbaTexColor, pbaTextures[1]); 
    kernelFloodUp<<< grid, block >>>(pbaTextures[1], pbaTexSize, pbaTexSize / m1); 

    // Passing information between bands
    grid = dim3(pbaTexSize / block.x, m1); 
    kernelPropagateInterband<<< grid, block >>>(pbaTextures[0], pbaTexSize, pbaTexSize / m1); 

    cudaBindTexture(0, pbaTexLinks, pbaTextures[0]); 
    kernelUpdateVertical<<< grid, block >>>(pbaTextures[1], pbaTexSize, m1, pbaTexSize / m1); 
    cudaUnbindTexture(pbaTexLinks); 
    cudaUnbindTexture(pbaTexColor); 
}

// Phase 2 of PBA. m2 must divides texture size
void pba2DPhase2(int m2) 
{
    // Compute proximate points locally in each band
    dim3 block = dim3(BLOCKSIZE);   
    dim3 grid = dim3(pbaTexSize / block.x, m2); 
    cudaBindTexture(0, pbaTexColor, pbaTextures[1]); 
    kernelProximatePoints<<< grid, block >>>(pbaTextures[0], pbaTexSize, pbaTexSize / m2); 

    cudaBindTexture(0, pbaTexLinks, pbaTextures[0]); 
    kernelCreateForwardPointers<<< grid, block >>>(pbaTextures[0], pbaTexSize, pbaTexSize / m2); 

    // Repeatly merging two bands into one
    for (int noBand = m2; noBand > 1; noBand /= 2) {
        grid = dim3(pbaTexSize / block.x, noBand / 2); 
        kernelMergeBands<<< grid, block >>>(pbaTextures[0], pbaTexSize, pbaTexSize / noBand); 
    }

    // Replace the forward link with the X coordinate of the seed to remove
    // the need of looking at the other texture. We need it for coloring.
    grid = dim3(pbaTexSize / block.x, pbaTexSize); 
    kernelDoubleToSingleList<<< grid, block >>>(pbaTextures[0], pbaTexSize); 
    cudaUnbindTexture(pbaTexLinks); 
    cudaUnbindTexture(pbaTexColor); 
}

// Phase 3 of PBA. m3 must divides texture size
void pba2DPhase3(int m3) 
{
    dim3 block = dim3(BLOCKSIZE / m3, m3); 
    dim3 grid = dim3(pbaTexSize / block.x); 
    cudaBindTexture(0, pbaTexColor, pbaTextures[0]); 
    kernelColor<<< grid, block >>>(pbaTextures[1], pbaTexSize); 
    cudaUnbindTexture(pbaTexColor); 
}



void skel2DFTCompute(short xm, short ym, short xM, short yM, int floodBand, int maurerBand, int colorBand)
{
    pba2DPhase1(floodBand,xm,ym,xM,yM);                                        //Vertical sweep

    pba2DTranspose(pbaTextures[1]);                                            //

    pba2DPhase2(maurerBand);                                                //Horizontal coloring

    pba2DPhase3(colorBand);                                                    //Row coloring

    pba2DTranspose(pbaTextures[1]); 
}





__global__ void kernelThresholdDT(unsigned char* output, int size, float threshold2, short xm, short ym, short xM, short yM)
//Input:    pbaTexColor: closest-site-ids per pixel, i.e. FT
//Output:   output: thresholded DT
{
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;
    
    if (tx>xm && ty>ym && tx<xM && ty<yM)                                    //careful not to index outside the image..
    {    
        int    id     = TOID(tx, ty, size);
      short2 voroid = tex1Dfetch(pbaTexColor,id);                            //get the closest-site to tx,ty into voroid.x,.y
      float  d2     = (tx-voroid.x)*(tx-voroid.x)+(ty-voroid.y)*(ty-voroid.y);
      output[id]    = (d2<=threshold2);                                        //threshold DT into binary image
    }
}



__global__ void kernelDT(float* output, int size, short xm, short ym, short xM, short yM)
//Input:    pbaTexColor: closest-site-ids per pixel, i.e. FT
//Output:   output: DT
{
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;
        
    if (tx>xm && ty>ym && tx<xM && ty<yM)                                    //careful not to index outside the image..
    {    
        int    id     = TOID(tx, ty, size);
      short2 voroid = tex1Dfetch(pbaTexColor,id);                            //get the closest-site to tx,ty into voroid.x,.y
      float  d2     = (tx-voroid.x)*(tx-voroid.x)+(ty-voroid.y)*(ty-voroid.y);
      output[id]    = sqrtf(d2);                                            //save the Euclidean DT
    }
}


__global__ void kernelSkel(unsigned char* output, short xm, short ym, 
                           short xM, short yM, short size, float threshold, float length)    
                                                                            //Input:    pbaTexColor: closest-site-ids per pixel
                                                                            //            pbaTexParam: labels for sites (only valid at site locations)
{                                                                            //Output:    output: binary thresholded skeleton
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;
    
    if (tx>xm && ty>ym && tx<xM && ty<yM) 
    {
        int    id     = TOID(tx, ty, size);
      int    Id     = id;
      short2 voroid = tex1Dfetch(pbaTexColor,id);                            //get the closest-site to tx,ty into voroid.x,.y
      int    id2    = TOID(voroid.x,voroid.y,size);                            //convert the site's coord to an index into pbaTexParam[], the site-label-texture
      float  imp    = tex1Dfetch(pbaTexParam,id2);                            //get the site's label

             ++id;                                                            //TOID(tx+1,ty,size)
             voroid = tex1Dfetch(pbaTexColor,id);                            //
             id2    = TOID(voroid.x,voroid.y,size);                            //
      float  imp_r  = tex1Dfetch(pbaTexParam,id2);                            //

             id     += size-1;                                                //TOID(tx,ty+1,size)
             voroid = tex1Dfetch(pbaTexColor,id);                            //
             id2    = TOID(voroid.x,voroid.y,size);                            //
      float  imp_u  = tex1Dfetch(pbaTexParam,id2);                            //

      float imp_dx  = fabsf(imp_r-imp);
      float imp_dy  = fabsf(imp_u-imp);
      float Imp     = max(imp_dx,imp_dy);
      Imp = min(Imp,fabsf(length-Imp));
      if (Imp>=threshold) output[Id] = 1;                                    //By filling only 1-values, we reduce memory access somehow (writing to output[] is expensive)
    } 
    
    //WARNING: this kernel may sometimes creates 2-pixel-thick branches.. Study the AFMM original code to see if this is correct.
}



#define X 1

__constant__ const                                                            //REMARK: put following constants (for kernelTopology) in CUDA constant-memory, as this gives a huge speed difference
unsigned char topo_patterns[][9] =        { {0,0,0,                            //These are the 3x3 templates that we use to detect skeleton endpoints
                                           0,X,0,                            //(with four 90-degree rotations for each)
                                           0,X,0},
                                          {0,0,0,
                                           0,X,0,
                                           0,0,X},
                                          {0,0,0,
                                           0,X,0,
                                           0,X,X},
                                          {0,0,0,
                                           0,X,0,
                                           X,X,0} 
                                        };
                                        
#define topo_NPATTERNS  4                                                        //Number of patterns we try to match (for kernelTopology)
                                                                                //REMARK: #define faster than __constant__

__constant__ const unsigned char topo_rot[][9] = { {0,1,2,3,4,5,6,7,8}, {2,5,8,1,4,7,0,3,6}, {8,7,6,5,4,3,2,1,0}, {6,3,0,7,4,1,8,5,2} };
                                                                                //These encode the four 90-degree rotations of the patterns (for kernelTopology);

__device__ unsigned int topo_gc            = 0;
__device__ unsigned int topo_gc_last    = 0;


__global__ void kernelTopology(unsigned char* output, short2* output_set, short xm, short ym, short xM, short yM, short size, int maxpts)    
{
    const int tx = blockIdx.x * blockDim.x + threadIdx.x;
    const int ty = blockIdx.y * blockDim.y + threadIdx.y;
        
    unsigned char t[9];
    
    if (tx>xm && ty>ym && tx<xM-1 && ty<yM-1)                                    //careful not to index outside the image; take into account the template size too
    {    
       int    id = TOID(tx, ty, size);     
       unsigned char  p  = tex1Dfetch(pbaTexGray,id);                            //get the skeleton pixel at tx,ty
       if (p)                                                                    //if the pixel isn't skeleton, nothing to do
       {
         unsigned char idx=0;
         for(int j=ty-1;j<=ty+1;++j)                                            //read the template into t[] for easier use
         {
           int id = TOID(tx-1, j, size);
           for(int i=0;i<=2;++i,++id,++idx)
              t[idx] = tex1Dfetch(pbaTexGray,id);                                //get the 3x3 template centered at the skel point tx,ty
         }
          
         for(unsigned char r=0;r<4;++r)                                            //try to match all rotations of a pattern:
         {
           const unsigned char* rr = topo_rot[r];
           for(unsigned char p=0;p<topo_NPATTERNS;++p)                            //try to match all patterns:
           {
             const unsigned char* pat = topo_patterns[p];
             unsigned char j = (p==0)? 0 : 7;                                    //Speedup: for all patterns except 1st, check only last 3 entries, the first 6 are identical for all patterns
             for(;j<9;++j)                                                        //try to match rotated pattern vs actual pattern
               if (pat[j]!=t[rr[j]]) break;                                        //this rotation failed
             if (j<6) break;                                                    //Speedup: if we have a mismatch on the 1st 6 pattern entries, then none of the patterns can match
                                                                                //           since all templates have the same first 6 entries.

             if (j==9)                                                            //this rotation succeeded: mark the pixel as a topology event and we're done
             {    
                int crt_gc = atomicInc(&topo_gc,maxpts);                        //REMARK: this serializes (compacts) all detected endpoints in one array.            
                output_set[crt_gc] = make_short2(tx,ty);                        //To do this, we use an atomic read-increment-return on a global counter, 
                                                                                //which is guaranteed to give all threads unique consecutive indexes in the array.
                output[id] = 1;                                                    //Also create the topology image
                return;
             }
           }
         }
       }
    }
    else                                                                        //Last thread: add zero-marker to the output point-set, so the reader knows how many points are really in there
    if (tx==xM-1 && ty==yM-1)                                                    //Also reset the global vector counter topo_gc, for the next parallel-run of this function
    { topo_gc_last = topo_gc; topo_gc = 0; }                                    //We do this in the last thread so that no one modifies topo_gc from now on. 
                                                                                //REMARK: this seems to be the only way I can read a __device__ variable back to the CPU
}




void skelft2DParams(int floodBand_, int maurerBand_, int colorBand_)        //Set up some params of the FT algorithm    
{
  floodBand   = floodBand_;
  maurerBand  = maurerBand_;
  colorBand   = colorBand_;
}





// Compute 2D FT / Voronoi diagram of a set of sites
// siteParam:   Site parameterization. 0 = non-site points; >0 = site parameter value.
// output:        FT. The (x,y) at (i,j) are the coords of the closest site to (i,j)
// size:        Texture size (pow 2)
void skelft2DFT(short* output, float* siteParam, short xm, short ym, short xM, short yM, int size) 
{
    skelft2DInitializeInput(siteParam,size);                                    // Initialization of already-allocated data structures

    skel2DFTCompute(xm, ym, xM, yM, floodBand, maurerBand, colorBand);            // Compute FT
    
    // Copy FT to CPU, if required
    if (output) cudaMemcpy(output, pbaTextures[1], size*size*sizeof(short2), cudaMemcpyDeviceToHost);
}






void skelft2DDT(float* outputDT, short xm, short ym, short xM, short yM)        //Compute exact DT from resident FT (in pbaTextures[1])
{
    dim3 block = dim3(BLOCKX,BLOCKY);
    dim3 grid  = dim3(pbaTexSize/block.x,pbaTexSize/block.y);
    

    cudaBindTexture(0, pbaTexColor, pbaTextures[1]);                            //Used to read the FT from

    xm = ym = 0; xM = yM = pbaTexSize-1;
    kernelDT <<< grid, block >>>((float*)pbaTextures[2], pbaTexSize, xm-1, ym-1, xM+1, yM+1);
    cudaUnbindTexture(pbaTexColor);
      
    //Copy DT to CPU
    if (outputDT) cudaMemcpy(outputDT, pbaTextures[2], pbaTexSize * pbaTexSize * sizeof(float), cudaMemcpyDeviceToHost);
}



void skelft2DDT(short* outputDT, float threshold,                                //Compute (thresholded) DT (into pbaTextures[2]) from resident FT (in pbaTextures[1])    
                      short xm, short ym, short xM, short yM)
{
    dim3 block = dim3(BLOCKX,BLOCKY);
    dim3 grid  = dim3(pbaTexSize/block.x,pbaTexSize/block.y);

    cudaBindTexture(0, pbaTexColor, pbaTextures[1]);                            //Used to read the FT from

    if (threshold>=0)
    {
      xm -= (short)threshold; if (xm<0) xm=0;
      ym -= (short)threshold; if (ym<0) ym=0;
      xM += (short)threshold; if (xM>pbaTexSize-1) xM=pbaTexSize-1;
      yM += (short)threshold; if (yM>pbaTexSize-1) yM=pbaTexSize-1;
    
      kernelThresholdDT<<< grid, block >>>((unsigned char*)pbaTextures[2], pbaTexSize, threshold*threshold, xm-1, ym-1, xM+1, yM+1);    
      cudaUnbindTexture(pbaTexColor);
    
      //Copy thresholded image to CPU
      if (outputDT) cudaMemcpy(outputDT, (unsigned char*)pbaTextures[2], pbaTexSize * pbaTexSize * sizeof(unsigned char), cudaMemcpyDeviceToHost);
    }
}




void skelft2DSkeleton(unsigned char* outputSkel, float length, float threshold,    //Compute thresholded skeleton (into pbaTextures[3]) from resident FT (in pbaTextures[1])
                      short xm,short ym,short xM,short yM)                        
{                                                                                //length:     boundary length
    dim3 block = dim3(BLOCKX,BLOCKY);                                            //threshold:  skeleton importance min-value (below this, we ignore branches)
    dim3 grid  = dim3(pbaTexSize/block.x,pbaTexSize/block.y);
    
    cudaBindTexture(0, pbaTexColor, pbaTextures[1]);                            //Used to read the resident FT
    cudaBindTexture(0, pbaTexParam, pbaTexSiteParam);                            //Used to read the resident boundary parameterization    
    cudaMemset(pbaTextures[3],0,sizeof(unsigned char)*pbaTexSize*pbaTexSize);    //Faster to zero result and then fill only 1-values (see kernel)
    
    kernelSkel<<< grid, block >>>((unsigned char*)pbaTextures[3], xm, ym, xM-1, yM-1, pbaTexSize, threshold, length);
    
    cudaUnbindTexture(pbaTexColor);
    cudaUnbindTexture(pbaTexParam);
    
    //Copy skeleton to CPU
    if (outputSkel) cudaMemcpy(outputSkel, pbaTextures[3], pbaTexSize * pbaTexSize * sizeof(unsigned char), cudaMemcpyDeviceToHost);
}




void skelft2DTopology(unsigned char* outputTopo, int* npts, short* outputPoints, //Compute topology-points of the resident skeleton (in pbaTextures[3])
                      short xm,short ym,short xM,short yM)                    
{
    int maxpts = (npts)? *npts : pbaTexSize*pbaTexSize;                            //This is the max # topo-points we are going to return in outputPoints[]

    dim3 block = dim3(BLOCKX,BLOCKY);
    dim3 grid  = dim3(pbaTexSize/block.x,pbaTexSize/block.y);
    
    cudaBindTexture(0, pbaTexGray, pbaTextures[3]);                                //Used to read the resident skeleton
    cudaMemset(pbaTextures[4],0,sizeof(unsigned char)*pbaTexSize*pbaTexSize);    //Faster to zero result and then fill only 1-values (see kernel)

    unsigned int zero = 0;
    cudaMemcpyToSymbol(topo_gc,&zero,sizeof(unsigned int),0,cudaMemcpyHostToDevice);        //Set topo_gc to 0

    kernelTopology<<< grid, block >>>((unsigned char*)pbaTextures[4], pbaTextures[5], xm, ym, xM, yM, pbaTexSize, maxpts+1);
    
    cudaUnbindTexture(pbaTexGray);

    if (outputPoints && maxpts)                                                    //If output-point vector desired, copy the end-points, put in pbaTexture[5] as a vector of short2's, 
    {                                                                            //into caller space. We copy only 'maxpts' elements, as the user instructed us.
        unsigned int num_pts;
        cudaMemcpyFromSymbol(&num_pts,topo_gc_last,sizeof(unsigned int),0,cudaMemcpyDeviceToHost);        //Get #topo-points we have detected from the device-var from CUDA
        if (npts && num_pts)                                                                            //Copy the topo-points to caller        
           cudaMemcpy(outputPoints,pbaTextures[5],num_pts*sizeof(short2),cudaMemcpyDeviceToHost);
        if (npts) *npts = num_pts;                                                //Return #detected topo-points to caller                                
    }
        
    if (outputTopo)                                                                //If topology image desired, copy it into user space
        cudaMemcpy(outputTopo,pbaTextures[4],pbaTexSize*pbaTexSize*sizeof(unsigned char), cudaMemcpyDeviceToHost);
}




__global__ void kernelSiteFromSkeleton(short2* outputSites, int size)                        //Initialize the Voronoi textures from the sites' encoding texture (parameterization)
{                                                                                            //REMARK: we interpret 'inputVoro' as a 2D texture, as it's much easier/faster like this
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;

    if (tx<size && ty<size)                                                                    //Careful not to go outside the image..
    {
      int i = TOID(tx,ty,size);
      unsigned char param = tex1Dfetch(pbaTexGray,i);                                        //The sites-param has non-zero (parameter) values precisely on non-boundary points

      short2& v = outputSites[i];
      v.x = v.y = MARKER;                                                                    //Non-boundary points are marked as 0 in the parameterization. Here we will compute the FT.
      if (param)                                                                            //These are points which define the 'sites' to compute the FT/skeleton (thus, have FT==identity)
      {                                                                                        //We could use an if-then-else here, but it's faster with an if-then
         v.x = tx; v.y = ty;
      }
    }
}




__global__ void kernelSkelInterpolate(float* output, int size)
{
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;

    if (tx<size && ty<size)                                                                    //Careful not to go outside the image..
    {
        int    id     = TOID(tx, ty, size);
      short2 vid    = tex1Dfetch(pbaTexColor,id);                            
      float  T      = sqrtf((tx-vid.x)*(tx-vid.x)+(ty-vid.y)*(ty-vid.y));
      short2 vid2   = tex1Dfetch(pbaTexColor2,id);                            
      float  D      = sqrtf((tx-vid2.x)*(tx-vid2.x)+(ty-vid2.y)*(ty-vid2.y));
      float  B      = ((D)? min(T/2/D,0.5f):0.5) + 0.5*((T)? max(1-D/T,0.0f):0);
      output[id]    = pow(B,0.2f);
    }
}




void skel2DSkeletonDT(float* outputSkelDT,short xm,short ym,short xM,short yM)
{
    dim3 block = dim3(BLOCKX,BLOCKY);
    dim3 grid  = dim3(pbaTexSize/block.x,pbaTexSize/block.y);

    cudaBindTexture(0,pbaTexGray,pbaTextures[3]);                            //Used to read the resident binary skeleton
    kernelSiteFromSkeleton<<<grid,block>>>(pbaTextures[0],pbaTexSize);        //1. Init pbaTextures[0] with sites on skeleton i.e. from pbaTexGray
    cudaUnbindTexture(pbaTexGray);
        
    //Must first save pbaTextures[1] since we may need it later..
    cudaMemcpy(pbaTextures[5],pbaTextures[1],pbaTexSize*pbaTexSize*sizeof(short2),cudaMemcpyDeviceToDevice);
    skel2DFTCompute(xm, ym, xM, yM, floodBand, maurerBand, colorBand);        //2. Compute FT of the skeleton into pbaTextures[6]
    cudaMemcpy(pbaTextures[6],pbaTextures[1],pbaTexSize*pbaTexSize*sizeof(short2),cudaMemcpyDeviceToDevice);
    cudaMemcpy(pbaTextures[1],pbaTextures[5],pbaTexSize*pbaTexSize*sizeof(short2),cudaMemcpyDeviceToDevice);
    
    //Compute interpolation        
    cudaBindTexture(0,pbaTexColor,pbaTextures[1]);                            // FT of boundary
    cudaBindTexture(0,pbaTexColor2,pbaTextures[6]);                            // FT of skeleton
    kernelSkelInterpolate<<<grid,block>>>((float*)pbaTextures[0],pbaTexSize);
    cudaUnbindTexture(pbaTexColor);
    cudaUnbindTexture(pbaTexColor2);
    if (outputSkelDT) cudaMemcpy(outputSkelDT, pbaTextures[0], pbaTexSize * pbaTexSize * sizeof(float), cudaMemcpyDeviceToHost);
}





__global__ void kernelFill(unsigned char* output, int size, unsigned char bg, unsigned char fg, short xm, short ym, short xM, short yM, bool ne)
{
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;
        
    if (tx>xm && ty>ym && tx<xM && ty<yM)                                        //careful not to index outside the image..
    {    
      int    id0 = TOID(tx, ty, size);
      unsigned char val = tex1Dfetch(pbaTexGray,id0);                            //
      if (val==fg)                                                                //do we have a filled pixel? Then fill all to left/top/up/bottom of it which is background
      {
        bool fill = false;
        int id = id0;
        if (ne)                                                                    //fill in north-east direction:
        {
            for(short x=tx+1;x<xM;++x)                                            //REMARK: here and below, the interesting thing is that it's faster, by about 10-15%, to fill a whole
            {                                                                    //        scanline rather than oly until the current block's borders (+1). The reason is that filling a whole
                                                                                //          scanline decreases the total #sweeps, which seems to be the limiting speed factor
              if (tex1Dfetch(pbaTexGray,++id)!=bg) break;
              output[id] = fg; fill = true;
            }

            id = id0;
            for(short y=ty-1;y>ym;--y)
            {
              if (tex1Dfetch(pbaTexGray,id-=size)!=bg) break;
              output[id] = fg; fill = true;
            }
        }
        else                                                                    //fill in south-west direction:
        {
            for(short x=tx-1;x>xm;--x)
            {
              if (tex1Dfetch(pbaTexGray,--id)!=bg) break;
              output[id] = fg; fill = true;
            }

            id = id0;
            for(short y=ty+1;y<yM;++y)
            {
              if (tex1Dfetch(pbaTexGray,id+=size)!=bg) break;
              output[id] = fg; fill = true;
            }
        }
        
        if (fill) fill_gc = true;                                                //if we filled anything, inform caller; we 'gather' this info from a local var into the
                                                                                //global var here, since it's faster than writing the global var in the for loops
      }      
    }
}




__global__ void kernelFillHoles(unsigned char* output, int size, unsigned char bg, unsigned char fg, unsigned char fill_fg)
{
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;
    
    if (tx>=0 && ty>=0 && tx<size && ty<size)                                    //careful not to index outside the image..
    {    
        int            id = TOID(tx, ty, size);
      unsigned char val = tex1Dfetch(pbaTexGray,id);                            //
      if (val==fill_fg)
         output[id] = bg;
      else if (val==bg)
         output[id] = fg;     
    }
}


int skelft2DFill(unsigned char* outputFill, short sx, short sy, short xm, short ym, short xM, short yM, unsigned char fill_value)
{
    dim3 block = dim3(BLOCKX,BLOCKY);
    dim3 grid  = dim3(pbaTexSize/block.x,pbaTexSize/block.y);

    unsigned char background;
    int id = sy * pbaTexSize + sx;
    cudaMemcpy(&background,(unsigned char*)pbaTextures[2]+id,sizeof(unsigned char),cudaMemcpyDeviceToHost); //See which is the value we have to fill from (sx,sy)
    
    cudaMemset(((unsigned char*)pbaTextures[2])+id,fill_value,sizeof(unsigned char));                    //Fill the seed (x,y) on the GPU    

    cudaBindTexture(0, pbaTexGray, pbaTextures[2]);                                                        //Used to read the thresholded DT

    int iter=0;
    bool xy = true;                                                                                        //Direction of filling for current sweep: either north-east or south-west
                                                                                                        //This kind of balances the memory-accesses nicely over kernel calls
    for(;;++iter,xy=!xy)                                                                                //Keep filling a sweep at a time until we have no background pixels anymore
    {    
       bool filled = false;                                                                                //Initialize flag: we didn't fill anything in this sweep
       cudaMemcpyToSymbol(fill_gc,&filled,sizeof(bool),0,cudaMemcpyHostToDevice);                        //Pass flag to CUDA
       kernelFill<<<grid, block>>>((unsigned char*)pbaTextures[2],pbaTexSize,background,fill_value,xm,ym,xM,yM,xy);    
                                                                                                        //One fill sweep       
       cudaMemcpyFromSymbol(&filled,fill_gc,sizeof(bool),0,cudaMemcpyDeviceToHost);                        //See if we filled anything in this sweep
       if (!filled) break;                                                                                //Nothing filled? Then we're done, the image didn't change
    }
    cudaUnbindTexture(pbaTexGray);
        
    if (outputFill) cudaMemcpy(outputFill, (unsigned char*)pbaTextures[2], pbaTexSize * pbaTexSize * sizeof(unsigned char), cudaMemcpyDeviceToHost);
    
    return iter;                                                                                        //Return #iterations done for the fill - useful as a performance measure for caller
}



int skelft2DFillHoles(unsigned char* outputFill, short sx, short sy, unsigned char foreground)
{
    unsigned char background;
    unsigned char fill_value = 128;
    int id = sy * pbaTexSize + sx;
    cudaMemcpy(&background,(unsigned char*)pbaTextures[2]+id,sizeof(unsigned char),cudaMemcpyDeviceToHost); //See which is the value at (sx,sy)

    int iter = skelft2DFill(0,sx,sy,0,0,pbaTexSize,pbaTexSize,fill_value);                                //First, fill the background surrounding the image with some special value
        
    dim3 block = dim3(BLOCKX,BLOCKY);
    dim3 grid  = dim3(pbaTexSize/block.x,pbaTexSize/block.y);
    

    cudaBindTexture(0, pbaTexGray, pbaTextures[2]);                                                        //Used to read the thresholded DT

    kernelFillHoles<<<grid, block>>>((unsigned char*)pbaTextures[2],pbaTexSize,background,foreground,fill_value);

    cudaUnbindTexture(pbaTexGray);
    
    if (outputFill) cudaMemcpy(outputFill, (unsigned char*)pbaTextures[2], pbaTexSize * pbaTexSize * sizeof(unsigned char), cudaMemcpyDeviceToHost);
    
    return iter;
}



//--------  Advection-related code  ----------------------------------------------------------------------


void advect2DSetSites(float* sites, int nsites)
{
    cudaMemcpy(pbaTexAdvectSites, sites, nsites * sizeof(float2), cudaMemcpyHostToDevice);                //Copy side-coords from CPU->GPU
}

//#define KERNEL(radius,dist) expf(-10*dist/radius)
#define KERNEL(radius,dist) ((radius-dist)/radius)



__global__ void kernelSplat(float* output, int size, int numpts, float d)
//REMARK: This could be done tens of times faster using OpenGL (draw point-sprites with a 2D distance-kernel texture)
{
    int offs = blockIdx.x * blockDim.x + threadIdx.x;

    if (offs < numpts)                                                        //careful not to index outside the site-vector..
    {    
      float2        p  = tex1Dfetch(pbaTexAdvect,offs);                        //splat the site whose coords are at location offs in pbaTexAdvect[]

      float d2   = d*d;
      short tx   = p.x, ty = p.y;                                            //REMARK: if I make these float, then something gets ccrewed up - why?
      short jmin = ty-d;
      short jmax = ty+d;

      for(short j=jmin;j<=jmax;++j)                                            //bounding-box of the splat at 'skel'
        {
           float dy = (j-ty)*(j-ty);                                        //precalc this for speed
           short imin = tx-d;
           short imax = tx+d;
           
           int offs = __mul24(int(j),size);
           
           for(short i=imin;i<=imax;++i)                                    //REMARK: this could be made ~1.5x faster by computing the x-scanline intersection (min,max) with the d2-circle..
             {
                float r2 = dy+(i-tx)*(i-tx);
                if (r2<=d2)                                                    //check we're really inside the splat
                {
                  int id = int(i)+offs; 
                  float val = KERNEL(d2,r2);
                  output[id] += val;                                        //Accumulate density (WARNING: hope this is thread-safe!!)
                                                                            //If not, I need atomicAdd() on floats which requires CUDA 2.0    
                }
             }
        }       
    }
}


inline __device__ float2 computeGradient(const float2& p)                    //Compute -gradient of density at p
{
    float v_d = tex2D(pbaTexDensity,p.x,p.y-1);
    float v_l = tex2D(pbaTexDensity,p.x-1,p.y);
    float v_r = tex2D(pbaTexDensity,p.x+1,p.y);
    float v_t = tex2D(pbaTexDensity,p.x,p.y+1);
    
    return make_float2((v_l-v_r)/2,(v_d-v_t)/2);
}


inline __device__ float2 computeGradientExplicit(float* density, const float2& p, int size)        //Compute gradient of density at p
{
    int x = p.x, y = p.y;
    float v_d = density[TOID(x,y-1,size)];
    float v_l = density[TOID(x-1,y,size)];
    float v_r = density[TOID(x+1,y,size)];
    float v_t = density[TOID(x,y+1,size)];
    
    return make_float2((v_r-v_l)/2,(v_t-v_d)/2);
}


__global__ void kernelGradient(float2* grad, int size)                            
{
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;

    const float eps = 0.0001;

    if (tx<size && ty<size)                                                    //Careful not to go outside the image..
    {
      float2 p = make_float2(tx,ty);
      float2 g = computeGradient(p);
      
      float gn = sqrtf(g.x*g.x+g.y*g.y);                                    //robustly normalize the gradient
      g.x /= gn+eps; g.y /= gn+eps;
            
      grad[TOID(tx,ty,size)] = g;    
    }
}


void advect2DSplat(float* output, int num_pts, float radius)
{
    dim3 block = dim3(BLOCKX*BLOCKY);                                            //Prepare the splatting kernel: this operates on a vector of 2D sites
    int numpts_b = (num_pts/block.x+1)*block.x;                                    //Find higher multiple of blocksize than # sites
    dim3 grid  = dim3(numpts_b/block.x);
    
    cudaMemset(pbaTexDensityData,0,sizeof(float)*pbaTexSize*pbaTexSize);        //Zero the density texture
    cudaBindTexture(0,pbaTexAdvect,pbaTexAdvectSites);                            //Bind sites to a texture

    kernelSplat<<< grid, block >>>(pbaTexDensityData, pbaTexSize, num_pts, radius);
                                                                                //Splat kernel    
    cudaThreadSynchronize();    
                                                                                
    cudaUnbindTexture(pbaTexAdvect);                                            //Done with the sites
                                                                                //Copy splat-map to CPU (if desired)
    if (output) cudaMemcpy(output, pbaTexDensityData, pbaTexSize * pbaTexSize * sizeof(float), cudaMemcpyDeviceToHost);
}



void advect2DGetSites(float* output, int num_pts)                                //Get sites CUDA->CPU
{
    cudaMemcpy(output,pbaTexAdvectSites,num_pts*sizeof(float2),cudaMemcpyDeviceToHost);
}


void advect2DGradient()                                                            //Compute and save gradient of density map
{
    dim3 block = dim3(BLOCKX,BLOCKY);
    dim3 grid  = dim3(pbaTexSize/block.x,pbaTexSize/block.y);
    
    cudaMemset(pbaTexGradientData,0,sizeof(float2)*pbaTexSize*pbaTexSize);        //Zero the gradient texture

    pbaTexDensity.filterMode = cudaFilterModeLinear;                            //The floating-point density map
    pbaTexDensity.normalized = false;
    cudaChannelFormatDesc channelDesc1 = cudaCreateChannelDesc(32,0,0,0,cudaChannelFormatKindFloat);
    cudaBindTexture2D(0,pbaTexDensity,pbaTexDensityData,channelDesc1,pbaTexSize,pbaTexSize,4*pbaTexSize);                            
        
    kernelGradient<<< grid, block >>>(pbaTexGradientData, pbaTexSize);            //Gradient kernel    
                                                                                
    cudaUnbindTexture(pbaTexDensity);
}