File indexing completed on 2026-09-05 08:17:31
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012
0013 #include <cassert>
0014 #include <cmath>
0015 #include <cstddef>
0016 #include <limits>
0017 #include <optional>
0018 #include <span>
0019
0020 #include <Eigen/Eigenvalues>
0021
0022 namespace Acts::detail {
0023
0024
0025 struct CircleFit {
0026
0027 Vector2 center = Vector2::Zero();
0028
0029 double radius = 0;
0030 };
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042 inline std::optional<CircleFit> fitCircleTaubin(
0043 std::span<const Vector3> points, std::span<const double> weights = {}) {
0044 const std::size_t n = points.size();
0045 if (n < 3) {
0046 return std::nullopt;
0047 }
0048 assert((weights.empty() || weights.size() == n) &&
0049 "weights must be empty or match the number of points");
0050
0051 const auto weightAt = [&](std::size_t i) {
0052 return weights.empty() ? 1 : weights[i];
0053 };
0054
0055
0056 double sumW = 0;
0057 double meanX = 0;
0058 double meanY = 0;
0059 for (std::size_t i = 0; i < n; ++i) {
0060 const double w = weightAt(i);
0061 sumW += w;
0062 meanX += w * points[i].x();
0063 meanY += w * points[i].y();
0064 }
0065 if (sumW <= 0) {
0066 return std::nullopt;
0067 }
0068 const double invW = 1 / sumW;
0069 meanX *= invW;
0070 meanY *= invW;
0071
0072
0073 double mxx = 0;
0074 double myy = 0;
0075 double mxy = 0;
0076 double mxz = 0;
0077 double myz = 0;
0078 double mzz = 0;
0079 double mz = 0;
0080 for (std::size_t i = 0; i < n; ++i) {
0081 const double w = weightAt(i);
0082 const double x = points[i].x() - meanX;
0083 const double y = points[i].y() - meanY;
0084 const double z = x * x + y * y;
0085 mxx += w * x * x;
0086 myy += w * y * y;
0087 mxy += w * x * y;
0088 mxz += w * x * z;
0089 myz += w * y * z;
0090 mzz += w * z * z;
0091 mz += w * z;
0092 }
0093 mxx *= invW;
0094 myy *= invW;
0095 mxy *= invW;
0096 mxz *= invW;
0097 myz *= invW;
0098 mzz *= invW;
0099 mz *= invW;
0100 if (mz <= 0) {
0101
0102 return std::nullopt;
0103 }
0104
0105
0106 SquareMatrix3 m;
0107 m << mzz - mz * mz, mxz, myz,
0108 mxz, mxx, mxy,
0109 myz, mxy, myy;
0110 SquareMatrix3 nMat = SquareMatrix3::Zero();
0111 nMat(0, 0) = 4 * mz;
0112 nMat(1, 1) = 1;
0113 nMat(2, 2) = 1;
0114
0115 Eigen::GeneralizedSelfAdjointEigenSolver<SquareMatrix3> solver(m, nMat);
0116 if (solver.info() != Eigen::Success) {
0117 return std::nullopt;
0118 }
0119
0120 const Vector3 a = solver.eigenvectors().col(0);
0121 const double coeffA = a(0);
0122 const double coeffB = a(1);
0123 const double coeffC = a(2);
0124 if (coeffA == 0) {
0125
0126 return std::nullopt;
0127 }
0128
0129 const double coeffD = -coeffA * mz;
0130 const double r2 = (coeffB * coeffB + coeffC * coeffC - 4 * coeffA * coeffD) /
0131 (4 * coeffA * coeffA);
0132 if (!std::isfinite(r2) || r2 <= 0) {
0133 return std::nullopt;
0134 }
0135 const double radius = std::sqrt(r2);
0136
0137
0138 if (constexpr double maxRadiusToSpread = 1e6;
0139 radius > maxRadiusToSpread * std::sqrt(mz)) {
0140 return std::nullopt;
0141 }
0142
0143 const Vector2 centerRel(-coeffB / (2 * coeffA), -coeffC / (2 * coeffA));
0144 return CircleFit{centerRel + Vector2(meanX, meanY), radius};
0145 }
0146
0147
0148
0149
0150
0151
0152
0153
0154
0155
0156
0157 inline std::optional<CircleFit> refineCircleGeometric(
0158 CircleFit fit, std::span<const Vector3> points,
0159 const std::size_t iterations, std::span<const double> weights = {}) {
0160 assert((weights.empty() || weights.size() == points.size()) &&
0161 "weights must be empty or match the number of points");
0162
0163 const auto weightAt = [&](std::size_t i) {
0164 return weights.empty() ? 1 : weights[i];
0165 };
0166
0167 for (std::size_t it = 0; it < iterations; ++it) {
0168 SquareMatrix3 jtj = SquareMatrix3::Zero();
0169 Vector3 jtr = Vector3::Zero();
0170 for (std::size_t i = 0; i < points.size(); ++i) {
0171 const Vector2 d = points[i].head<2>() - fit.center;
0172 const double dist = d.norm();
0173 if (dist < std::numeric_limits<double>::epsilon()) {
0174 continue;
0175 }
0176 const double w = weightAt(i);
0177 const double residual = dist - fit.radius;
0178
0179 const Vector3 j(-d.x() / dist, -d.y() / dist, -1);
0180 jtj += w * j * j.transpose();
0181 jtr += w * j * residual;
0182 }
0183 const Vector3 delta = jtj.ldlt().solve(-jtr);
0184 if (!delta.allFinite()) {
0185
0186 break;
0187 }
0188 fit.center.x() += delta.x();
0189 fit.center.y() += delta.y();
0190 fit.radius += delta.z();
0191 if (fit.radius <= 0) {
0192 return std::nullopt;
0193 }
0194 if (delta.norm() < 1e-9) {
0195 break;
0196 }
0197 }
0198 return fit;
0199 }
0200
0201 }