v2.0.0
Loading...
Searching...
No Matches
mne_icp.cpp
Go to the documentation of this file.
1//=============================================================================================================
34
35//=============================================================================================================
36// INCLUDES
37//=============================================================================================================
38
39#include "mne_icp.h"
40#include <iostream>
41
44
45//=============================================================================================================
46// QT INCLUDES
47//=============================================================================================================
48
49#include <QSharedPointer>
50#include <QDebug>
51
52//=============================================================================================================
53// EIGEN INCLUDES
54//=============================================================================================================
55
56#include <Eigen/Core>
57#include <Eigen/Dense>
58#include <Eigen/Eigenvalues>
59#include <Eigen/Geometry>
60
61//=============================================================================================================
62// USED NAMESPACES
63//=============================================================================================================
64
65using namespace MNELIB;
66using namespace Eigen;
67using namespace FIFFLIB;
68
69//=============================================================================================================
70// DEFINE GLOBAL METHODS
71//=============================================================================================================
72
73bool MNELIB::performIcp(const MNEProjectToSurface::SPtr mneSurfacePoints,
74 const Eigen::MatrixXf& matPointCloud,
75 FiffCoordTrans& transFromTo,
76 float& fRMSE,
77 bool bScale,
78 int iMaxIter,
79 float fTol,
80 const VectorXf& vecWeights)
86{
87 if(matPointCloud.rows() == 0){
88 qWarning() << "[MNELIB::icp] Passed point cloud is empty.";
89 return false;
90 }
91
92 // Initialization
93 int iNP = matPointCloud.rows(); // The number of points
94 float fMSEPrev = 0.0f;
95 float fMSE = 0.0f; // The mean square error
96 float fScale = 1.0f;
97 MatrixXf matP0 = matPointCloud; // Initial Set of points
98 MatrixXf matPk = matP0; // Transformed Set of points
99 MatrixXf matYk(matPk.rows(),matPk.cols()); // Iterative closest points on the surface
100 MatrixXf matDiff = matYk;
101 VectorXf vecSE(matDiff.rows());
102 Matrix4f matTrans; // the transformation matrix
103 VectorXi vecNearest; // Triangle of the new point
104 VectorXf vecDist; // The Distance between matX and matP
105
106 // Initial transformation - From point cloud To surface
107 FiffCoordTrans transICP = transFromTo;
108 matPk = transICP.apply_trans(matPk);
109
110 // Icp algorithm:
111 for(int iIter = 0; iIter < iMaxIter; ++iIter) {
112
113 // Step a: compute the closest point on the surface; eq 29
114 if(!mneSurfacePoints->find_closest_on_surface(matPk, iNP, matYk, vecNearest, vecDist)) {
115 qWarning() << "[MNELIB::icp] find_closest_on_surface was not successful.";
116 return false;
117 }
118
119 // Step b: compute the registration; eq 30
120 if(!fitMatchedPoints(matP0, matYk, matTrans, fScale, bScale, vecWeights)) {
121 qWarning() << "[MNELIB::icp] point cloud registration not successful";
122 }
123
124 // Step c: apply registration
125 transICP.trans = matTrans;
126 matPk = transICP.apply_trans(matP0);
127
128 // step d: compute mean-square-error and terminate if below fTol
129 vecDist = vecDist.cwiseProduct(vecDist);
130 fMSE = vecDist.sum() / iNP;
131 fRMSE = std::sqrt(fMSE);
132
133 if(std::sqrt(std::fabs(fMSE - fMSEPrev)) < fTol) {
134 transFromTo = transICP;
135 qInfo() << "[MNELIB::icp] ICP was successful and converged after " << iIter +1 << " iterations with RMSE dist: " << fRMSE * 1000 << " mm.";
136 return true;
137 }
138 fMSEPrev = fMSE;
139 qInfo() << "[MNELIB::icp] ICP iteration " << iIter + 1 << " with RMSE: " << fRMSE * 1000 << " mm.";
140 }
141 transFromTo = transICP;
142
143 qWarning() << "[MNELIB::icp] Maximum number of " << iMaxIter << " iterations exceeded with RMSE: " << fRMSE * 1000 << " mm.";
144 return true;
145}
146
147//=============================================================================================================
148
149bool MNELIB::fitMatchedPoints(const MatrixXf& matSrcPoint,
150 const MatrixXf& matDstPoint,
151 Eigen::Matrix4f& matTrans,
152 float fScale,
153 bool bScale,
154 const VectorXf& vecWeights)
162{
163 // init values
164 MatrixXf matP = matSrcPoint;
165 MatrixXf matX = matDstPoint;
166 VectorXf vecW = vecWeights;
167 VectorXf vecMuP; // column wise mean - center of mass
168 VectorXf vecMuX; // column wise mean - center of mass
169 MatrixXf matDot;
170 MatrixXf matSigmaPX; // cross-covariance
171 MatrixXf matAij; // Anti-Symmetric matrix
172 Vector3f vecDelta; // column vector, elements of matAij
173 Matrix4f matQ = Matrix4f::Identity(4,4);
174 Matrix3f matScale = Matrix3f::Identity(3,3); // scaling matrix
175 Matrix3f matRot = Matrix3f::Identity(3,3);
176 Vector3f vecTrans;
177 float fTrace = 0.0;
178 fScale = 1.0;
179
180 // test size of point clouds
181 if(matSrcPoint.size() != matDstPoint.size()) {
182 qWarning() << "[MNELIB::fitMatchedPoints] Point clouds do not match.";
183 return false;
184 }
185
186 // get center of mass
187 if(vecWeights.isZero()) {
188 vecMuP = matP.colwise().mean(); // eq 23
189 vecMuX = matX.colwise().mean();
190 matDot = matP.transpose() * matX;
191 matDot = matDot / matP.rows();
192 } else {
193 vecW = vecWeights / vecWeights.sum();
194 vecMuP = vecW.transpose() * matP;
195 vecMuX = vecW.transpose() * matX;
196
197 MatrixXf matXWeighted = matX;
198 for(int i = 0; i < (vecW.size()); ++i) {
199 matXWeighted.row(i) = matXWeighted.row(i) * vecW(i);
200 }
201 matDot = matP.transpose() * (matXWeighted);
202 }
203
204 // get cross-covariance
205 matSigmaPX = matDot - (vecMuP * vecMuX.transpose()); // eq 24
206 matAij = matSigmaPX - matSigmaPX.transpose();
207 vecDelta(0) = matAij(1,2); vecDelta(1) = matAij(2,0); vecDelta(2) = matAij(0,1);
208 fTrace = matSigmaPX.trace();
209 matQ(0,0) = fTrace; // eq 25
210 matQ.block(0,1,1,3) = vecDelta.transpose();
211 matQ.block(1,0,3,1) = vecDelta;
212 matQ.block(1,1,3,3) = matSigmaPX + matSigmaPX.transpose() - fTrace * MatrixXf::Identity(3,3);
213
214 // unit eigenvector coresponding to maximum eigenvalue of matQ is selected as optimal rotation quaterions q0,q1,q2,q3
215 SelfAdjointEigenSolver<MatrixXf> es(matQ);
216 Vector4f vecEigVec = es.eigenvectors().col(matQ.cols()-1); // only take last Eigen-Vector since this corresponds to the maximum Eigenvalue
217
218 // quatRot(w,x,y,z)
219 Quaternionf quatRot(vecEigVec(0),vecEigVec(1),vecEigVec(2),vecEigVec(3));
220 quatRot.normalize();
221 matRot = quatRot.matrix();
222
223 // get scaling factor and matrix
224 if(bScale) {
225 MatrixXf matDevX = matX.rowwise() - vecMuX.transpose();
226 MatrixXf matDevP = matP.rowwise() - vecMuP.transpose();
227 matDevX = matDevX.cwiseProduct(matDevX);
228 matDevP = matDevP.cwiseProduct(matDevP);
229
230 if(!vecWeights.isZero()) {
231 for(int i = 0; i < (vecW.size()); ++i) {
232 matDevX.row(i) = matDevX.row(i) * vecW(i);
233 matDevP.row(i) = matDevP.row(i) * vecW(i);
234 }
235 }
236 // get scaling factor and set scaling matrix
237 fScale = std::sqrt(matDevX.sum() / matDevP.sum());
238 matScale *= fScale;
239 }
240
241 // get translation and Rotation
242 vecTrans = vecMuX - fScale * matRot * vecMuP;
243 matRot *= matScale;
244
245 matTrans.block<3,3>(0,0) = matRot;
246 matTrans.block<3,1>(0,3) = vecTrans;
247 matTrans(3,3) = 1.0f;
248 matTrans.block<1,3>(3,0) = MatrixXf::Zero(1,3);
249 return true;
250}
251
252//=========================================================================================================
253
254bool MNELIB::discard3DPointOutliers(const QSharedPointer<MNELIB::MNEProjectToSurface> mneSurfacePoints,
255 const MatrixXf& matPointCloud,
256 const FiffCoordTrans& transFromTo,
257 VectorXi& vecTake,
258 MatrixXf& matTakePoint,
259 float fMaxDist)
260{
261 // Initialization
262 int iNP = matPointCloud.rows(); // The number of points
263 MatrixXf matP = matPointCloud; // Initial Set of points
264 MatrixXf matYk(matPointCloud.rows(),matPointCloud.cols()); // Iterative losest points on the surface
265 VectorXi vecNearest; // Triangle of the new point
266 VectorXf vecDist; // The Distance between matX and matP
267
268 // Initial transformation - From point cloud To surface
269 matP = transFromTo.apply_trans(matP);
270
271 int iDiscarded = 0;
272
273 // discard outliers if necessary
274 if(fMaxDist > 0.0) {
275 if(!mneSurfacePoints->find_closest_on_surface(matP, iNP, matYk, vecNearest, vecDist)) {
276 qWarning() << "[MNELIB::icp] find_closest_on_surface was not successful.";
277 return false;
278 }
279
280 for(int i = 0; i < vecDist.size(); ++i) {
281 if(std::fabs(vecDist(i)) < fMaxDist) {
282 vecTake.conservativeResize(vecTake.size()+1);
283 vecTake(vecTake.size()-1) = i;
284 matTakePoint.conservativeResize(matTakePoint.rows()+1,3);
285 matTakePoint.row(matTakePoint.rows()-1) = matPointCloud.row(i);
286 } else {
287 iDiscarded++;
288 }
289 }
290 }
291 qInfo() << "[MNELIB::discardOutliers] " << iDiscarded << "digitizers discarded.";
292 return true;
293}
294
295//=============================================================================================================
296// DEFINE MEMBER METHODS
297//=============================================================================================================
FiffCoordTrans class declaration.
ICP declarations.
Core MNE data structures (source spaces, source estimates, hemispheres).
bool performIcp(const QSharedPointer< MNELIB::MNEProjectToSurface > mneSurfacePoints, const Eigen::MatrixXf &matPointCloud, FIFFLIB::FiffCoordTrans &transFromTo, float &fRMSE, bool bScale=false, int iMaxIter=20, float fTol=0.001, const Eigen::VectorXf &vecWeights=vecDefaultWeights)
bool fitMatchedPoints(const Eigen::MatrixXf &matSrcPoint, const Eigen::MatrixXf &matDstPoint, Eigen::Matrix4f &matTrans, float fScale=1.0, bool bScale=false, const Eigen::VectorXf &vecWeights=vecDefaultWeights)
bool discard3DPointOutliers(const QSharedPointer< MNELIB::MNEProjectToSurface > mneSurfacePoints, const Eigen::MatrixXf &matPointCloud, const FIFFLIB::FiffCoordTrans &transFromTo, Eigen::VectorXi &vecTake, Eigen::MatrixXf &matTakePoint, float fMaxDist=0.0)
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
Coordinate transformation description.
Eigen::MatrixX3f apply_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > trans
QSharedPointer< MNEProjectToSurface > SPtr