Lagrange
compute_pointcloud_pca.h
1/*
2 * Copyright 2019 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14// Should we move the definition to a .cpp file? Any .cpp file
15// seeing Eigen/Eigenvalues would take ages to compile.
16#include <Eigen/Core>
17#include <Eigen/Eigenvalues>
18
19#include <lagrange/utils/assert.h>
20#include <lagrange/legacy/inline.h>
21
22namespace lagrange {
23LAGRANGE_LEGACY_INLINE
24namespace legacy {
25
26// compute_pointcloud_pca()
27//
28// Finds the principle components for a pointcloud
29// Assumes that the points are supplied in a matrix where each
30// ``row'' is a point.
31//
32// This is closely related to the inertia tensor, principal directions
33// and principal moments. But it is not exactly the same.
34//
35// COVARIANCE_MATRIX (tensor) = (P-eC)^T (P-eC)
36// Where C is the centroid, and e is column vector of ones.
37// eigenvalues and eigenvectors of this matrix would be
38// the principal weights and components.
39//
40// MOMENT OF INERTIA (tensor) = trace(P^T P)I - P^T P
41// eigenvalues and eigenvectors of this matrix would be
42// the principal moments and directions.
43//
44// This refactors segmentation/Pca.h
45
46template <typename Scalar>
48{
49 // The point around which the covariance matrix is evaluated.
50 // Col vector to be consistent with `components` and weights.
51 Eigen::Matrix<Scalar, Eigen::Dynamic, 1> center;
52 // Each column is a component, sorted by weight magnitudes.
53 // n_rows == n_cols == space dimension
54 Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> components;
55 // Each entry is a weight for the corresponding principal component
56 // n_rows == space dimension
57 // Col vector to be consistent with `components`.
58 Eigen::Matrix<Scalar, Eigen::Dynamic, 1> weights;
59};
60
61// Input:
62// points, Each row should be a point in the point cloud.
63// To be consistent with the rest of Lagrange.
64// should_shift_centeroid,
65// when true: covariance = (P-centroid)^T (P-centroid)
66// when false: covariance = (P)^T (P)
67// should_normalize, should we divide the result by number of points?
68template <typename Derived1>
70 const Eigen::MatrixBase<Derived1>& points,
71 const bool should_shift_centeroid,
72 const bool should_normalize)
73{
74 //
75 using Scalar = typename Derived1::Scalar;
76 using MatrixXS =
77 Eigen::Matrix<Scalar, Derived1::ColsAtCompileTime, Derived1::ColsAtCompileTime>;
78 using RowVectorXS = Eigen::Matrix<Scalar, 1, Derived1::ColsAtCompileTime>;
79
80 // Prechecks
81 la_runtime_assert(points.rows() >= 2, "There must be at least two points");
82
83 // Compute covariance
84 MatrixXS covariance;
85 RowVectorXS center;
86
87 if (should_shift_centeroid) {
88 center = points.colwise().mean();
89 } else {
90 center = RowVectorXS::Zero(points.cols());
91 } // end of should_shift_centroid
92
93 if (should_normalize) {
94 // We may instead divide by points.rows()-1 which applies the Bessel's correction.
95 // https://en.wikipedia.org/wiki/Bessel%27s_correction
96 covariance =
97 ((points.rowwise() - center).transpose() * (points.rowwise() - center)) / points.rows();
98 } else {
99 covariance = ((points.rowwise() - center).transpose() * (points.rowwise() - center));
100 } // end of should_normalize
101
102 // SelfAdjointEigenSolver is guaranteed to return the eigenvalues sorted
103 // in increasing order. Note that this is not true for the general EigenValueSolver.
104 Eigen::SelfAdjointEigenSolver<MatrixXS> eigs(covariance);
105 // Unless something is going wrong, the covariance matrix should
106 // be well behaved. Let's double check.
107 la_runtime_assert(eigs.info() == Eigen::Success, "Eigen decomposition failed");
108
109 ComputePointcloudPCAOutput<Scalar> output;
110 output.center = center.transpose();
111 output.components = eigs.eigenvectors(); // Columns are eigenvectors
112 output.weights = eigs.eigenvalues();
113
114 // make sure components follow the right hand rule
115 if (output.components.determinant() < 0.) {
116 output.components.col(0) *= -1;
117 }
118
119 return output;
120}
121
122} // namespace legacy
123} // namespace lagrange
#define la_runtime_assert(...)
Runtime assertion check.
Definition: assert.h:169
Main namespace for Lagrange.
Definition: AABBIGL.h:30
PointcloudPCAOutput< Scalar > compute_pointcloud_pca(span< const Scalar > points, ComputePointcloudPCAOptions options={})
Finds the principal components for a pointcloud.
Definition: compute_pointcloud_pca.cpp:23
Definition: compute_pointcloud_pca.h:48