LOST  0.0.1
LOST: Open-source Star Tracker
attitude-estimators.cpp
Go to the documentation of this file.
2 
3 #include <eigen3/Eigen/Dense>
4 #include <eigen3/Eigen/Eigenvalues>
5 
6 #include "decimal.hpp"
7 
8 namespace lost {
9 
10 #define EPSILON DECIMAL(0.0001) // threshold for 0 for Newton-Raphson method
11 
13  const Stars &stars,
14  const Catalog &catalog,
15  const StarIdentifiers &starIdentifiers) {
16  if (starIdentifiers.size() < 2) {
17  return Attitude();
18  }
19  assert(stars.size() >= 2);
20 
21  // attitude profile matrix
22  #ifdef LOST_FLOAT_MODE
23  Eigen::Matrix3f B;
24  #else
25  Eigen::Matrix3d B;
26  #endif
27 
28 
29  B.setZero();
30  for (const StarIdentifier &s : starIdentifiers) {
31  Star bStar = stars[s.starIndex];
32  Vec3 bStarSpatial = camera.CameraToSpatial(bStar.position);
33 
34  #ifdef LOST_FLOAT_MODE
35  Eigen::Vector3f bi;
36  Eigen::Vector3f ri;
37  #else
38  Eigen::Vector3d bi;
39  Eigen::Vector3d ri;
40  #endif
41 
42  bi << bStarSpatial.x, bStarSpatial.y, bStarSpatial.z;
43 
44  CatalogStar rStar = catalog[s.catalogIndex];
45  ri << rStar.spatial.x, rStar.spatial.y, rStar.spatial.z;
46  //Weight = 1 (can be changed later, in which case we want to make a vector to hold all weights {ai})
47  // Calculate matrix B = sum({ai}{bi}{ri}T)
48  B += ri * bi.transpose() * s.weight;
49  }
50 
51  // S = B + Transpose(B)
52  #ifdef LOST_FLOAT_MODE
53  Eigen::Matrix3f S = B + B.transpose();
54  #else
55  Eigen::Matrix3d S = B + B.transpose();
56  #endif
57 
58  //sigma = B[0][0] + B[1][1] + B[2][2]
59  decimal sigma = B.trace();
60 
61  //Z = [[B[1][2] - B[2][1]], [B[2][0] - B[0][2]], [B[0][1] - B[1][0]]]
62  #ifdef LOST_FLOAT_MODE
63  Eigen::Vector3f Z;
64  #else
65  Eigen::Vector3d Z;
66  #endif
67 
68  Z << B(1,2) - B(2,1),
69  B(2,0) - B(0,2),
70  B(0,1) - B(1,0);
71 
72  // K = [[[sigma], [Z[0]], [Z[1]], [Z[2]]], [[Z[0]], [S[0][0] - sigma], [S[0][1]], [S[0][2]]], [[Z[1]], [S[1][0]], [S[1][1] - sigma], [S[1][2]]], [[Z[2]], [S[2][0]], [S[2][1]], [S[2][2] - sigma]]]
73  #ifdef LOST_FLOAT_MODE
74  Eigen::Matrix4f K;
75  #else
76  Eigen::Matrix4d K;
77  #endif
78 
79  K << sigma, Z(0), Z(1), Z(2),
80  Z(0), S(0,0) - sigma, S(0,1), S(0,2),
81  Z(1), S(1,0), S(1,1) - sigma, S(1,2),
82  Z(2), S(2,0), S(2,1), S(2,2) - sigma;
83 
84  //Find eigenvalues of K, store the largest one as lambda
85  //find the maximum index
86  #ifdef LOST_FLOAT_MODE
87  Eigen::EigenSolver<Eigen::Matrix4f> solver(K);
88  Eigen::Vector4cf values = solver.eigenvalues();
89  Eigen::Matrix4cf vectors = solver.eigenvectors();
90  #else
91  Eigen::EigenSolver<Eigen::Matrix4d> solver(K);
92  Eigen::Vector4cd values = solver.eigenvalues();
93  Eigen::Matrix4cd vectors = solver.eigenvectors();
94  #endif
95 
96  int maxIndex = 0;
97  decimal maxEigenvalue = values(0).real();
98  for (int i = 1; i < values.size(); i++) {
99  if (values(i).real() > maxEigenvalue) {
100  maxIndex = i;
101  maxEigenvalue = values(i).real();
102  }
103  }
104 
105  //The return quaternion components = eigenvector assocaited with lambda
106  #ifdef LOST_FLOAT_MODE
107  Eigen::Vector4cf maxEigenvector = vectors.col(maxIndex);
108  #else
109  Eigen::Vector4cd maxEigenvector = vectors.col(maxIndex);
110  #endif
111 
112  // IMPORTANT: The matrix K is symmetric -- clearly first row and first column are equal.
113  // Furthermore, S is symmetric because s_i,j = b_i,j + b_j,i and s_j,i=b_j,i + b_i,j, so the
114  // bottom right 3x3 block of K is symmetric too. Thus all its eigenvalues and eigenvectors
115  // are real.
116  return Attitude(Quaternion(maxEigenvector[0].real(),
117  maxEigenvector[1].real(),
118  maxEigenvector[2].real(),
119  maxEigenvector[3].real()));
120 }
121 
123 static Mat3 TriadCoordinateFrame(Vec3 v1, Vec3 v2) {
124  Vec3 d1 = v1.Normalize();
125  Vec3 d2 = v1.CrossProduct(v2).Normalize();
126  Vec3 d3 = d1.CrossProduct(d2).Normalize();
127  return {
128  d1.x, d2.x, d3.x,
129  d1.y, d2.y, d3.y,
130  d1.z, d2.z, d3.z,
131  };
132 }
133 
135  const Stars &stars,
136  const Catalog &catalog,
137  const StarIdentifiers &starIds) {
138  if (starIds.size() < 2) {
139  return Attitude();
140  }
141  assert(stars.size() >= 2);
142 
143  // TODO: Better way of picking the two stars
145  a = starIds[0],
146  b = starIds[starIds.size()/2];
147 
148  Mat3 photoFrame = TriadCoordinateFrame(camera.CameraToSpatial(stars[a.starIndex].position),
149  camera.CameraToSpatial(stars[b.starIndex].position));
150  Mat3 catalogFrame = TriadCoordinateFrame(catalog[a.catalogIndex].spatial,
151  catalog[b.catalogIndex].spatial);
152 
153  // Now, we calculate the rotation that would bring the catalog vector to the photo vector. We
154  // have the relationship photoFrame = attitude*catalogFrame, and since catalogFrame is an
155  // orthogonal matrix, we have attitude = photoFrame*transpase(catalogFrame)
156  return Attitude(photoFrame*catalogFrame.Transpose());
157 }
158 
163 decimal QuestCharPoly(decimal x, decimal a, decimal b, decimal c, decimal d, decimal s) {return (DECIMAL_POW(x,2)-a) * (DECIMAL_POW(x,2)-b) - (c*x) + (c*s) - d;}
164 
168 decimal QuestCharPolyPrime(decimal x, decimal a, decimal b, decimal c) {return 4*DECIMAL_POW(x,3) - 2*(a+b)*x - c;}
169 
175  decimal height;
176  do {
177  height = QuestCharPoly(guess, a, b, c, d, s) / QuestCharPolyPrime(guess, a, b, c);
178  guess -= height;
179  } while (abs(height) >= EPSILON);
180 
181  return guess;
182 }
183 
185  const Stars &stars,
186  const Catalog &catalog,
187  const StarIdentifiers &starIdentifiers) {
188 
189  if (starIdentifiers.size() < 2) {
190  return Attitude();
191  }
192  assert(stars.size() >= 2);
193 
194  // initial guess for eigenvalue (sum of the weights)
195  decimal guess = 0;
196 
197  // attitude profile matrix
198  Mat3 B = {0, 0, 0, 0, 0, 0, 0, 0, 0};
199  for (const StarIdentifier &s : starIdentifiers) {
200  Star bStar = stars[s.starIndex];
201  Vec3 bStarSpatial = camera.CameraToSpatial(bStar.position);
202 
203  CatalogStar rStar = catalog[s.catalogIndex];
204  Vec3 rStarSpatial = {rStar.spatial.x, rStar.spatial.y, rStar.spatial.z};
205 
206  //Weight = 1 (can be changed later, in which case we want to make a vector to hold all weights {ai})
207  // Calculate matrix B = sum({ai}{bi}{ri}T)
208  B = B + (rStarSpatial.OuterProduct(bStarSpatial) * s.weight);
209 
210  // sum up the weights for initial guess of eigenvalue
211  guess += s.weight;
212  }
213 
214  // S = B + Transpose(B)
215  Mat3 S = B + B.Transpose();
216  //sigma = B[0][0] + B[1][1] + B[2][2]
217  decimal sigma = B.Trace();
218  //Z = [[B[1][2] - B[2][1]], [B[2][0] - B[0][2]], [B[0][1] - B[1][0]]]
219  Vec3 Z = {
220  B.At(1,2) - B.At(2,1),
221  B.At(2,0) - B.At(0,2),
222  B.At(0,1) - B.At(1,0)
223  };
224 
225  // calculate coefficients for characteristic polynomial
226  decimal delta = S.Det();
227  decimal kappa = (S.Inverse() * delta).Trace();
228  decimal a = DECIMAL_POW(sigma,2) - kappa;
229  decimal b = DECIMAL_POW(sigma,2) + (Z * Z);
230  decimal c = delta + (Z * S * Z);
231  decimal d = Z * (S * S) * Z;
232 
233  // Newton-Raphson method for estimating the largest eigenvalue
234  decimal eig = QuestEigenvalueEstimator(guess, a, b, c, d, sigma);
235 
236  // solve for the optimal quaternion: from https://ahrs.readthedocs.io/en/latest/filters/quest.html
237  decimal alpha = DECIMAL_POW(eig,2) - DECIMAL_POW(sigma, 2) + kappa;
238  decimal beta = eig - sigma;
239  decimal gamma = (eig + sigma) * alpha - delta;
240 
241  Vec3 X = ((kIdentityMat3 * alpha) + (S * beta) + (S * S)) * Z;
242  decimal scalar = 1 / DECIMAL_SQRT(DECIMAL_POW(gamma,2) + X.MagnitudeSq());
243  X = X * scalar;
244  gamma *= scalar;
245 
246  return Attitude(Quaternion(gamma, X.x, X.y, X.z));
247 }
248 
249 }
#define EPSILON
The attitude (orientation) of a spacecraft.
A full description of a camera. Enough information to reconstruct the camera matrix and then some.
Definition: camera.hpp:9
Vec3 CameraToSpatial(const Vec2 &) const
Gives a point in 3d space that could correspond to the given vector, using the same coordinate system...
Definition: camera.cpp:35
A star from the Bright Star Catalog.
Definition: star-utils.hpp:12
Vec3 spatial
The point on the unit sphere where the star lies.
Definition: star-utils.hpp:32
Attitude Go(const Camera &, const Stars &, const Catalog &, const StarIdentifiers &)
Actually run the star-id algorithm.
3x3 vector with decimaling point components
Mat3 Inverse() const
Inverse of a matrix.
decimal Det() const
Determinant of a matrix.
Mat3 Transpose() const
Transpose of a matrix.
decimal Trace() const
Trace of a matrix.
decimal At(int i, int j) const
Access the i,j-th element of the matrix.
A quaternion is a common way to represent a 3d rotation.
Attitude Go(const Camera &, const Stars &, const Catalog &, const StarIdentifiers &)
Actually run the star-id algorithm.
A "centroid" detected in an image.
Definition: star-utils.hpp:49
Vec2 position
The (x,y) pixel coordinates in the image (top left is 0,0)
Definition: star-utils.hpp:58
Records that a certain Star (detected in the image) corresponds to a certain CatalogStar.
Definition: star-utils.hpp:78
int catalogIndex
An index into an array of CatalogStar objects.
Definition: star-utils.hpp:95
int starIndex
An index into an array of Star objects.
Definition: star-utils.hpp:93
Attitude Go(const Camera &, const Stars &, const Catalog &, const StarIdentifiers &)
Actually run the star-id algorithm.
Three dimensional vector with decimaling point components.
Vec3 Normalize() const
Create a vector pointing in the same direction with magnitude 1.
decimal MagnitudeSq() const
The square of the magnitude.
Mat3 OuterProduct(const Vec3 &) const
The outer product of two vectors.
Vec3 CrossProduct(const Vec3 &) const
Usual vector cross product.
double decimal
Definition: decimal.hpp:11
#define DECIMAL_POW(base, power)
Definition: decimal.hpp:39
#define DECIMAL_SQRT(x)
Definition: decimal.hpp:40
LOST starting point.
std::vector< StarIdentifier > StarIdentifiers
Definition: star-utils.hpp:102
std::vector< Star > Stars
Definition: star-utils.hpp:101
decimal QuestEigenvalueEstimator(decimal guess, decimal a, decimal b, decimal c, decimal d, decimal s)
Approximates roots of a real function using the Newton-Raphson algorithm.
const Mat3 kIdentityMat3
3x3 identity matrix
decimal QuestCharPoly(decimal x, decimal a, decimal b, decimal c, decimal d, decimal s)
Characteristic polynomial of the quest K-matrix.
decimal QuestCharPolyPrime(decimal x, decimal a, decimal b, decimal c)
Derivitive of the characteristic polynomial of the quest K-matrix.
std::vector< CatalogStar > Catalog
Definition: star-utils.hpp:100