|
4 | 4 | #include <Logging.h> |
5 | 5 | #include <Particles/ParticleFile.h> |
6 | 6 | #include <Project/Project.h> |
7 | | -#include <vnl/algo/vnl_symmetric_eigensystem.h> |
8 | 7 |
|
9 | 8 | #include "ExternalLibs/tinyxml/tinyxml.h" |
10 | 9 | #include "Profiling.h" |
@@ -292,62 +291,57 @@ void ParticleShapeStatistics::compute_multi_level_analysis_statistics(std::vecto |
292 | 291 |
|
293 | 292 | //--------------------------------------------------------------------------- |
294 | 293 | int ParticleShapeStatistics::compute_shape_dev_modes_for_mca() { |
295 | | - unsigned int m = points_minus_mean_shape_dev_.rows(); |
296 | | - Eigen::MatrixXd A = |
297 | | - points_minus_mean_shape_dev_.transpose() * points_minus_mean_shape_dev_ * (1.0 / ((double)(num_samples_ - 1))); |
| 294 | + // Use SVD to compute PCA. For X = U * S * V^T: |
| 295 | + // - The covariance matrix X^T * X / (n-1) has eigenvalues S^2 / (n-1) and eigenvectors V |
| 296 | + // - The old code computed eigenvectors_ = X * V = U * S |
| 297 | + Eigen::BDCSVD<Eigen::MatrixXd> svd(points_minus_mean_shape_dev_, Eigen::ComputeThinU | Eigen::ComputeThinV); |
298 | 298 |
|
299 | | - vnl_matrix<double> vnlA = vnl_matrix<double>(A.data(), A.rows(), A.cols()); |
300 | | - vnl_symmetric_eigensystem<double> symEigen(vnlA); |
301 | | - Eigen::MatrixXd shape_dev_eigenSymEigenV = |
302 | | - Eigen::Map<Eigen::MatrixXd>(symEigen.V.transpose().data_block(), symEigen.V.rows(), symEigen.V.cols()); |
303 | | - Eigen::VectorXd shape_dev_eigenSymEigenD = Eigen::Map<Eigen::VectorXd>(symEigen.D.data_block(), symEigen.D.rows(), 1); |
| 299 | + Eigen::VectorXd eigenvalues_eigen = svd.singularValues().array().square() / (num_samples_ - 1); |
| 300 | + int num_modes = eigenvalues_eigen.size(); |
304 | 301 |
|
305 | | - eigenvectors_shape_dev_ = points_minus_mean_shape_dev_ * shape_dev_eigenSymEigenV; |
306 | | - eigenvalues_shape_dev_.resize(num_samples_); |
| 302 | + // Compute eigenvectors_ = X * V = U * S (matches old behavior before normalization) |
| 303 | + eigenvectors_shape_dev_ = svd.matrixU() * svd.singularValues().asDiagonal(); |
307 | 304 |
|
308 | | - for (unsigned int i = 0; i < num_samples_; i++) { |
309 | | - double total = 0.0f; |
310 | | - for (unsigned int j = 0; j < m; j++) { |
311 | | - total += eigenvectors_shape_dev_(j, i) * eigenvectors_shape_dev_(j, i); |
312 | | - } |
313 | | - total = sqrt(total); |
| 305 | + // normalize the eigenvectors |
| 306 | + for (int i = 0; i < eigenvectors_shape_dev_.cols(); i++) { |
| 307 | + eigenvectors_shape_dev_.col(i).normalize(); |
| 308 | + } |
314 | 309 |
|
315 | | - for (unsigned int j = 0; j < m; j++) { |
316 | | - eigenvectors_shape_dev_(j, i) = eigenvectors_shape_dev_(j, i) / (total + 1.0e-15); |
317 | | - } |
318 | | - eigenvalues_shape_dev_[i] = shape_dev_eigenSymEigenD(i); |
| 310 | + // SVD returns values in descending order, but we need ascending order for backward compatibility |
| 311 | + eigenvalues_shape_dev_.resize(num_modes); |
| 312 | + for (int i = 0; i < num_modes; i++) { |
| 313 | + eigenvalues_shape_dev_[i] = eigenvalues_eigen[num_modes - 1 - i]; |
319 | 314 | } |
| 315 | + eigenvectors_shape_dev_ = eigenvectors_shape_dev_.rowwise().reverse().eval(); |
| 316 | + |
320 | 317 | return 0; |
321 | 318 | } |
322 | 319 |
|
323 | 320 | //--------------------------------------------------------------------------- |
324 | 321 | int ParticleShapeStatistics::compute_relative_pose_modes_for_mca() { |
325 | | - unsigned int m = points_minus_mean_rel_pose_.rows(); |
326 | | - Eigen::MatrixXd A = |
327 | | - points_minus_mean_rel_pose_.transpose() * points_minus_mean_rel_pose_ * (1.0 / ((double)(num_samples_ - 1))); |
328 | | - auto vnlA = vnl_matrix<double>(A.data(), A.rows(), A.cols()); |
329 | | - vnl_symmetric_eigensystem<double> symEigen(vnlA); |
330 | | - Eigen::MatrixXd rel_pose_eigenSymEigenV = |
331 | | - Eigen::Map<Eigen::MatrixXd>(symEigen.V.transpose().data_block(), symEigen.V.rows(), symEigen.V.cols()); |
| 322 | + // Use SVD to compute PCA. For X = U * S * V^T: |
| 323 | + // - The covariance matrix X^T * X / (n-1) has eigenvalues S^2 / (n-1) and eigenvectors V |
| 324 | + // - The old code computed eigenvectors_ = X * V = U * S |
| 325 | + Eigen::BDCSVD<Eigen::MatrixXd> svd(points_minus_mean_rel_pose_, Eigen::ComputeThinU | Eigen::ComputeThinV); |
332 | 326 |
|
333 | | - Eigen::VectorXd rel_pose_eigenSymEigenD = Eigen::Map<Eigen::VectorXd>(symEigen.D.data_block(), symEigen.D.rows(), 1); |
| 327 | + Eigen::VectorXd eigenvalues_eigen = svd.singularValues().array().square() / (num_samples_ - 1); |
| 328 | + int num_modes = eigenvalues_eigen.size(); |
334 | 329 |
|
335 | | - eigenvectors_rel_pose_ = points_minus_mean_rel_pose_ * rel_pose_eigenSymEigenV; |
336 | | - eigenvalues_rel_pose_.resize(num_samples_); |
| 330 | + // Compute eigenvectors_ = X * V = U * S (matches old behavior before normalization) |
| 331 | + eigenvectors_rel_pose_ = svd.matrixU() * svd.singularValues().asDiagonal(); |
337 | 332 |
|
338 | | - for (unsigned int i = 0; i < num_samples_; i++) { |
339 | | - double total = 0.0f; |
340 | | - for (unsigned int j = 0; j < m; j++) { |
341 | | - total += eigenvectors_rel_pose_(j, i) * eigenvectors_rel_pose_(j, i); |
342 | | - } |
343 | | - total = sqrt(total); |
344 | | - |
345 | | - for (unsigned int j = 0; j < m; j++) { |
346 | | - eigenvectors_rel_pose_(j, i) = eigenvectors_rel_pose_(j, i) / (total + 1.0e-15); |
347 | | - } |
| 333 | + // normalize the eigenvectors |
| 334 | + for (int i = 0; i < eigenvectors_rel_pose_.cols(); i++) { |
| 335 | + eigenvectors_rel_pose_.col(i).normalize(); |
| 336 | + } |
348 | 337 |
|
349 | | - eigenvalues_rel_pose_[i] = rel_pose_eigenSymEigenD(i); |
| 338 | + // SVD returns values in descending order, but we need ascending order for backward compatibility |
| 339 | + eigenvalues_rel_pose_.resize(num_modes); |
| 340 | + for (int i = 0; i < num_modes; i++) { |
| 341 | + eigenvalues_rel_pose_[i] = eigenvalues_eigen[num_modes - 1 - i]; |
350 | 342 | } |
| 343 | + eigenvectors_rel_pose_ = eigenvectors_rel_pose_.rowwise().reverse().eval(); |
| 344 | + |
351 | 345 | return 0; |
352 | 346 | } |
353 | 347 |
|
@@ -583,17 +577,21 @@ int ParticleShapeStatistics::compute_modes() { |
583 | 577 | TIME_SCOPE("ParticleShapeStatistics::compute_modes"); |
584 | 578 | SW_DEBUG("computing PCA modes"); |
585 | 579 |
|
586 | | - double scale = std::sqrt(num_samples_ - 1); |
587 | | - Eigen::BDCSVD<Eigen::MatrixXd> svd(points_minus_mean_ / scale, Eigen::ComputeThinU); |
| 580 | + // Use SVD to compute PCA. For X = U * S * V^T: |
| 581 | + // - The covariance matrix X^T * X / (n-1) has eigenvalues S^2 / (n-1) and eigenvectors V |
| 582 | + // - The old code computed eigenvectors_ = X * V = U * S |
| 583 | + Eigen::BDCSVD<Eigen::MatrixXd> svd(points_minus_mean_, Eigen::ComputeThinU | Eigen::ComputeThinV); |
588 | 584 |
|
589 | | - Eigen::VectorXd eigenvalues_eigen = svd.singularValues().array().square(); |
590 | | - |
591 | | - // U * S gives us the eigenvectors in particle space (like points_minus_mean_ * V did before) |
592 | | - // But we want them normalized, so just use U (which is already orthonormal) |
593 | | - eigenvectors_ = svd.matrixU() * scale; // Scale back to match original magnitudes |
| 585 | + // Eigenvalues of X^T * X / (n-1) = singular_values^2 / (n-1) |
| 586 | + Eigen::VectorXd eigenvalues_eigen = svd.singularValues().array().square() / (num_samples_ - 1); |
594 | 587 |
|
| 588 | + // Eigenvectors in particle space: X * V = U * S |
| 589 | + // SVD returns singular values in descending order, so we need to reverse for ascending order |
595 | 590 | int num_modes = eigenvalues_eigen.size(); |
596 | 591 |
|
| 592 | + // Compute eigenvectors_ = X * V = U * S (matches old behavior before normalization) |
| 593 | + eigenvectors_ = svd.matrixU() * svd.singularValues().asDiagonal(); |
| 594 | + |
597 | 595 | // normalize the eigenvectors |
598 | 596 | for (int i = 0; i < eigenvectors_.cols(); i++) { |
599 | 597 | eigenvectors_.col(i).normalize(); |
|
0 commit comments