00001 /* 00002 Estimates the parameters of a 3D sphere from a set of points lying on 00003 the surface. 00004 00005 Copyright (C) 2010 - 2014 Christoph Haenisch 00006 00007 Chair of Medical Engineering (mediTEC) 00008 RWTH Aachen University 00009 Pauwelsstr. 20 00010 52074 Aachen 00011 Germany 00012 00013 Version 0.3.0 (2012-01-25) 00014 */ 00015 00021 #ifndef FITSPHERE_HPP_6184653510 00022 #define FITSPHERE_HPP_6184653510 00023 00024 00025 #include <cmath> 00026 #include <Eigen/Core> 00027 #include <Eigen/Dense> 00028 #include <Eigen/StdVector> 00029 #include <Eigen/SVD> 00030 #include <vector> 00031 00032 #include "ErrorObj.hpp" 00033 #include "Coordinate.hpp" 00034 #include "Fit3D.hpp" 00035 00036 00037 namespace TRTK 00038 { 00039 00040 00125 template <class T> 00126 class FitSphere : public Fit3D<T> 00127 { 00128 private: 00129 typedef Fit3D<T> super; 00130 00131 public: 00132 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 00133 00134 typedef T value_type; 00135 typedef typename super::Vector3T Vector3T; 00136 typedef typename super::Vector4T Vector4T; 00137 typedef typename super::VectorXT VectorXT; 00138 typedef typename super::MatrixXT MatrixXT; 00139 00140 FitSphere(); 00141 FitSphere(const std::vector<Coordinate<T> > & points); 00142 FitSphere(const std::vector<Vector3T> & points); 00143 FitSphere(const std::vector<Vector4T, Eigen::aligned_allocator<Vector4T> > & points); 00144 00145 virtual ~FitSphere(); 00146 00147 void compute(); 00148 00149 const Coordinate<T> & getCenterPoint() const; 00150 T getDistanceTo(const Coordinate<T> & point) const; 00151 unsigned getNumberPointsRequired() const; 00152 T getRadius() const; 00153 T getRMS() const; 00154 00155 enum Error { 00156 NOT_ENOUGH_POINTS, 00157 UNKNOWN_ERROR, 00158 WRONG_POINT_SIZE 00159 }; 00160 00161 private: 00162 Coordinate<T> m_center_point; 00163 T m_radius; 00164 }; 00165 00166 00172 template <class T> 00173 FitSphere<T>::FitSphere() : 00174 m_center_point(0, 0, 0), 00175 m_radius(0) 00176 { 00177 } 00178 00179 00188 template <class T> 00189 FitSphere<T>::FitSphere(const std::vector<Coordinate<T> > & points) : 00190 m_center_point(0, 0, 0), 00191 m_radius(0) 00192 { 00193 super::setPoints(points); 00194 } 00195 00196 00204 template <class T> 00205 FitSphere<T>::FitSphere(const std::vector<Vector3T> & points) : 00206 m_center_point(0, 0, 0), 00207 m_radius(0) 00208 { 00209 super::setPoints(points); 00210 } 00211 00212 00222 template <class T> 00223 FitSphere<T>::FitSphere(const std::vector<Vector4T, Eigen::aligned_allocator<Vector4T> > & points) : 00224 m_center_point(0, 0, 0), 00225 m_radius(0) 00226 { 00227 super::setPoints(points); 00228 } 00229 00230 00236 template <class T> 00237 FitSphere<T>::~FitSphere() 00238 { 00239 } 00240 00241 00255 template <class T> 00256 void FitSphere<T>::compute() 00257 { 00258 /* 00259 00260 Explanation of the algorithm: 00261 00262 A point (x, y, z) on the surface of a sphere with center point (x0, y0, zo) and 00263 radius R fullfills the following equation: 00264 00265 (x - x0)^2 + (y - y0)^2 + (z - z0)^2 = R^2 00266 00267 This can be rewritten as 00268 00269 2*x*x0 + 2*y*y0 + 2*z*z0 - x0^2 - y0^2 -z0^2 + R^2 = x^2 + y^2 + z^2 00270 00271 or written as dot product 00272 00273 [2x 2y 2z 1] * [x0 y0 z0 (R^2 - x0^2 - y0^2 - z0^2)]^T = x^2 + y^2 + z^2. 00274 00275 Now we have an equation of the form 00276 00277 A(i) * p = b(i). 00278 00279 where 00280 00281 A(i) = [2x 2y 2z 1] 00282 p = [x0 y0 z0 (R^2 - x0^2 - y0^2 - z0^2)]^T 00283 b(i) = x^2 + y^2 + z^2. 00284 00285 The index denotes, that this equation holds for a point (x(i), y(i), z(i)). Note 00286 that the vector p is constant for differing points of the same sphere. As you can 00287 see, the above equation is nothing else but a linear system. That is, in the 00288 above case A(i) is a matrix with one row only and p and b(i) are vectors, where 00289 b(i) also contains one row only. Since the above equation must hold for all 00290 points of a set of n points lying on the surface of a given sphere, we can 00291 construct a bigger matrix A and a vector b that still fullfill the above linear 00292 equation A * p = b. 00293 00294 A := [A(1) ... A(n)]^T 00295 b := [b(1) ... b(n)]^T 00296 00297 Now A is n times 4 matrix (which in general is non-square) and b a n times 1 00298 vector. As mentioned before, p is a constant vector wich contains the sought 00299 parameters. To solve the system, we just construct the pseudo inverse as follows: 00300 00301 A * p = b 00302 00303 ==> A^T * A * p = A^T * b (now A^T * A is square!) 00304 00305 ==> p = inverse(A^T * A) * A^T * b = pseudo_inverse(A) * b 00306 00307 */ 00308 00309 if (super::m_points.cols() < 4) 00310 { 00311 ErrorObj error; 00312 error.setClassName("FitSphere<T>"); 00313 error.setFunctionName("compute"); 00314 error.setErrorMessage("Not enough points to fit the sphere."); 00315 error.setErrorCode(NOT_ENOUGH_POINTS); 00316 throw error; 00317 } 00318 00319 // Construct matrix A. 00320 00321 const int number_points = super::m_points.cols(); 00322 00323 const VectorXT & x = super::m_points.row(0); 00324 const VectorXT & y = super::m_points.row(1); 00325 const VectorXT & z = super::m_points.row(2); 00326 00327 MatrixXT A(number_points, 4); 00328 00329 A.block(0, 0, number_points, 1) = 2 * x; 00330 A.block(0, 1, number_points, 1) = 2 * y; 00331 A.block(0, 2, number_points, 1) = 2 * z; 00332 A.block(0, 3, number_points, 1).setConstant(1); 00333 00334 // Construct vector b. 00335 00336 VectorXT b(number_points); 00337 00338 b = x.array().square() + y.array().square() + z.array().square(); 00339 00340 // Compute pseudo inverse. 00341 00342 MatrixXT pseudo_inverse = (A.transpose() * A).inverse() * A.transpose(); 00343 00344 // Compute vector p whose components are/contain the sought transformation entries. 00345 00346 VectorXT p = pseudo_inverse * b; // FIXME: inverse(A^T * A) * (A^T * b) is more efficient to compute 00347 00348 m_center_point[0] = p(0); // x0 00349 m_center_point[1] = p(1); // y0 00350 m_center_point[2] = p(2); // z0 00351 00352 using std::sqrt; 00353 00354 m_radius = sqrt(p(3) + p(0) * p(0) + p(1) * p(1) + p(2) * p(2)); // p(3) = R^2 - x0^2 - y0^2 - z0^2 00355 } 00356 00357 00363 template <class T> 00364 const Coordinate<T> & FitSphere<T>::getCenterPoint() const 00365 { 00366 return m_center_point; 00367 } 00368 00369 00377 template <class T> 00378 T FitSphere<T>::getDistanceTo(const Coordinate<T> & point) const 00379 { 00380 assert(point.size() == 3); 00381 00382 using std::abs; 00383 return abs((point - m_center_point).norm() - m_radius); 00384 } 00385 00386 00387 template <class T> 00388 inline unsigned FitSphere<T>::getNumberPointsRequired() const 00389 { 00390 return 4; 00391 } 00392 00393 00399 template <class T> 00400 T FitSphere<T>::getRadius() const 00401 { 00402 return m_radius; 00403 } 00404 00405 00411 template <class T> 00412 T FitSphere<T>::getRMS() const 00413 { 00414 const int number_of_points = super::m_points.cols(); 00415 00416 T sum_of_squared_errors = 0.0; 00417 00418 for (int i = 0; i < number_of_points; ++i) 00419 { 00420 // Compute the distance from the current point to the spehere and add it up. 00421 00422 Coordinate<T> point(super::m_points.col(i)); 00423 T distance = (point - m_center_point).norm() - m_radius; 00424 00425 sum_of_squared_errors += distance * distance; 00426 } 00427 00428 using std::sqrt; 00429 00430 return sqrt(sum_of_squared_errors / number_of_points); 00431 } 00432 00433 00434 } // namespace TRTK 00435 00436 00437 #endif // FITSPHERE_HPP_6184653510
Documentation generated by Doxygen