77 throw std::invalid_argument(
"DecodingCsp::fit: epochs must be non-empty");
79 if (
static_cast<int>(epochs.size()) != y.size()) {
80 throw std::invalid_argument(
81 "DecodingCsp::fit: epochs and y must have the same length");
85 std::set<int> classSet(y.data(), y.data() + y.size());
86 if (classSet.size() != 2) {
87 throw std::invalid_argument(
88 "DecodingCsp::fit: y must contain exactly 2 unique class labels");
91 auto it = classSet.begin();
96 std::vector<MatrixXd> epochs1, epochs2;
97 for (
int i = 0; i < y.size(); ++i) {
99 epochs1.push_back(epochs[
static_cast<size_t>(i)]);
101 epochs2.push_back(epochs[
static_cast<size_t>(i)]);
105 const auto n_ch = epochs1[0].rows();
108 MatrixXd cov1 = MatrixXd::Zero(n_ch, n_ch);
109 for (
const auto& epoch : epochs1) {
110 MatrixXd centered = epoch.colwise() - epoch.rowwise().mean();
111 cov1 += centered * centered.transpose()
112 /
static_cast<double>(epoch.cols() - 1);
114 cov1 /=
static_cast<double>(epochs1.size());
116 MatrixXd cov2 = MatrixXd::Zero(n_ch, n_ch);
117 for (
const auto& epoch : epochs2) {
118 MatrixXd centered = epoch.colwise() - epoch.rowwise().mean();
119 cov2 += centered * centered.transpose()
120 /
static_cast<double>(epoch.cols() - 1);
122 cov2 /=
static_cast<double>(epochs2.size());
125 MatrixXd cov_comp = cov1 + cov2;
128 SelfAdjointEigenSolver<MatrixXd> eig_comp(cov_comp);
129 VectorXd d = eig_comp.eigenvalues();
130 MatrixXd U = eig_comp.eigenvectors();
132 const double d_min = d.maxCoeff() * 1e-10;
133 for (Index i = 0; i < d.size(); ++i) {
134 if (d(i) < d_min) d(i) = d_min;
137 MatrixXd W = d.array().sqrt().inverse().matrix().asDiagonal()
141 MatrixXd S1 = W * cov1 * W.transpose();
142 SelfAdjointEigenSolver<MatrixXd> eig_s1(S1);
143 VectorXd lambdas = eig_s1.eigenvalues();
144 MatrixXd B = eig_s1.eigenvectors();
147 MatrixXd all_filters = B.transpose() * W;
150 int n_per_class = m_nComponents / 2;
151 int n_total = std::min(m_nComponents,
static_cast<int>(n_ch));
152 n_per_class = std::min(n_per_class,
static_cast<int>(n_ch) / 2);
154 m_filters = MatrixXd(n_total, n_ch);
155 VectorXd eigenvalues(n_total);
158 for (
int i = 0; i < n_per_class; ++i) {
159 m_filters.row(i) = all_filters.row(i);
160 eigenvalues(i) = lambdas(i);
163 for (
int i = 0; i < n_total - n_per_class; ++i) {
164 m_filters.row(n_per_class + i) =
165 all_filters.row(
static_cast<int>(n_ch) - 1 - i);
166 eigenvalues(n_per_class + i) =
167 lambdas(
static_cast<int>(n_ch) - 1 - i);
171 auto svd = m_filters.bdcSvd<ComputeThinU | ComputeThinV>();
172 m_patterns =
svd.solve(MatrixXd::Identity(n_total, n_total));
175 MatrixXd powerFeatures = computePowerFeatures(epochs);
176 m_mean = powerFeatures.colwise().mean();
178 VectorXd centered = VectorXd(powerFeatures.rows());
179 m_std = VectorXd(powerFeatures.cols());
180 for (
int c = 0; c < powerFeatures.cols(); ++c) {
181 centered = powerFeatures.col(c).array() - m_mean(c);
182 m_std(c) = std::sqrt(centered.squaredNorm()
183 /
static_cast<double>(centered.size()));
194 throw std::runtime_error(
"DecodingCsp::transform: not fitted");
199 const int nEpochs =
static_cast<int>(epochs.size());
200 const int nComp =
static_cast<int>(m_filters.rows());
201 const int nTimes =
static_cast<int>(epochs[0].cols());
203 MatrixXd result(nEpochs * nComp, nTimes);
204 for (
int e = 0; e < nEpochs; ++e) {
205 result.middleRows(
static_cast<Eigen::Index
>(e) * nComp, nComp) = m_filters * epochs[
static_cast<size_t>(e)];
211 MatrixXd
X = computePowerFeatures(epochs);
214 X =
X.array().max(1e-30).log().matrix();
217 for (
int c = 0; c <
X.cols(); ++c) {
219 if (s < 1e-15) s = 1.0;
220 X.col(c) = (
X.col(c).array() - m_mean(c)) / s;