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 nSamples = matData.cols();
103 refPos = headPos[params.
iRefIdx];
106 Vector3d meanTrans = Vector3d::Zero();
107 for (
const auto& hp : headPos) {
108 meanTrans += hp.translation;
110 meanTrans /= headPos.size();
112 refPos.
rotation = headPos[0].rotation;
124 MatrixXd result = matData;
127 for (
int iSeg = 0; iSeg < headPos.size(); ++iSeg) {
129 int iStart =
static_cast<int>(headPos[iSeg].dTime * dSFreq);
131 if (iSeg + 1 < headPos.size()) {
132 iEnd =
static_cast<int>(headPos[iSeg + 1].dTime * dSFreq);
137 iStart = qBound(0, iStart, nSamples);
138 iEnd = qBound(iStart, iEnd, nSamples);
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(
'#'))
192 QStringList parts = line.split(QRegularExpression(
"\\s+"), Qt::SkipEmptyParts);
193 if (parts.size() < 7)
198 entry.
dTime = parts[0].toDouble(&ok);
203 double q1 = parts[1].toDouble(&ok);
206 double q2 = parts[2].toDouble(&ok);
209 double q3 = parts[3].toDouble(&ok);
213 double tx = parts[4].toDouble(&ok);
216 double ty = parts[5].toDouble(&ok);
219 double tz = parts[6].toDouble(&ok);
223 if (parts.size() >= 8) {
224 entry.
dGof = parts[7].toDouble();
228 double q0sq = 1.0 - q1 * q1 - q2 * q2 - q3 * q3;
229 double q0 = (q0sq > 0.0) ? std::sqrt(q0sq) : 0.0;
230 entry.
rotation = Quaterniond(q0, q1, q2, q3);
233 positions.append(entry);
243 const QList<HeadPosEntry>& headPos)
246 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
247 qWarning() <<
"[MaxwellMovementComp::writeHeadPos] Cannot open file:" << sPath;
251 QTextStream out(&file);
252 out <<
"# Head position file\n";
253 out <<
"# time q1 q2 q3 tx ty tz gof\n";
255 for (
const auto& entry : headPos) {
257 out << QString::number(entry.dTime,
'f', 6) <<
" "
258 << QString::number(entry.rotation.x(),
'f', 6) <<
" "
259 << QString::number(entry.rotation.y(),
'f', 6) <<
" "
260 << QString::number(entry.rotation.z(),
'f', 6) <<
" "
261 << QString::number(entry.translation.x(),
'f', 6) <<
" "
262 << QString::number(entry.translation.y(),
'f', 6) <<
" "
263 << QString::number(entry.translation.z(),
'f', 6) <<
" "
264 << 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.
Configuration parameters for SSS/tSSS (defined outside class to work around a Clang default-argument/...
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,...