Kstars

internalguider.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>.
3
4 Based on lin_guider
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "internalguider.h"
10
11#include "ekos_guide_debug.h"
12#include "gmath.h"
13#include "Options.h"
14#include "auxiliary/kspaths.h"
15#include "fitsviewer/fitsdata.h"
16#include "fitsviewer/fitsview.h"
17#include "guidealgorithms.h"
18#include "ksnotification.h"
19#include "ekos/auxiliary/stellarsolverprofileeditor.h"
20#include "fitsviewer/fitsdata.h"
21#include "../guideview.h"
22
23#include <KMessageBox>
24
25#include <random>
26#include <chrono>
27#include <QTimer>
28#include <QString>
29
30#define MAX_GUIDE_STARS 10
31
32using namespace std::chrono_literals;
33
34namespace Ekos
35{
36InternalGuider::InternalGuider()
37{
38 // Create math object
39 pmath.reset(new cgmath());
40 connect(pmath.get(), &cgmath::newStarPosition, this, &InternalGuider::newStarPosition);
41 connect(pmath.get(), &cgmath::guideStats, this, &InternalGuider::guideStats);
42
43 // Do this so that stored calibration will be visible on the
44 // guide options menu. Calibration will get restored again when needed.
45 pmath->getMutableCalibration()->restore(
46 pierSide, Options::reverseDecOnPierSideChange(), subBinX, subBinY, nullptr);
47
48 state = GUIDE_IDLE;
49 m_DitherOrigin = QVector3D(0, 0, 0);
50
51 emit guideInfo("");
52
53 m_darkGuideTimer = std::make_unique<QTimer>(this);
54 m_captureTimer = std::make_unique<QTimer>(this);
55
56 setDarkGuideTimerInterval();
57
58 setExposureTime();
59
60 connect(this, &Ekos::GuideInterface::frameCaptureRequested, this, [ = ]()
61 {
62 this->m_captureTimer->start();
63 });
64}
65
66void InternalGuider::setExposureTime()
67{
68 Seconds seconds(Options::guideExposure());
69 setTimer(m_captureTimer, seconds);
70}
71
72void InternalGuider::setTimer(std::unique_ptr<QTimer> &timer, Seconds seconds)
73{
74 const std::chrono::duration<double, std::milli> inMilliseconds(seconds);
75 timer->setInterval((int)(inMilliseconds.count()));
76}
77
78void InternalGuider::setDarkGuideTimerInterval()
79{
80 constexpr double kMinInterval = 0.5; // 0.5s is the shortest allowed dark-guiding period.
81 const Seconds seconds(std::max(kMinInterval, Options::gPGDarkGuidingInterval()));
82 setTimer(m_darkGuideTimer, seconds);
83}
84
85void InternalGuider::resetDarkGuiding()
86{
87 m_darkGuideTimer->stop();
88 m_captureTimer->stop();
89}
90
91bool InternalGuider::isInferencePeriodFinished()
92{
93 auto const contribution = pmath->getGPG().predictionContribution();
94 return contribution >= 0.99;
95}
96bool InternalGuider::guide()
97{
98 if (state >= GUIDE_GUIDING)
99 {
100 return processGuiding();
101 }
102
103 if (state == GUIDE_SUSPENDED)
104 {
105 return true;
106 }
107 m_GuideFrame->disconnect(this);
108
109 pmath->start();
110 emit guideInfo("");
111
112 m_starLostCounter = 0;
113 m_highRMSCounter = 0;
114 m_DitherOrigin = QVector3D(0, 0, 0);
115
116 m_isFirstFrame = true;
117
118 if (state == GUIDE_IDLE)
119 {
120 if (Options::saveGuideLog())
121 guideLog.enable();
122 GuideLog::GuideInfo info;
123 fillGuideInfo(&info);
124 guideLog.startGuiding(info);
125 }
126 state = GUIDE_GUIDING;
127
128 emit newStatus(state);
129
130 emit frameCaptureRequested();
131
132 startDarkGuiding();
133
134 return true;
135}
136
137/**
138 * @brief InternalGuider::abort Abort all internal guider operations.
139 * This includes calibration, dithering, guiding, capturing, and reaquiring.
140 * The state is set to IDLE or ABORTED depending on the current state since
141 * ABORTED can lead to different behavior by external actors than IDLE
142 * @return True if abort succeeds, false otherwise.
143 */
144bool InternalGuider::abort()
145{
146 // calibrationStage = CAL_IDLE; remove totally when understand trackingStarSelected
147
148 logFile.close();
149 guideLog.endGuiding();
150 emit guideInfo("");
151
152 if (state == GUIDE_CALIBRATING ||
153 state == GUIDE_GUIDING ||
154 state == GUIDE_DITHERING ||
155 state == GUIDE_MANUAL_DITHERING ||
156 state == GUIDE_REACQUIRE)
157 {
158 if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
159 emit newStatus(GUIDE_DITHERING_ERROR);
160 emit newStatus(GUIDE_ABORTED);
161
162 qCDebug(KSTARS_EKOS_GUIDE) << "Aborting" << getGuideStatusString(state);
163 }
164 else
165 {
166 emit newStatus(GUIDE_IDLE);
167 qCDebug(KSTARS_EKOS_GUIDE) << "Stopping internal guider.";
168 }
169
170 resetDarkGuiding();
171 disconnect(m_darkGuideTimer.get(), nullptr, nullptr, nullptr);
172
173 pmath->abort();
174
175
176
177 m_ProgressiveDither.clear();
178 m_starLostCounter = 0;
179 m_highRMSCounter = 0;
180
181 m_DitherOrigin = QVector3D(0, 0, 0);
182
183 pmath->suspend(false);
184 state = GUIDE_IDLE;
185 qCDebug(KSTARS_EKOS_GUIDE) << "Guiding aborted.";
186
187 return true;
188}
189
190bool InternalGuider::suspend()
191{
192 guideLog.pauseInfo();
193 state = GUIDE_SUSPENDED;
194
195 resetDarkGuiding();
196 emit newStatus(state);
197
198 pmath->suspend(true);
199 emit guideInfo("");
200
201 return true;
202}
203
204void InternalGuider::startDarkGuiding()
205{
206 if (Options::gPGDarkGuiding())
207 {
208 connect(m_darkGuideTimer.get(), &QTimer::timeout, this, &InternalGuider::darkGuide, Qt::UniqueConnection);
209
210 // Start the two dark guide timers. The capture timer is started automatically by a signal.
211 m_darkGuideTimer->start();
212
213 qCDebug(KSTARS_EKOS_GUIDE) << "Starting dark guiding.";
214 }
215}
216
217bool InternalGuider::resume()
218{
219 qCDebug(KSTARS_EKOS_GUIDE) << "Resuming...";
220 emit guideInfo("");
221 guideLog.resumeInfo();
222 state = GUIDE_GUIDING;
223 emit newStatus(state);
224
225 pmath->suspend(false);
226
227 startDarkGuiding();
228
229 setExposureTime();
230
231 emit frameCaptureRequested();
232
233 return true;
234}
235
236bool InternalGuider::ditherXY(double x, double y)
237{
238 m_ProgressiveDither.clear();
239 m_DitherRetries = 0;
240 double cur_x, cur_y;
241 pmath->getTargetPosition(&cur_x, &cur_y);
242
243 // Find out how many "jumps" we need to perform in order to get to target.
244 // The current limit is now 1/4 of the box size to make sure the star stays within detection
245 // threashold inside the window.
246 double oneJump = (guideBoxSize / 4.0);
247 double targetX = cur_x, targetY = cur_y;
248 int xSign = (x >= cur_x) ? 1 : -1;
249 int ySign = (y >= cur_y) ? 1 : -1;
250
251 do
252 {
253 if (fabs(targetX - x) > oneJump)
254 targetX += oneJump * xSign;
255 else if (fabs(targetX - x) < oneJump)
256 targetX = x;
257
258 if (fabs(targetY - y) > oneJump)
259 targetY += oneJump * ySign;
260 else if (fabs(targetY - y) < oneJump)
261 targetY = y;
262
263 m_ProgressiveDither.enqueue(GuiderUtils::Vector(targetX, targetY, -1));
264
265 }
266 while (targetX != x || targetY != y);
267
268 m_DitherTargetPosition = m_ProgressiveDither.dequeue();
269 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
270 guideLog.ditherInfo(x, y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
271
272 state = GUIDE_MANUAL_DITHERING;
273 emit newStatus(state);
274
275 processGuiding();
276
277 return true;
278}
279
280bool InternalGuider::dither(double pixels)
281{
282 if (Options::ditherWithOnePulse() )
283 return onePulseDither(pixels);
284
285 double ret_x, ret_y;
286 pmath->getTargetPosition(&ret_x, &ret_y);
287
288 // Just calling getStarScreenPosition() will get the position at the last time the guide star
289 // was found, which is likely before the most recent guide pulse.
290 // Instead we call findLocalStarPosition() which does the analysis from the image.
291 // Unfortunately, processGuiding() will repeat that computation.
292 // We currently don't cache it.
293 GuiderUtils::Vector star_position = pmath->findLocalStarPosition(m_ImageData, m_GuideFrame, false);
294 if (pmath->isStarLost() || (star_position.x == -1) || (star_position.y == -1))
295 {
296 // If the star position is lost, just lose this iteration.
297 // If it happens too many time, abort.
298 if (++m_starLostCounter > MAX_LOST_STAR_THRESHOLD)
299 {
300 qCDebug(KSTARS_EKOS_GUIDE) << "Too many consecutive lost stars." << m_starLostCounter << "Aborting dither.";
301 return abortDither();
302 }
303 qCDebug(KSTARS_EKOS_GUIDE) << "Dither lost star. Trying again.";
304 emit frameCaptureRequested();
305 return true;
306 }
307 else
308 m_starLostCounter = 0;
309
310 if (state != GUIDE_DITHERING)
311 {
312 m_DitherRetries = 0;
313
314 auto seed = std::chrono::system_clock::now().time_since_epoch().count();
315 std::default_random_engine generator(seed);
316 std::uniform_real_distribution<double> angleMagnitude(0, 360);
317
318 double angle = angleMagnitude(generator) * dms::DegToRad;
319 double diff_x = pixels * cos(angle);
320 double diff_y = pixels * sin(angle);
321
322 if (pmath->getCalibration().declinationSwapEnabled())
323 diff_y *= -1;
324
325 if (m_DitherOrigin.x() == 0 && m_DitherOrigin.y() == 0)
326 {
327 m_DitherOrigin = QVector3D(ret_x, ret_y, 0);
328 }
329 double totalXOffset = ret_x - m_DitherOrigin.x();
330 double totalYOffset = ret_y - m_DitherOrigin.y();
331
332 // if we've dithered too far, and diff_x or diff_y is pushing us even further away, then change its direction.
333 // Note: it is possible that we've dithered too far, but diff_x/y is pointing in the right direction.
334 // Don't change it in that 2nd case.
335 if (((diff_x + totalXOffset > MAX_DITHER_TRAVEL) && (diff_x > 0)) ||
336 ((diff_x + totalXOffset < -MAX_DITHER_TRAVEL) && (diff_x < 0)))
337 {
338 qCDebug(KSTARS_EKOS_GUIDE)
339 << QString("Dithering target off by too much in X (abs(%1 + %2) > %3), adjust diff_x from %4 to %5")
340 .arg(diff_x).arg(totalXOffset).arg(MAX_DITHER_TRAVEL).arg(diff_x).arg(diff_x * -1.5);
341 diff_x *= -1.5;
342 }
343 if (((diff_y + totalYOffset > MAX_DITHER_TRAVEL) && (diff_y > 0)) ||
344 ((diff_y + totalYOffset < -MAX_DITHER_TRAVEL) && (diff_y < 0)))
345 {
346 qCDebug(KSTARS_EKOS_GUIDE)
347 << QString("Dithering target off by too much in Y (abs(%1 + %2) > %3), adjust diff_y from %4 to %5")
348 .arg(diff_y).arg(totalYOffset).arg(MAX_DITHER_TRAVEL).arg(diff_y).arg(diff_y * -1.5);
349 diff_y *= -1.5;
350 }
351
352 m_DitherTargetPosition = GuiderUtils::Vector(ret_x, ret_y, 0) + GuiderUtils::Vector(diff_x, diff_y, 0);
353
354 qCDebug(KSTARS_EKOS_GUIDE)
355 << QString("Dithering by %1 pixels. Target: %2,%3 Current: %4,%5 Move: %6,%7 Wander: %8,%9")
356 .arg(pixels, 3, 'f', 1)
357 .arg(m_DitherTargetPosition.x, 5, 'f', 1).arg(m_DitherTargetPosition.y, 5, 'f', 1)
358 .arg(ret_x, 5, 'f', 1).arg(ret_y, 5, 'f', 1)
359 .arg(diff_x, 4, 'f', 1).arg(diff_y, 4, 'f', 1)
360 .arg(totalXOffset + diff_x, 5, 'f', 1).arg(totalYOffset + diff_y, 5, 'f', 1);
361 guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
362
363 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
364
365 if (Options::gPGEnabled())
366 // This is the offset in image coordinates, but needs to be converted to RA.
367 pmath->getGPG().startDithering(diff_x, diff_y, pmath->getCalibration());
368
369 state = GUIDE_DITHERING;
370 emit newStatus(state);
371
372 processGuiding();
373
374 return true;
375 }
376
377 // These will be the RA & DEC drifts of the current star position from the reticle position in pixels.
378 double driftRA, driftDEC;
379 pmath->getCalibration().computeDrift(
381 GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0),
382 &driftRA, &driftDEC);
383
384 double pixelOffsetX = m_DitherTargetPosition.x - star_position.x;
385 double pixelOffsetY = m_DitherTargetPosition.y - star_position.y;
386
387 qCDebug(KSTARS_EKOS_GUIDE)
388 << QString("Dithering in progress. Current: %1,%2 Target: %3,%4 Diff: %5,%6 Wander: %8,%9")
389 .arg(star_position.x, 5, 'f', 1).arg(star_position.y, 5, 'f', 1)
390 .arg(m_DitherTargetPosition.x, 5, 'f', 1).arg(m_DitherTargetPosition.y, 5, 'f', 1)
391 .arg(pixelOffsetX, 4, 'f', 1).arg(pixelOffsetY, 4, 'f', 1)
392 .arg(star_position.x - m_DitherOrigin.x(), 5, 'f', 1)
393 .arg(star_position.y - m_DitherOrigin.y(), 5, 'f', 1);
394
395 if (Options::ditherWithOnePulse() || (fabs(driftRA) < 1 && fabs(driftDEC) < 1))
396 {
397 pmath->setTargetPosition(star_position.x, star_position.y);
398
399 // In one-pulse dithering we want the target to be whereever we end up
400 // after the pulse. So, the first guide frame should not send any pulses
401 // and should reset the reticle to the position it finds.
402 if (Options::ditherWithOnePulse())
403 m_isFirstFrame = true;
404
405 qCDebug(KSTARS_EKOS_GUIDE) << "Dither complete.";
406
407 if (Options::ditherSettle() > 0)
408 {
409 state = GUIDE_DITHERING_SETTLE;
410 guideLog.settleStartedInfo();
411 emit newStatus(state);
412 }
413
414 if (Options::gPGEnabled())
415 pmath->getGPG().ditheringSettled(true);
416
417 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
418 }
419 else
420 {
421 if (++m_DitherRetries > Options::ditherMaxIterations())
422 return abortDither();
423
424 processGuiding();
425 }
426
427 return true;
428}
429bool InternalGuider::onePulseDither(double pixels)
430{
431 qCDebug(KSTARS_EKOS_GUIDE) << "OnePulseDither(" << "pixels" << ")";
432
433 // Cancel any current guide exposures.
434 emit abortExposure();
435
436 double ret_x, ret_y;
437 pmath->getTargetPosition(&ret_x, &ret_y);
438
439 auto seed = std::chrono::system_clock::now().time_since_epoch().count();
440 std::default_random_engine generator(seed);
441 std::uniform_real_distribution<double> angleMagnitude(0, 360);
442
443 double angle = angleMagnitude(generator) * dms::DegToRad;
444 double diff_x = pixels * cos(angle);
445 double diff_y = pixels * sin(angle);
446
447 if (pmath->getCalibration().declinationSwapEnabled())
448 diff_y *= -1;
449
450 if (m_DitherOrigin.x() == 0 && m_DitherOrigin.y() == 0)
451 {
452 m_DitherOrigin = QVector3D(ret_x, ret_y, 0);
453 }
454 double totalXOffset = ret_x - m_DitherOrigin.x();
455 double totalYOffset = ret_y - m_DitherOrigin.y();
456
457 // If we've dithered too far, and diff_x or diff_y is pushing us even further away, then change its direction.
458 // Note: it is possible that we've dithered too far, but diff_x/y is pointing in the right direction.
459 // Don't change it in that 2nd case.
460 if (((diff_x + totalXOffset > MAX_DITHER_TRAVEL) && (diff_x > 0)) ||
461 ((diff_x + totalXOffset < -MAX_DITHER_TRAVEL) && (diff_x < 0)))
462 {
463 qCDebug(KSTARS_EKOS_GUIDE)
464 << QString("OPD: Dithering target off by too much in X (abs(%1 + %2) > %3), adjust diff_x from %4 to %5")
465 .arg(diff_x).arg(totalXOffset).arg(MAX_DITHER_TRAVEL).arg(diff_x).arg(diff_x * -1.5);
466 diff_x *= -1.5;
467 }
468 if (((diff_y + totalYOffset > MAX_DITHER_TRAVEL) && (diff_y > 0)) ||
469 ((diff_y + totalYOffset < -MAX_DITHER_TRAVEL) && (diff_y < 0)))
470 {
471 qCDebug(KSTARS_EKOS_GUIDE)
472 << QString("OPD: Dithering target off by too much in Y (abs(%1 + %2) > %3), adjust diff_y from %4 to %5")
473 .arg(diff_y).arg(totalYOffset).arg(MAX_DITHER_TRAVEL).arg(diff_y).arg(diff_y * -1.5);
474 diff_y *= -1.5;
475 }
476
477 m_DitherTargetPosition = GuiderUtils::Vector(ret_x, ret_y, 0) + GuiderUtils::Vector(diff_x, diff_y, 0);
478
479 qCDebug(KSTARS_EKOS_GUIDE)
480 << QString("OPD: Dithering by %1 pixels. Target: %2,%3 Current: %4,%5 Move: %6,%7 Wander: %8,%9")
481 .arg(pixels, 3, 'f', 1)
482 .arg(m_DitherTargetPosition.x, 5, 'f', 1).arg(m_DitherTargetPosition.y, 5, 'f', 1)
483 .arg(ret_x, 5, 'f', 1).arg(ret_y, 5, 'f', 1)
484 .arg(diff_x, 4, 'f', 1).arg(diff_y, 4, 'f', 1)
485 .arg(totalXOffset + diff_x, 5, 'f', 1).arg(totalYOffset + diff_y, 5, 'f', 1);
486 guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
487
488 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
489
490 if (Options::gPGEnabled())
491 // This is the offset in image coordinates, but needs to be converted to RA.
492 pmath->getGPG().startDithering(diff_x, diff_y, pmath->getCalibration());
493
494 state = GUIDE_DITHERING;
495 emit newStatus(state);
496
497 const GuiderUtils::Vector xyMove(diff_x, diff_y, 0);
498 const GuiderUtils::Vector raDecMove = pmath->getCalibration().rotateToRaDec(xyMove);
499 double raPulse = fabs(raDecMove.x * pmath->getCalibration().raPulseMillisecondsPerArcsecond());
500 double decPulse = fabs(raDecMove.y * pmath->getCalibration().decPulseMillisecondsPerArcsecond());
501 auto raDir = raDecMove.x > 0 ? RA_INC_DIR : RA_DEC_DIR;
502 auto decDir = raDecMove.y > 0 ? DEC_DEC_DIR : DEC_INC_DIR;
503
504 m_isFirstFrame = true;
505
506 // Send pulse if we have one active direction at least.
507 QString raDirString = raDir == RA_DEC_DIR ? "RA_DEC" : "RA_INC";
508 QString decDirString = decDir == DEC_INC_DIR ? "DEC_INC" : "DEC_DEC";
509
510 qCDebug(KSTARS_EKOS_GUIDE) << "OnePulseDither RA: " << raPulse << "ms" << raDirString << " DEC: " << decPulse << "ms " <<
512 emit newMultiPulse(raDir, raPulse, decDir, decPulse, StartCaptureAfterPulses);
513
514 double totalMSecs = 1000.0 * Options::ditherSettle() + std::max(raPulse, decPulse);
515
516 state = GUIDE_DITHERING_SETTLE;
517 guideLog.settleStartedInfo();
518 emit newStatus(state);
519
520 if (Options::gPGEnabled())
521 pmath->getGPG().ditheringSettled(true);
522
523 QTimer::singleShot(totalMSecs, this, SLOT(setDitherSettled()));
524 return true;
525}
526
527bool InternalGuider::abortDither()
528{
529 if (Options::ditherFailAbortsAutoGuide())
530 {
531 emit newStatus(Ekos::GUIDE_DITHERING_ERROR);
532 abort();
533 return false;
534 }
535 else
536 {
537 emit newLog(i18n("Warning: Dithering failed. Autoguiding shall continue as set in the options in case "
538 "of dither failure."));
539
540 if (Options::ditherSettle() > 0)
541 {
542 state = GUIDE_DITHERING_SETTLE;
543 guideLog.settleStartedInfo();
544 emit newStatus(state);
545 }
546
547 if (Options::gPGEnabled())
548 pmath->getGPG().ditheringSettled(false);
549
550 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
551 return true;
552 }
553}
554
555bool InternalGuider::processManualDithering()
556{
557 double cur_x, cur_y;
558 pmath->getTargetPosition(&cur_x, &cur_y);
559 pmath->getStarScreenPosition(&cur_x, &cur_y);
560
561 // These will be the RA & DEC drifts of the current star position from the reticle position in pixels.
562 double driftRA, driftDEC;
563 pmath->getCalibration().computeDrift(
564 GuiderUtils::Vector(cur_x, cur_y, 0),
565 GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0),
566 &driftRA, &driftDEC);
567
568 qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dithering in progress. Diff star X:" << driftRA << "Y:" << driftDEC;
569
570 if (fabs(driftRA) < guideBoxSize / 5.0 && fabs(driftDEC) < guideBoxSize / 5.0)
571 {
572 if (m_ProgressiveDither.empty() == false)
573 {
574 m_DitherTargetPosition = m_ProgressiveDither.dequeue();
575 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
576 qCDebug(KSTARS_EKOS_GUIDE) << "Next Dither Jump X:" << m_DitherTargetPosition.x << "Jump Y:" << m_DitherTargetPosition.y;
577 m_DitherRetries = 0;
578
579 processGuiding();
580
581 return true;
582 }
583
584 if (fabs(driftRA) < 1 && fabs(driftDEC) < 1)
585 {
586 pmath->setTargetPosition(cur_x, cur_y);
587 qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dither complete.";
588
589 if (Options::ditherSettle() > 0)
590 {
591 state = GUIDE_DITHERING_SETTLE;
592 guideLog.settleStartedInfo();
593 emit newStatus(state);
594 }
595
596 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
597 }
598 else
599 {
600 processGuiding();
601 }
602 }
603 else
604 {
605 if (++m_DitherRetries > Options::ditherMaxIterations())
606 {
607 emit newLog(i18n("Warning: Manual Dithering failed."));
608
609 if (Options::ditherSettle() > 0)
610 {
611 state = GUIDE_DITHERING_SETTLE;
612 guideLog.settleStartedInfo();
613 emit newStatus(state);
614 }
615
616 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
617 return true;
618 }
619
620 processGuiding();
621 }
622
623 return true;
624}
625
626void InternalGuider::setDitherSettled()
627{
628 guideLog.settleCompletedInfo();
629 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
630
631 // Back to guiding
632 state = GUIDE_GUIDING;
633}
634
635bool InternalGuider::calibrate()
636{
637 bool ccdInfo = true, scopeInfo = true;
639
640 if (subW == 0 || subH == 0)
641 {
642 errMsg = "CCD";
643 ccdInfo = false;
644 }
645
646 if (mountAperture == 0.0 || mountFocalLength == 0.0)
647 {
648 scopeInfo = false;
649 if (ccdInfo == false)
650 errMsg += " & Telescope";
651 else
652 errMsg += "Telescope";
653 }
654
655 if (ccdInfo == false || scopeInfo == false)
656 {
657 KSNotification::error(i18n("%1 info are missing. Please set the values in INDI Control Panel.", errMsg),
658 i18n("Missing Information"));
659 return false;
660 }
661
662 if (state != GUIDE_CALIBRATING)
663 {
664 pmath->getTargetPosition(&calibrationStartX, &calibrationStartY);
665 calibrationProcess.reset(
666 new CalibrationProcess(calibrationStartX, calibrationStartY,
667 !Options::twoAxisEnabled()));
668 state = GUIDE_CALIBRATING;
669 emit newStatus(GUIDE_CALIBRATING);
670 }
671
672 if (calibrationProcess->inProgress())
673 {
674 iterateCalibration();
675 return true;
676 }
677
678 if (restoreCalibration())
679 {
680 calibrationProcess.reset();
681 emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS);
682 KSNotification::event(QLatin1String("CalibrationRestored"),
683 i18n("Guiding calibration restored"), KSNotification::Guide);
684 reset();
685 return true;
686 }
687
688 // Initialize the calibration parameters.
689 // CCD pixel values comes in in microns and we want mm.
690 pmath->getMutableCalibration()->setParameters(
691 ccdPixelSizeX / 1000.0, ccdPixelSizeY / 1000.0, mountFocalLength,
692 subBinX, subBinY, pierSide, mountRA, mountDEC);
693
694 calibrationProcess->useCalibration(pmath->getMutableCalibration());
695
696 m_GuideFrame->disconnect(this);
697
698 // Must reset dec swap before we run any calibration procedure!
699 emit DESwapChanged(false);
700 pmath->setLostStar(false);
701
702 if (Options::saveGuideLog())
703 guideLog.enable();
704 GuideLog::GuideInfo info;
705 fillGuideInfo(&info);
706 guideLog.startCalibration(info);
707
708 calibrationProcess->startup();
709 calibrationProcess->setGuideLog(&guideLog);
710 iterateCalibration();
711
712 return true;
713}
714
715void InternalGuider::iterateCalibration()
716{
717 if (calibrationProcess->inProgress())
718 {
719 auto const timeStep = calculateGPGTimeStep();
720 pmath->performProcessing(GUIDE_CALIBRATING, m_ImageData, m_GuideFrame, timeStep);
721
722 QString info = "";
723 if (pmath->usingSEPMultiStar())
724 {
725 auto gs = pmath->getGuideStars();
726 info = QString("%1 stars, %2/%3 refs")
727 .arg(gs.getNumStarsDetected())
728 .arg(gs.getNumReferencesFound())
729 .arg(gs.getNumReferences());
730 }
731 emit guideInfo(info);
732
733 if (pmath->isStarLost())
734 {
735 emit newLog(i18n("Lost track of the guide star. Try increasing the square size or reducing pulse duration."));
736 emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
737 emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY,
738 i18n("Guide Star lost."));
739 reset();
740 return;
741 }
742 }
743 double starX, starY;
744 pmath->getStarScreenPosition(&starX, &starY);
745 calibrationProcess->iterate(starX, starY);
746
747 auto status = calibrationProcess->getStatus();
748 if (status != GUIDE_CALIBRATING)
749 emit newStatus(status);
750
751 QString logStatus = calibrationProcess->getLogStatus();
752 if (logStatus.length())
753 emit newLog(logStatus);
754
756 double x, y;
757 GuideInterface::CalibrationUpdateType type;
758 calibrationProcess->getCalibrationUpdate(&type, &updateMessage, &x, &y);
759 if (updateMessage.length())
760 emit calibrationUpdate(type, updateMessage, x, y);
761
762 GuideDirection pulseDirection;
763 int pulseMsecs;
764 calibrationProcess->getPulse(&pulseDirection, &pulseMsecs);
765 if (pulseDirection != NO_DIR)
766 emit newSinglePulse(pulseDirection, pulseMsecs, StartCaptureAfterPulses);
767
768 if (status == GUIDE_CALIBRATION_ERROR)
769 {
770 KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed"),
771 KSNotification::Guide, KSNotification::Alert);
772 reset();
773 }
774 else if (status == GUIDE_CALIBRATION_SUCCESS)
775 {
776 KSNotification::event(QLatin1String("CalibrationSuccessful"),
777 i18n("Guiding calibration completed successfully"), KSNotification::Guide);
778 emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled());
779 pmath->setTargetPosition(calibrationStartX, calibrationStartY);
780 reset();
781 }
782}
783
784void InternalGuider::setGuideView(const QSharedPointer<GuideView> &guideView)
785{
786 m_GuideFrame = guideView;
787}
788
789void InternalGuider::setImageData(const QSharedPointer<FITSData> &data)
790{
791 m_ImageData = data;
792 if (Options::saveGuideImages())
793 {
795 QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("guide/" +
796 now.toString("yyyy-MM-dd"));
797 QDir dir;
798 dir.mkpath(path);
799 // IS8601 contains colons but they are illegal under Windows OS, so replacing them with '-'
800 // The timestamp is no longer ISO8601 but it should solve interoperality issues between different OS hosts
801 QString name = "guide_frame_" + now.toString("HH-mm-ss") + ".fits";
802 QString filename = path + QStringLiteral("/") + name;
803 m_ImageData->saveImage(filename);
804 }
805}
806
807void InternalGuider::reset()
808{
809 qCDebug(KSTARS_EKOS_GUIDE) << "Resetting internal guider...";
810 state = GUIDE_IDLE;
811
812 resetDarkGuiding();
813
814 connect(m_GuideFrame.get(), &FITSView::trackingStarSelected, this, &InternalGuider::trackingStarSelected,
816 calibrationProcess.reset();
817}
818
819bool InternalGuider::clearCalibration()
820{
821 Options::setSerializedCalibration("");
822 pmath->getMutableCalibration()->reset();
823 return true;
824}
825
826bool InternalGuider::restoreCalibration()
827{
828 bool success = Options::reuseGuideCalibration() &&
829 pmath->getMutableCalibration()->restore(
830 pierSide, Options::reverseDecOnPierSideChange(),
831 subBinX, subBinY, &mountDEC);
832 if (success)
833 emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled());
834 return success;
835}
836
837void InternalGuider::setStarPosition(QVector3D &starCenter)
838{
839 pmath->setTargetPosition(starCenter.x(), starCenter.y());
840}
841
842void InternalGuider::trackingStarSelected(int x, int y)
843{
844 Q_UNUSED(x);
845 Q_UNUSED(y);
846 /*
847
848 Not sure what's going on here--manual star selection for calibration?
849 Don't really see how the logic works.
850
851 if (calibrationStage == CAL_IDLE)
852 return;
853
854 pmath->setTargetPosition(x, y);
855
856 calibrationStage = CAL_START;
857 */
858}
859
860void InternalGuider::setDECSwap(bool enable)
861{
862 pmath->getMutableCalibration()->setDeclinationSwapEnabled(enable);
863}
864
865void InternalGuider::setSquareAlgorithm(int index)
866{
867 if (index == SEP_MULTISTAR && !pmath->usingSEPMultiStar())
868 m_isFirstFrame = true;
869 pmath->setAlgorithmIndex(index);
870}
871
872bool InternalGuider::getReticleParameters(double * x, double * y)
873{
874 return pmath->getTargetPosition(x, y);
875}
876
877bool InternalGuider::setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture,
878 double mountFocalLength)
879{
880 this->ccdPixelSizeX = ccdPixelSizeX;
881 this->ccdPixelSizeY = ccdPixelSizeY;
882 this->mountAperture = mountAperture;
883 this->mountFocalLength = mountFocalLength;
884 return pmath->setGuiderParameters(ccdPixelSizeX, ccdPixelSizeY, mountAperture, mountFocalLength);
885}
886
887bool InternalGuider::setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY)
888{
889 if (w <= 0 || h <= 0)
890 return false;
891
892 subX = x;
893 subY = y;
894 subW = w;
895 subH = h;
896
897 subBinX = binX;
898 subBinY = binY;
899
900 pmath->setVideoParameters(w, h, subBinX, subBinY);
901
902 return true;
903}
904
905void InternalGuider::emitAxisPulse(const cproc_out_params * out)
906{
907 double raPulse = out->pulse_length[GUIDE_RA];
908 double dePulse = out->pulse_length[GUIDE_DEC];
909
910 //If the pulse was not sent to the mount, it should have 0 value
911 if(out->pulse_dir[GUIDE_RA] == NO_DIR)
912 raPulse = 0;
913 //If the pulse was not sent to the mount, it should have 0 value
914 if(out->pulse_dir[GUIDE_DEC] == NO_DIR)
915 dePulse = 0;
916 //If the pulse was in the Negative direction, it should have a negative sign.
917 if(out->pulse_dir[GUIDE_RA] == RA_INC_DIR)
918 raPulse = -raPulse;
919 //If the pulse was in the Negative direction, it should have a negative sign.
920 if(out->pulse_dir[GUIDE_DEC] == DEC_INC_DIR)
921 dePulse = -dePulse;
922
923 emit newAxisPulse(raPulse, dePulse);
924}
925
926bool InternalGuider::processGuiding()
927{
928 const cproc_out_params *out;
929
930 // On first frame, center the box (reticle) around the star so we do not start with an offset the results in
931 // unnecessary guiding pulses.
932 bool process = true;
933
934 if (m_isFirstFrame)
935 {
936 m_isFirstFrame = false;
937 if (state == GUIDE_GUIDING)
938 {
939 GuiderUtils::Vector star_pos = pmath->findLocalStarPosition(m_ImageData, m_GuideFrame, true);
940 if (star_pos.x != -1 && star_pos.y != -1)
941 pmath->setTargetPosition(star_pos.x, star_pos.y);
942 else
943 {
944 // We were not able to get started.
945 process = false;
946 m_isFirstFrame = true;
947 }
948 }
949 }
950
951 if (process)
952 {
953 auto const timeStep = calculateGPGTimeStep();
954 pmath->performProcessing(state, m_ImageData, m_GuideFrame, timeStep, &guideLog);
955 if (pmath->usingSEPMultiStar())
956 {
957 QString info = "";
958 auto gs = pmath->getGuideStars();
959 info = QString("%1 stars, %2/%3 refs")
960 .arg(gs.getNumStarsDetected())
961 .arg(gs.getNumReferencesFound())
962 .arg(gs.getNumReferences());
963
964 emit guideInfo(info);
965 }
966
967 // Restart the dark-guiding timer, so we get the full interval on its 1st timeout.
968 if (this->m_darkGuideTimer->isActive())
969 this->m_darkGuideTimer->start();
970 }
971
972 if (state == GUIDE_SUSPENDED)
973 {
974 if (Options::gPGEnabled())
975 emit frameCaptureRequested();
976 return true;
977 }
978 else
979 {
980 if (pmath->isStarLost())
981 m_starLostCounter++;
982 else
983 m_starLostCounter = 0;
984 }
985
986 // do pulse
987 out = pmath->getOutputParameters();
988
989 if (isPoorGuiding(out))
990 return true;
991
992 bool sendPulses = !pmath->isStarLost();
993
994
995 // Send pulse if we have one active direction at least.
996 if (sendPulses && (out->pulse_dir[GUIDE_RA] != NO_DIR || out->pulse_dir[GUIDE_DEC] != NO_DIR))
997 {
998 emit newMultiPulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA],
999 out->pulse_dir[GUIDE_DEC], out->pulse_length[GUIDE_DEC], StartCaptureAfterPulses);
1000 }
1001 else
1002 emit frameCaptureRequested();
1003
1004 if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
1005 return true;
1006
1007 // Hy 9/13/21: Check above just looks for GUIDE_DITHERING or GUIDE_MANUAL_DITHERING
1008 // but not the other dithering possibilities (error, success, settle).
1009 // Not sure if they should be included above, so conservatively not changing the
1010 // code, but don't think they should broadcast the newAxisDelta which might
1011 // interrup a capture.
1012 if (state < GUIDE_DITHERING)
1013 emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]);
1014
1015 emitAxisPulse(out);
1016 emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]);
1017 if (SEPMultiStarEnabled())
1018 emit newSNR(pmath->getGuideStarSNR());
1019
1020 return true;
1021}
1022
1023
1024// Here we calculate the time until the next time we will be emitting guiding corrections.
1025std::pair<Seconds, Seconds> InternalGuider::calculateGPGTimeStep()
1026{
1027 Seconds timeStep;
1028
1029 const Seconds guideDelay{(Options::guideDelay())};
1030
1031 auto const captureInterval = Seconds(m_captureTimer->intervalAsDuration()) + guideDelay;
1032 auto const darkGuideInterval = Seconds(m_darkGuideTimer->intervalAsDuration());
1033
1034 if (!Options::gPGDarkGuiding() || !isInferencePeriodFinished())
1035 {
1036 return std::pair<Seconds, Seconds>(captureInterval, captureInterval);
1037 }
1038 auto const captureTimeRemaining = Seconds(m_captureTimer->remainingTimeAsDuration()) + guideDelay;
1039 auto const darkGuideTimeRemaining = Seconds(m_darkGuideTimer->remainingTimeAsDuration());
1040 // Are both firing at the same time (or at least, both due)?
1041 if (captureTimeRemaining <= Seconds::zero()
1042 && darkGuideTimeRemaining <= Seconds::zero())
1043 {
1045 }
1046 else if (captureTimeRemaining <= Seconds::zero())
1047 {
1049 }
1050 else if (darkGuideTimeRemaining <= Seconds::zero())
1051 {
1053 }
1054 else
1055 {
1057 }
1058 return std::pair<Seconds, Seconds>(timeStep, captureInterval);
1059}
1060
1061
1062
1063void InternalGuider::darkGuide()
1064{
1065 // Only dark guide when guiding--e.g. don't dark guide if dithering.
1066 if (state != GUIDE_GUIDING)
1067 return;
1068
1069 if(Options::gPGDarkGuiding() && isInferencePeriodFinished())
1070 {
1071 const cproc_out_params *out;
1072 auto const timeStep = calculateGPGTimeStep();
1073 pmath->performDarkGuiding(state, timeStep);
1074
1075 out = pmath->getOutputParameters();
1076 emit newSinglePulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA], DontCaptureAfterPulses);
1077
1078 emitAxisPulse(out);
1079 }
1080}
1081
1082bool InternalGuider::isPoorGuiding(const cproc_out_params * out)
1083{
1084 double delta_rms = std::hypot(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]);
1085 if (delta_rms > Options::guideMaxDeltaRMS())
1086 m_highRMSCounter++;
1087 else
1088 m_highRMSCounter = 0;
1089
1090 uint8_t abortStarLostThreshold = (state == GUIDE_DITHERING
1091 || state == GUIDE_MANUAL_DITHERING) ? MAX_LOST_STAR_THRESHOLD * 3 : MAX_LOST_STAR_THRESHOLD;
1092 uint8_t abortRMSThreshold = (state == GUIDE_DITHERING
1093 || state == GUIDE_MANUAL_DITHERING) ? MAX_RMS_THRESHOLD * 3 : MAX_RMS_THRESHOLD;
1094 if (m_starLostCounter > abortStarLostThreshold || m_highRMSCounter > abortRMSThreshold)
1095 {
1096 qCDebug(KSTARS_EKOS_GUIDE) << "m_starLostCounter" << m_starLostCounter
1097 << "m_highRMSCounter" << m_highRMSCounter
1098 << "delta_rms" << delta_rms;
1099
1100 if (m_starLostCounter > abortStarLostThreshold)
1101 emit newLog(i18n("Lost track of the guide star. Searching for guide stars..."));
1102 else
1103 emit newLog(i18n("Delta RMS threshold value exceeded. Searching for guide stars..."));
1104
1105 reacquireTimer.start();
1106 rememberState = state;
1107 state = GUIDE_REACQUIRE;
1108 emit newStatus(state);
1109 return true;
1110 }
1111 return false;
1112}
1113bool InternalGuider::selectAutoStarSEPMultistar()
1114{
1115 m_GuideFrame->updateFrame();
1116 m_DitherOrigin = QVector3D(0, 0, 0);
1117 QVector3D newStarCenter = pmath->selectGuideStar(m_ImageData);
1118 if (newStarCenter.x() >= 0)
1119 {
1120 emit newStarPosition(newStarCenter, true);
1121 return true;
1122 }
1123 return false;
1124}
1125
1126bool InternalGuider::SEPMultiStarEnabled()
1127{
1128 return Options::guideAlgorithm() == SEP_MULTISTAR;
1129}
1130
1131bool InternalGuider::selectAutoStar()
1132{
1133 m_DitherOrigin = QVector3D(0, 0, 0);
1134 if (Options::guideAlgorithm() == SEP_MULTISTAR)
1135 return selectAutoStarSEPMultistar();
1136
1137 bool useNativeDetection = false;
1138
1139 QList<Edge *> starCenters;
1140
1141 if (Options::guideAlgorithm() != SEP_THRESHOLD)
1142 starCenters = GuideAlgorithms::detectStars(m_ImageData, m_GuideFrame->getTrackingBox());
1143
1144 if (starCenters.empty())
1145 {
1146 QVariantMap settings;
1147 settings["maxStarsCount"] = 50;
1148 settings["optionsProfileIndex"] = Options::guideOptionsProfile();
1149 settings["optionsProfileGroup"] = static_cast<int>(Ekos::GuideProfiles);
1150 m_ImageData->setSourceExtractorSettings(settings);
1151
1152 if (Options::guideAlgorithm() == SEP_THRESHOLD)
1153 m_ImageData->findStars(ALGORITHM_SEP).waitForFinished();
1154 else
1155 m_ImageData->findStars().waitForFinished();
1156
1157 starCenters = m_ImageData->getStarCenters();
1158 if (starCenters.empty())
1159 return false;
1160
1161 useNativeDetection = true;
1162 // For SEP, prefer flux total
1163 if (Options::guideAlgorithm() == SEP_THRESHOLD)
1164 std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b)
1165 {
1166 return a->val > b->val;
1167 });
1168 else
1169 std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b)
1170 {
1171 return a->width > b->width;
1172 });
1173
1174 m_GuideFrame->setStarsEnabled(true);
1175 m_GuideFrame->updateFrame();
1176 }
1177
1178 int maxX = m_ImageData->width();
1179 int maxY = m_ImageData->height();
1180
1181 int scores[MAX_GUIDE_STARS];
1182
1183 int maxIndex = MAX_GUIDE_STARS < starCenters.count() ? MAX_GUIDE_STARS : starCenters.count();
1184
1185 for (int i = 0; i < maxIndex; i++)
1186 {
1187 int score = 100;
1188
1189 Edge *center = starCenters.at(i);
1190
1192 {
1193 // Severely reject stars close to edges
1194 if (center->x < (center->width * 5) || center->y < (center->width * 5) ||
1195 center->x > (maxX - center->width * 5) || center->y > (maxY - center->width * 5))
1196 score -= 1000;
1197
1198 // Reject stars bigger than square
1199 if (center->width > float(guideBoxSize) / subBinX)
1200 score -= 1000;
1201 else
1202 {
1203 if (Options::guideAlgorithm() == SEP_THRESHOLD)
1204 score += sqrt(center->val);
1205 else
1206 // Moderately favor brighter stars
1207 score += center->width * center->width;
1208 }
1209
1210 // Moderately reject stars close to other stars
1211 foreach (Edge *edge, starCenters)
1212 {
1213 if (edge == center)
1214 continue;
1215
1216 if (fabs(center->x - edge->x) < center->width * 2 && fabs(center->y - edge->y) < center->width * 2)
1217 {
1218 score -= 15;
1219 break;
1220 }
1221 }
1222 }
1223 else
1224 {
1225 score = center->val;
1226 }
1227
1228 scores[i] = score;
1229 }
1230
1231 int maxScore = -1;
1232 int maxScoreIndex = -1;
1233 for (int i = 0; i < maxIndex; i++)
1234 {
1235 if (scores[i] > maxScore)
1236 {
1237 maxScore = scores[i];
1238 maxScoreIndex = i;
1239 }
1240 }
1241
1242 if (maxScoreIndex < 0)
1243 {
1244 qCDebug(KSTARS_EKOS_GUIDE) << "No suitable star detected.";
1245 return false;
1246 }
1247
1248 QVector3D newStarCenter(starCenters[maxScoreIndex]->x, starCenters[maxScoreIndex]->y, 0);
1249
1250 if (useNativeDetection == false)
1251 qDeleteAll(starCenters);
1252
1253 emit newStarPosition(newStarCenter, true);
1254
1255 return true;
1256}
1257
1258bool InternalGuider::reacquire()
1259{
1260 bool rc = selectAutoStar();
1261 if (rc)
1262 {
1263 m_highRMSCounter = m_starLostCounter = 0;
1264 m_isFirstFrame = true;
1265 pmath->reset();
1266 // If we were in the process of dithering, wait until settle and resume
1267 if (rememberState == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
1268 {
1269 if (Options::ditherSettle() > 0)
1270 {
1271 state = GUIDE_DITHERING_SETTLE;
1272 guideLog.settleStartedInfo();
1273 emit newStatus(state);
1274 }
1275
1276 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
1277 }
1278 else
1279 {
1280 state = GUIDE_GUIDING;
1281 emit newStatus(state);
1282 }
1283
1284 }
1285 else if (reacquireTimer.elapsed() > static_cast<int>(Options::guideLostStarTimeout() * 1000))
1286 {
1287 emit newLog(i18n("Failed to find any suitable guide stars. Aborting..."));
1288 abort();
1289 return false;
1290 }
1291
1292 emit frameCaptureRequested();
1293 return rc;
1294}
1295
1296void InternalGuider::fillGuideInfo(GuideLog::GuideInfo * info)
1297{
1298 // NOTE: just using the X values, phd2logview assumes x & y the same.
1299 // pixel scale in arc-sec / pixel. The 2nd and 3rd values seem redundent, but are
1300 // in the phd2 logs.
1301 info->pixelScale = (206.26481 * this->ccdPixelSizeX * this->subBinX) / this->mountFocalLength;
1302 info->binning = this->subBinX;
1303 info->focalLength = this->mountFocalLength;
1304 info->ra = this->mountRA.Degrees();
1305 info->dec = this->mountDEC.Degrees();
1306 info->azimuth = this->mountAzimuth.Degrees();
1307 info->altitude = this->mountAltitude.Degrees();
1308 info->pierSide = this->pierSide;
1309 info->xangle = pmath->getCalibration().getRAAngle();
1310 info->yangle = pmath->getCalibration().getDECAngle();
1311 // Calibration values in ms/pixel, xrate is in pixels/second.
1312 info->xrate = 1000.0 / pmath->getCalibration().raPulseMillisecondsPerArcsecond();
1313 info->yrate = 1000.0 / pmath->getCalibration().decPulseMillisecondsPerArcsecond();
1314}
1315
1316void InternalGuider::updateGPGParameters()
1317{
1318 setDarkGuideTimerInterval();
1319 pmath->getGPG().updateParameters();
1320}
1321
1322void InternalGuider::resetGPG()
1323{
1324 pmath->getGPG().reset();
1325 resetDarkGuiding();
1326}
1327
1328const Calibration &InternalGuider::getCalibration() const
1329{
1330 return pmath->getCalibration();
1331}
1332}
const double & Degrees() const
Definition dms.h:141
static constexpr double DegToRad
DegToRad is a const static member equal to the number of radians in one degree (dms::PI/180....
Definition dms.h:390
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:78
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardShortcut id)
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
QDateTime currentDateTime()
QString filePath(const QString &fileName) const const
qint64 elapsed() const const
virtual void close() override
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
T dequeue()
void enqueue(const T &t)
T * get() const const
QString arg(Args &&... args) const const
UniqueConnection
QTextStream & center(QTextStream &stream)
void timeout()
float x() const const
float y() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:02 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.