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

0001 /*  KStars tests
0002     SPDX-FileCopyrightText: 2020 Eric Dejouhanet <eric.dejouhanet@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <QTest>
0008 #include <memory>
0009 #include "testfitsdata.h"
0010 #include "Options.h"
0011 #include "ekos/auxiliary/solverutils.h"
0012 #include "ekos/auxiliary/stellarsolverprofile.h"
0013 #include <QtGlobal>
0014 
0015 Q_DECLARE_METATYPE(FITSMode);
0016 
0017 TestFitsData::TestFitsData(QObject *parent) : QObject(parent)
0018 {
0019 }
0020 
0021 void TestFitsData::initTestCase()
0022 {
0023     Options::setStellarSolverPartition(true);
0024 }
0025 
0026 void TestFitsData::cleanupTestCase()
0027 {
0028 }
0029 
0030 void TestFitsData::init()
0031 {
0032 
0033 }
0034 
0035 void TestFitsData::cleanup()
0036 {
0037 
0038 }
0039 
0040 void TestFitsData::testComputeHFR_data()
0041 {
0042 #if QT_VERSION < 0x050900
0043     QSKIP("Skipping fixture-based test on old QT version.");
0044 #else
0045     QTest::addColumn<QString>("NAME");
0046     QTest::addColumn<FITSMode>("MODE");
0047     QTest::addColumn<int>("NSTARS");
0048     QTest::addColumn<double>("HFR");
0049 
0050     // Normal HFR differs from focusHFR, in that the 'quickHFR' setting
0051     // defaults to true, so only the 25% in the center of the image
0052     // will have stars detected and HFRs computed. 'quickHFR' does not apply
0053     // to FITS_FOCUS images, only to FITS_NORMAL images.
0054 
0055     // Normal HFR tests
0056     QTest::newRow("NGC4535-1-FOCUS") << "ngc4535-autofocus1.fits" << FITS_FOCUS << 11 << 3.92;
0057     QTest::newRow("NGC4535-2-FOCUS") << "ngc4535-autofocus2.fits" << FITS_FOCUS << 17 << 2.13;
0058     QTest::newRow("NGC4535-3-FOCUS") << "ngc4535-autofocus3.fits" << FITS_FOCUS << 126 << 1.254911;
0059 
0060     // Focus HFR tests
0061     QTest::newRow("NGC4535-1-NORMAL") << "ngc4535-autofocus1.fits" << FITS_NORMAL << 3 << 3.17;
0062     QTest::newRow("NGC4535-2-NORMAL") << "ngc4535-autofocus2.fits" << FITS_NORMAL << 4 << 1.99;
0063     QTest::newRow("NGC4535-3-NORMAL") << "ngc4535-autofocus3.fits" << FITS_NORMAL << 30 << 1.22;
0064 #endif
0065 }
0066 
0067 void TestFitsData::testComputeHFR()
0068 {
0069 #if QT_VERSION < 0x050900
0070     QSKIP("Skipping fixture-based test on old QT version.");
0071 #else
0072     QFETCH(QString, NAME);
0073     QFETCH(FITSMode, MODE);
0074     QFETCH(int, NSTARS);
0075     QFETCH(double, HFR);
0076 
0077     if(!QFile::exists(NAME))
0078         QSKIP("Skipping load test because of missing fixture");
0079 
0080     std::unique_ptr<FITSData> d(new FITSData(MODE));
0081     QVERIFY(d != nullptr);
0082 
0083     QFuture<bool> worker = d->loadFromFile(NAME);
0084     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 60000);
0085     QVERIFY(worker.result());
0086 
0087     worker = d->findStars(ALGORITHM_SEP);
0088     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
0089     QVERIFY(worker.result());
0090 
0091     QCOMPARE(d->getDetectedStars(), NSTARS);
0092     QCOMPARE(d->getStarCenters().count(), NSTARS);
0093     QVERIFY2(abs(d->getHFR() - HFR) <= 0.1, qPrintable(QString("HFR expected(measured): %1(%2)").arg(HFR).arg(d->getHFR())));
0094 #endif
0095 }
0096 
0097 void TestFitsData::testBahtinovFocusHFR_data()
0098 {
0099 #if QT_VERSION < 0x050900
0100     QSKIP("Skipping fixture-based test on old QT version.");
0101 #else
0102     QTest::addColumn<QString>("NAME");
0103     QTest::addColumn<FITSMode>("MODE");
0104     QTest::addColumn<int>("NSTARS");
0105     QTest::addColumn<double>("HFR");
0106 
0107     QTest::newRow("BAHTINOV-1-NORMAL") << "bahtinov-focus.fits" << FITS_NORMAL << 1 << 1.544;
0108 #endif
0109 }
0110 
0111 void TestFitsData::testBahtinovFocusHFR()
0112 {
0113 #if QT_VERSION < 0x050900
0114     QSKIP("Skipping fixture-based test on old QT version.");
0115 #else
0116     QFETCH(QString, NAME);
0117     QFETCH(FITSMode, MODE);
0118     QFETCH(int, NSTARS);
0119     QFETCH(double, HFR);
0120 
0121     if(!QFile::exists(NAME))
0122         QSKIP("Skipping load test because of missing fixture");
0123 
0124     std::unique_ptr<FITSData> d(new FITSData(MODE));
0125     QVERIFY(d != nullptr);
0126 
0127     QFuture<bool> worker = d->loadFromFile(NAME);
0128     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
0129     QVERIFY(worker.result());
0130 
0131     // The bahtinov algorithm depends on which star is selected and number of average rows - not sure how to fiddle with that yet
0132     const QRect trackingBox(204, 240, 128, 128);
0133 
0134     d->findStars(ALGORITHM_BAHTINOV, trackingBox).waitForFinished();
0135     QCOMPARE(d->getDetectedStars(), NSTARS);
0136     QCOMPARE(d->getStarCenters().count(), 1);
0137     QVERIFY(abs(d->getHFR() - HFR) < 0.01);
0138 #endif
0139 }
0140 
0141 void TestFitsData::initGenericDataFixture()
0142 {
0143 #if QT_VERSION < 0x050900
0144     QSKIP("Skipping fixture-based test on old QT version.");
0145 #else
0146     QTest::addColumn<QString>("NAME");
0147     QTest::addColumn<FITSMode>("MODE");
0148 
0149     // Star count
0150     QTest::addColumn<int>("NSTARS_CENTROID");
0151     QTest::addColumn<int>("NSTARS_STELLARSOLVER");
0152 
0153     // HFRs using variouls methods
0154     QTest::addColumn<double>("HFR_CENTROID");
0155     QTest::addColumn<double>("HFR_GRADIENT");
0156     QTest::addColumn<double>("HFR_THRESHOLD");
0157     QTest::addColumn<double>("HFR_STELLARSOLVER");
0158 
0159     // Statistics
0160     QTest::addColumn<double>("ADU");
0161     QTest::addColumn<double>("MEAN");
0162     QTest::addColumn<double>("STDDEV");
0163     QTest::addColumn<double>("SNR");
0164     QTest::addColumn<long>("MAXIMUM");
0165     QTest::addColumn<long>("MINIMUM");
0166     QTest::addColumn<double>("MEDIAN");
0167 
0168     // This tracking box should detect a single centered star using SEP
0169     QTest::addColumn<QRect>("TRACKING_BOX");
0170 
0171     QTest::newRow("M47-1-NORMAL")
0172             << "m47_sim_stars.fits"
0173             << FITS_NORMAL
0174             << 80       // Stars found with the Centroid detection
0175             << 104      // Stars found with the StellarSolver detection - default profile limits count
0176             << 1.49     // HFR found with the Centroid detection
0177             << 1.482291 // HFR found with the Gradient detection
0178             << 0.0      // HFR found with the Threshold detection - not used
0179             << 1.482291 // HFR found with the StellarSolver detection
0180             << 41.08    // ADU
0181             << 41.08    // Mean
0182             << 360.2932 // StdDev
0183             << 0.114    // SNR
0184             << 57832L   // Max
0185             << 21L      // Min
0186             << 31.0     // Median
0187             << QRect(591 - 16 / 2, 482 - 16 / 2, 16, 16);
0188 #endif
0189 }
0190 
0191 void TestFitsData::testLoadFits_data()
0192 {
0193 #if QT_VERSION < 0x050900
0194     QSKIP("Skipping fixture-based test on old QT version.");
0195 #else
0196     initGenericDataFixture();
0197 #endif
0198 }
0199 
0200 void TestFitsData::testLoadFits()
0201 {
0202 #if QT_VERSION < 0x050900
0203     QSKIP("Skipping fixture-based test on old QT version.");
0204 #else
0205     QFETCH(QString, NAME);
0206     QFETCH(FITSMode, MODE);
0207     QFETCH(int, NSTARS_CENTROID);
0208     QFETCH(int, NSTARS_STELLARSOLVER);
0209     QFETCH(double, HFR_CENTROID);
0210     QFETCH(double, HFR_GRADIENT);
0211     QFETCH(double, HFR_THRESHOLD);
0212     Q_UNUSED(HFR_THRESHOLD);
0213     QFETCH(double, HFR_STELLARSOLVER);
0214     QFETCH(double, ADU);
0215     QFETCH(double, MEAN);
0216     QFETCH(double, STDDEV);
0217     QFETCH(double, SNR);
0218     QFETCH(long, MAXIMUM);
0219     QFETCH(long, MINIMUM);
0220     QFETCH(double, MEDIAN);
0221     QFETCH(QRect, TRACKING_BOX);
0222 
0223     if(!QFile::exists(NAME))
0224         QSKIP("Skipping load test because of missing fixture");
0225 
0226     std::unique_ptr<FITSData> fd(new FITSData(MODE));
0227     QVERIFY(fd != nullptr);
0228 
0229     QFuture<bool> worker = fd->loadFromFile(NAME);
0230     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
0231     QVERIFY(worker.result());
0232 
0233     // Statistics computation
0234     QVERIFY(abs(fd->getADU() - ADU) < 0.01);
0235     QVERIFY(abs(fd->getMean() - MEAN) < 0.01);
0236     fprintf(stderr, "%f vs %f (%f\n", fd->getStdDev(), STDDEV, abs(fd->getStdDev() - STDDEV));
0237     QVERIFY(abs(fd->getStdDev() - STDDEV) < 0.01);
0238     QVERIFY(abs(fd->getSNR() - SNR) < 0.001);
0239 
0240     // Minmax
0241     QCOMPARE((long)fd->getMax(), MAXIMUM);
0242     QCOMPARE((long)fd->getMin(), MINIMUM);
0243 
0244     QVERIFY(abs(fd->getMedian() - MEDIAN) < 0.01);
0245 
0246     // Without searching for stars, there are no stars found
0247     QCOMPARE(fd->getStarCenters().count(), 0);
0248     QCOMPARE(fd->getHFR(), -1.0);
0249 
0250     // Default algorithm is centroid, 80 stars with 1.495 as HFR
0251     fd->findStars().waitForFinished();
0252     QCOMPARE(fd->getDetectedStars(), NSTARS_CENTROID);
0253     QCOMPARE(fd->getStarCenters().count(), NSTARS_CENTROID);
0254     QVERIFY(abs(fd->getHFR() - HFR_CENTROID) < 0.01);
0255 
0256     // With the centroid algorithm, 80 stars with MEAN HFR 1.495
0257     fd->findStars(ALGORITHM_CENTROID).waitForFinished();
0258     QCOMPARE(fd->getDetectedStars(), NSTARS_CENTROID);
0259     QCOMPARE(fd->getStarCenters().count(), NSTARS_CENTROID);
0260     QVERIFY(abs(fd->getHFR() - HFR_CENTROID) < 0.01);
0261 
0262     // With the gradient algorithm, one single star found with HFR 1.801
0263     fd->findStars(ALGORITHM_GRADIENT).waitForFinished();
0264     QCOMPARE(fd->getDetectedStars(), 1);
0265     QCOMPARE(fd->getStarCenters().count(), 1);
0266     QVERIFY(abs(fd->getHFR() - HFR_GRADIENT) < 0.01);
0267 
0268     // The threshold algorithm depends on a global option - skip until we know how to fiddle with that
0269     //QCOMPARE(fd->findStars(ALGORITHM_THRESHOLD), -1);
0270     //QCOMPARE(fd->getDetectedStars(), 0);
0271     //QCOMPARE(fd->getStarCenters().count(), 0);
0272     //QCOMPARE(fd->getHFR(), -1.0);
0273 
0274     // With the SEP algorithm, 100 stars with MEAN HFR 2.08
0275     fd->findStars(ALGORITHM_SEP).waitForFinished();
0276     QCOMPARE(fd->getDetectedStars(), NSTARS_STELLARSOLVER);
0277     QCOMPARE(fd->getStarCenters().count(), NSTARS_STELLARSOLVER);
0278     QVERIFY(abs(fd->getHFR() - HFR_STELLARSOLVER) < 0.1);
0279 
0280     // Test the SEP algorithm with a tracking box, as used by the internal guider and subframe focus.
0281     fd->findStars(ALGORITHM_SEP, TRACKING_BOX).waitForFinished();
0282     auto centers = fd->getStarCenters();
0283     QCOMPARE(centers.count(), 1);
0284     // QWARN(QString("Center    %1,%2").arg(centers[0]->x).arg(centers[0]->y).toStdString().c_str());
0285     // QWARN(QString("TB Center %1,%2").arg(TRACKING_BOX.center().x()).arg(TRACKING_BOX.center().y()).toStdString().c_str());
0286     QVERIFY(abs(centers[0]->x - TRACKING_BOX.center().x()) <= 5);
0287     QVERIFY(abs(centers[0]->y - TRACKING_BOX.center().y()) <= 5);
0288 #endif
0289 }
0290 
0291 void TestFitsData::testCentroidAlgorithmBenchmark_data()
0292 {
0293 #if QT_VERSION < 0x050900
0294     QSKIP("Skipping fixture-based test on old QT version.");
0295 #else
0296     initGenericDataFixture();
0297 #endif
0298 }
0299 
0300 void TestFitsData::testCentroidAlgorithmBenchmark()
0301 {
0302 #if QT_VERSION < 0x050900
0303     QSKIP("Skipping fixture-based test on old QT version.");
0304 #else
0305     QFETCH(QString, NAME);
0306 
0307     if(!QFile::exists(NAME))
0308         QSKIP("Skipping load test because of missing fixture");
0309 
0310     std::unique_ptr<FITSData> d(new FITSData());
0311     QVERIFY(d != nullptr);
0312 
0313     QFuture<bool> worker = d->loadFromFile(NAME);
0314     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
0315     QVERIFY(worker.result());
0316 
0317     QBENCHMARK { d->findStars(ALGORITHM_CENTROID).waitForFinished(); }
0318 #endif
0319 }
0320 
0321 void TestFitsData::testGradientAlgorithmBenchmark_data()
0322 {
0323 #if QT_VERSION < 0x050900
0324     QSKIP("Skipping fixture-based test on old QT version.");
0325 #else
0326     initGenericDataFixture();
0327 #endif
0328 }
0329 
0330 void TestFitsData::testGradientAlgorithmBenchmark()
0331 {
0332 #if QT_VERSION < 0x050900
0333     QSKIP("Skipping fixture-based test on old QT version.");
0334 #else
0335     QFETCH(QString, NAME);
0336 
0337     if(!QFile::exists(NAME))
0338         QSKIP("Skipping load test because of missing fixture");
0339 
0340     std::unique_ptr<FITSData> d(new FITSData());
0341     QVERIFY(d != nullptr);
0342 
0343     QFuture<bool> worker = d->loadFromFile(NAME);
0344     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
0345     QVERIFY(worker.result());
0346 
0347     QBENCHMARK { d->findStars(ALGORITHM_GRADIENT).waitForFinished(); }
0348 #endif
0349 }
0350 
0351 void TestFitsData::testThresholdAlgorithmBenchmark_data()
0352 {
0353 #if QT_VERSION < 0x050900
0354     QSKIP("Skipping fixture-based test on old QT version.");
0355 #else
0356     initGenericDataFixture();
0357 #endif
0358 }
0359 
0360 void TestFitsData::testThresholdAlgorithmBenchmark()
0361 {
0362 #if QT_VERSION < 0x050900
0363     QSKIP("Skipping fixture-based test on old QT version.");
0364 #else
0365     QSKIP("Skipping benchmark of Threshold Algorithm, which requires fiddling with a global option");
0366 
0367     QFETCH(QString, NAME);
0368 
0369     if(!QFile::exists(NAME))
0370         QSKIP("Skipping load test because of missing fixture");
0371 
0372     std::unique_ptr<FITSData> d(new FITSData());
0373     QVERIFY(d != nullptr);
0374 
0375     QFuture<bool> worker = d->loadFromFile(NAME);
0376     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
0377     QVERIFY(worker.result());
0378 
0379     QBENCHMARK { d->findStars(ALGORITHM_THRESHOLD).waitForFinished(); }
0380 #endif
0381 }
0382 
0383 void TestFitsData::testSEPAlgorithmBenchmark_data()
0384 {
0385 #if QT_VERSION < 0x050900
0386     QSKIP("Skipping fixture-based test on old QT version.");
0387 #else
0388     initGenericDataFixture();
0389 #endif
0390 }
0391 
0392 void TestFitsData::testSEPAlgorithmBenchmark()
0393 {
0394 #if QT_VERSION < 0x050900
0395     QSKIP("Skipping fixture-based test on old QT version.");
0396 #else
0397     QFETCH(QString, NAME);
0398 
0399     if(!QFile::exists(NAME))
0400         QSKIP("Skipping load test because of missing fixture");
0401 
0402     std::unique_ptr<FITSData> d(new FITSData());
0403     QVERIFY(d != nullptr);
0404 
0405     QFuture<bool> worker = d->loadFromFile(NAME);
0406     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
0407     QVERIFY(worker.result());
0408 
0409     QBENCHMARK { d->findStars(ALGORITHM_SEP).waitForFinished(); }
0410 #endif
0411 }
0412 
0413 QString SolverLoop::status() const
0414 {
0415     return QString("%1/%2 %3% %4 %5")
0416            .arg(upto()).arg(repetitions).arg(upto() * 100.0 / repetitions, 2, 'f', 0)
0417            .arg(solver.get() && solver->isRunning() ? " running" : "")
0418            .arg(done() ? " Done" : "");
0419 }
0420 
0421 SolverLoop::SolverLoop(const QVector<QString> &files, const QString &dir, bool isDetecting, int numReps)
0422 {
0423     filenames = files;
0424     repetitions = numReps;
0425     directory = dir;
0426     detecting = isDetecting;
0427 
0428     // Preload the images.
0429     for (const QString &fname : files)
0430     {
0431         images.push_back(QSharedPointer<FITSData>(new FITSData));
0432         images.back().get()->loadFromFile(QString("%1/%2").arg(dir, fname)).waitForFinished();
0433     }
0434 }
0435 
0436 void SolverLoop::start()
0437 {
0438     startDetect(numDetects % filenames.size());
0439 }
0440 
0441 bool SolverLoop::done() const
0442 {
0443     return numDetects >= repetitions;
0444 }
0445 
0446 int SolverLoop::upto() const
0447 {
0448     return numDetects;
0449 }
0450 
0451 void SolverLoop::timeout()
0452 {
0453     qInfo() << QString("timed out on %1 %2 !!!!!!!!!!!!!!!!!!!!!").arg(currentIndex).arg(filenames[currentIndex]);
0454     if (detecting)
0455         watcher.cancel();
0456     else
0457         solver->abort();
0458 
0459     startDetect(currentIndex);
0460 }
0461 
0462 void SolverLoop::solverDone(bool timedOut, bool success, const FITSImage::Solution &solution,
0463                             double elapsedSeconds)
0464 {
0465     disconnect(&timer, &QTimer::timeout, this, &SolverLoop::timeout);
0466     timer.stop();
0467     disconnect(solver.get(), &SolverUtils::done, this, &SolverLoop::solverDone);
0468 
0469     const auto &filename = filenames[currentIndex];
0470     if (timedOut)
0471         qInfo() << QString("#%1: %2 Solver timed out: %3s").arg(numDetects).arg(filename).arg(elapsedSeconds, 0, 'f', 1);
0472     else if (!success)
0473         qInfo() << QString("#%1: %2 Solver failed: %3s").arg(numDetects).arg(filename).arg(elapsedSeconds, 0, 'f', 1);
0474     else
0475     {
0476         const double ra = solution.ra;
0477         const double dec = solution.dec;
0478         const double scale = solution.pixscale;
0479         qInfo() << QString("#%1: %2 Solver returned RA %3 DEC %4 Scale %5: %6s").arg(numDetects).arg(filename)
0480                 .arg(ra, 6, 'f', 3).arg(dec, 6, 'f', 3).arg(scale).arg(elapsedSeconds, 4, 'f', 1);
0481 
0482         if (numDetects++ < repetitions)
0483             startDetect(numDetects % filenames.size());
0484     }
0485 }
0486 
0487 void SolverLoop::randomTimeout()
0488 {
0489     disconnect(&watcher, &QFutureWatcher<bool>::finished, this, &SolverLoop::detectFinished);
0490     disconnect(&watcher, &QFutureWatcher<bool>::canceled, this, &SolverLoop::detectFinished);
0491     disconnect(&randomAbortTimer, &QTimer::timeout, this, &SolverLoop::randomTimeout);
0492     disconnect(&timer, &QTimer::timeout, this, &SolverLoop::timeout);
0493     timer.stop();
0494     randomAbortTimer.stop();
0495     qInfo() << QString("#%1: %2 random timeout was %3s (%4s)").arg(numDetects).arg(filenames[currentIndex])
0496             .arg(thisRandomTimeout, 3, 'f', 3).arg(dTimer.elapsed() / 1000.0, 3, 'f', 3);
0497     thisImage.reset();
0498     if (++numDetects < repetitions)
0499     {
0500         startDetect(numDetects % filenames.size());
0501     }
0502 }
0503 
0504 void SolverLoop::detectFinished()
0505 {
0506     disconnect(&timer, &QTimer::timeout, this, &SolverLoop::timeout);
0507     timer.stop();
0508     randomAbortTimer.stop();
0509     disconnect(&watcher, &QFutureWatcher<bool>::finished, this, &SolverLoop::detectFinished);
0510     disconnect(&watcher, &QFutureWatcher<bool>::canceled, this, &SolverLoop::detectFinished);
0511     bool result = watcher.result();
0512     if (result)
0513     {
0514         qInfo() << QString("#%1: %2 HFR %3 (%4s)").arg(numDetects).arg(filenames[currentIndex])
0515                 .arg(thisImage->getHFR(), 4, 'f', 2).arg(dTimer.elapsed() / 1000.0, 3, 'f', 3);
0516         if (++numDetects < repetitions)
0517         {
0518             startDetect(numDetects % filenames.size());
0519         }
0520     }
0521     else
0522     {
0523         QFAIL("Detect failed");
0524     }
0525 }
0526 
0527 void SolverLoop::startDetect(int index)
0528 {
0529     connect(&timer, &QTimer::timeout, this, &SolverLoop::timeout, Qt::UniqueConnection);
0530     timer.setSingleShot(true);
0531     timer.start(timeoutSecs * 1000);
0532 
0533     currentIndex = index;
0534     if (detecting)
0535     {
0536         thisImage.reset(new FITSData(images[currentIndex]));
0537         // detecting stars
0538         connect(&watcher, &QFutureWatcher<bool>::finished, this, &SolverLoop::detectFinished);
0539         connect(&watcher, &QFutureWatcher<bool>::canceled, this, &SolverLoop::detectFinished);
0540 
0541         if (randomAbortSecs > 0)
0542         {
0543             randomAbortTimer.setSingleShot(true);
0544             connect(&randomAbortTimer, &QTimer::timeout, this, &SolverLoop::randomTimeout, Qt::UniqueConnection);
0545             thisRandomTimeout = rand.generateDouble() * randomAbortSecs;
0546             randomAbortTimer.start(thisRandomTimeout * 1000);
0547 
0548         }
0549         dTimer.start();
0550         future = thisImage->findStars(ALGORITHM_SEP);
0551         watcher.setFuture(future);
0552     }
0553     else
0554     {
0555         // plate solving
0556         auto profiles = Ekos::getDefaultAlignOptionsProfiles();
0557         auto parameters = profiles.at(Options::solveOptionsProfile());
0558 
0559         // Double search radius
0560         Options::setSolverType(0); // Internal solver
0561         parameters.search_radius = parameters.search_radius * 2;
0562         solver.reset(new SolverUtils(parameters, 20), &QObject::deleteLater);
0563         connect(solver.get(), &SolverUtils::done, this, &SolverLoop::solverDone, Qt::UniqueConnection);
0564         solver->useScale(false, 0, 0);
0565         solver->usePosition(false, 0, 0);
0566         solver->runSolver(images[currentIndex]);
0567     }
0568 }
0569 
0570 // This tests how well we can detect stars and/or plate-solve a number of images
0571 // at the same time. Mostly a memory test--a failure would be a segv.
0572 // I have not provided the fits files as part of the source code, so you need to
0573 // provide them, add them the QVector<QString> variables, and make sure the directory
0574 // below points to the one holding your files.
0575 //
0576 // You may wish to reconfigure the section with SolverLoop at the bottom to have more
0577 // or less parallelism and switch between star detection and plate solves.
0578 //
0579 // You can want to run this as follows:
0580 //   export QTEST_FUNCTION_TIMEOUT=1000000000; testfitsdata testParallelSolvers -maxwarnings 1000000
0581 void TestFitsData::testParallelSolvers()
0582 {
0583 #define SKIP_PARALLEL_SOLVERS_TEST
0584 #ifdef SKIP_PARALLEL_SOLVERS_TEST
0585     QSKIP("Skipping testParallelSolvers");
0586     return;
0587 #else
0588     Options::setAutoDebayer(false);
0589 
0590     const QString dir = "/home/hy/Desktop/SharedFolder/DEBUG-solver";
0591     const QString dir1 = dir;
0592     const QVector<QString> files1 =
0593     {
0594         "guide_frame_00-20-08.fits",
0595         "guide_frame_00-20-12.fits",
0596         "guide_frame_00-20-15.fits",
0597         "guide_frame_00-20-18.fits",
0598         "guide_frame_00-20-21.fits",
0599         "guide_frame_00-20-24.fits",
0600         "guide_frame_00-20-27.fits",
0601     };
0602 
0603     const QString dir2 = dir;
0604     const QVector<QString> files2 =
0605     {
0606         "guide_frame_00-20-30.fits",
0607         "guide_frame_00-20-34.fits",
0608         "guide_frame_00-20-37.fits",
0609         "guide_frame_00-20-40.fits",
0610         "guide_frame_00-20-43.fits",
0611         "guide_frame_00-20-46.fits"
0612     };
0613 
0614     const QString dir3 = dir;
0615     const QVector<QString> files3 =
0616     {
0617         "m5_Light_LPR_120_secs_2022-03-12T04-44-56_201.fits",
0618         "m5_Light_LPR_120_secs_2022-03-12T04-47-02_202.fits",
0619         "m5_Light_LPR_120_secs_2022-03-12T04-49-04_203.fits",
0620         "m5_Light_LPR_120_secs_2022-03-12T04-51-06_204.fits",
0621         "m5_Light_LPR_120_secs_2022-03-12T04-53-07_205.fits"
0622     };
0623 
0624     const QString dir5 = dir;
0625     const QVector<QString> files5 =
0626     {
0627         "m74_Light_LPR_60_secs_2021-10-11T04-48-41_301.fits",
0628         "m74_Light_LPR_60_secs_2021-10-11T04-49-43_302.fits",
0629         "m74_Light_LPR_60_secs_2021-10-11T04-50-55_303.fits",
0630         "m74_Light_LPR_60_secs_2021-10-11T04-51-57_304.fits"
0631     };
0632 
0633     // Set the number of iterations here. THe more the better, e.g. 10000.
0634     constexpr int numIterations = 10000;
0635 
0636     // In the below declarations of SolverLoop,
0637     // for the 3rd arg: true means detect stars, false means plate solve them.
0638 
0639     // Detect stars in guide files
0640     SolverLoop loop1(files1, dir1, true, numIterations);
0641     loop1.setRandomAbort(1);
0642 
0643     // Detect stars in other guide files
0644     SolverLoop loop2(files2, dir2, true, numIterations);
0645     loop2.setRandomAbort(1);
0646 
0647     // Detect stars in subs
0648     SolverLoop loop3(files3, dir3, true, numIterations / 15);
0649     loop3.setRandomAbort(3);
0650 
0651     // This one solves the fits files
0652     SolverLoop loop4(files1, dir1, false, numIterations / 50);
0653 
0654     // This one solves the fits files
0655     SolverLoop loop5(files5, dir5, false, numIterations / 50);
0656 
0657     loop1.start();
0658     loop2.start();
0659     loop3.start();
0660     loop4.start();
0661     loop5.start();
0662 
0663     int iteration = 0;
0664     while(!loop1.done()
0665             || !loop2.done()
0666             || !loop3.done()
0667             || !loop4.done()
0668             || !loop5.done())
0669     {
0670         // The qWait is needed to allow message passing.
0671         QTest::qWait(10);
0672         if (iteration++ % 400 == 0)
0673             qInfo() << QString("%1 -- %2 -- %3 -- %4 -- %5")
0674                     .arg(loop1.status())
0675                     .arg(loop2.status())
0676                     .arg(loop3.status())
0677                     .arg(loop4.status())
0678                     .arg(loop5.status());
0679     }
0680 
0681     qInfo() << QString("%1 -- %2 -- %3 -- %4 -- %5")
0682             .arg(loop1.status())
0683             .arg(loop2.status())
0684             .arg(loop3.status())
0685             .arg(loop4.status())
0686             .arg(loop5.status());
0687 
0688     qInfo() << QString("Done!");
0689 #endif
0690 }
0691 
0692 QTEST_GUILESS_MAIN(TestFitsData)