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"
26const QMap<PolarAlignmentAssistant::Stage, KLocalizedString> PolarAlignmentAssistant::PAHStages =
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")},
45PolarAlignmentAssistant::PolarAlignmentAssistant(
Align *parent,
const QSharedPointer<AlignView> &view) : QWidget(parent)
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 SSolver::Parameters parameters;
137 parameters = profiles.at(Options::solveOptionsProfile());
139 catch (std::out_of_range
const &)
141 parameters = profiles[0];
145 parameters.search_radius = parameters.search_radius * 2;
146 constexpr double solverTimeout = 10.0;
152 m_Solver->useScale(Options::astrometryUseImageScale(), m_LastPixscale * 0.9, m_LastPixscale * 1.1);
153 m_Solver->usePosition(
true, m_LastRa, m_LastDec);
154 m_Solver->setHealpix(m_IndexToUse, m_HealpixToUse);
155 m_Solver->runSolver(m_ImageData);
158void PolarAlignmentAssistant::solverDone(
bool timedOut,
bool success,
const FITSImage::Solution &solution,
159 double elapsedSeconds)
161 disconnect(m_Solver.get(), &SolverUtils::done,
this, &PolarAlignmentAssistant::solverDone);
163 if (m_PAHStage != PAH_REFRESH)
166 if (timedOut || !success)
172 constexpr int MAX_NUM_HEALPIX_FAILURES = 2;
173 if (++m_NumHealpixFailures >= MAX_NUM_HEALPIX_FAILURES)
183 m_Solver->getSolutionHealpix(&m_IndexToUse, &m_HealpixToUse);
188 emit newLog(
i18n(
"Refresh solver timed out: %1s",
QString(
"%L1").arg(elapsedSeconds, 0,
'f', 1)));
189 emit captureAndSolve();
193 emit newLog(
i18n(
"Refresh solver failed: %1s",
QString(
"%L1").arg(elapsedSeconds, 0,
'f', 1)));
194 emit updatedErrorsChanged(-1, -1, -1);
195 emit captureAndSolve();
199 m_NumHealpixFailures = 0;
201 const double ra = solution.ra;
202 const double dec = solution.dec;
203 m_LastRa = solution.ra;
204 m_LastDec = solution.dec;
205 m_LastOrientation = solution.orientation;
206 m_LastPixscale = solution.pixscale;
208 emit newLog(
QString(
"Refresh solver success %1s: ra %2 dec %3 scale %4")
209 .arg(elapsedSeconds, 0,
'f', 1).arg(ra, 0,
'f', 3)
210 .arg(dec, 0,
'f', 3).arg(solution.pixscale));
213 SkyPoint refreshCoords(ra / 15.0, dec);
214 double azError = 0, altError = 0;
215 if (polarAlign.processRefreshCoords(refreshCoords, m_ImageData->getDateTime(), &azError, &altError))
217 updateRefreshDisplay(azError, altError);
219 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false :
true;
222 m_AlignView->injectWCS(solution.orientation, ra, dec, solution.pixscale, eastToTheRight,
false);
223 updatePlateSolveTriangle(m_ImageData);
226 emit newLog(
QString(
"Could not estimate mount rotation"));
229 emit captureAndSolve();
243 const SkyPoint &originalCoords = polarAlign.getPoint(2);
244 QPointF originalPixel, solutionPixel, altOnlyPixel, dummy;
245 QPointF centerPixel(image->width() / 2, image->height() / 2);
246 if (image->wcsToPixel(originalCoords, originalPixel, dummy) &&
247 image->wcsToPixel(refreshSolution, solutionPixel, dummy) &&
248 image->wcsToPixel(altOnlyRefreshSolution, altOnlyPixel, dummy))
250 m_AlignView->setCorrectionParams(originalPixel, solutionPixel, altOnlyPixel);
251 m_AlignView->setStarCircle(centerPixel);
255 qCDebug(KSTARS_EKOS_ALIGN) <<
"wcs failed";
264void upArrow(
QPainter *painter,
int w,
int h)
266 const double wCenter = w / 2, lineTop = 0.38, lineBottom = 0.9;
267 const double lineLength = h * (lineBottom - lineTop);
268 painter->
drawRect(wCenter - w * .1, h * lineTop, w * .2, lineLength);
273void downArrow(
QPainter *painter,
int w,
int h)
275 const double wCenter = w / 2, lineBottom = 0.62, lineTop = 0.1;
276 const double lineLength = h * (lineBottom - lineTop);
277 painter->
drawRect(wCenter - w * .1, h * lineTop, w * .2, lineLength);
282void leftArrow(
QPainter *painter,
int w,
int h)
284 const double hCenter = h / 2, lineLeft = 0.38, lineRight = 0.9;
285 const double lineLength = w * (lineRight - lineLeft);
286 painter->
drawRect(h * lineLeft, hCenter - h * .1, lineLength, h * .2);
291void rightArrow(
QPainter *painter,
int w,
int h)
293 const double hCenter = h / 2, lineLeft = .1, lineRight = .62;
294 const double lineLength = w * (lineRight - lineLeft);
295 painter->
drawRect(h * lineLeft, hCenter - h * .1, lineLength, h * .2);
304void PolarAlignmentAssistant::drawArrows(
double azError,
double altError)
306 constexpr double minError = 20.0 / 3600.0;
307 double absError = fabs(altError);
311 constexpr double largeErr = 10.0 / 60.0, smallErr = 1.0 / 60.0, largeSize = 100, smallSize = 20, c1 = 533.33, c2 = 11.111;
314 if (absError > largeErr)
316 else if (absError < smallErr)
318 else size = absError * c1 + c2;
321 altPixmap.fill(
QColor(
"transparent"));
325 if (altError > minError)
326 downArrow(&altPainter, size, size);
327 else if (altError < -minError)
328 upArrow(&altPainter, size, size);
329 arrowAlt->setPixmap(altPixmap);
331 absError = fabs(azError);
332 if (absError > largeErr)
334 else if (absError < smallErr)
336 else size = absError * c1 + c2;
339 azPixmap.fill(
QColor(
"transparent"));
343 if (azError > minError)
344 leftArrow(&azPainter, size, size);
345 else if (azError < -minError)
346 rightArrow(&azPainter, size, size);
347 arrowAz->setPixmap(azPixmap);
350bool PolarAlignmentAssistant::detectStarsPAHRefresh(
QList<Edge> *stars,
int num,
int x,
int y,
int *starIndex)
356 QVariantMap settings;
357 settings[
"optionsProfileIndex"] = Options::solveOptionsProfile();
358 settings[
"optionsProfileGroup"] =
static_cast<int>(Ekos::AlignProfiles);
359 m_ImageData->setSourceExtractorSettings(settings);
362 m_ImageData->findStars(ALGORITHM_SEP).waitForFinished();
364 QString debugString =
QString(
"PAA Refresh: Detected %1 stars (%2s)")
365 .
arg(m_ImageData->getStarCenters().size()).
arg(timer.elapsed() / 1000.0, 5,
'f', 3);
366 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
370 std::sort(detectedStars.
begin(), detectedStars.
end(), [](
const Edge * edge1,
const Edge * edge2) ->
bool { return edge1->sum > edge2->sum;});
373 double bestDist = 1e9;
375 for (
int i = 0; i < detectedStars.
size(); i++)
377 double dx = detectedStars[i]->x - x;
378 double dy = detectedStars[i]->y - y;
379 double dist = dx * dx + dy * dy;
387 int starCount = qMin(num, detectedStars.
count());
388 for (
int i = 0; i < starCount; i++)
389 stars->
append(*(detectedStars[i]));
390 if (bestIndex >= starCount)
394 stars->
append(*(detectedStars[bestIndex]));
395 *starIndex = starCount;
399 *starIndex = bestIndex;
401 debugString =
QString(
"PAA Refresh: User's star(%1,%2) is index %3").
arg(x).
arg(y).
arg(*starIndex);
402 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
404 detectedStars.
clear();
406 return stars->
count();
409void PolarAlignmentAssistant::updateRefreshDisplay(
double azE,
double altE)
411 drawArrows(azE, altE);
412 const double errDegrees = hypot(azE, altE);
413 dms totalError(errDegrees), azError(azE), altError(altE);
414 PAHUpdatedErrorTotal->setText(totalError.toDMSString());
415 PAHUpdatedErrorAlt->setText(altError.toDMSString());
416 PAHUpdatedErrorAz->setText(azError.toDMSString());
418 QString debugString =
QString(
"PAA Refresh(%1): Corrected az: %2 alt: %4 total: %6")
419 .
arg(refreshIteration).
arg(azError.toDMSString())
420 .
arg(altError.toDMSString()).
arg(totalError.toDMSString());
421 emit newLog(debugString);
422 emit updatedErrorsChanged(totalError.Degrees(), azError.Degrees(), altError.Degrees());
425void PolarAlignmentAssistant::processPAHRefresh()
427 m_AlignView->setStarCircle();
428 PAHUpdatedErrorTotal->clear();
429 PAHIteration->clear();
430 PAHUpdatedErrorAlt->clear();
431 PAHUpdatedErrorAz->clear();
434 PAHIteration->setText(
QString(
"Image %1").arg(imageNumber));
436 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
446 if ((pAHRefreshAlgorithm->currentIndex() == MOVE_STAR_UPDATE_ERR_ALGORITHM) || (refreshIteration == 0))
448 constexpr int MIN_PAH_REFRESH_STARS = 10;
454 int clickedStarIndex;
455 detectStarsPAHRefresh(&stars, 100, correctionFrom.x(), correctionFrom.y(), &clickedStarIndex);
456 if (clickedStarIndex < 0)
458 debugString =
QString(
"PAA Refresh(%1): Didn't find the clicked star near %2,%3")
459 .
arg(refreshIteration).
arg(correctionFrom.x()).
arg(correctionFrom.y());
460 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
462 emit newAlignTableResult(Align::ALIGN_RESULT_FAILED);
463 emit captureAndSolve();
467 debugString =
QString(
"PAA Refresh(%1): Refresh star(%2,%3) is index %4 with offset %5 %6")
468 .
arg(refreshIteration + 1).
arg(correctionFrom.x(), 4,
'f', 0)
469 .
arg(correctionFrom.y(), 4,
'f', 0).
arg(clickedStarIndex)
470 .
arg(stars[clickedStarIndex].x - correctionFrom.x(), 4,
'f', 0)
471 .
arg(stars[clickedStarIndex].y - correctionFrom.y(), 4,
'f', 0);
472 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
474 if (stars.
size() > MIN_PAH_REFRESH_STARS)
480 if (refreshIteration++ == 0)
484 starCorrespondencePAH.initialize(stars, clickedStarIndex);
485 if (clickedStarIndex >= 0)
487 setupCorrectionGraphics(
QPointF(stars[clickedStarIndex].x, stars[clickedStarIndex].y));
488 emit newCorrectionVector(
QLineF(correctionFrom, correctionTo));
489 emit newFrame(m_AlignView);
498 starCorrespondencePAH.find(stars, 200.0, &starMap,
false, 0.40);
502 for (
int i = 0; i < starMap.
size(); ++i)
504 if (starMap[i] == starCorrespondencePAH.guideStar())
506 dx = stars[i].x - correctionFrom.x();
507 dy = stars[i].y - correctionFrom.y();
515 for (
int i = 0; i < starMap.
size(); ++i)
517 if (starMap[i] != -1)
520 debugString =
QString(
"PAA Refresh(%1): starMap %2").
arg(refreshIteration).
arg(allOnes ?
"ALL -1's" :
"not all -1's");
521 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
528 m_AlignView->setStarCircle(
QPointF(stars[starIndex].x, stars[starIndex].y));
529 debugString =
QString(
"PAA Refresh(%1): User's star is now at %2,%3, with movement = %4,%5").
arg(refreshIteration)
530 .
arg(stars[starIndex].x, 4,
'f', 0).
arg(stars[starIndex].y, 4,
'f', 0).
arg(dx).
arg(dy);
531 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
534 if (polarAlign.pixelError(m_AlignView->keptImage(),
QPointF(stars[starIndex].x, stars[starIndex].y),
535 correctionTo, &azE, &altE))
537 updateRefreshDisplay(azE, altE);
538 debugString =
QString(
"PAA Refresh(%1): %2,%3 --> %4,%5 @ %6,%7")
539 .
arg(refreshIteration).
arg(correctionFrom.x(), 4,
'f', 0).
arg(correctionFrom.y(), 4,
'f', 0)
540 .
arg(correctionTo.x(), 4,
'f', 0).
arg(correctionTo.y(), 4,
'f', 0)
541 .
arg(stars[starIndex].x, 4,
'f', 0).
arg(stars[starIndex].y, 4,
'f', 0);
542 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
546 debugString =
QString(
"PAA Refresh(%1): pixelError failed to estimate the remaining correction").
arg(refreshIteration);
547 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
552 if (refreshIteration > 1)
554 debugString =
QString(
"PAA Refresh(%1): Didn't find the user's star").
arg(refreshIteration);
555 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
561 debugString =
QString(
"PAA Refresh(%1): Too few stars detected (%2)").
arg(refreshIteration).
arg(stars.
size());
562 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
563 emit updatedErrorsChanged(-1, -1, -1);
567 emit captureAndSolve();
570bool PolarAlignmentAssistant::processSolverFailure()
572 if ((m_PAHStage == PAH_FIRST_CAPTURE ||
573 m_PAHStage == PAH_SECOND_CAPTURE ||
574 m_PAHStage == PAH_THIRD_CAPTURE ||
575 m_PAHStage == PAH_FIRST_SOLVE ||
576 m_PAHStage == PAH_SECOND_SOLVE ||
577 m_PAHStage == PAH_THIRD_SOLVE)
578 && ++m_PAHRetrySolveCounter < 4)
580 emit newLog(
i18n(
"PAA: Solver failed, retrying."));
581 emit captureAndSolve();
585 if (m_PAHStage != PAH_IDLE)
587 emit newLog(
i18n(
"PAA: Stopping, solver failed too many times."));
594void PolarAlignmentAssistant::setPAHStage(Stage stage)
596 if (stage != m_PAHStage)
599 polarAlignWidget->updatePAHStage(stage);
600 emit newPAHStage(m_PAHStage);
604void PolarAlignmentAssistant::processMountRotation(
const dms &ra,
double settleDuration)
614 if (m_PAHStage == PAH_FIRST_ROTATE)
616 rotProgressMessage =
"First mount rotation completed degrees:";
617 rotDoneMessage =
i18n(
"Mount first rotation is complete.");
618 nextCapture = PAH_SECOND_CAPTURE;
619 nextSettle = PAH_FIRST_SETTLE;
621 else if (m_PAHStage == PAH_SECOND_ROTATE)
623 rotProgressMessage =
"Second mount rotation completed degrees:";
624 rotDoneMessage =
i18n(
"Mount second rotation is complete.");
625 nextCapture = PAH_THIRD_CAPTURE;
626 nextSettle = PAH_SECOND_SETTLE;
630 auto settle = [
this, rotDoneMessage, settleDuration, nextCapture, nextSettle]()
632 m_CurrentTelescope->StopWE();
633 emit newLog(rotDoneMessage);
635 int settleDurationMsec = settleDuration;
639 if (settleDurationMsec <= 1000)
640 settleDurationMsec = 2000;
642 setPAHStage(nextSettle);
643 updateDisplay(m_PAHStage, getPAHMessage());
645 emit newLog(
i18n(
"Settling..."));
648 setPAHStage(nextCapture);
649 updateDisplay(m_PAHStage, getPAHMessage());
653 if (m_PAHStage == PAH_FIRST_ROTATE || m_PAHStage == PAH_SECOND_ROTATE)
656 if(!pAHManualSlew->isChecked())
658 qCDebug(KSTARS_EKOS_ALIGN) << rotProgressMessage << traveledAngle;
661 if (std::abs(traveledAngle) > 0.5)
663 bool goingWest = pAHDirection->currentIndex() == 0;
667 if ( (traveledAngle < 0 && !goingWest) || (traveledAngle > 0 && goingWest))
669 m_CurrentTelescope->abort();
670 emit newLog(
i18n(
"Mount aborted. Reverse RA axis direction and try again."));
675 else if (std::abs(traveledAngle) > pAHRotation->value() * 0.90)
682bool PolarAlignmentAssistant::checkPAHForMeridianCrossing()
685 double hourAngle = m_CurrentTelescope->hourAngle().Degrees();
686 while (hourAngle < -180)
688 while (hourAngle > 180)
690 double ra = 0,
dec = 0;
691 m_CurrentTelescope->getEqCoords(&ra, &dec);
698 bool nearThePole = fabs(dec) > 88;
702 double degreesPerSlew = pAHRotation->value();
703 bool closeToMeridian = fabs(hourAngle) < 2.0 * degreesPerSlew;
704 bool goingWest = pAHDirection->currentIndex() == 0;
709 bool wouldCrossMeridian =
710 ((m_CurrentTelescope->pierSide() == ISD::Mount::PIER_EAST && !goingWest && closeToMeridian) ||
711 (m_CurrentTelescope->pierSide() == ISD::Mount::PIER_WEST && goingWest && closeToMeridian) ||
712 (m_CurrentTelescope->pierSide() == ISD::Mount::PIER_UNKNOWN && closeToMeridian));
714 return wouldCrossMeridian;
717void PolarAlignmentAssistant::updateDisplay(Stage stage,
const QString &message)
721 case PAH_FIRST_ROTATE:
722 case PAH_SECOND_ROTATE:
723 if (pAHManualSlew->isChecked())
725 polarAlignWidget->updatePAHStage(stage);
726 PAHWidgets->setCurrentWidget(PAHManualRotatePage);
727 manualRotateText->setText(message);
728 emit newPAHMessage(message);
732 case PAH_FIRST_CAPTURE:
733 case PAH_SECOND_CAPTURE:
734 case PAH_THIRD_CAPTURE:
735 case PAH_FIRST_SOLVE:
736 case PAH_SECOND_SOLVE:
737 case PAH_THIRD_SOLVE:
738 polarAlignWidget->updatePAHStage(stage);
739 PAHWidgets->setCurrentWidget(PAHMessagePage);
740 PAHMessageText->setText(message);
741 emit newPAHMessage(message);
750void PolarAlignmentAssistant::startPAHProcess()
752 qCInfo(KSTARS_EKOS_ALIGN) <<
QString(
"Starting Polar Alignment Assistant process %1 ...").
arg(PAA_VERSION);
754 auto executePAH = [ this ]()
756 setPAHStage(PAH_FIRST_CAPTURE);
758 if (Options::limitedResourcesMode())
759 emit newLog(
i18n(
"Warning: Equatorial Grid Lines will not be drawn due to limited resources mode."));
761 if (m_CurrentTelescope->hasAlignmentModel())
763 emit newLog(
i18n(
"Clearing mount Alignment Model..."));
764 m_CurrentTelescope->clearAlignmentModel();
768 m_CurrentTelescope->unpark();
771 if (m_CurrentTelescope->canControlTrack() && m_CurrentTelescope->isTracking() ==
false)
772 m_CurrentTelescope->setTrackEnabled(
true);
774 PAHStartB->setEnabled(
false);
775 PAHStopB->setEnabled(
true);
777 PAHUpdatedErrorTotal->clear();
778 PAHUpdatedErrorAlt->clear();
779 PAHUpdatedErrorAz->clear();
780 PAHOrigErrorTotal->clear();
781 PAHOrigErrorAlt->clear();
782 PAHOrigErrorAz->clear();
783 PAHIteration->setText(
"");
785 updateDisplay(m_PAHStage, getPAHMessage());
789 m_PAHRetrySolveCounter = 0;
790 emit captureAndSolve();
795 if (checkPAHForMeridianCrossing())
800 KSMessageBox::Instance()->disconnect(
this);
804 KSMessageBox::Instance()->warningContinueCancel(
i18n(
"This could cause the telescope to cross the meridian."),
805 i18n(
"Warning"), 15);
811void PolarAlignmentAssistant::stopPAHProcess()
813 if (m_PAHStage == PAH_IDLE)
823 if (m_PAHStage == PAH_REFRESH)
825 setPAHStage(PAH_POST_REFRESH);
826 polarAlignWidget->updatePAHStage(m_PAHStage);
828 qCInfo(KSTARS_EKOS_ALIGN) <<
"Stopping Polar Alignment Assistant process...";
831 if (m_CurrentTelescope && m_CurrentTelescope->isInMotion())
832 m_CurrentTelescope->abort();
834 setPAHStage(PAH_IDLE);
835 polarAlignWidget->updatePAHStage(m_PAHStage);
837 PAHStartB->setEnabled(
true);
838 PAHStopB->setEnabled(
false);
839 PAHRefreshB->setEnabled(
true);
840 PAHWidgets->setCurrentWidget(PAHIntroPage);
841 emit newPAHMessage(introText->text());
843 m_AlignView->reset();
844 m_AlignView->setRefreshEnabled(
false);
846 emit newFrame(m_AlignView);
847 disconnect(m_AlignView.get(), &AlignView::trackingStarSelected,
this,
848 &Ekos::PolarAlignmentAssistant::setPAHCorrectionOffset);
849 disconnect(m_AlignView.get(), &AlignView::newCorrectionVector,
this, &Ekos::PolarAlignmentAssistant::newCorrectionVector);
851 if (Options::pAHAutoPark())
853 m_CurrentTelescope->park();
854 emit newLog(
i18n(
"Parking the mount..."));
858void PolarAlignmentAssistant::rotatePAH()
860 double TargetDiffRA = pAHRotation->value();
861 bool westMeridian = pAHDirection->currentIndex() == 0;
873 if (pAHManualSlew->isChecked())
878 const SkyPoint telescopeCoord = m_CurrentTelescope->currentCoordinates();
881 m_StartCoord = telescopeCoord;
884 dms newTelescopeRA = (telescopeCoord.
ra() +
dms(TargetDiffRA)).
reduce();
886 targetPAH.setRA(newTelescopeRA);
887 targetPAH.setDec(telescopeCoord.
dec());
891 if (pAHMountSpeed->currentIndex() >= 0)
892 m_CurrentTelescope->setSlewRate(pAHMountSpeed->currentIndex());
894 m_CurrentTelescope->MoveWE(westMeridian ? ISD::Mount::MOTION_WEST : ISD::Mount::MOTION_EAST,
895 ISD::Mount::MOTION_START);
897 emit newLog(
i18n(
"Please wait until mount completes rotating to RA (%1) DE (%2)", targetPAH.ra().toHMSString(),
898 targetPAH.dec().toDMSString()));
901void PolarAlignmentAssistant::setupCorrectionGraphics(
const QPointF &pixel)
903 polarAlign.refreshSolution(&refreshSolution, &altOnlyRefreshSolution);
911 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionAltTo,
true))
913 qCInfo(KSTARS_EKOS_ALIGN) <<
QString(
i18n(
"PAA: Failed to findCorrectedPixel."));
917 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionTo))
919 qCInfo(KSTARS_EKOS_ALIGN) <<
QString(
i18n(
"PAA: Failed to findCorrectedPixel."));
922 QString debugString =
QString(
"PAA: Correction: %1,%2 --> %3,%4 (alt only %5,%6)")
923 .
arg(pixel.
x(), 4,
'f', 0).
arg(pixel.
y(), 4,
'f', 0)
924 .
arg(correctionTo.x(), 4,
'f', 0).
arg(correctionTo.y(), 4,
'f', 0)
925 .
arg(correctionAltTo.x(), 4,
'f', 0).
arg(correctionAltTo.y(), 4,
'f', 0);
926 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
927 correctionFrom = pixel;
929 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
930 updatePlateSolveTriangle(imageData);
932 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
938bool PolarAlignmentAssistant::calculatePAHError()
941 m_AlignView->holdOnToImage();
943 if (!polarAlign.findAxis())
945 emit newLog(
i18n(
"PAA: Failed to find RA Axis center."));
950 double azimuthError, altitudeError;
951 polarAlign.calculateAzAltError(&azimuthError, &altitudeError);
952 drawArrows(azimuthError, altitudeError);
953 dms polarError(hypot(altitudeError, azimuthError));
954 dms azError(azimuthError), altError(altitudeError);
961 .
arg(polarError.toDMSString()).
arg(azError.toDMSString())
962 .
arg(altError.toDMSString());
963 emit newLog(
QString(
"Polar Alignment Error: %1").arg(msg));
965 polarAlign.setMaxPixelSearchRange(polarError.Degrees() + 1);
968 PAHOrigErrorTotal->setText(polarError.toDMSString());
969 PAHOrigErrorAlt->setText(altError.toDMSString());
970 PAHOrigErrorAz->setText(azError.toDMSString());
972 setupCorrectionGraphics(
QPointF(m_ImageData->width() / 2, m_ImageData->height() / 2));
975 SkyPoint CP(0, (hemisphere == NORTH_HEMISPHERE) ? 90 : -90);
976 QPointF imagePoint, celestialPolePoint;
977 m_ImageData->wcsToPixel(CP, celestialPolePoint, imagePoint);
978 if (m_ImageData->contains(celestialPolePoint))
980 m_AlignView->setCelestialPole(celestialPolePoint);
982 if (polarAlign.findCorrectedPixel(m_ImageData, celestialPolePoint, &raAxis))
983 m_AlignView->setRaAxis(raAxis);
986 connect(m_AlignView.get(), &AlignView::trackingStarSelected,
this, &Ekos::PolarAlignmentAssistant::setPAHCorrectionOffset);
987 emit polarResultUpdated(
QLineF(correctionFrom, correctionTo), polarError.Degrees(), azError.Degrees(), altError.Degrees());
989 connect(m_AlignView.get(), &AlignView::newCorrectionVector,
this, &Ekos::PolarAlignmentAssistant::newCorrectionVector,
991 syncCorrectionVector();
992 emit newFrame(m_AlignView);
997void PolarAlignmentAssistant::syncCorrectionVector()
999 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
1001 emit newCorrectionVector(
QLineF(correctionFrom, correctionTo));
1002 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
1005void PolarAlignmentAssistant::setPAHCorrectionOffsetPercentage(
double dx,
double dy)
1007 double x = dx * m_AlignView->zoomedWidth();
1008 double y = dy * m_AlignView->zoomedHeight();
1009 setPAHCorrectionOffset(
static_cast<int>(round(x)),
static_cast<int>(round(y)));
1012void PolarAlignmentAssistant::setPAHCorrectionOffset(
int x,
int y)
1014 if (m_PAHStage == PAH_REFRESH)
1016 emit newLog(
i18n(
"Polar-alignment star cannot be updated during refresh phase as it might affect error measurements."));
1020 setupCorrectionGraphics(
QPointF(x, y));
1021 emit newCorrectionVector(
QLineF(correctionFrom, correctionTo));
1022 emit newFrame(m_AlignView);
1026void PolarAlignmentAssistant::setPAHSlewDone()
1028 emit newPAHMessage(
"Manual slew done.");
1031 case PAH_FIRST_ROTATE :
1032 setPAHStage(PAH_SECOND_CAPTURE);
1033 emit newLog(
i18n(
"First manual rotation done."));
1034 updateDisplay(m_PAHStage, getPAHMessage());
1036 case PAH_SECOND_ROTATE :
1037 setPAHStage(PAH_THIRD_CAPTURE);
1038 emit newLog(
i18n(
"Second manual rotation done."));
1039 updateDisplay(m_PAHStage, getPAHMessage());
1048void PolarAlignmentAssistant::startPAHRefreshProcess()
1050 qCInfo(KSTARS_EKOS_ALIGN) <<
"Starting Polar Alignment Assistant refreshing...";
1052 refreshIteration = 0;
1054 m_NumHealpixFailures = 0;
1056 setPAHStage(PAH_REFRESH);
1057 polarAlignWidget->updatePAHStage(m_PAHStage);
1058 auto message = getPAHMessage();
1059 refreshText->setText(message);
1060 emit newPAHMessage(message);
1062 PAHRefreshB->setEnabled(
false);
1065 if (m_AlignView->isEQGridShown())
1066 m_AlignView->toggleEQGrid();
1068 m_AlignView->setRefreshEnabled(
true);
1070 Options::setAstrometrySolverWCS(
false);
1071 Options::setAutoWCS(
false);
1074 emit captureAndSolve();
1077void PolarAlignmentAssistant::processPAHStage(
double orientation,
double ra,
double dec,
double pixscale,
1078 bool eastToTheRight,
short healpix,
short index)
1080 if (m_PAHStage == PAH_FIND_CP)
1083 i18n(
"Mount is synced to celestial pole. You can now continue Polar Alignment Assistant procedure."));
1084 setPAHStage(PAH_FIRST_CAPTURE);
1085 polarAlignWidget->updatePAHStage(m_PAHStage);
1089 if (m_PAHStage == PAH_FIRST_SOLVE || m_PAHStage == PAH_SECOND_SOLVE || m_PAHStage == PAH_THIRD_SOLVE)
1094 m_LastOrientation = orientation;
1095 m_LastPixscale = pixscale;
1096 m_HealpixToUse = healpix;
1097 m_IndexToUse = index;
1099 bool doWcs = (m_PAHStage == PAH_THIRD_SOLVE) || !Options::limitedResourcesMode();
1102 emit newLog(
i18n(
"Please wait while WCS data is processed..."));
1103 PAHMessageText->setText(
1104 m_PAHStage == PAH_FIRST_SOLVE
1105 ?
"Calculating WCS for the first image...</p>"
1106 : (m_PAHStage == PAH_SECOND_SOLVE ?
"Calculating WCS for the second image...</p>"
1107 :
"Calculating WCS for the third image...</p>"));
1109 connect(m_AlignView.get(), &AlignView::wcsToggled,
this, &Ekos::PolarAlignmentAssistant::setWCSToggled,
1111 m_AlignView->injectWCS(orientation, ra, dec, pixscale, eastToTheRight);
1118 m_ImageData = image;
1120 if (m_PAHStage == PAH_FIRST_CAPTURE)
1121 setPAHStage(PAH_FIRST_SOLVE);
1122 else if (m_PAHStage == PAH_SECOND_CAPTURE)
1123 setPAHStage(PAH_SECOND_SOLVE);
1124 else if (m_PAHStage == PAH_THIRD_CAPTURE)
1125 setPAHStage(PAH_THIRD_SOLVE);
1128 updateDisplay(m_PAHStage, getPAHMessage());
1131void PolarAlignmentAssistant::setWCSToggled(
bool result)
1133 emit newLog(
i18n(
"WCS data processing is complete."));
1135 disconnect(m_AlignView.get(), &AlignView::wcsToggled,
this, &Ekos::PolarAlignmentAssistant::setWCSToggled);
1137 if (m_PAHStage == PAH_FIRST_CAPTURE || m_PAHStage == PAH_FIRST_SOLVE)
1140 if (result ==
false && m_AlignInstance->wcsSynced() ==
true)
1142 emit newLog(
i18n(
"WCS info is now valid. Capturing next frame..."));
1143 emit captureAndSolve();
1148 polarAlign.addPoint(m_ImageData);
1150 setPAHStage(PAH_FIRST_ROTATE);
1151 auto msg = getPAHMessage();
1152 if (pAHManualSlew->isChecked())
1154 msg =
QString(
"Please rotate your mount about %1 deg in RA")
1155 .
arg(pAHRotation->value());
1158 updateDisplay(m_PAHStage, msg);
1162 else if (m_PAHStage == PAH_SECOND_CAPTURE || m_PAHStage == PAH_SECOND_SOLVE)
1164 setPAHStage(PAH_SECOND_ROTATE);
1165 auto msg = getPAHMessage();
1167 if (pAHManualSlew->isChecked())
1169 msg =
QString(
"Please rotate your mount about %1 deg in RA")
1170 .
arg(pAHRotation->value());
1173 updateDisplay(m_PAHStage, msg);
1175 polarAlign.addPoint(m_ImageData);
1179 else if (m_PAHStage == PAH_THIRD_CAPTURE || m_PAHStage == PAH_THIRD_SOLVE)
1182 if (result ==
false)
1184 emit newLog(
i18n(
"Failed to process World Coordinate System: %1. Try again.", m_ImageData->getLastError()));
1188 polarAlign.addPoint(m_ImageData);
1192 if (calculatePAHError())
1194 setPAHStage(PAH_STAR_SELECT);
1195 polarAlignWidget->updatePAHStage(m_PAHStage);
1196 PAHWidgets->setCurrentWidget(PAHRefreshPage);
1197 refreshText->setText(getPAHMessage());
1198 emit newPAHMessage(getPAHMessage());
1202 emit newLog(
i18n(
"PAA: Failed to find the RA axis. Quitting."));
1208void PolarAlignmentAssistant::setMountStatus(ISD::Mount::Status newState)
1212 case ISD::Mount::MOUNT_PARKING:
1213 case ISD::Mount::MOUNT_SLEWING:
1214 case ISD::Mount::MOUNT_MOVING:
1215 PAHStartB->setEnabled(
false);
1219 if (m_PAHStage == PAH_IDLE)
1220 PAHStartB->setEnabled(
true);
1225QString PolarAlignmentAssistant::getPAHMessage()
const
1231 return introText->text();
1232 case PAH_FIRST_CAPTURE:
1233 return i18n(
"<p>The assistant requires three images to find a solution. Ekos is now capturing the first image...</p>");
1234 case PAH_FIRST_SOLVE:
1235 return i18n(
"<p>Solving the <i>first</i> image...</p>");
1236 case PAH_FIRST_ROTATE:
1237 return i18n(
"<p>Executing the <i>first</i> mount rotation...</p>");
1238 case PAH_FIRST_SETTLE:
1239 return i18n(
"<p>Settling after the <i>first</i> mount rotation.</p>");
1240 case PAH_SECOND_SETTLE:
1241 return i18n(
"<p>Settling after the <i>second</i> mount rotation.</p>");
1242 case PAH_SECOND_CAPTURE:
1243 return i18n(
"<p>Capturing the second image...</p>");
1244 case PAH_SECOND_SOLVE:
1245 return i18n(
"<p>Solving the <i>second</i> image...</p>");
1246 case PAH_SECOND_ROTATE:
1247 return i18n(
"<p>Executing the <i>second</i> mount rotation...</p>");
1248 case PAH_THIRD_CAPTURE:
1249 return i18n(
"<p>Capturing the <i>third</i> and final image...</p>");
1250 case PAH_THIRD_SOLVE:
1251 return i18n(
"<p>Solving the <i>third</i> image...</p>");
1252 case PAH_STAR_SELECT:
1253 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
1254 return i18n(
"<p>Choose your exposure time & select an adjustment method. Then click <i>refresh</i> to begin adjustments.</p>");
1256 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>");
1258 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
1259 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>");
1261 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>");
1269void PolarAlignmentAssistant::setPAHRefreshAlgorithm(RefreshAlgorithm value)
1273 if ((m_PAHStage == PAH_REFRESH) && refreshIteration > 0 && (value != PLATE_SOLVE_ALGORITHM)
1274 && !starCorrespondencePAH.size())
1276 pAHRefreshAlgorithm->setCurrentIndex(PLATE_SOLVE_ALGORITHM);
1277 emit newLog(
i18n(
"Cannot change to MoveStar algorithm once refresh has begun"));
1280 if (m_PAHStage == PAH_REFRESH || m_PAHStage == PAH_STAR_SELECT)
1282 refreshText->setText(getPAHMessage());
1283 emit newPAHMessage(getPAHMessage());
1286 showUpdatedError((value == PLATE_SOLVE_ALGORITHM) ||
1287 (value == MOVE_STAR_UPDATE_ERR_ALGORITHM));
1288 if (value == PLATE_SOLVE_ALGORITHM)
1289 updatePlateSolveTriangle(m_ImageData);
1291 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
Align class handles plate-solving and polar alignment measurement and correction using astrometry....
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 reduce() const
return the equivalent angle between 0 and 360 degrees.
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.
void currentIndexChanged(int index)
void append(QList< T > &&value)
qsizetype count() const const
qsizetype size() const const
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)