v2.0.0
Loading...
Searching...
No Matches
maxwell_movement_comp.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
18
19//=============================================================================================================
20// QT INCLUDES
21//=============================================================================================================
22
23#include <QFile>
24#include <QTextStream>
25#include <QDebug>
26#include <QRegularExpression>
27
28//=============================================================================================================
29// USED NAMESPACES
30//=============================================================================================================
31
32using namespace UTILSLIB;
33using namespace FIFFLIB;
34using namespace Eigen;
35
36//=============================================================================================================
37// DEFINE MEMBER METHODS
38//=============================================================================================================
39
40FiffInfo MaxwellMovementComp::transformFiffInfo(const FiffInfo& fiffInfo,
41 const HeadPosEntry& headPosRef,
42 const HeadPosEntry& headPosCurrent)
43{
44 FiffInfo info(fiffInfo);
45
46 // Compute relative rotation: from current → ref
47 // R_rel = R_ref * R_current^-1
48 Matrix3d rotRef = headPosRef.rotation.toRotationMatrix();
49 Matrix3d rotCur = headPosCurrent.rotation.toRotationMatrix();
50 Matrix3d rotRel = rotRef * rotCur.transpose();
51
52 // Translation: t_rel = t_ref - R_rel * t_current
53 Vector3d tRel = headPosRef.translation - rotRel * headPosCurrent.translation;
54
55 // Apply transform to MEG channel positions
56 for (int i = 0; i < info.chs.size(); ++i) {
57 if (info.chs[i].kind == FIFFV_MEG_CH || info.chs[i].kind == FIFFV_REF_MEG_CH) {
58 // Transform position
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>();
63
64 // Transform orientation vectors
65 Vector3f ex = info.chs[i].chpos.ex;
66 Vector3d exd = ex.cast<double>();
67 exd = rotRel * exd;
68 info.chs[i].chpos.ex = exd.cast<float>();
69
70 Vector3f ey = info.chs[i].chpos.ey;
71 Vector3d eyd = ey.cast<double>();
72 eyd = rotRel * eyd;
73 info.chs[i].chpos.ey = eyd.cast<float>();
74
75 Vector3f ez = info.chs[i].chpos.ez;
76 Vector3d ezd = ez.cast<double>();
77 ezd = rotRel * ezd;
78 info.chs[i].chpos.ez = ezd.cast<float>();
79 }
80 }
81
82 return info;
83}
84
85//=============================================================================================================
86
87MatrixXd MaxwellMovementComp::apply(const MatrixXd& matData,
88 const FiffInfo& fiffInfo,
89 const QList<HeadPosEntry>& headPos,
90 double dSFreq,
91 const MaxwellMoveCompParams& params)
92{
93 if (headPos.isEmpty()) {
94 qWarning() << "[MaxwellMovementComp::apply] No head positions provided.";
95 return matData;
96 }
97
98 const int nSamples = matData.cols();
99
100 // Determine reference head position
101 HeadPosEntry refPos;
102 if (params.iRefIdx >= 0 && params.iRefIdx < headPos.size()) {
103 refPos = headPos[params.iRefIdx];
104 } else {
105 // Mean position
106 Vector3d meanTrans = Vector3d::Zero();
107 for (const auto& hp : headPos) {
108 meanTrans += hp.translation;
109 }
110 meanTrans /= headPos.size();
111 refPos.translation = meanTrans;
112 refPos.rotation = headPos[0].rotation; // Use first rotation as reference
113 }
114
115 // Compute reference SSS basis
116 SSSParams sssParams;
117 sssParams.iOrderIn = params.iOrderIn;
118 sssParams.iOrderOut = params.iOrderOut;
119 sssParams.origin = params.origin;
120 sssParams.dRegIn = params.dRegIn;
121
122 SSS::Basis basisRef = SSS::computeBasis(fiffInfo, sssParams);
123
124 MatrixXd result = matData;
125
126 // Process each time segment
127 for (int iSeg = 0; iSeg < headPos.size(); ++iSeg) {
128 // Determine sample range for this segment
129 int iStart = static_cast<int>(headPos[iSeg].dTime * dSFreq);
130 int iEnd;
131 if (iSeg + 1 < headPos.size()) {
132 iEnd = static_cast<int>(headPos[iSeg + 1].dTime * dSFreq);
133 } else {
134 iEnd = nSamples;
135 }
136
137 iStart = qBound(0, iStart, nSamples);
138 iEnd = qBound(iStart, iEnd, nSamples);
139 if (iStart >= iEnd)
140 continue;
141
142 int nSegSamples = iEnd - iStart;
143
144 // Transform FiffInfo to current head position
145 FiffInfo infoCurrentToRef = transformFiffInfo(fiffInfo, refPos, headPos[iSeg]);
146
147 // Compute SSS basis at current head position (as seen from reference)
148 SSS::Basis basisCurrent = SSS::computeBasis(infoCurrentToRef, sssParams);
149
150 // Extract segment data
151 MatrixXd segData = matData.middleCols(iStart, nSegSamples);
152
153 // Project into multipole space using current-position basis
154 // multipoles = pinv_current * data_meg
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]);
158 }
159
160 MatrixXd multipoles = basisCurrent.matPinvAll * megData;
161
162 // Reconstruct at reference position using only internal multipoles
163 MatrixXd reconstructed = basisRef.matSin * multipoles.topRows(basisRef.iNin);
164
165 // Place reconstructed data back
166 for (int ch = 0; ch < basisRef.megChannelIdx.size(); ++ch) {
167 result.row(basisRef.megChannelIdx[ch]).segment(iStart, nSegSamples) = reconstructed.row(ch);
168 }
169 }
170
171 return result;
172}
173
174//=============================================================================================================
175
176QList<HeadPosEntry> MaxwellMovementComp::readHeadPos(const QString& sPath)
177{
178 QList<HeadPosEntry> positions;
179
180 QFile file(sPath);
181 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
182 qWarning() << "[MaxwellMovementComp::readHeadPos] Cannot open file:" << sPath;
183 return positions;
184 }
185
186 QTextStream in(&file);
187 while (!in.atEnd()) {
188 QString line = in.readLine().trimmed();
189 if (line.isEmpty() || line.startsWith('#'))
190 continue;
191
192 QStringList parts = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
193 if (parts.size() < 7)
194 continue;
195
196 bool ok = false;
197 HeadPosEntry entry;
198 entry.dTime = parts[0].toDouble(&ok);
199 if (!ok)
200 continue;
201
202 // Quaternion (scalar-last or scalar-first depends on convention)
203 double q1 = parts[1].toDouble(&ok);
204 if (!ok)
205 continue;
206 double q2 = parts[2].toDouble(&ok);
207 if (!ok)
208 continue;
209 double q3 = parts[3].toDouble(&ok);
210 if (!ok)
211 continue;
212
213 double tx = parts[4].toDouble(&ok);
214 if (!ok)
215 continue;
216 double ty = parts[5].toDouble(&ok);
217 if (!ok)
218 continue;
219 double tz = parts[6].toDouble(&ok);
220 if (!ok)
221 continue;
222
223 if (parts.size() >= 8) {
224 entry.dGof = parts[7].toDouble();
225 }
226
227 // MNE convention: quaternion stored as (q1, q2, q3) with q0 computed
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);
231 entry.translation = Vector3d(tx, ty, tz);
232
233 positions.append(entry);
234 }
235
236 file.close();
237 return positions;
238}
239
240//=============================================================================================================
241
242bool MaxwellMovementComp::writeHeadPos(const QString& sPath,
243 const QList<HeadPosEntry>& headPos)
244{
245 QFile file(sPath);
246 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
247 qWarning() << "[MaxwellMovementComp::writeHeadPos] Cannot open file:" << sPath;
248 return false;
249 }
250
251 QTextStream out(&file);
252 out << "# Head position file\n";
253 out << "# time q1 q2 q3 tx ty tz gof\n";
254
255 for (const auto& entry : headPos) {
256 // Store quaternion as (q1, q2, q3), dropping q0 (recoverable)
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";
265 }
266
267 file.close();
268 return true;
269}
#define FIFFV_REF_MEG_CH
#define FIFFV_MEG_CH
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).
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 &params=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/...
Definition sss.h:85
double dRegIn
Definition sss.h:89
Eigen::Vector3d origin
Definition sss.h:88
static Basis computeBasis(const FIFFLIB::FiffInfo &fiffInfo, const Params &params=Params())
Definition sss.cpp:251
Precomputed SSS basis and projectors for a given sensor array.
Definition sss.h:114
Eigen::MatrixXd matSin
Definition sss.h:115
Eigen::MatrixXd matPinvAll
Definition sss.h:118
QVector< int > megChannelIdx
Definition sss.h:119
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:90