/*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkThinPlateSplineTransform.cxx,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkThinPlateSplineTransform.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPoints.h" vtkCxxRevisionMacro(vtkThinPlateSplineTransform, "$Revision: 1.33 $"); vtkStandardNewMacro(vtkThinPlateSplineTransform); //------------------------------------------------------------------------ // some dull matrix things inline double** vtkNewMatrix(int rows, int cols) { double *matrix = new double[rows*cols]; double **m = new double *[rows]; for(int i = 0; i < rows; i++) { m[i] = &matrix[i*cols]; } return m; } //------------------------------------------------------------------------ inline void vtkDeleteMatrix(double **m) { delete [] *m; delete [] m; } //------------------------------------------------------------------------ inline void vtkZeroMatrix(double **m, int rows, int cols) { for(int i = 0; i < rows; i++) { for(int j = 0; j < cols; j++) { m[i][j] = 0.0; } } } //------------------------------------------------------------------------ inline void vtkMatrixMultiply(double **a, double **b, double **c, int arows, int acols, int brows, int bcols) { if(acols != brows) { return; // acols must equal br otherwise we can't proceed } // c must have size arows*bcols (we assume this) for(int i = 0; i < arows; i++) { for(int j = 0; j < bcols; j++) { c[i][j] = 0.0; for(int k = 0; k < acols; k++) { c[i][j] += a[i][k]*b[k][j]; } } } } //------------------------------------------------------------------------ inline void vtkMatrixTranspose(double **a, double **b, int rows, int cols) { for(int i = 0; i < rows; i++) { for(int j = 0; j < cols; j++) { double tmp = a[i][j]; b[i][j] = a[j][i]; b[j][i] = tmp; } } } //------------------------------------------------------------------------ vtkThinPlateSplineTransform::vtkThinPlateSplineTransform() { this->SourceLandmarks=NULL; this->TargetLandmarks=NULL; this->Sigma = 1.0; // If the InverseFlag is set, then we use an iterative // method to invert the transformation. // The InverseTolerance sets the precision to which we want to // calculate the inverse. this->InverseTolerance = 0.001; this->InverseIterations = 500; this->Basis = -1; this->SetBasisToR2LogR(); this->NumberOfPoints = 0; this->MatrixW = NULL; } //------------------------------------------------------------------------ vtkThinPlateSplineTransform::~vtkThinPlateSplineTransform() { if (this->SourceLandmarks) { this->SourceLandmarks->Delete(); } if (this->TargetLandmarks) { this->TargetLandmarks->Delete(); } if (this->MatrixW) { vtkDeleteMatrix(this->MatrixW); this->MatrixW = NULL; } } //------------------------------------------------------------------------ void vtkThinPlateSplineTransform::SetSourceLandmarks(vtkPoints *source) { if (this->SourceLandmarks == source) { return; } if (this->SourceLandmarks) { this->SourceLandmarks->Delete(); } source->Register(this); this->SourceLandmarks = source; this->Modified(); } //------------------------------------------------------------------------ void vtkThinPlateSplineTransform::SetTargetLandmarks(vtkPoints *target) { if (this->TargetLandmarks == target) { return; } if (this->TargetLandmarks) { this->TargetLandmarks->Delete(); } target->Register(this); this->TargetLandmarks = target; this->Modified(); } //------------------------------------------------------------------------ unsigned long vtkThinPlateSplineTransform::GetMTime() { unsigned long result = this->vtkWarpTransform::GetMTime(); unsigned long mtime; if (this->SourceLandmarks) { mtime = this->SourceLandmarks->GetMTime(); if (mtime > result) { result = mtime; } } if (this->TargetLandmarks) { mtime = this->TargetLandmarks->GetMTime(); if (mtime > result) { result = mtime; } } return result; } //------------------------------------------------------------------------ void vtkThinPlateSplineTransform::InternalUpdate() { if (this->SourceLandmarks == NULL || this->TargetLandmarks == NULL) { if (this->MatrixW) { vtkDeleteMatrix(this->MatrixW); } this->MatrixW = NULL; this->NumberOfPoints = 0; return; } if (this->SourceLandmarks->GetNumberOfPoints() != this->TargetLandmarks->GetNumberOfPoints()) { vtkErrorMacro("Update: Source and Target Landmarks contain a different number of points"); return; } const vtkIdType N = this->SourceLandmarks->GetNumberOfPoints(); const int D = 3; // dimensions // the output weights matrix double **W = vtkNewMatrix(N+D+1,D); double **A = &W[N+1]; // the linear rotation + scale matrix double *C = W[N]; // the linear translation if (N >= 3) { // Notation and inspiration from: // Fred L. Bookstein (1997) "Shape and the Information in Medical Images: // A Decade of the Morphometric Synthesis" Computer Vision and Image // Understanding 66(2):97-118 // and online work published by Tim Cootes (http://www.wiau.man.ac.uk/~bim) // the input matrices double **L = vtkNewMatrix(N+D+1,N+D+1); double **X = vtkNewMatrix(N+D+1,D); // build L // will leave the bottom-right corner with zeros vtkZeroMatrix(L,N+D+1,N+D+1); int q,c; double p[3],p2[3]; double dx,dy,dz; double r; double (*phi)(double) = this->BasisFunction; for(q = 0; q < N; q++) { this->SourceLandmarks->GetPoint(q,p); // fill in the top-right and bottom-left corners of L (Q) L[N][q] = L[q][N] = 1.0; L[N+1][q] = L[q][N+1] = p[0]; L[N+2][q] = L[q][N+2] = p[1]; L[N+3][q] = L[q][N+3] = p[2]; // fill in the top-left corner of L (K), using symmetry for(c = 0; c < q; c++) { this->SourceLandmarks->GetPoint(c,p2); dx = p[0]-p2[0]; dy = p[1]-p2[1]; dz = p[2]-p2[2]; r = sqrt(dx*dx + dy*dy + dz*dz); L[q][c] = L[c][q] = phi(r/this->Sigma); } } // build X vtkZeroMatrix(X,N+D+1,D); for (q = 0; q < N; q++) { this->TargetLandmarks->GetPoint(q,p); X[q][0] = p[0]; X[q][1] = p[1]; X[q][2] = p[2]; } // solve for W, where W = Inverse(L)*X; // this is done via eigenvector decomposition so // that we can avoid singular values // W = V*Inverse(w)*U*X double *values = new double[N+D+1]; double **V = vtkNewMatrix(N+D+1,N+D+1); double **w = vtkNewMatrix(N+D+1,N+D+1); double **U = L; // reuse the space vtkMath::JacobiN(L,N+D+1,values,V); vtkMatrixTranspose(V,U,N+D+1,N+D+1); vtkIdType i, j; double maxValue = 0.0; // maximum eigenvalue for (i = 0; i < N+D+1; i++) { double tmp = fabs(values[i]); if (tmp > maxValue) { maxValue = tmp; } } for (i = 0; i < N+D+1; i++) { for (j = 0; j < N+D+1; j++) { w[i][j] = 0.0; } // here's the trick: don't invert the singular values if (fabs(values[i]/maxValue) > 1e-16) { w[i][i] = 1.0/values[i]; } } delete [] values; vtkMatrixMultiply(U,X,W,N+D+1,N+D+1,N+D+1,D); vtkMatrixMultiply(w,W,X,N+D+1,N+D+1,N+D+1,D); vtkMatrixMultiply(V,X,W,N+D+1,N+D+1,N+D+1,D); vtkDeleteMatrix(V); vtkDeleteMatrix(w); vtkDeleteMatrix(U); vtkDeleteMatrix(X); // now the linear portion of the warp must be checked // (this is a very poor check for now) if (fabs(vtkMath::Determinant3x3((double (*)[3]) *A)) < 1e-16) { for (i = 0; i < 3; i++) { if (sqrt(A[0][i]*A[0][i] + A[1][i]*A[1][i] + A[2][i]*A[2][i]) < 1e-16) { A[0][i] = A[1][i] = A[2][i] = A[i][0] = A[i][1] = A[i][2] = 0; A[i][i] = 1.0; } } } } // special cases, I added these to ensure that this class doesn't // misbehave if the user supplied fewer than 3 landmarks else // (N < 3) { vtkIdType i,j; // set nonlinear portion of transformation to zero for (i = 0; i < N; i++) { for (j = 0; j < D; j++) { W[i][j] = 0; } } if (N == 2) { // two landmarks, construct a similarity transformation double s0[3],t0[3],s1[3],t1[3]; this->SourceLandmarks->GetPoint(0,s0); this->TargetLandmarks->GetPoint(0,t0); this->SourceLandmarks->GetPoint(1,s1); this->TargetLandmarks->GetPoint(1,t1); double ds[3],dt[3],as[3],at[3]; double rs = 0, rt = 0; for (i = 0; i < 3; i++) { as[i] = (s0[i] + s1[i])/2; // average of endpoints ds[i] = s1[i] - s0[i]; // vector between points rs += ds[i]*ds[i]; at[i] = (t0[i] + t1[i])/2; dt[i] = t1[i] - t0[i]; rt += dt[i]*dt[i]; } // normalize the two vectors rs = sqrt(rs); ds[0] /= rs; ds[1] /= rs; ds[2] /= rs; rt = sqrt(rt); dt[0] /= rt; dt[1] /= rt; dt[2] /= rt; double w,x,y,z; // take dot & cross product w = ds[0]*dt[0] + ds[1]*dt[1] + ds[2]*dt[2]; x = ds[1]*dt[2] - ds[2]*dt[1]; y = ds[2]*dt[0] - ds[0]*dt[2]; z = ds[0]*dt[1] - ds[1]*dt[0]; double r = sqrt(x*x + y*y + z*z); double theta = atan2(r,w); // construct quaternion w = cos(theta/2); if (r != 0) { r = sin(theta/2)/r; x = x*r; y = y*r; z = z*r; } else // rotation by 180 degrees { // rotate around a vector perpendicular to ds vtkMath::Perpendiculars(ds,dt,0,0); r = sin(theta/2); x = dt[0]*r; y = dt[1]*r; z = dt[2]*r; } // now r is scale factor for matrix r = rt/rs; // build a rotation + scale matrix A[0][0] = (w*w + x*x - y*y - z*z)*r; A[0][1] = (x*y + w*z)*2*r; A[0][2] = (x*z - w*y)*2*r; A[1][0] = (x*y - w*z)*2*r; A[1][1] = (w*w - x*x + y*y - z*z)*r; A[1][2] = (y*z + w*x)*2*r; A[2][0] = (x*z + w*y)*2*r; A[2][1] = (y*z - w*x)*2*r; A[2][2] = (w*w - x*x - y*y + z*z)*r; // include the translation C[0] = at[0] - as[0]*A[0][0] - as[1]*A[1][0] - as[2]*A[2][0]; C[1] = at[1] - as[0]*A[0][1] - as[1]*A[1][1] - as[2]*A[2][1]; C[2] = at[2] - as[0]*A[0][2] - as[1]*A[1][2] - as[2]*A[2][2]; } else if (N == 1) // one landmark, translation only { double p[3],p2[3]; this->SourceLandmarks->GetPoint(0,p); this->TargetLandmarks->GetPoint(0,p2); for (i = 0; i < D; i++) { for (j = 0; j < D; j++) { A[i][j] = 0; } A[i][i] = 1; C[i] = p2[i] - p[i]; } } else // if no landmarks, set to identity { for (i = 0; i < D; i++) { for (j = 0; j < D; j++) { A[i][j] = 0; } A[i][i] = 1; C[i] = 0; } } } // left in for debug purposes /* cerr << "W =\n"; for (int i = 0; i < N+1+D; i++) { cerr << W[i][0] << ' ' << W[i][1] << ' ' << W[i][2] << '\n'; } cerr << "\n"; */ if (this->MatrixW) { vtkDeleteMatrix(this->MatrixW); } this->MatrixW = W; this->NumberOfPoints = N; } //------------------------------------------------------------------------ // The matrix W was created by Update. Not much has to be done to // apply the transform: do an affine transformation, then do // perturbations based on the landmarks. template inline void vtkThinPlateSplineForwardTransformPoint(vtkThinPlateSplineTransform *self, double **W, int N, double (*phi)(double), const T point[3], T output[3]) { if (N == 0) { output[0] = point[0]; output[1] = point[1]; output[2] = point[2]; return; } double *C = W[N]; double **A = &W[N+1]; double dx,dy,dz; double p[3]; double U,r; double invSigma = 1.0/self->GetSigma(); double x = 0, y = 0, z = 0; vtkPoints *sourceLandmarks = self->GetSourceLandmarks(); // do the nonlinear stuff for(vtkIdType i = 0; i < N; i++) { sourceLandmarks->GetPoint(i,p); dx = point[0]-p[0]; dy = point[1]-p[1]; dz = point[2]-p[2]; r = sqrt(dx*dx + dy*dy + dz*dz); U = phi(r*invSigma); x += U*W[i][0]; y += U*W[i][1]; z += U*W[i][2]; } // finish off with the affine transformation x += C[0] + point[0]*A[0][0] + point[1]*A[1][0] + point[2]*A[2][0]; y += C[1] + point[0]*A[0][1] + point[1]*A[1][1] + point[2]*A[2][1]; z += C[2] + point[0]*A[0][2] + point[1]*A[1][2] + point[2]*A[2][2]; output[0] = x; output[1] = y; output[2] = z; } void vtkThinPlateSplineTransform::ForwardTransformPoint(const double point[3], double output[3]) { vtkThinPlateSplineForwardTransformPoint(this, this->MatrixW, this->NumberOfPoints, this->BasisFunction, point, output); } void vtkThinPlateSplineTransform::ForwardTransformPoint(const float point[3], float output[3]) { vtkThinPlateSplineForwardTransformPoint(this, this->MatrixW, this->NumberOfPoints, this->BasisFunction, point, output); } //---------------------------------------------------------------------------- // calculate the thin plate spline as well as the jacobian template inline void vtkThinPlateSplineForwardTransformDerivative( vtkThinPlateSplineTransform *self, double **W, int N, double (*phi)(double, double&), const T point[3], T output[3], T derivative[3][3]) { if (N == 0) { for (int i = 0; i < 3; i++) { output[i] = point[i]; derivative[i][0] = derivative[i][1] = derivative[i][2] = 0.0; derivative[i][i] = 1.0; } return; } double *C = W[N]; double **A = &W[N+1]; double dx,dy,dz; double p[3]; double r, U, f, Ux, Uy, Uz; double x = 0, y = 0, z = 0; double invSigma = 1.0/self->GetSigma(); derivative[0][0] = derivative[0][1] = derivative[0][2] = 0; derivative[1][0] = derivative[1][1] = derivative[1][2] = 0; derivative[2][0] = derivative[2][1] = derivative[2][2] = 0; vtkPoints *sourceLandmarks = self->GetSourceLandmarks(); // do the nonlinear stuff for(vtkIdType i = 0; i < N; i++) { sourceLandmarks->GetPoint(i,p); dx = point[0]-p[0]; dy = point[1]-p[1]; dz = point[2]-p[2]; r = sqrt(dx*dx + dy*dy + dz*dz); // get both U and its derivative and do the sigma-mangling U = 0; f = 0; if (r != 0) { U = phi(r*invSigma,f); f *= invSigma/r; } Ux = f*dx; Uy = f*dy; Uz = f*dz; x += U*W[i][0]; y += U*W[i][1]; z += U*W[i][2]; derivative[0][0] += Ux*W[i][0]; derivative[0][1] += Uy*W[i][0]; derivative[0][2] += Uz*W[i][0]; derivative[1][0] += Ux*W[i][1]; derivative[1][1] += Uy*W[i][1]; derivative[1][2] += Uz*W[i][1]; derivative[2][0] += Ux*W[i][2]; derivative[2][1] += Uy*W[i][2]; derivative[2][2] += Uz*W[i][2]; } // finish with the affine transformation x += C[0] + point[0]*A[0][0] + point[1]*A[1][0] + point[2]*A[2][0]; y += C[1] + point[0]*A[0][1] + point[1]*A[1][1] + point[2]*A[2][1]; z += C[2] + point[0]*A[0][2] + point[1]*A[1][2] + point[2]*A[2][2]; output[0] = x; output[1] = y; output[2] = z; derivative[0][0] += A[0][0]; derivative[0][1] += A[1][0]; derivative[0][2] += A[2][0]; derivative[1][0] += A[0][1]; derivative[1][1] += A[1][1]; derivative[1][2] += A[2][1]; derivative[2][0] += A[0][2]; derivative[2][1] += A[1][2]; derivative[2][2] += A[2][2]; } void vtkThinPlateSplineTransform::ForwardTransformDerivative( const double point[3], double output[3], double derivative[3][3]) { vtkThinPlateSplineForwardTransformDerivative(this, this->MatrixW, this->NumberOfPoints, this->BasisDerivative, point, output, derivative); } void vtkThinPlateSplineTransform::ForwardTransformDerivative( const float point[3], float output[3], float derivative[3][3]) { vtkThinPlateSplineForwardTransformDerivative(this, this->MatrixW, this->NumberOfPoints, this->BasisDerivative, point, output, derivative); } //------------------------------------------------------------------------ void vtkThinPlateSplineTransform::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os,indent); os << indent << "Sigma: " << this->Sigma << "\n"; os << indent << "Basis: " << this->GetBasisAsString() << "\n"; os << indent << "Source Landmarks: " << this->SourceLandmarks << "\n"; if (this->SourceLandmarks) { this->SourceLandmarks->PrintSelf(os,indent.GetNextIndent()); } os << indent << "Target Landmarks: " << this->TargetLandmarks << "\n"; if (this->TargetLandmarks) { this->TargetLandmarks->PrintSelf(os,indent.GetNextIndent()); } } //---------------------------------------------------------------------------- vtkAbstractTransform *vtkThinPlateSplineTransform::MakeTransform() { return vtkThinPlateSplineTransform::New(); } //---------------------------------------------------------------------------- void vtkThinPlateSplineTransform::InternalDeepCopy( vtkAbstractTransform *transform) { vtkThinPlateSplineTransform *t = (vtkThinPlateSplineTransform *)transform; this->SetInverseTolerance(t->InverseTolerance); this->SetInverseIterations(t->InverseIterations); this->SetSigma(t->Sigma); this->SetBasis(t->GetBasis()); this->SetSourceLandmarks(t->SourceLandmarks); this->SetTargetLandmarks(t->TargetLandmarks); if (this->InverseFlag != t->InverseFlag) { this->InverseFlag = t->InverseFlag; this->Modified(); } } //------------------------------------------------------------------------ // a very basic radial basis function double vtkRBFr(double r) { return r; } // calculate both phi(r) its derivative wrt r double vtkRBFDRr(double r, double &dUdr) { dUdr = 1; return r; } //------------------------------------------------------------------------ // the standard 2D thin plate spline basis function double vtkRBFr2logr(double r) { if (r) { return r*r*log(r); } else { return 0; } } // calculate both phi(r) its derivative wrt r double vtkRBFDRr2logr(double r, double &dUdr) { if (r) { double tmp = log(r); dUdr = r*(1+2*tmp); return r*r*tmp; } else { dUdr = 0; return 0; } } //------------------------------------------------------------------------ void vtkThinPlateSplineTransform::SetBasis(int basis) { if (basis == this->Basis) { return; } switch (basis) { case VTK_RBF_CUSTOM: break; case VTK_RBF_R: this->BasisFunction = &vtkRBFr; this->BasisDerivative = &vtkRBFDRr; break; case VTK_RBF_R2LOGR: this->BasisFunction = &vtkRBFr2logr; this->BasisDerivative = &vtkRBFDRr2logr; break; default: vtkErrorMacro(<< "SetBasisFunction: Unrecognized basis function"); break; } this->Basis = basis; this->Modified(); } //------------------------------------------------------------------------ const char *vtkThinPlateSplineTransform::GetBasisAsString() { switch (this->Basis) { case VTK_RBF_CUSTOM: return "Custom"; case VTK_RBF_R: return "R"; case VTK_RBF_R2LOGR: return "R2LogR"; } return "Unknown"; }