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
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 whitener = invSqrtEig.asDiagonal() * noiseCov.eigvec.transpose();
106 } else {
107 whitener = MatrixXd::Identity(nChannels, nChannels);
108 }
109
110 MatrixXd projMat = MatrixXd::Identity(nChannels, nChannels);
111
112 // Whiten leadfield (shared across all frequencies)
113 MatrixXd Gw = whitener * G;
114
115 // Source normals
116 MatrixX3d nn = forward.source_nn.cast<double>();
117
118 // -----------------------------------------------------------------------
119 // Compute filter for each frequency
120 // -----------------------------------------------------------------------
121 for(int fi = 0; fi < nFreqs; ++fi) {
122 MatrixXd Cm = csdMatrices[fi];
123
124 if(Cm.rows() != nChannels || Cm.cols() != nChannels) {
125 qWarning("InvDICS::makeDICS - CSD[%d] dimension mismatch!", fi);
126 return InvBeamformer();
127 }
128
129 // Optional: take real part of CSD
130 if(realFilter) {
131 // CSD is provided as real-valued after user extracts real part,
132 // or we ensure it here
133 Cm = Cm.real();
134 }
135
136 // Whiten CSD
137 MatrixXd CmW = whitener * Cm * whitener.transpose();
138 CmW = (CmW + CmW.transpose()) * 0.5; // Ensure symmetry
139
140 // Compute filter
141 MatrixXd W;
142 MatrixX3d mpOri;
143
145 Gw, CmW, reg, nOrient,
146 weightNorm, pickOri, reduceRank, invMethod,
147 nn, W, mpOri);
148
149 if(!ok) {
150 qWarning("InvDICS::makeDICS - Filter computation failed at frequency %d (%.1f Hz)!",
151 fi, frequencies(fi));
152 return InvBeamformer();
153 }
154
155 result.weights.push_back(W);
156
157 // Store max-power orientation from first frequency
158 if(fi == 0 && pickOri == BeamformerPickOri::MaxPower) {
159 result.maxPowerOri = mpOri;
160 }
161 }
162
163 // -----------------------------------------------------------------------
164 // Populate metadata
165 // -----------------------------------------------------------------------
166 result.whitener = whitener;
167 result.proj = projMat;
168 result.chNames = forward.sol->row_names;
169 result.isFreOri = (nOrient == 3 && pickOri != BeamformerPickOri::Normal
170 && pickOri != BeamformerPickOri::MaxPower);
171 result.nSourcesTotal = nSources;
172 result.srcType = "surface";
173 result.weightNorm = weightNorm;
174 result.pickOri = pickOri;
175 result.inversion = invMethod;
176 result.reg = reg;
177 result.rank = static_cast<int>(nChannels);
178 result.sourceNn = forward.source_nn;
179 result.frequencies = frequencies;
180
181 VectorXi verts(0);
182 if(forward.src.size() >= 2) {
183 verts.resize(forward.src[0].vertno.size() + forward.src[1].vertno.size());
184 verts << forward.src[0].vertno, forward.src[1].vertno;
185 } else if(forward.src.size() == 1) {
186 verts = forward.src[0].vertno;
187 }
188 result.vertices = verts;
189
190 qInfo("InvDICS::makeDICS - Done. %d frequency filters computed.", nFreqs);
191
192 return result;
193}
194
195//=============================================================================================================
196
197InvSourceEstimate InvDICS::applyDICSCsd(const std::vector<MatrixXd> &csdMatrices,
198 const VectorXd &frequencies,
199 const InvBeamformer &filters)
200{
201 if(!filters.isValid() || filters.kind != "DICS") {
202 qWarning("InvDICS::applyDICSCsd - Invalid or non-DICS filters!");
203 return InvSourceEstimate();
204 }
205
206 const int nFreqs = static_cast<int>(csdMatrices.size());
207 const int nFilterFreqs = filters.nFreqs();
208
209 if(nFreqs != nFilterFreqs) {
210 qWarning("InvDICS::applyDICSCsd - CSD count (%d) does not match filter count (%d)!",
211 nFreqs, nFilterFreqs);
212 return InvSourceEstimate();
213 }
214
215 const int nOrient = filters.nOrient();
216 const int nSources = filters.nSources();
217 const int nChannels = filters.nChannels();
218
219 // Power matrix: (nSources, nFreqs)
220 MatrixXd powerMat(nSources, nFreqs);
221
222 for(int fi = 0; fi < nFreqs; ++fi) {
223 MatrixXd Cm = csdMatrices[fi];
224
225 // Whiten CSD
226 if(filters.whitener.size() > 0) {
227 Cm = filters.whitener * Cm * filters.whitener.transpose();
228 }
229
230 VectorXd power = InvBeamformerCompute::computePower(Cm, filters.weights[fi], nOrient);
231 powerMat.col(fi) = power;
232 }
233
234 // Use frequency as "time" axis for the source estimate
235 float fmin = (frequencies.size() > 0) ? static_cast<float>(frequencies(0)) : 0.0f;
236 float fstep = (frequencies.size() > 1)
237 ? static_cast<float>(frequencies(1) - frequencies(0))
238 : 1.0f;
239
240 InvSourceEstimate stc(powerMat, filters.vertices, fmin, fstep);
244
245 return stc;
246}
247
248//=============================================================================================================
249
251 float tmin,
252 float tstep,
253 const InvBeamformer &filters,
254 int freqIdx)
255{
256 if(!filters.isValid() || filters.kind != "DICS") {
257 qWarning("InvDICS::applyDICS - Invalid or non-DICS filters!");
258 return InvSourceEstimate();
259 }
260 if(freqIdx < 0 || freqIdx >= filters.nFreqs()) {
261 qWarning("InvDICS::applyDICS - freqIdx %d out of range (0..%d)!",
262 freqIdx, filters.nFreqs() - 1);
263 return InvSourceEstimate();
264 }
265
266 // Apply projection + whitening + spatial filter
267 MatrixXd processed = data;
268 if(filters.proj.size() > 0 && filters.proj.rows() == data.rows()) {
269 processed = filters.proj * processed;
270 }
271 if(filters.whitener.size() > 0 && filters.whitener.rows() == processed.rows()) {
272 processed = filters.whitener * processed;
273 }
274
275 MatrixXd sol = filters.weights[freqIdx] * processed;
276
277 // Combine XYZ if needed
278 const int nOrient = filters.nOrient();
279 if(nOrient == 3 && filters.pickOri != BeamformerPickOri::Vector) {
280 const int nSources = static_cast<int>(sol.rows()) / 3;
281 const int nTimes = static_cast<int>(sol.cols());
282 MatrixXd combined(nSources, nTimes);
283 for(int s = 0; s < nSources; ++s) {
284 combined.row(s) = sol.middleRows(s * 3, 3).colwise().norm();
285 }
286 sol = combined;
287 }
288
289 InvSourceEstimate stc(sol, filters.vertices, tmin, tstep);
293
294 return stc;
295}
296
297//=============================================================================================================
298
299QList<InvSourceEstimate> InvDICS::applyDICSEpochs(const QList<MatrixXd> &epochs,
300 float tmin,
301 float tstep,
302 const InvBeamformer &filters,
303 int freqIdx)
304{
305 QList<InvSourceEstimate> results;
306
307 if (epochs.isEmpty()) {
308 qWarning("InvDICS::applyDICSEpochs - No epochs provided.");
309 return results;
310 }
311 if (!filters.isValid() || filters.kind != "DICS") {
312 qWarning("InvDICS::applyDICSEpochs - Invalid or non-DICS filters!");
313 return results;
314 }
315
316 for (int i = 0; i < epochs.size(); ++i) {
317 InvSourceEstimate stc = applyDICS(epochs[i], tmin, tstep, filters, freqIdx);
318 if (stc.isEmpty()) {
319 qWarning("InvDICS::applyDICSEpochs - Epoch %d produced empty source estimate.", i);
320 }
321 results.append(stc);
322 }
323
324 return results;
325}
Forward solution (gain matrix mapping source dipoles to sensor measurements).
#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...
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:79
Eigen::MatrixXd eigvec
Definition fiff_cov.h:253
Eigen::VectorXd eig
Definition fiff_cov.h:252
Eigen::MatrixXd data
Definition fiff_cov.h:248
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:88
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:250
static QList< InvSourceEstimate > applyDICSEpochs(const QList< Eigen::MatrixXd > &epochs, float tmin, float tstep, const InvBeamformer &filters, int freqIdx=0)
Definition inv_dics.cpp:299
static InvSourceEstimate applyDICSCsd(const std::vector< Eigen::MatrixXd > &csdMatrices, const Eigen::VectorXd &frequencies, const InvBeamformer &filters)
Definition inv_dics.cpp:197
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