51constexpr double CSD_PI = 3.14159265358979323846;
58MatrixXd SurfaceLaplacian::evaluateLegendre(
const MatrixXd& matX,
int iMaxOrder)
62 const Index nElements = matX.size();
63 MatrixXd result(iMaxOrder + 1, nElements);
66 result.row(0).setOnes();
70 for (Index i = 0; i < nElements; ++i)
71 result(1, i) = matX.data()[i];
75 for (
int n = 1; n < iMaxOrder; ++n) {
76 const double a =
static_cast<double>(2 * n + 1) /
static_cast<double>(n + 1);
77 const double b =
static_cast<double>(n) /
static_cast<double>(n + 1);
78 for (Index i = 0; i < nElements; ++i)
79 result(n + 1, i) = a * matX.data()[i] * result(n, i) - b * result(n - 1, i);
87MatrixXd SurfaceLaplacian::computeG(
const MatrixXd& matCosAng,
92 const Index nRows = matCosAng.rows();
93 const Index nCols = matCosAng.cols();
96 MatrixXd legP = evaluateLegendre(matCosAng, iNLegendreTerms);
99 MatrixXd G = MatrixXd::Zero(nRows, nCols);
101 for (
int n = 1; n <= iNLegendreTerms; ++n) {
102 const double factor =
static_cast<double>(2 * n + 1) / (std::pow(
static_cast<double>(n), iStiffness) * std::pow(
static_cast<double>(n + 1), iStiffness) * 4.0 * CSD_PI);
104 for (Index i = 0; i < nRows * nCols; ++i)
105 G.data()[i] += factor * legP(n, i);
113MatrixXd SurfaceLaplacian::computeH(
const MatrixXd& matCosAng,
118 const Index nRows = matCosAng.rows();
119 const Index nCols = matCosAng.cols();
121 MatrixXd legP = evaluateLegendre(matCosAng, iNLegendreTerms);
123 MatrixXd H = MatrixXd::Zero(nRows, nCols);
125 for (
int n = 1; n <= iNLegendreTerms; ++n) {
126 const double factor =
static_cast<double>(2 * n + 1) / (std::pow(
static_cast<double>(n), iStiffness - 1) * std::pow(
static_cast<double>(n + 1), iStiffness - 1) * 4.0 * CSD_PI);
128 for (Index i = 0; i < nRows * nCols; ++i)
129 H.data()[i] += factor * legP(n, i);
141 double dSphereRadius)
143 const int nCh =
static_cast<int>(matPositions.rows());
145 qWarning() <<
"[SurfaceLaplacian::computeTransform] Need at least 2 channels.";
152 Vector3d centre = Vector3d::Zero();
153 if (dSphereRadius <= 0.0) {
155 centre = sphere.
center().cast<
double>();
156 dSphereRadius = sphere.
radius();
158 MatrixX3d posCentered = matPositions.rowwise() - centre.transpose();
161 MatrixX3d posNorm(nCh, 3);
162 for (
int i = 0; i < nCh; ++i) {
163 double norm = posCentered.row(i).norm();
165 posNorm.row(i) = posCentered.row(i) / norm;
167 posNorm.row(i).setZero();
171 MatrixXd cosAng = posNorm * posNorm.transpose();
174 cosAng = cosAng.cwiseMax(-1.0).cwiseMin(1.0);
177 MatrixXd G = computeG(cosAng, iStiffness, iNLegendreTerms);
178 MatrixXd H = computeH(cosAng, iStiffness, iNLegendreTerms);
181 for (
int i = 0; i < nCh; ++i)
185 MatrixXd Gi = G.inverse();
188 VectorXd TC = Gi.colwise().sum();
189 double sgi = TC.sum();
193 MatrixXd
Z = MatrixXd::Identity(nCh, nCh);
194 Z.array() -= 1.0 /
static_cast<double>(nCh);
197 MatrixXd Cp2 = Gi *
Z;
200 RowVectorXd c02 = Cp2.colwise().sum() / sgi;
203 MatrixXd C2 = Cp2 - TC * c02;
206 MatrixXd
X = (H.transpose() * C2) / (dSphereRadius * dSphereRadius);
214 const MatrixX3d& matPositions,
218 double dSphereRadius)
222 if (matData.rows() != matPositions.rows()) {
223 qWarning() <<
"[SurfaceLaplacian::compute] Data rows" << matData.rows()
224 <<
"!= position rows" << matPositions.rows();
229 iNLegendreTerms, dSphereRadius);
Spherical-spline surface Laplacian (Current Source Density) for EEG.
Best-fit sphere from a 3-D point cloud with closed-form and Nelder–Mead solvers.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
Result of a surface Laplacian (CSD) computation.
Eigen::MatrixXd matData
Transformed data (n_eeg_channels × n_times).
Eigen::MatrixXd matTransform
CSD transformation matrix (n_eeg × n_eeg).
static SurfaceLaplacianResult compute(const Eigen::MatrixXd &matData, const Eigen::MatrixX3d &matPositions, double dLambda2=1e-5, int iStiffness=4, int iNLegendreTerms=50, double dSphereRadius=-1.0)
static Eigen::MatrixXd computeTransform(const Eigen::MatrixX3d &matPositions, double dLambda2=1e-5, int iStiffness=4, int iNLegendreTerms=50, double dSphereRadius=-1.0)
3-D sphere value type with algebraic and Nelder–Mead best-fit factories.
Eigen::Vector3f & center()
static Sphere fit_sphere(const Eigen::MatrixX3f &points)