1 /*
2  * Medical Image Registration ToolKit (MIRTK)
3  *
4  * Copyright 2016 Imperial College London
5  * Copyright 2016 Andreas Schuh
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 
20 #include "mirtk/Polynomial.h"
21 
22 #include "mirtk/Assert.h"
23 #include "mirtk/Math.h"
24 #include "mirtk/Eigen.h"
25 
26 #include "Eigen/QR"
27 
28 
29 namespace mirtk {
30 
31 
32 // =============================================================================
33 // Auxiliaries
34 // =============================================================================
35 
36 // -----------------------------------------------------------------------------
37 // build the exponent array recursively
BuildCompleteModel(int p,int order)38 static Matrix BuildCompleteModel(int p, int order)
39 {
40   Matrix m;
41   if (p > 0) {
42     if (order <= 0) {
43       m.Initialize(1, p);
44     } else if (p == 1) {
45       m.Initialize(order + 1, 1);
46       for (int i = 0; i <= order; ++i) {
47         m(i, 0) = order - i;
48       }
49     } else {
50       Matrix m2;
51       for (int i = order; i >= 0; --i) {
52         const int r0 = m.Rows(); // before Resize
53         m2 = BuildCompleteModel(p - 1, order - i);
54         m.Resize(r0 + m2.Rows(), p);
55         for (int r = 0; r < m2.Rows(); ++r) {
56           m(r0 + r, 0) = i;
57           for (int c = 0; c < m2.Cols(); ++c) {
58             m(r0 + r, c + 1) = m2(r, c);
59           }
60         }
61       }
62     }
63   }
64   return m;
65 }
66 
67 // =============================================================================
68 // Construction/Destruction
69 // =============================================================================
70 
71 // -----------------------------------------------------------------------------
Polynomial(int order)72 Polynomial::Polynomial(int order)
73 :
74   _Order(order),
75   _Dimension(0)
76 {
77 }
78 
79 // -----------------------------------------------------------------------------
Polynomial(int p,int order,const Vector & coeff)80 Polynomial::Polynomial(int p, int order, const Vector &coeff)
81 :
82   _Order(0),
83   _Dimension(0)
84 {
85   Initialize(p, order);
86   if (coeff.Rows() > 0) Coefficients(coeff);
87 }
88 
89 // -----------------------------------------------------------------------------
CopyAttributes(const Polynomial & other)90 void Polynomial::CopyAttributes(const Polynomial &other)
91 {
92   _Order        = other._Order;
93   _Dimension    = other._Dimension;
94   _ModelTerms   = other._ModelTerms;
95   _Coefficients = other._Coefficients;
96   _Status       = other._Status;
97 }
98 
99 // -----------------------------------------------------------------------------
Polynomial(const Polynomial & other)100 Polynomial::Polynomial(const Polynomial &other)
101 {
102   CopyAttributes(other);
103 }
104 
105 // -----------------------------------------------------------------------------
operator =(const Polynomial & other)106 Polynomial &Polynomial::operator =(const Polynomial &other)
107 {
108   if (this != &other) {
109     Object::operator =(other);
110     CopyAttributes(other);
111   }
112   return *this;
113 }
114 
115 // -----------------------------------------------------------------------------
~Polynomial()116 Polynomial::~Polynomial()
117 {
118 }
119 
120 // -----------------------------------------------------------------------------
Initialize(int p,int order)121 int Polynomial::Initialize(int p, int order)
122 {
123   if (p <= 0) {
124     cerr << this->NameOfType() << "::Initialize: Dimension of independent variable space must be positive" << endl;
125     exit(1);
126   }
127   if ((order > 0 && order != _Order) || p != _Dimension) {
128     // Set order of polynomial model
129     if (order > 0) {
130       _Order = order;
131     } else if (_Order <= 0) {
132       cerr << this->NameOfType() << "::Initialize: Order of polynomial not set during construction" << endl;
133       exit(1);
134     }
135     _Dimension  = p;
136     _ModelTerms = BuildCompleteModel(_Dimension, _Order);
137   }
138   // Set coefficients to zero
139   _Coefficients.Initialize(NumberOfTerms());
140   // Set status of coefficients to active
141   _Status.resize(NumberOfTerms());
142   for (int i = 0; i < NumberOfTerms(); ++i) {
143     _Status[i] = Active;
144   }
145   // Return number of terms
146   return NumberOfTerms();
147 }
148 
149 // -----------------------------------------------------------------------------
SetConstantCoefficient(double value,enum Status status)150 void Polynomial::SetConstantCoefficient(double value, enum Status status)
151 {
152   for (int i = 0; i < NumberOfTerms(); ++i) {
153     if (IsConstant(i)) {
154       Coefficient(i, value);
155       Status(i, status);
156     }
157   }
158 }
159 
160 // =============================================================================
161 // Regression
162 // =============================================================================
163 
164 // -----------------------------------------------------------------------------
Fit(const Matrix & x,const Vector & y)165 double Polynomial::Fit(const Matrix &x, const Vector &y)
166 {
167   const int n  = x.Rows();
168   const int p  = x.Cols();
169   const int nt = NumberOfTerms();
170 
171   if (n <= 0) {
172     cerr << this->NameOfType() << "::Fit: No input points given!" << endl;
173     exit(1);
174   }
175   if (y.Rows() != n) {
176     cerr << this->NameOfType() << "::Fit: x and y have differing number of rows!" << endl;
177     exit(1);
178   }
179 
180   // Scale x to unit variance
181   Vector sigma(p);
182   Matrix xs(x);
183   for (int i = 0; i < p; ++i) {
184     sigma(i) = sqrt(x.ColVar(i));
185     if (sigma(i) == .0) sigma(i) = 1.0;
186     else xs.ScaleCol(i, 1.0 / sigma(i));
187   }
188 
189   // Build design matrix
190   Eigen::MatrixXd A(n, NumberOfActiveTerms());
191   Eigen::VectorXd b = VectorToEigen(y);
192   Vector scale(static_cast<int>(A.cols()));
193   double t;
194 
195   A.setOnes(), scale = 1.0;
196   for (int i = 0, c = 0; i < nt; ++i) {
197     if (Status(i) == Active) {
198       for (int j = 0; j < p;  ++j) {
199         if (_ModelTerms(i, j) != 0) {
200           for (int k = 0; k < n; ++k) {
201             A(k, c) *= pow(xs(k, j), _ModelTerms(i, j));
202           }
203           scale(c) /= pow(sigma(j), _ModelTerms(i, j));
204         }
205       }
206       ++c;
207     } else {
208       for (int k = 0; k < n; ++k) {
209         t = 1.0;
210         for (int j = 0; j < p;  ++j) {
211           t *= pow(x(k, j), _ModelTerms(i, j));
212         }
213         b(k) -= t * _Coefficients(i);
214       }
215     }
216   }
217 
218   // Estimate model using (column) pivoted QR for stability
219   Eigen::ColPivHouseholderQR<Eigen::MatrixXd> qr(A);
220   const Eigen::VectorXd coeff = qr.solve(b);
221 
222   // Recover the scaling
223   for (int i = 0, c = 0; i < NumberOfTerms(); ++i) {
224     if (Status(i) == Active) {
225       _Coefficients(i) = scale(c) * coeff[c];
226       ++c;
227     }
228   }
229 
230   // Return RMS error
231   Eigen::VectorXd ypred = A * coeff;
232   if (NumberOfPassiveTerms() > 0) {
233     ypred += VectorToEigen(y) - b;
234   }
235   double sum2 = .0, delta;
236   for (int i = 0; i < n; ++i) {
237     delta = y(i) - ypred(i);
238     sum2 += delta * delta;
239   }
240   return sqrt(sum2 / n);
241 }
242 
243 // -----------------------------------------------------------------------------
Fit(const Matrix & x,const Vector & y,const Array<int> & subset)244 double Polynomial::Fit(const Matrix &x, const Vector &y, const Array<int> &subset)
245 {
246   Matrix _x(static_cast<int>(subset.size()), x.Cols());
247   Vector _y(_x.Rows());
248   for (int i = 0; i < _x.Rows(); ++i) {
249     for (int j = 0; j < _x.Cols(); ++j) {
250       _x(i, j) = x(subset[i], j);
251     }
252     _y(i) = y(subset[i]);
253   }
254   return Fit(_x, _y);
255 }
256 
257 // -----------------------------------------------------------------------------
Fit(const Matrix & x,const Vector & y,const OrderedSet<int> & subset)258 double Polynomial::Fit(const Matrix &x, const Vector &y, const OrderedSet<int> &subset)
259 {
260   Matrix _x(static_cast<int>(subset.size()), x.Cols());
261   Vector _y(_x.Rows());
262   OrderedSet<int>::const_iterator it = subset.begin();
263   for (int i = 0; i < _x.Rows(); ++i, ++it) {
264     for (int j = 0; j < _x.Cols(); ++j) {
265       _x(i, j) = x(*it, j);
266     }
267     _y(i) = y(*it);
268   }
269   return Fit(_x, _y);
270 }
271 
272 // -----------------------------------------------------------------------------
FitSurface(const PointSet & x,const PointSet & n,double c)273 double Polynomial::FitSurface(const PointSet &x, const PointSet &n, double c)
274 {
275   mirtkAssert(x.Size() == n.Size(), "one normal for each point");
276   Matrix m(3 * x.Size(), 3);
277   Vector d(m.Rows());
278   for (int i = 0, j = 0; j < x.Size(); ++j) {
279     const Point &point  = x(j);
280     const Point &normal = n(j);
281     // point inside
282     m(i, 0) = point._x - c * normal._x;
283     m(i, 1) = point._y - c * normal._y;
284     m(i, 2) = point._z - c * normal._z;
285     d(i) = -c;
286     ++i;
287     // point on surface
288     m(i, 0) = point._x;
289     m(i, 1) = point._y;
290     m(i, 2) = point._z;
291     d(i) = .0;
292     ++i;
293     // point outside
294     m(i, 0) = point._x + c * normal._x;
295     m(i, 1) = point._y + c * normal._y;
296     m(i, 2) = point._z + c * normal._z;
297     d(i) = c;
298     ++i;
299   }
300   return Fit(m, d);
301 }
302 
303 // -----------------------------------------------------------------------------
FitSurface(const PointSet & x,const PointSet & n,const Array<int> & subset,double c)304 double Polynomial::FitSurface(const PointSet &x, const PointSet &n,
305                               const Array<int> &subset, double c)
306 {
307   mirtkAssert(x.Size() == n.Size(), "one normal for each point");
308   Matrix m(3 * static_cast<int>(subset.size()), 3);
309   Vector d(m.Rows());
310   int i = 0;
311   for (Array<int>::const_iterator it = subset.begin(); it != subset.end(); ++it) {
312     const Point &point  = x(*it);
313     const Point &normal = n(*it);
314     // point inside
315     m(i, 0) = point._x - c * normal._x;
316     m(i, 1) = point._y - c * normal._y;
317     m(i, 2) = point._z - c * normal._z;
318     d(i) = -c;
319     ++i;
320     // point on surface
321     m(i, 0) = point._x;
322     m(i, 1) = point._y;
323     m(i, 2) = point._z;
324     d(i) = .0;
325     ++i;
326     // point outside
327     m(i, 0) = point._x + c * normal._x;
328     m(i, 1) = point._y + c * normal._y;
329     m(i, 2) = point._z + c * normal._z;
330     d(i) = c;
331     ++i;
332   }
333   return Fit(m, d);
334 }
335 
336 // -----------------------------------------------------------------------------
FitSurface(const PointSet & x,const PointSet & n,const OrderedSet<int> & subset,double c)337 double Polynomial::FitSurface(const PointSet &x, const PointSet &n,
338                               const OrderedSet<int> &subset, double c)
339 {
340   mirtkAssert(x.Size() == n.Size(), "one normal for each point");
341   Matrix m(3 * static_cast<int>(subset.size()), 3);
342   Vector d(m.Rows());
343   int i = 0;
344   for (OrderedSet<int>::const_iterator it = subset.begin(); it != subset.end(); ++it) {
345     const Point &point  = x(*it);
346     const Point &normal = n(*it);
347     // point inside
348     m(i, 0) = point._x - c * normal._x;
349     m(i, 1) = point._y - c * normal._y;
350     m(i, 2) = point._z - c * normal._z;
351     d(i) = -c;
352     ++i;
353     // point on surface
354     m(i, 0) = point._x;
355     m(i, 1) = point._y;
356     m(i, 2) = point._z;
357     d(i) = .0;
358     ++i;
359     // point outside
360     m(i, 0) = point._x + c * normal._x;
361     m(i, 1) = point._y + c * normal._y;
362     m(i, 2) = point._z + c * normal._z;
363     d(i) = c;
364     ++i;
365   }
366   return Fit(m, d);
367 }
368 
369 // -----------------------------------------------------------------------------
FitSurface(const PointSet & x,const Array<int> & subset)370 double Polynomial::FitSurface(const PointSet &x, const Array<int> &subset)
371 {
372   Matrix m(static_cast<int>(subset.size()), 3);
373   Vector d(m.Rows());
374   int i = 0;
375   for (Array<int>::const_iterator it = subset.begin(); it != subset.end(); ++it) {
376     const Point &point = x(*it);
377     m(i, 0) = point._x;
378     m(i, 1) = point._y;
379     m(i, 2) = point._z;
380     d(i)    = .0;
381     ++i;
382   }
383   SetConstantCoefficient(1.0);
384   return Fit(m, d);
385 }
386 
387 // -----------------------------------------------------------------------------
FitSurface(const PointSet & x,const OrderedSet<int> & subset)388 double Polynomial::FitSurface(const PointSet &x, const OrderedSet<int> &subset)
389 {
390   Matrix m(static_cast<int>(subset.size()), 3);
391   Vector d(m.Rows());
392   int i = 0;
393   for (OrderedSet<int>::const_iterator it = subset.begin(); it != subset.end(); ++it) {
394     const Point &point = x(*it);
395     m(i, 0) = point._x;
396     m(i, 1) = point._y;
397     m(i, 2) = point._z;
398     d(i)    = .0;
399     ++i;
400   }
401   SetConstantCoefficient(1.0);
402   return Fit(m, d);
403 }
404 
405 // =============================================================================
406 // Evaluation
407 // =============================================================================
408 
409 // -----------------------------------------------------------------------------
Evaluate(const Matrix & x) const410 Vector Polynomial::Evaluate(const Matrix &x) const
411 {
412   mirtkAssert(_Dimension == x.Cols(), "independent variable dimensions must match");
413   double t;
414   Vector y(x.Rows());
415   for (int k = 0; k < x.Rows(); ++k) {
416     y(k) = .0;
417     for (int i = 0; i < _ModelTerms.Rows(); ++i) {
418       t = 1.0;
419       for (int j = 0; j < _Dimension; ++j) {
420         t *= pow(x(k, j), _ModelTerms(i, j));
421       }
422       y(k) += t * _Coefficients(i);
423     }
424   }
425   return y;
426 }
427 
428 // -----------------------------------------------------------------------------
Evaluate1stOrderDerivative(int j1,const Matrix & x) const429 Vector Polynomial::Evaluate1stOrderDerivative(int j1, const Matrix &x) const
430 {
431   mirtkAssert(_Dimension == x.Cols(), "independent variable dimensions must match");
432   double t;
433   Vector y(x.Rows());
434   for (int k = 0; k < x.Rows(); ++k) {
435     y(k) = .0;
436     for (int i = 0; i < _ModelTerms.Rows(); ++i) {
437       t = 1.0;
438       for (int j = 0; j < _Dimension; ++j) {
439         if (_ModelTerms(i, j) != .0) {
440           if (j == j1) {
441             t *= _ModelTerms(i, j) * pow(x(k, j), _ModelTerms(i, j) - 1);
442           } else {
443             t *= pow(x(k, j), _ModelTerms(i, j));
444           }
445         }
446       }
447       y(k) += t * _Coefficients(i);
448     }
449   }
450   return y;
451 }
452 
453 // -----------------------------------------------------------------------------
EvaluateGradient(const Point & p) const454 Vector3 Polynomial::EvaluateGradient(const Point &p) const
455 {
456   mirtkAssert(_Dimension == 3, "independent variable dimensions must match");
457   Vector3 g(.0), t;
458   for (int i = 0; i < _ModelTerms.Rows(); ++i) {
459     t[0] = pow(p._x, _ModelTerms(i, 0) - 1) * _ModelTerms(i, 0)
460          * pow(p._y, _ModelTerms(i, 1))
461          * pow(p._z, _ModelTerms(i, 2));
462     t[1] = pow(p._x, _ModelTerms(i, 0))
463          * pow(p._y, _ModelTerms(i, 1) - 1) * _ModelTerms(i, 1)
464          * pow(p._z, _ModelTerms(i, 2));
465     t[2] = pow(p._x, _ModelTerms(i, 0))
466          * pow(p._y, _ModelTerms(i, 1))
467          * pow(p._z, _ModelTerms(i, 2) - 1) * _ModelTerms(i, 2);
468     g += (t *= _Coefficients(i));
469   }
470   return g;
471 }
472 
473 // -----------------------------------------------------------------------------
Evaluate2ndOrderDerivative(int j1,int j2,const Matrix & x) const474 Vector Polynomial::Evaluate2ndOrderDerivative(int j1, int j2, const Matrix &x) const
475 {
476   mirtkAssert(_Dimension == x.Cols(), "independent variable dimensions must match");
477   double t;
478   Vector y(x.Rows());
479   for (int k = 0; k < x.Rows(); ++k) {
480     y(k) = .0;
481     for (int i = 0; i < _ModelTerms.Rows(); ++i) {
482       t = 1.0;
483       for (int j = 0; j < _Dimension; ++j) {
484         if (_ModelTerms(i, j) != .0) {
485           if (j == j1 || j == j2) {
486             if (j1 == j2) {
487               t *= (_ModelTerms(i, j) - 1) * _ModelTerms(i, j) * pow(x(k, j), _ModelTerms(i, j) - 2);
488             } else {
489               t *= _ModelTerms(i, j) * pow(x(k, j), _ModelTerms(i, j) - 1);
490             }
491           } else {
492             t *= pow(x(k, j), _ModelTerms(i, j));
493           }
494         }
495       }
496       y(k) += t * _Coefficients(i);
497     }
498   }
499   return y;
500 }
501 
502 // -----------------------------------------------------------------------------
EvaluateHessian(const Point & p) const503 Matrix3x3 Polynomial::EvaluateHessian(const Point &p) const
504 {
505   mirtkAssert(_Dimension == 3, "independent variable dimensions must match");
506   Matrix3x3 h;
507   for (int i = 0; i < _ModelTerms.Rows(); ++i) {
508     h[0][0] += _Coefficients(i)
509              * pow(p._x, _ModelTerms(i, 0) - 2) * _ModelTerms(i, 0) * (_ModelTerms(i, 0) - 1)
510              * pow(p._y, _ModelTerms(i, 1))
511              * pow(p._z, _ModelTerms(i, 2));
512     h[0][1] += _Coefficients(i)
513              * pow(p._x, _ModelTerms(i, 0) - 1) * _ModelTerms(i, 0)
514              * pow(p._y, _ModelTerms(i, 1) - 1) * _ModelTerms(i, 1)
515              * pow(p._z, _ModelTerms(i, 2));
516     h[0][2] += _Coefficients(i)
517              * pow(p._x, _ModelTerms(i, 0) - 1) * _ModelTerms(i, 0)
518              * pow(p._y, _ModelTerms(i, 1))
519              * pow(p._z, _ModelTerms(i, 2) - 1) * _ModelTerms(i, 2);
520     h[1][1] += _Coefficients(i)
521              * pow(p._x, _ModelTerms(i, 0))
522              * pow(p._y, _ModelTerms(i, 1) - 2) * _ModelTerms(i, 1) * (_ModelTerms(i, 1) - 1)
523              * pow(p._z, _ModelTerms(i, 2));
524     h[1][2] += _Coefficients(i)
525              * pow(p._x, _ModelTerms(i, 0))
526              * pow(p._y, _ModelTerms(i, 1) - 1) * _ModelTerms(i, 1)
527              * pow(p._z, _ModelTerms(i, 2) - 1) * _ModelTerms(i, 2);
528     h[2][2] += _Coefficients(i)
529              * pow(p._x, _ModelTerms(i, 0))
530              * pow(p._y, _ModelTerms(i, 1))
531              * pow(p._z, _ModelTerms(i, 2) - 2) * _ModelTerms(i, 2) * (_ModelTerms(i, 2) - 1);
532   }
533   h[1][0] = h[0][1];
534   h[2][0] = h[0][2];
535   h[2][1] = h[1][2];
536   return h;
537 }
538 
539 // =============================================================================
540 // Implicit surface
541 // =============================================================================
542 
543 // -----------------------------------------------------------------------------
EvaluateTaubinDistance(const Point & p) const544 double Polynomial::EvaluateTaubinDistance(const Point &p) const
545 {
546   mirtkAssert(_Dimension == 3, "independent variable dimensions must match");
547   return Evaluate(p) / EvaluateGradient(p).Length();
548 }
549 
550 // -----------------------------------------------------------------------------
EvaluateGaussianCurvature(const Point & p) const551 double Polynomial::EvaluateGaussianCurvature(const Point &p) const
552 {
553   mirtkAssert(_Dimension == 3, "independent variable dimensions must match");
554   Vector3   g = EvaluateGradient(p);
555   Matrix3x3 h = EvaluateHessian(p).Adjoint();
556   const double l2 = g.SquaredLength();
557   return g.Dot(h * g) / (l2 * l2);
558 }
559 
560 // -----------------------------------------------------------------------------
EvaluateMeanCurvature(const Point & p) const561 double Polynomial::EvaluateMeanCurvature(const Point &p) const
562 {
563   mirtkAssert(_Dimension == 3, "independent variable dimensions must match");
564   Vector3   g = EvaluateGradient(p);
565   Matrix3x3 h = EvaluateHessian(p);
566   const double l2 = g.SquaredLength();
567   return (g.Dot(h * g) - l2 * h.Trace()) / (2.0 * sqrt(l2) * l2);
568 }
569 
570 // =============================================================================
571 // Debugging
572 // =============================================================================
573 
574 // -----------------------------------------------------------------------------
Print(ostream & os,Indent indent) const575 ostream &Polynomial::Print(ostream &os, Indent indent) const
576 {
577   ostringstream line;
578   line << indent << "p(x) =";
579   if (NumberOfTerms() == 0) {
580     line << " undefined";
581   } else {
582     const int   varcount           = 3;
583     const char *varnames[varcount] = {"x", "y", "z"};
584     for (int i = 0; i < NumberOfTerms(); ++i) {
585       if (i > 0) {
586         if (line.str().size() > 70) {
587           os << line.str() << "\n";
588           line.str("");
589           line << indent << "     +";
590         } else {
591           line << " +";
592         }
593       }
594       line << " " << _Coefficients(i);
595       for (int j = 0; j < _Dimension; ++j) {
596         if (_ModelTerms(i, j) == .0) continue;
597         line << " ";
598         if (_Dimension <= varcount) {
599           line << varnames[j];
600         } else {
601           line << "x" << ToString(j);
602         }
603         if (_ModelTerms(i, j) != 1.0) {
604           line << "^" << ToString(_ModelTerms(i, j));
605         }
606       }
607     }
608   }
609   os << line.str() << "\n";
610   return os;
611 }
612 
613 
614 } // namespace mirtk
615