File indexing completed on 2025-04-27 12:55:57
0001 /* Ekos Polar Alignment Assistant Tool 0002 SPDX-FileCopyrightText: 2018-2021 Jasem Mutlaq 0003 SPDX-FileCopyrightText: 2020-2021 Hy Murveit 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "polaralignmentassistant.h" 0009 0010 #include "align.h" 0011 #include "alignview.h" 0012 #include "kstars.h" 0013 #include "kstarsdata.h" 0014 #include "ksmessagebox.h" 0015 #include "ekos/auxiliary/stellarsolverprofile.h" 0016 #include "ekos/auxiliary/solverutils.h" 0017 #include "Options.h" 0018 #include "polaralignwidget.h" 0019 #include <ekos_align_debug.h> 0020 0021 #define PAA_VERSION "v3.0" 0022 0023 namespace Ekos 0024 { 0025 0026 const QMap<PolarAlignmentAssistant::Stage, const char *> PolarAlignmentAssistant::PAHStages = 0027 { 0028 {PAH_IDLE, I18N_NOOP("Idle")}, 0029 {PAH_FIRST_CAPTURE, I18N_NOOP("First Capture")}, 0030 {PAH_FIRST_SOLVE, I18N_NOOP("First Solve")}, 0031 {PAH_FIND_CP, I18N_NOOP("Finding CP")}, 0032 {PAH_FIRST_ROTATE, I18N_NOOP("First Rotation")}, 0033 {PAH_FIRST_SETTLE, I18N_NOOP("First Settle")}, 0034 {PAH_SECOND_CAPTURE, I18N_NOOP("Second Capture")}, 0035 {PAH_SECOND_SOLVE, I18N_NOOP("Second Solve")}, 0036 {PAH_SECOND_ROTATE, I18N_NOOP("Second Rotation")}, 0037 {PAH_SECOND_SETTLE, I18N_NOOP("Second Settle")}, 0038 {PAH_THIRD_CAPTURE, I18N_NOOP("Third Capture")}, 0039 {PAH_THIRD_SOLVE, I18N_NOOP("Third Solve")}, 0040 {PAH_STAR_SELECT, I18N_NOOP("Select Star")}, 0041 {PAH_REFRESH, I18N_NOOP("Refreshing")}, 0042 {PAH_POST_REFRESH, I18N_NOOP("Refresh Complete")}, 0043 }; 0044 0045 PolarAlignmentAssistant::PolarAlignmentAssistant(Align *parent, const QSharedPointer<AlignView> &view) : QWidget(parent) 0046 { 0047 setupUi(this); 0048 polarAlignWidget = new PolarAlignWidget(); 0049 mainPALayout->insertWidget(0, polarAlignWidget); 0050 0051 m_AlignInstance = parent; 0052 m_AlignView = view; 0053 0054 showUpdatedError((pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM) || 0055 (pAHRefreshAlgorithm->currentIndex() == MOVE_STAR_UPDATE_ERR_ALGORITHM)); 0056 0057 connect(pAHRefreshAlgorithm, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) 0058 { 0059 setPAHRefreshAlgorithm(static_cast<RefreshAlgorithm>(index)); 0060 }); 0061 starCorrespondencePAH.reset(); 0062 0063 // PAH Connections 0064 PAHWidgets->setCurrentWidget(PAHIntroPage); 0065 connect(this, &PolarAlignmentAssistant::PAHEnabled, [&](bool enabled) 0066 { 0067 PAHStartB->setEnabled(enabled); 0068 directionLabel->setEnabled(enabled); 0069 pAHDirection->setEnabled(enabled); 0070 pAHRotation->setEnabled(enabled); 0071 pAHMountSpeed->setEnabled(enabled); 0072 pAHManualSlew->setEnabled(enabled); 0073 }); 0074 connect(PAHStartB, &QPushButton::clicked, this, &Ekos::PolarAlignmentAssistant::startPAHProcess); 0075 // PAH StopB is just a shortcut for the regular stop 0076 connect(PAHStopB, &QPushButton::clicked, this, &PolarAlignmentAssistant::stopPAHProcess); 0077 connect(PAHRefreshB, &QPushButton::clicked, this, &Ekos::PolarAlignmentAssistant::startPAHRefreshProcess); 0078 // done button for manual slewing during polar alignment: 0079 connect(PAHManualDone, &QPushButton::clicked, this, &Ekos::PolarAlignmentAssistant::setPAHSlewDone); 0080 0081 hemisphere = KStarsData::Instance()->geo()->lat()->Degrees() > 0 ? NORTH_HEMISPHERE : SOUTH_HEMISPHERE; 0082 0083 label_PAHOrigErrorAz->setText("Az: "); 0084 label_PAHOrigErrorAlt->setText("Alt: "); 0085 } 0086 0087 PolarAlignmentAssistant::~PolarAlignmentAssistant() 0088 { 0089 } 0090 0091 void PolarAlignmentAssistant::showUpdatedError(bool show) 0092 { 0093 label_PAHUpdatedErrorTotal->setVisible(show); 0094 PAHUpdatedErrorTotal->setVisible(show); 0095 label_PAHUpdatedErrorAlt->setVisible(show); 0096 PAHUpdatedErrorAlt->setVisible(show); 0097 label_PAHUpdatedErrorAz->setVisible(show); 0098 PAHUpdatedErrorAz->setVisible(show); 0099 } 0100 0101 void PolarAlignmentAssistant::syncMountSpeed(const QString &speed) 0102 { 0103 pAHMountSpeed->blockSignals(true); 0104 pAHMountSpeed->clear(); 0105 pAHMountSpeed->addItems(m_CurrentTelescope->slewRates()); 0106 pAHMountSpeed->setCurrentText(speed); 0107 pAHMountSpeed->blockSignals(false); 0108 } 0109 0110 void PolarAlignmentAssistant::setEnabled(bool enabled) 0111 { 0112 QWidget::setEnabled(enabled); 0113 0114 emit PAHEnabled(enabled); 0115 if (enabled) 0116 { 0117 PAHWidgets->setToolTip(QString()); 0118 FOVDisabledLabel->hide(); 0119 } 0120 else 0121 { 0122 PAHWidgets->setToolTip(i18n("<p>Polar Alignment tool requires a German Equatorial Mount.</p>")); 0123 FOVDisabledLabel->show(); 0124 } 0125 0126 } 0127 0128 // This solver is only used by the refresh plate-solving scheme. 0129 void PolarAlignmentAssistant::startSolver() 0130 { 0131 auto profiles = getDefaultAlignOptionsProfiles(); 0132 auto parameters = profiles.at(Options::solveOptionsProfile()); 0133 // Double search radius 0134 parameters.search_radius = parameters.search_radius * 2; 0135 constexpr double solverTimeout = 10.0; 0136 0137 m_Solver.reset(new SolverUtils(parameters, solverTimeout), &QObject::deleteLater); 0138 connect(m_Solver.get(), &SolverUtils::done, this, &PolarAlignmentAssistant::solverDone, Qt::UniqueConnection); 0139 0140 // Use the scale and position from the most recent solve. 0141 m_Solver->useScale(Options::astrometryUseImageScale(), m_LastPixscale * 0.9, m_LastPixscale * 1.1); 0142 m_Solver->usePosition(true, m_LastRa, m_LastDec); 0143 m_Solver->setHealpix(m_IndexToUse, m_HealpixToUse); 0144 m_Solver->runSolver(m_ImageData); 0145 } 0146 0147 void PolarAlignmentAssistant::solverDone(bool timedOut, bool success, const FITSImage::Solution &solution, 0148 double elapsedSeconds) 0149 { 0150 disconnect(m_Solver.get(), &SolverUtils::done, this, &PolarAlignmentAssistant::solverDone); 0151 0152 if (m_PAHStage != PAH_REFRESH) 0153 return; 0154 0155 if (timedOut || !success) 0156 { 0157 // I'm torn between 1 and 2 for this constant. 0158 // 1: If a solve failed, open up the search next time. 0159 // 2: A common reason for a solve to fail is because a knob was adjusted during the exposure. 0160 // Let it try one more time. 0161 constexpr int MAX_NUM_HEALPIX_FAILURES = 2; 0162 if (++m_NumHealpixFailures >= MAX_NUM_HEALPIX_FAILURES) 0163 { 0164 // Don't use the previous index and healpix next time we solve. 0165 m_IndexToUse = -1; 0166 m_HealpixToUse = -1; 0167 } 0168 } 0169 else 0170 { 0171 // Get the index and healpix from the successful solve. 0172 m_Solver->getSolutionHealpix(&m_IndexToUse, &m_HealpixToUse); 0173 } 0174 0175 if (timedOut) 0176 { 0177 emit newLog(i18n("Refresh solver timed out: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1))); 0178 emit captureAndSolve(); 0179 } 0180 else if (!success) 0181 { 0182 emit newLog(i18n("Refresh solver failed: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1))); 0183 emit updatedErrorsChanged(-1, -1, -1); 0184 emit captureAndSolve(); 0185 } 0186 else 0187 { 0188 m_NumHealpixFailures = 0; 0189 refreshIteration++; 0190 const double ra = solution.ra; 0191 const double dec = solution.dec; 0192 m_LastRa = solution.ra; 0193 m_LastDec = solution.dec; 0194 m_LastOrientation = solution.orientation; 0195 m_LastPixscale = solution.pixscale; 0196 0197 emit newLog(QString("Refresh solver success %1s: ra %2 dec %3 scale %4") 0198 .arg(elapsedSeconds, 0, 'f', 1).arg(ra, 0, 'f', 3) 0199 .arg(dec, 0, 'f', 3).arg(solution.pixscale)); 0200 0201 // RA is input in hours, not degrees! 0202 SkyPoint refreshCoords(ra / 15.0, dec); 0203 double azError = 0, altError = 0; 0204 if (polarAlign.processRefreshCoords(refreshCoords, m_ImageData->getDateTime(), &azError, &altError)) 0205 { 0206 updateRefreshDisplay(azError, altError); 0207 0208 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false : true; 0209 // The 2nd false means don't block. The code below doesn't work if we block 0210 // because wcsToPixel in updateTriangle() depends on the injectWCS being finished. 0211 m_AlignView->injectWCS(solution.orientation, ra, dec, solution.pixscale, eastToTheRight, false); 0212 updatePlateSolveTriangle(m_ImageData); 0213 } 0214 else 0215 emit newLog(QString("Could not estimate mount rotation")); 0216 } 0217 // Start the next refresh capture. 0218 emit captureAndSolve(); 0219 } 0220 0221 void PolarAlignmentAssistant::updatePlateSolveTriangle(const QSharedPointer<FITSData> &image) 0222 { 0223 if (image.isNull()) 0224 return; 0225 0226 // To render the triangle for the plate-solve refresh, we need image coordinates for 0227 // - the original (3rd image) center point (originalPoint) 0228 // - the solution point (solutionPoint), 0229 // - the alt-only solution point (altOnlyPoint), 0230 // - the current center of the image (centerPixel), 0231 // The triangle is made from the first 3 above. 0232 const SkyPoint &originalCoords = polarAlign.getPoint(2); 0233 QPointF originalPixel, solutionPixel, altOnlyPixel, dummy; 0234 QPointF centerPixel(image->width() / 2, image->height() / 2); 0235 if (image->wcsToPixel(originalCoords, originalPixel, dummy) && 0236 image->wcsToPixel(refreshSolution, solutionPixel, dummy) && 0237 image->wcsToPixel(altOnlyRefreshSolution, altOnlyPixel, dummy)) 0238 { 0239 m_AlignView->setCorrectionParams(originalPixel, solutionPixel, altOnlyPixel); 0240 m_AlignView->setStarCircle(centerPixel); 0241 } 0242 else 0243 { 0244 qCDebug(KSTARS_EKOS_ALIGN) << "wcs failed"; 0245 } 0246 } 0247 0248 namespace 0249 { 0250 0251 // These functions paint up/down/left/right-pointing arrows in a box of size w x h. 0252 0253 void upArrow(QPainter *painter, int w, int h) 0254 { 0255 const double wCenter = w / 2, lineTop = 0.38, lineBottom = 0.9; 0256 const double lineLength = h * (lineBottom - lineTop); 0257 painter->drawRect(wCenter - w * .1, h * lineTop, w * .2, lineLength); 0258 QPolygonF arrowHead; 0259 arrowHead << QPointF(wCenter, h * .1) << QPointF(w * .2, h * .4) << QPointF(w * .8, h * .4); 0260 painter->drawConvexPolygon(arrowHead); 0261 } 0262 void downArrow(QPainter *painter, int w, int h) 0263 { 0264 const double wCenter = w / 2, lineBottom = 0.62, lineTop = 0.1; 0265 const double lineLength = h * (lineBottom - lineTop); 0266 painter->drawRect(wCenter - w * .1, h * lineTop, w * .2, lineLength); 0267 QPolygonF arrowHead; 0268 arrowHead << QPointF(wCenter, h * .9) << QPointF(w * .2, h * .6) << QPointF(w * .8, h * .6); 0269 painter->drawConvexPolygon(arrowHead); 0270 } 0271 void leftArrow(QPainter *painter, int w, int h) 0272 { 0273 const double hCenter = h / 2, lineLeft = 0.38, lineRight = 0.9; 0274 const double lineLength = w * (lineRight - lineLeft); 0275 painter->drawRect(h * lineLeft, hCenter - h * .1, lineLength, h * .2); 0276 QPolygonF arrowHead; 0277 arrowHead << QPointF(w * .1, hCenter) << QPointF(w * .4, h * .2) << QPointF(w * .4, h * .8); 0278 painter->drawConvexPolygon(arrowHead); 0279 } 0280 void rightArrow(QPainter *painter, int w, int h) 0281 { 0282 const double hCenter = h / 2, lineLeft = .1, lineRight = .62; 0283 const double lineLength = w * (lineRight - lineLeft); 0284 painter->drawRect(h * lineLeft, hCenter - h * .1, lineLength, h * .2); 0285 QPolygonF arrowHead; 0286 arrowHead << QPointF(w * .9, hCenter) << QPointF(w * .6, h * .2) << QPointF(w * .6, h * .8); 0287 painter->drawConvexPolygon(arrowHead); 0288 } 0289 } // namespace 0290 0291 // Draw arrows that give the user a hint of how he/she needs to adjust the azimuth and altitude 0292 // knobs. The arrows' size changes depending on the azimuth and altitude error. 0293 void PolarAlignmentAssistant::drawArrows(double azError, double altError) 0294 { 0295 constexpr double minError = 20.0 / 3600.0; // 20 arcsec 0296 double absError = fabs(altError); 0297 0298 // these constants are worked out so a 10' error gives a size of 100 0299 // and a 1' error gives a size of 20. 0300 constexpr double largeErr = 10.0 / 60.0, smallErr = 1.0 / 60.0, largeSize = 100, smallSize = 20, c1 = 533.33, c2 = 11.111; 0301 0302 int size = 0; 0303 if (absError > largeErr) 0304 size = largeSize; 0305 else if (absError < smallErr) 0306 size = smallSize; 0307 else size = absError * c1 + c2; 0308 0309 QPixmap altPixmap(size, size); 0310 altPixmap.fill(QColor("transparent")); 0311 QPainter altPainter(&altPixmap); 0312 altPainter.setBrush(arrowAlt->palette().color(QPalette::WindowText)); 0313 0314 if (altError > minError) 0315 downArrow(&altPainter, size, size); 0316 else if (altError < -minError) 0317 upArrow(&altPainter, size, size); 0318 arrowAlt->setPixmap(altPixmap); 0319 0320 absError = fabs(azError); 0321 if (absError > largeErr) 0322 size = largeSize; 0323 else if (absError < smallErr) 0324 size = smallSize; 0325 else size = absError * c1 + c2; 0326 0327 QPixmap azPixmap(size, size); 0328 azPixmap.fill(QColor("transparent")); 0329 QPainter azPainter(&azPixmap); 0330 azPainter.setBrush(arrowAz->palette().color(QPalette::WindowText)); 0331 0332 if (azError > minError) 0333 leftArrow(&azPainter, size, size); 0334 else if (azError < -minError) 0335 rightArrow(&azPainter, size, size); 0336 arrowAz->setPixmap(azPixmap); 0337 } 0338 0339 bool PolarAlignmentAssistant::detectStarsPAHRefresh(QList<Edge> *stars, int num, int x, int y, int *starIndex) 0340 { 0341 stars->clear(); 0342 *starIndex = -1; 0343 0344 // Use the solver settings from the align tab for for "polar-align refresh" star detection. 0345 QVariantMap settings; 0346 settings["optionsProfileIndex"] = Options::solveOptionsProfile(); 0347 settings["optionsProfileGroup"] = static_cast<int>(Ekos::AlignProfiles); 0348 m_ImageData->setSourceExtractorSettings(settings); 0349 0350 QElapsedTimer timer; 0351 m_ImageData->findStars(ALGORITHM_SEP).waitForFinished(); 0352 0353 QString debugString = QString("PAA Refresh: Detected %1 stars (%2s)") 0354 .arg(m_ImageData->getStarCenters().size()).arg(timer.elapsed() / 1000.0, 5, 'f', 3); 0355 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0356 0357 QList<Edge *> detectedStars = m_ImageData->getStarCenters(); 0358 // Let's sort detectedStars by flux, starting with widest 0359 std::sort(detectedStars.begin(), detectedStars.end(), [](const Edge * edge1, const Edge * edge2) -> bool { return edge1->sum > edge2->sum;}); 0360 0361 // Find the closest star to the x,y position, which is where the user clicked on the alignView. 0362 double bestDist = 1e9; 0363 int bestIndex = -1; 0364 for (int i = 0; i < detectedStars.size(); i++) 0365 { 0366 double dx = detectedStars[i]->x - x; 0367 double dy = detectedStars[i]->y - y; 0368 double dist = dx * dx + dy * dy; 0369 if (dist < bestDist) 0370 { 0371 bestDist = dist; 0372 bestIndex = i; 0373 } 0374 } 0375 0376 int starCount = qMin(num, detectedStars.count()); 0377 for (int i = 0; i < starCount; i++) 0378 stars->append(*(detectedStars[i])); 0379 if (bestIndex >= starCount) 0380 { 0381 // If we found the star, but requested 'num' stars, and the user's star 0382 // is lower down in the list, add it and return num+1 stars. 0383 stars->append(*(detectedStars[bestIndex])); 0384 *starIndex = starCount; 0385 } 0386 else 0387 { 0388 *starIndex = bestIndex; 0389 } 0390 debugString = QString("PAA Refresh: User's star(%1,%2) is index %3").arg(x).arg(y).arg(*starIndex); 0391 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0392 0393 detectedStars.clear(); 0394 0395 return stars->count(); 0396 } 0397 0398 void PolarAlignmentAssistant::updateRefreshDisplay(double azE, double altE) 0399 { 0400 drawArrows(azE, altE); 0401 const double errDegrees = hypot(azE, altE); 0402 dms totalError(errDegrees), azError(azE), altError(altE); 0403 PAHUpdatedErrorTotal->setText(totalError.toDMSString()); 0404 PAHUpdatedErrorAlt->setText(altError.toDMSString()); 0405 PAHUpdatedErrorAz->setText(azError.toDMSString()); 0406 0407 QString debugString = QString("PAA Refresh(%1): Corrected az: %2 alt: %4 total: %6") 0408 .arg(refreshIteration).arg(azError.toDMSString()) 0409 .arg(altError.toDMSString()).arg(totalError.toDMSString()); 0410 emit newLog(debugString); 0411 emit updatedErrorsChanged(totalError.Degrees(), azError.Degrees(), altError.Degrees()); 0412 } 0413 0414 void PolarAlignmentAssistant::processPAHRefresh() 0415 { 0416 m_AlignView->setStarCircle(); 0417 PAHUpdatedErrorTotal->clear(); 0418 PAHIteration->clear(); 0419 PAHUpdatedErrorAlt->clear(); 0420 PAHUpdatedErrorAz->clear(); 0421 QString debugString; 0422 imageNumber++; 0423 PAHIteration->setText(QString("Image %1").arg(imageNumber)); 0424 0425 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM) 0426 { 0427 startSolver(); 0428 return; 0429 } 0430 0431 // Always run on the initial iteration to setup the user's star, 0432 // so that if it is enabled later the star could be tracked. 0433 // Flaw here is that if enough stars are not detected, iteration is not incremented, 0434 // so it may repeat. 0435 if ((pAHRefreshAlgorithm->currentIndex() == MOVE_STAR_UPDATE_ERR_ALGORITHM) || (refreshIteration == 0)) 0436 { 0437 constexpr int MIN_PAH_REFRESH_STARS = 10; 0438 0439 QList<Edge> stars; 0440 // Index of user's star in the detected stars list. In the first iteration 0441 // the stars haven't moved and we can just use the location of the click. 0442 // Later we'll need to find the star with starCorrespondence. 0443 int clickedStarIndex; 0444 detectStarsPAHRefresh(&stars, 100, correctionFrom.x(), correctionFrom.y(), &clickedStarIndex); 0445 if (clickedStarIndex < 0) 0446 { 0447 debugString = QString("PAA Refresh(%1): Didn't find the clicked star near %2,%3") 0448 .arg(refreshIteration).arg(correctionFrom.x()).arg(correctionFrom.y()); 0449 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0450 0451 emit newAlignTableResult(Align::ALIGN_RESULT_FAILED); 0452 emit captureAndSolve(); 0453 return; 0454 } 0455 0456 debugString = QString("PAA Refresh(%1): Refresh star(%2,%3) is index %4 with offset %5 %6") 0457 .arg(refreshIteration + 1).arg(correctionFrom.x(), 4, 'f', 0) 0458 .arg(correctionFrom.y(), 4, 'f', 0).arg(clickedStarIndex) 0459 .arg(stars[clickedStarIndex].x - correctionFrom.x(), 4, 'f', 0) 0460 .arg(stars[clickedStarIndex].y - correctionFrom.y(), 4, 'f', 0); 0461 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0462 0463 if (stars.size() > MIN_PAH_REFRESH_STARS) 0464 { 0465 int dx = 0; 0466 int dy = 0; 0467 int starIndex = -1; 0468 0469 if (refreshIteration++ == 0) 0470 { 0471 // First iteration. Setup starCorrespondence so we can find the user's star. 0472 // clickedStarIndex should be the index of a detected star near where the user clicked. 0473 starCorrespondencePAH.initialize(stars, clickedStarIndex); 0474 if (clickedStarIndex >= 0) 0475 { 0476 setupCorrectionGraphics(QPointF(stars[clickedStarIndex].x, stars[clickedStarIndex].y)); 0477 emit newCorrectionVector(QLineF(correctionFrom, correctionTo)); 0478 emit newFrame(m_AlignView); 0479 } 0480 } 0481 else 0482 { 0483 // Or, in other iterations find the movement of the "user's star". 0484 // The 0.40 means it's OK if star correspondence only finds 40% of the 0485 // reference stars (as we'd have more issues near the image edge otherwise). 0486 QVector<int> starMap; 0487 starCorrespondencePAH.find(stars, 200.0, &starMap, false, 0.40); 0488 0489 // Go through the starMap, and find the user's star, and compare its position 0490 // to its initial position. 0491 for (int i = 0; i < starMap.size(); ++i) 0492 { 0493 if (starMap[i] == starCorrespondencePAH.guideStar()) 0494 { 0495 dx = stars[i].x - correctionFrom.x(); 0496 dy = stars[i].y - correctionFrom.y(); 0497 starIndex = i; 0498 break; 0499 } 0500 } 0501 if (starIndex == -1) 0502 { 0503 bool allOnes = true; 0504 for (int i = 0; i < starMap.size(); ++i) 0505 { 0506 if (starMap[i] != -1) 0507 allOnes = false; 0508 } 0509 debugString = QString("PAA Refresh(%1): starMap %2").arg(refreshIteration).arg(allOnes ? "ALL -1's" : "not all -1's"); 0510 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0511 } 0512 } 0513 0514 if (starIndex >= 0) 0515 { 0516 // Annotate the user's star on the alignview. 0517 m_AlignView->setStarCircle(QPointF(stars[starIndex].x, stars[starIndex].y)); 0518 debugString = QString("PAA Refresh(%1): User's star is now at %2,%3, with movement = %4,%5").arg(refreshIteration) 0519 .arg(stars[starIndex].x, 4, 'f', 0).arg(stars[starIndex].y, 4, 'f', 0).arg(dx).arg(dy); 0520 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0521 0522 double azE, altE; 0523 if (polarAlign.pixelError(m_AlignView->keptImage(), QPointF(stars[starIndex].x, stars[starIndex].y), 0524 correctionTo, &azE, &altE)) 0525 { 0526 updateRefreshDisplay(azE, altE); 0527 debugString = QString("PAA Refresh(%1): %2,%3 --> %4,%5 @ %6,%7") 0528 .arg(refreshIteration).arg(correctionFrom.x(), 4, 'f', 0).arg(correctionFrom.y(), 4, 'f', 0) 0529 .arg(correctionTo.x(), 4, 'f', 0).arg(correctionTo.y(), 4, 'f', 0) 0530 .arg(stars[starIndex].x, 4, 'f', 0).arg(stars[starIndex].y, 4, 'f', 0); 0531 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0532 } 0533 else 0534 { 0535 debugString = QString("PAA Refresh(%1): pixelError failed to estimate the remaining correction").arg(refreshIteration); 0536 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0537 } 0538 } 0539 else 0540 { 0541 if (refreshIteration > 1) 0542 { 0543 debugString = QString("PAA Refresh(%1): Didn't find the user's star").arg(refreshIteration); 0544 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0545 } 0546 } 0547 } 0548 else 0549 { 0550 debugString = QString("PAA Refresh(%1): Too few stars detected (%2)").arg(refreshIteration).arg(stars.size()); 0551 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0552 emit updatedErrorsChanged(-1, -1, -1); 0553 } 0554 } 0555 // Finally start the next capture 0556 emit captureAndSolve(); 0557 } 0558 0559 bool PolarAlignmentAssistant::processSolverFailure() 0560 { 0561 if ((m_PAHStage == PAH_FIRST_CAPTURE || 0562 m_PAHStage == PAH_SECOND_CAPTURE || 0563 m_PAHStage == PAH_THIRD_CAPTURE || 0564 m_PAHStage == PAH_FIRST_SOLVE || 0565 m_PAHStage == PAH_SECOND_SOLVE || 0566 m_PAHStage == PAH_THIRD_SOLVE) 0567 && ++m_PAHRetrySolveCounter < 4) 0568 { 0569 emit newLog(i18n("PAA: Solver failed, retrying.")); 0570 emit captureAndSolve(); 0571 return true; 0572 } 0573 0574 if (m_PAHStage != PAH_IDLE) 0575 { 0576 emit newLog(i18n("PAA: Stopping, solver failed too many times.")); 0577 stopPAHProcess(); 0578 } 0579 0580 return false; 0581 } 0582 0583 void PolarAlignmentAssistant::setPAHStage(Stage stage) 0584 { 0585 if (stage != m_PAHStage) 0586 { 0587 m_PAHStage = stage; 0588 polarAlignWidget->updatePAHStage(stage); 0589 emit newPAHStage(m_PAHStage); 0590 } 0591 } 0592 0593 void PolarAlignmentAssistant::processMountRotation(const dms &ra, double settleDuration) 0594 { 0595 double deltaAngle = fabs(ra.deltaAngle(targetPAH.ra()).Degrees()); 0596 0597 QString rotProgressMessage; 0598 QString rotDoneMessage; 0599 Stage nextCapture; 0600 Stage nextSettle; 0601 0602 if (m_PAHStage == PAH_FIRST_ROTATE) 0603 { 0604 rotProgressMessage = "First mount rotation remaining degrees:"; 0605 rotDoneMessage = i18n("Mount first rotation is complete."); 0606 nextCapture = PAH_SECOND_CAPTURE; 0607 nextSettle = PAH_FIRST_SETTLE; 0608 } 0609 else if (m_PAHStage == PAH_SECOND_ROTATE) 0610 { 0611 rotProgressMessage = "Second mount rotation remaining degrees:"; 0612 rotDoneMessage = i18n("Mount second rotation is complete."); 0613 nextCapture = PAH_THIRD_CAPTURE; 0614 nextSettle = PAH_SECOND_SETTLE; 0615 } 0616 else return; 0617 0618 if (m_PAHStage == PAH_FIRST_ROTATE || m_PAHStage == PAH_SECOND_ROTATE) 0619 { 0620 // only wait for telescope to slew to new position if manual slewing is switched off 0621 if(!pAHManualSlew->isChecked()) 0622 { 0623 qCDebug(KSTARS_EKOS_ALIGN) << rotProgressMessage << deltaAngle; 0624 if (deltaAngle <= PAH_ROTATION_THRESHOLD) 0625 { 0626 m_CurrentTelescope->StopWE(); 0627 emit newLog(rotDoneMessage); 0628 0629 if (settleDuration <= 0) 0630 { 0631 setPAHStage(nextCapture); 0632 updateDisplay(m_PAHStage, getPAHMessage()); 0633 } 0634 else 0635 { 0636 setPAHStage(nextSettle); 0637 updateDisplay(m_PAHStage, getPAHMessage()); 0638 0639 emit newLog(i18n("Settling...")); 0640 QTimer::singleShot(settleDuration, [nextCapture, this]() 0641 { 0642 setPAHStage(nextCapture); 0643 updateDisplay(m_PAHStage, getPAHMessage()); 0644 }); 0645 } 0646 } 0647 // If for some reason we didn't stop, let's stop if we get too far 0648 else if (deltaAngle > pAHRotation->value() * 1.25) 0649 { 0650 m_CurrentTelescope->abort(); 0651 emit newLog(i18n("Mount aborted. Reverse RA axis direction and try again.")); 0652 stopPAHProcess(); 0653 } 0654 return; 0655 } // endif not manual slew 0656 } 0657 } 0658 0659 bool PolarAlignmentAssistant::checkPAHForMeridianCrossing() 0660 { 0661 // Make sure using -180 to 180 for hourAngle and DEC. (Yes dec should be between -90 and 90). 0662 double hourAngle = m_CurrentTelescope->hourAngle().Degrees(); 0663 while (hourAngle < -180) 0664 hourAngle += 360; 0665 while (hourAngle > 180) 0666 hourAngle -= 360; 0667 double ra = 0, dec = 0; 0668 m_CurrentTelescope->getEqCoords(&ra, &dec); 0669 while (dec < -180) 0670 dec += 360; 0671 while (dec > 180) 0672 dec -= 360; 0673 0674 // Don't do this check within 2 degrees of the poles. 0675 bool nearThePole = fabs(dec) > 88; 0676 if (nearThePole) 0677 return false; 0678 0679 double degreesPerSlew = pAHRotation->value(); 0680 bool closeToMeridian = fabs(hourAngle) < 2.0 * degreesPerSlew; 0681 bool goingWest = pAHDirection->currentIndex() == 0; 0682 0683 // If the pier is on the east side (pointing west) and will slew west and is within 2 slews of the HA=0, 0684 // or on the west side (pointing east) and will slew east, and is within 2 slews of HA=0 0685 // then warn and give the user a chance to cancel. 0686 bool wouldCrossMeridian = 0687 ((m_CurrentTelescope->pierSide() == ISD::Mount::PIER_EAST && !goingWest && closeToMeridian) || 0688 (m_CurrentTelescope->pierSide() == ISD::Mount::PIER_WEST && goingWest && closeToMeridian) || 0689 (m_CurrentTelescope->pierSide() == ISD::Mount::PIER_UNKNOWN && closeToMeridian)); 0690 0691 return wouldCrossMeridian; 0692 } 0693 0694 void PolarAlignmentAssistant::updateDisplay(Stage stage, const QString &message) 0695 { 0696 switch(stage) 0697 { 0698 case PAH_FIRST_ROTATE: 0699 case PAH_SECOND_ROTATE: 0700 if (pAHManualSlew->isChecked()) 0701 { 0702 polarAlignWidget->updatePAHStage(stage); 0703 PAHWidgets->setCurrentWidget(PAHManualRotatePage); 0704 manualRotateText->setText(message); 0705 emit newPAHMessage(message); 0706 return; 0707 } 0708 // fall through 0709 case PAH_FIRST_CAPTURE: 0710 case PAH_SECOND_CAPTURE: 0711 case PAH_THIRD_CAPTURE: 0712 case PAH_FIRST_SOLVE: 0713 case PAH_SECOND_SOLVE: 0714 case PAH_THIRD_SOLVE: 0715 polarAlignWidget->updatePAHStage(stage); 0716 PAHWidgets->setCurrentWidget(PAHMessagePage); 0717 PAHMessageText->setText(message); 0718 emit newPAHMessage(message); 0719 break; 0720 0721 default: 0722 break; 0723 0724 } 0725 } 0726 0727 void PolarAlignmentAssistant::startPAHProcess() 0728 { 0729 qCInfo(KSTARS_EKOS_ALIGN) << QString("Starting Polar Alignment Assistant process %1 ...").arg(PAA_VERSION); 0730 0731 auto executePAH = [ this ]() 0732 { 0733 setPAHStage(PAH_FIRST_CAPTURE); 0734 0735 if (Options::limitedResourcesMode()) 0736 emit newLog(i18n("Warning: Equatorial Grid Lines will not be drawn due to limited resources mode.")); 0737 0738 if (m_CurrentTelescope->hasAlignmentModel()) 0739 { 0740 emit newLog(i18n("Clearing mount Alignment Model...")); 0741 m_CurrentTelescope->clearAlignmentModel(); 0742 } 0743 0744 // Unpark 0745 m_CurrentTelescope->unpark(); 0746 0747 // Set tracking ON if not already 0748 if (m_CurrentTelescope->canControlTrack() && m_CurrentTelescope->isTracking() == false) 0749 m_CurrentTelescope->setTrackEnabled(true); 0750 0751 PAHStartB->setEnabled(false); 0752 PAHStopB->setEnabled(true); 0753 0754 PAHUpdatedErrorTotal->clear(); 0755 PAHUpdatedErrorAlt->clear(); 0756 PAHUpdatedErrorAz->clear(); 0757 PAHOrigErrorTotal->clear(); 0758 PAHOrigErrorAlt->clear(); 0759 PAHOrigErrorAz->clear(); 0760 PAHIteration->setText(""); 0761 0762 updateDisplay(m_PAHStage, getPAHMessage()); 0763 0764 m_AlignView->setCorrectionParams(QPointF(), QPointF(), QPointF()); 0765 0766 m_PAHRetrySolveCounter = 0; 0767 emit captureAndSolve(); 0768 }; 0769 0770 // Right off the bat, check if this alignment might cause a pier crash. 0771 // If we're crossing the meridian, warn unless within 5-degrees from the pole. 0772 if (checkPAHForMeridianCrossing()) 0773 { 0774 // Continue 0775 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executePAH]() 0776 { 0777 KSMessageBox::Instance()->disconnect(this); 0778 executePAH(); 0779 }); 0780 0781 KSMessageBox::Instance()->warningContinueCancel(i18n("This could cause the telescope to cross the meridian."), 0782 i18n("Warning"), 15); 0783 } 0784 else 0785 executePAH(); 0786 } 0787 0788 void PolarAlignmentAssistant::stopPAHProcess() 0789 { 0790 if (m_PAHStage == PAH_IDLE) 0791 return; 0792 0793 // Only display dialog if user clicked. 0794 // if ((static_cast<QPushButton *>(sender()) == PAHStopB) && KMessageBox::questionYesNo(KStars::Instance(), 0795 // i18n("Are you sure you want to stop the polar alignment process?"), 0796 // i18n("Polar Alignment Assistant"), KStandardGuiItem::yes(), KStandardGuiItem::no(), 0797 // "restart_PAA_process_dialog") == KMessageBox::No) 0798 // return; 0799 0800 if (m_PAHStage == PAH_REFRESH) 0801 { 0802 setPAHStage(PAH_POST_REFRESH); 0803 polarAlignWidget->updatePAHStage(m_PAHStage); 0804 } 0805 qCInfo(KSTARS_EKOS_ALIGN) << "Stopping Polar Alignment Assistant process..."; 0806 0807 0808 if (m_CurrentTelescope && m_CurrentTelescope->isInMotion()) 0809 m_CurrentTelescope->abort(); 0810 0811 setPAHStage(PAH_IDLE); 0812 polarAlignWidget->updatePAHStage(m_PAHStage); 0813 0814 PAHStartB->setEnabled(true); 0815 PAHStopB->setEnabled(false); 0816 PAHRefreshB->setEnabled(true); 0817 PAHWidgets->setCurrentWidget(PAHIntroPage); 0818 emit newPAHMessage(introText->text()); 0819 0820 m_AlignView->reset(); 0821 m_AlignView->setRefreshEnabled(false); 0822 0823 emit newFrame(m_AlignView); 0824 disconnect(m_AlignView.get(), &AlignView::trackingStarSelected, this, 0825 &Ekos::PolarAlignmentAssistant::setPAHCorrectionOffset); 0826 disconnect(m_AlignView.get(), &AlignView::newCorrectionVector, this, &Ekos::PolarAlignmentAssistant::newCorrectionVector); 0827 0828 if (Options::pAHAutoPark()) 0829 { 0830 m_CurrentTelescope->park(); 0831 emit newLog(i18n("Parking the mount...")); 0832 } 0833 } 0834 0835 void PolarAlignmentAssistant::rotatePAH() 0836 { 0837 double TargetDiffRA = pAHRotation->value(); 0838 bool westMeridian = pAHDirection->currentIndex() == 0; 0839 0840 // West 0841 if (westMeridian) 0842 TargetDiffRA *= -1; 0843 // East 0844 else 0845 TargetDiffRA *= 1; 0846 0847 // JM 2018-05-03: Hemispheres shouldn't affect rotation direction in RA 0848 0849 // if Manual slewing is selected, don't move the mount 0850 if (pAHManualSlew->isChecked()) 0851 { 0852 return; 0853 } 0854 0855 const SkyPoint telescopeCoord = m_CurrentTelescope->currentCoordinates(); 0856 0857 // TargetDiffRA is in degrees 0858 dms newTelescopeRA = (telescopeCoord.ra() + dms(TargetDiffRA)).reduce(); 0859 0860 targetPAH.setRA(newTelescopeRA); 0861 targetPAH.setDec(telescopeCoord.dec()); 0862 0863 //m_CurrentTelescope->Slew(&targetPAH); 0864 // Set Selected Speed 0865 if (pAHMountSpeed->currentIndex() >= 0) 0866 m_CurrentTelescope->setSlewRate(pAHMountSpeed->currentIndex()); 0867 // Go to direction 0868 m_CurrentTelescope->MoveWE(westMeridian ? ISD::Mount::MOTION_WEST : ISD::Mount::MOTION_EAST, 0869 ISD::Mount::MOTION_START); 0870 0871 emit newLog(i18n("Please wait until mount completes rotating to RA (%1) DE (%2)", targetPAH.ra().toHMSString(), 0872 targetPAH.dec().toDMSString())); 0873 } 0874 0875 void PolarAlignmentAssistant::setupCorrectionGraphics(const QPointF &pixel) 0876 { 0877 polarAlign.refreshSolution(&refreshSolution, &altOnlyRefreshSolution); 0878 0879 // We use the previously stored image (the 3rd PAA image) 0880 // so we can continue to estimate the correction even after 0881 // capturing new images during the refresh stage. 0882 const QSharedPointer<FITSData> &imageData = m_AlignView->keptImage(); 0883 0884 // Just the altitude correction 0885 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionAltTo, true)) 0886 { 0887 qCInfo(KSTARS_EKOS_ALIGN) << QString(i18n("PAA: Failed to findCorrectedPixel.")); 0888 return; 0889 } 0890 // The whole correction. 0891 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionTo)) 0892 { 0893 qCInfo(KSTARS_EKOS_ALIGN) << QString(i18n("PAA: Failed to findCorrectedPixel.")); 0894 return; 0895 } 0896 QString debugString = QString("PAA: Correction: %1,%2 --> %3,%4 (alt only %5,%6)") 0897 .arg(pixel.x(), 4, 'f', 0).arg(pixel.y(), 4, 'f', 0) 0898 .arg(correctionTo.x(), 4, 'f', 0).arg(correctionTo.y(), 4, 'f', 0) 0899 .arg(correctionAltTo.x(), 4, 'f', 0).arg(correctionAltTo.y(), 4, 'f', 0); 0900 qCDebug(KSTARS_EKOS_ALIGN) << debugString; 0901 correctionFrom = pixel; 0902 0903 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM) 0904 updatePlateSolveTriangle(imageData); 0905 else 0906 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo); 0907 0908 return; 0909 } 0910 0911 0912 bool PolarAlignmentAssistant::calculatePAHError() 0913 { 0914 // Hold on to the imageData so we can use it during the refresh phase. 0915 m_AlignView->holdOnToImage(); 0916 0917 if (!polarAlign.findAxis()) 0918 { 0919 emit newLog(i18n("PAA: Failed to find RA Axis center.")); 0920 stopPAHProcess(); 0921 return false; 0922 } 0923 0924 double azimuthError, altitudeError; 0925 polarAlign.calculateAzAltError(&azimuthError, &altitudeError); 0926 drawArrows(azimuthError, altitudeError); 0927 dms polarError(hypot(altitudeError, azimuthError)); 0928 dms azError(azimuthError), altError(altitudeError); 0929 0930 // No need to toggle EQGrid 0931 // if (m_AlignView->isEQGridShown() == false && !Options::limitedResourcesMode()) 0932 // m_AlignView->toggleEQGrid(); 0933 0934 QString msg = QString("%1. Azimuth: %2 Altitude: %3") 0935 .arg(polarError.toDMSString()).arg(azError.toDMSString()) 0936 .arg(altError.toDMSString()); 0937 emit newLog(QString("Polar Alignment Error: %1").arg(msg)); 0938 0939 polarAlign.setMaxPixelSearchRange(polarError.Degrees() + 1); 0940 0941 // These are viewed during the refresh phase. 0942 PAHOrigErrorTotal->setText(polarError.toDMSString()); 0943 PAHOrigErrorAlt->setText(altError.toDMSString()); 0944 PAHOrigErrorAz->setText(azError.toDMSString()); 0945 0946 setupCorrectionGraphics(QPointF(m_ImageData->width() / 2, m_ImageData->height() / 2)); 0947 0948 // Find Celestial pole location and mount's RA axis 0949 SkyPoint CP(0, (hemisphere == NORTH_HEMISPHERE) ? 90 : -90); 0950 QPointF imagePoint, celestialPolePoint; 0951 m_ImageData->wcsToPixel(CP, celestialPolePoint, imagePoint); 0952 if (m_ImageData->contains(celestialPolePoint)) 0953 { 0954 m_AlignView->setCelestialPole(celestialPolePoint); 0955 QPointF raAxis; 0956 if (polarAlign.findCorrectedPixel(m_ImageData, celestialPolePoint, &raAxis)) 0957 m_AlignView->setRaAxis(raAxis); 0958 } 0959 0960 connect(m_AlignView.get(), &AlignView::trackingStarSelected, this, &Ekos::PolarAlignmentAssistant::setPAHCorrectionOffset); 0961 emit polarResultUpdated(QLineF(correctionFrom, correctionTo), polarError.Degrees(), azError.Degrees(), altError.Degrees()); 0962 0963 connect(m_AlignView.get(), &AlignView::newCorrectionVector, this, &Ekos::PolarAlignmentAssistant::newCorrectionVector, 0964 Qt::UniqueConnection); 0965 syncCorrectionVector(); 0966 emit newFrame(m_AlignView); 0967 0968 return true; 0969 } 0970 0971 void PolarAlignmentAssistant::syncCorrectionVector() 0972 { 0973 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM) 0974 return; 0975 emit newCorrectionVector(QLineF(correctionFrom, correctionTo)); 0976 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo); 0977 } 0978 0979 void PolarAlignmentAssistant::setPAHCorrectionOffsetPercentage(double dx, double dy) 0980 { 0981 double x = dx * m_AlignView->zoomedWidth(); 0982 double y = dy * m_AlignView->zoomedHeight(); 0983 setPAHCorrectionOffset(static_cast<int>(round(x)), static_cast<int>(round(y))); 0984 } 0985 0986 void PolarAlignmentAssistant::setPAHCorrectionOffset(int x, int y) 0987 { 0988 if (m_PAHStage == PAH_REFRESH) 0989 { 0990 emit newLog(i18n("Polar-alignment star cannot be updated during refresh phase as it might affect error measurements.")); 0991 } 0992 else 0993 { 0994 setupCorrectionGraphics(QPointF(x, y)); 0995 emit newCorrectionVector(QLineF(correctionFrom, correctionTo)); 0996 emit newFrame(m_AlignView); 0997 } 0998 } 0999 1000 void PolarAlignmentAssistant::setPAHSlewDone() 1001 { 1002 emit newPAHMessage("Manual slew done."); 1003 switch(m_PAHStage) 1004 { 1005 case PAH_FIRST_ROTATE : 1006 setPAHStage(PAH_SECOND_CAPTURE); 1007 emit newLog(i18n("First manual rotation done.")); 1008 updateDisplay(m_PAHStage, getPAHMessage()); 1009 break; 1010 case PAH_SECOND_ROTATE : 1011 setPAHStage(PAH_THIRD_CAPTURE); 1012 emit newLog(i18n("Second manual rotation done.")); 1013 updateDisplay(m_PAHStage, getPAHMessage()); 1014 break; 1015 default : 1016 return; // no other stage should be able to trigger this event 1017 } 1018 } 1019 1020 1021 1022 void PolarAlignmentAssistant::startPAHRefreshProcess() 1023 { 1024 qCInfo(KSTARS_EKOS_ALIGN) << "Starting Polar Alignment Assistant refreshing..."; 1025 1026 refreshIteration = 0; 1027 imageNumber = 0; 1028 m_NumHealpixFailures = 0; 1029 1030 setPAHStage(PAH_REFRESH); 1031 polarAlignWidget->updatePAHStage(m_PAHStage); 1032 auto message = getPAHMessage(); 1033 refreshText->setText(message); 1034 emit newPAHMessage(message); 1035 1036 PAHRefreshB->setEnabled(false); 1037 1038 // Hide EQ Grids if shown 1039 if (m_AlignView->isEQGridShown()) 1040 m_AlignView->toggleEQGrid(); 1041 1042 m_AlignView->setRefreshEnabled(true); 1043 1044 Options::setAstrometrySolverWCS(false); 1045 Options::setAutoWCS(false); 1046 1047 // We for refresh, just capture really 1048 emit captureAndSolve(); 1049 } 1050 1051 void PolarAlignmentAssistant::processPAHStage(double orientation, double ra, double dec, double pixscale, 1052 bool eastToTheRight, short healpix, short index) 1053 { 1054 if (m_PAHStage == PAH_FIND_CP) 1055 { 1056 emit newLog( 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); 1060 return; 1061 } 1062 1063 if (m_PAHStage == PAH_FIRST_SOLVE || m_PAHStage == PAH_SECOND_SOLVE || m_PAHStage == PAH_THIRD_SOLVE) 1064 { 1065 // Used by refresh, when looking at the 3rd image. 1066 m_LastRa = ra; 1067 m_LastDec = dec; 1068 m_LastOrientation = orientation; 1069 m_LastPixscale = pixscale; 1070 m_HealpixToUse = healpix; 1071 m_IndexToUse = index; 1072 1073 bool doWcs = (m_PAHStage == PAH_THIRD_SOLVE) || !Options::limitedResourcesMode(); 1074 if (doWcs) 1075 { 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>")); 1082 } 1083 connect(m_AlignView.get(), &AlignView::wcsToggled, this, &Ekos::PolarAlignmentAssistant::setWCSToggled, 1084 Qt::UniqueConnection); 1085 m_AlignView->injectWCS(orientation, ra, dec, pixscale, eastToTheRight); 1086 return; 1087 } 1088 } 1089 1090 void PolarAlignmentAssistant::setImageData(const QSharedPointer<FITSData> &image) 1091 { 1092 m_ImageData = image; 1093 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); 1100 else 1101 return; 1102 updateDisplay(m_PAHStage, getPAHMessage()); 1103 } 1104 1105 void PolarAlignmentAssistant::setWCSToggled(bool result) 1106 { 1107 emit newLog(i18n("WCS data processing is complete.")); 1108 1109 disconnect(m_AlignView.get(), &AlignView::wcsToggled, this, &Ekos::PolarAlignmentAssistant::setWCSToggled); 1110 1111 if (m_PAHStage == PAH_FIRST_CAPTURE || m_PAHStage == PAH_FIRST_SOLVE) 1112 { 1113 // We need WCS to be synced first 1114 if (result == false && m_AlignInstance->wcsSynced() == true) 1115 { 1116 emit newLog(i18n("WCS info is now valid. Capturing next frame...")); 1117 emit captureAndSolve(); 1118 return; 1119 } 1120 1121 polarAlign.reset(); 1122 polarAlign.addPoint(m_ImageData); 1123 1124 setPAHStage(PAH_FIRST_ROTATE); 1125 auto msg = getPAHMessage(); 1126 if (pAHManualSlew->isChecked()) 1127 { 1128 msg = QString("Please rotate your mount about %1 deg in RA") 1129 .arg(pAHRotation->value()); 1130 emit newLog(msg); 1131 } 1132 updateDisplay(m_PAHStage, msg); 1133 1134 rotatePAH(); 1135 } 1136 else if (m_PAHStage == PAH_SECOND_CAPTURE || m_PAHStage == PAH_SECOND_SOLVE) 1137 { 1138 setPAHStage(PAH_SECOND_ROTATE); 1139 auto msg = getPAHMessage(); 1140 1141 if (pAHManualSlew->isChecked()) 1142 { 1143 msg = QString("Please rotate your mount about %1 deg in RA") 1144 .arg(pAHRotation->value()); 1145 emit newLog(msg); 1146 } 1147 updateDisplay(m_PAHStage, msg); 1148 1149 polarAlign.addPoint(m_ImageData); 1150 1151 rotatePAH(); 1152 } 1153 else if (m_PAHStage == PAH_THIRD_CAPTURE || m_PAHStage == PAH_THIRD_SOLVE) 1154 { 1155 // Critical error 1156 if (result == false) 1157 { 1158 emit newLog(i18n("Failed to process World Coordinate System: %1. Try again.", m_ImageData->getLastError())); 1159 return; 1160 } 1161 1162 polarAlign.addPoint(m_ImageData); 1163 1164 // We have 3 points which uniquely defines a circle with its center representing the RA Axis 1165 // We have celestial pole location. So correction vector is just the vector between these two points 1166 if (calculatePAHError()) 1167 { 1168 setPAHStage(PAH_STAR_SELECT); 1169 polarAlignWidget->updatePAHStage(m_PAHStage); 1170 PAHWidgets->setCurrentWidget(PAHRefreshPage); 1171 refreshText->setText(getPAHMessage()); 1172 emit newPAHMessage(getPAHMessage()); 1173 } 1174 else 1175 { 1176 emit newLog(i18n("PAA: Failed to find the RA axis. Quitting.")); 1177 stopPAHProcess(); 1178 } 1179 } 1180 } 1181 1182 void PolarAlignmentAssistant::setMountStatus(ISD::Mount::Status newState) 1183 { 1184 switch (newState) 1185 { 1186 case ISD::Mount::MOUNT_PARKING: 1187 case ISD::Mount::MOUNT_SLEWING: 1188 case ISD::Mount::MOUNT_MOVING: 1189 PAHStartB->setEnabled(false); 1190 break; 1191 1192 default: 1193 if (m_PAHStage == PAH_IDLE) 1194 PAHStartB->setEnabled(true); 1195 break; 1196 } 1197 } 1198 1199 QString PolarAlignmentAssistant::getPAHMessage() const 1200 { 1201 switch (m_PAHStage) 1202 { 1203 case PAH_IDLE: 1204 case PAH_FIND_CP: 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>"); 1229 else 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>"); 1231 case PAH_REFRESH: 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>"); 1234 else 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>"); 1236 default: 1237 break; 1238 } 1239 1240 return QString(); 1241 } 1242 1243 void PolarAlignmentAssistant::setPAHRefreshAlgorithm(RefreshAlgorithm value) 1244 { 1245 // If the star-correspondence method of tracking polar alignment error wasn't initialized, 1246 // at the start, it can't be turned on mid process. 1247 if ((m_PAHStage == PAH_REFRESH) && refreshIteration > 0 && (value != PLATE_SOLVE_ALGORITHM) 1248 && !starCorrespondencePAH.size()) 1249 { 1250 pAHRefreshAlgorithm->setCurrentIndex(PLATE_SOLVE_ALGORITHM); 1251 emit newLog(i18n("Cannot change to MoveStar algorithm once refresh has begun")); 1252 return; 1253 } 1254 if (m_PAHStage == PAH_REFRESH || m_PAHStage == PAH_STAR_SELECT) 1255 { 1256 refreshText->setText(getPAHMessage()); 1257 emit newPAHMessage(getPAHMessage()); 1258 } 1259 1260 showUpdatedError((value == PLATE_SOLVE_ALGORITHM) || 1261 (value == MOVE_STAR_UPDATE_ERR_ALGORITHM)); 1262 if (value == PLATE_SOLVE_ALGORITHM) 1263 updatePlateSolveTriangle(m_ImageData); 1264 else 1265 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo); 1266 1267 } 1268 1269 }