File indexing completed on 2024-05-12 15:23:44
0001 /* 0002 SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>. 0003 0004 Based on lin_guider 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "internalguider.h" 0010 0011 #include "ekos_guide_debug.h" 0012 #include "gmath.h" 0013 #include "Options.h" 0014 #include "auxiliary/kspaths.h" 0015 #include "fitsviewer/fitsdata.h" 0016 #include "fitsviewer/fitsview.h" 0017 #include "guidealgorithms.h" 0018 #include "ksnotification.h" 0019 #include "ekos/auxiliary/stellarsolverprofileeditor.h" 0020 #include "fitsviewer/fitsdata.h" 0021 #include "../guideview.h" 0022 0023 #include <KMessageBox> 0024 0025 #include <random> 0026 #include <chrono> 0027 #include <QTimer> 0028 #include <QString> 0029 0030 #define MAX_GUIDE_STARS 10 0031 0032 using namespace std::chrono_literals; 0033 0034 namespace Ekos 0035 { 0036 InternalGuider::InternalGuider() 0037 { 0038 // Create math object 0039 pmath.reset(new cgmath()); 0040 connect(pmath.get(), &cgmath::newStarPosition, this, &InternalGuider::newStarPosition); 0041 connect(pmath.get(), &cgmath::guideStats, this, &InternalGuider::guideStats); 0042 0043 // Do this so that stored calibration will be visible on the 0044 // guide options menu. Calibration will get restored again when needed. 0045 pmath->getMutableCalibration()->restore( 0046 pierSide, Options::reverseDecOnPierSideChange(), subBinX, subBinY, nullptr); 0047 0048 state = GUIDE_IDLE; 0049 m_DitherOrigin = QVector3D(0, 0, 0); 0050 0051 emit guideInfo(""); 0052 0053 m_darkGuideTimer = std::make_unique<QTimer>(this); 0054 m_captureTimer = std::make_unique<QTimer>(this); 0055 0056 setDarkGuideTimerInterval(); 0057 0058 setExposureTime(); 0059 0060 connect(this, &Ekos::GuideInterface::frameCaptureRequested, this, [ = ]() 0061 { 0062 this->m_captureTimer->start(); 0063 }); 0064 } 0065 0066 void InternalGuider::setExposureTime() 0067 { 0068 Seconds seconds(Options::guideExposure()); 0069 setTimer(m_captureTimer, seconds); 0070 } 0071 0072 void InternalGuider::setTimer(std::unique_ptr<QTimer> &timer, Seconds seconds) 0073 { 0074 const std::chrono::duration<double, std::milli> inMilliseconds(seconds); 0075 timer->setInterval((int)(inMilliseconds.count())); 0076 } 0077 0078 void InternalGuider::setDarkGuideTimerInterval() 0079 { 0080 constexpr double kMinInterval = 0.5; // 0.5s is the shortest allowed dark-guiding period. 0081 const Seconds seconds(std::max(kMinInterval, Options::gPGDarkGuidingInterval())); 0082 setTimer(m_darkGuideTimer, seconds); 0083 } 0084 0085 void InternalGuider::resetDarkGuiding() 0086 { 0087 m_darkGuideTimer->stop(); 0088 m_captureTimer->stop(); 0089 } 0090 0091 bool InternalGuider::isInferencePeriodFinished() 0092 { 0093 auto const contribution = pmath->getGPG().predictionContribution(); 0094 return contribution >= 0.99; 0095 } 0096 bool InternalGuider::guide() 0097 { 0098 if (state >= GUIDE_GUIDING) 0099 { 0100 return processGuiding(); 0101 } 0102 0103 if (state == GUIDE_SUSPENDED) 0104 { 0105 return true; 0106 } 0107 m_GuideFrame->disconnect(this); 0108 0109 pmath->start(); 0110 emit guideInfo(""); 0111 0112 m_starLostCounter = 0; 0113 m_highRMSCounter = 0; 0114 m_DitherOrigin = QVector3D(0, 0, 0); 0115 0116 m_isFirstFrame = true; 0117 0118 if (state == GUIDE_IDLE) 0119 { 0120 if (Options::saveGuideLog()) 0121 guideLog.enable(); 0122 GuideLog::GuideInfo info; 0123 fillGuideInfo(&info); 0124 guideLog.startGuiding(info); 0125 } 0126 state = GUIDE_GUIDING; 0127 0128 emit newStatus(state); 0129 0130 emit frameCaptureRequested(); 0131 0132 startDarkGuiding(); 0133 0134 return true; 0135 } 0136 0137 /** 0138 * @brief InternalGuider::abort Abort all internal guider operations. 0139 * This includes calibration, dithering, guiding, capturing, and reaquiring. 0140 * The state is set to IDLE or ABORTED depending on the current state since 0141 * ABORTED can lead to different behavior by external actors than IDLE 0142 * @return True if abort succeeds, false otherwise. 0143 */ 0144 bool InternalGuider::abort() 0145 { 0146 // calibrationStage = CAL_IDLE; remove totally when understand trackingStarSelected 0147 0148 logFile.close(); 0149 guideLog.endGuiding(); 0150 emit guideInfo(""); 0151 0152 if (state == GUIDE_CALIBRATING || 0153 state == GUIDE_GUIDING || 0154 state == GUIDE_DITHERING || 0155 state == GUIDE_MANUAL_DITHERING || 0156 state == GUIDE_REACQUIRE) 0157 { 0158 if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) 0159 emit newStatus(GUIDE_DITHERING_ERROR); 0160 emit newStatus(GUIDE_ABORTED); 0161 0162 qCDebug(KSTARS_EKOS_GUIDE) << "Aborting" << getGuideStatusString(state); 0163 } 0164 else 0165 { 0166 emit newStatus(GUIDE_IDLE); 0167 qCDebug(KSTARS_EKOS_GUIDE) << "Stopping internal guider."; 0168 } 0169 0170 resetDarkGuiding(); 0171 disconnect(m_darkGuideTimer.get(), nullptr, nullptr, nullptr); 0172 0173 pmath->abort(); 0174 0175 0176 0177 m_ProgressiveDither.clear(); 0178 m_starLostCounter = 0; 0179 m_highRMSCounter = 0; 0180 0181 m_DitherOrigin = QVector3D(0, 0, 0); 0182 0183 pmath->suspend(false); 0184 state = GUIDE_IDLE; 0185 qCDebug(KSTARS_EKOS_GUIDE) << "Guiding aborted."; 0186 0187 return true; 0188 } 0189 0190 bool InternalGuider::suspend() 0191 { 0192 guideLog.pauseInfo(); 0193 state = GUIDE_SUSPENDED; 0194 0195 resetDarkGuiding(); 0196 emit newStatus(state); 0197 0198 pmath->suspend(true); 0199 emit guideInfo(""); 0200 0201 return true; 0202 } 0203 0204 void InternalGuider::startDarkGuiding() 0205 { 0206 if (Options::gPGDarkGuiding()) 0207 { 0208 connect(m_darkGuideTimer.get(), &QTimer::timeout, this, &InternalGuider::darkGuide, Qt::UniqueConnection); 0209 0210 // Start the two dark guide timers. The capture timer is started automatically by a signal. 0211 m_darkGuideTimer->start(); 0212 0213 qCDebug(KSTARS_EKOS_GUIDE) << "Starting dark guiding."; 0214 } 0215 } 0216 0217 bool InternalGuider::resume() 0218 { 0219 qCDebug(KSTARS_EKOS_GUIDE) << "Resuming..."; 0220 emit guideInfo(""); 0221 guideLog.resumeInfo(); 0222 state = GUIDE_GUIDING; 0223 emit newStatus(state); 0224 0225 pmath->suspend(false); 0226 0227 startDarkGuiding(); 0228 0229 setExposureTime(); 0230 0231 emit frameCaptureRequested(); 0232 0233 return true; 0234 } 0235 0236 bool InternalGuider::ditherXY(double x, double y) 0237 { 0238 m_ProgressiveDither.clear(); 0239 m_DitherRetries = 0; 0240 double cur_x, cur_y; 0241 pmath->getTargetPosition(&cur_x, &cur_y); 0242 0243 // Find out how many "jumps" we need to perform in order to get to target. 0244 // The current limit is now 1/4 of the box size to make sure the star stays within detection 0245 // threashold inside the window. 0246 double oneJump = (guideBoxSize / 4.0); 0247 double targetX = cur_x, targetY = cur_y; 0248 int xSign = (x >= cur_x) ? 1 : -1; 0249 int ySign = (y >= cur_y) ? 1 : -1; 0250 0251 do 0252 { 0253 if (fabs(targetX - x) > oneJump) 0254 targetX += oneJump * xSign; 0255 else if (fabs(targetX - x) < oneJump) 0256 targetX = x; 0257 0258 if (fabs(targetY - y) > oneJump) 0259 targetY += oneJump * ySign; 0260 else if (fabs(targetY - y) < oneJump) 0261 targetY = y; 0262 0263 m_ProgressiveDither.enqueue(GuiderUtils::Vector(targetX, targetY, -1)); 0264 0265 } 0266 while (targetX != x || targetY != y); 0267 0268 m_DitherTargetPosition = m_ProgressiveDither.dequeue(); 0269 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y); 0270 guideLog.ditherInfo(x, y, m_DitherTargetPosition.x, m_DitherTargetPosition.y); 0271 0272 state = GUIDE_MANUAL_DITHERING; 0273 emit newStatus(state); 0274 0275 processGuiding(); 0276 0277 return true; 0278 } 0279 0280 bool InternalGuider::dither(double pixels) 0281 { 0282 if (Options::ditherWithOnePulse() ) 0283 return onePulseDither(pixels); 0284 0285 double ret_x, ret_y; 0286 pmath->getTargetPosition(&ret_x, &ret_y); 0287 0288 // Just calling getStarScreenPosition() will get the position at the last time the guide star 0289 // was found, which is likely before the most recent guide pulse. 0290 // Instead we call findLocalStarPosition() which does the analysis from the image. 0291 // Unfortunately, processGuiding() will repeat that computation. 0292 // We currently don't cache it. 0293 GuiderUtils::Vector star_position = pmath->findLocalStarPosition(m_ImageData, m_GuideFrame, false); 0294 if (pmath->isStarLost() || (star_position.x == -1) || (star_position.y == -1)) 0295 { 0296 // If the star position is lost, just lose this iteration. 0297 // If it happens too many time, abort. 0298 if (++m_starLostCounter > MAX_LOST_STAR_THRESHOLD) 0299 { 0300 qCDebug(KSTARS_EKOS_GUIDE) << "Too many consecutive lost stars." << m_starLostCounter << "Aborting dither."; 0301 return abortDither(); 0302 } 0303 qCDebug(KSTARS_EKOS_GUIDE) << "Dither lost star. Trying again."; 0304 emit frameCaptureRequested(); 0305 return true; 0306 } 0307 else 0308 m_starLostCounter = 0; 0309 0310 if (state != GUIDE_DITHERING) 0311 { 0312 m_DitherRetries = 0; 0313 0314 auto seed = std::chrono::system_clock::now().time_since_epoch().count(); 0315 std::default_random_engine generator(seed); 0316 std::uniform_real_distribution<double> angleMagnitude(0, 360); 0317 0318 double angle = angleMagnitude(generator) * dms::DegToRad; 0319 double diff_x = pixels * cos(angle); 0320 double diff_y = pixels * sin(angle); 0321 0322 if (pmath->getCalibration().declinationSwapEnabled()) 0323 diff_y *= -1; 0324 0325 if (m_DitherOrigin.x() == 0 && m_DitherOrigin.y() == 0) 0326 { 0327 m_DitherOrigin = QVector3D(ret_x, ret_y, 0); 0328 } 0329 double totalXOffset = ret_x - m_DitherOrigin.x(); 0330 double totalYOffset = ret_y - m_DitherOrigin.y(); 0331 0332 // if we've dithered too far, and diff_x or diff_y is pushing us even further away, then change its direction. 0333 // Note: it is possible that we've dithered too far, but diff_x/y is pointing in the right direction. 0334 // Don't change it in that 2nd case. 0335 if (((diff_x + totalXOffset > MAX_DITHER_TRAVEL) && (diff_x > 0)) || 0336 ((diff_x + totalXOffset < -MAX_DITHER_TRAVEL) && (diff_x < 0))) 0337 { 0338 qCDebug(KSTARS_EKOS_GUIDE) 0339 << QString("Dithering target off by too much in X (abs(%1 + %2) > %3), adjust diff_x from %4 to %5") 0340 .arg(diff_x).arg(totalXOffset).arg(MAX_DITHER_TRAVEL).arg(diff_x).arg(diff_x * -1.5); 0341 diff_x *= -1.5; 0342 } 0343 if (((diff_y + totalYOffset > MAX_DITHER_TRAVEL) && (diff_y > 0)) || 0344 ((diff_y + totalYOffset < -MAX_DITHER_TRAVEL) && (diff_y < 0))) 0345 { 0346 qCDebug(KSTARS_EKOS_GUIDE) 0347 << QString("Dithering target off by too much in Y (abs(%1 + %2) > %3), adjust diff_y from %4 to %5") 0348 .arg(diff_y).arg(totalYOffset).arg(MAX_DITHER_TRAVEL).arg(diff_y).arg(diff_y * -1.5); 0349 diff_y *= -1.5; 0350 } 0351 0352 m_DitherTargetPosition = GuiderUtils::Vector(ret_x, ret_y, 0) + GuiderUtils::Vector(diff_x, diff_y, 0); 0353 0354 qCDebug(KSTARS_EKOS_GUIDE) 0355 << QString("Dithering by %1 pixels. Target: %2,%3 Current: %4,%5 Move: %6,%7 Wander: %8,%9") 0356 .arg(pixels, 3, 'f', 1) 0357 .arg(m_DitherTargetPosition.x, 5, 'f', 1).arg(m_DitherTargetPosition.y, 5, 'f', 1) 0358 .arg(ret_x, 5, 'f', 1).arg(ret_y, 5, 'f', 1) 0359 .arg(diff_x, 4, 'f', 1).arg(diff_y, 4, 'f', 1) 0360 .arg(totalXOffset + diff_x, 5, 'f', 1).arg(totalYOffset + diff_y, 5, 'f', 1); 0361 guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y); 0362 0363 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y); 0364 0365 if (Options::gPGEnabled()) 0366 // This is the offset in image coordinates, but needs to be converted to RA. 0367 pmath->getGPG().startDithering(diff_x, diff_y, pmath->getCalibration()); 0368 0369 state = GUIDE_DITHERING; 0370 emit newStatus(state); 0371 0372 processGuiding(); 0373 0374 return true; 0375 } 0376 0377 // These will be the RA & DEC drifts of the current star position from the reticle position in pixels. 0378 double driftRA, driftDEC; 0379 pmath->getCalibration().computeDrift( 0380 star_position, 0381 GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0), 0382 &driftRA, &driftDEC); 0383 0384 double pixelOffsetX = m_DitherTargetPosition.x - star_position.x; 0385 double pixelOffsetY = m_DitherTargetPosition.y - star_position.y; 0386 0387 qCDebug(KSTARS_EKOS_GUIDE) 0388 << QString("Dithering in progress. Current: %1,%2 Target: %3,%4 Diff: %5,%6 Wander: %8,%9") 0389 .arg(star_position.x, 5, 'f', 1).arg(star_position.y, 5, 'f', 1) 0390 .arg(m_DitherTargetPosition.x, 5, 'f', 1).arg(m_DitherTargetPosition.y, 5, 'f', 1) 0391 .arg(pixelOffsetX, 4, 'f', 1).arg(pixelOffsetY, 4, 'f', 1) 0392 .arg(star_position.x - m_DitherOrigin.x(), 5, 'f', 1) 0393 .arg(star_position.y - m_DitherOrigin.y(), 5, 'f', 1); 0394 0395 if (Options::ditherWithOnePulse() || (fabs(driftRA) < 1 && fabs(driftDEC) < 1)) 0396 { 0397 pmath->setTargetPosition(star_position.x, star_position.y); 0398 0399 // In one-pulse dithering we want the target to be whereever we end up 0400 // after the pulse. So, the first guide frame should not send any pulses 0401 // and should reset the reticle to the position it finds. 0402 if (Options::ditherWithOnePulse()) 0403 m_isFirstFrame = true; 0404 0405 qCDebug(KSTARS_EKOS_GUIDE) << "Dither complete."; 0406 0407 if (Options::ditherSettle() > 0) 0408 { 0409 state = GUIDE_DITHERING_SETTLE; 0410 guideLog.settleStartedInfo(); 0411 emit newStatus(state); 0412 } 0413 0414 if (Options::gPGEnabled()) 0415 pmath->getGPG().ditheringSettled(true); 0416 0417 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); 0418 } 0419 else 0420 { 0421 if (++m_DitherRetries > Options::ditherMaxIterations()) 0422 return abortDither(); 0423 0424 processGuiding(); 0425 } 0426 0427 return true; 0428 } 0429 bool InternalGuider::onePulseDither(double pixels) 0430 { 0431 qCDebug(KSTARS_EKOS_GUIDE) << "OnePulseDither(" << "pixels" << ")"; 0432 0433 // Cancel any current guide exposures. 0434 emit abortExposure(); 0435 0436 double ret_x, ret_y; 0437 pmath->getTargetPosition(&ret_x, &ret_y); 0438 0439 auto seed = std::chrono::system_clock::now().time_since_epoch().count(); 0440 std::default_random_engine generator(seed); 0441 std::uniform_real_distribution<double> angleMagnitude(0, 360); 0442 0443 double angle = angleMagnitude(generator) * dms::DegToRad; 0444 double diff_x = pixels * cos(angle); 0445 double diff_y = pixels * sin(angle); 0446 0447 if (pmath->getCalibration().declinationSwapEnabled()) 0448 diff_y *= -1; 0449 0450 if (m_DitherOrigin.x() == 0 && m_DitherOrigin.y() == 0) 0451 { 0452 m_DitherOrigin = QVector3D(ret_x, ret_y, 0); 0453 } 0454 double totalXOffset = ret_x - m_DitherOrigin.x(); 0455 double totalYOffset = ret_y - m_DitherOrigin.y(); 0456 0457 // If we've dithered too far, and diff_x or diff_y is pushing us even further away, then change its direction. 0458 // Note: it is possible that we've dithered too far, but diff_x/y is pointing in the right direction. 0459 // Don't change it in that 2nd case. 0460 if (((diff_x + totalXOffset > MAX_DITHER_TRAVEL) && (diff_x > 0)) || 0461 ((diff_x + totalXOffset < -MAX_DITHER_TRAVEL) && (diff_x < 0))) 0462 { 0463 qCDebug(KSTARS_EKOS_GUIDE) 0464 << QString("OPD: Dithering target off by too much in X (abs(%1 + %2) > %3), adjust diff_x from %4 to %5") 0465 .arg(diff_x).arg(totalXOffset).arg(MAX_DITHER_TRAVEL).arg(diff_x).arg(diff_x * -1.5); 0466 diff_x *= -1.5; 0467 } 0468 if (((diff_y + totalYOffset > MAX_DITHER_TRAVEL) && (diff_y > 0)) || 0469 ((diff_y + totalYOffset < -MAX_DITHER_TRAVEL) && (diff_y < 0))) 0470 { 0471 qCDebug(KSTARS_EKOS_GUIDE) 0472 << QString("OPD: Dithering target off by too much in Y (abs(%1 + %2) > %3), adjust diff_y from %4 to %5") 0473 .arg(diff_y).arg(totalYOffset).arg(MAX_DITHER_TRAVEL).arg(diff_y).arg(diff_y * -1.5); 0474 diff_y *= -1.5; 0475 } 0476 0477 m_DitherTargetPosition = GuiderUtils::Vector(ret_x, ret_y, 0) + GuiderUtils::Vector(diff_x, diff_y, 0); 0478 0479 qCDebug(KSTARS_EKOS_GUIDE) 0480 << QString("OPD: Dithering by %1 pixels. Target: %2,%3 Current: %4,%5 Move: %6,%7 Wander: %8,%9") 0481 .arg(pixels, 3, 'f', 1) 0482 .arg(m_DitherTargetPosition.x, 5, 'f', 1).arg(m_DitherTargetPosition.y, 5, 'f', 1) 0483 .arg(ret_x, 5, 'f', 1).arg(ret_y, 5, 'f', 1) 0484 .arg(diff_x, 4, 'f', 1).arg(diff_y, 4, 'f', 1) 0485 .arg(totalXOffset + diff_x, 5, 'f', 1).arg(totalYOffset + diff_y, 5, 'f', 1); 0486 guideLog.ditherInfo(diff_x, diff_y, m_DitherTargetPosition.x, m_DitherTargetPosition.y); 0487 0488 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y); 0489 0490 if (Options::gPGEnabled()) 0491 // This is the offset in image coordinates, but needs to be converted to RA. 0492 pmath->getGPG().startDithering(diff_x, diff_y, pmath->getCalibration()); 0493 0494 state = GUIDE_DITHERING; 0495 emit newStatus(state); 0496 0497 const GuiderUtils::Vector xyMove(diff_x, diff_y, 0); 0498 const GuiderUtils::Vector raDecMove = pmath->getCalibration().rotateToRaDec(xyMove); 0499 double raPulse = fabs(raDecMove.x * pmath->getCalibration().raPulseMillisecondsPerArcsecond()); 0500 double decPulse = fabs(raDecMove.y * pmath->getCalibration().decPulseMillisecondsPerArcsecond()); 0501 auto raDir = raDecMove.x > 0 ? RA_INC_DIR : RA_DEC_DIR; 0502 auto decDir = raDecMove.y > 0 ? DEC_DEC_DIR : DEC_INC_DIR; 0503 0504 m_isFirstFrame = true; 0505 0506 // Send pulse if we have one active direction at least. 0507 QString raDirString = raDir == RA_DEC_DIR ? "RA_DEC" : "RA_INC"; 0508 QString decDirString = decDir == DEC_INC_DIR ? "DEC_INC" : "DEC_DEC"; 0509 0510 qCDebug(KSTARS_EKOS_GUIDE) << "OnePulseDither RA: " << raPulse << "ms" << raDirString << " DEC: " << decPulse << "ms " << 0511 decDirString; 0512 emit newMultiPulse(raDir, raPulse, decDir, decPulse, StartCaptureAfterPulses); 0513 0514 double totalMSecs = 1000.0 * Options::ditherSettle() + std::max(raPulse, decPulse); 0515 0516 state = GUIDE_DITHERING_SETTLE; 0517 guideLog.settleStartedInfo(); 0518 emit newStatus(state); 0519 0520 if (Options::gPGEnabled()) 0521 pmath->getGPG().ditheringSettled(true); 0522 0523 QTimer::singleShot(totalMSecs, this, SLOT(setDitherSettled())); 0524 return true; 0525 } 0526 0527 bool InternalGuider::abortDither() 0528 { 0529 if (Options::ditherFailAbortsAutoGuide()) 0530 { 0531 emit newStatus(Ekos::GUIDE_DITHERING_ERROR); 0532 abort(); 0533 return false; 0534 } 0535 else 0536 { 0537 emit newLog(i18n("Warning: Dithering failed. Autoguiding shall continue as set in the options in case " 0538 "of dither failure.")); 0539 0540 if (Options::ditherSettle() > 0) 0541 { 0542 state = GUIDE_DITHERING_SETTLE; 0543 guideLog.settleStartedInfo(); 0544 emit newStatus(state); 0545 } 0546 0547 if (Options::gPGEnabled()) 0548 pmath->getGPG().ditheringSettled(false); 0549 0550 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); 0551 return true; 0552 } 0553 } 0554 0555 bool InternalGuider::processManualDithering() 0556 { 0557 double cur_x, cur_y; 0558 pmath->getTargetPosition(&cur_x, &cur_y); 0559 pmath->getStarScreenPosition(&cur_x, &cur_y); 0560 0561 // These will be the RA & DEC drifts of the current star position from the reticle position in pixels. 0562 double driftRA, driftDEC; 0563 pmath->getCalibration().computeDrift( 0564 GuiderUtils::Vector(cur_x, cur_y, 0), 0565 GuiderUtils::Vector(m_DitherTargetPosition.x, m_DitherTargetPosition.y, 0), 0566 &driftRA, &driftDEC); 0567 0568 qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dithering in progress. Diff star X:" << driftRA << "Y:" << driftDEC; 0569 0570 if (fabs(driftRA) < guideBoxSize / 5.0 && fabs(driftDEC) < guideBoxSize / 5.0) 0571 { 0572 if (m_ProgressiveDither.empty() == false) 0573 { 0574 m_DitherTargetPosition = m_ProgressiveDither.dequeue(); 0575 pmath->setTargetPosition(m_DitherTargetPosition.x, m_DitherTargetPosition.y); 0576 qCDebug(KSTARS_EKOS_GUIDE) << "Next Dither Jump X:" << m_DitherTargetPosition.x << "Jump Y:" << m_DitherTargetPosition.y; 0577 m_DitherRetries = 0; 0578 0579 processGuiding(); 0580 0581 return true; 0582 } 0583 0584 if (fabs(driftRA) < 1 && fabs(driftDEC) < 1) 0585 { 0586 pmath->setTargetPosition(cur_x, cur_y); 0587 qCDebug(KSTARS_EKOS_GUIDE) << "Manual Dither complete."; 0588 0589 if (Options::ditherSettle() > 0) 0590 { 0591 state = GUIDE_DITHERING_SETTLE; 0592 guideLog.settleStartedInfo(); 0593 emit newStatus(state); 0594 } 0595 0596 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); 0597 } 0598 else 0599 { 0600 processGuiding(); 0601 } 0602 } 0603 else 0604 { 0605 if (++m_DitherRetries > Options::ditherMaxIterations()) 0606 { 0607 emit newLog(i18n("Warning: Manual Dithering failed.")); 0608 0609 if (Options::ditherSettle() > 0) 0610 { 0611 state = GUIDE_DITHERING_SETTLE; 0612 guideLog.settleStartedInfo(); 0613 emit newStatus(state); 0614 } 0615 0616 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); 0617 return true; 0618 } 0619 0620 processGuiding(); 0621 } 0622 0623 return true; 0624 } 0625 0626 void InternalGuider::setDitherSettled() 0627 { 0628 guideLog.settleCompletedInfo(); 0629 emit newStatus(Ekos::GUIDE_DITHERING_SUCCESS); 0630 0631 // Back to guiding 0632 state = GUIDE_GUIDING; 0633 } 0634 0635 bool InternalGuider::calibrate() 0636 { 0637 bool ccdInfo = true, scopeInfo = true; 0638 QString errMsg; 0639 0640 if (subW == 0 || subH == 0) 0641 { 0642 errMsg = "CCD"; 0643 ccdInfo = false; 0644 } 0645 0646 if (mountAperture == 0.0 || mountFocalLength == 0.0) 0647 { 0648 scopeInfo = false; 0649 if (ccdInfo == false) 0650 errMsg += " & Telescope"; 0651 else 0652 errMsg += "Telescope"; 0653 } 0654 0655 if (ccdInfo == false || scopeInfo == false) 0656 { 0657 KSNotification::error(i18n("%1 info are missing. Please set the values in INDI Control Panel.", errMsg), 0658 i18n("Missing Information")); 0659 return false; 0660 } 0661 0662 if (state != GUIDE_CALIBRATING) 0663 { 0664 pmath->getTargetPosition(&calibrationStartX, &calibrationStartY); 0665 calibrationProcess.reset( 0666 new CalibrationProcess(calibrationStartX, calibrationStartY, 0667 !Options::twoAxisEnabled())); 0668 state = GUIDE_CALIBRATING; 0669 emit newStatus(GUIDE_CALIBRATING); 0670 } 0671 0672 if (calibrationProcess->inProgress()) 0673 { 0674 iterateCalibration(); 0675 return true; 0676 } 0677 0678 if (restoreCalibration()) 0679 { 0680 calibrationProcess.reset(); 0681 emit newStatus(Ekos::GUIDE_CALIBRATION_SUCCESS); 0682 KSNotification::event(QLatin1String("CalibrationRestored"), 0683 i18n("Guiding calibration restored"), KSNotification::Guide); 0684 reset(); 0685 return true; 0686 } 0687 0688 // Initialize the calibration parameters. 0689 // CCD pixel values comes in in microns and we want mm. 0690 pmath->getMutableCalibration()->setParameters( 0691 ccdPixelSizeX / 1000.0, ccdPixelSizeY / 1000.0, mountFocalLength, 0692 subBinX, subBinY, pierSide, mountRA, mountDEC); 0693 0694 calibrationProcess->useCalibration(pmath->getMutableCalibration()); 0695 0696 m_GuideFrame->disconnect(this); 0697 0698 // Must reset dec swap before we run any calibration procedure! 0699 emit DESwapChanged(false); 0700 pmath->setLostStar(false); 0701 0702 if (Options::saveGuideLog()) 0703 guideLog.enable(); 0704 GuideLog::GuideInfo info; 0705 fillGuideInfo(&info); 0706 guideLog.startCalibration(info); 0707 0708 calibrationProcess->startup(); 0709 calibrationProcess->setGuideLog(&guideLog); 0710 iterateCalibration(); 0711 0712 return true; 0713 } 0714 0715 void InternalGuider::iterateCalibration() 0716 { 0717 if (calibrationProcess->inProgress()) 0718 { 0719 auto const timeStep = calculateGPGTimeStep(); 0720 pmath->performProcessing(GUIDE_CALIBRATING, m_ImageData, m_GuideFrame, timeStep); 0721 0722 QString info = ""; 0723 if (pmath->usingSEPMultiStar()) 0724 { 0725 auto gs = pmath->getGuideStars(); 0726 info = QString("%1 stars, %2/%3 refs") 0727 .arg(gs.getNumStarsDetected()) 0728 .arg(gs.getNumReferencesFound()) 0729 .arg(gs.getNumReferences()); 0730 } 0731 emit guideInfo(info); 0732 0733 if (pmath->isStarLost()) 0734 { 0735 emit newLog(i18n("Lost track of the guide star. Try increasing the square size or reducing pulse duration.")); 0736 emit newStatus(Ekos::GUIDE_CALIBRATION_ERROR); 0737 emit calibrationUpdate(GuideInterface::CALIBRATION_MESSAGE_ONLY, 0738 i18n("Guide Star lost.")); 0739 reset(); 0740 return; 0741 } 0742 } 0743 double starX, starY; 0744 pmath->getStarScreenPosition(&starX, &starY); 0745 calibrationProcess->iterate(starX, starY); 0746 0747 auto status = calibrationProcess->getStatus(); 0748 if (status != GUIDE_CALIBRATING) 0749 emit newStatus(status); 0750 0751 QString logStatus = calibrationProcess->getLogStatus(); 0752 if (logStatus.length()) 0753 emit newLog(logStatus); 0754 0755 QString updateMessage; 0756 double x, y; 0757 GuideInterface::CalibrationUpdateType type; 0758 calibrationProcess->getCalibrationUpdate(&type, &updateMessage, &x, &y); 0759 if (updateMessage.length()) 0760 emit calibrationUpdate(type, updateMessage, x, y); 0761 0762 GuideDirection pulseDirection; 0763 int pulseMsecs; 0764 calibrationProcess->getPulse(&pulseDirection, &pulseMsecs); 0765 if (pulseDirection != NO_DIR) 0766 emit newSinglePulse(pulseDirection, pulseMsecs, StartCaptureAfterPulses); 0767 0768 if (status == GUIDE_CALIBRATION_ERROR) 0769 { 0770 KSNotification::event(QLatin1String("CalibrationFailed"), i18n("Guiding calibration failed"), 0771 KSNotification::Guide, KSNotification::Alert); 0772 reset(); 0773 } 0774 else if (status == GUIDE_CALIBRATION_SUCCESS) 0775 { 0776 KSNotification::event(QLatin1String("CalibrationSuccessful"), 0777 i18n("Guiding calibration completed successfully"), KSNotification::Guide); 0778 emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled()); 0779 pmath->setTargetPosition(calibrationStartX, calibrationStartY); 0780 reset(); 0781 } 0782 } 0783 0784 void InternalGuider::setGuideView(const QSharedPointer<GuideView> &guideView) 0785 { 0786 m_GuideFrame = guideView; 0787 } 0788 0789 void InternalGuider::setImageData(const QSharedPointer<FITSData> &data) 0790 { 0791 m_ImageData = data; 0792 if (Options::saveGuideImages()) 0793 { 0794 QDateTime now(QDateTime::currentDateTime()); 0795 QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("guide/" + 0796 now.toString("yyyy-MM-dd")); 0797 QDir dir; 0798 dir.mkpath(path); 0799 // IS8601 contains colons but they are illegal under Windows OS, so replacing them with '-' 0800 // The timestamp is no longer ISO8601 but it should solve interoperality issues between different OS hosts 0801 QString name = "guide_frame_" + now.toString("HH-mm-ss") + ".fits"; 0802 QString filename = path + QStringLiteral("/") + name; 0803 m_ImageData->saveImage(filename); 0804 } 0805 } 0806 0807 void InternalGuider::reset() 0808 { 0809 qCDebug(KSTARS_EKOS_GUIDE) << "Resetting internal guider..."; 0810 state = GUIDE_IDLE; 0811 0812 resetDarkGuiding(); 0813 0814 connect(m_GuideFrame.get(), &FITSView::trackingStarSelected, this, &InternalGuider::trackingStarSelected, 0815 Qt::UniqueConnection); 0816 calibrationProcess.reset(); 0817 } 0818 0819 bool InternalGuider::clearCalibration() 0820 { 0821 Options::setSerializedCalibration(""); 0822 pmath->getMutableCalibration()->reset(); 0823 return true; 0824 } 0825 0826 bool InternalGuider::restoreCalibration() 0827 { 0828 bool success = Options::reuseGuideCalibration() && 0829 pmath->getMutableCalibration()->restore( 0830 pierSide, Options::reverseDecOnPierSideChange(), 0831 subBinX, subBinY, &mountDEC); 0832 if (success) 0833 emit DESwapChanged(pmath->getCalibration().declinationSwapEnabled()); 0834 return success; 0835 } 0836 0837 void InternalGuider::setStarPosition(QVector3D &starCenter) 0838 { 0839 pmath->setTargetPosition(starCenter.x(), starCenter.y()); 0840 } 0841 0842 void InternalGuider::trackingStarSelected(int x, int y) 0843 { 0844 Q_UNUSED(x); 0845 Q_UNUSED(y); 0846 /* 0847 0848 Not sure what's going on here--manual star selection for calibration? 0849 Don't really see how the logic works. 0850 0851 if (calibrationStage == CAL_IDLE) 0852 return; 0853 0854 pmath->setTargetPosition(x, y); 0855 0856 calibrationStage = CAL_START; 0857 */ 0858 } 0859 0860 void InternalGuider::setDECSwap(bool enable) 0861 { 0862 pmath->getMutableCalibration()->setDeclinationSwapEnabled(enable); 0863 } 0864 0865 void InternalGuider::setSquareAlgorithm(int index) 0866 { 0867 if (index == SEP_MULTISTAR && !pmath->usingSEPMultiStar()) 0868 m_isFirstFrame = true; 0869 pmath->setAlgorithmIndex(index); 0870 } 0871 0872 bool InternalGuider::getReticleParameters(double * x, double * y) 0873 { 0874 return pmath->getTargetPosition(x, y); 0875 } 0876 0877 bool InternalGuider::setGuiderParams(double ccdPixelSizeX, double ccdPixelSizeY, double mountAperture, 0878 double mountFocalLength) 0879 { 0880 this->ccdPixelSizeX = ccdPixelSizeX; 0881 this->ccdPixelSizeY = ccdPixelSizeY; 0882 this->mountAperture = mountAperture; 0883 this->mountFocalLength = mountFocalLength; 0884 return pmath->setGuiderParameters(ccdPixelSizeX, ccdPixelSizeY, mountAperture, mountFocalLength); 0885 } 0886 0887 bool InternalGuider::setFrameParams(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t binX, uint16_t binY) 0888 { 0889 if (w <= 0 || h <= 0) 0890 return false; 0891 0892 subX = x; 0893 subY = y; 0894 subW = w; 0895 subH = h; 0896 0897 subBinX = binX; 0898 subBinY = binY; 0899 0900 pmath->setVideoParameters(w, h, subBinX, subBinY); 0901 0902 return true; 0903 } 0904 0905 void InternalGuider::emitAxisPulse(const cproc_out_params * out) 0906 { 0907 double raPulse = out->pulse_length[GUIDE_RA]; 0908 double dePulse = out->pulse_length[GUIDE_DEC]; 0909 0910 //If the pulse was not sent to the mount, it should have 0 value 0911 if(out->pulse_dir[GUIDE_RA] == NO_DIR) 0912 raPulse = 0; 0913 //If the pulse was not sent to the mount, it should have 0 value 0914 if(out->pulse_dir[GUIDE_DEC] == NO_DIR) 0915 dePulse = 0; 0916 //If the pulse was in the Negative direction, it should have a negative sign. 0917 if(out->pulse_dir[GUIDE_RA] == RA_INC_DIR) 0918 raPulse = -raPulse; 0919 //If the pulse was in the Negative direction, it should have a negative sign. 0920 if(out->pulse_dir[GUIDE_DEC] == DEC_INC_DIR) 0921 dePulse = -dePulse; 0922 0923 emit newAxisPulse(raPulse, dePulse); 0924 } 0925 0926 bool InternalGuider::processGuiding() 0927 { 0928 const cproc_out_params *out; 0929 0930 // On first frame, center the box (reticle) around the star so we do not start with an offset the results in 0931 // unnecessary guiding pulses. 0932 bool process = true; 0933 0934 if (m_isFirstFrame) 0935 { 0936 m_isFirstFrame = false; 0937 if (state == GUIDE_GUIDING) 0938 { 0939 GuiderUtils::Vector star_pos = pmath->findLocalStarPosition(m_ImageData, m_GuideFrame, true); 0940 if (star_pos.x != -1 && star_pos.y != -1) 0941 pmath->setTargetPosition(star_pos.x, star_pos.y); 0942 else 0943 { 0944 // We were not able to get started. 0945 process = false; 0946 m_isFirstFrame = true; 0947 } 0948 } 0949 } 0950 0951 if (process) 0952 { 0953 auto const timeStep = calculateGPGTimeStep(); 0954 pmath->performProcessing(state, m_ImageData, m_GuideFrame, timeStep, &guideLog); 0955 if (pmath->usingSEPMultiStar()) 0956 { 0957 QString info = ""; 0958 auto gs = pmath->getGuideStars(); 0959 info = QString("%1 stars, %2/%3 refs") 0960 .arg(gs.getNumStarsDetected()) 0961 .arg(gs.getNumReferencesFound()) 0962 .arg(gs.getNumReferences()); 0963 0964 emit guideInfo(info); 0965 } 0966 0967 // Restart the dark-guiding timer, so we get the full interval on its 1st timeout. 0968 if (this->m_darkGuideTimer->isActive()) 0969 this->m_darkGuideTimer->start(); 0970 } 0971 0972 if (state == GUIDE_SUSPENDED) 0973 { 0974 if (Options::gPGEnabled()) 0975 emit frameCaptureRequested(); 0976 return true; 0977 } 0978 else 0979 { 0980 if (pmath->isStarLost()) 0981 m_starLostCounter++; 0982 else 0983 m_starLostCounter = 0; 0984 } 0985 0986 // do pulse 0987 out = pmath->getOutputParameters(); 0988 0989 if (isPoorGuiding(out)) 0990 return true; 0991 0992 bool sendPulses = !pmath->isStarLost(); 0993 0994 0995 // Send pulse if we have one active direction at least. 0996 if (sendPulses && (out->pulse_dir[GUIDE_RA] != NO_DIR || out->pulse_dir[GUIDE_DEC] != NO_DIR)) 0997 { 0998 emit newMultiPulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA], 0999 out->pulse_dir[GUIDE_DEC], out->pulse_length[GUIDE_DEC], StartCaptureAfterPulses); 1000 } 1001 else 1002 emit frameCaptureRequested(); 1003 1004 if (state == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) 1005 return true; 1006 1007 // Hy 9/13/21: Check above just looks for GUIDE_DITHERING or GUIDE_MANUAL_DITHERING 1008 // but not the other dithering possibilities (error, success, settle). 1009 // Not sure if they should be included above, so conservatively not changing the 1010 // code, but don't think they should broadcast the newAxisDelta which might 1011 // interrup a capture. 1012 if (state < GUIDE_DITHERING) 1013 emit newAxisDelta(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); 1014 1015 emitAxisPulse(out); 1016 emit newAxisSigma(out->sigma[GUIDE_RA], out->sigma[GUIDE_DEC]); 1017 if (SEPMultiStarEnabled()) 1018 emit newSNR(pmath->getGuideStarSNR()); 1019 1020 return true; 1021 } 1022 1023 1024 // Here we calculate the time until the next time we will be emitting guiding corrections. 1025 std::pair<Seconds, Seconds> InternalGuider::calculateGPGTimeStep() 1026 { 1027 Seconds timeStep; 1028 1029 const Seconds guideDelay{(Options::guideDelay())}; 1030 1031 auto const captureInterval = Seconds(m_captureTimer->intervalAsDuration()) + guideDelay; 1032 auto const darkGuideInterval = Seconds(m_darkGuideTimer->intervalAsDuration()); 1033 1034 if (!Options::gPGDarkGuiding() || !isInferencePeriodFinished()) 1035 { 1036 return std::pair<Seconds, Seconds>(captureInterval, captureInterval); 1037 } 1038 auto const captureTimeRemaining = Seconds(m_captureTimer->remainingTimeAsDuration()) + guideDelay; 1039 auto const darkGuideTimeRemaining = Seconds(m_darkGuideTimer->remainingTimeAsDuration()); 1040 // Are both firing at the same time (or at least, both due)? 1041 if (captureTimeRemaining <= Seconds::zero() 1042 && darkGuideTimeRemaining <= Seconds::zero()) 1043 { 1044 timeStep = std::min(captureInterval, darkGuideInterval); 1045 } 1046 else if (captureTimeRemaining <= Seconds::zero()) 1047 { 1048 timeStep = std::min(captureInterval, darkGuideTimeRemaining); 1049 } 1050 else if (darkGuideTimeRemaining <= Seconds::zero()) 1051 { 1052 timeStep = std::min(captureTimeRemaining, darkGuideInterval); 1053 } 1054 else 1055 { 1056 timeStep = std::min(captureTimeRemaining, darkGuideTimeRemaining); 1057 } 1058 return std::pair<Seconds, Seconds>(timeStep, captureInterval); 1059 } 1060 1061 1062 1063 void InternalGuider::darkGuide() 1064 { 1065 // Only dark guide when guiding--e.g. don't dark guide if dithering. 1066 if (state != GUIDE_GUIDING) 1067 return; 1068 1069 if(Options::gPGDarkGuiding() && isInferencePeriodFinished()) 1070 { 1071 const cproc_out_params *out; 1072 auto const timeStep = calculateGPGTimeStep(); 1073 pmath->performDarkGuiding(state, timeStep); 1074 1075 out = pmath->getOutputParameters(); 1076 emit newSinglePulse(out->pulse_dir[GUIDE_RA], out->pulse_length[GUIDE_RA], DontCaptureAfterPulses); 1077 1078 emitAxisPulse(out); 1079 } 1080 } 1081 1082 bool InternalGuider::isPoorGuiding(const cproc_out_params * out) 1083 { 1084 double delta_rms = std::hypot(out->delta[GUIDE_RA], out->delta[GUIDE_DEC]); 1085 if (delta_rms > Options::guideMaxDeltaRMS()) 1086 m_highRMSCounter++; 1087 else 1088 m_highRMSCounter = 0; 1089 1090 uint8_t abortStarLostThreshold = (state == GUIDE_DITHERING 1091 || state == GUIDE_MANUAL_DITHERING) ? MAX_LOST_STAR_THRESHOLD * 3 : MAX_LOST_STAR_THRESHOLD; 1092 uint8_t abortRMSThreshold = (state == GUIDE_DITHERING 1093 || state == GUIDE_MANUAL_DITHERING) ? MAX_RMS_THRESHOLD * 3 : MAX_RMS_THRESHOLD; 1094 if (m_starLostCounter > abortStarLostThreshold || m_highRMSCounter > abortRMSThreshold) 1095 { 1096 qCDebug(KSTARS_EKOS_GUIDE) << "m_starLostCounter" << m_starLostCounter 1097 << "m_highRMSCounter" << m_highRMSCounter 1098 << "delta_rms" << delta_rms; 1099 1100 if (m_starLostCounter > abortStarLostThreshold) 1101 emit newLog(i18n("Lost track of the guide star. Searching for guide stars...")); 1102 else 1103 emit newLog(i18n("Delta RMS threshold value exceeded. Searching for guide stars...")); 1104 1105 reacquireTimer.start(); 1106 rememberState = state; 1107 state = GUIDE_REACQUIRE; 1108 emit newStatus(state); 1109 return true; 1110 } 1111 return false; 1112 } 1113 bool InternalGuider::selectAutoStarSEPMultistar() 1114 { 1115 m_GuideFrame->updateFrame(); 1116 m_DitherOrigin = QVector3D(0, 0, 0); 1117 QVector3D newStarCenter = pmath->selectGuideStar(m_ImageData); 1118 if (newStarCenter.x() >= 0) 1119 { 1120 emit newStarPosition(newStarCenter, true); 1121 return true; 1122 } 1123 return false; 1124 } 1125 1126 bool InternalGuider::SEPMultiStarEnabled() 1127 { 1128 return Options::guideAlgorithm() == SEP_MULTISTAR; 1129 } 1130 1131 bool InternalGuider::selectAutoStar() 1132 { 1133 m_DitherOrigin = QVector3D(0, 0, 0); 1134 if (Options::guideAlgorithm() == SEP_MULTISTAR) 1135 return selectAutoStarSEPMultistar(); 1136 1137 bool useNativeDetection = false; 1138 1139 QList<Edge *> starCenters; 1140 1141 if (Options::guideAlgorithm() != SEP_THRESHOLD) 1142 starCenters = GuideAlgorithms::detectStars(m_ImageData, m_GuideFrame->getTrackingBox()); 1143 1144 if (starCenters.empty()) 1145 { 1146 QVariantMap settings; 1147 settings["maxStarsCount"] = 50; 1148 settings["optionsProfileIndex"] = Options::guideOptionsProfile(); 1149 settings["optionsProfileGroup"] = static_cast<int>(Ekos::GuideProfiles); 1150 m_ImageData->setSourceExtractorSettings(settings); 1151 1152 if (Options::guideAlgorithm() == SEP_THRESHOLD) 1153 m_ImageData->findStars(ALGORITHM_SEP).waitForFinished(); 1154 else 1155 m_ImageData->findStars().waitForFinished(); 1156 1157 starCenters = m_ImageData->getStarCenters(); 1158 if (starCenters.empty()) 1159 return false; 1160 1161 useNativeDetection = true; 1162 // For SEP, prefer flux total 1163 if (Options::guideAlgorithm() == SEP_THRESHOLD) 1164 std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b) 1165 { 1166 return a->val > b->val; 1167 }); 1168 else 1169 std::sort(starCenters.begin(), starCenters.end(), [](const Edge * a, const Edge * b) 1170 { 1171 return a->width > b->width; 1172 }); 1173 1174 m_GuideFrame->setStarsEnabled(true); 1175 m_GuideFrame->updateFrame(); 1176 } 1177 1178 int maxX = m_ImageData->width(); 1179 int maxY = m_ImageData->height(); 1180 1181 int scores[MAX_GUIDE_STARS]; 1182 1183 int maxIndex = MAX_GUIDE_STARS < starCenters.count() ? MAX_GUIDE_STARS : starCenters.count(); 1184 1185 for (int i = 0; i < maxIndex; i++) 1186 { 1187 int score = 100; 1188 1189 Edge *center = starCenters.at(i); 1190 1191 if (useNativeDetection) 1192 { 1193 // Severely reject stars close to edges 1194 if (center->x < (center->width * 5) || center->y < (center->width * 5) || 1195 center->x > (maxX - center->width * 5) || center->y > (maxY - center->width * 5)) 1196 score -= 1000; 1197 1198 // Reject stars bigger than square 1199 if (center->width > float(guideBoxSize) / subBinX) 1200 score -= 1000; 1201 else 1202 { 1203 if (Options::guideAlgorithm() == SEP_THRESHOLD) 1204 score += sqrt(center->val); 1205 else 1206 // Moderately favor brighter stars 1207 score += center->width * center->width; 1208 } 1209 1210 // Moderately reject stars close to other stars 1211 foreach (Edge *edge, starCenters) 1212 { 1213 if (edge == center) 1214 continue; 1215 1216 if (fabs(center->x - edge->x) < center->width * 2 && fabs(center->y - edge->y) < center->width * 2) 1217 { 1218 score -= 15; 1219 break; 1220 } 1221 } 1222 } 1223 else 1224 { 1225 score = center->val; 1226 } 1227 1228 scores[i] = score; 1229 } 1230 1231 int maxScore = -1; 1232 int maxScoreIndex = -1; 1233 for (int i = 0; i < maxIndex; i++) 1234 { 1235 if (scores[i] > maxScore) 1236 { 1237 maxScore = scores[i]; 1238 maxScoreIndex = i; 1239 } 1240 } 1241 1242 if (maxScoreIndex < 0) 1243 { 1244 qCDebug(KSTARS_EKOS_GUIDE) << "No suitable star detected."; 1245 return false; 1246 } 1247 1248 QVector3D newStarCenter(starCenters[maxScoreIndex]->x, starCenters[maxScoreIndex]->y, 0); 1249 1250 if (useNativeDetection == false) 1251 qDeleteAll(starCenters); 1252 1253 emit newStarPosition(newStarCenter, true); 1254 1255 return true; 1256 } 1257 1258 bool InternalGuider::reacquire() 1259 { 1260 bool rc = selectAutoStar(); 1261 if (rc) 1262 { 1263 m_highRMSCounter = m_starLostCounter = 0; 1264 m_isFirstFrame = true; 1265 pmath->reset(); 1266 // If we were in the process of dithering, wait until settle and resume 1267 if (rememberState == GUIDE_DITHERING || state == GUIDE_MANUAL_DITHERING) 1268 { 1269 if (Options::ditherSettle() > 0) 1270 { 1271 state = GUIDE_DITHERING_SETTLE; 1272 guideLog.settleStartedInfo(); 1273 emit newStatus(state); 1274 } 1275 1276 QTimer::singleShot(Options::ditherSettle() * 1000, this, SLOT(setDitherSettled())); 1277 } 1278 else 1279 { 1280 state = GUIDE_GUIDING; 1281 emit newStatus(state); 1282 } 1283 1284 } 1285 else if (reacquireTimer.elapsed() > static_cast<int>(Options::guideLostStarTimeout() * 1000)) 1286 { 1287 emit newLog(i18n("Failed to find any suitable guide stars. Aborting...")); 1288 abort(); 1289 return false; 1290 } 1291 1292 emit frameCaptureRequested(); 1293 return rc; 1294 } 1295 1296 void InternalGuider::fillGuideInfo(GuideLog::GuideInfo * info) 1297 { 1298 // NOTE: just using the X values, phd2logview assumes x & y the same. 1299 // pixel scale in arc-sec / pixel. The 2nd and 3rd values seem redundent, but are 1300 // in the phd2 logs. 1301 info->pixelScale = (206.26481 * this->ccdPixelSizeX * this->subBinX) / this->mountFocalLength; 1302 info->binning = this->subBinX; 1303 info->focalLength = this->mountFocalLength; 1304 info->ra = this->mountRA.Degrees(); 1305 info->dec = this->mountDEC.Degrees(); 1306 info->azimuth = this->mountAzimuth.Degrees(); 1307 info->altitude = this->mountAltitude.Degrees(); 1308 info->pierSide = this->pierSide; 1309 info->xangle = pmath->getCalibration().getRAAngle(); 1310 info->yangle = pmath->getCalibration().getDECAngle(); 1311 // Calibration values in ms/pixel, xrate is in pixels/second. 1312 info->xrate = 1000.0 / pmath->getCalibration().raPulseMillisecondsPerArcsecond(); 1313 info->yrate = 1000.0 / pmath->getCalibration().decPulseMillisecondsPerArcsecond(); 1314 } 1315 1316 void InternalGuider::updateGPGParameters() 1317 { 1318 setDarkGuideTimerInterval(); 1319 pmath->getGPG().updateParameters(); 1320 } 1321 1322 void InternalGuider::resetGPG() 1323 { 1324 pmath->getGPG().reset(); 1325 resetDarkGuiding(); 1326 } 1327 1328 const Calibration &InternalGuider::getCalibration() const 1329 { 1330 return pmath->getCalibration(); 1331 } 1332 }