36#ifndef _USE_MATH_DEFINES
37#define _USE_MATH_DEFINES
61#include <QtConcurrent/QtConcurrent>
92 if (m_sensors != sensorSet) {
93 m_sensors = sensorSet;
100 const MatrixXd& matProjectors,
102 const MatrixXd& matCoilsHead,
105 fit(matProjectedData, matProjectors, hpiModelParameters, matCoilsHead,
false, hpiFitResult);
111 const MatrixXd& matProjectors,
113 const MatrixXd& matCoilsHead,
114 const bool bOrderFrequencies,
117 if (matProjectedData.rows() != matProjectors.rows()) {
118 std::cout <<
"InvHpiFit::fit - Projector and data dimensions do not match. Returning." << std::endl;
120 }
else if (hpiModelParameters.
iNHpiCoils() != matCoilsHead.rows()) {
121 std::cout <<
"InvHpiFit::fit - Number of coils and hpi digitizers do not match. Returning." << std::endl;
123 }
else if (matProjectedData.rows() == 0 || matProjectors.rows() == 0) {
124 std::cout <<
"InvHpiFit::fit - No data or Projectors passed. Returning." << std::endl;
126 }
else if (m_sensors.ncoils() != matProjectedData.rows()) {
127 std::cout <<
"InvHpiFit::fit - Number of channels in sensors and data do not match. Returning." << std::endl;
131 const MatrixXd matAmplitudes = computeAmplitudes(matProjectedData,
134 const MatrixXd matCoilsSeed = computeSeedPoints(matAmplitudes,
139 CoilParam fittedCoilParams = dipfit(matCoilsSeed,
147 if (bOrderFrequencies) {
148 const std::vector<int> vecOrder = findCoilOrder(fittedCoilParams.
pos,
151 fittedCoilParams.
pos = order(vecOrder, fittedCoilParams.
pos);
157 hpiFitResult.
fittedCoils = getFittedPointSet(fittedCoilParams.
pos);
159 hpiFitResult.
devHeadTrans = computeDeviceHeadTransformation(fittedCoilParams.
pos,
169Eigen::MatrixXd InvHpiFit::computeAmplitudes(
const Eigen::MatrixXd& matProjectedData,
173 MatrixXd matTopo = m_signalModel.fitData(hpiModelParameters, matProjectedData);
174 matTopo.transposeInPlace();
177 const int iNumCoils = hpiModelParameters.
iNHpiCoils();
179 MatrixXd matAmpSine(matProjectedData.cols(), iNumCoils);
180 MatrixXd matAmpCosine(matProjectedData.cols(), iNumCoils);
182 matAmpSine = matTopo.leftCols(iNumCoils);
183 matAmpCosine = matTopo.middleCols(iNumCoils, iNumCoils);
186 for (
int j = 0; j < iNumCoils; ++j) {
189 fNS = matAmpSine.col(j).array().square().sum();
190 fNC = matAmpCosine.col(j).array().square().sum();
192 matAmpSine.col(j) = matAmpCosine.col(j);
201Eigen::MatrixXd InvHpiFit::computeSeedPoints(
const Eigen::MatrixXd& matAmplitudes,
202 const FIFFLIB::FiffCoordTrans& transDevHead,
203 const QVector<double>& vecError,
204 const Eigen::MatrixXd& matCoilsHead)
206 const int iNumCoils = matCoilsHead.rows();
207 MatrixXd matCoilsSeed = MatrixXd::Zero(iNumCoils, 3);
209 const double dError = std::accumulate(vecError.begin(), vecError.end(), .0) / vecError.size();
211 if (transDevHead.
trans != MatrixXd::Identity(4, 4).cast<
float>() && dError < 0.010) {
213 matCoilsSeed = transDevHead.
apply_inverse_trans(matCoilsHead.cast<
float>()).cast<
double>();
216 VectorXi vecChIdcs(iNumCoils);
218 for (
int j = 0; j < iNumCoils; j++) {
220 VectorXd::Index indMax;
221 matAmplitudes.col(j).maxCoeff(&indMax);
222 if (indMax < m_sensors.ncoils()) {
225 vecChIdcs(j) = iChIdx;
228 for (
int j = 0; j < vecChIdcs.rows(); ++j) {
229 if (vecChIdcs(j) < m_sensors.ncoils()) {
230 Vector3d r0 = m_sensors.r0(vecChIdcs(j));
231 Vector3d ez = m_sensors.ez(vecChIdcs(j));
232 matCoilsSeed.row(j) = (-1 * ez * 0.03 + r0).transpose();
241CoilParam InvHpiFit::dipfit(
const MatrixXd matCoilsSeed,
243 const MatrixXd& matData,
245 const MatrixXd& matProjectors,
246 const int iMaxIterations,
247 const float fAbortError)
251 QList<InvHpiFitData> lCoilData;
253 for (qint32 i = 0; i < iNumCoils; ++i) {
254 InvHpiFitData coilData;
255 coilData.
m_coilPos = matCoilsSeed.row(i);
262 lCoilData.append(coilData);
265 CoilParam coil(iNumCoils);
267 if (!lCoilData.isEmpty()) {
274 QFuture<void> future = QtConcurrent::map(lCoilData,
276 future.waitForFinished();
279 for (qint32 i = 0; i < lCoilData.size(); ++i) {
280 coil.pos.row(i) = lCoilData.at(i).m_coilPos;
281 coil.mom = lCoilData.at(i).m_errorInfo.moment.transpose();
282 coil.dpfiterror(i) = lCoilData.at(i).m_errorInfo.error;
283 coil.dpfitnumitr(i) = lCoilData.at(i).m_errorInfo.numIterations;
293std::vector<int> InvHpiFit::findCoilOrder(
const MatrixXd& matCoilsDev,
294 const MatrixXd& matCoilsHead)
297 MatrixXd matCoilTemp = matCoilsDev;
298 const int iNumCoils = matCoilsDev.rows();
300 std::vector<int> vecOrder(iNumCoils);
301 std::iota(vecOrder.begin(), vecOrder.end(), 0);
305 const double dErrorMin = 0.010;
306 double dErrorActual = 0.0;
307 double dErrorBest = dErrorMin;
309 MatrixXd matTrans(4, 4);
310 std::vector<int> vecOrderBest = vecOrder;
314 for (
int i = 0; i < iNumCoils; i++) {
315 matCoilTemp.row(i) = matCoilsDev.row(vecOrder[i]);
317 matTrans = computeTransformation(matCoilsHead, matCoilTemp);
318 dErrorActual = objectTrans(matCoilsHead, matCoilTemp, matTrans);
319 if (dErrorActual < dErrorMin && dErrorActual < dErrorBest) {
321 dErrorBest = dErrorActual;
322 vecOrderBest = vecOrder;
324 }
while (std::next_permutation(vecOrder.begin(), vecOrder.end()));
330double InvHpiFit::objectTrans(
const MatrixXd& matHeadCoil,
331 const MatrixXd& matCoil,
332 const MatrixXd& matTrans)
335 const int iNumCoils = matHeadCoil.rows();
336 MatrixXd matTemp = matCoil;
339 matTemp.conservativeResize(matCoil.rows(), matCoil.cols() + 1);
340 matTemp.block(0, 3, iNumCoils, 1).setOnes();
341 matTemp.transposeInPlace();
344 MatrixXd matTestPos = matTrans * matTemp;
347 MatrixXd matDiff = matTestPos.block(0, 0, 3, iNumCoils) - matHeadCoil.transpose();
348 VectorXd vecError = matDiff.colwise().norm();
351 double dError = matDiff.colwise().norm().mean();
359Eigen::MatrixXd InvHpiFit::order(
const std::vector<int>& vecOrder,
360 const Eigen::MatrixXd& matToOrder)
362 const int iNumCoils =
static_cast<int>(vecOrder.size());
363 MatrixXd matToOrderTemp = matToOrder;
365 for (
int i = 0; i < iNumCoils; i++) {
366 matToOrderTemp.row(i) = matToOrder.row(vecOrder[i]);
368 return matToOrderTemp;
373QVector<int> InvHpiFit::order(
const std::vector<int>& vecOrder,
374 const QVector<int>& vecToOrder)
376 const int iNumCoils =
static_cast<int>(vecOrder.size());
377 QVector<int> vecToOrderTemp = vecToOrder;
379 for (
int i = 0; i < iNumCoils; i++) {
380 vecToOrderTemp[i] = vecToOrder[vecOrder[i]];
382 return vecToOrderTemp;
387Eigen::VectorXd InvHpiFit::computeGoF(
const Eigen::VectorXd& vecDipFitError)
389 VectorXd vecGoF(vecDipFitError.size());
390 for (
int i = 0; i < vecDipFitError.size(); ++i) {
391 vecGoF(i) = 1 - vecDipFitError(i);
398FIFFLIB::FiffCoordTrans InvHpiFit::computeDeviceHeadTransformation(
const Eigen::MatrixXd& matCoilsDev,
399 const Eigen::MatrixXd& matCoilsHead)
401 const MatrixXd matTrans = computeTransformation(matCoilsHead, matCoilsDev);
407Eigen::Matrix4d InvHpiFit::computeTransformation(Eigen::MatrixXd matNH, MatrixXd matBT)
409 MatrixXd matXdiff, matYdiff, matZdiff, matC, matQ;
410 Matrix4d matTransFinal = Matrix4d::Identity(4, 4);
411 Matrix4d matRot = Matrix4d::Zero(4, 4);
412 Matrix4d matTrans = Matrix4d::Identity(4, 4);
413 double dMeanX, dMeanY, dMeanZ;
415 for (
int i = 0; i < 15; ++i) {
417 matXdiff = matNH.col(0) - matBT.col(0);
418 matYdiff = matNH.col(1) - matBT.col(1);
419 matZdiff = matNH.col(2) - matBT.col(2);
421 dMeanX = matXdiff.mean();
422 dMeanY = matYdiff.mean();
423 dMeanZ = matZdiff.mean();
426 for (
int j = 0; j < matBT.rows(); ++j) {
427 matBT(j, 0) = matBT(j, 0) + dMeanX;
428 matBT(j, 1) = matBT(j, 1) + dMeanY;
429 matBT(j, 2) = matBT(j, 2) + dMeanZ;
433 matC = matBT.transpose() * matNH;
435 JacobiSVD<MatrixXd>
svd(matC, Eigen::ComputeThinU | ComputeThinV);
437 matQ =
svd.matrixU() *
svd.matrixV().transpose();
440 if (matQ.determinant() < 0) {
441 matQ(0, 2) = matQ(0, 2) * -1;
442 matQ(1, 2) = matQ(1, 2) * -1;
443 matQ(2, 2) = matQ(2, 2) * -1;
447 matBT = matBT * matQ;
451 for (
int j = 0; j < 3; ++j) {
452 for (
int k = 0; k < 3; ++k) {
453 matRot(j, k) = matQ(k, j);
458 matTrans(0, 3) = dMeanX;
459 matTrans(1, 3) = dMeanY;
460 matTrans(2, 3) = dMeanZ;
465 matTransFinal = matRot * matTrans * matTransFinal;
467 return matTransFinal;
472QVector<double> InvHpiFit::computeEstimationError(
const Eigen::MatrixXd& matCoilsDev,
473 const Eigen::MatrixXd& matCoilsHead,
474 const FIFFLIB::FiffCoordTrans& transDevHead)
477 MatrixXd matTemp = matCoilsDev;
478 MatrixXd matTestPos = transDevHead.
apply_trans(matTemp.cast<
float>()).cast<
double>();
479 MatrixXd matDiffPos = matTestPos - matCoilsHead;
482 int iNumCoils = matCoilsDev.rows();
483 QVector<double> vecError(iNumCoils);
484 for (
int i = 0; i < matDiffPos.rows(); ++i) {
485 vecError[i] = matDiffPos.row(i).norm();
492FIFFLIB::FiffDigPointSet InvHpiFit::getFittedPointSet(
const Eigen::MatrixXd& matCoilsDev)
494 FiffDigPointSet fittedPointSet;
495 const int iNumCoils = matCoilsDev.rows();
497 for (
int i = 0; i < iNumCoils; ++i) {
498 FiffDigPoint digPoint;
501 digPoint.
r[0] = matCoilsDev(i, 0);
502 digPoint.
r[1] = matCoilsDev(i, 1);
503 digPoint.
r[2] = matCoilsDev(i, 2);
505 fittedPointSet << digPoint;
507 return fittedPointSet;
513 const Eigen::MatrixXf& transDevHead,
514 Eigen::MatrixXd& matPosition,
515 const Eigen::VectorXd& vecGoF,
516 const QVector<double>& vecError)
519 Matrix3f matRot = transDevHead.block(0, 0, 3, 3);
521 Eigen::Quaternionf quatHPI(matRot);
522 double dError = std::accumulate(vecError.begin(), vecError.end(), .0) / vecError.size();
524 matPosition.conservativeResize(matPosition.rows() + 1, 10);
525 matPosition(matPosition.rows() - 1, 0) = fTime;
526 matPosition(matPosition.rows() - 1, 1) = quatHPI.x();
527 matPosition(matPosition.rows() - 1, 2) = quatHPI.y();
528 matPosition(matPosition.rows() - 1, 3) = quatHPI.z();
529 matPosition(matPosition.rows() - 1, 4) = transDevHead(0, 3);
530 matPosition(matPosition.rows() - 1, 5) = transDevHead(1, 3);
531 matPosition(matPosition.rows() - 1, 6) = transDevHead(2, 3);
532 matPosition(matPosition.rows() - 1, 7) = vecGoF.mean();
533 matPosition(matPosition.rows() - 1, 8) = dError;
534 matPosition(matPosition.rows() - 1, 9) = 0;
540 const MatrixX4f& mDevHeadDest,
541 const float& fThreshRot,
542 const float& fThreshTrans)
546 Matrix3f mRot = mDevHeadT.block(0, 0, 3, 3);
547 Matrix3f mRotDest = mDevHeadDest.block(0, 0, 3, 3);
549 VectorXf vTrans = mDevHeadT.block(0, 3, 3, 1);
550 VectorXf vTransDest = mDevHeadDest.block(0, 3, 3, 1);
552 Quaternionf quat(mRot);
553 Quaternionf quatNew(mRotDest);
556 float fAngle = quat.angularDistance(quatNew);
557 fAngle = fAngle * 180 /
M_PI;
560 float fMove = (vTrans - vTransDest).norm();
563 if (fMove > fThreshTrans) {
564 qInfo() <<
"Large movement: " << fMove * 1000 <<
"mm";
566 }
else if (fAngle > fThreshRot) {
567 qInfo() <<
"Large rotation: " << fAngle <<
"degree";
Container for the FIFF_DIG_POINT records of a measurement (a parsed FIFFB_ISOTRAK block).
return FiffCoordTrans(from_frame, to_frame, R, moveVec)
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
Noise / data covariance matrix as stored under FIFFB_MNE_COV, with channel names, kind,...
Header-only Eigen matrix text I/O — round-trips dense matrices to whitespace-separated ASCII for cros...
Container of FwdCoil instances representing either a sensor-type template database or a concrete per-...
Sinusoidal HPI signal model — builds and inverts the regressor matrix that extracts coil amplitudes f...
Per-coil magnetic-dipole fitting workspace — Nelder-Mead optimiser plus leadfield computation for HPI...
Compact MEG sensor-geometry container (positions, orientations, integration weights) used by the HPI ...
Immutable configuration for the HPI signal model — coil drive frequencies, sample rate,...
Pre-processing front-end for HPI fitting — re-shapes raw MEG data, projectors and digitised coils int...
HPI (Head Position Indicator) fitting — estimates the MEG dewar-to-head transform from coil-current s...
FIFF file I/O, in-memory data structures and high-level readers/writers.
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
Forward modelling — BEM solver, spherical models, sensor/coil definitions and the lead-field assembly...
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.