Kstars

polaralignmentassistant.cpp
1/* Ekos Polar Alignment Assistant Tool
2 SPDX-FileCopyrightText: 2018-2021 Jasem Mutlaq
3 SPDX-FileCopyrightText: 2020-2021 Hy Murveit
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "polaralignmentassistant.h"
9
10#include "align.h"
11#include "alignview.h"
12#include "kstars.h"
13#include "kstarsdata.h"
14#include "ksmessagebox.h"
15#include "ekos/auxiliary/stellarsolverprofile.h"
16#include "ekos/auxiliary/solverutils.h"
17#include "Options.h"
18#include "polaralignwidget.h"
19#include <ekos_align_debug.h>
20
21#define PAA_VERSION "v3.0"
22
23namespace Ekos
24{
25
26const QMap<PolarAlignmentAssistant::Stage, const char *> PolarAlignmentAssistant::PAHStages =
27{
28 {PAH_IDLE, I18N_NOOP("Idle")},
29 {PAH_FIRST_CAPTURE, I18N_NOOP("First Capture")},
30 {PAH_FIRST_SOLVE, I18N_NOOP("First Solve")},
31 {PAH_FIND_CP, I18N_NOOP("Finding CP")},
32 {PAH_FIRST_ROTATE, I18N_NOOP("First Rotation")},
33 {PAH_FIRST_SETTLE, I18N_NOOP("First Settle")},
34 {PAH_SECOND_CAPTURE, I18N_NOOP("Second Capture")},
35 {PAH_SECOND_SOLVE, I18N_NOOP("Second Solve")},
36 {PAH_SECOND_ROTATE, I18N_NOOP("Second Rotation")},
37 {PAH_SECOND_SETTLE, I18N_NOOP("Second Settle")},
38 {PAH_THIRD_CAPTURE, I18N_NOOP("Third Capture")},
39 {PAH_THIRD_SOLVE, I18N_NOOP("Third Solve")},
40 {PAH_STAR_SELECT, I18N_NOOP("Select Star")},
41 {PAH_REFRESH, I18N_NOOP("Refreshing")},
42 {PAH_POST_REFRESH, I18N_NOOP("Refresh Complete")},
43};
44
45PolarAlignmentAssistant::PolarAlignmentAssistant(Align *parent, const QSharedPointer<AlignView> &view) : QWidget(parent)
46{
47 setupUi(this);
48 polarAlignWidget = new PolarAlignWidget();
49 mainPALayout->insertWidget(0, polarAlignWidget);
50
51 m_AlignInstance = parent;
52 m_AlignView = view;
53
54 showUpdatedError((pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM) ||
55 (pAHRefreshAlgorithm->currentIndex() == MOVE_STAR_UPDATE_ERR_ALGORITHM));
56
58 {
59 setPAHRefreshAlgorithm(static_cast<RefreshAlgorithm>(index));
60 });
61 starCorrespondencePAH.reset();
62
63 // PAH Connections
64 PAHWidgets->setCurrentWidget(PAHIntroPage);
65 connect(this, &PolarAlignmentAssistant::PAHEnabled, [&](bool enabled)
66 {
67 PAHStartB->setEnabled(enabled);
68 directionLabel->setEnabled(enabled);
69 pAHDirection->setEnabled(enabled);
70 pAHRotation->setEnabled(enabled);
71 pAHMountSpeed->setEnabled(enabled);
72 pAHManualSlew->setEnabled(enabled);
73 });
74 connect(PAHStartB, &QPushButton::clicked, this, &Ekos::PolarAlignmentAssistant::startPAHProcess);
75 // PAH StopB is just a shortcut for the regular stop
76 connect(PAHStopB, &QPushButton::clicked, this, &PolarAlignmentAssistant::stopPAHProcess);
77 connect(PAHRefreshB, &QPushButton::clicked, this, &Ekos::PolarAlignmentAssistant::startPAHRefreshProcess);
78 // done button for manual slewing during polar alignment:
79 connect(PAHManualDone, &QPushButton::clicked, this, &Ekos::PolarAlignmentAssistant::setPAHSlewDone);
80
81 hemisphere = KStarsData::Instance()->geo()->lat()->Degrees() > 0 ? NORTH_HEMISPHERE : SOUTH_HEMISPHERE;
82
83 label_PAHOrigErrorAz->setText("Az: ");
84 label_PAHOrigErrorAlt->setText("Alt: ");
85}
86
87PolarAlignmentAssistant::~PolarAlignmentAssistant()
88{
89}
90
91void PolarAlignmentAssistant::showUpdatedError(bool show)
92{
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);
99}
100
101void PolarAlignmentAssistant::syncMountSpeed(const QString &speed)
102{
103 pAHMountSpeed->blockSignals(true);
104 pAHMountSpeed->clear();
105 pAHMountSpeed->addItems(m_CurrentTelescope->slewRates());
106 pAHMountSpeed->setCurrentText(speed);
107 pAHMountSpeed->blockSignals(false);
108}
109
110void PolarAlignmentAssistant::setEnabled(bool enabled)
111{
112 QWidget::setEnabled(enabled);
113
114 emit PAHEnabled(enabled);
115 if (enabled)
116 {
117 PAHWidgets->setToolTip(QString());
118 FOVDisabledLabel->hide();
119 }
120 else
121 {
122 PAHWidgets->setToolTip(i18n("<p>Polar Alignment tool requires a German Equatorial Mount.</p>"));
123 FOVDisabledLabel->show();
124 }
125
126}
127
128// This solver is only used by the refresh plate-solving scheme.
129void PolarAlignmentAssistant::startSolver()
130{
131 auto profiles = getDefaultAlignOptionsProfiles();
132 auto parameters = profiles.at(Options::solveOptionsProfile());
133 // Double search radius
134 parameters.search_radius = parameters.search_radius * 2;
135 constexpr double solverTimeout = 10.0;
136
137 m_Solver.reset(new SolverUtils(parameters, solverTimeout), &QObject::deleteLater);
138 connect(m_Solver.get(), &SolverUtils::done, this, &PolarAlignmentAssistant::solverDone, Qt::UniqueConnection);
139
140 // Use the scale and position from the most recent solve.
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);
145}
146
147void PolarAlignmentAssistant::solverDone(bool timedOut, bool success, const FITSImage::Solution &solution,
148 double elapsedSeconds)
149{
150 disconnect(m_Solver.get(), &SolverUtils::done, this, &PolarAlignmentAssistant::solverDone);
151
152 if (m_PAHStage != PAH_REFRESH)
153 return;
154
155 if (timedOut || !success)
156 {
157 // I'm torn between 1 and 2 for this constant.
158 // 1: If a solve failed, open up the search next time.
159 // 2: A common reason for a solve to fail is because a knob was adjusted during the exposure.
160 // Let it try one more time.
161 constexpr int MAX_NUM_HEALPIX_FAILURES = 2;
162 if (++m_NumHealpixFailures >= MAX_NUM_HEALPIX_FAILURES)
163 {
164 // Don't use the previous index and healpix next time we solve.
165 m_IndexToUse = -1;
166 m_HealpixToUse = -1;
167 }
168 }
169 else
170 {
171 // Get the index and healpix from the successful solve.
172 m_Solver->getSolutionHealpix(&m_IndexToUse, &m_HealpixToUse);
173 }
174
175 if (timedOut)
176 {
177 emit newLog(i18n("Refresh solver timed out: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1)));
178 emit captureAndSolve();
179 }
180 else if (!success)
181 {
182 emit newLog(i18n("Refresh solver failed: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1)));
183 emit updatedErrorsChanged(-1, -1, -1);
184 emit captureAndSolve();
185 }
186 else
187 {
188 m_NumHealpixFailures = 0;
189 refreshIteration++;
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;
196
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));
200
201 // RA is input in hours, not degrees!
202 SkyPoint refreshCoords(ra / 15.0, dec);
203 double azError = 0, altError = 0;
204 if (polarAlign.processRefreshCoords(refreshCoords, m_ImageData->getDateTime(), &azError, &altError))
205 {
206 updateRefreshDisplay(azError, altError);
207
208 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false : true;
209 // The 2nd false means don't block. The code below doesn't work if we block
210 // because wcsToPixel in updateTriangle() depends on the injectWCS being finished.
211 m_AlignView->injectWCS(solution.orientation, ra, dec, solution.pixscale, eastToTheRight, false);
212 updatePlateSolveTriangle(m_ImageData);
213 }
214 else
215 emit newLog(QString("Could not estimate mount rotation"));
216 }
217 // Start the next refresh capture.
218 emit captureAndSolve();
219}
220
221void PolarAlignmentAssistant::updatePlateSolveTriangle(const QSharedPointer<FITSData> &image)
222{
223 if (image.isNull())
224 return;
225
226 // To render the triangle for the plate-solve refresh, we need image coordinates for
227 // - the original (3rd image) center point (originalPoint)
228 // - the solution point (solutionPoint),
229 // - the alt-only solution point (altOnlyPoint),
230 // - the current center of the image (centerPixel),
231 // The triangle is made from the first 3 above.
232 const SkyPoint &originalCoords = polarAlign.getPoint(2);
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))
238 {
239 m_AlignView->setCorrectionParams(originalPixel, solutionPixel, altOnlyPixel);
240 m_AlignView->setStarCircle(centerPixel);
241 }
242 else
243 {
244 qCDebug(KSTARS_EKOS_ALIGN) << "wcs failed";
245 }
246}
247
248namespace
249{
250
251// These functions paint up/down/left/right-pointing arrows in a box of size w x h.
252
253void upArrow(QPainter *painter, int w, int h)
254{
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);
259 arrowHead << QPointF(wCenter, h * .1) << QPointF(w * .2, h * .4) << QPointF(w * .8, h * .4);
261}
262void downArrow(QPainter *painter, int w, int h)
263{
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);
268 arrowHead << QPointF(wCenter, h * .9) << QPointF(w * .2, h * .6) << QPointF(w * .8, h * .6);
270}
271void leftArrow(QPainter *painter, int w, int h)
272{
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);
277 arrowHead << QPointF(w * .1, hCenter) << QPointF(w * .4, h * .2) << QPointF(w * .4, h * .8);
279}
280void rightArrow(QPainter *painter, int w, int h)
281{
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);
286 arrowHead << QPointF(w * .9, hCenter) << QPointF(w * .6, h * .2) << QPointF(w * .6, h * .8);
288}
289} // namespace
290
291// Draw arrows that give the user a hint of how he/she needs to adjust the azimuth and altitude
292// knobs. The arrows' size changes depending on the azimuth and altitude error.
293void PolarAlignmentAssistant::drawArrows(double azError, double altError)
294{
295 constexpr double minError = 20.0 / 3600.0; // 20 arcsec
296 double absError = fabs(altError);
297
298 // these constants are worked out so a 10' error gives a size of 100
299 // and a 1' error gives a size of 20.
300 constexpr double largeErr = 10.0 / 60.0, smallErr = 1.0 / 60.0, largeSize = 100, smallSize = 20, c1 = 533.33, c2 = 11.111;
301
302 int size = 0;
303 if (absError > largeErr)
304 size = largeSize;
305 else if (absError < smallErr)
306 size = smallSize;
307 else size = absError * c1 + c2;
308
309 QPixmap altPixmap(size, size);
310 altPixmap.fill(QColor("transparent"));
312 altPainter.setBrush(arrowAlt->palette().color(QPalette::WindowText));
313
314 if (altError > minError)
315 downArrow(&altPainter, size, size);
316 else if (altError < -minError)
317 upArrow(&altPainter, size, size);
318 arrowAlt->setPixmap(altPixmap);
319
321 if (absError > largeErr)
322 size = largeSize;
323 else if (absError < smallErr)
324 size = smallSize;
325 else size = absError * c1 + c2;
326
327 QPixmap azPixmap(size, size);
328 azPixmap.fill(QColor("transparent"));
330 azPainter.setBrush(arrowAz->palette().color(QPalette::WindowText));
331
332 if (azError > minError)
333 leftArrow(&azPainter, size, size);
334 else if (azError < -minError)
335 rightArrow(&azPainter, size, size);
336 arrowAz->setPixmap(azPixmap);
337}
338
339bool PolarAlignmentAssistant::detectStarsPAHRefresh(QList<Edge> *stars, int num, int x, int y, int *starIndex)
340{
341 stars->clear();
342 *starIndex = -1;
343
344 // Use the solver settings from the align tab for for "polar-align refresh" star detection.
345 QVariantMap settings;
346 settings["optionsProfileIndex"] = Options::solveOptionsProfile();
347 settings["optionsProfileGroup"] = static_cast<int>(Ekos::AlignProfiles);
348 m_ImageData->setSourceExtractorSettings(settings);
349
350 QElapsedTimer timer;
351 m_ImageData->findStars(ALGORITHM_SEP).waitForFinished();
352
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;
356
357 QList<Edge *> detectedStars = m_ImageData->getStarCenters();
358 // Let's sort detectedStars by flux, starting with widest
359 std::sort(detectedStars.begin(), detectedStars.end(), [](const Edge * edge1, const Edge * edge2) -> bool { return edge1->sum > edge2->sum;});
360
361 // Find the closest star to the x,y position, which is where the user clicked on the alignView.
362 double bestDist = 1e9;
363 int bestIndex = -1;
364 for (int i = 0; i < detectedStars.size(); i++)
365 {
366 double dx = detectedStars[i]->x - x;
367 double dy = detectedStars[i]->y - y;
368 double dist = dx * dx + dy * dy;
369 if (dist < bestDist)
370 {
371 bestDist = dist;
372 bestIndex = i;
373 }
374 }
375
376 int starCount = qMin(num, detectedStars.count());
377 for (int i = 0; i < starCount; i++)
378 stars->append(*(detectedStars[i]));
379 if (bestIndex >= starCount)
380 {
381 // If we found the star, but requested 'num' stars, and the user's star
382 // is lower down in the list, add it and return num+1 stars.
383 stars->append(*(detectedStars[bestIndex]));
385 }
386 else
387 {
389 }
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;
392
393 detectedStars.clear();
394
395 return stars->count();
396}
397
398void PolarAlignmentAssistant::updateRefreshDisplay(double azE, double altE)
399{
400 drawArrows(azE, altE);
401 const double errDegrees = hypot(azE, altE);
403 PAHUpdatedErrorTotal->setText(totalError.toDMSString());
404 PAHUpdatedErrorAlt->setText(altError.toDMSString());
405 PAHUpdatedErrorAz->setText(azError.toDMSString());
406
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());
412}
413
414void PolarAlignmentAssistant::processPAHRefresh()
415{
416 m_AlignView->setStarCircle();
417 PAHUpdatedErrorTotal->clear();
418 PAHIteration->clear();
419 PAHUpdatedErrorAlt->clear();
420 PAHUpdatedErrorAz->clear();
421 QString debugString;
422 imageNumber++;
423 PAHIteration->setText(QString("Image %1").arg(imageNumber));
424
425 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
426 {
427 startSolver();
428 return;
429 }
430
431 // Always run on the initial iteration to setup the user's star,
432 // so that if it is enabled later the star could be tracked.
433 // Flaw here is that if enough stars are not detected, iteration is not incremented,
434 // so it may repeat.
435 if ((pAHRefreshAlgorithm->currentIndex() == MOVE_STAR_UPDATE_ERR_ALGORITHM) || (refreshIteration == 0))
436 {
437 constexpr int MIN_PAH_REFRESH_STARS = 10;
438
439 QList<Edge> stars;
440 // Index of user's star in the detected stars list. In the first iteration
441 // the stars haven't moved and we can just use the location of the click.
442 // Later we'll need to find the star with starCorrespondence.
444 detectStarsPAHRefresh(&stars, 100, correctionFrom.x(), correctionFrom.y(), &clickedStarIndex);
445 if (clickedStarIndex < 0)
446 {
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;
450
451 emit newAlignTableResult(Align::ALIGN_RESULT_FAILED);
452 emit captureAndSolve();
453 return;
454 }
455
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;
462
463 if (stars.size() > MIN_PAH_REFRESH_STARS)
464 {
465 int dx = 0;
466 int dy = 0;
467 int starIndex = -1;
468
469 if (refreshIteration++ == 0)
470 {
471 // First iteration. Setup starCorrespondence so we can find the user's star.
472 // clickedStarIndex should be the index of a detected star near where the user clicked.
473 starCorrespondencePAH.initialize(stars, clickedStarIndex);
474 if (clickedStarIndex >= 0)
475 {
476 setupCorrectionGraphics(QPointF(stars[clickedStarIndex].x, stars[clickedStarIndex].y));
477 emit newCorrectionVector(QLineF(correctionFrom, correctionTo));
478 emit newFrame(m_AlignView);
479 }
480 }
481 else
482 {
483 // Or, in other iterations find the movement of the "user's star".
484 // The 0.40 means it's OK if star correspondence only finds 40% of the
485 // reference stars (as we'd have more issues near the image edge otherwise).
486 QVector<int> starMap;
487 starCorrespondencePAH.find(stars, 200.0, &starMap, false, 0.40);
488
489 // Go through the starMap, and find the user's star, and compare its position
490 // to its initial position.
491 for (int i = 0; i < starMap.size(); ++i)
492 {
493 if (starMap[i] == starCorrespondencePAH.guideStar())
494 {
495 dx = stars[i].x - correctionFrom.x();
496 dy = stars[i].y - correctionFrom.y();
497 starIndex = i;
498 break;
499 }
500 }
501 if (starIndex == -1)
502 {
503 bool allOnes = true;
504 for (int i = 0; i < starMap.size(); ++i)
505 {
506 if (starMap[i] != -1)
507 allOnes = false;
508 }
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;
511 }
512 }
513
514 if (starIndex >= 0)
515 {
516 // Annotate the user's star on the alignview.
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;
521
522 double azE, altE;
523 if (polarAlign.pixelError(m_AlignView->keptImage(), QPointF(stars[starIndex].x, stars[starIndex].y),
524 correctionTo, &azE, &altE))
525 {
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;
532 }
533 else
534 {
535 debugString = QString("PAA Refresh(%1): pixelError failed to estimate the remaining correction").arg(refreshIteration);
536 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
537 }
538 }
539 else
540 {
541 if (refreshIteration > 1)
542 {
543 debugString = QString("PAA Refresh(%1): Didn't find the user's star").arg(refreshIteration);
544 qCDebug(KSTARS_EKOS_ALIGN) << debugString;
545 }
546 }
547 }
548 else
549 {
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);
553 }
554 }
555 // Finally start the next capture
556 emit captureAndSolve();
557}
558
559bool PolarAlignmentAssistant::processSolverFailure()
560{
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)
568 {
569 emit newLog(i18n("PAA: Solver failed, retrying."));
570 emit captureAndSolve();
571 return true;
572 }
573
574 if (m_PAHStage != PAH_IDLE)
575 {
576 emit newLog(i18n("PAA: Stopping, solver failed too many times."));
577 stopPAHProcess();
578 }
579
580 return false;
581}
582
583void PolarAlignmentAssistant::setPAHStage(Stage stage)
584{
585 if (stage != m_PAHStage)
586 {
587 m_PAHStage = stage;
588 polarAlignWidget->updatePAHStage(stage);
589 emit newPAHStage(m_PAHStage);
590 }
591}
592
593void PolarAlignmentAssistant::processMountRotation(const dms &ra, double settleDuration)
594{
595 double deltaAngle = fabs(ra.deltaAngle(targetPAH.ra()).Degrees());
596
599 Stage nextCapture;
600 Stage nextSettle;
601
602 if (m_PAHStage == PAH_FIRST_ROTATE)
603 {
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;
608 }
609 else if (m_PAHStage == PAH_SECOND_ROTATE)
610 {
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;
615 }
616 else return;
617
618 if (m_PAHStage == PAH_FIRST_ROTATE || m_PAHStage == PAH_SECOND_ROTATE)
619 {
620 // only wait for telescope to slew to new position if manual slewing is switched off
621 if(!pAHManualSlew->isChecked())
622 {
623 qCDebug(KSTARS_EKOS_ALIGN) << rotProgressMessage << deltaAngle;
624 if (deltaAngle <= PAH_ROTATION_THRESHOLD)
625 {
626 m_CurrentTelescope->StopWE();
627 emit newLog(rotDoneMessage);
628
629 if (settleDuration <= 0)
630 {
631 setPAHStage(nextCapture);
632 updateDisplay(m_PAHStage, getPAHMessage());
633 }
634 else
635 {
636 setPAHStage(nextSettle);
637 updateDisplay(m_PAHStage, getPAHMessage());
638
639 emit newLog(i18n("Settling..."));
641 {
642 setPAHStage(nextCapture);
643 updateDisplay(m_PAHStage, getPAHMessage());
644 });
645 }
646 }
647 // If for some reason we didn't stop, let's stop if we get too far
648 else if (deltaAngle > pAHRotation->value() * 1.25)
649 {
650 m_CurrentTelescope->abort();
651 emit newLog(i18n("Mount aborted. Reverse RA axis direction and try again."));
652 stopPAHProcess();
653 }
654 return;
655 } // endif not manual slew
656 }
657}
658
659bool PolarAlignmentAssistant::checkPAHForMeridianCrossing()
660{
661 // Make sure using -180 to 180 for hourAngle and DEC. (Yes dec should be between -90 and 90).
662 double hourAngle = m_CurrentTelescope->hourAngle().Degrees();
663 while (hourAngle < -180)
664 hourAngle += 360;
665 while (hourAngle > 180)
666 hourAngle -= 360;
667 double ra = 0, dec = 0;
668 m_CurrentTelescope->getEqCoords(&ra, &dec);
669 while (dec < -180)
670 dec += 360;
671 while (dec > 180)
672 dec -= 360;
673
674 // Don't do this check within 2 degrees of the poles.
675 bool nearThePole = fabs(dec) > 88;
676 if (nearThePole)
677 return false;
678
679 double degreesPerSlew = pAHRotation->value();
680 bool closeToMeridian = fabs(hourAngle) < 2.0 * degreesPerSlew;
681 bool goingWest = pAHDirection->currentIndex() == 0;
682
683 // If the pier is on the east side (pointing west) and will slew west and is within 2 slews of the HA=0,
684 // or on the west side (pointing east) and will slew east, and is within 2 slews of HA=0
685 // then warn and give the user a chance to cancel.
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));
690
691 return wouldCrossMeridian;
692}
693
694void PolarAlignmentAssistant::updateDisplay(Stage stage, const QString &message)
695{
696 switch(stage)
697 {
698 case PAH_FIRST_ROTATE:
699 case PAH_SECOND_ROTATE:
700 if (pAHManualSlew->isChecked())
701 {
702 polarAlignWidget->updatePAHStage(stage);
703 PAHWidgets->setCurrentWidget(PAHManualRotatePage);
704 manualRotateText->setText(message);
705 emit newPAHMessage(message);
706 return;
707 }
708 // fall through
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);
719 break;
720
721 default:
722 break;
723
724 }
725}
726
727void PolarAlignmentAssistant::startPAHProcess()
728{
729 qCInfo(KSTARS_EKOS_ALIGN) << QString("Starting Polar Alignment Assistant process %1 ...").arg(PAA_VERSION);
730
731 auto executePAH = [ this ]()
732 {
733 setPAHStage(PAH_FIRST_CAPTURE);
734
735 if (Options::limitedResourcesMode())
736 emit newLog(i18n("Warning: Equatorial Grid Lines will not be drawn due to limited resources mode."));
737
738 if (m_CurrentTelescope->hasAlignmentModel())
739 {
740 emit newLog(i18n("Clearing mount Alignment Model..."));
741 m_CurrentTelescope->clearAlignmentModel();
742 }
743
744 // Unpark
745 m_CurrentTelescope->unpark();
746
747 // Set tracking ON if not already
748 if (m_CurrentTelescope->canControlTrack() && m_CurrentTelescope->isTracking() == false)
749 m_CurrentTelescope->setTrackEnabled(true);
750
751 PAHStartB->setEnabled(false);
752 PAHStopB->setEnabled(true);
753
754 PAHUpdatedErrorTotal->clear();
755 PAHUpdatedErrorAlt->clear();
756 PAHUpdatedErrorAz->clear();
757 PAHOrigErrorTotal->clear();
758 PAHOrigErrorAlt->clear();
759 PAHOrigErrorAz->clear();
760 PAHIteration->setText("");
761
762 updateDisplay(m_PAHStage, getPAHMessage());
763
764 m_AlignView->setCorrectionParams(QPointF(), QPointF(), QPointF());
765
766 m_PAHRetrySolveCounter = 0;
767 emit captureAndSolve();
768 };
769
770 // Right off the bat, check if this alignment might cause a pier crash.
771 // If we're crossing the meridian, warn unless within 5-degrees from the pole.
772 if (checkPAHForMeridianCrossing())
773 {
774 // Continue
775 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executePAH]()
776 {
777 KSMessageBox::Instance()->disconnect(this);
778 executePAH();
779 });
780
781 KSMessageBox::Instance()->warningContinueCancel(i18n("This could cause the telescope to cross the meridian."),
782 i18n("Warning"), 15);
783 }
784 else
785 executePAH();
786}
787
788void PolarAlignmentAssistant::stopPAHProcess()
789{
790 if (m_PAHStage == PAH_IDLE)
791 return;
792
793 // Only display dialog if user clicked.
794 // if ((static_cast<QPushButton *>(sender()) == PAHStopB) && KMessageBox::questionYesNo(KStars::Instance(),
795 // i18n("Are you sure you want to stop the polar alignment process?"),
796 // i18n("Polar Alignment Assistant"), KStandardGuiItem::yes(), KStandardGuiItem::no(),
797 // "restart_PAA_process_dialog") == KMessageBox::No)
798 // return;
799
800 if (m_PAHStage == PAH_REFRESH)
801 {
802 setPAHStage(PAH_POST_REFRESH);
803 polarAlignWidget->updatePAHStage(m_PAHStage);
804 }
805 qCInfo(KSTARS_EKOS_ALIGN) << "Stopping Polar Alignment Assistant process...";
806
807
808 if (m_CurrentTelescope && m_CurrentTelescope->isInMotion())
809 m_CurrentTelescope->abort();
810
811 setPAHStage(PAH_IDLE);
812 polarAlignWidget->updatePAHStage(m_PAHStage);
813
814 PAHStartB->setEnabled(true);
815 PAHStopB->setEnabled(false);
816 PAHRefreshB->setEnabled(true);
817 PAHWidgets->setCurrentWidget(PAHIntroPage);
818 emit newPAHMessage(introText->text());
819
820 m_AlignView->reset();
821 m_AlignView->setRefreshEnabled(false);
822
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);
827
828 if (Options::pAHAutoPark())
829 {
830 m_CurrentTelescope->park();
831 emit newLog(i18n("Parking the mount..."));
832 }
833}
834
835void PolarAlignmentAssistant::rotatePAH()
836{
837 double TargetDiffRA = pAHRotation->value();
838 bool westMeridian = pAHDirection->currentIndex() == 0;
839
840 // West
841 if (westMeridian)
842 TargetDiffRA *= -1;
843 // East
844 else
845 TargetDiffRA *= 1;
846
847 // JM 2018-05-03: Hemispheres shouldn't affect rotation direction in RA
848
849 // if Manual slewing is selected, don't move the mount
850 if (pAHManualSlew->isChecked())
851 {
852 return;
853 }
854
855 const SkyPoint telescopeCoord = m_CurrentTelescope->currentCoordinates();
856
857 // TargetDiffRA is in degrees
858 dms newTelescopeRA = (telescopeCoord.ra() + dms(TargetDiffRA)).reduce();
859
860 targetPAH.setRA(newTelescopeRA);
861 targetPAH.setDec(telescopeCoord.dec());
862
863 //m_CurrentTelescope->Slew(&targetPAH);
864 // Set Selected Speed
865 if (pAHMountSpeed->currentIndex() >= 0)
866 m_CurrentTelescope->setSlewRate(pAHMountSpeed->currentIndex());
867 // Go to direction
868 m_CurrentTelescope->MoveWE(westMeridian ? ISD::Mount::MOTION_WEST : ISD::Mount::MOTION_EAST,
869 ISD::Mount::MOTION_START);
870
871 emit newLog(i18n("Please wait until mount completes rotating to RA (%1) DE (%2)", targetPAH.ra().toHMSString(),
872 targetPAH.dec().toDMSString()));
873}
874
875void PolarAlignmentAssistant::setupCorrectionGraphics(const QPointF &pixel)
876{
877 polarAlign.refreshSolution(&refreshSolution, &altOnlyRefreshSolution);
878
879 // We use the previously stored image (the 3rd PAA image)
880 // so we can continue to estimate the correction even after
881 // capturing new images during the refresh stage.
882 const QSharedPointer<FITSData> &imageData = m_AlignView->keptImage();
883
884 // Just the altitude correction
885 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionAltTo, true))
886 {
887 qCInfo(KSTARS_EKOS_ALIGN) << QString(i18n("PAA: Failed to findCorrectedPixel."));
888 return;
889 }
890 // The whole correction.
891 if (!polarAlign.findCorrectedPixel(imageData, pixel, &correctionTo))
892 {
893 qCInfo(KSTARS_EKOS_ALIGN) << QString(i18n("PAA: Failed to findCorrectedPixel."));
894 return;
895 }
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;
902
903 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
904 updatePlateSolveTriangle(imageData);
905 else
906 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
907
908 return;
909}
910
911
912bool PolarAlignmentAssistant::calculatePAHError()
913{
914 // Hold on to the imageData so we can use it during the refresh phase.
915 m_AlignView->holdOnToImage();
916
917 if (!polarAlign.findAxis())
918 {
919 emit newLog(i18n("PAA: Failed to find RA Axis center."));
920 stopPAHProcess();
921 return false;
922 }
923
925 polarAlign.calculateAzAltError(&azimuthError, &altitudeError);
926 drawArrows(azimuthError, altitudeError);
929
930 // No need to toggle EQGrid
931 // if (m_AlignView->isEQGridShown() == false && !Options::limitedResourcesMode())
932 // m_AlignView->toggleEQGrid();
933
934 QString msg = QString("%1. Azimuth: %2 Altitude: %3")
935 .arg(polarError.toDMSString()).arg(azError.toDMSString())
936 .arg(altError.toDMSString());
937 emit newLog(QString("Polar Alignment Error: %1").arg(msg));
938
939 polarAlign.setMaxPixelSearchRange(polarError.Degrees() + 1);
940
941 // These are viewed during the refresh phase.
942 PAHOrigErrorTotal->setText(polarError.toDMSString());
943 PAHOrigErrorAlt->setText(altError.toDMSString());
944 PAHOrigErrorAz->setText(azError.toDMSString());
945
946 setupCorrectionGraphics(QPointF(m_ImageData->width() / 2, m_ImageData->height() / 2));
947
948 // Find Celestial pole location and mount's RA axis
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))
953 {
954 m_AlignView->setCelestialPole(celestialPolePoint);
955 QPointF raAxis;
956 if (polarAlign.findCorrectedPixel(m_ImageData, celestialPolePoint, &raAxis))
957 m_AlignView->setRaAxis(raAxis);
958 }
959
960 connect(m_AlignView.get(), &AlignView::trackingStarSelected, this, &Ekos::PolarAlignmentAssistant::setPAHCorrectionOffset);
961 emit polarResultUpdated(QLineF(correctionFrom, correctionTo), polarError.Degrees(), azError.Degrees(), altError.Degrees());
962
963 connect(m_AlignView.get(), &AlignView::newCorrectionVector, this, &Ekos::PolarAlignmentAssistant::newCorrectionVector,
965 syncCorrectionVector();
966 emit newFrame(m_AlignView);
967
968 return true;
969}
970
971void PolarAlignmentAssistant::syncCorrectionVector()
972{
973 if (pAHRefreshAlgorithm->currentIndex() == PLATE_SOLVE_ALGORITHM)
974 return;
975 emit newCorrectionVector(QLineF(correctionFrom, correctionTo));
976 m_AlignView->setCorrectionParams(correctionFrom, correctionTo, correctionAltTo);
977}
978
979void PolarAlignmentAssistant::setPAHCorrectionOffsetPercentage(double dx, double dy)
980{
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)));
984}
985
986void PolarAlignmentAssistant::setPAHCorrectionOffset(int x, int y)
987{
988 if (m_PAHStage == PAH_REFRESH)
989 {
990 emit newLog(i18n("Polar-alignment star cannot be updated during refresh phase as it might affect error measurements."));
991 }
992 else
993 {
994 setupCorrectionGraphics(QPointF(x, y));
995 emit newCorrectionVector(QLineF(correctionFrom, correctionTo));
996 emit newFrame(m_AlignView);
997 }
998}
999
1000void 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
1022void 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
1051void 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,
1085 m_AlignView->injectWCS(orientation, ra, dec, pixscale, eastToTheRight);
1086 return;
1087 }
1088}
1089
1090void 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
1105void 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
1182void 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
1199QString 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
1243void 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}
The sky coordinates of a point in the sky.
Definition skypoint.h:45
const CachingDms & dec() const
Definition skypoint.h:269
const CachingDms & ra() const
Definition skypoint.h:263
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
const dms deltaAngle(dms angle) const
deltaAngle Return the shortest difference (path) between this angle and the supplied angle.
Definition dms.cpp:259
QString i18n(const char *text, const TYPE &arg...)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:78
ISD is a collection of INDI Standard Devices.
void clicked(bool checked)
void currentIndexChanged(int index)
void accepted()
void deleteLater()
void drawConvexPolygon(const QPointF *points, int pointCount)
void drawRect(const QRectF &rectangle)
qreal x() const const
qreal y() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
UniqueConnection
QTextStream & dec(QTextStream &stream)
void setEnabled(bool)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Feb 24 2024 19:53:37 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.