51#define _USE_MATH_DEFINES
74#include <QtConcurrent/QtConcurrent>
96 : m_sensors(sensorSet),
106 if(m_sensors != sensorSet) {
107 m_sensors = sensorSet;
114 const MatrixXd& matProjectors,
116 const MatrixXd& matCoilsHead,
119 fit(matProjectedData,matProjectors,hpiModelParameters,matCoilsHead,
false,hpiFitResult);
125 const MatrixXd& matProjectors,
127 const MatrixXd& matCoilsHead,
128 const bool bOrderFrequencies,
131 if(matProjectedData.rows() != matProjectors.rows()) {
132 std::cout<<
"InvHpiFit::fit - Projector and data dimensions do not match. Returning."<<std::endl;
134 }
else if(hpiModelParameters.
iNHpiCoils()!= matCoilsHead.rows()) {
135 std::cout<<
"InvHpiFit::fit - Number of coils and hpi digitizers do not match. Returning."<<std::endl;
137 }
else if(matProjectedData.rows()==0 || matProjectors.rows()==0) {
138 std::cout<<
"InvHpiFit::fit - No data or Projectors passed. Returning."<<std::endl;
140 }
else if(m_sensors.ncoils() != matProjectedData.rows()) {
141 std::cout<<
"InvHpiFit::fit - Number of channels in sensors and data do not match. Returning."<<std::endl;
145 const MatrixXd matAmplitudes = computeAmplitudes(matProjectedData,
148 const MatrixXd matCoilsSeed = computeSeedPoints(matAmplitudes,
153 CoilParam fittedCoilParams = dipfit(matCoilsSeed,
161 if(bOrderFrequencies) {
162 const std::vector<int> vecOrder = findCoilOrder(fittedCoilParams.
pos,
165 fittedCoilParams.
pos = order(vecOrder,fittedCoilParams.
pos);
171 hpiFitResult.
fittedCoils = getFittedPointSet(fittedCoilParams.
pos);
173 hpiFitResult.
devHeadTrans = computeDeviceHeadTransformation(fittedCoilParams.
pos,
183Eigen::MatrixXd InvHpiFit::computeAmplitudes(
const Eigen::MatrixXd& matProjectedData,
187 MatrixXd matTopo = m_signalModel.fitData(hpiModelParameters,matProjectedData);
188 matTopo.transposeInPlace();
191 const int iNumCoils = hpiModelParameters.
iNHpiCoils();
193 MatrixXd matAmpSine(matProjectedData.cols(), iNumCoils);
194 MatrixXd matAmpCosine(matProjectedData.cols(), iNumCoils);
196 matAmpSine = matTopo.leftCols(iNumCoils);
197 matAmpCosine = matTopo.middleCols(iNumCoils,iNumCoils);
200 for(
int j = 0; j < iNumCoils; ++j) {
203 fNS = matAmpSine.col(j).array().square().sum();
204 fNC = matAmpCosine.col(j).array().square().sum();
206 matAmpSine.col(j) = matAmpCosine.col(j);
215Eigen::MatrixXd InvHpiFit::computeSeedPoints(
const Eigen::MatrixXd& matAmplitudes,
216 const FIFFLIB::FiffCoordTrans& transDevHead,
217 const QVector<double>& vecError,
218 const Eigen::MatrixXd& matCoilsHead)
220 const int iNumCoils = matCoilsHead.rows();
221 MatrixXd matCoilsSeed = MatrixXd::Zero(iNumCoils,3);
223 const double dError = std::accumulate(vecError.begin(), vecError.end(), .0) / vecError.size();
225 if(transDevHead.
trans != MatrixXd::Identity(4,4).cast<
float>() && dError < 0.010) {
227 matCoilsSeed = transDevHead.
apply_inverse_trans(matCoilsHead.cast<
float>()).cast<
double>();
230 VectorXi vecChIdcs(iNumCoils);
232 for (
int j = 0; j < iNumCoils; j++) {
234 VectorXd::Index indMax;
235 matAmplitudes.col(j).maxCoeff(&indMax);
236 if(indMax < m_sensors.ncoils()) {
239 vecChIdcs(j) = iChIdx;
242 for (
int j = 0; j < vecChIdcs.rows(); ++j) {
243 if(vecChIdcs(j) < m_sensors.ncoils()) {
244 Vector3d r0 = m_sensors.r0(vecChIdcs(j));
245 Vector3d ez = m_sensors.ez(vecChIdcs(j));
246 matCoilsSeed.row(j) = (-1 * ez * 0.03 + r0);
255CoilParam InvHpiFit::dipfit(
const MatrixXd matCoilsSeed,
257 const MatrixXd& matData,
259 const MatrixXd& matProjectors,
260 const int iMaxIterations,
261 const float fAbortError)
265 QList<InvHpiFitData> lCoilData;
267 for(qint32 i = 0; i < iNumCoils; ++i) {
268 InvHpiFitData coilData;
269 coilData.
m_coilPos = matCoilsSeed.row(i);
276 lCoilData.append(coilData);
279 CoilParam coil(iNumCoils);
281 if(!lCoilData.isEmpty()) {
288 QFuture<void> future = QtConcurrent::map(lCoilData,
290 future.waitForFinished();
293 for(qint32 i = 0; i < lCoilData.size(); ++i) {
294 coil.pos.row(i) = lCoilData.at(i).m_coilPos;
295 coil.mom = lCoilData.at(i).m_errorInfo.moment.transpose();
296 coil.dpfiterror(i) = lCoilData.at(i).m_errorInfo.error;
297 coil.dpfitnumitr(i) = lCoilData.at(i).m_errorInfo.numIterations;
307std::vector<int> InvHpiFit::findCoilOrder(
const MatrixXd& matCoilsDev,
308 const MatrixXd& matCoilsHead)
311 MatrixXd matCoilTemp = matCoilsDev;
312 const int iNumCoils = matCoilsDev.rows();
314 std::vector<int> vecOrder(iNumCoils);
315 std::iota(vecOrder.begin(), vecOrder.end(), 0);;
318 const double dErrorMin = 0.010;
319 double dErrorActual = 0.0;
320 double dErrorBest = dErrorMin;
322 MatrixXd matTrans(4,4);
323 std::vector<int> vecOrderBest = vecOrder;
325 bool bSuccess =
false;
328 for(
int i = 0; i < iNumCoils; i++) {
329 matCoilTemp.row(i) = matCoilsDev.row(vecOrder[i]);
331 matTrans = computeTransformation(matCoilsHead,matCoilTemp);
332 dErrorActual = objectTrans(matCoilsHead,matCoilTemp,matTrans);
333 if(dErrorActual < dErrorMin && dErrorActual < dErrorBest) {
335 dErrorBest = dErrorActual;
336 vecOrderBest = vecOrder;
339 }
while (std::next_permutation(vecOrder.begin(), vecOrder.end()));
345double InvHpiFit::objectTrans(
const MatrixXd& matHeadCoil,
346 const MatrixXd& matCoil,
347 const MatrixXd& matTrans)
350 const int iNumCoils = matHeadCoil.rows();
351 MatrixXd matTemp = matCoil;
354 matTemp.conservativeResize(matCoil.rows(),matCoil.cols()+1);
355 matTemp.block(0,3,iNumCoils,1).setOnes();
356 matTemp.transposeInPlace();
359 MatrixXd matTestPos = matTrans * matTemp;
362 MatrixXd matDiff = matTestPos.block(0,0,3,iNumCoils) - matHeadCoil.transpose();
363 VectorXd vecError = matDiff.colwise().norm();
366 double dError = matDiff.colwise().norm().mean();;
373Eigen::MatrixXd InvHpiFit::order(
const std::vector<int>& vecOrder,
374 const Eigen::MatrixXd& matToOrder)
376 const int iNumCoils = vecOrder.size();
377 MatrixXd matToOrderTemp = matToOrder;
379 for(
int i = 0; i < iNumCoils; i++) {
380 matToOrderTemp.row(i) = matToOrder.row(vecOrder[i]);
382 return matToOrderTemp;
387QVector<int> InvHpiFit::order(
const std::vector<int>& vecOrder,
388 const QVector<int>& vecToOrder)
390 const int iNumCoils = vecOrder.size();
391 QVector<int> vecToOrderTemp = vecToOrder;
393 for(
int i = 0; i < iNumCoils; i++) {
394 vecToOrderTemp[i] = vecToOrder[vecOrder[i]];
396 return vecToOrderTemp;
401Eigen::VectorXd InvHpiFit::computeGoF(
const Eigen::VectorXd& vecDipFitError)
403 VectorXd vecGoF(vecDipFitError.size());
404 for(
int i = 0; i < vecDipFitError.size(); ++i) {
405 vecGoF(i) = 1 - vecDipFitError(i);
412FIFFLIB::FiffCoordTrans InvHpiFit::computeDeviceHeadTransformation(
const Eigen::MatrixXd& matCoilsDev,
413 const Eigen::MatrixXd& matCoilsHead)
415 const MatrixXd matTrans = computeTransformation(matCoilsHead,matCoilsDev);
416 return FiffCoordTrans(1,4,matTrans.cast<
float>(),
true);
421Eigen::Matrix4d InvHpiFit::computeTransformation(Eigen::MatrixXd matNH, MatrixXd matBT)
423 MatrixXd matXdiff, matYdiff, matZdiff, matC, matQ;
424 Matrix4d matTransFinal = Matrix4d::Identity(4,4);
425 Matrix4d matRot = Matrix4d::Zero(4,4);
426 Matrix4d matTrans = Matrix4d::Identity(4,4);
427 double dMeanX,dMeanY,dMeanZ,dNormf;
429 for(
int i = 0; i < 15; ++i) {
431 matXdiff = matNH.col(0) - matBT.col(0);
432 matYdiff = matNH.col(1) - matBT.col(1);
433 matZdiff = matNH.col(2) - matBT.col(2);
435 dMeanX = matXdiff.mean();
436 dMeanY = matYdiff.mean();
437 dMeanZ = matZdiff.mean();
440 for (
int j = 0; j < matBT.rows(); ++j) {
441 matBT(j,0) = matBT(j,0) + dMeanX;
442 matBT(j,1) = matBT(j,1) + dMeanY;
443 matBT(j,2) = matBT(j,2) + dMeanZ;
447 matC = matBT.transpose() * matNH;
449 JacobiSVD< MatrixXd > svd(matC ,Eigen::ComputeThinU | ComputeThinV);
451 matQ = svd.matrixU() * svd.matrixV().transpose();
454 if(matQ.determinant() < 0) {
455 matQ(0,2) = matQ(0,2) * -1;
456 matQ(1,2) = matQ(1,2) * -1;
457 matQ(2,2) = matQ(2,2) * -1;
461 matBT = matBT * matQ;
464 dNormf = (matNH.transpose()-matBT.transpose()).norm();
468 for(
int j = 0; j < 3; ++j) {
469 for(
int k = 0; k < 3; ++k) {
470 matRot(j,k) = matQ(k,j);
475 matTrans(0,3) = dMeanX;
476 matTrans(1,3) = dMeanY;
477 matTrans(2,3) = dMeanZ;
482 matTransFinal = matRot * matTrans * matTransFinal;
484 return matTransFinal;
489QVector<double> InvHpiFit::computeEstimationError(
const Eigen::MatrixXd& matCoilsDev,
490 const Eigen::MatrixXd& matCoilsHead,
491 const FIFFLIB::FiffCoordTrans& transDevHead)
494 MatrixXd matTemp = matCoilsDev;
495 MatrixXd matTestPos = transDevHead.
apply_trans(matTemp.cast<
float>()).cast<
double>();
496 MatrixXd matDiffPos = matTestPos - matCoilsHead;
499 int iNumCoils = matCoilsDev.rows();
500 QVector<double> vecError(iNumCoils);
501 for(
int i = 0; i < matDiffPos.rows(); ++i) {
502 vecError[i] = matDiffPos.row(i).norm();
509FIFFLIB::FiffDigPointSet InvHpiFit::getFittedPointSet(
const Eigen::MatrixXd& matCoilsDev)
511 FiffDigPointSet fittedPointSet;
512 const int iNumCoils = matCoilsDev.rows();
514 for(
int i = 0; i < iNumCoils; ++i) {
515 FiffDigPoint digPoint;
518 digPoint.
r[0] = matCoilsDev(i,0);
519 digPoint.
r[1] = matCoilsDev(i,1);
520 digPoint.
r[2] = matCoilsDev(i,2);
522 fittedPointSet << digPoint;
524 return fittedPointSet;
530 const Eigen::MatrixXf& transDevHead,
531 Eigen::MatrixXd& matPosition,
532 const Eigen::VectorXd& vecGoF,
533 const QVector<double>& vecError)
536 Matrix3f matRot = transDevHead.block(0,0,3,3);
538 Eigen::Quaternionf quatHPI(matRot);
539 double dError = std::accumulate(vecError.begin(), vecError.end(), .0) / vecError.size();
541 matPosition.conservativeResize(matPosition.rows()+1, 10);
542 matPosition(matPosition.rows()-1,0) = fTime;
543 matPosition(matPosition.rows()-1,1) = quatHPI.x();
544 matPosition(matPosition.rows()-1,2) = quatHPI.y();
545 matPosition(matPosition.rows()-1,3) = quatHPI.z();
546 matPosition(matPosition.rows()-1,4) = transDevHead(0,3);
547 matPosition(matPosition.rows()-1,5) = transDevHead(1,3);
548 matPosition(matPosition.rows()-1,6) = transDevHead(2,3);
549 matPosition(matPosition.rows()-1,7) = vecGoF.mean();
550 matPosition(matPosition.rows()-1,8) = dError;
551 matPosition(matPosition.rows()-1,9) = 0;
557 const MatrixX4f& mDevHeadDest,
558 const float& fThreshRot,
559 const float& fThreshTrans)
563 Matrix3f mRot = mDevHeadT.block(0,0,3,3);
564 Matrix3f mRotDest = mDevHeadDest.block(0,0,3,3);
566 VectorXf vTrans = mDevHeadT.block(0,3,3,1);
567 VectorXf vTransDest = mDevHeadDest.block(0,3,3,1);
569 Quaternionf quat(mRot);
570 Quaternionf quatNew(mRotDest);
573 float fAngle = quat.angularDistance(quatNew);
574 fAngle = fAngle * 180 /
M_PI;
577 float fMove = (vTrans-vTransDest).norm();
580 if(fMove > fThreshTrans) {
581 qInfo() <<
"Large movement: " << fMove*1000 <<
"mm";
583 }
else if (fAngle > fThreshRot) {
584 qInfo() <<
"Large rotation: " << fAngle <<
"degree";
InvHpiFitData class declaration.
InvSensorSet class declaration.
InvHpiModelParameters class declaration.
InvHpiFit class declaration.
InvSignalModel class declaration.
InvHpiDataUpdater class declaration.
FiffDigPointSet class declaration.
FiffCov class declaration.
IOUtils class declaration.
FwdCoilSet class declaration.
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
Forward modelling (BEM, MEG/EEG lead fields).
Eigen::MatrixX3f apply_inverse_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
Eigen::MatrixX3f apply_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > trans
Estimated dipole parameters (position, moment, goodness-of-fit) for a single HPI coil.
Eigen::VectorXd dpfiterror
Complete HPI fit output: per-coil dipole parameters, head-to-device transform, fit error,...
FIFFLIB::FiffCoordTrans devHeadTrans
QVector< double > errorDistances
FIFFLIB::FiffDigPointSet fittedCoils
static bool compareTransformation(const Eigen::MatrixX4f &mDevHeadT, const Eigen::MatrixX4f &mDevHeadDest, const float &fThreshRot, const float &fThreshTrans)
void fit(const Eigen::MatrixXd &matProjectedData, const Eigen::MatrixXd &matProjectors, const InvHpiModelParameters &hpiModelParameters, const Eigen::MatrixXd &matCoilsHead, HpiFitResult &hpiFitResult)
void checkForUpdate(const InvSensorSet &sensorSet)
static void storeHeadPosition(float fTime, const Eigen::MatrixXf &matTransDevHead, Eigen::MatrixXd &matPosition, const Eigen::VectorXd &vecGoF, const QVector< double > &vecError)
Eigen::RowVectorXd m_sensorData
Eigen::MatrixXd m_matProjector
Eigen::MatrixXd m_coilPos
void doDipfitConcurrent()
Configuration parameters for the HPI signal model (line frequency, coil frequencies,...
QVector< int > vecHpiFreqs() const
Stores MEG sensor geometry (positions, orientations, weights, coil count) for a single sensor type.
Generates the forward sinusoidal model matrix for HPI coil signals at known drive frequencies.