45#include <Eigen/Eigenvalues>
65 : m_nComponents(nComponents)
66 , m_regParam(regParam)
74 double signalLow,
double signalHigh,
75 double noiseLow,
double noiseHigh)
77 if (noiseLow > signalLow || signalHigh > noiseHigh) {
78 throw std::invalid_argument(
79 "DecodingSsd::fit: signal band must be within noise band");
82 const auto n_ch = data.rows();
83 const auto n_times = data.cols();
85 if (n_ch < 2 || n_times < 2) {
86 throw std::invalid_argument(
87 "DecodingSsd::fit: data must have >= 2 channels and >= 2 time points");
90 int n_comp = std::min(m_nComponents,
static_cast<int>(n_ch));
93 MatrixXd centered = data.colwise() - data.rowwise().mean();
96 MatrixXd data_signal = bandpassFilter(centered, sfreq, signalLow, signalHigh);
97 MatrixXd data_noise = bandpassFilter(centered, sfreq, noiseLow, noiseHigh);
100 MatrixXd cov_signal = (data_signal * data_signal.transpose())
101 /
static_cast<double>(n_times - 1);
102 MatrixXd cov_noise = (data_noise * data_noise.transpose())
103 /
static_cast<double>(n_times - 1);
106 cov_noise += m_regParam * cov_noise.trace()
107 /
static_cast<double>(n_ch)
108 * MatrixXd::Identity(n_ch, n_ch);
111 GeneralizedSelfAdjointEigenSolver<MatrixXd> ges(cov_signal, cov_noise);
112 if (ges.info() != Eigen::Success) {
113 throw std::runtime_error(
114 "DecodingSsd::fit: eigenvalue decomposition failed");
118 VectorXd evals = ges.eigenvalues().reverse();
119 MatrixXd evecs = ges.eigenvectors().rowwise().reverse();
121 m_eigenvalues = evals.head(n_comp);
122 m_filters = evecs.leftCols(n_comp).transpose();
125 MatrixXd cov_full = (centered * centered.transpose())
126 /
static_cast<double>(n_times - 1);
127 MatrixXd WtCW = m_filters * cov_full * m_filters.transpose();
128 m_patterns = (cov_full * m_filters.transpose()
139 throw std::runtime_error(
"DecodingSsd::transform: not fitted");
141 return m_filters * data;
148 double signalLow,
double signalHigh,
149 double noiseLow,
double noiseHigh)
151 fit(data, sfreq, signalLow, signalHigh, noiseLow, noiseHigh);
160 throw std::runtime_error(
"DecodingSsd::apply: not fitted");
166 return m_patterns * ssdSources;
174 throw std::runtime_error(
"DecodingSsd::filters: not fitted");
184 throw std::runtime_error(
"DecodingSsd::patterns: not fitted");
194 throw std::runtime_error(
"DecodingSsd::eigenvalues: not fitted");
196 return m_eigenvalues;
208MatrixXd DecodingSsd::bandpassFilter(
const MatrixXd& data,
213 const auto n_ch = data.rows();
214 const auto n_times = data.cols();
216 int filter_order = std::min(
217 static_cast<int>(std::round(3.0 * sfreq / lowFreq)),
218 static_cast<int>(n_times) - 1);
219 if (filter_order % 2 != 0) ++filter_order;
220 filter_order = std::min(filter_order, 128);
222 const int half = filter_order / 2;
223 const double pi =
M_PI;
226 VectorXd h(filter_order + 1);
227 double w_low = 2.0 * pi * lowFreq / sfreq;
228 double w_high = 2.0 * pi * highFreq / sfreq;
230 for (
int i = 0; i <= filter_order; ++i) {
233 h(i) = (w_high - w_low) / pi;
235 h(i) = (std::sin(w_high *
static_cast<double>(n))
236 - std::sin(w_low *
static_cast<double>(n)))
237 / (pi *
static_cast<double>(n));
242 2.0 * pi *
static_cast<double>(i)
243 /
static_cast<double>(filter_order));
247 double center = (lowFreq + highFreq) / 2.0;
248 double w_center = 2.0 * pi * center / sfreq;
250 for (
int i = 0; i <= filter_order; ++i) {
251 gain += h(i) * std::cos(
252 w_center *
static_cast<double>(i - half));
254 if (std::abs(gain) > 1e-12) h /= std::abs(gain);
257 MatrixXd result(n_ch, n_times);
259 for (Index ch = 0; ch < n_ch; ++ch) {
260 VectorXd forward(n_times);
261 for (Index t = 0; t < n_times; ++t) {
263 for (
int k = 0; k <= filter_order; ++k) {
265 if (idx >= 0 && idx < n_times)
266 sum += h(k) * data(ch, idx);
271 VectorXd backward(n_times);
272 for (Index t = n_times - 1; t >= 0; --t) {
274 for (
int k = 0; k <= filter_order; ++k) {
276 if (idx >= 0 && idx < n_times)
277 sum += h(k) * forward(idx);
282 result.row(ch) = backward.transpose();
Spatio-Spectral Decomposition (SSD) for noise-aware narrowband enhancement of continuous M/EEG.
Supervised and unsupervised spatial-filter decompositions for M/EEG decoding.
DecodingSsd(int nComponents=6, double regParam=0.05)
Eigen::MatrixXd apply(const Eigen::Ref< const Eigen::MatrixXd > &data) const
const Eigen::MatrixXd & filters() const
const Eigen::VectorXd & eigenvalues() const
Eigen::MatrixXd transform(const Eigen::Ref< const Eigen::MatrixXd > &data) const
const Eigen::MatrixXd & patterns() const
void fit(const Eigen::Ref< const Eigen::MatrixXd > &data, double sfreq, double signalLow, double signalHigh, double noiseLow, double noiseHigh)
Eigen::MatrixXd fitTransform(const Eigen::Ref< const Eigen::MatrixXd > &data, double sfreq, double signalLow, double signalHigh, double noiseLow, double noiseHigh)