8 #include "polaralignmentassistant.h"
12 #include "kstarsdata.h"
13 #include "ksnotification.h"
14 #include "ksmessagebox.h"
15 #include "ekos/auxiliary/stellarsolverprofile.h"
16 #include "ekos/auxiliary/solverutils.h"
18 #include "QProgressIndicator.h"
19 #include "polaralignwidget.h"
20 #include <ekos_align_debug.h>
22 #define PAA_VERSION "v3.0"
30 {PAH_FIRST_CAPTURE,
I18N_NOOP(
"First Capture")},
31 {PAH_FIRST_SOLVE,
I18N_NOOP(
"First Solve")},
33 {PAH_FIRST_ROTATE,
I18N_NOOP(
"First Rotation")},
34 {PAH_FIRST_SETTLE,
I18N_NOOP(
"First Settle")},
35 {PAH_SECOND_CAPTURE,
I18N_NOOP(
"Second Capture")},
36 {PAH_SECOND_SOLVE,
I18N_NOOP(
"Second Solve")},
37 {PAH_SECOND_ROTATE,
I18N_NOOP(
"Second Rotation")},
38 {PAH_SECOND_SETTLE,
I18N_NOOP(
"Second Settle")},
39 {PAH_THIRD_CAPTURE,
I18N_NOOP(
"Third Capture")},
40 {PAH_THIRD_SOLVE,
I18N_NOOP(
"Third Solve")},
41 {PAH_STAR_SELECT,
I18N_NOOP(
"Select Star")},
43 {PAH_POST_REFRESH,
I18N_NOOP(
"Refresh Complete")},
49 polarAlignWidget =
new PolarAlignWidget();
50 mainPALayout->insertWidget(0, polarAlignWidget);
57 Options::setPAHMountSpeedIndex(index);
60 showUpdatedError((Options::pAHRefreshAlgorithm() == PLATE_SOLVE_ALGORITHM) ||
61 (Options::pAHRefreshAlgorithm() == MOVE_STAR_UPDATE_ERR_ALGORITHM));
63 PAHRefreshAlgorithmCombo->setCurrentIndex(Options::pAHRefreshAlgorithm());
66 setPAHRefreshAlgorithm(
static_cast<PAHRefreshAlgorithm
>(index));
68 starCorrespondencePAH.reset();
71 PAHWidgets->setCurrentWidget(PAHIntroPage);
72 connect(
this, &PolarAlignmentAssistant::PAHEnabled, [&](
bool enabled)
74 PAHStartB->setEnabled(enabled);
75 directionLabel->setEnabled(enabled);
76 PAHDirectionCombo->setEnabled(enabled);
77 PAHRotationSpin->setEnabled(enabled);
78 PAHSlewRateCombo->setEnabled(enabled);
79 PAHManual->setEnabled(enabled);
81 connect(PAHStartB, &
QPushButton::clicked,
this, &Ekos::PolarAlignmentAssistant::startPAHProcess);
84 connect(PAHRefreshB, &
QPushButton::clicked,
this, &Ekos::PolarAlignmentAssistant::startPAHRefreshProcess);
86 connect(PAHManualDone, &
QPushButton::clicked,
this, &Ekos::PolarAlignmentAssistant::setPAHSlewDone);
88 hemisphere = KStarsData::Instance()->
geo()->
lat()->
Degrees() > 0 ? NORTH_HEMISPHERE : SOUTH_HEMISPHERE;
90 label_PAHOrigErrorAz->setText(
"Az: ");
91 label_PAHOrigErrorAlt->setText(
"Alt: ");
94 PolarAlignmentAssistant::~PolarAlignmentAssistant()
98 void PolarAlignmentAssistant::showUpdatedError(
bool show)
100 label_PAHUpdatedErrorTotal->setVisible(show);
101 PAHUpdatedErrorTotal->setVisible(show);
102 label_PAHUpdatedErrorAlt->setVisible(show);
103 PAHUpdatedErrorAlt->setVisible(show);
104 label_PAHUpdatedErrorAz->setVisible(show);
105 PAHUpdatedErrorAz->setVisible(show);
108 void PolarAlignmentAssistant::syncMountSpeed()
110 PAHSlewRateCombo->blockSignals(
true);
111 PAHSlewRateCombo->clear();
112 PAHSlewRateCombo->addItems(m_CurrentTelescope->slewRates());
113 const uint16_t configMountSpeed = Options::pAHMountSpeedIndex();
114 if (configMountSpeed < PAHSlewRateCombo->count())
115 PAHSlewRateCombo->setCurrentIndex(configMountSpeed);
118 int currentSlewRateIndex = m_CurrentTelescope->getSlewRate();
119 if (currentSlewRateIndex >= 0)
121 PAHSlewRateCombo->setCurrentIndex(currentSlewRateIndex);
122 Options::setPAHMountSpeedIndex(currentSlewRateIndex);
125 PAHSlewRateCombo->blockSignals(
false);
128 void PolarAlignmentAssistant::setEnabled(
bool enabled)
132 emit PAHEnabled(enabled);
135 PAHWidgets->setToolTip(
QString());
136 FOVDisabledLabel->hide();
140 PAHWidgets->setToolTip(
i18n(
141 "<p>Polar Alignment Helper tool requires the following:</p><p>1. German Equatorial Mount</p><p>2. FOV >"
142 " 0.5 degrees</p><p>For small FOVs, use the Legacy Polar Alignment Tool.</p>"));
143 FOVDisabledLabel->show();
149 void PolarAlignmentAssistant::startSolver()
151 auto profiles = getDefaultAlignOptionsProfiles();
152 auto parameters = profiles.at(Options::solveOptionsProfile());
154 parameters.search_radius = parameters.search_radius * 2;
155 constexpr
double solverTimeout = 10.0;
158 connect(m_Solver.get(), &SolverUtils::done,
this, &PolarAlignmentAssistant::solverDone,
Qt::UniqueConnection);
161 m_Solver->useScale(Options::astrometryUseImageScale(), m_LastPixscale * 0.9, m_LastPixscale * 1.1);
162 m_Solver->usePosition(
true, m_LastRa, m_LastDec);
163 m_Solver->setHealpix(m_IndexToUse, m_HealpixToUse);
164 m_Solver->runSolver(m_ImageData);
167 void PolarAlignmentAssistant::solverDone(
bool timedOut,
bool success,
const FITSImage::Solution &solution,
168 double elapsedSeconds)
170 disconnect(m_Solver.get(), &SolverUtils::done,
this, &PolarAlignmentAssistant::solverDone);
172 if (m_PAHStage != PAH_REFRESH)
175 if (timedOut || !success)
181 constexpr
int MAX_NUM_HEALPIX_FAILURES = 2;
182 if (++m_NumHealpixFailures >= MAX_NUM_HEALPIX_FAILURES)
192 m_Solver->getSolutionHealpix(&m_IndexToUse, &m_HealpixToUse);
197 emit newLog(
i18n(
"Refresh solver timed out: %1s",
QString(
"%L1").arg(elapsedSeconds, 0,
'f', 1)));
198 emit captureAndSolve();
202 emit newLog(
i18n(
"Refresh solver failed: %1s",
QString(
"%L1").arg(elapsedSeconds, 0,
'f', 1)));
203 emit captureAndSolve();
207 m_NumHealpixFailures = 0;
209 const double ra = solution.ra;
210 const double dec = solution.
dec;
211 m_LastRa = solution.ra;
212 m_LastDec = solution.
dec;
213 m_LastOrientation = solution.orientation;
214 m_LastPixscale = solution.pixscale;
216 emit newLog(
QString(
"Refresh solver success %1s: ra %2 dec %3 scale %4")
217 .arg(elapsedSeconds, 0,
'f', 1).arg(ra, 0,
'f', 3)
218 .arg(dec, 0,
'f', 3).arg(solution.pixscale));
221 SkyPoint refreshCoords(ra / 15.0, dec);
222 double azError = 0, altError = 0;
223 if (polarAlign.processRefreshCoords(refreshCoords, m_ImageData->getDateTime(), &azError, &altError))
225 updateRefreshDisplay(azError, altError);
227 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false :
true;
230 m_AlignView->injectWCS(solution.orientation, ra, dec, solution.pixscale, eastToTheRight,
false,
false);
231 updatePlateSolveTriangle(m_ImageData);
234 emit newLog(
QString(
"Could not estimate mount rotation"));
237 emit captureAndSolve();
251 const SkyPoint &originalCoords = polarAlign.getPoint(2);
252 QPointF originalPixel, solutionPixel, altOnlyPixel, dummy;
253 QPointF centerPixel(image->width() / 2, image->height() / 2);
254 if (image->wcsToPixel(originalCoords, originalPixel, dummy) &&
255 image->wcsToPixel(refreshSolution, solutionPixel, dummy) &&
256 image->wcsToPixel(altOnlyRefreshSolution, altOnlyPixel, dummy))
258 m_AlignView->setCorrectionParams(originalPixel, solutionPixel, altOnlyPixel);
259 m_AlignView->setStarCircle(centerPixel);
263 qCDebug(KSTARS_EKOS_ALIGN) <<
"wcs failed";
272 void upArrow(
QPainter *painter,
int w,
int h)
274 const double wCenter = w / 2, lineTop = 0.38, lineBottom = 0.9;
275 const double lineLength = h * (lineBottom - lineTop);
276 painter->
drawRect(wCenter - w * .1, h * lineTop, w * .2, lineLength);
281 void downArrow(
QPainter *painter,
int w,
int h)
283 const double wCenter = w / 2, lineBottom = 0.62, lineTop = 0.1;
284 const double lineLength = h * (lineBottom - lineTop);
285 painter->
drawRect(wCenter - w * .1, h * lineTop, w * .2, lineLength);
290 void leftArrow(
QPainter *painter,
int w,
int h)
292 const double hCenter = h / 2, lineLeft = 0.38, lineRight = 0.9;
293 const double lineLength = w * (lineRight - lineLeft);
294 painter->
drawRect(h * lineLeft, hCenter - h * .1, lineLength, h * .2);
299 void rightArrow(
QPainter *painter,
int w,
int h)
301 const double hCenter = h / 2, lineLeft = .1, lineRight = .62;
302 const double lineLength = w * (lineRight - lineLeft);
303 painter->
drawRect(h * lineLeft, hCenter - h * .1, lineLength, h * .2);
312 void PolarAlignmentAssistant::drawArrows(
double azError,
double altError)
314 constexpr
double minError = 20.0 / 3600.0;
315 double absError = fabs(altError);
319 constexpr
double largeErr = 10.0 / 60.0, smallErr = 1.0 / 60.0, largeSize = 100, smallSize = 20, c1 = 533.33, c2 = 11.111;
322 if (absError > largeErr)
324 else if (absError < smallErr)
326 else size = absError * c1 + c2;
329 altPixmap.fill(
QColor(
"transparent"));
333 if (altError > minError)
334 downArrow(&altPainter, size, size);
335 else if (altError < -minError)
336 upArrow(&altPainter, size, size);
337 arrowAlt->setPixmap(altPixmap);
339 absError = fabs(azError);
340 if (absError > largeErr)
342 else if (absError < smallErr)
344 else size = absError * c1 + c2;
347 azPixmap.fill(
QColor(
"transparent"));
351 if (azError > minError)
352 leftArrow(&azPainter, size, size);
353 else if (azError < -minError)
354 rightArrow(&azPainter, size, size);
355 arrowAz->setPixmap(azPixmap);
358 bool PolarAlignmentAssistant::detectStarsPAHRefresh(
QList<Edge> *stars,
int num,
int x,
int y,
int *starIndex)
364 QVariantMap settings;
365 settings[
"optionsProfileIndex"] = Options::solveOptionsProfile();
366 settings[
"optionsProfileGroup"] =
static_cast<int>(Ekos::AlignProfiles);
367 m_ImageData->setSourceExtractorSettings(settings);
370 m_ImageData->findStars(ALGORITHM_SEP).waitForFinished();
372 QString debugString =
QString(
"PAA Refresh: Detected %1 stars (%2s)")
373 .
arg(m_ImageData->getStarCenters().size()).
arg(timer.
elapsed() / 1000.0, 5,
'f', 3);
374 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
378 std::sort(detectedStars.
begin(), detectedStars.
end(), [](
const Edge * edge1,
const Edge * edge2) ->
bool { return edge1->sum > edge2->sum;});
381 double bestDist = 1e9;
383 for (
int i = 0; i < detectedStars.
size(); i++)
385 double dx = detectedStars[i]->x - x;
386 double dy = detectedStars[i]->y - y;
387 double dist = dx * dx + dy * dy;
395 int starCount = qMin(num, detectedStars.
count());
396 for (
int i = 0; i < starCount; i++)
397 stars->
append(*(detectedStars[i]));
398 if (bestIndex >= starCount)
402 stars->
append(*(detectedStars[bestIndex]));
403 *starIndex = starCount;
407 *starIndex = bestIndex;
409 debugString =
QString(
"PAA Refresh: User's star(%1,%2) is index %3").
arg(x).
arg(y).
arg(*starIndex);
410 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
412 detectedStars.
clear();
414 return stars->
count();
417 void PolarAlignmentAssistant::updateRefreshDisplay(
double azE,
double altE)
419 drawArrows(azE, altE);
420 const double errDegrees = hypot(azE, altE);
421 dms totalError(errDegrees), azError(azE), altError(altE);
422 PAHUpdatedErrorTotal->setText(totalError.toDMSString());
423 PAHUpdatedErrorAlt->setText(altError.toDMSString());
424 PAHUpdatedErrorAz->setText(azError.toDMSString());
426 QString debugString =
QString(
"PAA Refresh(%1): Corrected az: %2 alt: %4 total: %6")
427 .
arg(refreshIteration).
arg(azError.toDMSString())
428 .
arg(altError.toDMSString()).
arg(totalError.toDMSString());
429 emit newLog(debugString);
430 emit updatedErrorsChanged(totalError.Degrees(), azError.Degrees(), altError.Degrees());
433 void PolarAlignmentAssistant::processPAHRefresh()
435 m_AlignView->setStarCircle();
436 PAHUpdatedErrorTotal->clear();
437 PAHIteration->clear();
438 PAHUpdatedErrorAlt->clear();
439 PAHUpdatedErrorAz->clear();
442 PAHIteration->setText(
QString(
"Image %1").arg(imageNumber));
444 if (Options::pAHRefreshAlgorithm() == PLATE_SOLVE_ALGORITHM)
454 if ((Options::pAHRefreshAlgorithm() == MOVE_STAR_UPDATE_ERR_ALGORITHM) || (refreshIteration == 0))
456 constexpr
int MIN_PAH_REFRESH_STARS = 10;
462 int clickedStarIndex;
463 detectStarsPAHRefresh(&stars, 100, correctionFrom.x(), correctionFrom.y(), &clickedStarIndex);
464 if (clickedStarIndex < 0)
466 debugString =
QString(
"PAA Refresh(%1): Didn't find the clicked star near %2,%3")
467 .
arg(refreshIteration).
arg(correctionFrom.x()).
arg(correctionFrom.y());
468 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
470 emit newAlignTableResult(Align::ALIGN_RESULT_FAILED);
471 emit captureAndSolve();
475 debugString =
QString(
"PAA Refresh(%1): Refresh star(%2,%3) is index %4 with offset %5 %6")
476 .
arg(refreshIteration + 1).
arg(correctionFrom.x(), 4,
'f', 0)
477 .
arg(correctionFrom.y(), 4,
'f', 0).
arg(clickedStarIndex)
478 .
arg(stars[clickedStarIndex].x - correctionFrom.x(), 4,
'f', 0)
479 .
arg(stars[clickedStarIndex].y - correctionFrom.y(), 4,
'f', 0);
480 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
482 if (stars.
size() > MIN_PAH_REFRESH_STARS)
488 if (refreshIteration++ == 0)
492 starCorrespondencePAH.initialize(stars, clickedStarIndex);
493 if (clickedStarIndex >= 0)
495 setupCorrectionGraphics(
QPointF(stars[clickedStarIndex].x, stars[clickedStarIndex].y));
496 emit newCorrectionVector(
QLineF(correctionFrom, correctionTo));
497 emit newFrame(m_AlignView);
506 starCorrespondencePAH.find(stars, 200.0, &starMap,
false, 0.40);
510 for (
int i = 0; i < starMap.
size(); ++i)
512 if (starMap[i] == starCorrespondencePAH.guideStar())
514 dx = stars[i].x - correctionFrom.x();
515 dy = stars[i].y - correctionFrom.y();
523 for (
int i = 0; i < starMap.
size(); ++i)
525 if (starMap[i] != -1)
528 debugString =
QString(
"PAA Refresh(%1): starMap %2").
arg(refreshIteration).
arg(allOnes ?
"ALL -1's" :
"not all -1's");
529 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
536 m_AlignView->setStarCircle(
QPointF(stars[starIndex].x, stars[starIndex].y));
537 debugString =
QString(
"PAA Refresh(%1): User's star is now at %2,%3, with movement = %4,%5").
arg(refreshIteration)
538 .
arg(stars[starIndex].x, 4,
'f', 0).
arg(stars[starIndex].y, 4,
'f', 0).
arg(dx).
arg(dy);
539 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
542 if (polarAlign.pixelError(m_AlignView->keptImage(),
QPointF(stars[starIndex].x, stars[starIndex].y),
543 correctionTo, &azE, &altE))
545 updateRefreshDisplay(azE, altE);
546 debugString =
QString(
"PAA Refresh(%1): %2,%3 --> %4,%5 @ %6,%7")
547 .
arg(refreshIteration).
arg(correctionFrom.x(), 4,
'f', 0).
arg(correctionFrom.y(), 4,
'f', 0)
548 .
arg(correctionTo.x(), 4,
'f', 0).
arg(correctionTo.y(), 4,
'f', 0)
549 .
arg(stars[starIndex].x, 4,
'f', 0).
arg(stars[starIndex].y, 4,
'f', 0);
550 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
554 debugString =
QString(
"PAA Refresh(%1): pixelError failed to estimate the remaining correction").
arg(refreshIteration);
555 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
560 if (refreshIteration > 1)
562 debugString =
QString(
"PAA Refresh(%1): Didn't find the user's star").
arg(refreshIteration);
563 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
569 debugString =
QString(
"PAA Refresh(%1): Too few stars detected (%2)").
arg(refreshIteration).
arg(stars.
size());
570 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
574 emit captureAndSolve();
577 bool PolarAlignmentAssistant::processSolverFailure()
579 if ((m_PAHStage == PAH_FIRST_CAPTURE ||
580 m_PAHStage == PAH_SECOND_CAPTURE ||
581 m_PAHStage == PAH_THIRD_CAPTURE ||
582 m_PAHStage == PAH_FIRST_SOLVE ||
583 m_PAHStage == PAH_SECOND_SOLVE ||
584 m_PAHStage == PAH_THIRD_SOLVE)
585 && ++m_PAHRetrySolveCounter < 4)
587 emit captureAndSolve();
591 if (m_PAHStage != PAH_IDLE)
593 emit newLog(
i18n(
"PAA: Stopping, solver failed too many times."));
600 void PolarAlignmentAssistant::setPAHStage(PAHStage stage)
602 if (stage != m_PAHStage)
605 polarAlignWidget->updatePAHStage(stage);
606 emit newPAHStage(m_PAHStage);
610 void PolarAlignmentAssistant::processMountRotation(
const dms &ra,
double settleDuration)
616 PAHStage nextCapture;
619 if (m_PAHStage == PAH_FIRST_ROTATE)
621 rotProgressMessage =
"First mount rotation remaining degrees:";
622 rotDoneMessage =
i18n(
"Mount first rotation is complete.");
623 nextCapture = PAH_SECOND_CAPTURE;
624 nextSettle = PAH_FIRST_SETTLE;
626 else if (m_PAHStage == PAH_SECOND_ROTATE)
628 rotProgressMessage =
"Second mount rotation remaining degrees:";
629 rotDoneMessage =
i18n(
"Mount second rotation is complete.");
630 nextCapture = PAH_THIRD_CAPTURE;
631 nextSettle = PAH_SECOND_SETTLE;
635 if (m_PAHStage == PAH_FIRST_ROTATE || m_PAHStage == PAH_SECOND_ROTATE)
638 if(!PAHManual->isChecked())
640 qCDebug(KSTARS_EKOS_ALIGN) << rotProgressMessage << deltaAngle;
641 if (deltaAngle <= PAH_ROTATION_THRESHOLD)
643 m_CurrentTelescope->StopWE();
644 emit newLog(rotDoneMessage);
646 if (settleDuration <= 0)
648 setPAHStage(nextCapture);
649 updateDisplay(m_PAHStage, getPAHMessage());
653 setPAHStage(nextSettle);
654 updateDisplay(m_PAHStage, getPAHMessage());
656 emit newLog(
i18n(
"Settling..."));
659 setPAHStage(nextCapture);
660 updateDisplay(m_PAHStage, getPAHMessage());
665 else if (deltaAngle > PAHRotationSpin->value() * 1.25)
667 m_CurrentTelescope->Abort();
668 emit newLog(
i18n(
"Mount aborted. Reverse RA axis direction and try again."));
676 bool PolarAlignmentAssistant::checkPAHForMeridianCrossing()
679 double hourAngle = m_CurrentTelescope->hourAngle().Degrees();
680 while (hourAngle < -180)
682 while (hourAngle > 180)
684 double ra = 0,
dec = 0;
685 m_CurrentTelescope->getEqCoords(&ra, &dec);
692 bool nearThePole = fabs(dec) > 88;
696 double degreesPerSlew = PAHRotationSpin->value();
697 bool closeToMeridian = fabs(hourAngle) < 2.0 * degreesPerSlew;
698 bool goingWest = PAHDirectionCombo->currentIndex() == 0;
703 bool wouldCrossMeridian =
704 ((m_CurrentTelescope->pierSide() == ISD::Mount::PIER_EAST && !goingWest && closeToMeridian) ||
705 (m_CurrentTelescope->pierSide() == ISD::Mount::PIER_WEST && goingWest && closeToMeridian) ||
706 (m_CurrentTelescope->pierSide() == ISD::Mount::PIER_UNKNOWN && closeToMeridian));
708 return wouldCrossMeridian;
711 void PolarAlignmentAssistant::updateDisplay(PAHStage stage,
const QString &
message)
715 case PAH_FIRST_ROTATE:
716 case PAH_SECOND_ROTATE:
717 if (PAHManual->isChecked())
719 polarAlignWidget->updatePAHStage(stage);
720 PAHWidgets->setCurrentWidget(PAHManualRotatePage);
721 manualRotateText->setText(
message);
726 case PAH_FIRST_CAPTURE:
727 case PAH_SECOND_CAPTURE:
728 case PAH_THIRD_CAPTURE:
729 case PAH_FIRST_SOLVE:
730 case PAH_SECOND_SOLVE:
731 case PAH_THIRD_SOLVE:
732 polarAlignWidget->updatePAHStage(stage);
733 PAHWidgets->setCurrentWidget(PAHMessagePage);
734 PAHMessageText->setText(
message);
744 void PolarAlignmentAssistant::startPAHProcess()
746 qCInfo(KSTARS_EKOS_ALIGN) <<
QString(
"Starting Polar Alignment Assistant process %1 ...").
arg(PAA_VERSION);
748 auto executePAH = [ this ]()
750 setPAHStage(PAH_FIRST_CAPTURE);
752 if (Options::limitedResourcesMode())
753 emit newLog(
i18n(
"Warning: Equatorial Grid Lines will not be drawn due to limited resources mode."));
755 if (m_CurrentTelescope->hasAlignmentModel())
757 emit newLog(
i18n(
"Clearing mount Alignment Model..."));
758 m_CurrentTelescope->clearAlignmentModel();
762 m_CurrentTelescope->UnPark();
765 if (m_CurrentTelescope->canControlTrack() && m_CurrentTelescope->isTracking() ==
false)
766 m_CurrentTelescope->setTrackEnabled(
true);
768 PAHStartB->setEnabled(
false);
769 PAHStopB->setEnabled(
true);
771 PAHUpdatedErrorTotal->clear();
772 PAHUpdatedErrorAlt->clear();
773 PAHUpdatedErrorAz->clear();
774 PAHOrigErrorTotal->clear();
775 PAHOrigErrorAlt->clear();
776 PAHOrigErrorAz->clear();
777 PAHIteration->setText(
"");
779 updateDisplay(m_PAHStage, getPAHMessage());
783 m_PAHRetrySolveCounter = 0;
784 emit captureAndSolve();
789 if (checkPAHForMeridianCrossing())
798 KSMessageBox::Instance()->warningContinueCancel(
i18n(
"This could cause the telescope to cross the meridian."),
799 i18n(
"Warning"), 15);
805 void PolarAlignmentAssistant::stopPAHProcess()
807 if (m_PAHStage == PAH_IDLE)
809 if (m_PAHStage == PAH_REFRESH)
811 setPAHStage(PAH_POST_REFRESH);
812 polarAlignWidget->updatePAHStage(m_PAHStage);
814 qCInfo(KSTARS_EKOS_ALIGN) <<
"Stopping Polar Alignment Assistant process...";
818 i18n(
"Are you sure you want to stop the polar alignment process?"),
820 "restart_PAA_process_dialog") == KMessageBox::No)
823 if (m_CurrentTelescope && m_CurrentTelescope->isInMotion())
824 m_CurrentTelescope->Abort();
826 setPAHStage(PAH_IDLE);
827 polarAlignWidget->updatePAHStage(m_PAHStage);
829 PAHStartB->setEnabled(
true);
830 PAHStopB->setEnabled(
false);
831 PAHRefreshB->setEnabled(
true);
832 PAHWidgets->setCurrentWidget(PAHIntroPage);
833 emit newPAHMessage(introText->text());
835 m_AlignView->reset();
836 m_AlignView->setRefreshEnabled(
false);
838 emit newFrame(m_AlignView);
839 disconnect(m_AlignView.get(), &AlignView::trackingStarSelected,
this, &Ekos::PolarAlignmentAssistant::setPAHCorrectionOffset);
840 disconnect(m_AlignView.get(), &AlignView::newCorrectionVector,
this, &Ekos::PolarAlignmentAssistant::newCorrectionVector);
842 if (Options::pAHAutoPark())
844 m_CurrentTelescope->Park();
845 emit newLog(
i18n(
"Parking the mount..."));
849 void PolarAlignmentAssistant::rotatePAH()
851 double TargetDiffRA = PAHRotationSpin->value();
852 bool westMeridian = PAHDirectionCombo->currentIndex() == 0;
864 if (PAHManual->isChecked())
869 const SkyPoint telescopeCoord = m_CurrentTelescope->currentCoordinates();
872 dms newTelescopeRA = (telescopeCoord.
ra() +
dms(TargetDiffRA)).reduce();
874 targetPAH.setRA(newTelescopeRA);
875 targetPAH.setDec(telescopeCoord.
dec());
879 if (PAHSlewRateCombo->currentIndex() >= 0)
880 m_CurrentTelescope->setSlewRate(PAHSlewRateCombo->currentIndex());
882 m_CurrentTelescope->MoveWE(westMeridian ? ISD::Mount::MOTION_WEST : ISD::Mount::MOTION_EAST,
883 ISD::Mount::MOTION_START);
885 emit newLog(
i18n(
"Please wait until mount completes rotating to RA (%1) DE (%2)", targetPAH.ra().toHMSString(),
886 targetPAH.dec().toDMSString()));
889 void PolarAlignmentAssistant::setupCorrectionGraphics(
const QPointF &pixel)
891 polarAlign.refreshSolution(&refreshSolution, &altOnlyRefreshSolution);
899 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionAltTo,
true))
901 qCInfo(KSTARS_EKOS_ALIGN) <<
QString(
i18n(
"PAA: Failed to findCorrectedPixel."));
905 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionTo))
907 qCInfo(KSTARS_EKOS_ALIGN) <<
QString(
i18n(
"PAA: Failed to findCorrectedPixel."));
910 QString debugString =
QString(
"PAA: Correction: %1,%2 --> %3,%4 (alt only %5,%6)")
911 .
arg(pixel.
x(), 4,
'f', 0).
arg(pixel.
y(), 4,
'f', 0)
912 .
arg(correctionTo.x(), 4,
'f', 0).
arg(correctionTo.y(), 4,
'f', 0)
913 .
arg(correctionAltTo.x(), 4,
'f', 0).
arg(correctionAltTo.y(), 4,
'f', 0);
914 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
915 correctionFrom = pixel;
917 if (Options::pAHRefreshAlgorithm() == PLATE_SOLVE_ALGORITHM)
918 updatePlateSolveTriangle(imageData);
920 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
926 bool PolarAlignmentAssistant::calculatePAHError()
929 m_AlignView->holdOnToImage();
931 if (!polarAlign.findAxis())
933 emit newLog(
i18n(
"PAA: Failed to find RA Axis center."));
938 double azimuthError, altitudeError;
939 polarAlign.calculateAzAltError(&azimuthError, &altitudeError);
940 drawArrows(azimuthError, altitudeError);
941 dms polarError(hypot(altitudeError, azimuthError));
942 dms azError(azimuthError), altError(altitudeError);
944 if (m_AlignView->isEQGridShown() ==
false && !Options::limitedResourcesMode())
945 m_AlignView->toggleEQGrid();
948 .
arg(polarError.toDMSString()).
arg(azError.toDMSString())
949 .
arg(altError.toDMSString());
950 emit newLog(
QString(
"Polar Alignment Error: %1").arg(msg));
952 polarAlign.setMaxPixelSearchRange(polarError.Degrees() + 1);
955 PAHOrigErrorTotal->setText(polarError.toDMSString());
956 PAHOrigErrorAlt->setText(altError.toDMSString());
957 PAHOrigErrorAz->setText(azError.toDMSString());
959 setupCorrectionGraphics(
QPointF(m_ImageData->width() / 2, m_ImageData->height() / 2));
962 SkyPoint CP(0, (hemisphere == NORTH_HEMISPHERE) ? 90 : -90);
963 QPointF imagePoint, celestialPolePoint;
964 m_ImageData->wcsToPixel(CP, celestialPolePoint, imagePoint);
965 if (m_ImageData->contains(celestialPolePoint))
967 m_AlignView->setCelestialPole(celestialPolePoint);
969 if (polarAlign.findCorrectedPixel(m_ImageData, celestialPolePoint, &raAxis))
970 m_AlignView->setRaAxis(raAxis);
973 connect(m_AlignView.get(), &AlignView::trackingStarSelected,
this, &Ekos::PolarAlignmentAssistant::setPAHCorrectionOffset);
974 emit polarResultUpdated(
QLineF(correctionFrom, correctionTo), polarError.Degrees(), azError.Degrees(), altError.Degrees());
976 connect(m_AlignView.get(), &AlignView::newCorrectionVector,
this, &Ekos::PolarAlignmentAssistant::newCorrectionVector,
978 syncCorrectionVector();
979 emit newFrame(m_AlignView);
984 void PolarAlignmentAssistant::syncCorrectionVector()
986 if (Options::pAHRefreshAlgorithm() == PLATE_SOLVE_ALGORITHM)
988 emit newCorrectionVector(
QLineF(correctionFrom, correctionTo));
989 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
992 void PolarAlignmentAssistant::setPAHCorrectionOffsetPercentage(
double dx,
double dy)
994 double x = dx * m_AlignView->zoomedWidth();
995 double y = dy * m_AlignView->zoomedHeight();
996 setPAHCorrectionOffset(
static_cast<int>(round(x)),
static_cast<int>(round(y)));
999 void PolarAlignmentAssistant::setPAHCorrectionOffset(
int x,
int y)
1001 if (m_PAHStage == PAH_REFRESH)
1003 emit newLog(
i18n(
"Polar-alignment star cannot be updated during refresh phase as it might affect error measurements."));
1007 setupCorrectionGraphics(
QPointF(x, y));
1008 emit newCorrectionVector(
QLineF(correctionFrom, correctionTo));
1009 emit newFrame(m_AlignView);
1013 void PolarAlignmentAssistant::setPAHSlewDone()
1015 emit newPAHMessage(
"Manual slew done.");
1018 case PAH_FIRST_ROTATE :
1019 setPAHStage(PAH_SECOND_CAPTURE);
1020 emit newLog(
i18n(
"First manual rotation done."));
1021 updateDisplay(m_PAHStage, getPAHMessage());
1023 case PAH_SECOND_ROTATE :
1024 setPAHStage(PAH_THIRD_CAPTURE);
1025 emit newLog(
i18n(
"Second manual rotation done."));
1026 updateDisplay(m_PAHStage, getPAHMessage());
1035 void PolarAlignmentAssistant::startPAHRefreshProcess()
1037 qCInfo(KSTARS_EKOS_ALIGN) <<
"Starting Polar Alignment Assistant refreshing...";
1039 refreshIteration = 0;
1042 m_HealpixToUse = -1;
1043 m_NumHealpixFailures = 0;
1045 setPAHStage(PAH_REFRESH);
1046 polarAlignWidget->updatePAHStage(m_PAHStage);
1047 refreshText->setText(getPAHMessage());
1049 PAHRefreshB->setEnabled(
false);
1052 if (m_AlignView->isEQGridShown())
1053 m_AlignView->toggleEQGrid();
1055 m_AlignView->setRefreshEnabled(
true);
1057 Options::setAstrometrySolverWCS(
false);
1058 Options::setAutoWCS(
false);
1061 emit captureAndSolve();
1064 void PolarAlignmentAssistant::processPAHStage(
double orientation,
double ra,
double dec,
double pixscale,
1065 bool eastToTheRight)
1067 if (m_PAHStage == PAH_FIND_CP)
1070 i18n(
"Mount is synced to celestial pole. You can now continue Polar Alignment Assistant procedure."));
1071 setPAHStage(PAH_FIRST_CAPTURE);
1072 polarAlignWidget->updatePAHStage(m_PAHStage);
1076 if (m_PAHStage == PAH_FIRST_SOLVE || m_PAHStage == PAH_SECOND_SOLVE || m_PAHStage == PAH_THIRD_SOLVE)
1081 m_LastOrientation = orientation;
1082 m_LastPixscale = pixscale;
1084 bool doWcs = (m_PAHStage == PAH_THIRD_SOLVE) || !Options::limitedResourcesMode();
1087 emit newLog(
i18n(
"Please wait while WCS data is processed..."));
1088 PAHMessageText->setText(
1089 m_PAHStage == PAH_FIRST_SOLVE
1090 ?
"Calculating WCS for the first image...</p>"
1091 : (m_PAHStage == PAH_SECOND_SOLVE ?
"Calculating WCS for the second image...</p>"
1092 :
"Calculating WCS for the third image...</p>"));
1094 connect(m_AlignView.get(), &AlignView::wcsToggled,
this, &Ekos::PolarAlignmentAssistant::setWCSToggled,
Qt::UniqueConnection);
1095 m_AlignView->injectWCS(orientation, ra, dec, pixscale, eastToTheRight, doWcs);
1100 QJsonObject PolarAlignmentAssistant::getPAHSettings()
const
1104 settings.
insert(
"mountDirection", PAHDirectionCombo->currentIndex());
1105 settings.
insert(
"mountSpeed", PAHSlewRateCombo->currentIndex());
1106 settings.
insert(
"mountRotation", PAHRotationSpin->value());
1107 settings.
insert(
"refresh", PAHExposure->value());
1108 settings.
insert(
"manualslew", PAHManual->isChecked());
1113 void PolarAlignmentAssistant::setPAHSettings(
const QJsonObject &settings)
1115 PAHDirectionCombo->setCurrentIndex(settings[
"mountDirection"].toInt(0));
1116 PAHRotationSpin->setValue(settings[
"mountRotation"].toInt(30));
1117 PAHExposure->setValue(settings[
"refresh"].toDouble(2));
1118 if (settings.
contains(
"mountSpeed"))
1119 PAHSlewRateCombo->setCurrentIndex(settings[
"mountSpeed"].toInt(0));
1120 PAHManual->setChecked(settings[
"manualslew"].toBool(
false));
1125 m_ImageData = image;
1127 if (m_PAHStage == PAH_FIRST_CAPTURE)
1128 setPAHStage(PAH_FIRST_SOLVE);
1129 else if (m_PAHStage == PAH_SECOND_CAPTURE)
1130 setPAHStage(PAH_SECOND_SOLVE);
1131 else if (m_PAHStage == PAH_THIRD_CAPTURE)
1132 setPAHStage(PAH_THIRD_SOLVE);
1135 updateDisplay(m_PAHStage, getPAHMessage());
1138 void PolarAlignmentAssistant::setWCSToggled(
bool result)
1140 emit newLog(
i18n(
"WCS data processing is complete."));
1142 disconnect(m_AlignView.get(), &AlignView::wcsToggled,
this, &Ekos::PolarAlignmentAssistant::setWCSToggled);
1144 if (m_PAHStage == PAH_FIRST_CAPTURE || m_PAHStage == PAH_FIRST_SOLVE)
1147 if (result ==
false && m_AlignInstance->wcsSynced() ==
true)
1149 emit newLog(
i18n(
"WCS info is now valid. Capturing next frame..."));
1150 emit captureAndSolve();
1155 polarAlign.addPoint(m_ImageData);
1157 setPAHStage(PAH_FIRST_ROTATE);
1158 auto msg = getPAHMessage();
1159 if (PAHManual->isChecked())
1161 msg =
QString(
"Please rotate your mount about %1 deg in RA")
1162 .
arg(PAHRotationSpin->value());
1165 updateDisplay(m_PAHStage, msg);
1169 else if (m_PAHStage == PAH_SECOND_CAPTURE || m_PAHStage == PAH_SECOND_SOLVE)
1171 setPAHStage(PAH_SECOND_ROTATE);
1172 auto msg = getPAHMessage();
1174 if (PAHManual->isChecked())
1176 msg =
QString(
"Please rotate your mount about %1 deg in RA")
1177 .
arg(PAHRotationSpin->value());
1180 updateDisplay(m_PAHStage, msg);
1182 polarAlign.addPoint(m_ImageData);
1186 else if (m_PAHStage == PAH_THIRD_CAPTURE || m_PAHStage == PAH_THIRD_SOLVE)
1189 if (result ==
false)
1191 emit newLog(
i18n(
"Failed to process World Coordinate System: %1. Try again.", m_ImageData->getLastError()));
1195 polarAlign.addPoint(m_ImageData);
1199 if (calculatePAHError())
1201 setPAHStage(PAH_STAR_SELECT);
1202 polarAlignWidget->updatePAHStage(m_PAHStage);
1203 PAHWidgets->setCurrentWidget(PAHRefreshPage);
1204 refreshText->setText(getPAHMessage());
1205 emit newPAHMessage(getPAHMessage());
1209 emit newLog(
i18n(
"PAA: Failed to find the RA axis. Quitting."));
1215 void PolarAlignmentAssistant::setMountStatus(ISD::Mount::Status newState)
1219 case ISD::Mount::MOUNT_PARKING:
1220 case ISD::Mount::MOUNT_SLEWING:
1221 case ISD::Mount::MOUNT_MOVING:
1222 PAHStartB->setEnabled(
false);
1226 if (m_PAHStage == PAH_IDLE)
1227 PAHStartB->setEnabled(
true);
1232 QString PolarAlignmentAssistant::getPAHMessage()
const
1238 return introText->text();
1239 case PAH_FIRST_CAPTURE:
1240 return "<p>The assistant requires three images to find a solution. Ekos is now capturing the first image...</p>";
1241 case PAH_FIRST_SOLVE:
1242 return "<p>Solving the <i>first</i> image...</p>";
1243 case PAH_FIRST_ROTATE:
1244 return "<p>Executing the <i>first</i> mount rotation...</p>";
1245 case PAH_FIRST_SETTLE:
1246 return "<p>Settling after the <i>first</i> mount rotation.</p>";
1247 case PAH_SECOND_SETTLE:
1248 return "<p>Settling after the <i>second</i> mount rotation.</p>";
1249 case PAH_SECOND_CAPTURE:
1250 return "<p>Capturing the second image...</p>";
1251 case PAH_SECOND_SOLVE:
1252 return "<p>Solving the <i>second</i> image...</p>";
1253 case PAH_SECOND_ROTATE:
1254 return "<p>Executing the <i>second</i> mount rotation...</p>";
1255 case PAH_THIRD_CAPTURE:
1256 return "<p>Capturing the <i>third</i> and final image...</p>";
1257 case PAH_THIRD_SOLVE:
1258 return "<p>Solving the <i>third</i> image...</p>";
1259 case PAH_STAR_SELECT:
1260 if (Options::pAHRefreshAlgorithm() == PLATE_SOLVE_ALGORITHM)
1261 return "<p>Choose your exposure time & select an adjustment method. Then click <i>refresh</i> to begin adjustments.</p>";
1263 return "<p>Choose your exposure time & select an adjustment method. Click <i>Refresh</i> to begin.</p><p>Correction triangle is plotted above. <i>Zoom in and select a bright star</i> to reposition the correction vector. Use the <i>MoveStar & Calc Error</i> method to estimate the remaining error.</p>";
1265 if (Options::pAHRefreshAlgorithm() == PLATE_SOLVE_ALGORITHM)
1266 return "<p>Adjust mount's <i>Altitude and Azimuth knobs</i> to reduce the polar alignment error.</p><p>Be patient, plate solving can be affected by knob movement. Consider using results after 2 images. Click <i>Stop</i> when the you're finished.</p>";
1268 return "<p>Adjust mount's <i>Altitude knob</i> to move the star along the yellow line, then adjust the <i>Azimuth knob</i> to move it along the Green line until the selected star is centered within the crosshair.</p><p>Click <i>Stop</i> when the star is centered.</p>";
1269 case PAH_POST_REFRESH:
1276 void PolarAlignmentAssistant::setPAHRefreshAlgorithm(PAHRefreshAlgorithm value)
1280 if ((m_PAHStage == PAH_REFRESH) && refreshIteration > 0 && (value != PLATE_SOLVE_ALGORITHM)
1281 && !starCorrespondencePAH.size())
1283 PAHRefreshAlgorithmCombo->setCurrentIndex(PLATE_SOLVE_ALGORITHM);
1284 Options::setPAHRefreshAlgorithm(PLATE_SOLVE_ALGORITHM);
1285 emit newLog(
i18n(
"Cannot change to MoveStar algorithm once refresh has begun"));
1288 Options::setPAHRefreshAlgorithm(value);
1289 if (m_PAHStage == PAH_REFRESH || m_PAHStage == PAH_STAR_SELECT)
1291 refreshText->setText(getPAHMessage());
1292 emit newPAHMessage(getPAHMessage());
1294 showUpdatedError((Options::pAHRefreshAlgorithm() == PLATE_SOLVE_ALGORITHM) ||
1295 (Options::pAHRefreshAlgorithm() == MOVE_STAR_UPDATE_ERR_ALGORITHM));
1296 if (Options::pAHRefreshAlgorithm() == PLATE_SOLVE_ALGORITHM)
1297 updatePlateSolveTriangle(m_ImageData);
1299 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);