LOST  0.0.1
LOST: Open-source Star Tracker
star-id.cpp
Go to the documentation of this file.
1 #include <stdlib.h>
2 #include <math.h>
3 #include <assert.h>
4 #include <vector>
5 #include <algorithm>
6 #include <chrono>
7 #include <unordered_map>
8 
9 #include "star-id.hpp"
10 #include "star-id-private.hpp"
11 #include "databases.hpp"
12 #include "attitude-utils.hpp"
13 
14 namespace lost {
15 
17  const unsigned char *, const Stars &stars, const Catalog &catalog, const Camera &) const {
18 
19  StarIdentifiers result;
20 
21  unsigned int randomSeed = 123456;
22  for (int i = 0; i < (int)stars.size(); i++) {
23  result.push_back(StarIdentifier(i, rand_r(&randomSeed) % catalog.size()));
24  }
25 
26  return result;
27 }
28 
30  const unsigned char *database, const Stars &stars, const Catalog &catalog, const Camera &camera) const {
31 
32  StarIdentifiers identified;
33  MultiDatabase multiDatabase(database);
34  const unsigned char *databaseBuffer = multiDatabase.SubDatabasePointer(PairDistanceKVectorDatabase::kMagicValue);
35  if (databaseBuffer == NULL) {
36  return identified;
37  }
38  DeserializeContext des(databaseBuffer);
39  PairDistanceKVectorDatabase vectorDatabase(&des);
40 
41  for (int i = 0; i < (int)stars.size(); i++) {
42  std::vector<int16_t> votes(catalog.size(), 0);
43  Vec3 iSpatial = camera.CameraToSpatial(stars[i].position).Normalize();
44  for (int j = 0; j < (int)stars.size(); j++) {
45  if (i != j) {
46  // TODO: find a faster way to do this:
47  std::vector<bool> votedInPair(catalog.size(), false);
48  Vec3 jSpatial = camera.CameraToSpatial(stars[j].position).Normalize();
49  decimal greatCircleDistance = AngleUnit(iSpatial, jSpatial);
50  //give a greater range for min-max Query for bigger radius (GreatCircleDistance)
51  decimal lowerBoundRange = greatCircleDistance - tolerance;
52  decimal upperBoundRange = greatCircleDistance + tolerance;
53  const int16_t *upperBoundSearch;
54  const int16_t *lowerBoundSearch = vectorDatabase.FindPairsLiberal(
55  lowerBoundRange, upperBoundRange, &upperBoundSearch);
56  //loop from lowerBoundSearch till numReturnedPairs, add one vote to each star in the pairs in the datastructure
57  for (const int16_t *k = lowerBoundSearch; k != upperBoundSearch; k++) {
58  if ((k - lowerBoundSearch) % 2 == 0) {
59  decimal actualAngle = AngleUnit(catalog[*k].spatial, catalog[*(k+1)].spatial);
60  assert(actualAngle <= greatCircleDistance + tolerance * 2);
61  assert(actualAngle >= greatCircleDistance - tolerance * 2);
62  }
63  if (!votedInPair[*k] || true) {
64  // if (i == 542 && *k == 9085) {
65  // printf("INC, distance %f from query %f to %f\n", greatCircleDistance,
66  // lowerBoundRange, upperBoundRange);
67  // }
68  votes[*k]++;
69  votedInPair[*k] = true;
70  }
71  }
72  // US voting system
73  }
74  }
75  // Find star w most votes
76  int16_t maxVotes = votes[0];
77  int indexOfMax = 0;
78  for (int v = 1; v < (int)votes.size(); v++) {
79  if (votes[v] > maxVotes) {
80  maxVotes = votes[v];
81  indexOfMax = v;
82  }
83  }
84  // if (i == 542) {
85  // for (decimal dist : vectorDatabase.StarDistances(9085, catalog)) {
86  // printf("Actual 9085 distance: %f\n", dist);
87  // }
88  // puts("Debug star.");
89  // for (int i = 0; i < (int)votes.size(); i++) {
90  // if (votes[i] > maxVotes/2) {
91  // printf("Star %4d received %d votes.\n", catalog[i].name, votes[i]);
92  // }
93  // }
94  // printf("Debug star: Actually voted for %d with %d votes\n",
95  // catalog[indexOfMax].name, maxVotes);
96  // }
97  // printf("Max votes: %d\n", maxVotes);
98  //starIndex = i, catalog index = indexOfMax
99  StarIdentifier newStar(i, indexOfMax);
100  // Set identified[i] to value of catalog index of star w most votesr
101  identified.push_back(newStar);
102  }
103  //optimizations? N^2
104  //https://www.researchgate.net/publication/3007679_Geometric_voting_algorithm_for_star_trackers
105  //
106  // Do we have a metric for localization uncertainty? Star brighntess?
107  //loop i from 1 through n
108  std::vector<int16_t> verificationVotes(identified.size(), 0);
109  for (int i = 0; i < (int)identified.size(); i++) {
110  //loop j from i+1 through n
111  for (int j = i + 1; j < (int)identified.size(); j++) {
112  // Calculate distance between catalog stars
113  CatalogStar first = catalog[identified[i].catalogIndex];
114  CatalogStar second = catalog[identified[j].catalogIndex];
115  decimal cDist = AngleUnit(first.spatial, second.spatial);
116 
117  Star firstIdentified = stars[identified[i].starIndex];
118  Star secondIdentified = stars[identified[j].starIndex];
119  Vec3 firstSpatial = camera.CameraToSpatial(firstIdentified.position);
120  Vec3 secondSpatial = camera.CameraToSpatial(secondIdentified.position);
121  decimal sDist = Angle(firstSpatial, secondSpatial);
122 
123  //if sDist is in the range of (distance between stars in the image +- R)
124  //add a vote for the match
125  if (DECIMAL_ABS(sDist - cDist) < tolerance) {
126  verificationVotes[i]++;
127  verificationVotes[j]++;
128  }
129  }
130  }
131  // Find star w most votes
132  int maxVotes = verificationVotes.size() > 0 ? verificationVotes[0] : 0;
133  for (int v = 1; v < (int)verificationVotes.size(); v++) {
134  if (verificationVotes[v] > maxVotes) {
135  maxVotes = verificationVotes[v];
136  }
137  }
138 
139  // If the stars are within a certain range of the maximal number of votes,
140  // we consider it correct.
141  // maximal votes = maxVotes
142  StarIdentifiers verified;
143  int thresholdVotes = maxVotes * 3 / 4;
144  printf("Verification threshold: %d\n", thresholdVotes);
145  for (int i = 0; i < (int)verificationVotes.size(); i++) {
146  if (verificationVotes[i] > thresholdVotes) {
147  verified.push_back(identified[i]);
148  }
149  }
150 
151  return verified;
152 }
153 
154 /*
155  * Strategies:
156  * 1. For each star, enumerate all stars which have the same combination of distances to some
157  * other stars, getting down to a hopefully small (<10) list of candidates for each star, then
158  * do a quad-nested loop to correlate them.
159  * 2. Loop through all possible stars in the catalog for star i. Then look at edge ij, using
160  * this to select possible j-th stars. If ever there is not a possible j-th star, continue the
161  * i-loop. When a possible ij combination is found, loop through k stars according to ik. IF
162  * none are found, continue the outer i loop. If some are found, check jk for each one. For each possible ijk triangle,
163  */
164 
170 public:
176  : pairs(NULL), end(NULL) { };
177 
184  PairDistanceInvolvingIterator(const int16_t *pairs, const int16_t *end, int16_t involving)
185  : pairs(pairs), end(end), involving(involving) {
186 
187  assert((end-pairs)%2 == 0);
188  ForwardUntilInvolving();
189  };
190 
191  // PairDistanceInvolvingIterator operator++() {
192  // PairDistanceInvolvingIterator result(*this);
193  // ++(*this);
194  // return result;
195  // }
196 
199  assert(HasValue());
200  pairs += 2;
201  ForwardUntilInvolving();
202  return *this;
203  }
204 
206  int16_t operator*() const {
207  return curValue;
208  }
209 
211  bool HasValue() {
212  return pairs != end;
213  }
214 
215  // bool operator==(const PairDistanceInvolvingIterator &other) const {
216  // return ()other.pairs == pairs;
217  // }
218 
219  // bool operator!=(const PairDistanceInvolvingIterator &other) const {
220  // return !(*this == other);
221  // }
222 private:
223  const int16_t *pairs;
224  const int16_t *end;
225  int16_t involving;
226  int16_t curValue;
227 
229  void ForwardUntilInvolving() {
230  while (pairs != end) {
231  if (pairs[0] == involving) {
232  curValue = pairs[1];
233  return;
234  }
235  if (pairs[1] == involving) {
236  curValue = pairs[0];
237  return;
238  }
239  pairs += 2;
240  }
241  }
242 };
243 
245  std::vector<int16_t> result;
246  for (; it.HasValue(); ++it) {
247  result.push_back(*it);
248  }
249  return result;
250 }
251 
259 std::unordered_multimap<int16_t, int16_t> PairDistanceQueryToMap(const int16_t *pairs, const int16_t *end) {
260  std::unordered_multimap<int16_t, int16_t> result;
261  for (const int16_t *p = pairs; p != end; p += 2) {
262  result.emplace(p[0], p[1]);
263  result.emplace(p[1], p[0]);
264  }
265  return result;
266 }
267 
268 decimal IRUnidentifiedCentroid::VerticalAnglesToAngleFrom90(decimal v1, decimal v2) {
270 }
271 
277  const Star &otherStar = stars[starId.starIndex];
278  Vec2 positionDifference = otherStar.position - star->position;
279  decimal angleFromVertical = DECIMAL_ATAN2(positionDifference.y, positionDifference.x);
280 
281  for (const auto &otherPair : identifiedStarsInRange) {
282  decimal curAngleFrom90 = VerticalAnglesToAngleFrom90(otherPair.first, angleFromVertical);
283  if (curAngleFrom90 < bestAngleFrom90) {
284  bestAngleFrom90 = curAngleFrom90;
285  bestStar1 = starId;
286  bestStar2 = otherPair.second;
287  }
288  }
289 
290  identifiedStarsInRange.emplace_back(angleFromVertical, starId);
291 }
292 
299 std::vector<std::vector<IRUnidentifiedCentroid *>::iterator> FindUnidentifiedCentroidsInRange(
300  std::vector<IRUnidentifiedCentroid *> *centroids, const Star &star, const Camera &camera,
301  decimal minDistance, decimal maxDistance) {
302 
303  Vec3 ourSpatial = camera.CameraToSpatial(star.position).Normalize();
304 
305  decimal minCos = DECIMAL_COS(maxDistance);
306  decimal maxCos = DECIMAL_COS(minDistance);
307 
308  std::vector<std::vector<IRUnidentifiedCentroid *>::iterator> result;
309  for (auto it = centroids->begin(); it != centroids->end(); ++it) {
310  Vec3 theirSpatial = camera.CameraToSpatial((*it)->star->position).Normalize();
311  decimal angleCos = ourSpatial * theirSpatial;
312  if (angleCos >= minCos && angleCos <= maxCos) {
313  result.push_back(it);
314  }
315  }
316 
317  return result;
318 
319  // TODO: optimize by sorting on x-coordinate (like in tracking), or maybe even kd-tree
320 
321  // // Find the first centroid that is within range of the given centroid.
322  // auto firstInRange = std::lower_bound(centroids->begin(), centroids->end(), star.position.x - maxDistance,
323  // [](const IRUnidentifiedCentroid &centroid, decimal x) {
324  // return centroid.star.position.x < x;
325  // });
326 
327  // // Find the first centroid that is not within range of the given centroid.
328  // auto firstNotInRange = std::lower_bound(firstInRange, centroids->end(), star.position.x + maxDistance,
329  // [](const IRUnidentifiedCentroid &centroid, decimal x) {
330  // return centroid.star.position.x <= x;
331  // });
332 
333  // // Copy the pointers to the stars into the result vector.
334  // for (auto it = firstInRange; it != firstNotInRange; ++it) {
335  // decimal distance = Distance(star.position, it->star.position);
336  // if (distance >= minDistance && distance <= maxDistance) {
337  // result.push_back(&*it);
338  // }
339  // }
340 }
341 
350 void AddToAllUnidentifiedCentroids(const StarIdentifier &starId, const Stars &stars,
351  std::vector<IRUnidentifiedCentroid *> *aboveThresholdCentroids,
352  std::vector<IRUnidentifiedCentroid *> *belowThresholdCentroids,
353  decimal minDistance, decimal maxDistance,
354  decimal angleFrom90Threshold,
355  const Camera &camera) {
356 
357  std::vector<int16_t> nowBelowThreshold; // centroid indices newly moved above the threshold
358  // don't need to iterate through the centroids that are already below the threshold, for performance.
359  for (auto centroidIt : FindUnidentifiedCentroidsInRange(aboveThresholdCentroids, stars[starId.starIndex], camera, minDistance, maxDistance)) {
360  (*centroidIt)->AddIdentifiedStar(starId, stars);
361  if ((*centroidIt)->bestAngleFrom90 <= angleFrom90Threshold) {
362  belowThresholdCentroids->push_back(*centroidIt);
363  nowBelowThreshold.push_back((*centroidIt)->index);
364  }
365  }
366  // remove all centroids with indices in nowBelowThreshold from aboveThresholdCentroids
367  aboveThresholdCentroids->erase(std::remove_if(aboveThresholdCentroids->begin(), aboveThresholdCentroids->end(),
368  [&nowBelowThreshold](const IRUnidentifiedCentroid *centroid) {
369  return std::find(nowBelowThreshold.begin(), nowBelowThreshold.end(), centroid->index) != nowBelowThreshold.end();
370  }), aboveThresholdCentroids->end());
371 }
372 
384 std::vector<int16_t> IdentifyThirdStar(const PairDistanceKVectorDatabase &db,
385  const Catalog &catalog,
386  int16_t catalogIndex1, int16_t catalogIndex2,
387  decimal distance1, decimal distance2,
388  decimal tolerance) {
389 
390  const int16_t *query1End;
391  const int16_t *query1 = db.FindPairsExact(catalog, distance1-tolerance, distance1+tolerance, &query1End);
392 
393  const Vec3 &spatial1 = catalog[catalogIndex1].spatial;
394  const Vec3 &spatial2 = catalog[catalogIndex2].spatial;
395  const Vec3 cross = spatial1.CrossProduct(spatial2);
396 
397  // Use PairDistanceInvolvingIterator to find catalog candidates for the unidentified centroid from both sides.
398 
399  std::vector<int16_t> result;
400  // find all the catalog stars that are in both annuli
401  for (PairDistanceInvolvingIterator candidateIt(query1, query1End, catalogIndex1);
402  candidateIt.HasValue();
403  ++candidateIt) {
404 
405  Vec3 candidateSpatial = catalog[*candidateIt].spatial;
406 
407  decimal angle2 = AngleUnit(candidateSpatial, spatial2);
408 
409  // check distance to second star
410  if (!(angle2 >= distance2-tolerance && angle2 <= distance2+tolerance)) {
411  continue;
412  }
413 
414  // check spectrality
415  decimal spectralTorch = cross * candidateSpatial;
416  // if they are nearly coplanar, don't need to check spectrality
417  // TODO: Implement ^^. Not high priority, since always checking spectrality is conservative.
418  if (spectralTorch <= 0) {
419  continue;
420  }
421 
422  // we've made it through the gauntlet!
423  result.push_back(*candidateIt);
424  }
425 
426  return result;
427 }
428 
429 IRUnidentifiedCentroid *SelectNextUnidentifiedCentroid(std::vector<IRUnidentifiedCentroid *> *aboveThresholdCentroids,
430  std::vector<IRUnidentifiedCentroid *> *belowThresholdCentroids) {
431  if (!belowThresholdCentroids->empty()) {
432  auto result = belowThresholdCentroids->back();
433  belowThresholdCentroids->pop_back();
434  return result;
435  }
436 
437  // need to find the best in aboveThreshold, if any
438  auto bestAboveThreshold = std::min_element(aboveThresholdCentroids->begin(), aboveThresholdCentroids->end(),
439  [](const IRUnidentifiedCentroid *a, const IRUnidentifiedCentroid *b) {
440  return a->bestAngleFrom90 < b->bestAngleFrom90;
441  });
442 
443  // 10 is arbitrary; but really it should be less than DECIMAL_M_PI_2 when set
444  if (bestAboveThreshold != aboveThresholdCentroids->end() && (*bestAboveThreshold)->bestAngleFrom90 < 10) {
445  auto result = *bestAboveThreshold;
446  aboveThresholdCentroids->erase(bestAboveThreshold);
447  return result;
448  }
449 
450  return NULL;
451 }
452 
454 
462  const Stars &stars,
463  const PairDistanceKVectorDatabase &db,
464  const Catalog &catalog,
465  const Camera &camera,
466  decimal tolerance) {
467 #ifdef LOST_DEBUG_PERFORMANCE
468  auto startTimestamp = std::chrono::steady_clock::now();
469 #endif
470  // initialize all unidentified centroids
471  std::vector<IRUnidentifiedCentroid> allUnidentifiedCentroids;
472  std::vector<IRUnidentifiedCentroid *> aboveThresholdUnidentifiedCentroids;
473  std::vector<IRUnidentifiedCentroid *> belowThresholdUnidentifiedCentroids;
474  allUnidentifiedCentroids.reserve(stars.size());
475  for (size_t i = 0; i < stars.size(); i++) {
476  allUnidentifiedCentroids.push_back(IRUnidentifiedCentroid(stars[i], i));
477  }
478  // add everything from allUnidentifiedCentroids to above threshold
479  aboveThresholdUnidentifiedCentroids.reserve(allUnidentifiedCentroids.size());
480  for (size_t i = 0; i < allUnidentifiedCentroids.size(); i++) {
481  // only add if index is not equal to any starIndex in identifiers already
482  if (std::find_if(identifiers->begin(), identifiers->end(),
483  [&allUnidentifiedCentroids, i](const StarIdentifier &identifier) {
484  return identifier.starIndex == allUnidentifiedCentroids[i].index;
485  }) == identifiers->end()) {
486 
487  aboveThresholdUnidentifiedCentroids.push_back(&allUnidentifiedCentroids[i]);
488  }
489  }
490 
491  // sort unidentified centroids by x coordinate
492  // Don't need this until we fix the Find thing
493  // std::sort(unidentifiedCentroids.begin(), unidentifiedCentroids.end(),
494  // [](const IRUnidentifiedCentroid &a, const IRUnidentifiedCentroid &b) {
495  // return a.star->position.x < b.star->position.x;
496  // });
497 
498  // for each identified star, add it to the list of identified stars for each unidentified centroid within range
499  for (const auto &starId : *identifiers) {
500  AddToAllUnidentifiedCentroids(starId, stars,
501  &aboveThresholdUnidentifiedCentroids, &belowThresholdUnidentifiedCentroids,
502  db.MinDistance(), db.MaxDistance(),
504  camera);
505  }
506 
507  int numExtraIdentifiedStars = 0;
508 
509  // keep getting the best unidentified centroid and identifying it
510  while (!belowThresholdUnidentifiedCentroids.empty() || !aboveThresholdUnidentifiedCentroids.empty()) {
511  IRUnidentifiedCentroid *nextUnidentifiedCentroid
512  = SelectNextUnidentifiedCentroid(&aboveThresholdUnidentifiedCentroids, &belowThresholdUnidentifiedCentroids);
513  if (nextUnidentifiedCentroid == NULL) {
514  break;
515  }
516 
517  // Project next stars to 3d, find angle between them and current unidentified centroid
518  Vec3 unidentifiedSpatial = camera.CameraToSpatial(nextUnidentifiedCentroid->star->position);
519  Vec3 spatial1 = camera.CameraToSpatial(stars[nextUnidentifiedCentroid->bestStar1.starIndex].position);
520  Vec3 spatial2 = camera.CameraToSpatial(stars[nextUnidentifiedCentroid->bestStar2.starIndex].position);
521  decimal d1 = Angle(spatial1, unidentifiedSpatial);
522  decimal d2 = Angle(spatial2, unidentifiedSpatial);
523  decimal spectralTorch = spatial1.CrossProduct(spatial2) * unidentifiedSpatial;
524 
525  // find all the catalog stars that are in both annuli
526  // flip arguments for appropriate spectrality.
527  std::vector<int16_t> candidates =
528  spectralTorch > 0
529  ? IdentifyThirdStar(db,
530  catalog,
531  nextUnidentifiedCentroid->bestStar1.catalogIndex,
532  nextUnidentifiedCentroid->bestStar2.catalogIndex,
533  d1, d2, tolerance)
534  : IdentifyThirdStar(db,
535  catalog,
536  nextUnidentifiedCentroid->bestStar2.catalogIndex,
537  nextUnidentifiedCentroid->bestStar1.catalogIndex,
538  d2, d1, tolerance);
539 
540  if (candidates.size() != 1) { // if there is not exactly one candidate, we can't identify the star. Just remove it from the list.
541  if (candidates.size() > 1) {
542  std::cerr << "WARNING: Multiple catalog stars matched during identify remaining stars. This should be rare." << std::endl;
543  }
544  } else {
545  // identify the centroid
546  identifiers->emplace_back(nextUnidentifiedCentroid->index, candidates[0]);
547 
548  // update nearby unidentified centroids with the new identified star
549  AddToAllUnidentifiedCentroids(identifiers->back(), stars,
550  &aboveThresholdUnidentifiedCentroids, &belowThresholdUnidentifiedCentroids,
551  db.MinDistance(), db.MaxDistance(),
552  // TODO should probably tune this:
554  camera);
555 
556  ++numExtraIdentifiedStars;
557  }
558  }
559 
560  // Select() should always empty out this list
561  assert(belowThresholdUnidentifiedCentroids.empty());
562 
563 #ifdef LOST_DEBUG_PERFORMANCE
564  auto endTimestamp = std::chrono::steady_clock::now();
565  std::cout << "IdentifyRemainingStarsPairDistance took " << std::chrono::duration_cast<std::chrono::microseconds>(endTimestamp - startTimestamp).count() << "us" << std::endl;
566 #endif
567 
568  return numExtraIdentifiedStars;
569 }
570 
572  const unsigned char *database, const Stars &stars, const Catalog &catalog, const Camera &camera) const {
573 
574  StarIdentifiers identified;
575  MultiDatabase multiDatabase(database);
576  const unsigned char *databaseBuffer = multiDatabase.SubDatabasePointer(PairDistanceKVectorDatabase::kMagicValue);
577  if (databaseBuffer == NULL || stars.size() < 4) {
578  std::cerr << "Not enough stars, or database missing." << std::endl;
579  return identified;
580  }
581  DeserializeContext des(databaseBuffer);
582  PairDistanceKVectorDatabase vectorDatabase(&des);
583 
584  // smallest normal single-precision decimal is around 10^-38 so we should be all good. See
585  // Analytic_Star_Pattern_Probability on the HSL wiki for details.
586  decimal expectedMismatchesConstant = DECIMAL_POW(numFalseStars, 4) * DECIMAL_POW(tolerance, 5) / 2 / DECIMAL_POW(DECIMAL_M_PI, 2);
587 
588  // this iteration technique is described in the Pyramid paper. Briefly: i will always be the
589  // lowest index, then dj and dk are how many indexes ahead the j-th star is from the i-th, and
590  // k-th from the j-th. In addition, we here add some other numbers so that the pyramids are not
591  // weird lines in wide FOV images. TODO: Select the starting points to ensure that the first pyramids are all within measurement tolerance.
592  int numStars = (int)stars.size();
593  // the idea is that the square root is about across the FOV horizontally
594  int across = floor(sqrt(numStars))*2;
595  int halfwayAcross = floor(sqrt(numStars)/2);
596  long totalIterations = 0;
597 
598  int jMax = numStars - 3;
599  for (int jIter = 0; jIter < jMax; jIter++) {
600  int dj = 1+(jIter+halfwayAcross)%jMax;
601 
602  int kMax = numStars-dj-2;
603  for (int kIter = 0; kIter < kMax; kIter++) {
604  int dk = 1+(kIter+across)%kMax;
605 
606  int rMax = numStars-dj-dk-1;
607  for (int rIter = 0; rIter < rMax; rIter++) {
608  int dr = 1+(rIter+halfwayAcross)%rMax;
609 
610  int iMax = numStars-dj-dk-dr-1;
611  for (int iIter = 0; iIter <= iMax; iIter++) {
612  int i = (iIter + iMax/2)%(iMax+1); // start near the center of the photo
613 
614  // identification failure due to cutoff
615  if (++totalIterations > cutoff) {
616  std::cerr << "Cutoff reached." << std::endl;
617  return identified;
618  }
619 
620  int j = i+dj;
621  int k = j+dk;
622  int r = k+dr;
623 
624  assert(i != j && j != k && k != r && i != k && i != r && j != r);
625 
626  // TODO: move this out of the loop?
627  Vec3 iSpatial = camera.CameraToSpatial(stars[i].position).Normalize();
628  Vec3 jSpatial = camera.CameraToSpatial(stars[j].position).Normalize();
629  Vec3 kSpatial = camera.CameraToSpatial(stars[k].position).Normalize();
630 
631  decimal ijDist = AngleUnit(iSpatial, jSpatial);
632 
633  decimal iSinInner = DECIMAL_SIN(Angle(jSpatial - iSpatial, kSpatial - iSpatial));
634  decimal jSinInner = DECIMAL_SIN(Angle(iSpatial - jSpatial, kSpatial - jSpatial));
635  decimal kSinInner = DECIMAL_SIN(Angle(iSpatial - kSpatial, jSpatial - kSpatial));
636 
637  // if we made it this far, all 6 angles are confirmed! Now check
638  // that this match would not often occur due to chance.
639  // See Analytic_Star_Pattern_Probability on the HSL wiki for details
640  decimal expectedMismatches = expectedMismatchesConstant
641  * DECIMAL_SIN(ijDist)
642  / kSinInner
643  / std::max(std::max(iSinInner, jSinInner), kSinInner);
644 
645  if (expectedMismatches > maxMismatchProbability) {
646  std::cout << "skip: mismatch prob." << std::endl;
647  continue;
648  }
649 
650  Vec3 rSpatial = camera.CameraToSpatial(stars[r].position).Normalize();
651 
652  // sign of determinant, to detect flipped patterns
653  bool spectralTorch = iSpatial.CrossProduct(jSpatial)*kSpatial > 0;
654 
655  decimal ikDist = AngleUnit(iSpatial, kSpatial);
656  decimal irDist = AngleUnit(iSpatial, rSpatial);
657  decimal jkDist = AngleUnit(jSpatial, kSpatial);
658  decimal jrDist = AngleUnit(jSpatial, rSpatial);
659  decimal krDist = AngleUnit(kSpatial, rSpatial); // TODO: we don't really need to
660  // check krDist, if k has been
661  // verified by i and j it's fine.
662 
663  // we check the distances with the extra tolerance requirement to ensure that
664  // there isn't some pyramid that's just outside the database's bounds, but
665  // within measurement tolerance of the observed pyramid, since that would
666  // possibly cause a non-unique pyramid to be identified as unique.
667 #define _CHECK_DISTANCE(_dist) if (_dist < vectorDatabase.MinDistance() + tolerance || _dist > vectorDatabase.MaxDistance() - tolerance) { continue; }
668  _CHECK_DISTANCE(ikDist);
669  _CHECK_DISTANCE(irDist);
670  _CHECK_DISTANCE(jkDist);
671  _CHECK_DISTANCE(jrDist);
672  _CHECK_DISTANCE(krDist);
673 #undef _CHECK_DISTANCE
674 
675  const int16_t *ijEnd, *ikEnd, *irEnd;
676  const int16_t *const ijQuery = vectorDatabase.FindPairsLiberal(ijDist - tolerance, ijDist + tolerance, &ijEnd);
677  const int16_t *const ikQuery = vectorDatabase.FindPairsLiberal(ikDist - tolerance, ikDist + tolerance, &ikEnd);
678  const int16_t *const irQuery = vectorDatabase.FindPairsLiberal(irDist - tolerance, irDist + tolerance, &irEnd);
679 
680  std::unordered_multimap<int16_t, int16_t> ikMap = PairDistanceQueryToMap(ikQuery, ikEnd);
681  std::unordered_multimap<int16_t, int16_t> irMap = PairDistanceQueryToMap(irQuery, irEnd);
682 
683  int iMatch = -1, jMatch = -1, kMatch = -1, rMatch = -1;
684  for (const int16_t *iCandidateQuery = ijQuery; iCandidateQuery != ijEnd; iCandidateQuery++) {
685  int iCandidate = *iCandidateQuery;
686  // depending on parity, the first or second star in the pair is the "other" one
687  int jCandidate = (iCandidateQuery - ijQuery) % 2 == 0
688  ? iCandidateQuery[1]
689  : iCandidateQuery[-1];
690 
691  const Vec3 &iCandidateSpatial = catalog[iCandidate].spatial;
692  const Vec3 &jCandidateSpatial = catalog[jCandidate].spatial;
693 
694  Vec3 ijCandidateCross = iCandidateSpatial.CrossProduct(jCandidateSpatial);
695 
696  for (auto kCandidateIt = ikMap.equal_range(iCandidate); kCandidateIt.first != kCandidateIt.second; kCandidateIt.first++) {
697  // kCandidate.first is iterator, then ->second is the value (other star)
698  int kCandidate = kCandidateIt.first->second;
699  Vec3 kCandidateSpatial = catalog[kCandidate].spatial;
700  bool candidateSpectralTorch = ijCandidateCross*kCandidateSpatial > 0;
701  // checking the spectral-ity early to fail fast
702  if (candidateSpectralTorch != spectralTorch) {
703  continue;
704  }
705 
706  // small optimization: We can calculate jk before iterating through r, so we will!
707  decimal jkCandidateDist = AngleUnit(jCandidateSpatial, kCandidateSpatial);
708  if (jkCandidateDist < jkDist - tolerance || jkCandidateDist > jkDist + tolerance) {
709  continue;
710  }
711 
712  // TODO: if there are no jr matches, there's no reason to
713  // continue iterating through all the other k-s. Possibly
714  // enumarete all r matches, according to ir, before this loop
715  for (auto rCandidateIt = irMap.equal_range(iCandidate); rCandidateIt.first != rCandidateIt.second; rCandidateIt.first++) {
716  int rCandidate = rCandidateIt.first->second;
717  const Vec3 &rCandidateSpatial = catalog[rCandidate].spatial;
718  decimal jrCandidateDist = AngleUnit(jCandidateSpatial, rCandidateSpatial);
719  decimal krCandidateDist;
720  if (jrCandidateDist < jrDist - tolerance || jrCandidateDist > jrDist + tolerance) {
721  continue;
722  }
723  krCandidateDist = AngleUnit(kCandidateSpatial, rCandidateSpatial);
724  if (krCandidateDist < krDist - tolerance || krCandidateDist > krDist + tolerance) {
725  continue;
726  }
727 
728  // we have a match!
729 
730  if (iMatch == -1) {
731  iMatch = iCandidate;
732  jMatch = jCandidate;
733  kMatch = kCandidate;
734  rMatch = rCandidate;
735  } else {
736  // uh-oh, stinky!
737  // TODO: test duplicate detection, it's hard to cause it in the real catalog...
738  std::cerr << "Pyramid not unique, skipping..." << std::endl;
739  goto sensorContinue;
740  }
741  }
742  }
743 
744 
745  }
746 
747  if (iMatch != -1) {
748  std::cout.precision(6);
749  std::cout << "Matched unique pyramid!" << std::endl << "Expected mismatches: " << std::scientific << expectedMismatches << std::endl << std::fixed;
750  identified.push_back(StarIdentifier(i, iMatch));
751  identified.push_back(StarIdentifier(j, jMatch));
752  identified.push_back(StarIdentifier(k, kMatch));
753  identified.push_back(StarIdentifier(r, rMatch));
754 
755  int numAdditionallyIdentified = IdentifyRemainingStarsPairDistance(&identified, stars, vectorDatabase, catalog, camera, tolerance);
756  printf("Identified an additional %d stars.\n", numAdditionallyIdentified);
757  assert(numAdditionallyIdentified == (int)identified.size()-4);
758 
759  return identified;
760  }
761 
762  sensorContinue:;
763  }
764  }
765  }
766  }
767 
768  std::cerr << "Tried all pyramids; none matched." << std::endl;
769  return identified;
770 }
771 
772 }
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
StarIdentifiers Go(const unsigned char *database, const Stars &, const Catalog &, const Camera &) const
Actualy perform the star idenification. This is the "main" function for StarIdAlgorithm.
Definition: star-id.cpp:16
StarIdentifiers Go(const unsigned char *database, const Stars &, const Catalog &, const Camera &) const
Actualy perform the star idenification. This is the "main" function for StarIdAlgorithm.
Definition: star-id.cpp:29
unidentified centroid used in IdentifyRemainingStarsPairDistance The "angles" through here are "trian...
const Star * star
Index into list of all centroids.
StarIdentifier bestStar1
For the pair of other centroids forming the triangular angle closest to 90 degrees,...
void AddIdentifiedStar(const StarIdentifier &starId, const Stars &stars)
When a centroid within range of this centroid is identified, call this function.
Definition: star-id.cpp:276
StarIdentifier bestStar2
One star corresponding to bestAngleFrom90.
int16_t index
The other star corresponding to bestAngleFrom90.
const unsigned char * SubDatabasePointer(int32_t magicValue) const
MultiDatabase memory layout:
Definition: databases.cpp:316
Given a list of star pairs, finds all those pairs which involve a certain star.
Definition: star-id.cpp:169
PairDistanceInvolvingIterator(const int16_t *pairs, const int16_t *end, int16_t involving)
The main constructor.
Definition: star-id.cpp:184
int16_t operator*() const
Access the curent pair.
Definition: star-id.cpp:206
PairDistanceInvolvingIterator & operator++()
Move to the next matching pair.
Definition: star-id.cpp:198
bool HasValue()
Whether the iterator is currently on a value. (false if iteration is complete)
Definition: star-id.cpp:211
PairDistanceInvolvingIterator()
Create a "past-the-end" iterator.
Definition: star-id.cpp:175
A database storing distances between pairs of stars.
Definition: databases.hpp:54
decimal MinDistance() const
Lower bound on stored star pair distances.
Definition: databases.hpp:65
static const int32_t kMagicValue
Magic value to use when storing inside a MultiDatabase.
Definition: databases.hpp:70
const int16_t * FindPairsExact(const Catalog &, decimal min, decimal max, const int16_t **end) const
Definition: databases.cpp:248
const int16_t * FindPairsLiberal(decimal min, decimal max, const int16_t **end) const
Return at least all the star pairs whose inter-star distance is between min and max.
Definition: databases.cpp:237
decimal MaxDistance() const
Upper bound on stored star pair distances.
Definition: databases.hpp:63
StarIdentifiers Go(const unsigned char *database, const Stars &, const Catalog &, const Camera &) const
Actualy perform the star idenification. This is the "main" function for StarIdAlgorithm.
Definition: star-id.cpp:571
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
Three dimensional vector with decimaling point components.
Vec3 Normalize() const
Create a vector pointing in the same direction with magnitude 1.
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_M_PI
Definition: decimal.hpp:29
#define DECIMAL_M_PI_4
Definition: decimal.hpp:31
#define DECIMAL_SIN(x)
Definition: decimal.hpp:52
#define DECIMAL_ABS(x)
Definition: decimal.hpp:49
#define DECIMAL_M_PI_2
Definition: decimal.hpp:30
#define DECIMAL_ATAN2(x, y)
Definition: decimal.hpp:58
#define DECIMAL_COS(x)
Definition: decimal.hpp:53
LOST starting point.
const decimal kAngleFrom90SoftThreshold
Definition: star-id.cpp:453
std::vector< StarIdentifier > StarIdentifiers
Definition: star-utils.hpp:102
std::vector< Star > Stars
Definition: star-utils.hpp:101
decimal AngleUnit(const Vec3 &vec1, const Vec3 &vec2)
Calculate the inner angle, in radians, between two /unit/ vectors.
std::vector< int16_t > ConsumeInvolvingIterator(PairDistanceInvolvingIterator it)
Definition: star-id.cpp:244
decimal Angle(const Vec3 &vec1, const Vec3 &vec2)
Calculate the inner angle, in radians, between two vectors.
std::vector< std::vector< IRUnidentifiedCentroid * >::iterator > FindUnidentifiedCentroidsInRange(std::vector< IRUnidentifiedCentroid * > *centroids, const Star &star, const Camera &camera, decimal minDistance, decimal maxDistance)
Return all the unidentified centroids within the requested distance bounds from star
Definition: star-id.cpp:299
int IdentifyRemainingStarsPairDistance(StarIdentifiers *, const Stars &, const PairDistanceKVectorDatabase &, const Catalog &, const Camera &, decimal tolerance)
Given some identified stars, attempt to identify the rest.
Definition: star-id.cpp:461
decimal DecimalModulo(decimal x, decimal mod)
Given a decimal, find it "modulo" another decimal, in the true mathematical sense (not remainder).
std::vector< int16_t > IdentifyThirdStar(const PairDistanceKVectorDatabase &db, const Catalog &catalog, int16_t catalogIndex1, int16_t catalogIndex2, decimal distance1, decimal distance2, decimal tolerance)
Given two already-identified centroids, and the distance from each to an as-yet unidentified third ce...
Definition: star-id.cpp:384
std::unordered_multimap< int16_t, int16_t > PairDistanceQueryToMap(const int16_t *pairs, const int16_t *end)
Given the result of a pair-distance kvector query, build a hashmultimap of stars to other stars that ...
Definition: star-id.cpp:259
std::vector< CatalogStar > Catalog
Definition: star-utils.hpp:100
IRUnidentifiedCentroid * SelectNextUnidentifiedCentroid(std::vector< IRUnidentifiedCentroid * > *aboveThresholdCentroids, std::vector< IRUnidentifiedCentroid * > *belowThresholdCentroids)
Definition: star-id.cpp:429
void AddToAllUnidentifiedCentroids(const StarIdentifier &starId, const Stars &stars, std::vector< IRUnidentifiedCentroid * > *aboveThresholdCentroids, std::vector< IRUnidentifiedCentroid * > *belowThresholdCentroids, decimal minDistance, decimal maxDistance, decimal angleFrom90Threshold, const Camera &camera)
Given a list of unidentified centroids not yet at the soft threshold, and a list of unidentified cent...
Definition: star-id.cpp:350
#define _CHECK_DISTANCE(_dist)
A two dimensional vector with decimaling point components.