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

KDE's Doxygen guidelines are available online.