#include "mrilib.h" #ifdef USE_OMP #include #endif #undef DEBUG /* for some extra printouts */ /****************************************************************************** Radial Basis Function (RBF) interpolation in 3D. + These functions are not designed for high efficiency! + They are designed for repeated interpolations from the same set of knots, using different sets of values each time. + The C2 polynomial with compact support (0 < r < 1) f(r) = (1-r)^4*(4r+1) is the RBF used here, with a global linear polynomial. + Setup for a given collection of knots: RBF_setup_knots() + Setup for a given set of values at the knots: RBF_setup_evalues() + Evaluation at an arbitrary set of points: RBF_evaluate() + optional: speedup evaluation a little via: RBF_setup_kranges() *******************************************************************************/ /*............................................................................*/ /* RBF expansion i=nk-1 f(x) = sum { alpha_i * RBF(x-q_i) } + b_0 + x*b_x +y*b_y + z*b_z i=0 where the weights alpha_i for knot location q_i are chosen so that the expansion f(q_i) matches the function f() at that location, for each knot q_i. The (optional) global linear polynomial is available to provide nonzero values of f(x) for x far away from any knots. *//*..........................................................................*/ #if 0 /* would be needed for thin-plate spline RBF */ #undef Rlog #define Rlog(x) (((x)<=0.0f) ? 0.0f : logf(x)) #endif /*! C2 polynomial: argument is 1-r, which should be between 0 and 1; note that this function is "positive definite": [M] matrix will be p.d. */ #undef RBF_func #define RBF_func(x) ((x)*(x)*(x)*(x)*(5.0f-4.0f*x)) /*----------------------------------------------------------------------------*/ static int verb = 0 ; void RBF_set_verbosity( int ii ){ verb = ii ; } /*----------------------------------------------------------------------------*/ /*! Evaluate RBF expansion on a set of points in rbg, given the set of knots in rbk, and given the coefficients of the RBF (or the values at the fit points) in rbe, and store results in array val -- which should be rbg->npt points long. *//*--------------------------------------------------------------------------*/ int RBF_evaluate( RBF_knots *rbk, RBF_evalues *rbe, RBF_evalgrid *rbg, float *val ) { int npt , nk ; double ct ; ENTRY("RBF_evaluate") ; if( rbk == NULL || rbe == NULL || rbg == NULL || val == NULL ){ ERROR_message("Illegal call to RBF_evaluate?!") ; RETURN(0) ; } /* if needed, convert rbe to RBF weights, from function values */ npt = RBF_setup_evalues( rbk , rbe ) ; if( npt == 0 ){ ERROR_message("bad evalues input to RBF_evaluate") ; RETURN(0) ; } /* evaluate RBF + linear polynomial at each output point */ npt = rbg->npt ; nk = rbk->nknot ; if( verb ) INFO_message("RBF_evaluate: %d points X %d knots",npt,nk) ; ct = COX_clock_time() ; AFNI_OMP_START ; #pragma omp parallel if(npt*nk > 9999) { int ii , jj , uselin=rbk->uselin ; float rr , xm,ym,zm , xd,yd,zd , rad,rqq , xt,yt,zt , *xx,*yy,*zz ; float xk , yk , zk , sum , *ev ; float b0=0.0,bx=0.0,by=0.0,bz=0.0 , rai ; RBFKINT *kfirst , *klast ; int kbot,ktop ; /* load some local variables */ rad = rbk->rad ; rqq = rbk->rqq ; rai = 1.0f / rad ; xm = rbk->xmid ; ym = rbk->ymid ; zm = rbk->zmid ; xd = rbk->xscl ; yd = rbk->yscl ; zd = rbk->zscl ; xx = rbk->xknot; yy = rbk->yknot; zz = rbk->zknot ; ev = rbe->val ; if( uselin ){ b0 = rbe->b0 ; bx = rbe->bx ; by = rbe->by ; bz = rbe->bz ; } kfirst = rbg->kfirst ; klast = rbg->klast ; kbot = 0 ; ktop = nk-1 ; #pragma omp for for( ii=0 ; ii < npt ; ii++ ){ xt = rbg->xpt[ii]; yt = rbg->ypt[ii]; zt = rbg->zpt[ii]; /* output xyz */ if( kfirst != NULL ){ kbot = kfirst[ii] ; ktop = klast[ii] ; } for( sum=0.0f,jj=kbot ; jj <= ktop ; jj++ ){ xk = xt-xx[jj]; rr =xk*xk; if( rr >= rqq ) continue; yk = yt-yy[jj]; rr+=yk*yk; if( rr >= rqq ) continue; zk = zt-zz[jj]; rr+=zk*zk; if( rr >= rqq ) continue; rr = 1.0f - sqrtf(rr) * rai ; sum += ev[jj] * RBF_func(rr) ; } val[ii] = sum ; if( uselin ) val[ii] += b0 + bx*(xt-xm)*xd + by*(yt-ym)*yd + bz*(zt-zm)*zd ; } } /* end OpenMP */ AFNI_OMP_END ; if( verb ) ININFO_message(" Elapsed = %.1f",COX_clock_time()-ct) ; RETURN(1) ; } /*----------------------------------------------------------------------------*/ /*! Setup the particular values from which to interpolate RBF-wise; that is, convert them from values at each fit point to the weights used for the RBF centered at each knot (plus the linear polynomial). Return value is 0 if bad things happened, 1 if good things happened. *//*--------------------------------------------------------------------------*/ int RBF_setup_evalues( RBF_knots *rbk , RBF_evalues *rbe ) { int nn , ii , jj ; float *mat , *vv ; double *aa ; ENTRY("RBF_setup_evalues") ; if( rbk == NULL || rbe == NULL || rbe->val == NULL ){ ERROR_message("bad call to RBF_setup_evalues") ; RETURN(0) ; } if( rbe->code > 0 ) RETURN(1) ; /* already contains RBF weights */ if( verb ) INFO_message("RBF_setup_evalues: solve for knot weights") ; nn = rbk->nknot ; vv = rbe->val ; aa = (double *)calloc(sizeof(double),nn) ; for( ii=0 ; ii < nn ; ii++ ) aa[ii] = (double)vv[ii] ; /* compute [aa] = [Minv][vv] using Choleski factors */ rcmat_lowert_solve( rbk->Lmat , aa ) ; /* [Linv] [vv] */ rcmat_uppert_solve( rbk->Lmat , aa ) ; /* [Ltinv] [Linv] [vv] */ /* adjust [aa] via [Q] matrix, for linear polynomial part of fit */ if( rbk->uselin ){ double q0,qx,qy,qz , b0,bx,by,bz ; dmat44 Q=rbk->Qmat ; float *P0=rbk->P0 , *Px=rbk->Px , *Py=rbk->Py , *Pz=rbk->Pz ; for( q0=qx=qy=qz=0.0,ii=0 ; ii < nn ; ii++ ){ /* compute [Pt][aa] */ q0 += P0[ii]*aa[ii] ; qx += Px[ii]*aa[ii] ; qy += Py[ii]*aa[ii] ; qz += Pz[ii]*aa[ii] ; } DMAT44_VEC( Q , q0,qx,qy,qz , b0,bx,by,bz ) ; /* compute [beta] */ rbe->b0 = b0 ; rbe->bx = bx ; rbe->by = by ; rbe->bz = bz ; for( ii=0 ; ii < nn ; ii++ ) /* [aa] = [vv] - [P][beta] */ aa[ii] = (double)vv[ii] - b0*P0[ii] - bx*Px[ii] - by*Py[ii] - bz*Pz[ii] ; rcmat_lowert_solve( rbk->Lmat , aa ) ; /* compute [Minv][aa] */ rcmat_uppert_solve( rbk->Lmat , aa ) ; /* as final RBF weights */ #ifdef DEBUG INFO_message("RBF_setup_evalues: b0=%g bx=%g by=%g bz=%g",b0,bx,by,bz) ; #endif } /* put results back into rbe struct */ for( ii=0 ; ii < nn ; ii++ ) vv[ii] = (float)aa[ii] ; rbe->code = 1 ; /* code that rbe is converted to RBF weights from values */ free(aa) ; RETURN(2) ; } /*----------------------------------------------------------------------------*/ /*! Setup the knots struct for radial basis function evaluation. *//*--------------------------------------------------------------------------*/ RBF_knots * RBF_setup_knots( int nk, float radius, int uselin, float *xx , float *yy , float *zz ) { RBF_knots *rbk ; int ii , jj , nn , jtop ; float **mat ; rcmat *rcm ; float *P0=NULL,*Px=NULL,*Py=NULL,*Pz=NULL ; float rr , xm,ym,zm , xd,yd,zd , rad,rqq , xt,yt,zt ; double ct ; ENTRY("RBF_setup_knots") ; if( nk < 5 || xx == NULL || yy == NULL || zz == NULL ){ ERROR_message("Illegal inputs to RBF_setup_knots") ; RETURN(NULL) ; } ct = COX_clock_time() ; /* Setup middle of knot field and scale radius: xm = middle (median) of the x knot coordinates xd = scale (MAD) of the x knot distances from xm; would be about L/4 if knots are uniformly spaced in 1D over a distance of L ==> 4*xd/nk is about inter-knot x distance in 1D uniform case ==> 4*(xd+yd+zd)/(3*cbrt(nk)) is about mean inter-knot distance in 3D RBF support radius is chosen to be a small multiple of this distance */ qmedmad_float( nk , xx , &xm , &xd ) ; qmedmad_float( nk , yy , &ym , &yd ) ; qmedmad_float( nk , zz , &zm , &zd ) ; if( radius <= 0.0f ) rad = 4.321f*(xd+yd+zd) / cbrtf((float)nk) ; /* RBF support radius */ else rad = radius ; /* user-selected RBF radius */ rqq = rad*rad ; /* for testing radius-squared */ if( verb ) INFO_message("RBF_setup_knots: knots=%d radius=%.1f",nk,rad) ; xd = 1.0f / xd ; /* scale factors : (x-xm)*xd is */ yd = 1.0f / yd ; /* dimensionless x-value relative */ zd = 1.0f / zd ; /* to middle at xm */ /*...................................................................*//* Set up symmetric nk X nk matrix for interpolation: mat[ii][jj] = RBF( knot[ii] - knot[jj] ) for jj=0..ii. Call this matrix M. It is positive definite by the choice of the particular radial basis function being used. If we are NOT using linear polynomials, then the system of equations to solve for RBF knot weights [alpha] given knot function values [v] is [M] [alpha] = [v] We Choleski decompose [M] = [L] [Lt] so that we solve via [alpha] = [Ltinv][Linv] [v] This will be done in function RBF_setup_evalues(), which converts values of the function to be interpolated at the knots [v] into weights [alpha]. If we ARE using a global linear polynomial, then the system of equations to solve is [ M P ] [alpha] = [v] [ Pt 0 ] [beta ] = [0] where [P] is an nk X 4 matrix, with entries P_{i0}=1 P_{i1}=x_i P_{i2}=y_i P_{i3}=z_i (knot coordinates) Pt = P-transpose [beta] = 4-vector of weights for linear polynomial The purpose of this is to make the RBF part of the expansion not contain any linear component -- that will be carried by the global linear polynomial. Without this constraint, the system would be over-determined: nk equations with nk+4 unknowns. The first nk equations express the interpolation property: the evaluated RBF sum plus the linear polynomial equals [v] at the knots. The last 4 equations express the property that the [alpha] weights by themselves do NOT have any component of a global linear polynomial. The symmetric compound (nk+4)X(nk+4) matrix is obviously not positive definite (since it has 0 values on the diagonal), so cannot be solved directly by Choleski factorization. Instead, we solve it via a bordering technique: [M][alpha] + [P][beta] = [v] ==> [alpha] = [Minv][v] - [Minv][P][beta] (*) [Pt][alpha] = [0] ==> [Pt][Minv][v] = [Pt][Minv][P][beta] ==> [beta] = inv{ [Pt][Minv][P] }[Pt][Minv][v] (**) Once [beta] is known from Eqn(**), [alpha] is found by using Eqn(*). In details, given the Choleski factors of [M]: Compute 4x4 matrix [Q] = inv{ [Pt][Ltinv][Linv][P] } (done below) Given [v], [ahat] = [Ltinv][Linv][v] (==[alpha] if not using [P]) [beta] = [Q][Pt][ahat] [alpha] = [Ltinv][Linv]{ [v] - [P][beta] } We then use [alpha] to evaluate the RBF at each output grid point, and use [beta] to evaluate the global linear polynomial. *//*.....................................................................*/ /* create the [M] matrix rows */ nn = nk ; mat = (float **)malloc(sizeof(float *)*nn) ; for( ii=0 ; ii < nn ; ii++ ) mat[ii] = (float *)calloc((ii+1),sizeof(float)) ; /* create the [P] matrix columns as needed for the linear polynomial */ if( uselin ){ P0 = (float *)calloc(nn,sizeof(float)) ; Px = (float *)calloc(nn,sizeof(float)) ; Py = (float *)calloc(nn,sizeof(float)) ; Pz = (float *)calloc(nn,sizeof(float)) ; } if( verb > 1 ) ININFO_message(" matrix computation") ; /* load the [M] matrix and the [P] matrix */ for( ii=0 ; ii < nn ; ii++ ){ for( jj=0 ; jj < ii ; jj++ ){ /* RBF between knots */ xt = xx[ii]-xx[jj] ; yt = yy[ii]-yy[jj] ; zt = zz[ii]-zz[jj] ; rr = xt*xt + yt*yt + zt*zt ; if( rr >= rqq ) continue ; rr = 1.0f - sqrtf(rr)/rad ; mat[ii][jj] = RBF_func(rr) ; } mat[ii][ii] = 1.0000005f ; /* RBF(0) = 1 by definition */ if( uselin ){ /* [P] matrix for linear polynomial */ P0[ii] = 1.0f ; Px[ii] = (xx[ii]-xm)*xd ; Py[ii] = (yy[ii]-ym)*yd ; Pz[ii] = (zz[ii]-zm)*zd ; } } if( verb > 1 ) ININFO_message(" matrix factorization") ; /* convert mat[][] into an rcmat sparse symmetric matrix struct */ rcm = rcmat_from_rows( nn , mat ) ; for( ii=0 ; ii < nn ; ii++ ) free(mat[ii]) ; /* don't need mat */ free(mat) ; /* no more */ if( rcm == NULL ){ ERROR_message("RBF_setup_knots: setup of rcmat fails!?") ; if( uselin ){ free(P0); free(Px); free(Py); free(Pz); } RETURN(NULL) ; } /* Choleski decompose M matrix */ ii = rcmat_choleski( rcm ) ; if( ii > 0 ){ ERROR_message("RBF_setup_knots: Choleski of rcmat fails at row %d!",ii) ; if( uselin ){ free(P0); free(Px); free(Py); free(Pz); } RETURN(NULL) ; } /* create output struct, save stuff into it */ rbk = (RBF_knots *)calloc(1,sizeof(RBF_knots)) ; rbk->nknot = nk ; rbk->rad = rad ; rbk->rqq = rqq ; rbk->xmid = xm ; rbk->xscl = xd ; rbk->ymid = ym ; rbk->yscl = yd ; rbk->zmid = zm ; rbk->zscl = zd ; rbk->xknot = (float *)calloc(sizeof(float),nk) ; /* saved knots */ rbk->yknot = (float *)calloc(sizeof(float),nk) ; rbk->zknot = (float *)calloc(sizeof(float),nk) ; memcpy(rbk->xknot,xx,sizeof(float)*nk) ; memcpy(rbk->yknot,yy,sizeof(float)*nk) ; memcpy(rbk->zknot,zz,sizeof(float)*nk) ; rbk->Lmat = rcm ; /* saved [L] matrix */ rbk->uselin = uselin ; rbk->P0 = P0 ; rbk->Px = Px ; rbk->Py = Py ; rbk->Pz = Pz ; /* compute the Q matrix for the linear polynomial coefficients */ if( uselin ){ double *vv[4],*vi,*vj ; dmat44 Q ; double sum ; register int kk ; #ifdef DEBUG INFO_message(" compute Q matrix") ; #endif vv[0] = (double *)malloc(sizeof(double)*nn) ; vv[1] = (double *)malloc(sizeof(double)*nn) ; vv[2] = (double *)malloc(sizeof(double)*nn) ; vv[3] = (double *)malloc(sizeof(double)*nn) ; for( ii=0 ; ii < nn ; ii++ ){ vv[0][ii] = P0[ii] ; vv[1][ii] = Px[ii] ; vv[2][ii] = Py[ii] ; vv[3][ii] = Pz[ii] ; } /* compute [Linv][P] into the 4 columns [vv] */ rcmat_lowert_solve(rcm,vv[0]) ; rcmat_lowert_solve(rcm,vv[1]) ; rcmat_lowert_solve(rcm,vv[2]) ; rcmat_lowert_solve(rcm,vv[3]) ; /* compute 4x4 matrix [vv]^T [vv], then invert it to get output [Q] */ for( ii=0 ; ii < 4 ; ii++ ){ vi = vv[ii] ; for( jj=0 ; jj < 4 ; jj++ ){ vj = vv[jj] ; for( sum=0.0,kk=0 ; kk < nn ; kk++ ) sum += vi[kk]*vj[kk] ; Q.m[ii][jj] = sum ; } } free(vv[0]) ; free(vv[1]) ; free(vv[2]) ; free(vv[3]) ; rbk->Qmat = generic_dmat44_inverse(Q) ; /* [Q] matrix to be saved */ #ifdef DEBUG DUMP_DMAT44("Q",Q) ; DUMP_DMAT44("Qinv",rbk->Qmat) ; #endif } if( verb > 1 ) ININFO_message(" Elapsed = %.1f",COX_clock_time()-ct) ; RETURN(rbk) ; } /*------------------------------------------------------------------*/ #if 0 #undef ADDTO_intar #define ADDTO_intar(nar,nal,ar,val) \ do{ if( (nar) == (nal) ){ \ (nal) = 1.2*(nal)+16; (ar) = (int *)realloc((ar),sizeof(int)*(nal)); \ } \ (ar)[(nar)++] = (val); \ } while(0) #undef CLIP_intar #define CLIP_intar(nar,nal,ar) \ do{ if( (nar) < (nal) && (nar) > 0 ){ \ (nal) = (nar); (ar) = (int *)realloc((ar),sizeof(int)*(nal)); \ }} while(0) #endif /*------------------------------------------------------------------*/ /*! For speedup, for each grid point compute the first and last knots that can affect its output value. This will save time when looping over knots to evaluate the RBF expansion. *//*----------------------------------------------------------------*/ void RBF_setup_kranges( RBF_knots *rbk , RBF_evalgrid *rbg ) { int npt , nk ; double ct ; ENTRY("RBF_setup_kranges") ; if( rbk == NULL || rbg == NULL ) EXRETURN ; if( rbg->klast != NULL ){ free(rbg->klast) ; rbg->klast = NULL; } if( rbg->kfirst!= NULL ){ free(rbg->kfirst); rbg->kfirst= NULL; } if( rbk->nknot > RBFKINT_MAX ) EXRETURN ; /* too many to store r/ /* load some local variables */ npt = rbg->npt ; nk = rbk->nknot ; rbg->kfirst = (RBFKINT *)calloc(sizeof(RBFKINT),npt) ; rbg->klast = (RBFKINT *)calloc(sizeof(RBFKINT),npt) ; if( verb ){ INFO_message("RBF_setup_kranges: %d grid points",npt) ; } ct = COX_clock_time() ; AFNI_OMP_START ; #pragma omp parallel if(npt*nk > 9999) { int ii,jj , kbot,ktop ; float xt,yt,zt, rqq, xk,yk,zk, rr, *xx,*yy,*zz ; RBFKINT *klast , *kfirst ; rqq = rbk->rqq ; xx = rbk->xknot; yy = rbk->yknot; zz = rbk->zknot; kfirst = rbg->kfirst ; klast = rbg->klast ; #pragma omp for for( ii=0 ; ii < npt ; ii++ ){ xt = rbg->xpt[ii]; yt = rbg->ypt[ii]; zt = rbg->zpt[ii]; kbot = ktop = -1 ; for( jj=0 ; jj < nk ; jj++ ){ xk = xt-xx[jj]; rr =xk*xk; if( rr >= rqq ) continue; yk = yt-yy[jj]; rr+=yk*yk; if( rr >= rqq ) continue; zk = zt-zz[jj]; rr+=zk*zk; if( rr >= rqq ) continue; ktop = jj ; if( kbot < 0 ) kbot = jj ; } if( kbot >= 0 ){ kfirst[ii] = (RBFKINT)kbot ; klast [ii] = (RBFKINT)ktop ; } } } /* end OpenMP */ AFNI_OMP_END ; if( verb > 1 ){ float ntot=0.0f ; int ii ; for( ii=0 ; ii < npt ; ii++ ) ntot += (1.0f+rbg->klast[ii]-rbg->kfirst[ii]) ; ININFO_message(" average krange = %.1f Elapsed = %.1f", ntot/npt , COX_clock_time()-ct ) ; } EXRETURN ; }