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
21 #include <KMessageBox>
22
23 #include <random>
24 #include <chrono>
25 #include <QTimer>
26
27 #define MAX_GUIDE_STARS 10
28
29 namespace Ekos
30 {
InternalGuider()31 InternalGuider::InternalGuider()
32 {
33 // Create math object
34 pmath.reset(new cgmath());
35 connect(pmath.get(), &cgmath::newStarPosition, this, &InternalGuider::newStarPosition);
36 connect(pmath.get(), &cgmath::guideStats, this, &InternalGuider::guideStats);
37
38 // Do this so that stored calibration will be visible on the
39 // guide options menu. Calibration will get restored again when needed.
40 pmath->getMutableCalibration()->restore(
41 pierSide, Options::reverseDecOnPierSideChange(), subBinX, subBinY, nullptr);
42
43 state = GUIDE_IDLE;
44 }
45
guide()46 bool InternalGuider::guide()
47 {
48 if (state >= GUIDE_GUIDING)
49 {
50 return processGuiding();
51 }
52
53 if (state == GUIDE_SUSPENDED)
54 {
55 return true;
56 }
57 guideFrame->disconnect(this);
58
59 pmath->start();
60
61 m_starLostCounter = 0;
62 m_highRMSCounter = 0;
63
64 m_isFirstFrame = true;
65
66 if (state == GUIDE_IDLE)
67 {
68 if (Options::saveGuideLog())
69 guideLog.enable();
70 GuideLog::GuideInfo info;
71 fillGuideInfo(&info);
72 guideLog.startGuiding(info);
73 }
74
75 state = GUIDE_GUIDING;
76 emit newStatus(state);
77
78 emit frameCaptureRequested();
79
80 return true;
81 }
82
83 /**
84 * @brief InternalGuider::abort Abort all internal guider operations.
85 * This includes calibration, dithering, guiding, capturing, and reaquiring.
86 * The state is set to IDLE or ABORTED depending on the current state since
87 * ABORTED can lead to different behavior by external actors than IDLE
88 * @return True if abort succeeds, false otherwise.
89 */
abort()90 bool InternalGuider::abort()
91 {
92 // calibrationStage = CAL_IDLE; remove totally when understand trackingStarSelected
93
94 logFile.close();
95 guideLog.endGuiding();
96
97 if (state == GUIDE_CALIBRATING ||
98 state == GUIDE_GUIDING ||
99 state == GUIDE_DITHERING ||
100 state == GUIDE_MANUAL_DITHERING ||
101 state == GUIDE_REACQUIRE)
102 {
103 if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
104 emit newStatus(GUIDE_DITHERING_ERROR);
105 emit newStatus(GUIDE_ABORTED);
106
107 qCDebug(KSTARS_EKOS_GUIDE) << "Aborting" << getGuideStatusString(state);
108 }
109 else
110 {
111 emit newStatus(GUIDE_IDLE);
112 qCDebug(KSTARS_EKOS_GUIDE) << "Stopping internal guider.";
113 }
114
115 pmath->abort();
116
117 m_ProgressiveDither.clear();
118 m_starLostCounter = 0;
119 m_highRMSCounter = 0;
120 accumulator.first = accumulator.second = 0;
121
122 pmath->suspend(false);
123 state = GUIDE_IDLE;
124
125 return true;
126 }
127
suspend()128 bool InternalGuider::suspend()
129 {
130 guideLog.pauseInfo();
131 state = GUIDE_SUSPENDED;
132 emit newStatus(state);
133
134 pmath->suspend(true);
135
136 return true;
137 }
138
resume()139 bool InternalGuider::resume()
140 {
141 guideLog.resumeInfo();
142 state = GUIDE_GUIDING;
143 emit newStatus(state);
144
145 pmath->suspend(false);
146
147 emit frameCaptureRequested();
148
149 return true;
150 }
151
ditherXY(double x,double y)152 bool InternalGuider::ditherXY(double x, double y)
153 {
154 m_ProgressiveDither.clear();
155 m_DitherRetries = 0;
156 double cur_x, cur_y;
157 pmath->getTargetPosition(&cur_x, &cur_y);
158
159 // Find out how many "jumps" we need to perform in order to get to target.
160 // The current limit is now 1/4 of the box size to make sure the star stays within detection
161 // threashold inside the window.
162 double oneJump = (guideBoxSize / 4.0);
163 double targetX = cur_x, targetY = cur_y;
164 int xSign = (x >= cur_x) ? 1 : -1;
165 int ySign = (y >= cur_y) ? 1 : -1;
166
167 do
168 {
169 if (fabs(targetX - x) > oneJump)
170 targetX += oneJump * xSign;
171 else if (fabs(targetX - x) < oneJump)
172 targetX = x;
173
174 if (fabs(targetY - y) > oneJump)
175 targetY += oneJump * ySign;
176 else if (fabs(targetY - y) < oneJump)
177 targetY = y;
178
179 m_ProgressiveDither.enqueue(GuiderUtils::Vector(targetX, targetY, -1));
180
181 }
182 while (targetX != x || targetY != y);
183
184 m_DitherTargetPosition = m_ProgressiveDither.dequeue();
185 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
186 guideLog.ditherInfo(x, y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
187
188 state = GUIDE_MANUAL_DITHERING;
189 emit newStatus(state);
190
191 processGuiding();
192
193 return true;
194 }
195
dither(double pixels)196 bool InternalGuider::dither(double pixels)
197 {
198 double ret_x, ret_y;
199 pmath->getTargetPosition(&ret_x, &ret_y);
200
201 // Just calling getStarScreenPosition() will get the position at the last time the guide star
202 // was found, which is likely before the most recent guide pulse.
203 // Instead we call findLocalStarPosition() which does the analysis from the image.
204 // Unfortunately, processGuiding() will repeat that computation.
205 // We currently don't cache it.
206 GuiderUtils::Vector star_position = pmath->findLocalStarPosition(m_ImageData, guideFrame);
207 if (pmath->isStarLost() || (star_position.x == -1) || (star_position.y == -1))
208 {
209 // If the star position is lost, just lose this iteration.
210 // If it happens too many time, abort.
211 constexpr int abortStarLostThreshold = MAX_LOST_STAR_THRESHOLD * 3;
212 if (++m_starLostCounter > abortStarLostThreshold)
213 {
214 qCDebug(KSTARS_EKOS_GUIDE) << "Too many consecutive lost stars." << m_starLostCounter << "Aborting dither.";
215 return abortDither();
216 }
217 qCDebug(KSTARS_EKOS_GUIDE) << "Dither lost star. Trying again.";
218 emit frameCaptureRequested();
219 return true;
220 }
221 else
222 m_starLostCounter = 0;
223
224 if (state != GUIDE_DITHERING)
225 {
226 m_DitherRetries = 0;
227
228 auto seed = std::chrono::system_clock::now().time_since_epoch().count();
229 std::default_random_engine generator(seed);
230 std::uniform_real_distribution<double> angleMagnitude(0, 360);
231
232 double angle = angleMagnitude(generator) * dms::DegToRad;
233 double diff_x = pixels * cos(angle);
234 double diff_y = pixels * sin(angle);
235
236 if (pmath->getCalibration().declinationSwapEnabled())
237 diff_y *= -1;
238
239 if (fabs(diff_x + accumulator.first) > MAX_DITHER_TRAVEL)
240 diff_x *= -1.5;
241 accumulator.first += diff_x;
242 if (fabs(diff_y + accumulator.second) > MAX_DITHER_TRAVEL)
243 diff_y *= -1.5;
244 accumulator.second += diff_y;
245
246 m_DitherTargetPosition = GuiderUtils::Vector(ret_x, ret_y, 0) + GuiderUtils::Vector(diff_x, diff_y, 0);
247
248 qCDebug(KSTARS_EKOS_GUIDE) << "Dithering process started.. Reticle Target Pos X " << m_DitherTargetPosition.x << " Y " <<
249 m_DitherTargetPosition.y;
250 guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y);
251
252 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
253
254 if (Options::gPGEnabled())
255 // This is the offset in image coordinates, but needs to be converted to RA.
256 pmath->getGPG().startDithering(diff_x, diff_y, pmath->getCalibration());
257
258 state = GUIDE_DITHERING;
259 emit newStatus(state);
260
261 processGuiding();
262
263 return true;
264 }
265
266 // These will be the RA & DEC drifts of the current star position from the reticle position in pixels.
267 double driftRA, driftDEC;
268 pmath->getCalibration().computeDrift(
269 star_position,
270 GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0),
271 &driftRA, &driftDEC);
272
273 qCDebug(KSTARS_EKOS_GUIDE) << "Dithering in progress. Current" << star_position.x << star_position.y << "Target" <<
274 m_DitherTargetPosition.x <<
275 m_DitherTargetPosition.y << "Diff star X:" << driftRA << "Y:" << driftDEC;
276
277 if (fabs(driftRA) < 1 && fabs(driftDEC) < 1)
278 {
279 pmath->setTargetPosition(star_position.x, star_position.y);
280 qCDebug(KSTARS_EKOS_GUIDE) << "Dither complete.";
281
282 if (Options::ditherSettle() > 0)
283 {
284 state = GUIDE_DITHERING_SETTLE;
285 guideLog.settleStartedInfo();
286 emit newStatus(state);
287 }
288
289 if (Options::gPGEnabled())
290 pmath->getGPG().ditheringSettled(true);
291
292 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
293 }
294 else
295 {
296 if (++m_DitherRetries > Options::ditherMaxIterations())
297 return abortDither();
298
299 processGuiding();
300 }
301
302 return true;
303 }
304
abortDither()305 bool InternalGuider::abortDither()
306 {
307 if (Options::ditherFailAbortsAutoGuide())
308 {
309 emit newStatus(Ekos::GUIDE_DITHERING_ERROR);
310 abort();
311 return false;
312 }
313 else
314 {
315 emit newLog(i18n("Warning: Dithering failed. Autoguiding shall continue as set in the options in case "
316 "of dither failure."));
317
318 if (Options::ditherSettle() > 0)
319 {
320 state = GUIDE_DITHERING_SETTLE;
321 guideLog.settleStartedInfo();
322 emit newStatus(state);
323 }
324
325 if (Options::gPGEnabled())
326 pmath->getGPG().ditheringSettled(false);
327
328 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
329 return true;
330 }
331 }
332
processManualDithering()333 bool InternalGuider::processManualDithering()
334 {
335 double cur_x, cur_y;
336 pmath->getTargetPosition(&cur_x, &cur_y);
337 pmath->getStarScreenPosition(&cur_x, &cur_y);
338
339 // These will be the RA & DEC drifts of the current star position from the reticle position in pixels.
340 double driftRA, driftDEC;
341 pmath->getCalibration().computeDrift(
342 GuiderUtils::Vector(cur_x, cur_y, 0),
343 GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0),
344 &driftRA, &driftDEC);
345
346 qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dithering in progress. Diff star X:" << driftRA << "Y:" << driftDEC;
347
348 if (fabs(driftRA) < guideBoxSize / 5.0 && fabs(driftDEC) < guideBoxSize / 5.0)
349 {
350 if (m_ProgressiveDither.empty() == false)
351 {
352 m_DitherTargetPosition = m_ProgressiveDither.dequeue();
353 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y);
354 qCDebug(KSTARS_EKOS_GUIDE) << "Next Dither Jump X:" << m_DitherTargetPosition.x << "Jump Y:" << m_DitherTargetPosition.y;
355 m_DitherRetries = 0;
356
357 processGuiding();
358
359 return true;
360 }
361
362 if (fabs(driftRA) < 1 && fabs(driftDEC) < 1)
363 {
364 pmath->setTargetPosition(cur_x, cur_y);
365 qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dither complete.";
366
367 if (Options::ditherSettle() > 0)
368 {
369 state = GUIDE_DITHERING_SETTLE;
370 guideLog.settleStartedInfo();
371 emit newStatus(state);
372 }
373
374 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
375 }
376 else
377 {
378 processGuiding();
379 }
380 }
381 else
382 {
383 if (++m_DitherRetries > Options::ditherMaxIterations())
384 {
385 emit newLog(i18n("Warning: Manual Dithering failed."));
386
387 if (Options::ditherSettle() > 0)
388 {
389 state = GUIDE_DITHERING_SETTLE;
390 guideLog.settleStartedInfo();
391 emit newStatus(state);
392 }
393
394 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
395 return true;
396 }
397
398 processGuiding();
399 }
400
401 return true;
402 }
403
setDitherSettled()404 void InternalGuider::setDitherSettled()
405 {
406 guideLog.settleCompletedInfo();
407 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS);
408
409 // Back to guiding
410 state = GUIDE_GUIDING;
411 }
412
calibrate()413 bool InternalGuider::calibrate()
414 {
415 bool ccdInfo = true, scopeInfo = true;
416 QString errMsg;
417
418 if (subW == 0 || subH == 0)
419 {
420 errMsg = "CCD";
421 ccdInfo = false;
422 }
423
424 if (mountAperture == 0.0 || mountFocalLength == 0.0)
425 {
426 scopeInfo = false;
427 if (ccdInfo == false)
428 errMsg += " & Telescope";
429 else
430 errMsg += "Telescope";
431 }
432
433 if (ccdInfo == false || scopeInfo == false)
434 {
435 KSNotification::error(i18n("%1 info are missing. Please set the values in INDI Control Panel.", errMsg),
436 i18n("Missing Information"));
437 return false;
438 }
439
440 if (state != GUIDE_CALIBRATING)
441 {
442 pmath->getTargetPosition(&calibrationStartX, &calibrationStartY);
443 calibrationProcess.reset(
444 new CalibrationProcess(calibrationStartX, calibrationStartY,
445 !Options::twoAxisEnabled()));
446 state = GUIDE_CALIBRATING;
447 emit newStatus(GUIDE_CALIBRATING);
448 }
449
450 if (calibrationProcess->inProgress())
451 {
452 iterateCalibration();
453 return true;
454 }
455
456 if (restoreCalibration())
457 {
458 calibrationProcess.reset();
459 emit newStatus(Ekos::GUIDE_CALIBRATION_SUCESS);
460 KSNotification::event(QLatin1String("CalibrationRestored"),
461 i18n("Guiding calibration restored"));
462 reset();
463 return true;
464 }
465
466 // Initialize the calibration parameters.
467 // CCD pixel values comes in in microns and we want mm.
468 pmath->getMutableCalibration()->setParameters(
469 ccdPixelSizeX / 1000.0, ccdPixelSizeY / 1000.0, mountFocalLength,
470 subBinX, subBinY, pierSide, mountRA, mountDEC);
471
472 calibrationProcess->useCalibration(pmath->getMutableCalibration());
473
474 guideFrame->disconnect(this);
475
476 // Must reset dec swap before we run any calibration procedure!
477 emit DESwapChanged(false);
478 pmath->setLostStar(false);
479
480 if (Options::saveGuideLog())
481 guideLog.enable();
482 GuideLog::GuideInfo info;
483 fillGuideInfo(&info);
484 guideLog.startCalibration(info);
485
486 calibrationProcess->startup();
487 calibrationProcess->setGuideLog(&guideLog);
488 iterateCalibration();
489
490 return true;
491 }
492
iterateCalibration()493 void InternalGuider::iterateCalibration()
494 {
495 if (calibrationProcess->inProgress())
496 {
497 pmath->performProcessing(GUIDE_CALIBRATING, m_ImageData, guideFrame);
498 if (pmath->isStarLost())
499 {
500 emit newLog(i18n("Lost track of the guide star. Try increasing the square size or reducing pulse duration."));
501 emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR);
502 emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY,
503 i18n("Guide Star lost."));
504 reset();
505 return;
506 }
507 }
508 double starX, starY;
509 pmath->getStarScreenPosition(&starX, &starY);
510 calibrationProcess->iterate(starX, starY);
511
512 auto status = calibrationProcess->getStatus();
513 if (status != GUIDE_CALIBRATING)
514 emit newStatus(status);
515
516 QString logStatus = calibrationProcess->getLogStatus();
517 if (logStatus.length())
518 emit newLog(logStatus);
519
520 QString updateMessage;
521 double x, y;
522 GuideInterface::CalibrationUpdateType type;
523 calibrationProcess->getCalibrationUpdate(&type, &updateMessage, &x, &y);
524 if (updateMessage.length())
525 emit calibrationUpdate(type, updateMessage, x, y);
526
527 GuideDirection pulseDirection;
528 int pulseMsecs;
529 calibrationProcess->getPulse(&pulseDirection, &pulseMsecs);
530 if (pulseDirection != NO_DIR)
531 emit newPulse(pulseDirection, pulseMsecs);
532
533 if (status == GUIDE_CALIBRATION_ERROR)
534 {
535 KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed"),
536 KSNotification::EVENT_ALERT);
537 reset();
538 }
539 else if (status == GUIDE_CALIBRATION_SUCESS)
540 {
541 KSNotification::event(QLatin1String("CalibrationSuccessful"),
542 i18n("Guiding calibration completed successfully"));
543 emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled());
544 pmath->setTargetPosition(calibrationStartX, calibrationStartY);
545 reset();
546 }
547 }
548
setGuideView(GuideView * guideView)549 void InternalGuider::setGuideView(GuideView *guideView)
550 {
551 guideFrame = guideView;
552 }
553
setImageData(const QSharedPointer<FITSData> & data)554 void InternalGuider::setImageData(const QSharedPointer<FITSData> &data)
555 {
556 m_ImageData = data;
557 }
558
reset()559 void InternalGuider::reset()
560 {
561 state = GUIDE_IDLE;
562
563 connect(guideFrame, SIGNAL(trackingStarSelected(int, int)), this, SLOT(trackingStarSelected(int, int)),
564 Qt::UniqueConnection);
565 calibrationProcess.reset();
566 }
567
clearCalibration()568 bool InternalGuider::clearCalibration()
569 {
570 Options::setSerializedCalibration("");
571 pmath->getMutableCalibration()->reset();
572 return true;
573 }
574
restoreCalibration()575 bool InternalGuider::restoreCalibration()
576 {
577 bool success = Options::reuseGuideCalibration() &&
578 pmath->getMutableCalibration()->restore(
579 pierSide, Options::reverseDecOnPierSideChange(),
580 subBinX, subBinY, &mountDEC);
581 if (success)
582 emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled());
583 return success;
584 }
585
setStarPosition(QVector3D & starCenter)586 void InternalGuider::setStarPosition(QVector3D &starCenter)
587 {
588 pmath->setTargetPosition(starCenter.x(), starCenter.y());
589 }
590
trackingStarSelected(int x,int y)591 void InternalGuider::trackingStarSelected(int x, int y)
592 {
593 /*
594
595 Not sure what's going on here--manual star selection for calibration?
596 Don't really see how the logic works.
597
598 if (calibrationStage == CAL_IDLE)
599 return;
600
601 pmath->setTargetPosition(x, y);
602
603 calibrationStage = CAL_START;
604 */
605 }
606
setDECSwap(bool enable)607 void InternalGuider::setDECSwap(bool enable)
608 {
609 pmath->getMutableCalibration()->setDeclinationSwapEnabled(enable);
610 }
611
setSquareAlgorithm(int index)612 void InternalGuider::setSquareAlgorithm(int index)
613 {
614 if (index == SEP_MULTISTAR && !pmath->usingSEPMultiStar())
615 m_isFirstFrame = true;
616 pmath->setAlgorithmIndex(index);
617 }
618
setReticleParameters(double x,double y)619 void InternalGuider::setReticleParameters(double x, double y)
620 {
621 pmath->setTargetPosition(x, y);
622 }
623
getReticleParameters(double * x,double * y)624 bool InternalGuider::getReticleParameters(double *x, double *y)
625 {
626 return pmath->getTargetPosition(x, y);
627 }
628
setGuiderParams(double ccdPixelSizeX,double ccdPixelSizeY,double mountAperture,double mountFocalLength)629 bool InternalGuider::setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture,
630 double mountFocalLength)
631 {
632 this->ccdPixelSizeX = ccdPixelSizeX;
633 this->ccdPixelSizeY = ccdPixelSizeY;
634 this->mountAperture = mountAperture;
635 this->mountFocalLength = mountFocalLength;
636 return pmath->setGuiderParameters(ccdPixelSizeX, ccdPixelSizeY, mountAperture, mountFocalLength);
637 }
638
setFrameParams(uint16_t x,uint16_t y,uint16_t w,uint16_t h,uint16_t binX,uint16_t binY)639 bool InternalGuider::setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY)
640 {
641 if (w <= 0 || h <= 0)
642 return false;
643
644 subX = x;
645 subY = y;
646 subW = w;
647 subH = h;
648
649 subBinX = binX;
650 subBinY = binY;
651
652 pmath->setVideoParameters(w, h, subBinX, subBinY);
653
654 return true;
655 }
656
processGuiding()657 bool InternalGuider::processGuiding()
658 {
659 const cproc_out_params *out;
660
661 // On first frame, center the box (reticle) around the star so we do not start with an offset the results in
662 // unnecessary guiding pulses.
663 if (m_isFirstFrame)
664 {
665 if (state == GUIDE_GUIDING)
666 {
667 GuiderUtils::Vector star_pos = pmath->findLocalStarPosition(m_ImageData, guideFrame);
668 pmath->setTargetPosition(star_pos.x, star_pos.y);
669 }
670 m_isFirstFrame = false;
671 }
672 // calc math. it tracks square
673 pmath->performProcessing(state, m_ImageData, guideFrame, &guideLog);
674
675 if (state == GUIDE_SUSPENDED)
676 {
677 if (Options::gPGEnabled())
678 emit frameCaptureRequested();
679 return true;
680 }
681
682 if (pmath->isStarLost())
683 m_starLostCounter++;
684 else
685 m_starLostCounter = 0;
686
687 // do pulse
688 out = pmath->getOutputParameters();
689
690 bool sendPulses = true;
691
692 double delta_rms = std::hypot(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]);
693 if (delta_rms > Options::guideMaxDeltaRMS())
694 m_highRMSCounter++;
695 else
696 m_highRMSCounter = 0;
697
698 uint8_t abortStarLostThreshold = (state == GUIDE_DITHERING
699 || state == GUIDE_MANUAL_DITHERING) ? MAX_LOST_STAR_THRESHOLD * 3 : MAX_LOST_STAR_THRESHOLD;
700 uint8_t abortRMSThreshold = (state == GUIDE_DITHERING
701 || state == GUIDE_MANUAL_DITHERING) ? MAX_RMS_THRESHOLD * 3 : MAX_RMS_THRESHOLD;
702 if (m_starLostCounter > abortStarLostThreshold || m_highRMSCounter > abortRMSThreshold)
703 {
704 qCDebug(KSTARS_EKOS_GUIDE) << "m_starLostCounter" << m_starLostCounter
705 << "m_highRMSCounter" << m_highRMSCounter
706 << "delta_rms" << delta_rms;
707
708 if (m_starLostCounter > abortStarLostThreshold)
709 emit newLog(i18n("Lost track of the guide star. Searching for guide stars..."));
710 else
711 emit newLog(i18n("Delta RMS threshold value exceeded. Searching for guide stars..."));
712
713 reacquireTimer.start();
714 rememberState = state;
715 state = GUIDE_REACQUIRE;
716 emit newStatus(state);
717 return true;
718 }
719
720 if (sendPulses)
721 {
722 emit newPulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA],
723 out->pulse_dir[GUIDE_DEC], out->pulse_length[GUIDE_DEC]);
724
725 // Wait until pulse is over before capturing an image
726 const int waitMS = qMax(out->pulse_length[GUIDE_RA], out->pulse_length[GUIDE_DEC]);
727 // If less than MAX_IMMEDIATE_CAPTURE ms, then capture immediately
728 if (waitMS > MAX_IMMEDIATE_CAPTURE)
729 // Issue frame requests MAX_IMMEDIATE_CAPTURE ms before timeout to account for
730 // propagation delays
731 QTimer::singleShot(waitMS - PROPAGATION_DELAY, [&]()
732 {
733 emit frameCaptureRequested();
734 });
735 else
736 emit frameCaptureRequested();
737 }
738 else
739 emit frameCaptureRequested();
740
741 if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
742 return true;
743
744 // Hy 9/13/21: Check above just looks for GUIDE_DITHERING or GUIDE_MANUAL_DITHERING
745 // but not the other dithering possibilities (error, success, settle).
746 // Not sure if they should be included above, so conservatively not changing the
747 // code, but don't think they should broadcast the newAxisDelta which might
748 // interrup a capture.
749 if (state < GUIDE_DITHERING)
750 emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]);
751
752 double raPulse = out->pulse_length[GUIDE_RA];
753 double dePulse = out->pulse_length[GUIDE_DEC];
754
755 //If the pulse was not sent to the mount, it should have 0 value
756 if(out->pulse_dir[GUIDE_RA] == NO_DIR)
757 raPulse = 0;
758 //If the pulse was not sent to the mount, it should have 0 value
759 if(out->pulse_dir[GUIDE_DEC] == NO_DIR)
760 dePulse = 0;
761 //If the pulse was in the Negative direction, it should have a negative sign.
762 if(out->pulse_dir[GUIDE_RA] == RA_INC_DIR)
763 raPulse = -raPulse;
764 //If the pulse was in the Negative direction, it should have a negative sign.
765 if(out->pulse_dir[GUIDE_DEC] == DEC_INC_DIR)
766 dePulse = -dePulse;
767
768 emit newAxisPulse(raPulse, dePulse);
769
770 emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]);
771 if (SEPMultiStarEnabled())
772 emit newSNR(pmath->getGuideStarSNR());
773
774 return true;
775 }
776
selectAutoStarSEPMultistar()777 bool InternalGuider::selectAutoStarSEPMultistar()
778 {
779 guideFrame->updateFrame();
780 QVector3D newStarCenter = pmath->selectGuideStar(m_ImageData);
781 if (newStarCenter.x() >= 0)
782 {
783 emit newStarPosition(newStarCenter, true);
784 return true;
785 }
786 return false;
787 }
788
SEPMultiStarEnabled()789 bool InternalGuider::SEPMultiStarEnabled()
790 {
791 return Options::guideAlgorithm() == SEP_MULTISTAR;
792 }
793
selectAutoStar()794 bool InternalGuider::selectAutoStar()
795 {
796 if (Options::guideAlgorithm() == SEP_MULTISTAR)
797 return selectAutoStarSEPMultistar();
798
799 bool useNativeDetection = false;
800
801 QList<Edge *> starCenters;
802
803 if (Options::guideAlgorithm() != SEP_THRESHOLD)
804 starCenters = GuideAlgorithms::detectStars(m_ImageData, guideFrame->getTrackingBox());
805
806 if (starCenters.empty())
807 {
808 QVariantMap settings;
809 settings["maxStarsCount"] = 50;
810 settings["optionsProfileIndex"] = Options::guideOptionsProfile();
811 settings["optionsProfileGroup"] = static_cast<int>(Ekos::GuideProfiles);
812 m_ImageData->setSourceExtractorSettings(settings);
813
814 if (Options::guideAlgorithm() == SEP_THRESHOLD)
815 m_ImageData->findStars(ALGORITHM_SEP).waitForFinished();
816 else
817 m_ImageData->findStars().waitForFinished();
818
819 starCenters = m_ImageData->getStarCenters();
820 if (starCenters.empty())
821 return false;
822
823 useNativeDetection = true;
824 // For SEP, prefer flux total
825 if (Options::guideAlgorithm() == SEP_THRESHOLD)
826 std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b)
827 {
828 return a->val > b->val;
829 });
830 else
831 std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b)
832 {
833 return a->width > b->width;
834 });
835
836 guideFrame->setStarsEnabled(true);
837 guideFrame->updateFrame();
838 }
839
840 int maxX = m_ImageData->width();
841 int maxY = m_ImageData->height();
842
843 int scores[MAX_GUIDE_STARS];
844
845 int maxIndex = MAX_GUIDE_STARS < starCenters.count() ? MAX_GUIDE_STARS : starCenters.count();
846
847 for (int i = 0; i < maxIndex; i++)
848 {
849 int score = 100;
850
851 Edge *center = starCenters.at(i);
852
853 if (useNativeDetection)
854 {
855 // Severely reject stars close to edges
856 if (center->x < (center->width * 5) || center->y < (center->width * 5) ||
857 center->x > (maxX - center->width * 5) || center->y > (maxY - center->width * 5))
858 score -= 1000;
859
860 // Reject stars bigger than square
861 if (center->width > float(guideBoxSize) / subBinX)
862 score -= 1000;
863 else
864 {
865 if (Options::guideAlgorithm() == SEP_THRESHOLD)
866 score += sqrt(center->val);
867 else
868 // Moderately favor brighter stars
869 score += center->width * center->width;
870 }
871
872 // Moderately reject stars close to other stars
873 foreach (Edge *edge, starCenters)
874 {
875 if (edge == center)
876 continue;
877
878 if (fabs(center->x - edge->x) < center->width * 2 && fabs(center->y - edge->y) < center->width * 2)
879 {
880 score -= 15;
881 break;
882 }
883 }
884 }
885 else
886 {
887 score = center->val;
888 }
889
890 scores[i] = score;
891 }
892
893 int maxScore = -1;
894 int maxScoreIndex = -1;
895 for (int i = 0; i < maxIndex; i++)
896 {
897 if (scores[i] > maxScore)
898 {
899 maxScore = scores[i];
900 maxScoreIndex = i;
901 }
902 }
903
904 if (maxScoreIndex < 0)
905 {
906 qCDebug(KSTARS_EKOS_GUIDE) << "No suitable star detected.";
907 return false;
908 }
909
910 QVector3D newStarCenter(starCenters[maxScoreIndex]->x, starCenters[maxScoreIndex]->y, 0);
911
912 if (useNativeDetection == false)
913 qDeleteAll(starCenters);
914
915 emit newStarPosition(newStarCenter, true);
916
917 return true;
918 }
919
reacquire()920 bool InternalGuider::reacquire()
921 {
922 bool rc = selectAutoStar();
923 if (rc)
924 {
925 m_highRMSCounter = m_starLostCounter = 0;
926 m_isFirstFrame = true;
927 pmath->reset();
928 // If we were in the process of dithering, wait until settle and resume
929 if (rememberState == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING)
930 {
931 if (Options::ditherSettle() > 0)
932 {
933 state = GUIDE_DITHERING_SETTLE;
934 guideLog.settleStartedInfo();
935 emit newStatus(state);
936 }
937
938 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled()));
939 }
940 else
941 {
942 state = GUIDE_GUIDING;
943 emit newStatus(state);
944 }
945
946 }
947 else if (reacquireTimer.elapsed() > static_cast<int>(Options::guideLostStarTimeout() * 1000))
948 {
949 emit newLog(i18n("Failed to find any suitable guide stars. Aborting..."));
950 abort();
951 return false;
952 }
953
954 emit frameCaptureRequested();
955 return rc;
956 }
957
fillGuideInfo(GuideLog::GuideInfo * info)958 void InternalGuider::fillGuideInfo(GuideLog::GuideInfo *info)
959 {
960 // NOTE: just using the X values, phd2logview assumes x & y the same.
961 // pixel scale in arc-sec / pixel. The 2nd and 3rd values seem redundent, but are
962 // in the phd2 logs.
963 info->pixelScale = (206.26481 * this->ccdPixelSizeX * this->subBinX) / this->mountFocalLength;
964 info->binning = this->subBinX;
965 info->focalLength = this->mountFocalLength;
966 info->ra = this->mountRA.Degrees();
967 info->dec = this->mountDEC.Degrees();
968 info->azimuth = this->mountAzimuth.Degrees();
969 info->altitude = this->mountAltitude.Degrees();
970 info->pierSide = this->pierSide;
971 info->xangle = pmath->getCalibration().getRAAngle();
972 info->yangle = pmath->getCalibration().getDECAngle();
973 // Calibration values in ms/pixel, xrate is in pixels/second.
974 info->xrate = 1000.0 / pmath->getCalibration().raPulseMillisecondsPerPixel();
975 info->yrate = 1000.0 / pmath->getCalibration().decPulseMillisecondsPerPixel();
976 }
977
updateGPGParameters()978 void InternalGuider::updateGPGParameters()
979 {
980 pmath->getGPG().updateParameters();
981 }
982
resetGPG()983 void InternalGuider::resetGPG()
984 {
985 pmath->getGPG().reset();
986 }
987
getCalibration() const988 const Calibration &InternalGuider::getCalibration() const
989 {
990 return pmath->getCalibration();
991 }
992 }
993