36#ifndef _USE_MATH_DEFINES
37#define _USE_MATH_DEFINES
60#include <QtConcurrent/QtConcurrent>
91 if (m_sensors != sensorSet) {
92 m_sensors = sensorSet;
99 const MatrixXd& matProjectors,
101 const MatrixXd& matCoilsHead,
104 fit(matProjectedData, matProjectors, hpiModelParameters, matCoilsHead,
false, hpiFitResult);
110 const MatrixXd& matProjectors,
112 const MatrixXd& matCoilsHead,
113 const bool bOrderFrequencies,
116 if (matProjectedData.rows() != matProjectors.rows()) {
117 qWarning(
"InvHpiFit::fit - Projector and data dimensions do not match. Returning.");
119 }
else if (hpiModelParameters.
iNHpiCoils() != matCoilsHead.rows()) {
120 qWarning(
"InvHpiFit::fit - Number of coils and hpi digitizers do not match. Returning.");
122 }
else if (matProjectedData.rows() == 0 || matProjectors.rows() == 0) {
123 qWarning(
"InvHpiFit::fit - No data or Projectors passed. Returning.");
125 }
else if (m_sensors.ncoils() != matProjectedData.rows()) {
126 qWarning(
"InvHpiFit::fit - Number of channels in sensors and data do not match. Returning.");
130 const MatrixXd matAmplitudes = computeAmplitudes(matProjectedData,
133 const MatrixXd matCoilsSeed = computeSeedPoints(matAmplitudes,
138 CoilParam fittedCoilParams = dipfit(matCoilsSeed,
146 if (bOrderFrequencies) {
147 const std::vector<int> vecOrder = findCoilOrder(fittedCoilParams.
pos,
150 fittedCoilParams.
pos = order(vecOrder, fittedCoilParams.
pos);
156 hpiFitResult.
fittedCoils = getFittedPointSet(fittedCoilParams.
pos);
158 hpiFitResult.
devHeadTrans = computeDeviceHeadTransformation(fittedCoilParams.
pos,
168Eigen::MatrixXd InvHpiFit::computeAmplitudes(
const Eigen::MatrixXd& matProjectedData,
172 MatrixXd matTopo = m_signalModel.fitData(hpiModelParameters, matProjectedData);
173 matTopo.transposeInPlace();
176 const int iNumCoils = hpiModelParameters.
iNHpiCoils();
178 MatrixXd matAmpSine(matProjectedData.cols(), iNumCoils);
179 MatrixXd matAmpCosine(matProjectedData.cols(), iNumCoils);
181 matAmpSine = matTopo.leftCols(iNumCoils);
182 matAmpCosine = matTopo.middleCols(iNumCoils, iNumCoils);
185 for (
int j = 0; j < iNumCoils; ++j) {
188 fNS = matAmpSine.col(j).array().square().sum();
189 fNC = matAmpCosine.col(j).array().square().sum();
191 matAmpSine.col(j) = matAmpCosine.col(j);
200Eigen::MatrixXd InvHpiFit::computeSeedPoints(
const Eigen::MatrixXd& matAmplitudes,
201 const FIFFLIB::FiffCoordTrans& transDevHead,
202 const QVector<double>& vecError,
203 const Eigen::MatrixXd& matCoilsHead)
205 const int iNumCoils = matCoilsHead.rows();
206 MatrixXd matCoilsSeed = MatrixXd::Zero(iNumCoils, 3);
208 const double dError = std::accumulate(vecError.begin(), vecError.end(), .0) / vecError.size();
210 if (transDevHead.
trans != MatrixXd::Identity(4, 4).cast<
float>() && dError < 0.010) {
212 matCoilsSeed = transDevHead.
apply_inverse_trans(matCoilsHead.cast<
float>()).cast<
double>();
215 VectorXi vecChIdcs(iNumCoils);
217 for (
int j = 0; j < iNumCoils; j++) {
219 VectorXd::Index indMax;
220 matAmplitudes.col(j).maxCoeff(&indMax);
221 if (indMax < m_sensors.ncoils()) {
224 vecChIdcs(j) = iChIdx;
227 for (
int j = 0; j < vecChIdcs.rows(); ++j) {
228 if (vecChIdcs(j) < m_sensors.ncoils()) {
229 Vector3d r0 = m_sensors.r0(vecChIdcs(j));
230 Vector3d ez = m_sensors.ez(vecChIdcs(j));
231 matCoilsSeed.row(j) = (-1 * ez * 0.03 + r0).transpose();
240CoilParam InvHpiFit::dipfit(
const MatrixXd matCoilsSeed,
242 const MatrixXd& matData,
244 const MatrixXd& matProjectors,
245 const int iMaxIterations,
246 const float fAbortError)
250 QList<InvHpiFitData> lCoilData;
252 for (qint32 i = 0; i < iNumCoils; ++i) {
253 InvHpiFitData coilData;
254 coilData.
m_coilPos = matCoilsSeed.row(i);
261 lCoilData.append(coilData);
264 CoilParam coil(iNumCoils);
266 if (!lCoilData.isEmpty()) {
273 QFuture<void> future = QtConcurrent::map(lCoilData,
275 future.waitForFinished();
278 for (qint32 i = 0; i < lCoilData.size(); ++i) {
279 coil.pos.row(i) = lCoilData.at(i).m_coilPos;
280 coil.mom = lCoilData.at(i).m_errorInfo.moment.transpose();
281 coil.dpfiterror(i) = lCoilData.at(i).m_errorInfo.error;
282 coil.dpfitnumitr(i) = lCoilData.at(i).m_errorInfo.numIterations;
290std::vector<int> InvHpiFit::findCoilOrder(
const MatrixXd& matCoilsDev,
291 const MatrixXd& matCoilsHead)
294 MatrixXd matCoilTemp = matCoilsDev;
295 const int iNumCoils = matCoilsDev.rows();
297 std::vector<int> vecOrder(iNumCoils);
298 std::iota(vecOrder.begin(), vecOrder.end(), 0);
302 const double dErrorMin = 0.010;
303 double dErrorActual = 0.0;
304 double dErrorBest = dErrorMin;
306 MatrixXd matTrans(4, 4);
307 std::vector<int> vecOrderBest = vecOrder;
311 for (
int i = 0; i < iNumCoils; i++) {
312 matCoilTemp.row(i) = matCoilsDev.row(vecOrder[i]);
314 matTrans = computeTransformation(matCoilsHead, matCoilTemp);
315 dErrorActual = objectTrans(matCoilsHead, matCoilTemp, matTrans);
316 if (dErrorActual < dErrorMin && dErrorActual < dErrorBest) {
318 dErrorBest = dErrorActual;
319 vecOrderBest = vecOrder;
321 }
while (std::next_permutation(vecOrder.begin(), vecOrder.end()));
327double InvHpiFit::objectTrans(
const MatrixXd& matHeadCoil,
328 const MatrixXd& matCoil,
329 const MatrixXd& matTrans)
332 const int iNumCoils = matHeadCoil.rows();
333 MatrixXd matTemp = matCoil;
336 matTemp.conservativeResize(matCoil.rows(), matCoil.cols() + 1);
337 matTemp.block(0, 3, iNumCoils, 1).setOnes();
338 matTemp.transposeInPlace();
341 MatrixXd matTestPos = matTrans * matTemp;
344 MatrixXd matDiff = matTestPos.block(0, 0, 3, iNumCoils) - matHeadCoil.transpose();
345 VectorXd vecError = matDiff.colwise().norm();
348 double dError = matDiff.colwise().norm().mean();
356Eigen::MatrixXd InvHpiFit::order(
const std::vector<int>& vecOrder,
357 const Eigen::MatrixXd& matToOrder)
359 const int iNumCoils =
static_cast<int>(vecOrder.size());
360 MatrixXd matToOrderTemp = matToOrder;
362 for (
int i = 0; i < iNumCoils; i++) {
363 matToOrderTemp.row(i) = matToOrder.row(vecOrder[i]);
365 return matToOrderTemp;
370QVector<int> InvHpiFit::order(
const std::vector<int>& vecOrder,
371 const QVector<int>& vecToOrder)
373 const int iNumCoils =
static_cast<int>(vecOrder.size());
374 QVector<int> vecToOrderTemp = vecToOrder;
376 for (
int i = 0; i < iNumCoils; i++) {
377 vecToOrderTemp[i] = vecToOrder[vecOrder[i]];
379 return vecToOrderTemp;
384Eigen::VectorXd InvHpiFit::computeGoF(
const Eigen::VectorXd& vecDipFitError)
386 VectorXd vecGoF(vecDipFitError.size());
387 for (
int i = 0; i < vecDipFitError.size(); ++i) {
388 vecGoF(i) = 1 - vecDipFitError(i);
395FIFFLIB::FiffCoordTrans InvHpiFit::computeDeviceHeadTransformation(
const Eigen::MatrixXd& matCoilsDev,
396 const Eigen::MatrixXd& matCoilsHead)
398 const MatrixXd matTrans = computeTransformation(matCoilsHead, matCoilsDev);
404Eigen::Matrix4d InvHpiFit::computeTransformation(Eigen::MatrixXd matNH, MatrixXd matBT)
406 MatrixXd matXdiff, matYdiff, matZdiff, matC, matQ;
407 Matrix4d matTransFinal = Matrix4d::Identity(4, 4);
408 Matrix4d matRot = Matrix4d::Zero(4, 4);
409 Matrix4d matTrans = Matrix4d::Identity(4, 4);
410 double dMeanX, dMeanY, dMeanZ;
412 for (
int i = 0; i < 15; ++i) {
414 matXdiff = matNH.col(0) - matBT.col(0);
415 matYdiff = matNH.col(1) - matBT.col(1);
416 matZdiff = matNH.col(2) - matBT.col(2);
418 dMeanX = matXdiff.mean();
419 dMeanY = matYdiff.mean();
420 dMeanZ = matZdiff.mean();
423 for (
int j = 0; j < matBT.rows(); ++j) {
424 matBT(j, 0) = matBT(j, 0) + dMeanX;
425 matBT(j, 1) = matBT(j, 1) + dMeanY;
426 matBT(j, 2) = matBT(j, 2) + dMeanZ;
430 matC = matBT.transpose() * matNH;
432 JacobiSVD<MatrixXd>
svd(matC, Eigen::ComputeThinU | ComputeThinV);
434 matQ =
svd.matrixU() *
svd.matrixV().transpose();
437 if (matQ.determinant() < 0) {
438 matQ(0, 2) = matQ(0, 2) * -1;
439 matQ(1, 2) = matQ(1, 2) * -1;
440 matQ(2, 2) = matQ(2, 2) * -1;
444 matBT = matBT * matQ;
448 for (
int j = 0; j < 3; ++j) {
449 for (
int k = 0; k < 3; ++k) {
450 matRot(j, k) = matQ(k, j);
455 matTrans(0, 3) = dMeanX;
456 matTrans(1, 3) = dMeanY;
457 matTrans(2, 3) = dMeanZ;
462 matTransFinal = matRot * matTrans * matTransFinal;
464 return matTransFinal;
469QVector<double> InvHpiFit::computeEstimationError(
const Eigen::MatrixXd& matCoilsDev,
470 const Eigen::MatrixXd& matCoilsHead,
471 const FIFFLIB::FiffCoordTrans& transDevHead)
474 MatrixXd matTemp = matCoilsDev;
475 MatrixXd matTestPos = transDevHead.
apply_trans(matTemp.cast<
float>()).cast<
double>();
476 MatrixXd matDiffPos = matTestPos - matCoilsHead;
479 int iNumCoils = matCoilsDev.rows();
480 QVector<double> vecError(iNumCoils);
481 for (
int i = 0; i < matDiffPos.rows(); ++i) {
482 vecError[i] = matDiffPos.row(i).norm();
489FIFFLIB::FiffDigPointSet InvHpiFit::getFittedPointSet(
const Eigen::MatrixXd& matCoilsDev)
491 FiffDigPointSet fittedPointSet;
492 const int iNumCoils = matCoilsDev.rows();
494 for (
int i = 0; i < iNumCoils; ++i) {
495 FiffDigPoint digPoint;
498 digPoint.
r[0] = matCoilsDev(i, 0);
499 digPoint.
r[1] = matCoilsDev(i, 1);
500 digPoint.
r[2] = matCoilsDev(i, 2);
502 fittedPointSet << digPoint;
504 return fittedPointSet;
510 const Eigen::MatrixXf& transDevHead,
511 Eigen::MatrixXd& matPosition,
512 const Eigen::VectorXd& vecGoF,
513 const QVector<double>& vecError)
516 Matrix3f matRot = transDevHead.block(0, 0, 3, 3);
518 Eigen::Quaternionf quatHPI(matRot);
519 double dError = std::accumulate(vecError.begin(), vecError.end(), .0) / vecError.size();
521 matPosition.conservativeResize(matPosition.rows() + 1, 10);
522 matPosition(matPosition.rows() - 1, 0) = fTime;
523 matPosition(matPosition.rows() - 1, 1) = quatHPI.x();
524 matPosition(matPosition.rows() - 1, 2) = quatHPI.y();
525 matPosition(matPosition.rows() - 1, 3) = quatHPI.z();
526 matPosition(matPosition.rows() - 1, 4) = transDevHead(0, 3);
527 matPosition(matPosition.rows() - 1, 5) = transDevHead(1, 3);
528 matPosition(matPosition.rows() - 1, 6) = transDevHead(2, 3);
529 matPosition(matPosition.rows() - 1, 7) = vecGoF.mean();
530 matPosition(matPosition.rows() - 1, 8) = dError;
531 matPosition(matPosition.rows() - 1, 9) = 0;
537 const MatrixX4f& mDevHeadDest,
538 const float& fThreshRot,
539 const float& fThreshTrans)
543 Matrix3f mRot = mDevHeadT.block(0, 0, 3, 3);
544 Matrix3f mRotDest = mDevHeadDest.block(0, 0, 3, 3);
546 VectorXf vTrans = mDevHeadT.block(0, 3, 3, 1);
547 VectorXf vTransDest = mDevHeadDest.block(0, 3, 3, 1);
549 Quaternionf quat(mRot);
550 Quaternionf quatNew(mRotDest);
553 float fAngle = quat.angularDistance(quatNew);
554 fAngle = fAngle * 180 / M_PI;
557 float fMove = (vTrans - vTransDest).norm();
560 if (fMove > fThreshTrans) {
561 qInfo() <<
"Large movement: " << fMove * 1000 <<
"mm";
563 }
else if (fAngle > fThreshRot) {
564 qInfo() <<
"Large rotation: " << fAngle <<
"degree";
constexpr auto FIFFV_POINT_EEG
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.