34#define _USE_MATH_DEFINES
57#include <QtConcurrent/QtConcurrent>
79 : m_sensors(sensorSet),
89 if(m_sensors != sensorSet) {
90 m_sensors = sensorSet;
97 const MatrixXd& matProjectors,
99 const MatrixXd& matCoilsHead,
102 fit(matProjectedData,matProjectors,hpiModelParameters,matCoilsHead,
false,hpiFitResult);
108 const MatrixXd& matProjectors,
110 const MatrixXd& matCoilsHead,
111 const bool bOrderFrequencies,
114 if(matProjectedData.rows() != matProjectors.rows()) {
115 std::cout<<
"InvHpiFit::fit - Projector and data dimensions do not match. Returning."<<std::endl;
117 }
else if(hpiModelParameters.
iNHpiCoils()!= matCoilsHead.rows()) {
118 std::cout<<
"InvHpiFit::fit - Number of coils and hpi digitizers do not match. Returning."<<std::endl;
120 }
else if(matProjectedData.rows()==0 || matProjectors.rows()==0) {
121 std::cout<<
"InvHpiFit::fit - No data or Projectors passed. Returning."<<std::endl;
123 }
else if(m_sensors.ncoils() != matProjectedData.rows()) {
124 std::cout<<
"InvHpiFit::fit - Number of channels in sensors and data do not match. Returning."<<std::endl;
128 const MatrixXd matAmplitudes = computeAmplitudes(matProjectedData,
131 const MatrixXd matCoilsSeed = computeSeedPoints(matAmplitudes,
136 CoilParam fittedCoilParams = dipfit(matCoilsSeed,
144 if(bOrderFrequencies) {
145 const std::vector<int> vecOrder = findCoilOrder(fittedCoilParams.
pos,
148 fittedCoilParams.
pos = order(vecOrder,fittedCoilParams.
pos);
154 hpiFitResult.
fittedCoils = getFittedPointSet(fittedCoilParams.
pos);
156 hpiFitResult.
devHeadTrans = computeDeviceHeadTransformation(fittedCoilParams.
pos,
166Eigen::MatrixXd InvHpiFit::computeAmplitudes(
const Eigen::MatrixXd& matProjectedData,
170 MatrixXd matTopo = m_signalModel.fitData(hpiModelParameters,matProjectedData);
171 matTopo.transposeInPlace();
174 const int iNumCoils = hpiModelParameters.
iNHpiCoils();
176 MatrixXd matAmpSine(matProjectedData.cols(), iNumCoils);
177 MatrixXd matAmpCosine(matProjectedData.cols(), iNumCoils);
179 matAmpSine = matTopo.leftCols(iNumCoils);
180 matAmpCosine = matTopo.middleCols(iNumCoils,iNumCoils);
183 for(
int j = 0; j < iNumCoils; ++j) {
186 fNS = matAmpSine.col(j).array().square().sum();
187 fNC = matAmpCosine.col(j).array().square().sum();
189 matAmpSine.col(j) = matAmpCosine.col(j);
198Eigen::MatrixXd InvHpiFit::computeSeedPoints(
const Eigen::MatrixXd& matAmplitudes,
199 const FIFFLIB::FiffCoordTrans& transDevHead,
200 const QVector<double>& vecError,
201 const Eigen::MatrixXd& matCoilsHead)
203 const int iNumCoils = matCoilsHead.rows();
204 MatrixXd matCoilsSeed = MatrixXd::Zero(iNumCoils,3);
206 const double dError = std::accumulate(vecError.begin(), vecError.end(), .0) / vecError.size();
208 if(transDevHead.
trans != MatrixXd::Identity(4,4).cast<
float>() && dError < 0.010) {
210 matCoilsSeed = transDevHead.
apply_inverse_trans(matCoilsHead.cast<
float>()).cast<
double>();
213 VectorXi vecChIdcs(iNumCoils);
215 for (
int j = 0; j < iNumCoils; j++) {
217 VectorXd::Index indMax;
218 matAmplitudes.col(j).maxCoeff(&indMax);
219 if(indMax < m_sensors.ncoils()) {
222 vecChIdcs(j) = iChIdx;
225 for (
int j = 0; j < vecChIdcs.rows(); ++j) {
226 if(vecChIdcs(j) < m_sensors.ncoils()) {
227 Vector3d r0 = m_sensors.r0(vecChIdcs(j));
228 Vector3d ez = m_sensors.ez(vecChIdcs(j));
229 matCoilsSeed.row(j) = (-1 * ez * 0.03 + r0).transpose();
238CoilParam InvHpiFit::dipfit(
const MatrixXd matCoilsSeed,
240 const MatrixXd& matData,
242 const MatrixXd& matProjectors,
243 const int iMaxIterations,
244 const float fAbortError)
248 QList<InvHpiFitData> lCoilData;
250 for(qint32 i = 0; i < iNumCoils; ++i) {
251 InvHpiFitData coilData;
252 coilData.
m_coilPos = matCoilsSeed.row(i);
259 lCoilData.append(coilData);
262 CoilParam coil(iNumCoils);
264 if(!lCoilData.isEmpty()) {
271 QFuture<void> future = QtConcurrent::map(lCoilData,
273 future.waitForFinished();
276 for(qint32 i = 0; i < lCoilData.size(); ++i) {
277 coil.pos.row(i) = lCoilData.at(i).m_coilPos;
278 coil.mom = lCoilData.at(i).m_errorInfo.moment.transpose();
279 coil.dpfiterror(i) = lCoilData.at(i).m_errorInfo.error;
280 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);;
301 const double dErrorMin = 0.010;
302 double dErrorActual = 0.0;
303 double dErrorBest = dErrorMin;
305 MatrixXd matTrans(4,4);
306 std::vector<int> vecOrderBest = vecOrder;
308 bool bSuccess =
false;
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;
322 }
while (std::next_permutation(vecOrder.begin(), vecOrder.end()));
328double InvHpiFit::objectTrans(
const MatrixXd& matHeadCoil,
329 const MatrixXd& matCoil,
330 const MatrixXd& matTrans)
333 const int iNumCoils = matHeadCoil.rows();
334 MatrixXd matTemp = matCoil;
337 matTemp.conservativeResize(matCoil.rows(),matCoil.cols()+1);
338 matTemp.block(0,3,iNumCoils,1).setOnes();
339 matTemp.transposeInPlace();
342 MatrixXd matTestPos = matTrans * matTemp;
345 MatrixXd matDiff = matTestPos.block(0,0,3,iNumCoils) - matHeadCoil.transpose();
346 VectorXd vecError = matDiff.colwise().norm();
349 double dError = matDiff.colwise().norm().mean();;
356Eigen::MatrixXd InvHpiFit::order(
const std::vector<int>& vecOrder,
357 const Eigen::MatrixXd& matToOrder)
359 const int iNumCoils = 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 = 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,dNormf;
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;
447 dNormf = (matNH.transpose()-matBT.transpose()).norm();
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";
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-...
return FiffCoordTrans(from_frame, to_frame, R, moveVec)
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
Container for the FIFF_DIG_POINT records of a measurement (a parsed FIFFB_ISOTRAK block).
Noise / data covariance matrix as stored under FIFFB_MNE_COV, with channel names, kind,...
HPI (Head Position Indicator) fitting — estimates the MEG dewar-to-head transform from coil-current s...
Sinusoidal HPI signal model — builds and inverts the regressor matrix that extracts coil amplitudes f...
Pre-processing front-end for HPI fitting — re-shapes raw MEG data, projectors and digitised coils int...
Immutable configuration for the HPI signal model — coil drive frequencies, sample rate,...
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 ...
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.