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