26#include <QRegularExpression>
44 FiffInfo info(fiffInfo);
48 Matrix3d rotRef = headPosRef.
rotation.toRotationMatrix();
49 Matrix3d rotCur = headPosCurrent.
rotation.toRotationMatrix();
50 Matrix3d rotRel = rotRef * rotCur.transpose();
56 for (
int i = 0; i < info.chs.size(); ++i) {
59 Vector3f r0 = info.chs[i].chpos.r0;
60 Vector3d r0d = r0.cast<
double>();
61 r0d = rotRel * r0d + tRel;
62 info.chs[i].chpos.r0 = r0d.cast<
float>();
65 Vector3f ex = info.chs[i].chpos.ex;
66 Vector3d exd = ex.cast<
double>();
68 info.chs[i].chpos.ex = exd.cast<
float>();
70 Vector3f ey = info.chs[i].chpos.ey;
71 Vector3d eyd = ey.cast<
double>();
73 info.chs[i].chpos.ey = eyd.cast<
float>();
75 Vector3f ez = info.chs[i].chpos.ez;
76 Vector3d ezd = ez.cast<
double>();
78 info.chs[i].chpos.ez = ezd.cast<
float>();
89 const QList<HeadPosEntry>& headPos,
93 if (headPos.isEmpty()) {
94 qWarning() <<
"[MaxwellMovementComp::apply] No head positions provided.";
98 const int nChannels = matData.rows();
99 const int nSamples = matData.cols();
104 refPos = headPos[params.
iRefIdx];
107 Vector3d meanTrans = Vector3d::Zero();
108 for (
const auto& hp : headPos) {
109 meanTrans += hp.translation;
111 meanTrans /= headPos.size();
113 refPos.
rotation = headPos[0].rotation;
125 MatrixXd result = matData;
128 for (
int iSeg = 0; iSeg < headPos.size(); ++iSeg) {
130 int iStart =
static_cast<int>(headPos[iSeg].dTime * dSFreq);
132 if (iSeg + 1 < headPos.size()) {
133 iEnd =
static_cast<int>(headPos[iSeg + 1].dTime * dSFreq);
138 iStart = qBound(0, iStart, nSamples);
139 iEnd = qBound(iStart, iEnd, nSamples);
140 if (iStart >= iEnd)
continue;
142 int nSegSamples = iEnd - iStart;
145 FiffInfo infoCurrentToRef = transformFiffInfo(fiffInfo, refPos, headPos[iSeg]);
151 MatrixXd segData = matData.middleCols(iStart, nSegSamples);
155 MatrixXd megData(basisCurrent.
megChannelIdx.size(), nSegSamples);
156 for (
int ch = 0; ch < basisCurrent.
megChannelIdx.size(); ++ch) {
157 megData.row(ch) = segData.row(basisCurrent.
megChannelIdx[ch]);
160 MatrixXd multipoles = basisCurrent.
matPinvAll * megData;
163 MatrixXd reconstructed = basisRef.
matSin * multipoles.topRows(basisRef.
iNin);
167 result.row(basisRef.
megChannelIdx[ch]).segment(iStart, nSegSamples) = reconstructed.row(ch);
178 QList<HeadPosEntry> positions;
181 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
182 qWarning() <<
"[MaxwellMovementComp::readHeadPos] Cannot open file:" << sPath;
186 QTextStream in(&file);
187 while (!in.atEnd()) {
188 QString line = in.readLine().trimmed();
189 if (line.isEmpty() || line.startsWith(
'#'))
continue;
191 QStringList parts = line.split(QRegularExpression(
"\\s+"), Qt::SkipEmptyParts);
192 if (parts.size() < 7)
continue;
196 entry.
dTime = parts[0].toDouble(&ok);
if (!ok)
continue;
199 double q1 = parts[1].toDouble(&ok);
if (!ok)
continue;
200 double q2 = parts[2].toDouble(&ok);
if (!ok)
continue;
201 double q3 = parts[3].toDouble(&ok);
if (!ok)
continue;
203 double tx = parts[4].toDouble(&ok);
if (!ok)
continue;
204 double ty = parts[5].toDouble(&ok);
if (!ok)
continue;
205 double tz = parts[6].toDouble(&ok);
if (!ok)
continue;
207 if (parts.size() >= 8) {
208 entry.
dGof = parts[7].toDouble();
212 double q0sq = 1.0 - q1*q1 - q2*q2 - q3*q3;
213 double q0 = (q0sq > 0.0) ? std::sqrt(q0sq) : 0.0;
214 entry.
rotation = Quaterniond(q0, q1, q2, q3);
217 positions.append(entry);
227 const QList<HeadPosEntry>& headPos)
230 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
231 qWarning() <<
"[MaxwellMovementComp::writeHeadPos] Cannot open file:" << sPath;
235 QTextStream out(&file);
236 out <<
"# Head position file\n";
237 out <<
"# time q1 q2 q3 tx ty tz gof\n";
239 for (
const auto& entry : headPos) {
241 out << QString::number(entry.dTime,
'f', 6) <<
" "
242 << QString::number(entry.rotation.x(),
'f', 6) <<
" "
243 << QString::number(entry.rotation.y(),
'f', 6) <<
" "
244 << QString::number(entry.rotation.z(),
'f', 6) <<
" "
245 << QString::number(entry.translation.x(),
'f', 6) <<
" "
246 << QString::number(entry.translation.y(),
'f', 6) <<
" "
247 << QString::number(entry.translation.z(),
'f', 6) <<
" "
248 << QString::number(entry.dGof,
'f', 6) <<
"\n";
Maxwell movement compensation for MEG.
FIFF file I/O, in-memory data structures and high-level readers/writers.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
A head position entry (time, translation, rotation quaternion).
Eigen::Quaterniond rotation
Eigen::Vector3d translation
Parameters for Maxwell movement compensation.
static Eigen::MatrixXd apply(const Eigen::MatrixXd &matData, const FIFFLIB::FiffInfo &fiffInfo, const QList< HeadPosEntry > &headPos, double dSFreq, const MaxwellMoveCompParams ¶ms=MaxwellMoveCompParams())
Apply movement compensation via SSS.
static bool writeHeadPos(const QString &sPath, const QList< HeadPosEntry > &headPos)
Write head positions to a file.
static QList< HeadPosEntry > readHeadPos(const QString &sPath)
Read head positions from a file.
Implements Signal Space Separation (SSS) and temporal SSS (tSSS) for MEG data.
static Basis computeBasis(const FIFFLIB::FiffInfo &fiffInfo, const Params ¶ms=Params())
Precomputed SSS basis and projectors for a given sensor array.
Eigen::MatrixXd matPinvAll
QVector< int > megChannelIdx
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...