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

KDE's Doxygen guidelines are available online.