9#include "internalguider.h"
11#include "ekos_guide_debug.h"
14#include "auxiliary/kspaths.h"
15#include "fitsviewer/fitsdata.h"
16#include "fitsviewer/fitsview.h"
17#include "guidealgorithms.h"
18#include "ksnotification.h"
19#include "ekos/auxiliary/stellarsolverprofileeditor.h"
20#include "fitsviewer/fitsdata.h"
21#include "../guideview.h"
22#include "ekos/guide/opsguide.h"
31#define MAX_GUIDE_STARS 10
33using namespace std::chrono_literals;
37InternalGuider::InternalGuider()
40 pmath.reset(
new cgmath());
41 connect(pmath.get(), &cgmath::newStarPosition,
this, &InternalGuider::newStarPosition);
42 connect(pmath.get(), &cgmath::guideStats,
this, &InternalGuider::guideStats);
45 m_DitherOrigin = QVector3D(0, 0, 0);
49 m_darkGuideTimer = std::make_unique<QTimer>(
this);
50 m_captureTimer = std::make_unique<QTimer>(
this);
51 m_ditherSettleTimer = std::make_unique<QTimer>(
this);
53 setDarkGuideTimerInterval();
57 connect(
this, &Ekos::GuideInterface::frameCaptureRequested,
this, [ = ]()
59 this->m_captureTimer->start();
63void InternalGuider::setExposureTime()
65 Seconds seconds(Options::guideExposure());
66 setTimer(m_captureTimer, seconds);
69void InternalGuider::setTimer(std::unique_ptr<QTimer> &timer, Seconds seconds)
71 const std::chrono::duration<double, std::milli> inMilliseconds(seconds);
72 timer->setInterval((
int)(inMilliseconds.count()));
75void InternalGuider::setDarkGuideTimerInterval()
77 constexpr double kMinInterval = 0.5;
78 const Seconds seconds(std::max(kMinInterval, Options::gPGDarkGuidingInterval()));
79 setTimer(m_darkGuideTimer, seconds);
82void InternalGuider::resetDarkGuiding()
84 m_darkGuideTimer->stop();
85 m_captureTimer->stop();
88bool InternalGuider::isInferencePeriodFinished()
90 auto const contribution = pmath->getGPG().predictionContribution();
91 return contribution >= 0.99;
93bool InternalGuider::guide()
95 if (state >= GUIDE_GUIDING)
97 return processGuiding();
100 if (state == GUIDE_SUSPENDED)
104 m_GuideFrame->disconnect(
this);
109 m_starLostCounter = 0;
110 m_highRMSCounter = 0;
111 m_DitherOrigin = QVector3D(0, 0, 0);
113 m_isFirstFrame =
true;
115 if (state == GUIDE_IDLE)
117 if (Options::saveGuideLog())
119 GuideLog::GuideInfo info;
120 fillGuideInfo(&info);
121 guideLog.startGuiding(info);
123 state = GUIDE_GUIDING;
125 emit newStatus(state);
127 emit frameCaptureRequested();
141bool InternalGuider::abort()
145 disableDitherSettleTimer();
148 guideLog.endGuiding();
151 if (state == GUIDE_CALIBRATING ||
152 state == GUIDE_GUIDING ||
153 state == GUIDE_DITHERING ||
154 state == GUIDE_MANUAL_DITHERING ||
155 state == GUIDE_REACQUIRE)
157 if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
158 emit newStatus(GUIDE_DITHERING_ERROR);
159 emit newStatus(GUIDE_ABORTED);
161 qCDebug(KSTARS_EKOS_GUIDE) <<
"Aborting" << getGuideStatusString(state);
165 emit newStatus(GUIDE_IDLE);
166 qCDebug(KSTARS_EKOS_GUIDE) <<
"Stopping internal guider.";
170 disconnect(m_darkGuideTimer.get(),
nullptr,
nullptr,
nullptr);
176 m_ProgressiveDither.clear();
177 m_starLostCounter = 0;
178 m_highRMSCounter = 0;
180 m_DitherOrigin = QVector3D(0, 0, 0);
182 pmath->suspend(
false);
184 qCDebug(KSTARS_EKOS_GUIDE) <<
"Guiding aborted.";
189bool InternalGuider::suspend()
191 disableDitherSettleTimer();
192 guideLog.pauseInfo();
193 state = GUIDE_SUSPENDED;
196 emit newStatus(state);
198 pmath->suspend(
true);
204void InternalGuider::startDarkGuiding()
206 if (Options::gPGDarkGuiding())
211 m_darkGuideTimer->start();
213 qCDebug(KSTARS_EKOS_GUIDE) <<
"Starting dark guiding.";
217bool InternalGuider::resume()
219 qCDebug(KSTARS_EKOS_GUIDE) <<
"Resuming...";
221 guideLog.resumeInfo();
222 state = GUIDE_GUIDING;
223 emit newStatus(state);
225 pmath->suspend(
false);
231 emit frameCaptureRequested();
236bool InternalGuider::ditherXY(
double x,
double y)
238 m_ProgressiveDither.clear();
241 pmath->getTargetPosition(&cur_x, &cur_y);
246 double oneJump = (guideBoxSize / 4.0);
247 double targetX = cur_x, targetY = cur_y;
248 int xSign = (x >= cur_x) ? 1 : -1;
249 int ySign = (y >= cur_y) ? 1 : -1;
253 if (fabs(targetX - x) > oneJump)
254 targetX += oneJump * xSign;
255 else if (fabs(targetX - x) < oneJump)
258 if (fabs(targetY - y) > oneJump)
259 targetY += oneJump * ySign;
260 else if (fabs(targetY - y) < oneJump)
263 m_ProgressiveDither.enqueue(GuiderUtils::Vector(targetX, targetY, -1));
266 while (targetX != x || targetY != y);
268 m_DitherTargetPosition = m_ProgressiveDither.dequeue();
269 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
270 guideLog.ditherInfo(x, y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
272 state = GUIDE_MANUAL_DITHERING;
273 emit newStatus(state);
280bool InternalGuider::dither(
double pixels)
282 if (Options::ditherWithOnePulse() )
283 return onePulseDither(pixels);
286 pmath->getTargetPosition(&ret_x, &ret_y);
293 GuiderUtils::Vector star_position = pmath->findLocalStarPosition(m_ImageData, m_GuideFrame,
false);
294 if (pmath->isStarLost() || (star_position.x == -1) || (star_position.y == -1))
298 if (++m_starLostCounter > MAX_LOST_STAR_THRESHOLD)
300 qCDebug(KSTARS_EKOS_GUIDE) <<
"Too many consecutive lost stars." << m_starLostCounter <<
"Aborting dither.";
301 return abortDither();
303 qCDebug(KSTARS_EKOS_GUIDE) <<
"Dither lost star. Trying again.";
304 emit frameCaptureRequested();
308 m_starLostCounter = 0;
310 if (state != GUIDE_DITHERING)
314 auto seed = std::chrono::system_clock::now().time_since_epoch().count();
315 std::default_random_engine generator(seed);
316 std::uniform_real_distribution<double> angleMagnitude(0, 360);
319 double diff_x = pixels * cos(angle);
320 double diff_y = pixels * sin(angle);
322 if (pmath->getCalibration().declinationSwapEnabled())
325 if (m_DitherOrigin.x() == 0 && m_DitherOrigin.y() == 0)
327 m_DitherOrigin = QVector3D(ret_x, ret_y, 0);
329 double totalXOffset = ret_x - m_DitherOrigin.x();
330 double totalYOffset = ret_y - m_DitherOrigin.y();
335 if (((diff_x + totalXOffset > MAX_DITHER_TRAVEL) && (diff_x > 0)) ||
336 ((diff_x + totalXOffset < -MAX_DITHER_TRAVEL) && (diff_x < 0)))
338 qCDebug(KSTARS_EKOS_GUIDE)
339 << QString(
"Dithering target off by too much in X (abs(%1 + %2) > %3), adjust diff_x from %4 to %5")
340 .arg(diff_x).arg(totalXOffset).arg(MAX_DITHER_TRAVEL).arg(diff_x).arg(diff_x * -1.5);
343 if (((diff_y + totalYOffset > MAX_DITHER_TRAVEL) && (diff_y > 0)) ||
344 ((diff_y + totalYOffset < -MAX_DITHER_TRAVEL) && (diff_y < 0)))
346 qCDebug(KSTARS_EKOS_GUIDE)
347 << QString(
"Dithering target off by too much in Y (abs(%1 + %2) > %3), adjust diff_y from %4 to %5")
348 .arg(diff_y).arg(totalYOffset).arg(MAX_DITHER_TRAVEL).arg(diff_y).arg(diff_y * -1.5);
352 m_DitherTargetPosition = GuiderUtils::Vector(ret_x, ret_y, 0) + GuiderUtils::Vector(diff_x, diff_y, 0);
354 qCDebug(KSTARS_EKOS_GUIDE)
355 << QString(
"Dithering by %1 pixels. Target: %2,%3 Current: %4,%5 Move: %6,%7 Wander: %8,%9")
356 .arg(pixels, 3,
'f', 1)
357 .arg(m_DitherTargetPosition.x, 5,
'f', 1).arg(m_DitherTargetPosition.y, 5,
'f', 1)
358 .arg(ret_x, 5,
'f', 1).arg(ret_y, 5,
'f', 1)
359 .arg(diff_x, 4,
'f', 1).arg(diff_y, 4,
'f', 1)
360 .arg(totalXOffset + diff_x, 5,
'f', 1).arg(totalYOffset + diff_y, 5,
'f', 1);
361 guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
363 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
365 if (Options::rAGuidePulseAlgorithm() == OpsGuide::GPG_ALGORITHM)
367 pmath->getGPG().startDithering(diff_x, diff_y, pmath->getCalibration());
369 state = GUIDE_DITHERING;
370 emit newStatus(state);
378 double driftRA, driftDEC;
379 pmath->getCalibration().computeDrift(
381 GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0),
382 &driftRA, &driftDEC);
384 double pixelOffsetX = m_DitherTargetPosition.x - star_position.x;
385 double pixelOffsetY = m_DitherTargetPosition.y - star_position.y;
387 qCDebug(KSTARS_EKOS_GUIDE)
388 << QString(
"Dithering in progress. Current: %1,%2 Target: %3,%4 Diff: %5,%6 Wander: %8,%9")
389 .arg(star_position.x, 5,
'f', 1).arg(star_position.y, 5,
'f', 1)
390 .arg(m_DitherTargetPosition.x, 5,
'f', 1).arg(m_DitherTargetPosition.y, 5,
'f', 1)
391 .arg(pixelOffsetX, 4,
'f', 1).arg(pixelOffsetY, 4,
'f', 1)
392 .arg(star_position.x - m_DitherOrigin.x(), 5,
'f', 1)
393 .arg(star_position.y - m_DitherOrigin.y(), 5,
'f', 1);
395 if (Options::ditherWithOnePulse() || (fabs(driftRA) < 1 && fabs(driftDEC) < 1))
397 pmath->setTargetPosition(star_position.x, star_position.y);
402 if (Options::ditherWithOnePulse())
403 m_isFirstFrame =
true;
405 qCDebug(KSTARS_EKOS_GUIDE) <<
"Dither complete.";
407 if (Options::ditherSettle() > 0)
409 state = GUIDE_DITHERING_SETTLE;
410 guideLog.settleStartedInfo();
411 emit newStatus(state);
414 if (Options::rAGuidePulseAlgorithm() == OpsGuide::GPG_ALGORITHM)
416 pmath->getGPG().ditheringSettled(
true);
418 startDitherSettleTimer(Options::ditherSettle() * 1000);
422 if (++m_DitherRetries > Options::ditherMaxIterations())
423 return abortDither();
430bool InternalGuider::onePulseDither(
double pixels)
432 qCDebug(KSTARS_EKOS_GUIDE) <<
"OnePulseDither(" <<
"pixels" <<
")";
435 emit abortExposure();
438 pmath->getTargetPosition(&ret_x, &ret_y);
440 auto seed = std::chrono::system_clock::now().time_since_epoch().count();
441 std::default_random_engine generator(seed);
442 std::uniform_real_distribution<double> angleMagnitude(0, 360);
445 double diff_x = pixels * cos(angle);
446 double diff_y = pixels * sin(angle);
448 if (pmath->getCalibration().declinationSwapEnabled())
451 if (m_DitherOrigin.x() == 0 && m_DitherOrigin.y() == 0)
453 m_DitherOrigin = QVector3D(ret_x, ret_y, 0);
455 double totalXOffset = ret_x - m_DitherOrigin.x();
456 double totalYOffset = ret_y - m_DitherOrigin.y();
461 if (((diff_x + totalXOffset > MAX_DITHER_TRAVEL) && (diff_x > 0)) ||
462 ((diff_x + totalXOffset < -MAX_DITHER_TRAVEL) && (diff_x < 0)))
464 qCDebug(KSTARS_EKOS_GUIDE)
465 << QString(
"OPD: Dithering target off by too much in X (abs(%1 + %2) > %3), adjust diff_x from %4 to %5")
466 .arg(diff_x).arg(totalXOffset).arg(MAX_DITHER_TRAVEL).arg(diff_x).arg(diff_x * -1.5);
469 if (((diff_y + totalYOffset > MAX_DITHER_TRAVEL) && (diff_y > 0)) ||
470 ((diff_y + totalYOffset < -MAX_DITHER_TRAVEL) && (diff_y < 0)))
472 qCDebug(KSTARS_EKOS_GUIDE)
473 << QString(
"OPD: Dithering target off by too much in Y (abs(%1 + %2) > %3), adjust diff_y from %4 to %5")
474 .arg(diff_y).arg(totalYOffset).arg(MAX_DITHER_TRAVEL).arg(diff_y).arg(diff_y * -1.5);
478 m_DitherTargetPosition = GuiderUtils::Vector(ret_x, ret_y, 0) + GuiderUtils::Vector(diff_x, diff_y, 0);
480 qCDebug(KSTARS_EKOS_GUIDE)
481 << QString(
"OPD: Dithering by %1 pixels. Target: %2,%3 Current: %4,%5 Move: %6,%7 Wander: %8,%9")
482 .arg(pixels, 3,
'f', 1)
483 .arg(m_DitherTargetPosition.x, 5,
'f', 1).arg(m_DitherTargetPosition.y, 5,
'f', 1)
484 .arg(ret_x, 5,
'f', 1).arg(ret_y, 5,
'f', 1)
485 .arg(diff_x, 4,
'f', 1).arg(diff_y, 4,
'f', 1)
486 .arg(totalXOffset + diff_x, 5,
'f', 1).arg(totalYOffset + diff_y, 5,
'f', 1);
487 guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
489 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
491 if (Options::rAGuidePulseAlgorithm() == OpsGuide::GPG_ALGORITHM)
493 pmath->getGPG().startDithering(diff_x, diff_y, pmath->getCalibration());
495 state = GUIDE_DITHERING;
496 emit newStatus(state);
498 const GuiderUtils::Vector xyMove(diff_x, diff_y, 0);
499 const GuiderUtils::Vector raDecMove = pmath->getCalibration().rotateToRaDec(xyMove);
500 double raPulse = fabs(raDecMove.x * pmath->getCalibration().raPulseMillisecondsPerArcsecond());
501 double decPulse = fabs(raDecMove.y * pmath->getCalibration().decPulseMillisecondsPerArcsecond());
502 auto raDir = raDecMove.x > 0 ? RA_INC_DIR : RA_DEC_DIR;
503 auto decDir = raDecMove.y > 0 ? DEC_DEC_DIR : DEC_INC_DIR;
505 m_isFirstFrame =
true;
508 QString raDirString = raDir == RA_DEC_DIR ?
"RA_DEC" :
"RA_INC";
509 QString decDirString = decDir == DEC_INC_DIR ?
"DEC_INC" :
"DEC_DEC";
511 qCDebug(KSTARS_EKOS_GUIDE) <<
"OnePulseDither RA: " << raPulse <<
"ms" << raDirString <<
" DEC: " << decPulse <<
"ms " <<
515 emit newMultiPulse(raDir, raPulse, decDir, decPulse, DontCaptureAfterPulses);
517 double totalMSecs = 1000.0 * Options::ditherSettle() + std::max(raPulse, decPulse) + 100;
519 state = GUIDE_DITHERING_SETTLE;
520 guideLog.settleStartedInfo();
521 emit newStatus(state);
523 if (Options::rAGuidePulseAlgorithm() == OpsGuide::GPG_ALGORITHM)
524 pmath->getGPG().ditheringSettled(
true);
526 startDitherSettleTimer(totalMSecs);
530bool InternalGuider::abortDither()
532 if (Options::ditherFailAbortsAutoGuide())
534 emit newStatus(Ekos::GUIDE_DITHERING_ERROR);
540 emit newLog(
i18n(
"Warning: Dithering failed. Autoguiding shall continue as set in the options in case "
541 "of dither failure."));
543 if (Options::ditherSettle() > 0)
545 state = GUIDE_DITHERING_SETTLE;
546 guideLog.settleStartedInfo();
547 emit newStatus(state);
550 if (Options::rAGuidePulseAlgorithm() == OpsGuide::GPG_ALGORITHM)
551 pmath->getGPG().ditheringSettled(
false);
553 startDitherSettleTimer(Options::ditherSettle() * 1000);
558bool InternalGuider::processManualDithering()
561 pmath->getTargetPosition(&cur_x, &cur_y);
562 pmath->getStarScreenPosition(&cur_x, &cur_y);
565 double driftRA, driftDEC;
566 pmath->getCalibration().computeDrift(
567 GuiderUtils::Vector(cur_x, cur_y, 0),
568 GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0),
569 &driftRA, &driftDEC);
571 qCDebug(KSTARS_EKOS_GUIDE) <<
"Manual Dithering in progress. Diff star X:" << driftRA <<
"Y:" << driftDEC;
573 if (fabs(driftRA) < guideBoxSize / 5.0 && fabs(driftDEC) < guideBoxSize / 5.0)
575 if (m_ProgressiveDither.empty() ==
false)
577 m_DitherTargetPosition = m_ProgressiveDither.dequeue();
578 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
579 qCDebug(KSTARS_EKOS_GUIDE) <<
"Next Dither Jump X:" << m_DitherTargetPosition.x <<
"Jump Y:" << m_DitherTargetPosition.y;
587 if (fabs(driftRA) < 1 && fabs(driftDEC) < 1)
589 pmath->setTargetPosition(cur_x, cur_y);
590 qCDebug(KSTARS_EKOS_GUIDE) <<
"Manual Dither complete.";
592 if (Options::ditherSettle() > 0)
594 state = GUIDE_DITHERING_SETTLE;
595 guideLog.settleStartedInfo();
596 emit newStatus(state);
599 startDitherSettleTimer(Options::ditherSettle() * 1000);
608 if (++m_DitherRetries > Options::ditherMaxIterations())
610 emit newLog(
i18n(
"Warning: Manual Dithering failed."));
612 if (Options::ditherSettle() > 0)
614 state = GUIDE_DITHERING_SETTLE;
615 guideLog.settleStartedInfo();
616 emit newStatus(state);
619 startDitherSettleTimer(Options::ditherSettle() * 1000);
629void InternalGuider::startDitherSettleTimer(
int ms)
631 m_ditherSettleTimer->setSingleShot(
true);
633 m_ditherSettleTimer->start(ms);
636void InternalGuider::disableDitherSettleTimer()
639 m_ditherSettleTimer->stop();
642void InternalGuider::setDitherSettled()
644 disableDitherSettleTimer();
647 if (state != GUIDE_IDLE && state != GUIDE_ABORTED && state != GUIDE_SUSPENDED)
649 guideLog.settleCompletedInfo();
650 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
653 state = GUIDE_GUIDING;
657bool InternalGuider::calibrate()
659 bool ccdInfo =
true, scopeInfo =
true;
662 if (subW == 0 || subH == 0)
668 if (mountAperture == 0.0 || mountFocalLength == 0.0)
671 if (ccdInfo ==
false)
672 errMsg +=
" & Telescope";
674 errMsg +=
"Telescope";
677 if (ccdInfo ==
false || scopeInfo ==
false)
679 KSNotification::error(
i18n(
"%1 info are missing. Please set the values in INDI Control Panel.", errMsg),
680 i18n(
"Missing Information"));
684 if (state != GUIDE_CALIBRATING)
686 pmath->getTargetPosition(&calibrationStartX, &calibrationStartY);
687 calibrationProcess.reset(
688 new CalibrationProcess(calibrationStartX, calibrationStartY,
689 !Options::twoAxisEnabled()));
690 state = GUIDE_CALIBRATING;
691 emit newStatus(GUIDE_CALIBRATING);
694 if (calibrationProcess->inProgress())
696 iterateCalibration();
700 if (restoreCalibration())
702 calibrationProcess.reset();
703 emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS);
704 KSNotification::event(QLatin1String(
"CalibrationRestored"),
705 i18n(
"Guiding calibration restored"), KSNotification::Guide);
712 pmath->getMutableCalibration()->setParameters(
713 ccdPixelSizeX / 1000.0, ccdPixelSizeY / 1000.0, mountFocalLength,
714 subBinX, subBinY, pierSide, mountRA, mountDEC);
716 calibrationProcess->useCalibration(pmath->getMutableCalibration());
718 m_GuideFrame->disconnect(
this);
721 emit DESwapChanged(
false);
722 pmath->setLostStar(
false);
724 if (Options::saveGuideLog())
726 GuideLog::GuideInfo info;
727 fillGuideInfo(&info);
728 guideLog.startCalibration(info);
730 calibrationProcess->startup();
731 calibrationProcess->setGuideLog(&guideLog);
732 iterateCalibration();
737void InternalGuider::iterateCalibration()
739 if (calibrationProcess->inProgress())
741 auto const timeStep = calculateGPGTimeStep();
742 pmath->performProcessing(GUIDE_CALIBRATING, m_ImageData, m_GuideFrame, timeStep);
745 if (pmath->usingSEPMultiStar())
747 auto gs = pmath->getGuideStars();
748 info = QString(
"%1 stars, %2/%3 refs")
749 .arg(gs.getNumStarsDetected())
750 .arg(gs.getNumReferencesFound())
751 .arg(gs.getNumReferences());
753 emit guideInfo(info);
755 if (pmath->isStarLost())
757 emit newLog(
i18n(
"Lost track of the guide star. Try increasing the square size or reducing pulse duration."));
758 emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
759 emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY,
760 i18n(
"Guide Star lost."));
766 pmath->getStarScreenPosition(&starX, &starY);
767 calibrationProcess->iterate(starX, starY);
769 auto status = calibrationProcess->getStatus();
770 if (status != GUIDE_CALIBRATING)
771 emit newStatus(status);
773 QString logStatus = calibrationProcess->getLogStatus();
775 emit newLog(logStatus);
777 QString updateMessage;
779 GuideInterface::CalibrationUpdateType
type;
780 calibrationProcess->getCalibrationUpdate(&type, &updateMessage, &x, &y);
781 if (updateMessage.
length())
782 emit calibrationUpdate(type, updateMessage, x, y);
784 GuideDirection pulseDirection;
786 calibrationProcess->getPulse(&pulseDirection, &pulseMsecs);
787 if (pulseDirection != NO_DIR)
788 emit newSinglePulse(pulseDirection, pulseMsecs, StartCaptureAfterPulses);
790 if (status == GUIDE_CALIBRATION_ERROR)
792 KSNotification::event(QLatin1String(
"CalibrationFailed"),
i18n(
"Guiding calibration failed"),
793 KSNotification::Guide, KSNotification::Alert);
796 else if (status == GUIDE_CALIBRATION_SUCCESS)
798 KSNotification::event(QLatin1String(
"CalibrationSuccessful"),
799 i18n(
"Guiding calibration completed successfully"), KSNotification::Guide);
800 emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled());
801 pmath->setTargetPosition(calibrationStartX, calibrationStartY);
806void InternalGuider::setGuideView(
const QSharedPointer<GuideView> &guideView)
808 m_GuideFrame = guideView;
811void InternalGuider::setImageData(
const QSharedPointer<FITSData> &data)
814 if (Options::saveGuideImages())
818 now.toString(
"yyyy-MM-dd"));
823 QString
name =
"guide_frame_" + now.toString(
"HH-mm-ss") +
".fits";
824 QString filename =
path + QStringLiteral(
"/") +
name;
825 m_ImageData->saveImage(filename);
829void InternalGuider::reset()
831 qCDebug(KSTARS_EKOS_GUIDE) <<
"Resetting internal guider...";
836 connect(m_GuideFrame.get(), &FITSView::trackingStarSelected,
this, &InternalGuider::trackingStarSelected,
838 calibrationProcess.reset();
841bool InternalGuider::clearCalibration()
843 Options::setSerializedCalibration(
"");
844 pmath->getMutableCalibration()->reset();
848bool InternalGuider::restoreCalibration()
850 bool success = Options::reuseGuideCalibration() &&
851 pmath->getMutableCalibration()->restore(
852 pierSide, Options::reverseDecOnPierSideChange(),
853 subBinX, subBinY, &mountDEC);
855 emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled());
859void InternalGuider::setStarPosition(QVector3D &starCenter)
861 pmath->setTargetPosition(starCenter.
x(), starCenter.
y());
864void InternalGuider::trackingStarSelected(
int x,
int y)
882void InternalGuider::setDECSwap(
bool enable)
884 pmath->getMutableCalibration()->setDeclinationSwapEnabled(enable);
887void InternalGuider::setStarDetectionAlgorithm(
int index)
889 if (index == SEP_MULTISTAR && !pmath->usingSEPMultiStar())
890 m_isFirstFrame =
true;
891 pmath->setStarDetectionAlgorithmIndex(index);
894bool InternalGuider::getReticleParameters(
double * x,
double * y)
896 return pmath->getTargetPosition(x, y);
899bool InternalGuider::setGuiderParams(
double ccdPixelSizeX,
double ccdPixelSizeY,
double mountAperture,
900 double mountFocalLength)
902 this->ccdPixelSizeX = ccdPixelSizeX;
903 this->ccdPixelSizeY = ccdPixelSizeY;
904 this->mountAperture = mountAperture;
905 this->mountFocalLength = mountFocalLength;
906 return pmath->setGuiderParameters(mountAperture);
909bool InternalGuider::setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY)
911 if (w <= 0 || h <= 0)
922 pmath->setVideoParameters(w, h, subBinX, subBinY);
927void InternalGuider::emitAxisPulse(
const cproc_out_params * out)
929 double raPulse = out->pulse_length[GUIDE_RA];
930 double dePulse = out->pulse_length[GUIDE_DEC];
933 if(out->pulse_dir[GUIDE_RA] == NO_DIR)
936 if(out->pulse_dir[GUIDE_DEC] == NO_DIR)
940 if(out->pulse_dir[GUIDE_RA] == RA_INC_DIR)
943 if(out->pulse_dir[GUIDE_DEC] == DEC_DEC_DIR)
946 emit newAxisPulse(raPulse, dePulse);
949bool InternalGuider::processGuiding()
951 const cproc_out_params *out;
959 m_isFirstFrame =
false;
960 if (state == GUIDE_GUIDING)
962 GuiderUtils::Vector star_pos = pmath->findLocalStarPosition(m_ImageData, m_GuideFrame,
true);
963 if (star_pos.x != -1 && star_pos.y != -1)
964 pmath->setTargetPosition(star_pos.x, star_pos.y);
969 m_isFirstFrame =
true;
976 auto const timeStep = calculateGPGTimeStep();
977 pmath->performProcessing(state, m_ImageData, m_GuideFrame, timeStep, &guideLog);
978 if (pmath->usingSEPMultiStar())
981 auto gs = pmath->getGuideStars();
982 info = QString(
"%1 stars, %2/%3 refs")
983 .arg(gs.getNumStarsDetected())
984 .arg(gs.getNumReferencesFound())
985 .arg(gs.getNumReferences());
987 emit guideInfo(info);
991 if (this->m_darkGuideTimer->isActive())
992 this->m_darkGuideTimer->start();
995 if (state == GUIDE_SUSPENDED)
997 if (Options::rAGuidePulseAlgorithm() == OpsGuide::GPG_ALGORITHM)
998 emit frameCaptureRequested();
1003 if (pmath->isStarLost())
1004 m_starLostCounter++;
1006 m_starLostCounter = 0;
1010 out = pmath->getOutputParameters();
1012 if (isPoorGuiding(out))
1017 if ((state == GUIDE_DITHERING_SETTLE || state == GUIDE_DITHERING) && Options::ditherWithOnePulse())
1020 bool sendPulses = !pmath->isStarLost();
1023 if (sendPulses && (out->pulse_dir[GUIDE_RA] != NO_DIR || out->pulse_dir[GUIDE_DEC] != NO_DIR))
1025 emit newMultiPulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA],
1026 out->pulse_dir[GUIDE_DEC], out->pulse_length[GUIDE_DEC], StartCaptureAfterPulses);
1029 emit frameCaptureRequested();
1031 if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING || state == GUIDE_DITHERING_SETTLE)
1039 if (state < GUIDE_DITHERING)
1043 emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]);
1046 emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]);
1047 if (SEPMultiStarEnabled())
1048 emit newSNR(pmath->getGuideStarSNR());
1055std::pair<Seconds, Seconds> InternalGuider::calculateGPGTimeStep()
1059 const Seconds guideDelay{(Options::guideDelay())};
1061 auto const captureInterval = Seconds(m_captureTimer->intervalAsDuration()) + guideDelay;
1062 auto const darkGuideInterval = Seconds(m_darkGuideTimer->intervalAsDuration());
1064 if (!Options::gPGDarkGuiding() || !isInferencePeriodFinished())
1066 return std::pair<Seconds, Seconds>(captureInterval, captureInterval);
1068 auto const captureTimeRemaining = Seconds(m_captureTimer->remainingTimeAsDuration()) + guideDelay;
1069 auto const darkGuideTimeRemaining = Seconds(m_darkGuideTimer->remainingTimeAsDuration());
1071 if (captureTimeRemaining <= Seconds::zero()
1072 && darkGuideTimeRemaining <= Seconds::zero())
1074 timeStep = std::min(captureInterval, darkGuideInterval);
1076 else if (captureTimeRemaining <= Seconds::zero())
1078 timeStep = std::min(captureInterval, darkGuideTimeRemaining);
1080 else if (darkGuideTimeRemaining <= Seconds::zero())
1082 timeStep = std::min(captureTimeRemaining, darkGuideInterval);
1086 timeStep = std::min(captureTimeRemaining, darkGuideTimeRemaining);
1088 return std::pair<Seconds, Seconds>(timeStep, captureInterval);
1093void InternalGuider::darkGuide()
1096 if (state != GUIDE_GUIDING)
1099 if(Options::gPGDarkGuiding() && isInferencePeriodFinished())
1101 const cproc_out_params *out;
1102 auto const timeStep = calculateGPGTimeStep();
1103 pmath->performDarkGuiding(state, timeStep);
1105 out = pmath->getOutputParameters();
1106 emit newSinglePulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA], DontCaptureAfterPulses);
1112bool InternalGuider::isPoorGuiding(
const cproc_out_params * out)
1114 double delta_rms = std::hypot(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]);
1115 if (delta_rms > Options::guideMaxDeltaRMS())
1118 m_highRMSCounter = 0;
1120 uint8_t abortStarLostThreshold = (state == GUIDE_DITHERING
1121 || state == GUIDE_MANUAL_DITHERING) ? MAX_LOST_STAR_THRESHOLD * 3 : MAX_LOST_STAR_THRESHOLD;
1122 uint8_t abortRMSThreshold = (state == GUIDE_DITHERING
1123 || state == GUIDE_MANUAL_DITHERING) ? MAX_RMS_THRESHOLD * 3 : MAX_RMS_THRESHOLD;
1124 if (m_starLostCounter > abortStarLostThreshold || m_highRMSCounter > abortRMSThreshold)
1126 qCDebug(KSTARS_EKOS_GUIDE) <<
"m_starLostCounter" << m_starLostCounter
1127 <<
"m_highRMSCounter" << m_highRMSCounter
1128 <<
"delta_rms" << delta_rms;
1130 if (m_starLostCounter > abortStarLostThreshold)
1131 emit newLog(
i18n(
"Lost track of the guide star. Searching for guide stars..."));
1133 emit newLog(
i18n(
"Delta RMS threshold value exceeded. Searching for guide stars..."));
1135 reacquireTimer.start();
1136 rememberState = state;
1137 state = GUIDE_REACQUIRE;
1138 emit newStatus(state);
1143bool InternalGuider::selectAutoStarSEPMultistar()
1145 m_GuideFrame->updateFrame();
1146 m_DitherOrigin = QVector3D(0, 0, 0);
1147 QVector3D newStarCenter = pmath->selectGuideStar(m_ImageData);
1148 if (newStarCenter.
x() >= 0)
1150 emit newStarPosition(newStarCenter,
true);
1156bool InternalGuider::SEPMultiStarEnabled()
1158 return Options::guideAlgorithm() == SEP_MULTISTAR;
1161bool InternalGuider::selectAutoStar()
1163 m_DitherOrigin = QVector3D(0, 0, 0);
1164 if (Options::guideAlgorithm() == SEP_MULTISTAR)
1165 return selectAutoStarSEPMultistar();
1167 bool useNativeDetection =
false;
1169 QList<Edge *> starCenters;
1171 if (Options::guideAlgorithm() != SEP_THRESHOLD)
1172 starCenters = GuideAlgorithms::detectStars(m_ImageData, m_GuideFrame->getTrackingBox());
1174 if (starCenters.
empty())
1176 QVariantMap settings;
1177 settings[
"maxStarsCount"] = 50;
1178 settings[
"optionsProfileIndex"] = Options::guideOptionsProfile();
1179 settings[
"optionsProfileGroup"] =
static_cast<int>(Ekos::GuideProfiles);
1180 m_ImageData->setSourceExtractorSettings(settings);
1182 if (Options::guideAlgorithm() == SEP_THRESHOLD)
1183 m_ImageData->findStars(ALGORITHM_SEP).waitForFinished();
1185 m_ImageData->findStars().waitForFinished();
1187 starCenters = m_ImageData->getStarCenters();
1188 if (starCenters.
empty())
1191 useNativeDetection =
true;
1193 if (Options::guideAlgorithm() == SEP_THRESHOLD)
1194 std::sort(starCenters.
begin(), starCenters.
end(), [](
const Edge * a,
const Edge * b)
1196 return a->val > b->val;
1199 std::sort(starCenters.
begin(), starCenters.
end(), [](
const Edge * a,
const Edge * b)
1201 return a->width > b->width;
1204 m_GuideFrame->setStarsEnabled(
true);
1205 m_GuideFrame->updateFrame();
1208 int maxX = m_ImageData->width();
1209 int maxY = m_ImageData->height();
1211 int scores[MAX_GUIDE_STARS];
1213 int maxIndex = MAX_GUIDE_STARS < starCenters.
count() ? MAX_GUIDE_STARS : starCenters.
count();
1215 for (
int i = 0; i < maxIndex; i++)
1221 if (useNativeDetection)
1229 if (
center->width >
float(guideBoxSize) / subBinX)
1233 if (Options::guideAlgorithm() == SEP_THRESHOLD)
1234 score += sqrt(
center->val);
1241 foreach (Edge *edge, starCenters)
1262 int maxScoreIndex = -1;
1263 for (
int i = 0; i < maxIndex; i++)
1265 if (scores[i] > maxScore)
1267 maxScore = scores[i];
1272 if (maxScoreIndex < 0)
1274 qCDebug(KSTARS_EKOS_GUIDE) <<
"No suitable star detected.";
1278 QVector3D newStarCenter(starCenters[maxScoreIndex]->x, starCenters[maxScoreIndex]->y, 0);
1280 if (useNativeDetection ==
false)
1281 qDeleteAll(starCenters);
1283 emit newStarPosition(newStarCenter,
true);
1288bool InternalGuider::reacquire()
1290 bool rc = selectAutoStar();
1293 m_highRMSCounter = m_starLostCounter = 0;
1294 m_isFirstFrame =
true;
1297 if (rememberState == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
1299 if (Options::ditherSettle() > 0)
1301 state = GUIDE_DITHERING_SETTLE;
1302 guideLog.settleStartedInfo();
1303 emit newStatus(state);
1306 startDitherSettleTimer(Options::ditherSettle() * 1000);
1310 state = GUIDE_GUIDING;
1311 emit newStatus(state);
1315 else if (reacquireTimer.elapsed() >
static_cast<int>(Options::guideLostStarTimeout() * 1000))
1317 emit newLog(
i18n(
"Failed to find any suitable guide stars. Aborting..."));
1322 emit frameCaptureRequested();
1326void InternalGuider::fillGuideInfo(GuideLog::GuideInfo * info)
1331 info->pixelScale = (206.26481 * this->ccdPixelSizeX * this->subBinX) / this->mountFocalLength;
1332 info->binning = this->subBinX;
1333 info->focalLength = this->mountFocalLength;
1334 info->ra = this->mountRA.Degrees();
1335 info->dec = this->mountDEC.Degrees();
1336 info->azimuth = this->mountAzimuth.Degrees();
1337 info->altitude = this->mountAltitude.Degrees();
1338 info->pierSide = this->pierSide;
1339 info->xangle = pmath->getCalibration().getRAAngle();
1340 info->yangle = pmath->getCalibration().getDECAngle();
1342 info->xrate = 1000.0 / pmath->getCalibration().raPulseMillisecondsPerArcsecond();
1343 info->yrate = 1000.0 / pmath->getCalibration().decPulseMillisecondsPerArcsecond();
1346void InternalGuider::updateGPGParameters()
1348 setDarkGuideTimerInterval();
1349 pmath->getGPG().updateParameters();
1352void InternalGuider::resetGPG()
1354 pmath->getGPG().reset();
1358const Calibration &InternalGuider::getCalibration()
const
1360 return pmath->getCalibration();
static constexpr double DegToRad
DegToRad is a const static member equal to the number of radians in one degree (dms::PI/180....
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
Ekos is an advanced Astrophotography tool for Linux.
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
QDateTime currentDateTime()
const_reference at(qsizetype i) const const
qsizetype count() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
qsizetype length() const const
QTextStream & center(QTextStream &stream)