File indexing completed on 2024-05-12 15:23:44

0001 /*
0002     SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>.
0003 
0004     Based on lin_guider
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "internalguider.h"
0010 
0011 #include "ekos_guide_debug.h"
0012 #include "gmath.h"
0013 #include "Options.h"
0014 #include "auxiliary/kspaths.h"
0015 #include "fitsviewer/fitsdata.h"
0016 #include "fitsviewer/fitsview.h"
0017 #include "guidealgorithms.h"
0018 #include "ksnotification.h"
0019 #include "ekos/auxiliary/stellarsolverprofileeditor.h"
0020 #include "fitsviewer/fitsdata.h"
0021 #include "../guideview.h"
0022 
0023 #include <KMessageBox>
0024 
0025 #include <random>
0026 #include <chrono>
0027 #include <QTimer>
0028 #include <QString>
0029 
0030 #define MAX_GUIDE_STARS           10
0031 
0032 using namespace std::chrono_literals;
0033 
0034 namespace Ekos
0035 {
0036 InternalGuider::InternalGuider()
0037 {
0038     // Create math object
0039     pmath.reset(new cgmath());
0040     connect(pmath.get(), &cgmath::newStarPosition, this, &InternalGuider::newStarPosition);
0041     connect(pmath.get(), &cgmath::guideStats, this, &InternalGuider::guideStats);
0042 
0043     // Do this so that stored calibration will be visible on the
0044     // guide options menu. Calibration will get restored again when needed.
0045     pmath->getMutableCalibration()->restore(
0046         pierSide, Options::reverseDecOnPierSideChange(), subBinX, subBinY, nullptr);
0047 
0048     state = GUIDE_IDLE;
0049     m_DitherOrigin = QVector3D(0, 0, 0);
0050 
0051     emit guideInfo("");
0052 
0053     m_darkGuideTimer = std::make_unique<QTimer>(this);
0054     m_captureTimer = std::make_unique<QTimer>(this);
0055 
0056     setDarkGuideTimerInterval();
0057 
0058     setExposureTime();
0059 
0060     connect(this, &Ekos::GuideInterface::frameCaptureRequested, this, [ = ]()
0061     {
0062         this->m_captureTimer->start();
0063     });
0064 }
0065 
0066 void InternalGuider::setExposureTime()
0067 {
0068     Seconds seconds(Options::guideExposure());
0069     setTimer(m_captureTimer, seconds);
0070 }
0071 
0072 void InternalGuider::setTimer(std::unique_ptr<QTimer> &timer, Seconds seconds)
0073 {
0074     const std::chrono::duration<double, std::milli> inMilliseconds(seconds);
0075     timer->setInterval((int)(inMilliseconds.count()));
0076 }
0077 
0078 void InternalGuider::setDarkGuideTimerInterval()
0079 {
0080     constexpr double kMinInterval = 0.5;  // 0.5s is the shortest allowed dark-guiding period.
0081     const Seconds seconds(std::max(kMinInterval, Options::gPGDarkGuidingInterval()));
0082     setTimer(m_darkGuideTimer, seconds);
0083 }
0084 
0085 void InternalGuider::resetDarkGuiding()
0086 {
0087     m_darkGuideTimer->stop();
0088     m_captureTimer->stop();
0089 }
0090 
0091 bool InternalGuider::isInferencePeriodFinished()
0092 {
0093     auto const contribution = pmath->getGPG().predictionContribution();
0094     return contribution >= 0.99;
0095 }
0096 bool InternalGuider::guide()
0097 {
0098     if (state >= GUIDE_GUIDING)
0099     {
0100         return processGuiding();
0101     }
0102 
0103     if (state == GUIDE_SUSPENDED)
0104     {
0105         return true;
0106     }
0107     m_GuideFrame->disconnect(this);
0108 
0109     pmath->start();
0110     emit guideInfo("");
0111 
0112     m_starLostCounter = 0;
0113     m_highRMSCounter = 0;
0114     m_DitherOrigin = QVector3D(0, 0, 0);
0115 
0116     m_isFirstFrame = true;
0117 
0118     if (state == GUIDE_IDLE)
0119     {
0120         if (Options::saveGuideLog())
0121             guideLog.enable();
0122         GuideLog::GuideInfo info;
0123         fillGuideInfo(&info);
0124         guideLog.startGuiding(info);
0125     }
0126     state = GUIDE_GUIDING;
0127 
0128     emit newStatus(state);
0129 
0130     emit frameCaptureRequested();
0131 
0132     startDarkGuiding();
0133 
0134     return true;
0135 }
0136 
0137 /**
0138  * @brief InternalGuider::abort Abort all internal guider operations.
0139  * This includes calibration, dithering, guiding, capturing, and reaquiring.
0140  * The state is set to IDLE or ABORTED depending on the current state since
0141  * ABORTED can lead to different behavior by external actors than IDLE
0142  * @return True if abort succeeds, false otherwise.
0143  */
0144 bool InternalGuider::abort()
0145 {
0146     // calibrationStage = CAL_IDLE; remove totally when understand trackingStarSelected
0147 
0148     logFile.close();
0149     guideLog.endGuiding();
0150     emit guideInfo("");
0151 
0152     if (state == GUIDE_CALIBRATING ||
0153             state == GUIDE_GUIDING ||
0154             state == GUIDE_DITHERING ||
0155             state == GUIDE_MANUAL_DITHERING ||
0156             state == GUIDE_REACQUIRE)
0157     {
0158         if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
0159             emit newStatus(GUIDE_DITHERING_ERROR);
0160         emit newStatus(GUIDE_ABORTED);
0161 
0162         qCDebug(KSTARS_EKOS_GUIDE) << "Aborting" << getGuideStatusString(state);
0163     }
0164     else
0165     {
0166         emit newStatus(GUIDE_IDLE);
0167         qCDebug(KSTARS_EKOS_GUIDE) << "Stopping internal guider.";
0168     }
0169 
0170     resetDarkGuiding();
0171     disconnect(m_darkGuideTimer.get(), nullptr, nullptr, nullptr);
0172 
0173     pmath->abort();
0174 
0175 
0176 
0177     m_ProgressiveDither.clear();
0178     m_starLostCounter = 0;
0179     m_highRMSCounter = 0;
0180 
0181     m_DitherOrigin = QVector3D(0, 0, 0);
0182 
0183     pmath->suspend(false);
0184     state = GUIDE_IDLE;
0185     qCDebug(KSTARS_EKOS_GUIDE) << "Guiding aborted.";
0186 
0187     return true;
0188 }
0189 
0190 bool InternalGuider::suspend()
0191 {
0192     guideLog.pauseInfo();
0193     state = GUIDE_SUSPENDED;
0194 
0195     resetDarkGuiding();
0196     emit newStatus(state);
0197 
0198     pmath->suspend(true);
0199     emit guideInfo("");
0200 
0201     return true;
0202 }
0203 
0204 void InternalGuider::startDarkGuiding()
0205 {
0206     if (Options::gPGDarkGuiding())
0207     {
0208         connect(m_darkGuideTimer.get(), &QTimer::timeout, this, &InternalGuider::darkGuide, Qt::UniqueConnection);
0209 
0210         // Start the two dark guide timers. The capture timer is started automatically by a signal.
0211         m_darkGuideTimer->start();
0212 
0213         qCDebug(KSTARS_EKOS_GUIDE) << "Starting dark guiding.";
0214     }
0215 }
0216 
0217 bool InternalGuider::resume()
0218 {
0219     qCDebug(KSTARS_EKOS_GUIDE) << "Resuming...";
0220     emit guideInfo("");
0221     guideLog.resumeInfo();
0222     state = GUIDE_GUIDING;
0223     emit newStatus(state);
0224 
0225     pmath->suspend(false);
0226 
0227     startDarkGuiding();
0228 
0229     setExposureTime();
0230 
0231     emit frameCaptureRequested();
0232 
0233     return true;
0234 }
0235 
0236 bool InternalGuider::ditherXY(double x, double y)
0237 {
0238     m_ProgressiveDither.clear();
0239     m_DitherRetries = 0;
0240     double cur_x, cur_y;
0241     pmath->getTargetPosition(&cur_x, &cur_y);
0242 
0243     // Find out how many "jumps" we need to perform in order to get to target.
0244     // The current limit is now 1/4 of the box size to make sure the star stays within detection
0245     // threashold inside the window.
0246     double oneJump = (guideBoxSize / 4.0);
0247     double targetX = cur_x, targetY = cur_y;
0248     int xSign = (x >= cur_x) ? 1 : -1;
0249     int ySign = (y >= cur_y) ? 1 : -1;
0250 
0251     do
0252     {
0253         if (fabs(targetX - x) > oneJump)
0254             targetX += oneJump * xSign;
0255         else if (fabs(targetX - x) < oneJump)
0256             targetX = x;
0257 
0258         if (fabs(targetY - y) > oneJump)
0259             targetY += oneJump * ySign;
0260         else if (fabs(targetY - y) < oneJump)
0261             targetY = y;
0262 
0263         m_ProgressiveDither.enqueue(GuiderUtils::Vector(targetX, targetY, -1));
0264 
0265     }
0266     while (targetX != x || targetY != y);
0267 
0268     m_DitherTargetPosition = m_ProgressiveDither.dequeue();
0269     pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
0270     guideLog.ditherInfo(x, y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
0271 
0272     state = GUIDE_MANUAL_DITHERING;
0273     emit newStatus(state);
0274 
0275     processGuiding();
0276 
0277     return true;
0278 }
0279 
0280 bool InternalGuider::dither(double pixels)
0281 {
0282     if (Options::ditherWithOnePulse() )
0283         return onePulseDither(pixels);
0284 
0285     double ret_x, ret_y;
0286     pmath->getTargetPosition(&ret_x, &ret_y);
0287 
0288     // Just calling getStarScreenPosition() will get the position at the last time the guide star
0289     // was found, which is likely before the most recent guide pulse.
0290     // Instead we call findLocalStarPosition() which does the analysis from the image.
0291     // Unfortunately, processGuiding() will repeat that computation.
0292     // We currently don't cache it.
0293     GuiderUtils::Vector star_position = pmath->findLocalStarPosition(m_ImageData, m_GuideFrame, false);
0294     if (pmath->isStarLost() || (star_position.x == -1) || (star_position.y == -1))
0295     {
0296         // If the star position is lost, just lose this iteration.
0297         // If it happens too many time, abort.
0298         if (++m_starLostCounter > MAX_LOST_STAR_THRESHOLD)
0299         {
0300             qCDebug(KSTARS_EKOS_GUIDE) << "Too many consecutive lost stars." << m_starLostCounter << "Aborting dither.";
0301             return abortDither();
0302         }
0303         qCDebug(KSTARS_EKOS_GUIDE) << "Dither lost star. Trying again.";
0304         emit frameCaptureRequested();
0305         return true;
0306     }
0307     else
0308         m_starLostCounter = 0;
0309 
0310     if (state != GUIDE_DITHERING)
0311     {
0312         m_DitherRetries = 0;
0313 
0314         auto seed = std::chrono::system_clock::now().time_since_epoch().count();
0315         std::default_random_engine generator(seed);
0316         std::uniform_real_distribution<double> angleMagnitude(0, 360);
0317 
0318         double angle  = angleMagnitude(generator) * dms::DegToRad;
0319         double diff_x = pixels * cos(angle);
0320         double diff_y = pixels * sin(angle);
0321 
0322         if (pmath->getCalibration().declinationSwapEnabled())
0323             diff_y *= -1;
0324 
0325         if (m_DitherOrigin.x() == 0 && m_DitherOrigin.y() == 0)
0326         {
0327             m_DitherOrigin = QVector3D(ret_x, ret_y, 0);
0328         }
0329         double totalXOffset = ret_x - m_DitherOrigin.x();
0330         double totalYOffset = ret_y - m_DitherOrigin.y();
0331 
0332         // if we've dithered too far, and diff_x or diff_y is pushing us even further away, then change its direction.
0333         // Note: it is possible that we've dithered too far, but diff_x/y is pointing in the right direction.
0334         // Don't change it in that 2nd case.
0335         if (((diff_x + totalXOffset > MAX_DITHER_TRAVEL) && (diff_x > 0)) ||
0336                 ((diff_x + totalXOffset < -MAX_DITHER_TRAVEL) && (diff_x < 0)))
0337         {
0338             qCDebug(KSTARS_EKOS_GUIDE)
0339                     << QString("Dithering target off by too much in X (abs(%1 + %2) > %3), adjust diff_x from %4 to %5")
0340                     .arg(diff_x).arg(totalXOffset).arg(MAX_DITHER_TRAVEL).arg(diff_x).arg(diff_x * -1.5);
0341             diff_x *= -1.5;
0342         }
0343         if (((diff_y + totalYOffset > MAX_DITHER_TRAVEL) && (diff_y > 0)) ||
0344                 ((diff_y + totalYOffset < -MAX_DITHER_TRAVEL) && (diff_y < 0)))
0345         {
0346             qCDebug(KSTARS_EKOS_GUIDE)
0347                     << QString("Dithering target off by too much in Y (abs(%1 + %2) > %3), adjust diff_y from %4 to %5")
0348                     .arg(diff_y).arg(totalYOffset).arg(MAX_DITHER_TRAVEL).arg(diff_y).arg(diff_y * -1.5);
0349             diff_y *= -1.5;
0350         }
0351 
0352         m_DitherTargetPosition = GuiderUtils::Vector(ret_x, ret_y, 0) + GuiderUtils::Vector(diff_x, diff_y, 0);
0353 
0354         qCDebug(KSTARS_EKOS_GUIDE)
0355                 << QString("Dithering by %1 pixels. Target:  %2,%3 Current: %4,%5 Move: %6,%7 Wander: %8,%9")
0356                 .arg(pixels, 3, 'f', 1)
0357                 .arg(m_DitherTargetPosition.x, 5, 'f', 1).arg(m_DitherTargetPosition.y, 5, 'f', 1)
0358                 .arg(ret_x, 5, 'f', 1).arg(ret_y, 5, 'f', 1)
0359                 .arg(diff_x, 4, 'f', 1).arg(diff_y, 4, 'f', 1)
0360                 .arg(totalXOffset + diff_x, 5, 'f', 1).arg(totalYOffset + diff_y, 5, 'f', 1);
0361         guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
0362 
0363         pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
0364 
0365         if (Options::gPGEnabled())
0366             // This is the offset in image coordinates, but needs to be converted to RA.
0367             pmath->getGPG().startDithering(diff_x, diff_y, pmath->getCalibration());
0368 
0369         state = GUIDE_DITHERING;
0370         emit newStatus(state);
0371 
0372         processGuiding();
0373 
0374         return true;
0375     }
0376 
0377     // These will be the RA & DEC drifts of the current star position from the reticle position in pixels.
0378     double driftRA, driftDEC;
0379     pmath->getCalibration().computeDrift(
0380         star_position,
0381         GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0),
0382         &driftRA, &driftDEC);
0383 
0384     double pixelOffsetX = m_DitherTargetPosition.x - star_position.x;
0385     double pixelOffsetY = m_DitherTargetPosition.y - star_position.y;
0386 
0387     qCDebug(KSTARS_EKOS_GUIDE)
0388             << QString("Dithering in progress.   Current: %1,%2 Target:  %3,%4 Diff: %5,%6 Wander: %8,%9")
0389             .arg(star_position.x, 5, 'f', 1).arg(star_position.y, 5, 'f', 1)
0390             .arg(m_DitherTargetPosition.x, 5, 'f', 1).arg(m_DitherTargetPosition.y, 5, 'f', 1)
0391             .arg(pixelOffsetX, 4, 'f', 1).arg(pixelOffsetY, 4, 'f', 1)
0392             .arg(star_position.x - m_DitherOrigin.x(), 5, 'f', 1)
0393             .arg(star_position.y - m_DitherOrigin.y(), 5, 'f', 1);
0394 
0395     if (Options::ditherWithOnePulse() || (fabs(driftRA) < 1 && fabs(driftDEC) < 1))
0396     {
0397         pmath->setTargetPosition(star_position.x, star_position.y);
0398 
0399         // In one-pulse dithering we want the target to be whereever we end up
0400         // after the pulse. So, the first guide frame should not send any pulses
0401         // and should reset the reticle to the position it finds.
0402         if (Options::ditherWithOnePulse())
0403             m_isFirstFrame = true;
0404 
0405         qCDebug(KSTARS_EKOS_GUIDE) << "Dither complete.";
0406 
0407         if (Options::ditherSettle() > 0)
0408         {
0409             state = GUIDE_DITHERING_SETTLE;
0410             guideLog.settleStartedInfo();
0411             emit newStatus(state);
0412         }
0413 
0414         if (Options::gPGEnabled())
0415             pmath->getGPG().ditheringSettled(true);
0416 
0417         QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
0418     }
0419     else
0420     {
0421         if (++m_DitherRetries > Options::ditherMaxIterations())
0422             return abortDither();
0423 
0424         processGuiding();
0425     }
0426 
0427     return true;
0428 }
0429 bool InternalGuider::onePulseDither(double pixels)
0430 {
0431     qCDebug(KSTARS_EKOS_GUIDE) << "OnePulseDither(" << "pixels" << ")";
0432 
0433     // Cancel any current guide exposures.
0434     emit abortExposure();
0435 
0436     double ret_x, ret_y;
0437     pmath->getTargetPosition(&ret_x, &ret_y);
0438 
0439     auto seed = std::chrono::system_clock::now().time_since_epoch().count();
0440     std::default_random_engine generator(seed);
0441     std::uniform_real_distribution<double> angleMagnitude(0, 360);
0442 
0443     double angle  = angleMagnitude(generator) * dms::DegToRad;
0444     double diff_x = pixels * cos(angle);
0445     double diff_y = pixels * sin(angle);
0446 
0447     if (pmath->getCalibration().declinationSwapEnabled())
0448         diff_y *= -1;
0449 
0450     if (m_DitherOrigin.x() == 0 && m_DitherOrigin.y() == 0)
0451     {
0452         m_DitherOrigin = QVector3D(ret_x, ret_y, 0);
0453     }
0454     double totalXOffset = ret_x - m_DitherOrigin.x();
0455     double totalYOffset = ret_y - m_DitherOrigin.y();
0456 
0457     // If we've dithered too far, and diff_x or diff_y is pushing us even further away, then change its direction.
0458     // Note: it is possible that we've dithered too far, but diff_x/y is pointing in the right direction.
0459     // Don't change it in that 2nd case.
0460     if (((diff_x + totalXOffset > MAX_DITHER_TRAVEL) && (diff_x > 0)) ||
0461             ((diff_x + totalXOffset < -MAX_DITHER_TRAVEL) && (diff_x < 0)))
0462     {
0463         qCDebug(KSTARS_EKOS_GUIDE)
0464                 << QString("OPD: Dithering target off by too much in X (abs(%1 + %2) > %3), adjust diff_x from %4 to %5")
0465                 .arg(diff_x).arg(totalXOffset).arg(MAX_DITHER_TRAVEL).arg(diff_x).arg(diff_x * -1.5);
0466         diff_x *= -1.5;
0467     }
0468     if (((diff_y + totalYOffset > MAX_DITHER_TRAVEL) && (diff_y > 0)) ||
0469             ((diff_y + totalYOffset < -MAX_DITHER_TRAVEL) && (diff_y < 0)))
0470     {
0471         qCDebug(KSTARS_EKOS_GUIDE)
0472                 << QString("OPD: Dithering target off by too much in Y (abs(%1 + %2) > %3), adjust diff_y from %4 to %5")
0473                 .arg(diff_y).arg(totalYOffset).arg(MAX_DITHER_TRAVEL).arg(diff_y).arg(diff_y * -1.5);
0474         diff_y *= -1.5;
0475     }
0476 
0477     m_DitherTargetPosition = GuiderUtils::Vector(ret_x, ret_y, 0) + GuiderUtils::Vector(diff_x, diff_y, 0);
0478 
0479     qCDebug(KSTARS_EKOS_GUIDE)
0480             << QString("OPD: Dithering by %1 pixels. Target:  %2,%3 Current: %4,%5 Move: %6,%7 Wander: %8,%9")
0481             .arg(pixels, 3, 'f', 1)
0482             .arg(m_DitherTargetPosition.x, 5, 'f', 1).arg(m_DitherTargetPosition.y, 5, 'f', 1)
0483             .arg(ret_x, 5, 'f', 1).arg(ret_y, 5, 'f', 1)
0484             .arg(diff_x, 4, 'f', 1).arg(diff_y, 4, 'f', 1)
0485             .arg(totalXOffset + diff_x, 5, 'f', 1).arg(totalYOffset + diff_y, 5, 'f', 1);
0486     guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
0487 
0488     pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
0489 
0490     if (Options::gPGEnabled())
0491         // This is the offset in image coordinates, but needs to be converted to RA.
0492         pmath->getGPG().startDithering(diff_x, diff_y, pmath->getCalibration());
0493 
0494     state = GUIDE_DITHERING;
0495     emit newStatus(state);
0496 
0497     const GuiderUtils::Vector xyMove(diff_x, diff_y, 0);
0498     const GuiderUtils::Vector raDecMove = pmath->getCalibration().rotateToRaDec(xyMove);
0499     double raPulse = fabs(raDecMove.x * pmath->getCalibration().raPulseMillisecondsPerArcsecond());
0500     double decPulse = fabs(raDecMove.y * pmath->getCalibration().decPulseMillisecondsPerArcsecond());
0501     auto raDir = raDecMove.x > 0 ? RA_INC_DIR : RA_DEC_DIR;
0502     auto decDir = raDecMove.y > 0 ? DEC_DEC_DIR : DEC_INC_DIR;
0503 
0504     m_isFirstFrame = true;
0505 
0506     // Send pulse if we have one active direction at least.
0507     QString raDirString = raDir == RA_DEC_DIR ? "RA_DEC" : "RA_INC";
0508     QString decDirString = decDir == DEC_INC_DIR ? "DEC_INC" : "DEC_DEC";
0509 
0510     qCDebug(KSTARS_EKOS_GUIDE) << "OnePulseDither RA: " << raPulse << "ms" << raDirString << " DEC: " << decPulse << "ms " <<
0511                                decDirString;
0512     emit newMultiPulse(raDir, raPulse, decDir, decPulse, StartCaptureAfterPulses);
0513 
0514     double totalMSecs = 1000.0 * Options::ditherSettle() + std::max(raPulse, decPulse);
0515 
0516     state = GUIDE_DITHERING_SETTLE;
0517     guideLog.settleStartedInfo();
0518     emit newStatus(state);
0519 
0520     if (Options::gPGEnabled())
0521         pmath->getGPG().ditheringSettled(true);
0522 
0523     QTimer::singleShot(totalMSecs, this, SLOT(setDitherSettled()));
0524     return true;
0525 }
0526 
0527 bool InternalGuider::abortDither()
0528 {
0529     if (Options::ditherFailAbortsAutoGuide())
0530     {
0531         emit newStatus(Ekos::GUIDE_DITHERING_ERROR);
0532         abort();
0533         return false;
0534     }
0535     else
0536     {
0537         emit newLog(i18n("Warning: Dithering failed. Autoguiding shall continue as set in the options in case "
0538                          "of dither failure."));
0539 
0540         if (Options::ditherSettle() > 0)
0541         {
0542             state = GUIDE_DITHERING_SETTLE;
0543             guideLog.settleStartedInfo();
0544             emit newStatus(state);
0545         }
0546 
0547         if (Options::gPGEnabled())
0548             pmath->getGPG().ditheringSettled(false);
0549 
0550         QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
0551         return true;
0552     }
0553 }
0554 
0555 bool InternalGuider::processManualDithering()
0556 {
0557     double cur_x, cur_y;
0558     pmath->getTargetPosition(&cur_x, &cur_y);
0559     pmath->getStarScreenPosition(&cur_x, &cur_y);
0560 
0561     // These will be the RA & DEC drifts of the current star position from the reticle position in pixels.
0562     double driftRA, driftDEC;
0563     pmath->getCalibration().computeDrift(
0564         GuiderUtils::Vector(cur_x, cur_y, 0),
0565         GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0),
0566         &driftRA, &driftDEC);
0567 
0568     qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dithering in progress. Diff star X:" << driftRA << "Y:" << driftDEC;
0569 
0570     if (fabs(driftRA) < guideBoxSize / 5.0 && fabs(driftDEC) < guideBoxSize / 5.0)
0571     {
0572         if (m_ProgressiveDither.empty() == false)
0573         {
0574             m_DitherTargetPosition = m_ProgressiveDither.dequeue();
0575             pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
0576             qCDebug(KSTARS_EKOS_GUIDE) << "Next Dither Jump X:" << m_DitherTargetPosition.x << "Jump Y:" << m_DitherTargetPosition.y;
0577             m_DitherRetries = 0;
0578 
0579             processGuiding();
0580 
0581             return true;
0582         }
0583 
0584         if (fabs(driftRA) < 1 && fabs(driftDEC) < 1)
0585         {
0586             pmath->setTargetPosition(cur_x, cur_y);
0587             qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dither complete.";
0588 
0589             if (Options::ditherSettle() > 0)
0590             {
0591                 state = GUIDE_DITHERING_SETTLE;
0592                 guideLog.settleStartedInfo();
0593                 emit newStatus(state);
0594             }
0595 
0596             QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
0597         }
0598         else
0599         {
0600             processGuiding();
0601         }
0602     }
0603     else
0604     {
0605         if (++m_DitherRetries > Options::ditherMaxIterations())
0606         {
0607             emit newLog(i18n("Warning: Manual Dithering failed."));
0608 
0609             if (Options::ditherSettle() > 0)
0610             {
0611                 state = GUIDE_DITHERING_SETTLE;
0612                 guideLog.settleStartedInfo();
0613                 emit newStatus(state);
0614             }
0615 
0616             QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
0617             return true;
0618         }
0619 
0620         processGuiding();
0621     }
0622 
0623     return true;
0624 }
0625 
0626 void InternalGuider::setDitherSettled()
0627 {
0628     guideLog.settleCompletedInfo();
0629     emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
0630 
0631     // Back to guiding
0632     state = GUIDE_GUIDING;
0633 }
0634 
0635 bool InternalGuider::calibrate()
0636 {
0637     bool ccdInfo = true, scopeInfo = true;
0638     QString errMsg;
0639 
0640     if (subW == 0 || subH == 0)
0641     {
0642         errMsg  = "CCD";
0643         ccdInfo = false;
0644     }
0645 
0646     if (mountAperture == 0.0 || mountFocalLength == 0.0)
0647     {
0648         scopeInfo = false;
0649         if (ccdInfo == false)
0650             errMsg += " & Telescope";
0651         else
0652             errMsg += "Telescope";
0653     }
0654 
0655     if (ccdInfo == false || scopeInfo == false)
0656     {
0657         KSNotification::error(i18n("%1 info are missing. Please set the values in INDI Control Panel.", errMsg),
0658                               i18n("Missing Information"));
0659         return false;
0660     }
0661 
0662     if (state != GUIDE_CALIBRATING)
0663     {
0664         pmath->getTargetPosition(&calibrationStartX, &calibrationStartY);
0665         calibrationProcess.reset(
0666             new CalibrationProcess(calibrationStartX, calibrationStartY,
0667                                    !Options::twoAxisEnabled()));
0668         state = GUIDE_CALIBRATING;
0669         emit newStatus(GUIDE_CALIBRATING);
0670     }
0671 
0672     if (calibrationProcess->inProgress())
0673     {
0674         iterateCalibration();
0675         return true;
0676     }
0677 
0678     if (restoreCalibration())
0679     {
0680         calibrationProcess.reset();
0681         emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS);
0682         KSNotification::event(QLatin1String("CalibrationRestored"),
0683                               i18n("Guiding calibration restored"), KSNotification::Guide);
0684         reset();
0685         return true;
0686     }
0687 
0688     // Initialize the calibration parameters.
0689     // CCD pixel values comes in in microns and we want mm.
0690     pmath->getMutableCalibration()->setParameters(
0691         ccdPixelSizeX / 1000.0, ccdPixelSizeY / 1000.0, mountFocalLength,
0692         subBinX, subBinY, pierSide, mountRA, mountDEC);
0693 
0694     calibrationProcess->useCalibration(pmath->getMutableCalibration());
0695 
0696     m_GuideFrame->disconnect(this);
0697 
0698     // Must reset dec swap before we run any calibration procedure!
0699     emit DESwapChanged(false);
0700     pmath->setLostStar(false);
0701 
0702     if (Options::saveGuideLog())
0703         guideLog.enable();
0704     GuideLog::GuideInfo info;
0705     fillGuideInfo(&info);
0706     guideLog.startCalibration(info);
0707 
0708     calibrationProcess->startup();
0709     calibrationProcess->setGuideLog(&guideLog);
0710     iterateCalibration();
0711 
0712     return true;
0713 }
0714 
0715 void InternalGuider::iterateCalibration()
0716 {
0717     if (calibrationProcess->inProgress())
0718     {
0719         auto const timeStep = calculateGPGTimeStep();
0720         pmath->performProcessing(GUIDE_CALIBRATING, m_ImageData, m_GuideFrame, timeStep);
0721 
0722         QString info = "";
0723         if (pmath->usingSEPMultiStar())
0724         {
0725             auto gs = pmath->getGuideStars();
0726             info = QString("%1 stars, %2/%3 refs")
0727                    .arg(gs.getNumStarsDetected())
0728                    .arg(gs.getNumReferencesFound())
0729                    .arg(gs.getNumReferences());
0730         }
0731         emit guideInfo(info);
0732 
0733         if (pmath->isStarLost())
0734         {
0735             emit newLog(i18n("Lost track of the guide star. Try increasing the square size or reducing pulse duration."));
0736             emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
0737             emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY,
0738                                    i18n("Guide Star lost."));
0739             reset();
0740             return;
0741         }
0742     }
0743     double starX, starY;
0744     pmath->getStarScreenPosition(&starX, &starY);
0745     calibrationProcess->iterate(starX, starY);
0746 
0747     auto status = calibrationProcess->getStatus();
0748     if (status != GUIDE_CALIBRATING)
0749         emit newStatus(status);
0750 
0751     QString logStatus = calibrationProcess->getLogStatus();
0752     if (logStatus.length())
0753         emit newLog(logStatus);
0754 
0755     QString updateMessage;
0756     double x, y;
0757     GuideInterface::CalibrationUpdateType type;
0758     calibrationProcess->getCalibrationUpdate(&type, &updateMessage, &x, &y);
0759     if (updateMessage.length())
0760         emit calibrationUpdate(type, updateMessage, x, y);
0761 
0762     GuideDirection pulseDirection;
0763     int pulseMsecs;
0764     calibrationProcess->getPulse(&pulseDirection, &pulseMsecs);
0765     if (pulseDirection != NO_DIR)
0766         emit newSinglePulse(pulseDirection, pulseMsecs, StartCaptureAfterPulses);
0767 
0768     if (status == GUIDE_CALIBRATION_ERROR)
0769     {
0770         KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed"),
0771                               KSNotification::Guide, KSNotification::Alert);
0772         reset();
0773     }
0774     else if (status == GUIDE_CALIBRATION_SUCCESS)
0775     {
0776         KSNotification::event(QLatin1String("CalibrationSuccessful"),
0777                               i18n("Guiding calibration completed successfully"), KSNotification::Guide);
0778         emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled());
0779         pmath->setTargetPosition(calibrationStartX, calibrationStartY);
0780         reset();
0781     }
0782 }
0783 
0784 void InternalGuider::setGuideView(const QSharedPointer<GuideView> &guideView)
0785 {
0786     m_GuideFrame = guideView;
0787 }
0788 
0789 void InternalGuider::setImageData(const QSharedPointer<FITSData> &data)
0790 {
0791     m_ImageData = data;
0792     if (Options::saveGuideImages())
0793     {
0794         QDateTime now(QDateTime::currentDateTime());
0795         QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("guide/" +
0796                        now.toString("yyyy-MM-dd"));
0797         QDir dir;
0798         dir.mkpath(path);
0799         // IS8601 contains colons but they are illegal under Windows OS, so replacing them with '-'
0800         // The timestamp is no longer ISO8601 but it should solve interoperality issues between different OS hosts
0801         QString name     = "guide_frame_" + now.toString("HH-mm-ss") + ".fits";
0802         QString filename = path + QStringLiteral("/") + name;
0803         m_ImageData->saveImage(filename);
0804     }
0805 }
0806 
0807 void InternalGuider::reset()
0808 {
0809     qCDebug(KSTARS_EKOS_GUIDE) << "Resetting internal guider...";
0810     state = GUIDE_IDLE;
0811 
0812     resetDarkGuiding();
0813 
0814     connect(m_GuideFrame.get(), &FITSView::trackingStarSelected, this, &InternalGuider::trackingStarSelected,
0815             Qt::UniqueConnection);
0816     calibrationProcess.reset();
0817 }
0818 
0819 bool InternalGuider::clearCalibration()
0820 {
0821     Options::setSerializedCalibration("");
0822     pmath->getMutableCalibration()->reset();
0823     return true;
0824 }
0825 
0826 bool InternalGuider::restoreCalibration()
0827 {
0828     bool success = Options::reuseGuideCalibration() &&
0829                    pmath->getMutableCalibration()->restore(
0830                        pierSide, Options::reverseDecOnPierSideChange(),
0831                        subBinX, subBinY, &mountDEC);
0832     if (success)
0833         emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled());
0834     return success;
0835 }
0836 
0837 void InternalGuider::setStarPosition(QVector3D &starCenter)
0838 {
0839     pmath->setTargetPosition(starCenter.x(), starCenter.y());
0840 }
0841 
0842 void InternalGuider::trackingStarSelected(int x, int y)
0843 {
0844     Q_UNUSED(x);
0845     Q_UNUSED(y);
0846     /*
0847 
0848       Not sure what's going on here--manual star selection for calibration?
0849       Don't really see how the logic works.
0850 
0851     if (calibrationStage == CAL_IDLE)
0852         return;
0853 
0854     pmath->setTargetPosition(x, y);
0855 
0856     calibrationStage = CAL_START;
0857     */
0858 }
0859 
0860 void InternalGuider::setDECSwap(bool enable)
0861 {
0862     pmath->getMutableCalibration()->setDeclinationSwapEnabled(enable);
0863 }
0864 
0865 void InternalGuider::setSquareAlgorithm(int index)
0866 {
0867     if (index == SEP_MULTISTAR && !pmath->usingSEPMultiStar())
0868         m_isFirstFrame = true;
0869     pmath->setAlgorithmIndex(index);
0870 }
0871 
0872 bool InternalGuider::getReticleParameters(double * x, double * y)
0873 {
0874     return pmath->getTargetPosition(x, y);
0875 }
0876 
0877 bool InternalGuider::setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture,
0878                                      double mountFocalLength)
0879 {
0880     this->ccdPixelSizeX    = ccdPixelSizeX;
0881     this->ccdPixelSizeY    = ccdPixelSizeY;
0882     this->mountAperture    = mountAperture;
0883     this->mountFocalLength = mountFocalLength;
0884     return pmath->setGuiderParameters(ccdPixelSizeX, ccdPixelSizeY, mountAperture, mountFocalLength);
0885 }
0886 
0887 bool InternalGuider::setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY)
0888 {
0889     if (w <= 0 || h <= 0)
0890         return false;
0891 
0892     subX = x;
0893     subY = y;
0894     subW = w;
0895     subH = h;
0896 
0897     subBinX = binX;
0898     subBinY = binY;
0899 
0900     pmath->setVideoParameters(w, h, subBinX, subBinY);
0901 
0902     return true;
0903 }
0904 
0905 void InternalGuider::emitAxisPulse(const cproc_out_params * out)
0906 {
0907     double raPulse = out->pulse_length[GUIDE_RA];
0908     double dePulse = out->pulse_length[GUIDE_DEC];
0909 
0910     //If the pulse was not sent to the mount, it should have 0 value
0911     if(out->pulse_dir[GUIDE_RA] == NO_DIR)
0912         raPulse = 0;
0913     //If the pulse was not sent to the mount, it should have 0 value
0914     if(out->pulse_dir[GUIDE_DEC] == NO_DIR)
0915         dePulse = 0;
0916     //If the pulse was in the Negative direction, it should have a negative sign.
0917     if(out->pulse_dir[GUIDE_RA] == RA_INC_DIR)
0918         raPulse = -raPulse;
0919     //If the pulse was in the Negative direction, it should have a negative sign.
0920     if(out->pulse_dir[GUIDE_DEC] == DEC_INC_DIR)
0921         dePulse = -dePulse;
0922 
0923     emit newAxisPulse(raPulse, dePulse);
0924 }
0925 
0926 bool InternalGuider::processGuiding()
0927 {
0928     const cproc_out_params *out;
0929 
0930     // On first frame, center the box (reticle) around the star so we do not start with an offset the results in
0931     // unnecessary guiding pulses.
0932     bool process = true;
0933 
0934     if (m_isFirstFrame)
0935     {
0936         m_isFirstFrame = false;
0937         if (state == GUIDE_GUIDING)
0938         {
0939             GuiderUtils::Vector star_pos = pmath->findLocalStarPosition(m_ImageData, m_GuideFrame, true);
0940             if (star_pos.x != -1 && star_pos.y != -1)
0941                 pmath->setTargetPosition(star_pos.x, star_pos.y);
0942             else
0943             {
0944                 // We were not able to get started.
0945                 process = false;
0946                 m_isFirstFrame = true;
0947             }
0948         }
0949     }
0950 
0951     if (process)
0952     {
0953         auto const timeStep = calculateGPGTimeStep();
0954         pmath->performProcessing(state, m_ImageData, m_GuideFrame, timeStep, &guideLog);
0955         if (pmath->usingSEPMultiStar())
0956         {
0957             QString info = "";
0958             auto gs = pmath->getGuideStars();
0959             info = QString("%1 stars, %2/%3 refs")
0960                    .arg(gs.getNumStarsDetected())
0961                    .arg(gs.getNumReferencesFound())
0962                    .arg(gs.getNumReferences());
0963 
0964             emit guideInfo(info);
0965         }
0966 
0967         // Restart the dark-guiding timer, so we get the full interval on its 1st timeout.
0968         if (this->m_darkGuideTimer->isActive())
0969             this->m_darkGuideTimer->start();
0970     }
0971 
0972     if (state == GUIDE_SUSPENDED)
0973     {
0974         if (Options::gPGEnabled())
0975             emit frameCaptureRequested();
0976         return true;
0977     }
0978     else
0979     {
0980         if (pmath->isStarLost())
0981             m_starLostCounter++;
0982         else
0983             m_starLostCounter = 0;
0984     }
0985 
0986     // do pulse
0987     out = pmath->getOutputParameters();
0988 
0989     if (isPoorGuiding(out))
0990         return true;
0991 
0992     bool sendPulses = !pmath->isStarLost();
0993 
0994 
0995     // Send pulse if we have one active direction at least.
0996     if (sendPulses && (out->pulse_dir[GUIDE_RA] != NO_DIR || out->pulse_dir[GUIDE_DEC] != NO_DIR))
0997     {
0998         emit newMultiPulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA],
0999                            out->pulse_dir[GUIDE_DEC], out->pulse_length[GUIDE_DEC], StartCaptureAfterPulses);
1000     }
1001     else
1002         emit frameCaptureRequested();
1003 
1004     if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
1005         return true;
1006 
1007     // Hy 9/13/21: Check above just looks for GUIDE_DITHERING or GUIDE_MANUAL_DITHERING
1008     // but not the other dithering possibilities (error, success, settle).
1009     // Not sure if they should be included above, so conservatively not changing the
1010     // code, but don't think they should broadcast the newAxisDelta which might
1011     // interrup a capture.
1012     if (state < GUIDE_DITHERING)
1013         emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]);
1014 
1015     emitAxisPulse(out);
1016     emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]);
1017     if (SEPMultiStarEnabled())
1018         emit newSNR(pmath->getGuideStarSNR());
1019 
1020     return true;
1021 }
1022 
1023 
1024 // Here we calculate the time until the next time we will be emitting guiding corrections.
1025 std::pair<Seconds, Seconds> InternalGuider::calculateGPGTimeStep()
1026 {
1027     Seconds timeStep;
1028 
1029     const Seconds guideDelay{(Options::guideDelay())};
1030 
1031     auto const captureInterval = Seconds(m_captureTimer->intervalAsDuration()) + guideDelay;
1032     auto const darkGuideInterval = Seconds(m_darkGuideTimer->intervalAsDuration());
1033 
1034     if (!Options::gPGDarkGuiding() || !isInferencePeriodFinished())
1035     {
1036         return std::pair<Seconds, Seconds>(captureInterval, captureInterval);
1037     }
1038     auto const captureTimeRemaining = Seconds(m_captureTimer->remainingTimeAsDuration()) + guideDelay;
1039     auto const darkGuideTimeRemaining = Seconds(m_darkGuideTimer->remainingTimeAsDuration());
1040     // Are both firing at the same time (or at least, both due)?
1041     if (captureTimeRemaining <= Seconds::zero()
1042             && darkGuideTimeRemaining <= Seconds::zero())
1043     {
1044         timeStep = std::min(captureInterval, darkGuideInterval);
1045     }
1046     else if (captureTimeRemaining <= Seconds::zero())
1047     {
1048         timeStep = std::min(captureInterval, darkGuideTimeRemaining);
1049     }
1050     else if (darkGuideTimeRemaining <= Seconds::zero())
1051     {
1052         timeStep = std::min(captureTimeRemaining, darkGuideInterval);
1053     }
1054     else
1055     {
1056         timeStep = std::min(captureTimeRemaining, darkGuideTimeRemaining);
1057     }
1058     return std::pair<Seconds, Seconds>(timeStep, captureInterval);
1059 }
1060 
1061 
1062 
1063 void InternalGuider::darkGuide()
1064 {
1065     // Only dark guide when guiding--e.g. don't dark guide if dithering.
1066     if (state != GUIDE_GUIDING)
1067         return;
1068 
1069     if(Options::gPGDarkGuiding() && isInferencePeriodFinished())
1070     {
1071         const cproc_out_params *out;
1072         auto const timeStep = calculateGPGTimeStep();
1073         pmath->performDarkGuiding(state, timeStep);
1074 
1075         out = pmath->getOutputParameters();
1076         emit newSinglePulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA], DontCaptureAfterPulses);
1077 
1078         emitAxisPulse(out);
1079     }
1080 }
1081 
1082 bool InternalGuider::isPoorGuiding(const cproc_out_params * out)
1083 {
1084     double delta_rms = std::hypot(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]);
1085     if (delta_rms > Options::guideMaxDeltaRMS())
1086         m_highRMSCounter++;
1087     else
1088         m_highRMSCounter = 0;
1089 
1090     uint8_t abortStarLostThreshold = (state == GUIDE_DITHERING
1091                                       || state == GUIDE_MANUAL_DITHERING) ? MAX_LOST_STAR_THRESHOLD * 3 : MAX_LOST_STAR_THRESHOLD;
1092     uint8_t abortRMSThreshold = (state == GUIDE_DITHERING
1093                                  || state == GUIDE_MANUAL_DITHERING) ? MAX_RMS_THRESHOLD * 3 : MAX_RMS_THRESHOLD;
1094     if (m_starLostCounter > abortStarLostThreshold || m_highRMSCounter > abortRMSThreshold)
1095     {
1096         qCDebug(KSTARS_EKOS_GUIDE) << "m_starLostCounter" << m_starLostCounter
1097                                    << "m_highRMSCounter" << m_highRMSCounter
1098                                    << "delta_rms" << delta_rms;
1099 
1100         if (m_starLostCounter > abortStarLostThreshold)
1101             emit newLog(i18n("Lost track of the guide star. Searching for guide stars..."));
1102         else
1103             emit newLog(i18n("Delta RMS threshold value exceeded. Searching for guide stars..."));
1104 
1105         reacquireTimer.start();
1106         rememberState = state;
1107         state = GUIDE_REACQUIRE;
1108         emit newStatus(state);
1109         return true;
1110     }
1111     return false;
1112 }
1113 bool InternalGuider::selectAutoStarSEPMultistar()
1114 {
1115     m_GuideFrame->updateFrame();
1116     m_DitherOrigin = QVector3D(0, 0, 0);
1117     QVector3D newStarCenter = pmath->selectGuideStar(m_ImageData);
1118     if (newStarCenter.x() >= 0)
1119     {
1120         emit newStarPosition(newStarCenter, true);
1121         return true;
1122     }
1123     return false;
1124 }
1125 
1126 bool InternalGuider::SEPMultiStarEnabled()
1127 {
1128     return Options::guideAlgorithm() == SEP_MULTISTAR;
1129 }
1130 
1131 bool InternalGuider::selectAutoStar()
1132 {
1133     m_DitherOrigin = QVector3D(0, 0, 0);
1134     if (Options::guideAlgorithm() == SEP_MULTISTAR)
1135         return selectAutoStarSEPMultistar();
1136 
1137     bool useNativeDetection = false;
1138 
1139     QList<Edge *> starCenters;
1140 
1141     if (Options::guideAlgorithm() != SEP_THRESHOLD)
1142         starCenters = GuideAlgorithms::detectStars(m_ImageData, m_GuideFrame->getTrackingBox());
1143 
1144     if (starCenters.empty())
1145     {
1146         QVariantMap settings;
1147         settings["maxStarsCount"] = 50;
1148         settings["optionsProfileIndex"] = Options::guideOptionsProfile();
1149         settings["optionsProfileGroup"] = static_cast<int>(Ekos::GuideProfiles);
1150         m_ImageData->setSourceExtractorSettings(settings);
1151 
1152         if (Options::guideAlgorithm() == SEP_THRESHOLD)
1153             m_ImageData->findStars(ALGORITHM_SEP).waitForFinished();
1154         else
1155             m_ImageData->findStars().waitForFinished();
1156 
1157         starCenters = m_ImageData->getStarCenters();
1158         if (starCenters.empty())
1159             return false;
1160 
1161         useNativeDetection = true;
1162         // For SEP, prefer flux total
1163         if (Options::guideAlgorithm() == SEP_THRESHOLD)
1164             std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b)
1165         {
1166             return a->val > b->val;
1167         });
1168         else
1169             std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b)
1170         {
1171             return a->width > b->width;
1172         });
1173 
1174         m_GuideFrame->setStarsEnabled(true);
1175         m_GuideFrame->updateFrame();
1176     }
1177 
1178     int maxX = m_ImageData->width();
1179     int maxY = m_ImageData->height();
1180 
1181     int scores[MAX_GUIDE_STARS];
1182 
1183     int maxIndex = MAX_GUIDE_STARS < starCenters.count() ? MAX_GUIDE_STARS : starCenters.count();
1184 
1185     for (int i = 0; i < maxIndex; i++)
1186     {
1187         int score = 100;
1188 
1189         Edge *center = starCenters.at(i);
1190 
1191         if (useNativeDetection)
1192         {
1193             // Severely reject stars close to edges
1194             if (center->x < (center->width * 5) || center->y < (center->width * 5) ||
1195                     center->x > (maxX - center->width * 5) || center->y > (maxY - center->width * 5))
1196                 score -= 1000;
1197 
1198             // Reject stars bigger than square
1199             if (center->width > float(guideBoxSize) / subBinX)
1200                 score -= 1000;
1201             else
1202             {
1203                 if (Options::guideAlgorithm() == SEP_THRESHOLD)
1204                     score += sqrt(center->val);
1205                 else
1206                     // Moderately favor brighter stars
1207                     score += center->width * center->width;
1208             }
1209 
1210             // Moderately reject stars close to other stars
1211             foreach (Edge *edge, starCenters)
1212             {
1213                 if (edge == center)
1214                     continue;
1215 
1216                 if (fabs(center->x - edge->x) < center->width * 2 && fabs(center->y - edge->y) < center->width * 2)
1217                 {
1218                     score -= 15;
1219                     break;
1220                 }
1221             }
1222         }
1223         else
1224         {
1225             score = center->val;
1226         }
1227 
1228         scores[i] = score;
1229     }
1230 
1231     int maxScore      = -1;
1232     int maxScoreIndex = -1;
1233     for (int i = 0; i < maxIndex; i++)
1234     {
1235         if (scores[i] > maxScore)
1236         {
1237             maxScore      = scores[i];
1238             maxScoreIndex = i;
1239         }
1240     }
1241 
1242     if (maxScoreIndex < 0)
1243     {
1244         qCDebug(KSTARS_EKOS_GUIDE) << "No suitable star detected.";
1245         return false;
1246     }
1247 
1248     QVector3D newStarCenter(starCenters[maxScoreIndex]->x, starCenters[maxScoreIndex]->y, 0);
1249 
1250     if (useNativeDetection == false)
1251         qDeleteAll(starCenters);
1252 
1253     emit newStarPosition(newStarCenter, true);
1254 
1255     return true;
1256 }
1257 
1258 bool InternalGuider::reacquire()
1259 {
1260     bool rc = selectAutoStar();
1261     if (rc)
1262     {
1263         m_highRMSCounter = m_starLostCounter = 0;
1264         m_isFirstFrame = true;
1265         pmath->reset();
1266         // If we were in the process of dithering, wait until settle and resume
1267         if (rememberState == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
1268         {
1269             if (Options::ditherSettle() > 0)
1270             {
1271                 state = GUIDE_DITHERING_SETTLE;
1272                 guideLog.settleStartedInfo();
1273                 emit newStatus(state);
1274             }
1275 
1276             QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
1277         }
1278         else
1279         {
1280             state = GUIDE_GUIDING;
1281             emit newStatus(state);
1282         }
1283 
1284     }
1285     else if (reacquireTimer.elapsed() > static_cast<int>(Options::guideLostStarTimeout() * 1000))
1286     {
1287         emit newLog(i18n("Failed to find any suitable guide stars. Aborting..."));
1288         abort();
1289         return false;
1290     }
1291 
1292     emit frameCaptureRequested();
1293     return rc;
1294 }
1295 
1296 void InternalGuider::fillGuideInfo(GuideLog::GuideInfo * info)
1297 {
1298     // NOTE: just using the X values, phd2logview assumes x & y the same.
1299     // pixel scale in arc-sec / pixel. The 2nd and 3rd values seem redundent, but are
1300     // in the phd2 logs.
1301     info->pixelScale = (206.26481 * this->ccdPixelSizeX * this->subBinX) / this->mountFocalLength;
1302     info->binning = this->subBinX;
1303     info->focalLength = this->mountFocalLength;
1304     info->ra = this->mountRA.Degrees();
1305     info->dec = this->mountDEC.Degrees();
1306     info->azimuth = this->mountAzimuth.Degrees();
1307     info->altitude = this->mountAltitude.Degrees();
1308     info->pierSide = this->pierSide;
1309     info->xangle = pmath->getCalibration().getRAAngle();
1310     info->yangle = pmath->getCalibration().getDECAngle();
1311     // Calibration values in ms/pixel, xrate is in pixels/second.
1312     info->xrate = 1000.0 / pmath->getCalibration().raPulseMillisecondsPerArcsecond();
1313     info->yrate = 1000.0 / pmath->getCalibration().decPulseMillisecondsPerArcsecond();
1314 }
1315 
1316 void InternalGuider::updateGPGParameters()
1317 {
1318     setDarkGuideTimerInterval();
1319     pmath->getGPG().updateParameters();
1320 }
1321 
1322 void InternalGuider::resetGPG()
1323 {
1324     pmath->getGPG().reset();
1325     resetDarkGuiding();
1326 }
1327 
1328 const Calibration &InternalGuider::getCalibration() const
1329 {
1330     return pmath->getCalibration();
1331 }
1332 }