v2.0.0
Loading...
Searching...
No Matches
sphere.cpp
Go to the documentation of this file.
1//=============================================================================================================
26
27//=============================================================================================================
28// INCLUDES
29//=============================================================================================================
30
31#include "sphere.h"
32#include "simplex_algorithm.h"
33
34#include <QDebug>
35
36//=============================================================================================================
37// EIGEN INCLUDES
38//=============================================================================================================
39
40#include <Eigen/Dense>
41
42
43//=============================================================================================================
44// USED NAMESPACES
45//=============================================================================================================
46
47using namespace UTILSLIB;
48using namespace Eigen;
49
50//=============================================================================================================
51// DEFINE MEMBER METHODS
52//=============================================================================================================
53
54Sphere::Sphere(const Vector3f& center, float radius)
55: m_center(center)
56, m_r(radius)
57{
58}
59
60//=============================================================================================================
61
62Sphere Sphere::fit_sphere(const MatrixX3f& points)
63{
64 const VectorXf& x = points.col(0);
65 const VectorXf& y = points.col(1);
66 const VectorXf& z = points.col(2);
67
68 VectorXf point_means = points.colwise().mean();
69
70 VectorXf x_mean_free = x.array() - point_means(0);
71 VectorXf y_mean_free = y.array() - point_means(1);
72 VectorXf z_mean_free = z.array() - point_means(2);
73
74 Matrix3f A;
75 A << (x.cwiseProduct(x_mean_free)).mean(), 2 * (x.cwiseProduct(y_mean_free)).mean(), 2 * (x.cwiseProduct(z_mean_free)).mean(),
76 0, (y.cwiseProduct(y_mean_free)).mean(), 2 * (y.cwiseProduct(z_mean_free)).mean(),
77 0, 0, (z.cwiseProduct(z_mean_free)).mean();
78
79 Matrix3f A_T = A.transpose();
80 A += A_T;
81
82 Vector3f b;
83 VectorXf sq_sum = x.array().pow(2) + y.array().pow(2) + z.array().pow(2);
84 b << (sq_sum.cwiseProduct(x_mean_free)).mean(),
85 (sq_sum.cwiseProduct(y_mean_free)).mean(),
86 (sq_sum.cwiseProduct(z_mean_free)).mean();
87
88 Vector3f center = A.ldlt().solve(b);
89
90 MatrixX3f tmp(points.rows(), 3);
91 tmp.col(0) = x.array() - center(0);
92 tmp.col(1) = y.array() - center(1);
93 tmp.col(2) = z.array() - center(2);
94
95 float r = sqrt(tmp.array().pow(2).rowwise().sum().mean());
96
97 return Sphere(center, r);
98}
99
100//=============================================================================================================
101
102Sphere Sphere::fit_sphere_simplex(const MatrixX3f& points, double simplex_size)
103{
104 VectorXf center;
105 float R;
106 if (fit_sphere_to_points(points, simplex_size, center, R)) {
107 return Sphere(center, R);
108 }
109
110 return Sphere(Vector3f(), 0.0f);
111}
112
113//=============================================================================================================
114
115bool Sphere::fit_sphere_to_points(float** rr, int np, float simplex_size, float* r0, float* R)
116{
117 if (np <= 0)
118 return false;
119
120 MatrixXf rr_eigen(np, 3);
121 VectorXf r0_eigen(3);
122 for (int k = 0; k < np; k++) {
123 rr_eigen(k, 0) = rr[k][0];
124 rr_eigen(k, 1) = rr[k][1];
125 rr_eigen(k, 2) = rr[k][2];
126 }
127
128 r0_eigen(0) = r0[0];
129 r0_eigen(1) = r0[1];
130 r0_eigen(2) = r0[2];
131
132 bool state = UTILSLIB::Sphere::fit_sphere_to_points(rr_eigen, simplex_size, r0_eigen, *R);
133
134 r0[0] = r0_eigen(0);
135 r0[1] = r0_eigen(1);
136 r0[2] = r0_eigen(2);
137
138 return state;
139}
140
141//=============================================================================================================
142
143bool Sphere::fit_sphere_to_points(const MatrixXf& rr, float simplex_size, VectorXf& r0, float& R)
144{
145 // int np = rr.rows();
146
147 /*
148 * Find the optimal sphere origin
149 */
150 if (rr.rows() == 0) {
151 qWarning("Sphere::fit_sphere_to_points - No points were passed.");
152 return false;
153 }
154
155 FitUser user;
156 float ftol = 1e-5f;
157 int max_eval = 500;
158 int report_interval = -1;
159 int neval;
160 MatrixXf init_simplex;
161 VectorXf init_vals(4);
162
163 VectorXf cm(3);
164 float R0 = 0.1f;
165
166 user.rr = rr;
167
168 calculate_cm_ave_dist(rr, cm, R0); // [done]
169
170 init_simplex = make_initial_simplex(cm, simplex_size);
171
172 user.report = false;
173
174 for (int k = 0; k < 4; k++) {
175 init_vals[k] = fit_eval(static_cast<VectorXf>(init_simplex.row(k)), &user);
176 }
177
178 user.report = false;
179
180 // Capture user data in type-safe lambda — no void* needed
181 auto cost = [&user](const VectorXf& x) -> float {
182 return fit_eval(x, &user);
183 };
184
185 //Start the minimization
186 if (!SimplexAlgorithm::simplex_minimize<float>(init_simplex, /* The initial simplex */
187 init_vals, /* Function values at the vertices */
188 ftol, /* Relative convergence tolerance */
189 0.0f, /* No spatial convergence tolerance */
190 cost, /* The cost function (captures user data) */
191 max_eval, /* Maximum number of function evaluations */
192 neval, /* Number of function evaluations */
193 report_interval, /* How often to report (-1 = no_reporting) */
194 report_func)) /* The function to be called when reporting */
195 {
196 return false;
197 }
198
199 r0 = init_simplex.row(0);
200 R = opt_rad(r0, &user);
201
202 return true;
203}
204
205//=============================================================================================================
206
207bool Sphere::report_func(int loop, const VectorXf& fitpar, double fval_lo, double /*fval_hi*/, double /*par_diff*/)
208{
209 /*
210 * Report periodically
211 */
212 const VectorXf& r0 = fitpar;
213
214 qDebug().noquote().nospace() << "loop: " << loop << "; r0: " << 1000 * r0[0] << ", r1: " << 1000 * r0[1] << ", r2: " << 1000 * r0[2] << "; fval: " << fval_lo;
215
216 return true;
217}
218
219//=============================================================================================================
220
221void Sphere::calculate_cm_ave_dist(const MatrixXf& rr, VectorXf& cm, float& avep)
222{
223 cm = rr.colwise().mean();
224 MatrixXf diff = rr.rowwise() - cm.transpose();
225 avep = diff.rowwise().norm().mean();
226}
227
228//=============================================================================================================
229
230MatrixXf Sphere::make_initial_simplex(const VectorXf& pars, float size)
231{
232 /*
233 * Make the initial tetrahedron
234 */
235 int npar = pars.size();
236
237 MatrixXf simplex = MatrixXf::Zero(npar + 1, npar);
238
239 simplex.rowwise() += pars.transpose();
240
241 for (int k = 1; k < npar + 1; k++) {
242 simplex(k, k - 1) += size;
243 }
244
245 return simplex;
246}
247
248//=============================================================================================================
249
250float Sphere::fit_eval(const VectorXf& fitpar, const void* user_data)
251{
252 /*
253 * Calculate the cost function value
254 * Optimize for the radius inside here
255 */
256 const FitUser* user = static_cast<const FitUser*>(user_data);
257 const VectorXf& r0 = fitpar;
258
259 float F;
260
261 MatrixXf diff = user->rr.rowwise() - r0.transpose();
262 VectorXf one = diff.rowwise().norm();
263
264 float sum = one.sum();
265 float sum2 = one.dot(one);
266
267 F = sum2 - sum * sum / user->rr.rows();
268
269 if (user->report)
270 qDebug().noquote().nospace() << "r0: " << 1000 * r0[0] << ", r1: " << 1000 * r0[1] << ", r2: " << 1000 * r0[2] << "; R: " << 1000 * sum / user->rr.rows() << "; fval: " << F;
271
272 return F;
273}
274
275//=============================================================================================================
276
277float Sphere::opt_rad(const VectorXf& r0, const FitUser* user)
278{
279 MatrixXf diff = user->rr.rowwise() - r0.transpose();
280 return diff.rowwise().norm().mean();
281}
Eigen::Matrix3f R
Best-fit sphere from a 3-D point cloud with closed-form and Nelder–Mead solvers.
Header-only Nelder–Mead simplex minimiser with pluggable cost and report callables.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
static bool simplex_minimize(Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > &p, Eigen::Matrix< T, Eigen::Dynamic, 1 > &y, T ftol, T stol, CostFunc &&func, int max_eval, int &neval, int report, ReportFunc &&report_func)
Cost-function workspace for Sphere::fit_sphere_simplex: holds the nx3 point cloud and a verbose-repor...
Definition sphere.h:58
Eigen::MatrixXf rr
Definition sphere.h:59
static bool fit_sphere_to_points(const Eigen::MatrixXf &rr, float simplex_size, Eigen::VectorXf &r0, float &R)
static Sphere fit_sphere_simplex(const Eigen::MatrixX3f &points, double simplex_size=2e-2)
Definition sphere.cpp:102
Eigen::Vector3f & center()
Definition sphere.h:113
float & radius()
Definition sphere.h:124
static Sphere fit_sphere(const Eigen::MatrixX3f &points)
Definition sphere.cpp:62
Sphere(const Eigen::Vector3f &center, float radius)
Definition sphere.cpp:54