File indexing completed on 2024-04-21 14:47:19

0001 /*
0002     SPDX-FileCopyrightText: 2020 Hy Murveit <hy@murveit.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "ekos/guide/internalguide/starcorrespondence.h"
0008 
0009 #include <QTest>
0010 
0011 #include <QObject>
0012 
0013 
0014 class TestStarCorrespondence : public QObject
0015 {
0016         Q_OBJECT
0017 
0018     public:
0019         /** @short Constructor */
0020         TestStarCorrespondence();
0021 
0022         /** @short Destructor */
0023         ~TestStarCorrespondence() override = default;
0024 
0025     private slots:
0026         void basicTest();
0027 };
0028 
0029 #include "teststarcorrespondence.moc"
0030 
0031 TestStarCorrespondence::TestStarCorrespondence() : QObject()
0032 {
0033 }
0034 
0035 Edge makeEdge(float x, float y)
0036 {
0037     Edge e;
0038     e.x = x;
0039     e.y = y;
0040     return e;
0041 }
0042 
0043 void runTest(int guideStar)
0044 {
0045     constexpr double maxDistanceToStar = 5.0;
0046 
0047     // Setup a number of stars.
0048     QList<Edge> stars;
0049     stars.append(makeEdge(90, 70));
0050     stars.append(makeEdge(110, 70));
0051     stars.append(makeEdge(0, 0));
0052     stars.append(makeEdge(10, 30));
0053     stars.append(makeEdge(20, 70));
0054     stars.append(makeEdge(70, 10));
0055     stars.append(makeEdge(70, 80));
0056     stars.append(makeEdge(80, 40));
0057     stars.append(makeEdge(30, 20));
0058     stars.append(makeEdge(30, 80));
0059     stars.append(makeEdge(50, 60));
0060     // Assign guideStar as the guide star.
0061     StarCorrespondence c(stars, guideStar);
0062     c.setImageSize(120, 100); // Not necessary, but speeds things up.
0063     QVector<int> output;
0064 
0065     // Identity test.
0066     Edge gStar = c.find(stars, maxDistanceToStar, &output, false);
0067     QVERIFY2(gStar.x == stars[guideStar].x, "Identity");
0068     QVERIFY2(gStar.y == stars[guideStar].y, "Identity");
0069     for (int i = 0; i < stars.size(); ++i)
0070     {
0071         QVERIFY2(output[i] == i, "Identity");
0072     }
0073 
0074     srand(21);
0075     // Move the stars randomly upto 2 pixels away
0076     QList<Edge> stars2;
0077     for (int i = 0; i < stars.size(); ++i)
0078     {
0079         double rand_fraction = ((rand() % 200) - 100) / 50.0;
0080         double x = stars[i].x + rand_fraction;
0081         rand_fraction = ((rand() % 200) - 100) / 50.0;
0082         double y = stars[i].y + rand_fraction;
0083         stars2.append(makeEdge(x, y));
0084     }
0085     gStar = c.find(stars2, maxDistanceToStar, &output, false);
0086     QVERIFY2(gStar.x == stars2[guideStar].x, "small move");
0087     QVERIFY2(gStar.y == stars2[guideStar].y, "small move");
0088     for (int i = 0; i < stars.size(); ++i)
0089     {
0090         QVERIFY2(output[i] == i, "small move");
0091     }
0092 
0093     // Remove one of the stars (not the guide star which is 0-5).
0094     stars2.removeAt(8);
0095     gStar = c.find(stars2, maxDistanceToStar, &output, false);
0096     QVERIFY2(gStar.x == stars2[guideStar].x, "one removed");
0097     QVERIFY2(gStar.y == stars2[guideStar].y, "one removed");
0098     for (int i = 0; i < stars2.size(); ++i)
0099     {
0100         if (i < 8) QVERIFY2(output[i] == i, "one removed");
0101         else QVERIFY2(output[i] == (i + 1), "one removed");
0102     }
0103 
0104     // Remove the guide star. Nothing should be returned.
0105     double guideX = stars2[guideStar].x;
0106     double guideY = stars2[guideStar].y;
0107     stars2.removeAt(guideStar);
0108     c.setAllowMissingGuideStar(false);
0109     gStar = c.find(stars2, maxDistanceToStar, &output, false);
0110     QVERIFY2(gStar.x == -1, "guide removed");
0111     QVERIFY2(gStar.y == -1, "guide removed");
0112     for (int i = 0; i < stars2.size(); ++i)
0113     {
0114         QVERIFY2(output[i] == -1, "guide removed");
0115     }
0116 
0117     // Allow it to be resilient to missing guide stars.
0118     c.setAllowMissingGuideStar(true);
0119     gStar = c.find(stars2, maxDistanceToStar, &output, false);
0120     // There's been noise added so it won't recover the guide star exactly.
0121     QVERIFY2(fabs(gStar.x - guideX) < 2, "guide and 8 removed");
0122     QVERIFY2(fabs(gStar.y - guideY) < 2, "guide and 8 removed");
0123     for (int i = 0; i < stars2.size(); ++i)
0124     {
0125         // previously we removed 8, but also removing the guide star moves the index for star 8 to 7.
0126         const int missingStar = 7;
0127         if (i < missingStar && i < guideStar)
0128             QVERIFY2(output[i] == i, "guide and 8 removed");
0129         else if (i < guideStar || i < missingStar)
0130             QVERIFY2(output[i] == (i + 1), "guide and 8 removed");
0131         else
0132             QVERIFY2(output[i] == (i + 2), "guide and 8 removed");
0133     }
0134 
0135     // Use  clean star positions to exactly recover the lost guide star.
0136     guideX = stars[guideStar].x;
0137     guideY = stars[guideStar].y;
0138     stars.removeAt(guideStar);
0139     c.setAllowMissingGuideStar(true);
0140     gStar = c.find(stars, maxDistanceToStar, &output, false);
0141     QVERIFY2(gStar.x == guideX, "guide removed and recovered");
0142     QVERIFY2(gStar.y == guideY, "guide removed and recovered");
0143     for (int i = 0; i < stars2.size(); ++i)
0144     {
0145         if (i < guideStar)
0146             QVERIFY2(output[i] == i, "guide removed & recovered");
0147         else
0148             QVERIFY2(output[i] == (i + 1), "guide removed & recovered");
0149     }
0150 }
0151 
0152 void runAdaptationTest()
0153 {
0154     // Setup a number of stars.
0155     QList<Edge> stars;
0156     stars.append(makeEdge(100, 50));
0157     stars.append(makeEdge(150, 50));
0158     stars.append(makeEdge(100, 80));
0159     StarCorrespondence c(stars, 0);
0160     QVERIFY(c.reference(0).x == 100);
0161     QVERIFY(c.reference(0).y == 50);
0162     QVERIFY(c.reference(1).x == 150);
0163     QVERIFY(c.reference(1).y == 50);
0164     QVERIFY(c.reference(2).x == 100);
0165     QVERIFY(c.reference(2).y == 80);
0166 
0167     // Move star #1 from 150,50 to 151,148. Keep the other two in place.
0168     stars[1].x = 151;
0169     stars[1].y = 48;
0170     QVector<int> output;
0171     // Find the stars. All 3 should still correspond.
0172     // The position of star#1 should move a little towards its new position.
0173     c.find(stars, 5.0, &output, true);
0174     QVERIFY(output[0] == 0);
0175     QVERIFY(output[1] == 1);
0176     QVERIFY(output[2] == 2);
0177     QVERIFY(c.reference(0).x == 100);
0178     QVERIFY(c.reference(0).y == 50);
0179     QVERIFY(c.reference(1).x > 150);
0180     QVERIFY(c.reference(1).y < 50);
0181     QVERIFY(c.reference(2).x == 100);
0182     QVERIFY(c.reference(2).y == 80);
0183 
0184     // Check the exact position it moved to.
0185     // Exact values for ref 1 using the adaptation equation should be:
0186     // alpha = 1 / 25**0.865
0187     // x(1) = alpha * 151 + (1-alpha) * 150
0188     // y(1) = alpha * 48 + (1-alpha) * 50
0189     // Of course, if the time constant or equation changes, this shuld be updated.
0190     const double alpha = 1 / pow(25.0, 0.865);
0191     const double x1 = alpha * 151 + (1 - alpha) * 150;
0192     const double y1 = alpha * 48 + (1 - alpha) * 50;
0193     QVERIFY(fabs(c.reference(1).x - x1) < .0001);
0194     QVERIFY(fabs(c.reference(1).y - y1) < .0001);
0195 }
0196 
0197 // Checks a set of reference stars and detected stars which should fail star correspondence.
0198 void runNoCorrespondenceTest()
0199 {
0200     constexpr double maxDistanceToStar = 10.0;
0201 
0202     // Setup a number of stars.
0203     QList<Edge> stars;
0204     stars.append(makeEdge(1186.73, 483.568));
0205     stars.append(makeEdge(367.712, 293.589));
0206     stars.append(makeEdge(190.09, 944.418));
0207     stars.append(makeEdge(1067.85, 30.3361));
0208     stars.append(makeEdge(1067.58, 313.792));
0209     // Assign guideStar as the guide star.
0210     StarCorrespondence c(stars, 0);
0211     c.setImageSize(1280, 960); // Not necessary, but speeds things up.
0212     QVector<int> output;
0213 
0214     // This detected set only has 2 of the ref stars, so multistar should fail.
0215     QList<Edge> stars2;
0216     stars2.append(makeEdge(241.8, 125.5));
0217     stars2.append(makeEdge(233.5, 119.3));
0218     stars2.append(makeEdge(1186.6, 483.5)); // ref 0
0219     stars2.append(makeEdge(368.1, 294.1));  // ref 1
0220     stars2.append(makeEdge(1006.5, 909.7));
0221     stars2.append(makeEdge(903.3, 448.7));
0222     stars2.append(makeEdge(940.5, 177.2));
0223     stars2.append(makeEdge(695.4, 97.6));
0224 
0225     c.setAllowMissingGuideStar(true);
0226     Edge gStar = c.find(stars2, maxDistanceToStar, &output, false);
0227     QVERIFY(gStar.x == -1);
0228     QVERIFY(gStar.y == -1);
0229     for (int i = 0; i < output.size(); ++i)
0230         QVERIFY(output[i] == -1);
0231 }
0232 
0233 void TestStarCorrespondence::basicTest()
0234 {
0235     for (int i = 0; i < 6; ++i)
0236         runTest(i);
0237     runAdaptationTest();
0238     runNoCorrespondenceTest();
0239 }
0240 
0241 QTEST_GUILESS_MAIN(TestStarCorrespondence)