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 // Step a: compute the closest point on the surface; eq 29
95 if (!mneSurfacePoints->find_closest_on_surface(matPk, iNP, matYk, vecNearest, vecDist)) {
96 qWarning() << "[MNELIB::icp] find_closest_on_surface was not successful.";
97 return false;
98 }
99
100 // Step b: compute the registration; eq 30
101 if (!fitMatchedPoints(matP0, matYk, matTrans, fScale, bScale, vecWeights)) {
102 qWarning() << "[MNELIB::icp] point cloud registration not successful";
103 }
104
105 // Step c: apply registration
106 transICP.trans = matTrans;
107 matPk = transICP.apply_trans(matP0);
108
109 // step d: compute mean-square-error and terminate if below fTol
110 vecDist = vecDist.cwiseProduct(vecDist);
111 fMSE = vecDist.sum() / iNP;
112 fRMSE = std::sqrt(fMSE);
113
114 if (std::sqrt(std::fabs(fMSE - fMSEPrev)) < fTol) {
115 transFromTo = transICP;
116 qInfo() << "[MNELIB::icp] ICP was successful and converged after " << iIter + 1 << " iterations with RMSE dist: " << fRMSE * 1000 << " mm.";
117 return true;
118 }
119 fMSEPrev = fMSE;
120 qInfo() << "[MNELIB::icp] ICP iteration " << iIter + 1 << " with RMSE: " << fRMSE * 1000 << " mm.";
121 }
122 transFromTo = transICP;
123
124 qWarning() << "[MNELIB::icp] Maximum number of " << iMaxIter << " iterations exceeded with RMSE: " << fRMSE * 1000 << " mm.";
125 return true;
126}
127
128//=============================================================================================================
129
130bool MNELIB::fitMatchedPoints(const MatrixXf& matSrcPoint,
131 const MatrixXf& matDstPoint,
132 Eigen::Matrix4f& matTrans,
133 float fScale,
134 bool bScale,
135 const VectorXf& vecWeights)
143{
144 // init values
145 MatrixXf matP = matSrcPoint;
146 MatrixXf matX = matDstPoint;
147 VectorXf vecW = vecWeights;
148 VectorXf vecMuP; // column wise mean - center of mass
149 VectorXf vecMuX; // column wise mean - center of mass
150 MatrixXf matDot;
151 MatrixXf matSigmaPX; // cross-covariance
152 MatrixXf matAij; // Anti-Symmetric matrix
153 Vector3f vecDelta; // column vector, elements of matAij
154 Matrix4f matQ = Matrix4f::Identity(4, 4);
155 Matrix3f matScale = Matrix3f::Identity(3, 3); // scaling matrix
156 Matrix3f matRot = Matrix3f::Identity(3, 3);
157 Vector3f vecTrans;
158 float fTrace = 0.0;
159 fScale = 1.0;
160
161 // test size of point clouds
162 if (matSrcPoint.size() != matDstPoint.size()) {
163 qWarning() << "[MNELIB::fitMatchedPoints] Point clouds do not match.";
164 return false;
165 }
166
167 // get center of mass
168 if (vecWeights.isZero()) {
169 vecMuP = matP.colwise().mean(); // eq 23
170 vecMuX = matX.colwise().mean();
171 matDot = matP.transpose() * matX;
172 matDot = matDot / matP.rows();
173 } else {
174 vecW = vecWeights / vecWeights.sum();
175 vecMuP = vecW.transpose() * matP;
176 vecMuX = vecW.transpose() * matX;
177
178 MatrixXf matXWeighted = matX;
179 for (int i = 0; i < (vecW.size()); ++i) {
180 matXWeighted.row(i) = matXWeighted.row(i) * vecW(i);
181 }
182 matDot = matP.transpose() * (matXWeighted);
183 }
184
185 // get cross-covariance
186 matSigmaPX = matDot - (vecMuP * vecMuX.transpose()); // eq 24
187 matAij = matSigmaPX - matSigmaPX.transpose();
188 vecDelta(0) = matAij(1, 2);
189 vecDelta(1) = matAij(2, 0);
190 vecDelta(2) = matAij(0, 1);
191 fTrace = matSigmaPX.trace();
192 matQ(0, 0) = fTrace; // eq 25
193 matQ.block(0, 1, 1, 3) = vecDelta.transpose();
194 matQ.block(1, 0, 3, 1) = vecDelta;
195 matQ.block(1, 1, 3, 3) = matSigmaPX + matSigmaPX.transpose() - fTrace * MatrixXf::Identity(3, 3);
196
197 // unit eigenvector coresponding to maximum eigenvalue of matQ is selected as optimal rotation quaterions q0,q1,q2,q3
198 SelfAdjointEigenSolver<MatrixXf> es(matQ);
199 Vector4f vecEigVec = es.eigenvectors().col(matQ.cols() - 1); // only take last Eigen-Vector since this corresponds to the maximum Eigenvalue
200
201 // quatRot(w,x,y,z)
202 Quaternionf quatRot(vecEigVec(0), vecEigVec(1), vecEigVec(2), vecEigVec(3));
203 quatRot.normalize();
204 matRot = quatRot.matrix();
205
206 // get scaling factor and matrix
207 if (bScale) {
208 MatrixXf matDevX = matX.rowwise() - vecMuX.transpose();
209 MatrixXf matDevP = matP.rowwise() - vecMuP.transpose();
210 matDevX = matDevX.cwiseProduct(matDevX);
211 matDevP = matDevP.cwiseProduct(matDevP);
212
213 if (!vecWeights.isZero()) {
214 for (int i = 0; i < (vecW.size()); ++i) {
215 matDevX.row(i) = matDevX.row(i) * vecW(i);
216 matDevP.row(i) = matDevP.row(i) * vecW(i);
217 }
218 }
219 // get scaling factor and set scaling matrix
220 fScale = std::sqrt(matDevX.sum() / matDevP.sum());
221 matScale *= fScale;
222 }
223
224 // get translation and Rotation
225 vecTrans = vecMuX - fScale * matRot * vecMuP;
226 matRot *= matScale;
227
228 matTrans.block<3, 3>(0, 0) = matRot;
229 matTrans.block<3, 1>(0, 3) = vecTrans;
230 matTrans(3, 3) = 1.0f;
231 matTrans.block<1, 3>(3, 0) = MatrixXf::Zero(1, 3);
232 return true;
233}
234
235//=========================================================================================================
236
237bool MNELIB::discard3DPointOutliers(const QSharedPointer<MNELIB::MNEProjectToSurface> mneSurfacePoints,
238 const MatrixXf& matPointCloud,
239 const FiffCoordTrans& transFromTo,
240 VectorXi& vecTake,
241 MatrixXf& matTakePoint,
242 float fMaxDist)
243{
244 // Initialization
245 int iNP = matPointCloud.rows(); // The number of points
246 MatrixXf matP = matPointCloud; // Initial Set of points
247 MatrixXf matYk(matPointCloud.rows(), matPointCloud.cols()); // Iterative losest points on the surface
248 VectorXi vecNearest; // Triangle of the new point
249 VectorXf vecDist; // The Distance between matX and matP
250
251 // Initial transformation - From point cloud To surface
252 matP = transFromTo.apply_trans(matP);
253
254 int iDiscarded = 0;
255
256 // discard outliers if necessary
257 if (fMaxDist > 0.0) {
258 if (!mneSurfacePoints->find_closest_on_surface(matP, iNP, matYk, vecNearest, vecDist)) {
259 qWarning() << "[MNELIB::icp] find_closest_on_surface was not successful.";
260 return false;
261 }
262
263 for (int i = 0; i < vecDist.size(); ++i) {
264 if (std::fabs(vecDist(i)) < fMaxDist) {
265 vecTake.conservativeResize(vecTake.size() + 1);
266 vecTake(vecTake.size() - 1) = i;
267 matTakePoint.conservativeResize(matTakePoint.rows() + 1, 3);
268 matTakePoint.row(matTakePoint.rows() - 1) = matPointCloud.row(i);
269 } else {
270 iDiscarded++;
271 }
272 }
273 }
274 qInfo() << "[MNELIB::discardOutliers] " << iDiscarded << "digitizers discarded.";
275 return true;
276}
277
278//=============================================================================================================
279// DEFINE MEMBER METHODS
280//=============================================================================================================
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
Iterative Closest Point alignment between MEG digitiser points and an MRI head surface.
Geometric projection of a 3D point onto the closest cortex triangle.
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