Kstars

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

KDE's Doxygen guidelines are available online.