8#include "polaralignmentassistant.h"
13#include "kstarsdata.h"
14#include "ksmessagebox.h"
15#include "ekos/auxiliary/stellarsolverprofile.h"
16#include "ekos/auxiliary/solverutils.h"
18#include "polaralignwidget.h"
19#include <ekos_align_debug.h>
21#define PAA_VERSION "v3.0"
28 {PAH_IDLE,
ki18n(
"Idle")},
29 {PAH_FIRST_CAPTURE,
ki18n(
"First Capture")},
30 {PAH_FIRST_SOLVE,
ki18n(
"First Solve")},
31 {PAH_FIND_CP,
ki18n(
"Finding CP")},
32 {PAH_FIRST_ROTATE,
ki18n(
"First Rotation")},
33 {PAH_FIRST_SETTLE,
ki18n(
"First Settle")},
34 {PAH_SECOND_CAPTURE,
ki18n(
"Second Capture")},
35 {PAH_SECOND_SOLVE,
ki18n(
"Second Solve")},
36 {PAH_SECOND_ROTATE,
ki18n(
"Second Rotation")},
37 {PAH_SECOND_SETTLE,
ki18n(
"Second Settle")},
38 {PAH_THIRD_CAPTURE,
ki18n(
"Third Capture")},
39 {PAH_THIRD_SOLVE,
ki18n(
"Third Solve")},
40 {PAH_STAR_SELECT,
ki18n(
"Select Star")},
41 {PAH_REFRESH,
ki18n(
"Refreshing")},
42 {PAH_POST_REFRESH,
ki18n(
"Refresh Complete")},
48 polarAlignWidget =
new PolarAlignWidget();
49 mainPALayout->insertWidget(0, polarAlignWidget);
51 m_AlignInstance = parent;
54 showUpdatedError((pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM) ||
55 (pAHRefreshAlgorithm->currentIndex() == MOVE_STAR_UPDATE_ERR_ALGORITHM));
59 setPAHRefreshAlgorithm(
static_cast<RefreshAlgorithm
>(index));
61 starCorrespondencePAH.reset();
64 PAHWidgets->setCurrentWidget(PAHIntroPage);
65 connect(
this, &PolarAlignmentAssistant::PAHEnabled, [&](
bool enabled)
67 PAHStartB->setEnabled(enabled);
68 directionLabel->setEnabled(enabled);
69 pAHDirection->setEnabled(enabled);
70 pAHRotation->setEnabled(enabled);
71 pAHMountSpeed->setEnabled(enabled);
72 pAHManualSlew->setEnabled(enabled);
81 hemisphere = KStarsData::Instance()->
geo()->
lat()->
Degrees() > 0 ? NORTH_HEMISPHERE : SOUTH_HEMISPHERE;
83 label_PAHOrigErrorAz->setText(
"Az: ");
84 label_PAHOrigErrorAlt->setText(
"Alt: ");
87PolarAlignmentAssistant::~PolarAlignmentAssistant()
91void PolarAlignmentAssistant::showUpdatedError(
bool show)
93 label_PAHUpdatedErrorTotal->setVisible(show);
94 PAHUpdatedErrorTotal->setVisible(show);
95 label_PAHUpdatedErrorAlt->setVisible(show);
96 PAHUpdatedErrorAlt->setVisible(show);
97 label_PAHUpdatedErrorAz->setVisible(show);
98 PAHUpdatedErrorAz->setVisible(show);
101void PolarAlignmentAssistant::syncMountSpeed(
const QString &speed)
103 pAHMountSpeed->blockSignals(
true);
104 pAHMountSpeed->clear();
105 pAHMountSpeed->addItems(m_CurrentTelescope->slewRates());
106 pAHMountSpeed->setCurrentText(speed);
107 pAHMountSpeed->blockSignals(
false);
110void PolarAlignmentAssistant::setEnabled(
bool enabled)
114 emit PAHEnabled(enabled);
117 PAHWidgets->setToolTip(
QString());
118 FOVDisabledLabel->hide();
122 PAHWidgets->setToolTip(
i18n(
"<p>Polar Alignment tool requires a German Equatorial Mount.</p>"));
123 FOVDisabledLabel->show();
129void PolarAlignmentAssistant::startSolver()
131 auto profiles = getDefaultAlignOptionsProfiles();
132 auto parameters = profiles.at(Options::solveOptionsProfile());
134 parameters.search_radius = parameters.search_radius * 2;
135 constexpr double solverTimeout = 10.0;
141 m_Solver->useScale(Options::astrometryUseImageScale(), m_LastPixscale * 0.9, m_LastPixscale * 1.1);
142 m_Solver->usePosition(
true, m_LastRa, m_LastDec);
143 m_Solver->setHealpix(m_IndexToUse, m_HealpixToUse);
144 m_Solver->runSolver(m_ImageData);
147void PolarAlignmentAssistant::solverDone(
bool timedOut,
bool success,
const FITSImage::Solution &solution,
148 double elapsedSeconds)
150 disconnect(m_Solver.get(), &SolverUtils::done,
this, &PolarAlignmentAssistant::solverDone);
152 if (m_PAHStage != PAH_REFRESH)
155 if (timedOut || !success)
161 constexpr int MAX_NUM_HEALPIX_FAILURES = 2;
162 if (++m_NumHealpixFailures >= MAX_NUM_HEALPIX_FAILURES)
172 m_Solver->getSolutionHealpix(&m_IndexToUse, &m_HealpixToUse);
177 emit newLog(
i18n(
"Refresh solver timed out: %1s",
QString(
"%L1").arg(elapsedSeconds, 0,
'f', 1)));
178 emit captureAndSolve();
182 emit newLog(
i18n(
"Refresh solver failed: %1s",
QString(
"%L1").arg(elapsedSeconds, 0,
'f', 1)));
183 emit updatedErrorsChanged(-1, -1, -1);
184 emit captureAndSolve();
188 m_NumHealpixFailures = 0;
190 const double ra = solution.ra;
191 const double dec = solution.dec;
192 m_LastRa = solution.ra;
193 m_LastDec = solution.dec;
194 m_LastOrientation = solution.orientation;
195 m_LastPixscale = solution.pixscale;
197 emit newLog(
QString(
"Refresh solver success %1s: ra %2 dec %3 scale %4")
198 .arg(elapsedSeconds, 0,
'f', 1).arg(ra, 0,
'f', 3)
199 .arg(dec, 0,
'f', 3).arg(solution.pixscale));
202 SkyPoint refreshCoords(ra / 15.0, dec);
203 double azError = 0, altError = 0;
204 if (polarAlign.processRefreshCoords(refreshCoords, m_ImageData->getDateTime(), &azError, &altError))
206 updateRefreshDisplay(azError, altError);
208 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false :
true;
211 m_AlignView->injectWCS(solution.orientation, ra, dec, solution.pixscale, eastToTheRight,
false);
212 updatePlateSolveTriangle(m_ImageData);
215 emit newLog(
QString(
"Could not estimate mount rotation"));
218 emit captureAndSolve();
232 const SkyPoint &originalCoords = polarAlign.getPoint(2);
233 QPointF originalPixel, solutionPixel, altOnlyPixel, dummy;
234 QPointF centerPixel(image->width() / 2, image->height() / 2);
235 if (image->wcsToPixel(originalCoords, originalPixel, dummy) &&
236 image->wcsToPixel(refreshSolution, solutionPixel, dummy) &&
237 image->wcsToPixel(altOnlyRefreshSolution, altOnlyPixel, dummy))
239 m_AlignView->setCorrectionParams(originalPixel, solutionPixel, altOnlyPixel);
240 m_AlignView->setStarCircle(centerPixel);
244 qCDebug(KSTARS_EKOS_ALIGN) <<
"wcs failed";
253void upArrow(
QPainter *painter,
int w,
int h)
255 const double wCenter = w / 2, lineTop = 0.38, lineBottom = 0.9;
256 const double lineLength = h * (lineBottom - lineTop);
257 painter->
drawRect(wCenter - w * .1, h * lineTop, w * .2, lineLength);
262void downArrow(
QPainter *painter,
int w,
int h)
264 const double wCenter = w / 2, lineBottom = 0.62, lineTop = 0.1;
265 const double lineLength = h * (lineBottom - lineTop);
266 painter->
drawRect(wCenter - w * .1, h * lineTop, w * .2, lineLength);
271void leftArrow(
QPainter *painter,
int w,
int h)
273 const double hCenter = h / 2, lineLeft = 0.38, lineRight = 0.9;
274 const double lineLength = w * (lineRight - lineLeft);
275 painter->
drawRect(h * lineLeft, hCenter - h * .1, lineLength, h * .2);
280void rightArrow(
QPainter *painter,
int w,
int h)
282 const double hCenter = h / 2, lineLeft = .1, lineRight = .62;
283 const double lineLength = w * (lineRight - lineLeft);
284 painter->
drawRect(h * lineLeft, hCenter - h * .1, lineLength, h * .2);
293void PolarAlignmentAssistant::drawArrows(
double azError,
double altError)
295 constexpr double minError = 20.0 / 3600.0;
296 double absError = fabs(altError);
300 constexpr double largeErr = 10.0 / 60.0, smallErr = 1.0 / 60.0, largeSize = 100, smallSize = 20, c1 = 533.33, c2 = 11.111;
303 if (absError > largeErr)
305 else if (absError < smallErr)
307 else size = absError * c1 + c2;
310 altPixmap.fill(
QColor(
"transparent"));
314 if (altError > minError)
315 downArrow(&altPainter, size, size);
316 else if (altError < -minError)
317 upArrow(&altPainter, size, size);
318 arrowAlt->setPixmap(altPixmap);
320 absError = fabs(azError);
321 if (absError > largeErr)
323 else if (absError < smallErr)
325 else size = absError * c1 + c2;
328 azPixmap.fill(
QColor(
"transparent"));
332 if (azError > minError)
333 leftArrow(&azPainter, size, size);
334 else if (azError < -minError)
335 rightArrow(&azPainter, size, size);
336 arrowAz->setPixmap(azPixmap);
339bool PolarAlignmentAssistant::detectStarsPAHRefresh(
QList<Edge> *stars,
int num,
int x,
int y,
int *starIndex)
345 QVariantMap settings;
346 settings[
"optionsProfileIndex"] = Options::solveOptionsProfile();
347 settings[
"optionsProfileGroup"] =
static_cast<int>(Ekos::AlignProfiles);
348 m_ImageData->setSourceExtractorSettings(settings);
351 m_ImageData->findStars(ALGORITHM_SEP).waitForFinished();
353 QString debugString =
QString(
"PAA Refresh: Detected %1 stars (%2s)")
354 .
arg(m_ImageData->getStarCenters().size()).
arg(timer.elapsed() / 1000.0, 5,
'f', 3);
355 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
359 std::sort(detectedStars.
begin(), detectedStars.
end(), [](
const Edge * edge1,
const Edge * edge2) ->
bool { return edge1->sum > edge2->sum;});
362 double bestDist = 1e9;
364 for (
int i = 0; i < detectedStars.
size(); i++)
366 double dx = detectedStars[i]->x - x;
367 double dy = detectedStars[i]->y - y;
368 double dist = dx * dx + dy * dy;
376 int starCount = qMin(num, detectedStars.
count());
377 for (
int i = 0; i < starCount; i++)
378 stars->
append(*(detectedStars[i]));
379 if (bestIndex >= starCount)
383 stars->
append(*(detectedStars[bestIndex]));
384 *starIndex = starCount;
388 *starIndex = bestIndex;
390 debugString =
QString(
"PAA Refresh: User's star(%1,%2) is index %3").
arg(x).
arg(y).
arg(*starIndex);
391 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
393 detectedStars.
clear();
395 return stars->
count();
398void PolarAlignmentAssistant::updateRefreshDisplay(
double azE,
double altE)
400 drawArrows(azE, altE);
401 const double errDegrees = hypot(azE, altE);
402 dms totalError(errDegrees), azError(azE), altError(altE);
403 PAHUpdatedErrorTotal->setText(totalError.toDMSString());
404 PAHUpdatedErrorAlt->setText(altError.toDMSString());
405 PAHUpdatedErrorAz->setText(azError.toDMSString());
407 QString debugString =
QString(
"PAA Refresh(%1): Corrected az: %2 alt: %4 total: %6")
408 .
arg(refreshIteration).
arg(azError.toDMSString())
409 .
arg(altError.toDMSString()).
arg(totalError.toDMSString());
410 emit newLog(debugString);
411 emit updatedErrorsChanged(totalError.Degrees(), azError.Degrees(), altError.Degrees());
414void PolarAlignmentAssistant::processPAHRefresh()
416 m_AlignView->setStarCircle();
417 PAHUpdatedErrorTotal->clear();
418 PAHIteration->clear();
419 PAHUpdatedErrorAlt->clear();
420 PAHUpdatedErrorAz->clear();
423 PAHIteration->setText(
QString(
"Image %1").arg(imageNumber));
425 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
435 if ((pAHRefreshAlgorithm->currentIndex() == MOVE_STAR_UPDATE_ERR_ALGORITHM) || (refreshIteration == 0))
437 constexpr int MIN_PAH_REFRESH_STARS = 10;
443 int clickedStarIndex;
444 detectStarsPAHRefresh(&stars, 100, correctionFrom.x(), correctionFrom.y(), &clickedStarIndex);
445 if (clickedStarIndex < 0)
447 debugString =
QString(
"PAA Refresh(%1): Didn't find the clicked star near %2,%3")
448 .
arg(refreshIteration).
arg(correctionFrom.x()).
arg(correctionFrom.y());
449 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
451 emit newAlignTableResult(Align::ALIGN_RESULT_FAILED);
452 emit captureAndSolve();
456 debugString =
QString(
"PAA Refresh(%1): Refresh star(%2,%3) is index %4 with offset %5 %6")
457 .
arg(refreshIteration + 1).
arg(correctionFrom.x(), 4,
'f', 0)
458 .
arg(correctionFrom.y(), 4,
'f', 0).
arg(clickedStarIndex)
459 .
arg(stars[clickedStarIndex].x - correctionFrom.x(), 4,
'f', 0)
460 .
arg(stars[clickedStarIndex].y - correctionFrom.y(), 4,
'f', 0);
461 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
463 if (stars.
size() > MIN_PAH_REFRESH_STARS)
469 if (refreshIteration++ == 0)
473 starCorrespondencePAH.initialize(stars, clickedStarIndex);
474 if (clickedStarIndex >= 0)
476 setupCorrectionGraphics(
QPointF(stars[clickedStarIndex].x, stars[clickedStarIndex].y));
477 emit newCorrectionVector(
QLineF(correctionFrom, correctionTo));
478 emit newFrame(m_AlignView);
487 starCorrespondencePAH.find(stars, 200.0, &starMap,
false, 0.40);
491 for (
int i = 0; i < starMap.
size(); ++i)
493 if (starMap[i] == starCorrespondencePAH.guideStar())
495 dx = stars[i].x - correctionFrom.x();
496 dy = stars[i].y - correctionFrom.y();
504 for (
int i = 0; i < starMap.
size(); ++i)
506 if (starMap[i] != -1)
509 debugString =
QString(
"PAA Refresh(%1): starMap %2").
arg(refreshIteration).
arg(allOnes ?
"ALL -1's" :
"not all -1's");
510 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
517 m_AlignView->setStarCircle(
QPointF(stars[starIndex].x, stars[starIndex].y));
518 debugString =
QString(
"PAA Refresh(%1): User's star is now at %2,%3, with movement = %4,%5").
arg(refreshIteration)
519 .
arg(stars[starIndex].x, 4,
'f', 0).
arg(stars[starIndex].y, 4,
'f', 0).
arg(dx).
arg(dy);
520 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
523 if (polarAlign.pixelError(m_AlignView->keptImage(),
QPointF(stars[starIndex].x, stars[starIndex].y),
524 correctionTo, &azE, &altE))
526 updateRefreshDisplay(azE, altE);
527 debugString =
QString(
"PAA Refresh(%1): %2,%3 --> %4,%5 @ %6,%7")
528 .
arg(refreshIteration).
arg(correctionFrom.x(), 4,
'f', 0).
arg(correctionFrom.y(), 4,
'f', 0)
529 .
arg(correctionTo.x(), 4,
'f', 0).
arg(correctionTo.y(), 4,
'f', 0)
530 .
arg(stars[starIndex].x, 4,
'f', 0).
arg(stars[starIndex].y, 4,
'f', 0);
531 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
535 debugString =
QString(
"PAA Refresh(%1): pixelError failed to estimate the remaining correction").
arg(refreshIteration);
536 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
541 if (refreshIteration > 1)
543 debugString =
QString(
"PAA Refresh(%1): Didn't find the user's star").
arg(refreshIteration);
544 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
550 debugString =
QString(
"PAA Refresh(%1): Too few stars detected (%2)").
arg(refreshIteration).
arg(stars.
size());
551 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
552 emit updatedErrorsChanged(-1, -1, -1);
556 emit captureAndSolve();
559bool PolarAlignmentAssistant::processSolverFailure()
561 if ((m_PAHStage == PAH_FIRST_CAPTURE ||
562 m_PAHStage == PAH_SECOND_CAPTURE ||
563 m_PAHStage == PAH_THIRD_CAPTURE ||
564 m_PAHStage == PAH_FIRST_SOLVE ||
565 m_PAHStage == PAH_SECOND_SOLVE ||
566 m_PAHStage == PAH_THIRD_SOLVE)
567 && ++m_PAHRetrySolveCounter < 4)
569 emit newLog(
i18n(
"PAA: Solver failed, retrying."));
570 emit captureAndSolve();
574 if (m_PAHStage != PAH_IDLE)
576 emit newLog(
i18n(
"PAA: Stopping, solver failed too many times."));
583void PolarAlignmentAssistant::setPAHStage(Stage stage)
585 if (stage != m_PAHStage)
588 polarAlignWidget->updatePAHStage(stage);
589 emit newPAHStage(m_PAHStage);
593void PolarAlignmentAssistant::processMountRotation(
const dms &ra,
double settleDuration)
602 if (m_PAHStage == PAH_FIRST_ROTATE)
604 rotProgressMessage =
"First mount rotation remaining degrees:";
605 rotDoneMessage =
i18n(
"Mount first rotation is complete.");
606 nextCapture = PAH_SECOND_CAPTURE;
607 nextSettle = PAH_FIRST_SETTLE;
609 else if (m_PAHStage == PAH_SECOND_ROTATE)
611 rotProgressMessage =
"Second mount rotation remaining degrees:";
612 rotDoneMessage =
i18n(
"Mount second rotation is complete.");
613 nextCapture = PAH_THIRD_CAPTURE;
614 nextSettle = PAH_SECOND_SETTLE;
618 if (m_PAHStage == PAH_FIRST_ROTATE || m_PAHStage == PAH_SECOND_ROTATE)
621 if(!pAHManualSlew->isChecked())
623 qCDebug(KSTARS_EKOS_ALIGN) << rotProgressMessage << deltaAngle;
624 if (deltaAngle <= PAH_ROTATION_THRESHOLD)
626 m_CurrentTelescope->StopWE();
627 emit newLog(rotDoneMessage);
629 if (settleDuration <= 0)
631 setPAHStage(nextCapture);
632 updateDisplay(m_PAHStage, getPAHMessage());
636 setPAHStage(nextSettle);
637 updateDisplay(m_PAHStage, getPAHMessage());
639 emit newLog(
i18n(
"Settling..."));
642 setPAHStage(nextCapture);
643 updateDisplay(m_PAHStage, getPAHMessage());
648 else if (deltaAngle > pAHRotation->value() * 1.25)
650 m_CurrentTelescope->abort();
651 emit newLog(
i18n(
"Mount aborted. Reverse RA axis direction and try again."));
659bool PolarAlignmentAssistant::checkPAHForMeridianCrossing()
662 double hourAngle = m_CurrentTelescope->hourAngle().Degrees();
663 while (hourAngle < -180)
665 while (hourAngle > 180)
667 double ra = 0,
dec = 0;
668 m_CurrentTelescope->getEqCoords(&ra, &dec);
675 bool nearThePole = fabs(dec) > 88;
679 double degreesPerSlew = pAHRotation->value();
680 bool closeToMeridian = fabs(hourAngle) < 2.0 * degreesPerSlew;
681 bool goingWest = pAHDirection->currentIndex() == 0;
686 bool wouldCrossMeridian =
687 ((m_CurrentTelescope->pierSide() == ISD::Mount::PIER_EAST && !goingWest && closeToMeridian) ||
688 (m_CurrentTelescope->pierSide() == ISD::Mount::PIER_WEST && goingWest && closeToMeridian) ||
689 (m_CurrentTelescope->pierSide() == ISD::Mount::PIER_UNKNOWN && closeToMeridian));
691 return wouldCrossMeridian;
694void PolarAlignmentAssistant::updateDisplay(Stage stage,
const QString &message)
698 case PAH_FIRST_ROTATE:
699 case PAH_SECOND_ROTATE:
700 if (pAHManualSlew->isChecked())
702 polarAlignWidget->updatePAHStage(stage);
703 PAHWidgets->setCurrentWidget(PAHManualRotatePage);
704 manualRotateText->setText(message);
705 emit newPAHMessage(message);
709 case PAH_FIRST_CAPTURE:
710 case PAH_SECOND_CAPTURE:
711 case PAH_THIRD_CAPTURE:
712 case PAH_FIRST_SOLVE:
713 case PAH_SECOND_SOLVE:
714 case PAH_THIRD_SOLVE:
715 polarAlignWidget->updatePAHStage(stage);
716 PAHWidgets->setCurrentWidget(PAHMessagePage);
717 PAHMessageText->setText(message);
718 emit newPAHMessage(message);
727void PolarAlignmentAssistant::startPAHProcess()
729 qCInfo(KSTARS_EKOS_ALIGN) <<
QString(
"Starting Polar Alignment Assistant process %1 ...").
arg(PAA_VERSION);
731 auto executePAH = [ this ]()
733 setPAHStage(PAH_FIRST_CAPTURE);
735 if (Options::limitedResourcesMode())
736 emit newLog(
i18n(
"Warning: Equatorial Grid Lines will not be drawn due to limited resources mode."));
738 if (m_CurrentTelescope->hasAlignmentModel())
740 emit newLog(
i18n(
"Clearing mount Alignment Model..."));
741 m_CurrentTelescope->clearAlignmentModel();
745 m_CurrentTelescope->unpark();
748 if (m_CurrentTelescope->canControlTrack() && m_CurrentTelescope->isTracking() ==
false)
749 m_CurrentTelescope->setTrackEnabled(
true);
751 PAHStartB->setEnabled(
false);
752 PAHStopB->setEnabled(
true);
754 PAHUpdatedErrorTotal->clear();
755 PAHUpdatedErrorAlt->clear();
756 PAHUpdatedErrorAz->clear();
757 PAHOrigErrorTotal->clear();
758 PAHOrigErrorAlt->clear();
759 PAHOrigErrorAz->clear();
760 PAHIteration->setText(
"");
762 updateDisplay(m_PAHStage, getPAHMessage());
766 m_PAHRetrySolveCounter = 0;
767 emit captureAndSolve();
772 if (checkPAHForMeridianCrossing())
781 KSMessageBox::Instance()->warningContinueCancel(
i18n(
"This could cause the telescope to cross the meridian."),
782 i18n(
"Warning"), 15);
788void PolarAlignmentAssistant::stopPAHProcess()
790 if (m_PAHStage == PAH_IDLE)
800 if (m_PAHStage == PAH_REFRESH)
802 setPAHStage(PAH_POST_REFRESH);
803 polarAlignWidget->updatePAHStage(m_PAHStage);
805 qCInfo(KSTARS_EKOS_ALIGN) <<
"Stopping Polar Alignment Assistant process...";
808 if (m_CurrentTelescope && m_CurrentTelescope->isInMotion())
809 m_CurrentTelescope->abort();
811 setPAHStage(PAH_IDLE);
812 polarAlignWidget->updatePAHStage(m_PAHStage);
814 PAHStartB->setEnabled(
true);
815 PAHStopB->setEnabled(
false);
816 PAHRefreshB->setEnabled(
true);
817 PAHWidgets->setCurrentWidget(PAHIntroPage);
818 emit newPAHMessage(introText->text());
820 m_AlignView->reset();
821 m_AlignView->setRefreshEnabled(
false);
823 emit newFrame(m_AlignView);
824 disconnect(m_AlignView.get(), &AlignView::trackingStarSelected,
this,
825 &Ekos::PolarAlignmentAssistant::setPAHCorrectionOffset);
826 disconnect(m_AlignView.get(), &AlignView::newCorrectionVector,
this, &Ekos::PolarAlignmentAssistant::newCorrectionVector);
828 if (Options::pAHAutoPark())
830 m_CurrentTelescope->park();
831 emit newLog(
i18n(
"Parking the mount..."));
835void PolarAlignmentAssistant::rotatePAH()
837 double TargetDiffRA = pAHRotation->value();
838 bool westMeridian = pAHDirection->currentIndex() == 0;
850 if (pAHManualSlew->isChecked())
855 const SkyPoint telescopeCoord = m_CurrentTelescope->currentCoordinates();
858 dms newTelescopeRA = (telescopeCoord.
ra() +
dms(TargetDiffRA)).reduce();
860 targetPAH.setRA(newTelescopeRA);
861 targetPAH.setDec(telescopeCoord.
dec());
865 if (pAHMountSpeed->currentIndex() >= 0)
866 m_CurrentTelescope->setSlewRate(pAHMountSpeed->currentIndex());
868 m_CurrentTelescope->MoveWE(westMeridian ? ISD::Mount::MOTION_WEST :
ISD::Mount::MOTION_EAST,
869 ISD::Mount::MOTION_START);
871 emit newLog(
i18n(
"Please wait until mount completes rotating to RA (%1) DE (%2)", targetPAH.ra().toHMSString(),
872 targetPAH.dec().toDMSString()));
875void PolarAlignmentAssistant::setupCorrectionGraphics(
const QPointF &pixel)
877 polarAlign.refreshSolution(&refreshSolution, &altOnlyRefreshSolution);
885 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionAltTo,
true))
887 qCInfo(KSTARS_EKOS_ALIGN) <<
QString(
i18n(
"PAA: Failed to findCorrectedPixel."));
891 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionTo))
893 qCInfo(KSTARS_EKOS_ALIGN) <<
QString(
i18n(
"PAA: Failed to findCorrectedPixel."));
896 QString debugString =
QString(
"PAA: Correction: %1,%2 --> %3,%4 (alt only %5,%6)")
897 .
arg(pixel.
x(), 4,
'f', 0).
arg(pixel.
y(), 4,
'f', 0)
898 .
arg(correctionTo.x(), 4,
'f', 0).
arg(correctionTo.y(), 4,
'f', 0)
899 .
arg(correctionAltTo.x(), 4,
'f', 0).
arg(correctionAltTo.y(), 4,
'f', 0);
900 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
901 correctionFrom = pixel;
903 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
904 updatePlateSolveTriangle(imageData);
906 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
912bool PolarAlignmentAssistant::calculatePAHError()
915 m_AlignView->holdOnToImage();
917 if (!polarAlign.findAxis())
919 emit newLog(
i18n(
"PAA: Failed to find RA Axis center."));
924 double azimuthError, altitudeError;
925 polarAlign.calculateAzAltError(&azimuthError, &altitudeError);
926 drawArrows(azimuthError, altitudeError);
927 dms polarError(hypot(altitudeError, azimuthError));
928 dms azError(azimuthError), altError(altitudeError);
935 .
arg(polarError.toDMSString()).
arg(azError.toDMSString())
936 .
arg(altError.toDMSString());
937 emit newLog(
QString(
"Polar Alignment Error: %1").arg(msg));
939 polarAlign.setMaxPixelSearchRange(polarError.Degrees() + 1);
942 PAHOrigErrorTotal->setText(polarError.toDMSString());
943 PAHOrigErrorAlt->setText(altError.toDMSString());
944 PAHOrigErrorAz->setText(azError.toDMSString());
946 setupCorrectionGraphics(
QPointF(m_ImageData->width() / 2, m_ImageData->height() / 2));
949 SkyPoint CP(0, (hemisphere == NORTH_HEMISPHERE) ? 90 : -90);
950 QPointF imagePoint, celestialPolePoint;
951 m_ImageData->wcsToPixel(CP, celestialPolePoint, imagePoint);
952 if (m_ImageData->contains(celestialPolePoint))
954 m_AlignView->setCelestialPole(celestialPolePoint);
956 if (polarAlign.findCorrectedPixel(m_ImageData, celestialPolePoint, &raAxis))
957 m_AlignView->setRaAxis(raAxis);
960 connect(m_AlignView.get(), &AlignView::trackingStarSelected,
this, &Ekos::PolarAlignmentAssistant::setPAHCorrectionOffset);
961 emit polarResultUpdated(
QLineF(correctionFrom, correctionTo), polarError.Degrees(), azError.Degrees(), altError.Degrees());
963 connect(m_AlignView.get(), &AlignView::newCorrectionVector,
this, &Ekos::PolarAlignmentAssistant::newCorrectionVector,
965 syncCorrectionVector();
966 emit newFrame(m_AlignView);
971void PolarAlignmentAssistant::syncCorrectionVector()
973 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
975 emit newCorrectionVector(
QLineF(correctionFrom, correctionTo));
976 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
979void PolarAlignmentAssistant::setPAHCorrectionOffsetPercentage(
double dx,
double dy)
981 double x = dx * m_AlignView->zoomedWidth();
982 double y = dy * m_AlignView->zoomedHeight();
983 setPAHCorrectionOffset(
static_cast<int>(round(x)),
static_cast<int>(round(y)));
986void PolarAlignmentAssistant::setPAHCorrectionOffset(
int x,
int y)
988 if (m_PAHStage == PAH_REFRESH)
990 emit newLog(
i18n(
"Polar-alignment star cannot be updated during refresh phase as it might affect error measurements."));
994 setupCorrectionGraphics(
QPointF(x, y));
995 emit newCorrectionVector(
QLineF(correctionFrom, correctionTo));
996 emit newFrame(m_AlignView);
1000void PolarAlignmentAssistant::setPAHSlewDone()
1002 emit newPAHMessage(
"Manual slew done.");
1005 case PAH_FIRST_ROTATE :
1006 setPAHStage(PAH_SECOND_CAPTURE);
1007 emit newLog(
i18n(
"First manual rotation done."));
1008 updateDisplay(m_PAHStage, getPAHMessage());
1010 case PAH_SECOND_ROTATE :
1011 setPAHStage(PAH_THIRD_CAPTURE);
1012 emit newLog(
i18n(
"Second manual rotation done."));
1013 updateDisplay(m_PAHStage, getPAHMessage());
1022void PolarAlignmentAssistant::startPAHRefreshProcess()
1024 qCInfo(KSTARS_EKOS_ALIGN) <<
"Starting Polar Alignment Assistant refreshing...";
1026 refreshIteration = 0;
1028 m_NumHealpixFailures = 0;
1030 setPAHStage(PAH_REFRESH);
1031 polarAlignWidget->updatePAHStage(m_PAHStage);
1032 auto message = getPAHMessage();
1033 refreshText->setText(message);
1034 emit newPAHMessage(message);
1036 PAHRefreshB->setEnabled(
false);
1039 if (m_AlignView->isEQGridShown())
1040 m_AlignView->toggleEQGrid();
1042 m_AlignView->setRefreshEnabled(
true);
1044 Options::setAstrometrySolverWCS(
false);
1045 Options::setAutoWCS(
false);
1048 emit captureAndSolve();
1051void PolarAlignmentAssistant::processPAHStage(
double orientation,
double ra,
double dec,
double pixscale,
1052 bool eastToTheRight,
short healpix,
short index)
1054 if (m_PAHStage == PAH_FIND_CP)
1057 i18n(
"Mount is synced to celestial pole. You can now continue Polar Alignment Assistant procedure."));
1058 setPAHStage(PAH_FIRST_CAPTURE);
1059 polarAlignWidget->updatePAHStage(m_PAHStage);
1063 if (m_PAHStage == PAH_FIRST_SOLVE || m_PAHStage == PAH_SECOND_SOLVE || m_PAHStage == PAH_THIRD_SOLVE)
1068 m_LastOrientation = orientation;
1069 m_LastPixscale = pixscale;
1070 m_HealpixToUse = healpix;
1071 m_IndexToUse = index;
1073 bool doWcs = (m_PAHStage == PAH_THIRD_SOLVE) || !Options::limitedResourcesMode();
1076 emit newLog(
i18n(
"Please wait while WCS data is processed..."));
1077 PAHMessageText->setText(
1078 m_PAHStage == PAH_FIRST_SOLVE
1079 ?
"Calculating WCS for the first image...</p>"
1080 : (m_PAHStage == PAH_SECOND_SOLVE ?
"Calculating WCS for the second image...</p>"
1081 :
"Calculating WCS for the third image...</p>"));
1083 connect(m_AlignView.get(), &AlignView::wcsToggled,
this, &Ekos::PolarAlignmentAssistant::setWCSToggled,
1085 m_AlignView->injectWCS(orientation, ra, dec, pixscale, eastToTheRight);
1092 m_ImageData = image;
1094 if (m_PAHStage == PAH_FIRST_CAPTURE)
1095 setPAHStage(PAH_FIRST_SOLVE);
1096 else if (m_PAHStage == PAH_SECOND_CAPTURE)
1097 setPAHStage(PAH_SECOND_SOLVE);
1098 else if (m_PAHStage == PAH_THIRD_CAPTURE)
1099 setPAHStage(PAH_THIRD_SOLVE);
1102 updateDisplay(m_PAHStage, getPAHMessage());
1105void PolarAlignmentAssistant::setWCSToggled(
bool result)
1107 emit newLog(
i18n(
"WCS data processing is complete."));
1109 disconnect(m_AlignView.get(), &AlignView::wcsToggled,
this, &Ekos::PolarAlignmentAssistant::setWCSToggled);
1111 if (m_PAHStage == PAH_FIRST_CAPTURE || m_PAHStage == PAH_FIRST_SOLVE)
1114 if (result ==
false && m_AlignInstance->wcsSynced() ==
true)
1116 emit newLog(
i18n(
"WCS info is now valid. Capturing next frame..."));
1117 emit captureAndSolve();
1122 polarAlign.addPoint(m_ImageData);
1124 setPAHStage(PAH_FIRST_ROTATE);
1125 auto msg = getPAHMessage();
1126 if (pAHManualSlew->isChecked())
1128 msg =
QString(
"Please rotate your mount about %1 deg in RA")
1129 .
arg(pAHRotation->value());
1132 updateDisplay(m_PAHStage, msg);
1136 else if (m_PAHStage == PAH_SECOND_CAPTURE || m_PAHStage == PAH_SECOND_SOLVE)
1138 setPAHStage(PAH_SECOND_ROTATE);
1139 auto msg = getPAHMessage();
1141 if (pAHManualSlew->isChecked())
1143 msg =
QString(
"Please rotate your mount about %1 deg in RA")
1144 .
arg(pAHRotation->value());
1147 updateDisplay(m_PAHStage, msg);
1149 polarAlign.addPoint(m_ImageData);
1153 else if (m_PAHStage == PAH_THIRD_CAPTURE || m_PAHStage == PAH_THIRD_SOLVE)
1156 if (result ==
false)
1158 emit newLog(
i18n(
"Failed to process World Coordinate System: %1. Try again.", m_ImageData->getLastError()));
1162 polarAlign.addPoint(m_ImageData);
1166 if (calculatePAHError())
1168 setPAHStage(PAH_STAR_SELECT);
1169 polarAlignWidget->updatePAHStage(m_PAHStage);
1170 PAHWidgets->setCurrentWidget(PAHRefreshPage);
1171 refreshText->setText(getPAHMessage());
1172 emit newPAHMessage(getPAHMessage());
1176 emit newLog(
i18n(
"PAA: Failed to find the RA axis. Quitting."));
1182void PolarAlignmentAssistant::setMountStatus(ISD::Mount::Status newState)
1186 case ISD::Mount::MOUNT_PARKING:
1187 case ISD::Mount::MOUNT_SLEWING:
1188 case ISD::Mount::MOUNT_MOVING:
1189 PAHStartB->setEnabled(
false);
1193 if (m_PAHStage == PAH_IDLE)
1194 PAHStartB->setEnabled(
true);
1199QString PolarAlignmentAssistant::getPAHMessage()
const
1205 return introText->text();
1206 case PAH_FIRST_CAPTURE:
1207 return i18n(
"<p>The assistant requires three images to find a solution. Ekos is now capturing the first image...</p>");
1208 case PAH_FIRST_SOLVE:
1209 return i18n(
"<p>Solving the <i>first</i> image...</p>");
1210 case PAH_FIRST_ROTATE:
1211 return i18n(
"<p>Executing the <i>first</i> mount rotation...</p>");
1212 case PAH_FIRST_SETTLE:
1213 return i18n(
"<p>Settling after the <i>first</i> mount rotation.</p>");
1214 case PAH_SECOND_SETTLE:
1215 return i18n(
"<p>Settling after the <i>second</i> mount rotation.</p>");
1216 case PAH_SECOND_CAPTURE:
1217 return i18n(
"<p>Capturing the second image...</p>");
1218 case PAH_SECOND_SOLVE:
1219 return i18n(
"<p>Solving the <i>second</i> image...</p>");
1220 case PAH_SECOND_ROTATE:
1221 return i18n(
"<p>Executing the <i>second</i> mount rotation...</p>");
1222 case PAH_THIRD_CAPTURE:
1223 return i18n(
"<p>Capturing the <i>third</i> and final image...</p>");
1224 case PAH_THIRD_SOLVE:
1225 return i18n(
"<p>Solving the <i>third</i> image...</p>");
1226 case PAH_STAR_SELECT:
1227 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
1228 return i18n(
"<p>Choose your exposure time & select an adjustment method. Then click <i>refresh</i> to begin adjustments.</p>");
1230 return i18n(
"<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>");
1232 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
1233 return i18n(
"<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 you're finished.</p>");
1235 return i18n(
"<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>");
1243void PolarAlignmentAssistant::setPAHRefreshAlgorithm(RefreshAlgorithm value)
1247 if ((m_PAHStage == PAH_REFRESH) && refreshIteration > 0 && (value != PLATE_SOLVE_ALGORITHM)
1248 && !starCorrespondencePAH.size())
1250 pAHRefreshAlgorithm->setCurrentIndex(PLATE_SOLVE_ALGORITHM);
1251 emit newLog(
i18n(
"Cannot change to MoveStar algorithm once refresh has begun"));
1254 if (m_PAHStage == PAH_REFRESH || m_PAHStage == PAH_STAR_SELECT)
1256 refreshText->setText(getPAHMessage());
1257 emit newPAHMessage(getPAHMessage());
1260 showUpdatedError((value == PLATE_SOLVE_ALGORITHM) ||
1261 (value == MOVE_STAR_UPDATE_ERR_ALGORITHM));
1262 if (value == PLATE_SOLVE_ALGORITHM)
1263 updatePlateSolveTriangle(m_ImageData);
1265 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
const CachingDms * lat() const
The sky coordinates of a point in the sky.
const CachingDms & dec() const
const CachingDms & ra() const
An angle, stored as degrees, but expressible in many ways.
const dms deltaAngle(dms angle) const
deltaAngle Return the shortest difference (path) between this angle and the supplied angle.
const double & Degrees() const
KLocalizedString KI18N_EXPORT ki18n(const char *text)
QString i18n(const char *text, const TYPE &arg...)
Ekos is an advanced Astrophotography tool for Linux.
ISD is a collection of INDI Standard Devices.
void currentIndexChanged(int index)
void append(QList< T > &&value)
qsizetype count() const const
qsizetype size() const const
bool disconnect(const QMetaObject::Connection &connection)
void drawConvexPolygon(const QPoint *points, int pointCount)
void drawRect(const QRect &rectangle)
bool isNull() const const
QString arg(Args &&... args) const const
QTextStream & dec(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)