File indexing completed on 2024-03-24 15:18:31

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/focus/focusalgorithms.h"
0008 
0009 #include <QTest>
0010 #include <memory>
0011 
0012 #include <QObject>
0013 
0014 // At this point, only the methods in focusalgorithms.h are tested.
0015 
0016 class TestFocus : public QObject
0017 {
0018         Q_OBJECT
0019 
0020     public:
0021         /** @short Constructor */
0022         TestFocus();
0023 
0024         /** @short Destructor */
0025         ~TestFocus() override = default;
0026 
0027     private slots:
0028         void basicTest();
0029         void restartTest();
0030         void L1PHyperbolaTest();
0031         void L1PParabolaTest();
0032         void L1PQuadraticTest();
0033 };
0034 
0035 #include "testfocus.moc"
0036 
0037 using Ekos::FocusAlgorithmInterface;
0038 
0039 FocusAlgorithmInterface::FocusParams makeParams()
0040 {
0041     const int maxTravel = 100000;
0042     const int initialStepSize = 25;
0043     const int startPosition = 10000;
0044     const int minPositionAllowed = 0;
0045     const int maxPositionAllowed = 1000000;
0046     const int maxIterations = 30;
0047     const double focusTolerance = 0.05;
0048     const QString filterName = "Red";
0049     const double temperature = 20.0;
0050     const double initialOutwardSteps = 5;
0051     const int numSteps = 11;
0052     const Ekos::Focus::Algorithm focusAlgorithm = Ekos::Focus::FOCUS_LINEAR;
0053     const int backlash = 0;
0054     const Ekos::CurveFitting::CurveFit curveFit = Ekos::CurveFitting::FOCUS_QUADRATIC;
0055     const bool useWeights = false;
0056     const Ekos::Focus::StarMeasure starMeasure = Ekos::Focus::FOCUS_STAR_HFR;
0057     const Ekos::Focus::StarPSF starPSF = Ekos::Focus::FOCUS_STAR_GAUSSIAN;
0058     const bool refineCurveFit = false;
0059     const Ekos::Focus::FocusWalk focusWalk = Ekos::Focus::FOCUS_WALK_CLASSIC;
0060     const bool donutBuster = false;
0061     auto const optimisationDirection = Ekos::CurveFitting::OPTIMISATION_MINIMISE;
0062     auto const weightCalculation = Mathematics::RobustStatistics::SCALE_QESTIMATOR;
0063 
0064     const FocusAlgorithmInterface::FocusParams params(nullptr,
0065             maxTravel, initialStepSize, startPosition, minPositionAllowed,
0066             maxPositionAllowed, maxIterations, focusTolerance, filterName,
0067             temperature, initialOutwardSteps, numSteps, focusAlgorithm, backlash,
0068             curveFit, useWeights, starMeasure, starPSF, refineCurveFit, focusWalk,
0069             donutBuster, optimisationDirection, weightCalculation);
0070 
0071     return params;
0072 }
0073 
0074 FocusAlgorithmInterface::FocusParams makeL1PHyperbolaParams()
0075 {
0076     const int maxTravel = 100000;
0077     const int initialStepSize = 25;
0078     const int startPosition = 10000;
0079     const int minPositionAllowed = 0;
0080     const int maxPositionAllowed = 1000000;
0081     const int maxIterations = 30;
0082     const double focusTolerance = 0.05;
0083     const QString filterName = "Green";
0084     const double temperature = 20.0;
0085     const double initialOutwardSteps = 5;
0086     const int numSteps = 11;
0087     const Ekos::Focus::Algorithm focusAlgorithm = Ekos::Focus::FOCUS_LINEAR1PASS;
0088     const int backlash = 300;
0089     const Ekos::CurveFitting::CurveFit curveFit = Ekos::CurveFitting::FOCUS_HYPERBOLA;
0090     const bool useWeights = true;
0091     const Ekos::Focus::StarMeasure starMeasure = Ekos::Focus::FOCUS_STAR_HFR;
0092     const Ekos::Focus::StarPSF starPSF = Ekos::Focus::FOCUS_STAR_GAUSSIAN;
0093     const bool refineCurveFit = false;
0094     const Ekos::Focus::FocusWalk focusWalk = Ekos::Focus::FOCUS_WALK_CLASSIC;
0095     const bool donutBuster = false;
0096     auto const optimisationDirection = Ekos::CurveFitting::OPTIMISATION_MINIMISE;
0097     auto const weightCalculation = Mathematics::RobustStatistics::SCALE_QESTIMATOR;
0098 
0099     const FocusAlgorithmInterface::FocusParams params(nullptr,
0100             maxTravel, initialStepSize, startPosition, minPositionAllowed,
0101             maxPositionAllowed, maxIterations, focusTolerance, filterName,
0102             temperature, initialOutwardSteps, numSteps, focusAlgorithm, backlash,
0103             curveFit, useWeights, starMeasure, starPSF, refineCurveFit, focusWalk,
0104             donutBuster, optimisationDirection, weightCalculation);
0105 
0106     return params;
0107 }
0108 
0109 FocusAlgorithmInterface::FocusParams makeL1PParabolaParams()
0110 {
0111     const int maxTravel = 100000;
0112     const int initialStepSize = 25;
0113     const int startPosition = 10000;
0114     const int minPositionAllowed = 0;
0115     const int maxPositionAllowed = 1000000;
0116     const int maxIterations = 30;
0117     const double focusTolerance = 0.05;
0118     const QString filterName = "Blue";
0119     const double temperature = 20.0;
0120     const double initialOutwardSteps = 5;
0121     const int numSteps = 11;
0122     const Ekos::Focus::Algorithm focusAlgorithm = Ekos::Focus::FOCUS_LINEAR1PASS;
0123     const int backlash = 200;
0124     const Ekos::CurveFitting::CurveFit curveFit = Ekos::CurveFitting::FOCUS_PARABOLA;
0125     const bool useWeights = false;
0126     const Ekos::Focus::StarMeasure starMeasure = Ekos::Focus::FOCUS_STAR_HFR;
0127     const Ekos::Focus::StarPSF starPSF = Ekos::Focus::FOCUS_STAR_GAUSSIAN;
0128     const bool refineCurveFit = false;
0129     const Ekos::Focus::FocusWalk focusWalk = Ekos::Focus::FOCUS_WALK_CLASSIC;
0130     const bool donutBuster = false;
0131     auto const optimisationDirection = Ekos::CurveFitting::OPTIMISATION_MINIMISE;
0132     auto const weightCalculation = Mathematics::RobustStatistics::SCALE_QESTIMATOR;
0133 
0134     const FocusAlgorithmInterface::FocusParams params(nullptr,
0135             maxTravel, initialStepSize, startPosition, minPositionAllowed,
0136             maxPositionAllowed, maxIterations, focusTolerance, filterName,
0137             temperature, initialOutwardSteps, numSteps, focusAlgorithm, backlash,
0138             curveFit, useWeights, starMeasure, starPSF, refineCurveFit, focusWalk,
0139             donutBuster, optimisationDirection, weightCalculation);
0140 
0141     return params;
0142 }
0143 
0144 FocusAlgorithmInterface::FocusParams makeL1PQuadraticParams()
0145 {
0146     const int maxTravel = 100000;
0147     const int initialStepSize = 25;
0148     const int startPosition = 10000;
0149     const int minPositionAllowed = 0;
0150     const int maxPositionAllowed = 1000000;
0151     const int maxIterations = 30;
0152     const double focusTolerance = 0.05;
0153     const QString filterName = "Lum";
0154     const double temperature = 20.0;
0155     const double initialOutwardSteps = 5;
0156     const int numSteps = 11;
0157     const Ekos::Focus::Algorithm focusAlgorithm = Ekos::Focus::FOCUS_LINEAR1PASS;
0158     const int backlash = 0;
0159     const Ekos::CurveFitting::CurveFit curveFit = Ekos::CurveFitting::FOCUS_QUADRATIC;
0160     const bool useWeights = false;
0161     const Ekos::Focus::StarMeasure starMeasure = Ekos::Focus::FOCUS_STAR_HFR;
0162     const Ekos::Focus::StarPSF starPSF = Ekos::Focus::FOCUS_STAR_GAUSSIAN;
0163     const bool refineCurveFit = false;
0164     const Ekos::Focus::FocusWalk focusWalk = Ekos::Focus::FOCUS_WALK_CLASSIC;
0165     const bool donutBuster = false;
0166     auto const optimisationDirection = Ekos::CurveFitting::OPTIMISATION_MINIMISE;
0167     auto const weightCalculation = Mathematics::RobustStatistics::SCALE_QESTIMATOR;
0168 
0169     const FocusAlgorithmInterface::FocusParams params(nullptr,
0170             maxTravel, initialStepSize, startPosition, minPositionAllowed,
0171             maxPositionAllowed, maxIterations, focusTolerance, filterName,
0172             temperature, initialOutwardSteps, numSteps, focusAlgorithm, backlash,
0173             curveFit, useWeights, starMeasure, starPSF, refineCurveFit, focusWalk,
0174             donutBuster, optimisationDirection, weightCalculation);
0175 
0176     return params;
0177 }
0178 
0179 TestFocus::TestFocus() : QObject()
0180 {
0181 }
0182 
0183 #define CompareFloat(d1,d2) QVERIFY(fabs((d1) - (d2)) < .0001)
0184 
0185 void TestFocus::basicTest()
0186 {
0187     // Test initial position. Since we set it up so that the min/max PositionAllowed
0188     // are not near the start position, we should start out with
0189     // initial position +
0190     auto params = makeParams();
0191     std::unique_ptr<FocusAlgorithmInterface> focuser(MakeLinearFocuser(params));
0192     int position = focuser->initialPosition();
0193     // The first position should be initialPosition + stepSize * initialOutwardSteps,
0194     // unless maxPositionAllowed or maxTravel doesn't allow that.
0195     QCOMPARE(position, static_cast <int> (params.startPosition + params.initialOutwardSteps * params.initialStepSize));
0196 
0197     params.maxTravel = 4 * params.initialStepSize;
0198     focuser.reset(MakeLinearFocuser(params));
0199     position = focuser->initialPosition();
0200     QCOMPARE(position, params.startPosition + params.maxTravel);
0201 
0202     params.maxPositionAllowed = params.startPosition + 3 * params.initialStepSize;
0203     focuser.reset(MakeLinearFocuser(params));
0204     position = focuser->initialPosition();
0205     QCOMPARE(position, params.maxPositionAllowed);
0206 
0207     // go back to the default params
0208     params = makeParams();
0209     focuser.reset(MakeLinearFocuser(params));
0210     int currentPosition = focuser->initialPosition();
0211 
0212     // Here we run the algorithm, feeding it a v-curve, and watching it solve.
0213     // First pass: Should see the position reducing by initialStepSize
0214     position = focuser->newMeasurement(currentPosition, 5, 1);
0215     QCOMPARE(position, currentPosition - params.initialStepSize);
0216     currentPosition = position;
0217 
0218     position = focuser->newMeasurement(currentPosition, 4, 1);
0219     QCOMPARE(position, currentPosition - params.initialStepSize);
0220     currentPosition = position;
0221 
0222     position = focuser->newMeasurement(currentPosition, 3, 1);
0223     QCOMPARE(position, currentPosition - params.initialStepSize);
0224     currentPosition = position;
0225 
0226     position = focuser->newMeasurement(currentPosition, 2, 1);
0227     QCOMPARE(position, currentPosition - params.initialStepSize);
0228     currentPosition = position;
0229 
0230     position = focuser->newMeasurement(currentPosition, 1, 1);
0231     QCOMPARE(position, currentPosition - params.initialStepSize);
0232     currentPosition = position;
0233 
0234     // Level off and then increase.
0235 
0236     position = focuser->newMeasurement(currentPosition, 1, 1);
0237     QCOMPARE(position, currentPosition - params.initialStepSize);
0238     currentPosition = position;
0239 
0240     position = focuser->newMeasurement(currentPosition, 2, 1);
0241     QCOMPARE(position, currentPosition - params.initialStepSize);
0242     currentPosition = position;
0243 
0244     position = focuser->newMeasurement(currentPosition, 3, 1);
0245     QCOMPARE(position, currentPosition - params.initialStepSize);
0246     currentPosition = position;
0247 
0248     // At the point the focuser should end the first pass and start a new 2nd pass.
0249     // The position to start the 2nd pass is hardcoded here (it is a polynomial
0250     // curve-fit of the above data).
0251     int secondPassStart = 10064;
0252     position = focuser->newMeasurement(currentPosition, 4, 1);
0253     QCOMPARE(position, secondPassStart);
0254     currentPosition = position;
0255 
0256     // The 2nd pass steps down by half step-size, and should terminate when we
0257     // pass in an HFR within tolerance of the best HFR in the first pass (which was 1.0).
0258 
0259     position = focuser->newMeasurement(currentPosition, 1.4, 1);
0260     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0261     currentPosition = position;
0262 
0263     // Save the state from here for testing the retry logic below.
0264     std::unique_ptr<FocusAlgorithmInterface> focuserRetry(focuser->Copy());
0265     int retryPosition = currentPosition;
0266 
0267     position = focuser->newMeasurement(currentPosition, 1.3, 1);
0268     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0269     currentPosition = position;
0270 
0271     position = focuser->newMeasurement(currentPosition, 1.2, 1);
0272     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0273     currentPosition = position;
0274 
0275     position = focuser->newMeasurement(currentPosition, 1.1, 1);
0276     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0277     currentPosition = position;
0278 
0279     // Making a copy of the focuser state at this point, for testing a scenario below
0280     // where the system does not get close enough to the desired HFR.
0281     std::unique_ptr<FocusAlgorithmInterface> focuserMissTolerance(focuser->Copy());
0282     int missTolerancePosition = currentPosition;
0283 
0284     // 1.04 is within tolerance of 1.0
0285     // It would complete, but we now cross the 1st-pass-best position (10014)
0286     // and use even smaller steps.
0287     position = focuser->newMeasurement(currentPosition, 1.04, 1);
0288     QCOMPARE(position, currentPosition - params.initialStepSize / 4);
0289     QVERIFY(!focuser->isDone());
0290     currentPosition = position;
0291 
0292     // Now let it finally complete.
0293     position = focuser->newMeasurement(currentPosition, 1.3, 1);
0294     QCOMPARE(position, currentPosition);
0295     position = focuser->newMeasurement(currentPosition, 1.3, 1);
0296     // Since the focuser is past the 1st pass min, it only retries once then finishes.
0297     QCOMPARE(position, -1);
0298     QVERIFY(focuser->isDone());
0299     QCOMPARE(focuser->solution(), currentPosition);
0300 
0301     // Test the focuser retry logic. When the focuser gets within its tolerance of the solution
0302     // in its 2nd pass, but hasn't yet reached the 1st pass' minimum, it will continue to iterate
0303     // and move its position inward as the HFRs it measures continue to improve.
0304     // However, if the HFR sample got worse, it will resample at the same position a few times,
0305     // in case there was noise in the sample.
0306 
0307     focuser.swap(focuserRetry);
0308     position = focuser->newMeasurement(retryPosition, 1.04, 1);
0309     QCOMPARE(position, retryPosition - params.initialStepSize / 2);
0310     currentPosition = position;
0311 
0312     position = focuser->newMeasurement(currentPosition, 1.05, 1);
0313     // HFR got worse, we're within tolderance of the solution. It should retry the same position.
0314     QCOMPARE(position, currentPosition);
0315 
0316     position = focuser->newMeasurement(currentPosition, 1.10, 1);
0317     QCOMPARE(position, currentPosition);
0318 
0319     position = focuser->newMeasurement(currentPosition, 1.10, 1);
0320     QCOMPARE(position, currentPosition);
0321 
0322     // Making a copy of the focuser state at this point, for later testing.
0323     std::unique_ptr<FocusAlgorithmInterface> focuserRetry2(focuser->Copy());
0324     int retry2Position = currentPosition;
0325 
0326     // Try 2 scenarious from this position.
0327 
0328     // 1. HFR is worse again compared to the best value above (1.04), give up.
0329     position = focuser->newMeasurement(retry2Position, 1.05, 1);
0330     QCOMPARE(position, -1);
0331     QVERIFY(focuser->isDone());
0332     QCOMPARE(focuser->solution(), currentPosition);
0333 
0334     // 2. HFR gets better. Continue moving toward the pass1 solution.
0335     focuser.swap(focuserRetry2);
0336     position = focuser->newMeasurement(retry2Position, 1.03, 1);
0337     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0338     currentPosition = position;
0339 
0340     position = focuser->newMeasurement(currentPosition, 1.02, 1);
0341     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0342     currentPosition = position;
0343 
0344     // Finally we're close enough to the 1st pass best to finish.
0345     // Since we crossed the 1st pass best position, we use a smaller step size.
0346     position = focuser->newMeasurement(currentPosition, 1.01, 1);
0347     QCOMPARE(position, currentPosition - params.initialStepSize / 4);
0348     currentPosition = position;
0349 
0350     // Fail a few times trying to get better
0351     position = focuser->newMeasurement(currentPosition, 1.05, 1);
0352     QCOMPARE(position, currentPosition);
0353     position = focuser->newMeasurement(currentPosition, 1.05, 1);
0354     // Since the focuser is past the 1st pass min, it only retries once then finishes.
0355     QCOMPARE(position, -1);
0356     QVERIFY(focuser->isDone());
0357     QCOMPARE(focuser->solution(), currentPosition);
0358 
0359     // Retry testing is done.
0360 
0361     // We pick up from above just before the system reached the desired tolerance.
0362     // In this case, it misses the desired HFR value, and continues processing.
0363     focuser.swap(focuserMissTolerance);
0364 
0365     // Sample HFR=1.06 instead of the 1.04 above--not within tolerance.
0366     position = focuser->newMeasurement(missTolerancePosition, 1.06, 1);
0367     QCOMPARE(position, missTolerancePosition - params.initialStepSize / 2);
0368     currentPosition = position;
0369 
0370     // HFRs continue to get worse (the min on this v-curve missed the threshold).
0371     position = focuser->newMeasurement(currentPosition, 1.1, 1);
0372     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0373     currentPosition = position;
0374 
0375     position = focuser->newMeasurement(currentPosition, 1.5, 1);
0376     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0377     currentPosition = position;
0378 
0379     // At this point, the focuser realizes it missed the minimum and resets.
0380     // The value it goes back to is a polynomial fit, and hardcoded here.
0381     int returnPosition = 10038;
0382     position = focuser->newMeasurement(currentPosition, 2.0, 1);
0383     QCOMPARE(position, returnPosition);
0384     currentPosition = position;
0385 
0386     position = focuser->newMeasurement(currentPosition, 1.5, 1);
0387     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0388     currentPosition = position;
0389 
0390     position = focuser->newMeasurement(currentPosition, 1.1, 1);
0391     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0392     currentPosition = position;
0393 
0394     // Making a copy of the focuser state at this point, for later testing.
0395     std::unique_ptr<FocusAlgorithmInterface> focuser2(focuser->Copy());
0396     int currentPosition2 = currentPosition;
0397 
0398     // This time we get within tolerance,
0399     // and we've crossed the 1st-pass min so smaller step size.
0400     position = focuser->newMeasurement(currentPosition, 1.03, 1);
0401     QCOMPARE(position, currentPosition - params.initialStepSize / 4);
0402     currentPosition = position;
0403 
0404     // Start getting worse and eventually finish.
0405     position = focuser->newMeasurement(currentPosition, 1.10, 1);
0406     QCOMPARE(position, currentPosition);
0407     position = focuser->newMeasurement(currentPosition, 1.10, 1);
0408     // Since the focuser is past the 1st pass min, it only retries once then finishes.
0409     QCOMPARE(position, -1);
0410     QVERIFY(focuser->isDone());
0411     QCOMPARE(focuser->solution(), currentPosition);
0412 
0413     // Alternative reality.
0414     focuser.swap(focuser2);
0415 
0416     // We miss the tolerance again.
0417     position = focuser->newMeasurement(currentPosition2, 1.06, 1);
0418     QCOMPARE(position, currentPosition2 - params.initialStepSize / 2);
0419     currentPosition = position;
0420 
0421     position = focuser->newMeasurement(currentPosition, 1.1, 1);
0422     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0423     currentPosition = position;
0424 
0425     position = focuser->newMeasurement(currentPosition, 1.2, 1);
0426     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0427     currentPosition = position;
0428 
0429     // It should notice we're getting worse (3 consecutive) and reset again.
0430     int returnPosition2 = 10038;
0431     position = focuser->newMeasurement(currentPosition, 1.3, 1);
0432     QCOMPARE(position, returnPosition2);
0433     currentPosition = position;
0434 
0435     position = focuser->newMeasurement(currentPosition2, 1.06, 1);
0436     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0437     currentPosition = position;
0438 
0439     position = focuser->newMeasurement(currentPosition2, 1.1, 1);
0440     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0441     currentPosition = position;
0442 
0443     position = focuser->newMeasurement(currentPosition2, 1.2, 1);
0444     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0445     currentPosition = position;
0446 
0447     // Again it notices 3 bad in a row
0448     int returnPosition3 = 10038;
0449     position = focuser->newMeasurement(currentPosition, 1.4, 1);
0450     QCOMPARE(position, returnPosition3);
0451 
0452     // Now we're within 3 of the max number of iterations.
0453     // It resets again, trying to place us near the best position seen.
0454     int returnPosition4 = 10031;
0455     position = focuser->newMeasurement(currentPosition2, 1.06, 1);
0456     QCOMPARE(position, returnPosition4);
0457     currentPosition = position;
0458 
0459     position = focuser->newMeasurement(currentPosition2, 1.2, 1);
0460     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0461     currentPosition = position;
0462 
0463     position = focuser->newMeasurement(currentPosition2, 1.3, 1);
0464     QCOMPARE(position, currentPosition - params.initialStepSize / 2);
0465     currentPosition = position;
0466 
0467     // And at this point we exceed the allowed number of steps (30) and fail.
0468     position = focuser->newMeasurement(currentPosition2, 1.4, 1);
0469     QCOMPARE(position, -1);
0470     QVERIFY(focuser->isDone());
0471     QCOMPARE(focuser->solution(), -1);
0472 }
0473 
0474 // This test replays a situation from a log where the focuser started "too low"
0475 // and restarts a bit higher.
0476 void TestFocus::restartTest()
0477 {
0478     auto params = makeParams();
0479     params.startPosition = 11592;
0480     std::unique_ptr<FocusAlgorithmInterface> focuser(MakeLinearFocuser(params));
0481 
0482     int currentPosition = focuser->initialPosition();
0483     QCOMPARE(currentPosition, 11717);
0484 
0485     int position = focuser->newMeasurement(currentPosition, 1.24586, 1);
0486     QCOMPARE(position, 11692);
0487     currentPosition = position;
0488 
0489     position = focuser->newMeasurement(currentPosition, 1.22075, 1);
0490     QCOMPARE(position, 11667);
0491     currentPosition = position;
0492 
0493     position = focuser->newMeasurement(currentPosition, 1.47694, 1);
0494     QCOMPARE(position, 11642);
0495     currentPosition = position;
0496 
0497     position = focuser->newMeasurement(currentPosition, 1.71925, 1);
0498     QCOMPARE(position, 11617);
0499     currentPosition = position;
0500 
0501     position = focuser->newMeasurement(currentPosition, 1.73312, 1);
0502     QCOMPARE(position, 11592);
0503     currentPosition = position;
0504 
0505     position = focuser->newMeasurement(currentPosition, 1.90926, 1);
0506     QCOMPARE(position, 11567);
0507     currentPosition = position;
0508 
0509     position = focuser->newMeasurement(currentPosition, 2.09901, 1);
0510 
0511     // At this point it should restart with a higher initial position, but not too high.
0512     QCOMPARE(position, 11842);
0513 }
0514 
0515 void TestFocus::L1PHyperbolaTest()
0516 {
0517     // Linear 1 Pass hyperbola curve test.
0518     auto params = makeL1PHyperbolaParams();
0519     std::unique_ptr<FocusAlgorithmInterface> focuser(MakeLinearFocuser(params));
0520     int position = focuser->initialPosition();
0521     // The first position should be initialPosition + stepSize * initialOutwardSteps,
0522     // unless maxPositionAllowed or maxTravel doesn't allow that.
0523     QCOMPARE(position, static_cast <int> (params.startPosition + params.initialOutwardSteps * params.initialStepSize));
0524 
0525     params.maxTravel = 4 * params.initialStepSize;
0526     focuser.reset(MakeLinearFocuser(params));
0527     position = focuser->initialPosition();
0528     QCOMPARE(position, params.startPosition + params.maxTravel);
0529 
0530     params.maxPositionAllowed = params.startPosition + 3 * params.initialStepSize;
0531     focuser.reset(MakeLinearFocuser(params));
0532     position = focuser->initialPosition();
0533     QCOMPARE(position, params.maxPositionAllowed);
0534 
0535     // go back to the default params
0536     params = makeL1PHyperbolaParams();
0537     focuser.reset(MakeLinearFocuser(params));
0538     int currentPosition = focuser->initialPosition();
0539 
0540     // Here we run the algorithm, feeding it a v-curve, and watching it solve.
0541     // Should see the position reducing by initialStepSize
0542     position = focuser->newMeasurement(currentPosition, 5.01, 1);
0543     QCOMPARE(position, currentPosition - params.initialStepSize);
0544     currentPosition = position;
0545 
0546     position = focuser->newMeasurement(currentPosition, 4.0067, 1);
0547     QCOMPARE(position, currentPosition - params.initialStepSize);
0548     currentPosition = position;
0549 
0550     position = focuser->newMeasurement(currentPosition, 2.981, 1);
0551     QCOMPARE(position, currentPosition - params.initialStepSize);
0552     currentPosition = position;
0553 
0554     position = focuser->newMeasurement(currentPosition, 2.03, 1);
0555     QCOMPARE(position, currentPosition - params.initialStepSize);
0556     currentPosition = position;
0557 
0558     position = focuser->newMeasurement(currentPosition, 0.996, 1);
0559     QCOMPARE(position, currentPosition - params.initialStepSize);
0560     currentPosition = position;
0561 
0562     position = focuser->newMeasurement(currentPosition, 0.9, 1);
0563     QCOMPARE(position, currentPosition - params.initialStepSize);
0564     currentPosition = position;
0565 
0566     // Level off and then increase.
0567 
0568     position = focuser->newMeasurement(currentPosition, 1.1, 1);
0569     QCOMPARE(position, currentPosition - params.initialStepSize);
0570     currentPosition = position;
0571 
0572     position = focuser->newMeasurement(currentPosition, 2.03, 1);
0573     QCOMPARE(position, currentPosition - params.initialStepSize);
0574     currentPosition = position;
0575 
0576     position = focuser->newMeasurement(currentPosition, 2.987, 1);
0577     QCOMPARE(position, currentPosition - params.initialStepSize);
0578     currentPosition = position;
0579 
0580     position = focuser->newMeasurement(currentPosition, 4.006, 1);
0581     QCOMPARE(position, currentPosition - params.initialStepSize);
0582     currentPosition = position;
0583 
0584     // Send in the last datapoint to complete the first pass. The focuser will move to the solution
0585     // found by the curve fitting process.
0586     int solution = 10000;
0587     position = focuser->newMeasurement(currentPosition, 5, 1);
0588     QCOMPARE(position, solution);
0589 
0590     // Check the latestHDR method
0591     QCOMPARE(focuser->latestValue(), 5.0);
0592 
0593     // Check the getMeasurements method
0594     QVector<int> pos;
0595     QVector<double> hfrs;
0596     QVector<double> weights;
0597     focuser->getMeasurements(&pos, &hfrs, &weights);
0598     QVERIFY(pos.size() == 11);
0599     QVERIFY(hfrs.size() == 11);
0600     QVERIFY(weights.size() == 11);
0601     QCOMPARE(pos.last(), currentPosition);
0602     QCOMPARE(hfrs.last(), 5.0);
0603     QCOMPARE(weights.last(), 1.0);
0604     currentPosition = position;
0605 
0606     // Check the getPass1Measurements method - for L1P this is the same as getMeasurement
0607     QVector<int> posPass1;
0608     QVector<double> hfrsPass1;
0609     QVector<double> weightsPass1;
0610     QVector<bool> outliersPass1;
0611     focuser->getPass1Measurements(&posPass1, &hfrsPass1, &weightsPass1, &outliersPass1);
0612     QCOMPARE(pos.last(), posPass1.last());
0613     QCOMPARE(hfrs.last(), hfrsPass1.last());
0614     QCOMPARE(weights.last(), weightsPass1.last());
0615 
0616     // Check the getTextStatus method
0617     double R2 = 0.8;
0618     QCOMPARE(focuser->getTextStatus(R2), "L1P [Green]: Hyperbola (W). Moving to Solution");
0619 
0620     // Now let it complete.
0621     position = focuser->newMeasurement(currentPosition, 0.91, 1);
0622     // Focuser returns a position of -1 to indicate its done.
0623     QCOMPARE(position, -1);
0624     // Check the isDone method
0625     QVERIFY(focuser->isDone());
0626     //Check the solution method
0627     QCOMPARE(focuser->solution(), currentPosition);
0628     // Check the doneReason method
0629     QCOMPARE(focuser->doneReason(), "Solution found.");
0630 }
0631 
0632 void TestFocus::L1PParabolaTest()
0633 {
0634     // Linear 1 Pass parabola curve test.
0635     auto params = makeL1PParabolaParams();
0636     std::unique_ptr<FocusAlgorithmInterface> focuser(MakeLinearFocuser(params));
0637     int position = focuser->initialPosition();
0638     // The first position should be initialPosition + stepSize * initialOutwardSteps,
0639     // unless maxPositionAllowed or maxTravel doesn't allow that.
0640     QCOMPARE(position, static_cast <int> (params.startPosition + params.initialOutwardSteps * params.initialStepSize));
0641 
0642     params.maxTravel = 4 * params.initialStepSize;
0643     focuser.reset(MakeLinearFocuser(params));
0644     position = focuser->initialPosition();
0645     QCOMPARE(position, params.startPosition + params.maxTravel);
0646 
0647     params.maxPositionAllowed = params.startPosition + 3 * params.initialStepSize;
0648     focuser.reset(MakeLinearFocuser(params));
0649     position = focuser->initialPosition();
0650     QCOMPARE(position, params.maxPositionAllowed);
0651 
0652     // go back to the default params
0653     params = makeL1PParabolaParams();
0654     focuser.reset(MakeLinearFocuser(params));
0655     int currentPosition = focuser->initialPosition();
0656 
0657     // Here we run the algorithm, feeding it a v-curve, and watching it solve.
0658     // Should see the position reducing by initialStepSize
0659     position = focuser->newMeasurement(currentPosition, 5.01, 1);
0660     QCOMPARE(position, currentPosition - params.initialStepSize);
0661     currentPosition = position;
0662 
0663     position = focuser->newMeasurement(currentPosition, 4.0067, 1);
0664     QCOMPARE(position, currentPosition - params.initialStepSize);
0665     currentPosition = position;
0666 
0667     position = focuser->newMeasurement(currentPosition, 2.981, 1);
0668     QCOMPARE(position, currentPosition - params.initialStepSize);
0669     currentPosition = position;
0670 
0671     position = focuser->newMeasurement(currentPosition, 2.03, 1);
0672     QCOMPARE(position, currentPosition - params.initialStepSize);
0673     currentPosition = position;
0674 
0675     position = focuser->newMeasurement(currentPosition, 0.996, 1);
0676     QCOMPARE(position, currentPosition - params.initialStepSize);
0677     currentPosition = position;
0678 
0679     position = focuser->newMeasurement(currentPosition, 0.9, 1);
0680     QCOMPARE(position, currentPosition - params.initialStepSize);
0681     currentPosition = position;
0682 
0683     // Level off and then increase.
0684 
0685     position = focuser->newMeasurement(currentPosition, 1.1, 1);
0686     QCOMPARE(position, currentPosition - params.initialStepSize);
0687     currentPosition = position;
0688 
0689     position = focuser->newMeasurement(currentPosition, 2.03, 1);
0690     QCOMPARE(position, currentPosition - params.initialStepSize);
0691     currentPosition = position;
0692 
0693     position = focuser->newMeasurement(currentPosition, 2.987, 1);
0694     QCOMPARE(position, currentPosition - params.initialStepSize);
0695     currentPosition = position;
0696 
0697     position = focuser->newMeasurement(currentPosition, 4.006, 1);
0698     QCOMPARE(position, currentPosition - params.initialStepSize);
0699     currentPosition = position;
0700 
0701     // Send in the last datapoint to complete the first pass. The focuser will move to the solution
0702     // found by the curve fitting process.
0703     int solution = 10000;
0704     position = focuser->newMeasurement(currentPosition, 5, 1);
0705     QCOMPARE(position, solution);
0706 
0707     // Check the latestHDR method
0708     QCOMPARE(focuser->latestValue(), 5.0);
0709 
0710     // Check the getMeasurements method
0711     QVector<int> pos;
0712     QVector<double> hfrs;
0713     QVector<double> weights;
0714     focuser->getMeasurements(&pos, &hfrs, &weights);
0715     QVERIFY(pos.size() == 11);
0716     QVERIFY(hfrs.size() == 11);
0717     QVERIFY(weights.size() == 11);
0718     QCOMPARE(pos.last(), currentPosition);
0719     QCOMPARE(hfrs.last(), 5.0);
0720     QCOMPARE(weights.last(), 1.0);
0721     currentPosition = position;
0722 
0723     // Check the getPass1Measurements method - for L1P this is the same as getMeasurement
0724     QVector<int> posPass1;
0725     QVector<double> hfrsPass1;
0726     QVector<double> weightsPass1;
0727     QVector<bool> outliersPass1;
0728     focuser->getPass1Measurements(&posPass1, &hfrsPass1, &weightsPass1, &outliersPass1);
0729     QCOMPARE(pos.last(), posPass1.last());
0730     QCOMPARE(hfrs.last(), hfrsPass1.last());
0731     QCOMPARE(weights.last(), weightsPass1.last());
0732 
0733     // Check the getTextStatus method
0734     double R2 = 0.8;
0735     QCOMPARE(focuser->getTextStatus(R2), "L1P [Blue]: Parabola (U). Moving to Solution");
0736 
0737     // Now let it complete.
0738     position = focuser->newMeasurement(currentPosition, 0.91, 1);
0739     // Focuser returns a position of -1 to indicate its done.
0740     QCOMPARE(position, -1);
0741     // Check the isDone method
0742     QVERIFY(focuser->isDone());
0743     //Check the solution method
0744     QCOMPARE(focuser->solution(), currentPosition);
0745     // Check the doneReason method
0746     QCOMPARE(focuser->doneReason(), "Solution found.");
0747 }
0748 
0749 void TestFocus::L1PQuadraticTest()
0750 {
0751     // Linear 1 Pass quadratic curve test - should be the same as parabola
0752     auto params = makeL1PQuadraticParams();
0753     std::unique_ptr<FocusAlgorithmInterface> focuser(MakeLinearFocuser(params));
0754     int position = focuser->initialPosition();
0755     // The first position should be initialPosition + stepSize * initialOutwardSteps,
0756     // unless maxPositionAllowed or maxTravel doesn't allow that.
0757     QCOMPARE(position, static_cast <int> (params.startPosition + params.initialOutwardSteps * params.initialStepSize));
0758 
0759     params.maxTravel = 4 * params.initialStepSize;
0760     focuser.reset(MakeLinearFocuser(params));
0761     position = focuser->initialPosition();
0762     QCOMPARE(position, params.startPosition + params.maxTravel);
0763 
0764     params.maxPositionAllowed = params.startPosition + 3 * params.initialStepSize;
0765     focuser.reset(MakeLinearFocuser(params));
0766     position = focuser->initialPosition();
0767     QCOMPARE(position, params.maxPositionAllowed);
0768 
0769     // go back to the default params
0770     params = makeL1PQuadraticParams();
0771     focuser.reset(MakeLinearFocuser(params));
0772     int currentPosition = focuser->initialPosition();
0773 
0774     // Here we run the algorithm, feeding it a v-curve, and watching it solve.
0775     // Should see the position reducing by initialStepSize
0776     position = focuser->newMeasurement(currentPosition, 5, 1);
0777     QCOMPARE(position, currentPosition - params.initialStepSize);
0778     currentPosition = position;
0779 
0780     position = focuser->newMeasurement(currentPosition, 4, 1);
0781     QCOMPARE(position, currentPosition - params.initialStepSize);
0782     currentPosition = position;
0783 
0784     position = focuser->newMeasurement(currentPosition, 3, 1);
0785     QCOMPARE(position, currentPosition - params.initialStepSize);
0786     currentPosition = position;
0787 
0788     position = focuser->newMeasurement(currentPosition, 2, 1);
0789     QCOMPARE(position, currentPosition - params.initialStepSize);
0790     currentPosition = position;
0791 
0792     position = focuser->newMeasurement(currentPosition, 1, 1);
0793     QCOMPARE(position, currentPosition - params.initialStepSize);
0794     currentPosition = position;
0795 
0796     position = focuser->newMeasurement(currentPosition, 0.9, 1);
0797     QCOMPARE(position, currentPosition - params.initialStepSize);
0798     currentPosition = position;
0799 
0800     // Level off and then increase.
0801 
0802     position = focuser->newMeasurement(currentPosition, 1.1, 1);
0803     QCOMPARE(position, currentPosition - params.initialStepSize);
0804     currentPosition = position;
0805 
0806     position = focuser->newMeasurement(currentPosition, 2, 1);
0807     QCOMPARE(position, currentPosition - params.initialStepSize);
0808     currentPosition = position;
0809 
0810     position = focuser->newMeasurement(currentPosition, 3, 1);
0811     QCOMPARE(position, currentPosition - params.initialStepSize);
0812     currentPosition = position;
0813 
0814     position = focuser->newMeasurement(currentPosition, 4, 1);
0815     QCOMPARE(position, currentPosition - params.initialStepSize);
0816     currentPosition = position;
0817 
0818     // Send in the last datapoint to complete the first pass. The focuser will move to the solution
0819     // found by the curve fitting process.
0820     int solution = 10000;
0821     position = focuser->newMeasurement(currentPosition, 5, 1);
0822     QCOMPARE(position, solution);
0823 
0824     // Check the latestHDR method
0825     QCOMPARE(focuser->latestValue(), 5.0);
0826 
0827     // Check the getMeasurements method
0828     QVector<int> pos;
0829     QVector<double> hfrs;
0830     QVector<double> weights;
0831     focuser->getMeasurements(&pos, &hfrs, &weights);
0832     QVERIFY(pos.size() == 11);
0833     QVERIFY(hfrs.size() == 11);
0834     QVERIFY(weights.size() == 11);
0835     QCOMPARE(pos.last(), currentPosition);
0836     QCOMPARE(hfrs.last(), 5.0);
0837     QCOMPARE(weights.last(), 1.0);
0838     currentPosition = position;
0839 
0840     // Check the getPass1Measurements method - for L1P this is the same as getMeasurement
0841     QVector<int> posPass1;
0842     QVector<double> hfrsPass1;
0843     QVector<double> weightsPass1;
0844     QVector<bool> outliersPass1;
0845     focuser->getPass1Measurements(&posPass1, &hfrsPass1, &weightsPass1, &outliersPass1);
0846     QCOMPARE(pos.last(), posPass1.last());
0847     QCOMPARE(hfrs.last(), hfrsPass1.last());
0848     QCOMPARE(weights.last(), weightsPass1.last());
0849 
0850     // Check the getTextStatus method
0851     double R2 = 0.8;
0852     QCOMPARE(focuser->getTextStatus(R2), "L1P [Lum]: Quadratic. Moving to Solution");
0853 
0854     // Now let it complete.
0855     position = focuser->newMeasurement(currentPosition, 0.91, 1);
0856     // Focuser returns a position of -1 to indicate its done.
0857     QCOMPARE(position, -1);
0858     // Check the isDone method
0859     QVERIFY(focuser->isDone());
0860     //Check the solution method
0861     QCOMPARE(focuser->solution(), currentPosition);
0862     // Check the doneReason method
0863     QCOMPARE(focuser->doneReason(), "Solution found.");
0864 }
0865 
0866 QTEST_GUILESS_MAIN(TestFocus)