v2.0.0
Loading...
Searching...
No Matches
inv_dics.cpp
Go to the documentation of this file.
1//=============================================================================================================
20
21//=============================================================================================================
22// INCLUDES
23//=============================================================================================================
24
25#include "inv_dics.h"
27
29#include <fiff/fiff_cov.h>
30#include <fiff/fiff_info.h>
31
32#include <QDebug>
33
34//=============================================================================================================
35// EIGEN INCLUDES
36//=============================================================================================================
37
38#include <Eigen/Dense>
39
40//=============================================================================================================
41// USED NAMESPACES
42//=============================================================================================================
43
44using namespace Eigen;
45using namespace INVLIB;
46using namespace MNELIB;
47using namespace FIFFLIB;
48
49//=============================================================================================================
50// DEFINE MEMBER METHODS
51//=============================================================================================================
52
53InvBeamformer InvDICS::makeDICS([[maybe_unused]] const FiffInfo& info,
54 const MNEForwardSolution& forward,
55 const std::vector<MatrixXd>& csdMatrices,
56 const VectorXd& frequencies,
57 double reg,
58 bool realFilter,
59 const FiffCov& noiseCov,
60 BeamformerPickOri pickOri,
61 BeamformerWeightNorm weightNorm,
62 bool reduceRank,
63 BeamformerInversion invMethod)
64{
65 InvBeamformer result;
66 result.kind = "DICS";
67
68 const int nFreqs = static_cast<int>(csdMatrices.size());
69 if (nFreqs == 0) {
70 qWarning("InvDICS::makeDICS - No CSD matrices provided!");
71 return result;
72 }
73 if (frequencies.size() != nFreqs) {
74 qWarning("InvDICS::makeDICS - Frequency vector size mismatch with CSD count!");
75 return result;
76 }
77
78 // -----------------------------------------------------------------------
79 // Extract leadfield
80 // -----------------------------------------------------------------------
81 if (!forward.sol || forward.sol->data.size() == 0) {
82 qWarning("InvDICS::makeDICS - Forward solution has no gain matrix!");
83 return result;
84 }
85
86 MatrixXd G = forward.sol->data;
87 const int nChannels = static_cast<int>(G.rows());
88 const int nOrient = (forward.source_ori == FIFFV_MNE_FREE_ORI) ? 3 : 1;
89 const int nSources = static_cast<int>(G.cols()) / nOrient;
90
91 qInfo("InvDICS::makeDICS - Leadfield: %d channels x %d sources (n_orient=%d), %d frequencies",
92 nChannels, nSources, nOrient, nFreqs);
93
94 // -----------------------------------------------------------------------
95 // Whitening matrix
96 // -----------------------------------------------------------------------
97 MatrixXd whitener;
98 if (noiseCov.data.size() > 0 && noiseCov.eig.size() > 0 && noiseCov.eigvec.size() > 0) {
99 VectorXd invSqrtEig(noiseCov.eig.size());
100 for (int i = 0; i < noiseCov.eig.size(); ++i) {
101 invSqrtEig(i) = (noiseCov.eig(i) > 1e-30)
102 ? 1.0 / std::sqrt(noiseCov.eig(i))
103 : 0.0;
104 }
105 // Rows of FiffCov::eigvec are the eigenvectors.
106 whitener = invSqrtEig.asDiagonal() * noiseCov.eigvec;
107 } else {
108 whitener = MatrixXd::Identity(nChannels, nChannels);
109 }
110
111 MatrixXd projMat = MatrixXd::Identity(nChannels, nChannels);
112
113 // Whiten leadfield (shared across all frequencies)
114 MatrixXd Gw = whitener * G;
115
116 // Source normals
117 MatrixX3d nn = forward.source_nn.cast<double>();
118 if (nOrient == 3 && nn.rows() == 3 * nSources) {
119 // Free orientation stores three rows per source; mne-python uses nn[2::3].
120 MatrixX3d perSource(nSources, 3);
121 for (int s = 0; s < nSources; ++s)
122 perSource.row(s) = nn.row(3 * s + 2);
123 nn = perSource;
124 }
125
126 // -----------------------------------------------------------------------
127 // Compute filter for each frequency
128 // -----------------------------------------------------------------------
129 for (int fi = 0; fi < nFreqs; ++fi) {
130 MatrixXd Cm = csdMatrices[fi];
131
132 if (Cm.rows() != nChannels || Cm.cols() != nChannels) {
133 qWarning("InvDICS::makeDICS - CSD[%d] dimension mismatch!", fi);
134 return InvBeamformer();
135 }
136
137 // Optional: take real part of CSD
138 if (realFilter) {
139 // CSD is provided as real-valued after user extracts real part,
140 // or we ensure it here
141 Cm = Cm.real();
142 }
143
144 // Whiten CSD
145 MatrixXd CmW = whitener * Cm * whitener.transpose();
146 CmW = (CmW + CmW.transpose()) * 0.5; // Ensure symmetry
147
148 // Compute filter
149 MatrixXd W;
150 MatrixX3d mpOri;
151
153 Gw, CmW, reg, nOrient,
154 weightNorm, pickOri, reduceRank, invMethod,
155 nn, W, mpOri);
156
157 if (!ok) {
158 qWarning("InvDICS::makeDICS - Filter computation failed at frequency %d (%.1f Hz)!",
159 fi, frequencies(fi));
160 return InvBeamformer();
161 }
162
163 result.weights.push_back(W);
164
165 // Store max-power orientation from first frequency
166 if (fi == 0 && pickOri == BeamformerPickOri::MaxPower) {
167 result.maxPowerOri = mpOri;
168 }
169 }
170
171 // -----------------------------------------------------------------------
172 // Populate metadata
173 // -----------------------------------------------------------------------
174 result.whitener = whitener;
175 result.proj = projMat;
176 result.chNames = forward.sol->row_names;
177 result.isFreOri = (nOrient == 3 && pickOri != BeamformerPickOri::Normal && pickOri != BeamformerPickOri::MaxPower);
178 result.nSourcesTotal = nSources;
179 result.srcType = "surface";
180 result.weightNorm = weightNorm;
181 result.pickOri = pickOri;
182 result.inversion = invMethod;
183 result.reg = reg;
184 result.rank = static_cast<int>(nChannels);
185 result.sourceNn = forward.source_nn;
186 result.frequencies = frequencies;
187
188 VectorXi verts(0);
189 if (forward.src.size() >= 2) {
190 verts.resize(forward.src[0].vertno.size() + forward.src[1].vertno.size());
191 verts << forward.src[0].vertno, forward.src[1].vertno;
192 // Keep where the left hemisphere ends. The concatenation above is the
193 // only place that knows it, and without it the result cannot be
194 // written as the -lh/-rh pair mne-python expects.
195 result.nVerticesLh = static_cast<int>(forward.src[0].vertno.size());
196 } else if (forward.src.size() == 1) {
197 verts = forward.src[0].vertno;
198 }
199 result.vertices = verts;
200
201 qInfo("InvDICS::makeDICS - Done. %d frequency filters computed.", nFreqs);
202
203 return result;
204}
205
206//=============================================================================================================
207
208InvSourceEstimate InvDICS::applyDICSCsd(const std::vector<MatrixXd>& csdMatrices,
209 const VectorXd& frequencies,
210 const InvBeamformer& filters)
211{
212 if (!filters.isValid() || filters.kind != "DICS") {
213 qWarning("InvDICS::applyDICSCsd - Invalid or non-DICS filters!");
214 return InvSourceEstimate();
215 }
216
217 const int nFreqs = static_cast<int>(csdMatrices.size());
218 const int nFilterFreqs = filters.nFreqs();
219
220 if (nFreqs != nFilterFreqs) {
221 qWarning("InvDICS::applyDICSCsd - CSD count (%d) does not match filter count (%d)!",
222 nFreqs, nFilterFreqs);
223 return InvSourceEstimate();
224 }
225
226 const int nOrient = filters.nOrient();
227 const int nSources = filters.nSources();
228
229
230 // Power matrix: (nSources, nFreqs)
231 MatrixXd powerMat(nSources, nFreqs);
232
233 for (int fi = 0; fi < nFreqs; ++fi) {
234 MatrixXd Cm = csdMatrices[fi];
235
236 // Whiten CSD
237 if (filters.whitener.size() > 0) {
238 Cm = filters.whitener * Cm * filters.whitener.transpose();
239 }
240
241 VectorXd power = InvBeamformerCompute::computePower(Cm, filters.weights[fi], nOrient);
242 powerMat.col(fi) = power;
243 }
244
245 // Use frequency as "time" axis for the source estimate
246 float fmin = (frequencies.size() > 0) ? static_cast<float>(frequencies(0)) : 0.0f;
247 float fstep = (frequencies.size() > 1)
248 ? static_cast<float>(frequencies(1) - frequencies(0))
249 : 1.0f;
250
251 InvSourceEstimate stc(powerMat, filters.vertices, fmin, fstep);
254 stc.nVerticesLh = filters.nVerticesLh;
256
257 return stc;
258}
259
260//=============================================================================================================
261
263 float tmin,
264 float tstep,
265 const InvBeamformer& filters,
266 int freqIdx)
267{
268 if (!filters.isValid() || filters.kind != "DICS") {
269 qWarning("InvDICS::applyDICS - Invalid or non-DICS filters!");
270 return InvSourceEstimate();
271 }
272 if (freqIdx < 0 || freqIdx >= filters.nFreqs()) {
273 qWarning("InvDICS::applyDICS - freqIdx %d out of range (0..%d)!",
274 freqIdx, filters.nFreqs() - 1);
275 return InvSourceEstimate();
276 }
277
278 // Apply projection + whitening + spatial filter
279 MatrixXd processed = data;
280 if (filters.proj.size() > 0 && filters.proj.rows() == data.rows()) {
281 processed = filters.proj * processed;
282 }
283 if (filters.whitener.size() > 0 && filters.whitener.rows() == processed.rows()) {
284 processed = filters.whitener * processed;
285 }
286
287 MatrixXd sol = filters.weights[freqIdx] * processed;
288
289 // Combine XYZ if needed
290 const int nOrient = filters.nOrient();
291 if (nOrient == 3 && filters.pickOri != BeamformerPickOri::Vector) {
292 const int nSources = static_cast<int>(sol.rows()) / 3;
293 const int nTimes = static_cast<int>(sol.cols());
294 MatrixXd combined(nSources, nTimes);
295 for (int s = 0; s < nSources; ++s) {
296 combined.row(s) = sol.middleRows(s * 3, 3).colwise().norm();
297 }
298 sol = combined;
299 }
300
301 InvSourceEstimate stc(sol, filters.vertices, tmin, tstep);
304 stc.nVerticesLh = filters.nVerticesLh;
306
307 return stc;
308}
309
310//=============================================================================================================
311
312QList<InvSourceEstimate> InvDICS::applyDICSEpochs(const QList<MatrixXd>& epochs,
313 float tmin,
314 float tstep,
315 const InvBeamformer& filters,
316 int freqIdx)
317{
318 QList<InvSourceEstimate> results;
319
320 if (epochs.isEmpty()) {
321 qWarning("InvDICS::applyDICSEpochs - No epochs provided.");
322 return results;
323 }
324 if (!filters.isValid() || filters.kind != "DICS") {
325 qWarning("InvDICS::applyDICSEpochs - Invalid or non-DICS filters!");
326 return results;
327 }
328
329 for (int i = 0; i < epochs.size(); ++i) {
330 InvSourceEstimate stc = applyDICS(epochs[i], tmin, tstep, filters, freqIdx);
331 if (stc.isEmpty()) {
332 qWarning("InvDICS::applyDICSEpochs - Epoch %d produced empty source estimate.", i);
333 }
334 results.append(stc);
335 }
336
337 return results;
338}
#define FIFFV_MNE_FREE_ORI
Full FIFF measurement metadata: everything from FIFFB_MEAS / FIFFB_MEAS_INFO needed to interpret a re...
Noise / data covariance matrix as stored under FIFFB_MNE_COV, with channel names, kind,...
Shared math kernels (filter derivation, source power, regularised pseudo-inverse) used by both the LC...
Dynamic Imaging of Coherent Sources (DICS) beamformer — frequency-domain source-power and source-time...
Forward solution (gain matrix mapping source dipoles to sensor measurements).
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
FIFF noise / data covariance: matrix, channel names, kind, applied projectors, bads,...
Definition fiff_cov.h:82
Eigen::MatrixXd eigvec
Definition fiff_cov.h:258
Eigen::VectorXd eig
Definition fiff_cov.h:257
Eigen::MatrixXd data
Definition fiff_cov.h:253
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:90
Computed beamformer spatial filter container.
BeamformerPickOri pickOri
Eigen::VectorXd frequencies
BeamformerWeightNorm weightNorm
Eigen::MatrixX3f sourceNn
BeamformerInversion inversion
Eigen::MatrixX3d maxPowerOri
Eigen::VectorXi vertices
std::vector< Eigen::MatrixXd > weights
Eigen::MatrixXd whitener
static Eigen::VectorXd computePower(const Eigen::MatrixXd &Cm, const Eigen::MatrixXd &W, int nOrient)
static bool computeBeamformer(const Eigen::MatrixXd &G, const Eigen::MatrixXd &Cm, double reg, int nOrient, BeamformerWeightNorm weightNorm, BeamformerPickOri pickOri, bool reduceRank, BeamformerInversion invMethod, const Eigen::MatrixX3d &nn, Eigen::MatrixXd &W, Eigen::MatrixX3d &maxPowerOri)
static InvBeamformer makeDICS(const FIFFLIB::FiffInfo &info, const MNELIB::MNEForwardSolution &forward, const std::vector< Eigen::MatrixXd > &csdMatrices, const Eigen::VectorXd &frequencies, double reg=0.05, bool realFilter=true, const FIFFLIB::FiffCov &noiseCov=FIFFLIB::FiffCov(), BeamformerPickOri pickOri=BeamformerPickOri::None, BeamformerWeightNorm weightNorm=BeamformerWeightNorm::UnitNoiseGain, bool reduceRank=false, BeamformerInversion invMethod=BeamformerInversion::Matrix)
Definition inv_dics.cpp:53
static InvSourceEstimate applyDICS(const Eigen::MatrixXd &data, float tmin, float tstep, const InvBeamformer &filters, int freqIdx=0)
Definition inv_dics.cpp:262
static QList< InvSourceEstimate > applyDICSEpochs(const QList< Eigen::MatrixXd > &epochs, float tmin, float tstep, const InvBeamformer &filters, int freqIdx=0)
Definition inv_dics.cpp:312
static InvSourceEstimate applyDICSCsd(const std::vector< Eigen::MatrixXd > &csdMatrices, const Eigen::VectorXd &frequencies, const InvBeamformer &filters)
Definition inv_dics.cpp:208
Source-space inverse-solution container with dense grid plus optional focal-dipole,...
InvSourceSpaceType sourceSpaceType
InvOrientationType orientationType
In-memory representation of an -fwd.fif forward solution.
MNELIB::MNESourceSpaces src
FIFFLIB::FiffNamedMatrix::SDPtr sol