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