File indexing completed on 2024-05-12 15:23:41

0001 /*
0002     SPDX-FileCopyrightText: 2012 Andrew Stepanenko
0003 
0004     Modified by Jasem Mutlaq <mutlaqja@ikarustech.com> for KStars:
0005     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "gmath.h"
0011 
0012 #include "Options.h"
0013 #include "fitsviewer/fitsdata.h"
0014 #include "fitsviewer/fitsview.h"
0015 #include "auxiliary/kspaths.h"
0016 #include "ekos_guide_debug.h"
0017 #include "ekos/auxiliary/stellarsolverprofileeditor.h"
0018 #include "guidealgorithms.h"
0019 #include "guidelog.h"
0020 #include "../guideview.h"
0021 
0022 #include <QVector3D>
0023 #include <cmath>
0024 #include <set>
0025 
0026 // Qt version calming
0027 #include <qtendl.h>
0028 
0029 GuiderUtils::Vector cgmath::findLocalStarPosition(QSharedPointer<FITSData> &imageData,
0030         QSharedPointer<GuideView> &guideView, bool firstFrame)
0031 {
0032     GuiderUtils::Vector position;
0033     if (usingSEPMultiStar())
0034     {
0035         QRect trackingBox = guideView->getTrackingBox();
0036         position = guideStars.findGuideStar(imageData, trackingBox, guideView, firstFrame);
0037 
0038     }
0039     else
0040         position = GuideAlgorithms::findLocalStarPosition(
0041                        imageData, algorithm, video_width, video_height,
0042                        guideView->getTrackingBox());
0043 
0044     if (position.x == -1 || position.y == -1)
0045         setLostStar(true);
0046     return position;
0047 }
0048 
0049 
0050 cgmath::cgmath() : QObject()
0051 {
0052     // sky coord. system vars.
0053     starPosition = GuiderUtils::Vector(0);
0054     targetPosition = GuiderUtils::Vector(0);
0055 
0056     // processing
0057     in_params.reset();
0058     out_params.reset();
0059     driftUpto[GUIDE_RA] = driftUpto[GUIDE_DEC] = 0;
0060     drift[GUIDE_RA]                                = new double[CIRCULAR_BUFFER_SIZE];
0061     drift[GUIDE_DEC]                               = new double[CIRCULAR_BUFFER_SIZE];
0062     memset(drift[GUIDE_RA], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
0063     memset(drift[GUIDE_DEC], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
0064     drift_integral[GUIDE_RA] = drift_integral[GUIDE_DEC] = 0;
0065 
0066     logFile.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("guide_log.txt"));
0067     gpg.reset(new GPG());
0068 }
0069 
0070 cgmath::~cgmath()
0071 {
0072     delete[] drift[GUIDE_RA];
0073     delete[] drift[GUIDE_DEC];
0074 }
0075 
0076 bool cgmath::setVideoParameters(int vid_wd, int vid_ht, int binX, int binY)
0077 {
0078     if (vid_wd <= 0 || vid_ht <= 0)
0079         return false;
0080 
0081     video_width  = vid_wd / binX;
0082     video_height = vid_ht / binY;
0083 
0084     calibration.setBinningUsed(binX, binY);
0085     guideStars.setCalibration(calibration);
0086 
0087     return true;
0088 }
0089 
0090 bool cgmath::setGuiderParameters(double ccd_pix_wd, double ccd_pix_ht, double guider_aperture, double guider_focal)
0091 {
0092     if (ccd_pix_wd < 0)
0093         ccd_pix_wd = 0;
0094     if (ccd_pix_ht < 0)
0095         ccd_pix_ht = 0;
0096     if (guider_focal <= 0)
0097         guider_focal = 1;
0098 
0099     aperture = guider_aperture;
0100     guideStars.setCalibration(calibration);
0101 
0102     return true;
0103 }
0104 
0105 // This logging will be removed in favor of guidelog.h.
0106 void cgmath::createGuideLog()
0107 {
0108     logFile.close();
0109     logFile.open(QIODevice::WriteOnly | QIODevice::Text);
0110     QTextStream out(&logFile);
0111 
0112     out << "Guiding rate,x15 arcsec/sec: " << Qt::endl;
0113     out << "Focal,mm: " << calibration.getFocalLength() << Qt::endl;
0114     out << "Aperture,mm: " << aperture << Qt::endl;
0115     out << "F/D: " << calibration.getFocalLength() / aperture << Qt::endl;
0116     out << "Frame #, Time Elapsed (ms), RA Error (arcsec), RA Correction (ms), RA Correction Direction, DEC Error "
0117         "(arcsec), DEC Correction (ms), DEC Correction Direction"
0118         << Qt::endl;
0119 
0120     logTime.restart();
0121 }
0122 
0123 bool cgmath::setTargetPosition(double x, double y)
0124 {
0125     // check frame ranges
0126     if (x < 0)
0127         x = 0;
0128     if (y < 0)
0129         y = 0;
0130     if (x >= (double)video_width - 1)
0131         x = (double)video_width - 1;
0132     if (y >= (double)video_height - 1)
0133         y = (double)video_height - 1;
0134 
0135     targetPosition = GuiderUtils::Vector(x, y, 0);
0136 
0137     guideStars.setCalibration(calibration);
0138 
0139     return true;
0140 }
0141 
0142 bool cgmath::getTargetPosition(double *x, double *y) const
0143 {
0144     *x = targetPosition.x;
0145     *y = targetPosition.y;
0146     return true;
0147 }
0148 
0149 int cgmath::getAlgorithmIndex(void) const
0150 {
0151     return algorithm;
0152 }
0153 
0154 void cgmath::getStarScreenPosition(double *dx, double *dy) const
0155 {
0156     *dx = starPosition.x;
0157     *dy = starPosition.y;
0158 }
0159 
0160 bool cgmath::reset()
0161 {
0162     iterationCounter = 0;
0163     driftUpto[GUIDE_RA] = driftUpto[GUIDE_DEC] = 0;
0164     drift_integral[GUIDE_RA] = drift_integral[GUIDE_DEC] = 0;
0165     out_params.reset();
0166 
0167     memset(drift[GUIDE_RA], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
0168     memset(drift[GUIDE_DEC], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
0169 
0170     return true;
0171 }
0172 
0173 void cgmath::setAlgorithmIndex(int algorithmIndex)
0174 {
0175     if (algorithmIndex < 0 || algorithmIndex > SEP_MULTISTAR)
0176         return;
0177 
0178     algorithm = algorithmIndex;
0179 }
0180 
0181 bool cgmath::usingSEPMultiStar() const
0182 {
0183     return algorithm == SEP_MULTISTAR;
0184 }
0185 
0186 void cgmath::updateCircularBuffers(void)
0187 {
0188     iterationCounter++;
0189 
0190     driftUpto[GUIDE_RA]++;
0191     driftUpto[GUIDE_DEC]++;
0192     if (driftUpto[GUIDE_RA] >= CIRCULAR_BUFFER_SIZE)
0193         driftUpto[GUIDE_RA] = 0;
0194     if (driftUpto[GUIDE_DEC] >= CIRCULAR_BUFFER_SIZE)
0195         driftUpto[GUIDE_DEC] = 0;
0196 }
0197 
0198 //-------------------- Processing ---------------------------
0199 void cgmath::start()
0200 {
0201     iterationCounter                   = 0;
0202     driftUpto[GUIDE_RA] = driftUpto[GUIDE_DEC] = 0;
0203     drift_integral[GUIDE_RA] = drift_integral[GUIDE_DEC] = 0;
0204     out_params.reset();
0205 
0206     memset(drift[GUIDE_RA], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
0207     memset(drift[GUIDE_DEC], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
0208 
0209     if (calibration.getFocalLength() > 0 && aperture > 0)
0210         createGuideLog();
0211 
0212     gpg->reset();
0213 }
0214 
0215 void cgmath::abort()
0216 {
0217     guideStars.reset();
0218 }
0219 
0220 void cgmath::suspend(bool mode)
0221 {
0222     suspended = mode;
0223 }
0224 
0225 bool cgmath::isSuspended() const
0226 {
0227     return suspended;
0228 }
0229 
0230 bool cgmath::isStarLost() const
0231 {
0232     return lost_star;
0233 }
0234 
0235 void cgmath::setLostStar(bool is_lost)
0236 {
0237     lost_star = is_lost;
0238 }
0239 
0240 namespace
0241 {
0242 QString axisStr(int raDEC)
0243 {
0244     if (raDEC == GUIDE_RA)
0245         return "RA";
0246     else if (raDEC == GUIDE_DEC)
0247         return "DEC";
0248     else
0249         return "???";
0250 }
0251 
0252 const QString directionStr(GuideDirection dir)
0253 {
0254     switch (dir)
0255     {
0256         case RA_DEC_DIR:
0257             return "Decrease RA";
0258         case RA_INC_DIR:
0259             return "Increase RA";
0260         case DEC_DEC_DIR:
0261             return "Decrease DEC";
0262         case DEC_INC_DIR:
0263             return "Increase DEC";
0264         default:
0265             return "NO DIR";
0266     }
0267 }
0268 }  // namespace
0269 
0270 bool cgmath::configureInParams(Ekos::GuideState state)
0271 {
0272     const bool dithering = state == Ekos::GuideState::GUIDE_DITHERING;
0273 
0274     if (!dithering)
0275     {
0276         in_params.proportional_gain[0] = Options::rAProportionalGain();
0277         in_params.proportional_gain[1] = Options::dECProportionalGain();
0278 
0279         in_params.integral_gain[0] = Options::rAIntegralGain();
0280         in_params.integral_gain[1] = Options::dECIntegralGain();
0281 
0282         // Always pulse if we're dithering.
0283         in_params.enabled[0] = Options::rAGuideEnabled();
0284         in_params.enabled[1] = Options::dECGuideEnabled();
0285 
0286         in_params.min_pulse_arcsec[0] = Options::rAMinimumPulseArcSec();
0287         in_params.min_pulse_arcsec[1] = Options::dECMinimumPulseArcSec();
0288 
0289         in_params.max_pulse_arcsec[0] = Options::rAMaximumPulseArcSec();
0290         in_params.max_pulse_arcsec[1] = Options::dECMaximumPulseArcSec();
0291 
0292         // RA W/E enable (but always pulse if dithering).
0293         // East RA+ enabled?
0294         in_params.enabled_axis1[0] = Options::eastRAGuideEnabled();
0295         // West RA- enabled?
0296         in_params.enabled_axis2[0] = Options::westRAGuideEnabled();
0297 
0298         // DEC N/S enable (but always pulse if dithering).
0299         // North DEC+ enabled?
0300         in_params.enabled_axis1[1] = Options::northDECGuideEnabled();
0301         // South DEC- enabled?
0302         in_params.enabled_axis2[1] = Options::southDECGuideEnabled();
0303     }
0304     else
0305     {
0306         // If we're dithering, enable all axes and use full pulses.
0307         in_params.proportional_gain[0] = 1.0;
0308         in_params.proportional_gain[1] = 1.0;
0309         in_params.integral_gain[0] = 0.0;
0310         in_params.integral_gain[1] = 0.0;
0311         in_params.min_pulse_arcsec[0] = 0.0;
0312         in_params.min_pulse_arcsec[1] = 0.0;
0313         in_params.max_pulse_arcsec[0] = Options::rAMaximumPulseArcSec();
0314         in_params.max_pulse_arcsec[1] = Options::dECMaximumPulseArcSec();
0315         in_params.enabled[0] = true;
0316         in_params.enabled[1] = true;
0317         in_params.enabled_axis1[0] = true;
0318         in_params.enabled_axis2[0] = true;
0319         in_params.enabled_axis1[1] = true;
0320         in_params.enabled_axis2[1] = true;
0321     }
0322 
0323     return dithering;
0324 }
0325 
0326 void cgmath::updateOutParams(int k, const double arcsecDrift, int pulseLength, GuideDirection pulseDirection)
0327 {
0328     out_params.pulse_dir[k]  = pulseDirection;
0329     out_params.pulse_length[k] = pulseLength;
0330     out_params.delta[k] = arcsecDrift;
0331 }
0332 
0333 void cgmath::outputGuideLog()
0334 {
0335     if (Options::guideLogging())
0336     {
0337         QTextStream out(&logFile);
0338         out << iterationCounter << "," << logTime.elapsed() << "," << out_params.delta[0] << "," << out_params.pulse_length[0] <<
0339             ","
0340             << directionStr(out_params.pulse_dir[0]) << "," << out_params.delta[1] << ","
0341             << out_params.pulse_length[1] << "," << directionStr(out_params.pulse_dir[1]) << Qt::endl;
0342     }
0343 }
0344 
0345 void cgmath::processAxis(const int k, const bool dithering, const bool darkGuide, const Seconds &timeStep,
0346                          const QString &label)
0347 {
0348     // zero all out commands
0349     GuideDirection pulseDirection = NO_DIR;
0350     int pulseLength = 0;  // milliseconds
0351     GuideDirection dir;
0352 
0353     // Get the drift for this axis
0354     const int idx = driftUpto[k];
0355     const double arcsecDrift = drift[k][idx];
0356 
0357     const double pulseConverter = (k == GUIDE_RA) ?
0358                                   calibration.raPulseMillisecondsPerArcsecond() :
0359                                   calibration.decPulseMillisecondsPerArcsecond();
0360     const double maxPulseMilliseconds = in_params.max_pulse_arcsec[k] * pulseConverter;
0361 
0362     // GPG pulse computation
0363     bool useGPG = !dithering && Options::gPGEnabled() && (k == GUIDE_RA) && in_params.enabled[k];
0364     if (useGPG && darkGuide)
0365     {
0366         gpg->darkGuiding(&pulseLength, &dir, calibration, timeStep);
0367         pulseDirection = dir;
0368     }
0369     else if (darkGuide)
0370     {
0371         // We should not be dark guiding without GPG
0372         qCDebug(KSTARS_EKOS_GUIDE) << "Warning: dark guiding without GPG or while dithering.";
0373         return;
0374     }
0375     else if (useGPG && gpg->computePulse(arcsecDrift,
0376                                          usingSEPMultiStar() ? &guideStars : nullptr, &pulseLength, &dir, calibration, timeStep))
0377     {
0378         pulseDirection = dir;
0379         pulseLength = std::min(pulseLength, static_cast<int>(maxPulseMilliseconds + 0.5));
0380     }
0381     else
0382     {
0383         // This is the main non-GPG guide-pulse computation.
0384         // Traditionally it was hardwired so that proportional_gain=133 was about a control gain of 1.0
0385         // This is now in the 0.0 - 1.0 range, and multiplies the calibrated mount performance.
0386 
0387         // Compute the average drift in the recent past for the integral control term.
0388         drift_integral[k] = 0;
0389         for (int i = 0; i < CIRCULAR_BUFFER_SIZE; ++i)
0390             drift_integral[k] += drift[k][i];
0391         drift_integral[k] /= (double)CIRCULAR_BUFFER_SIZE;
0392 
0393         if (in_params.integral_gain[k] > 0)
0394             qCDebug(KSTARS_EKOS_GUIDE) << label << "drift[" << axisStr(k) << "] = " << arcsecDrift
0395                                        << " integral[" << axisStr(k) << "] = " << drift_integral[k];
0396 
0397         const double arcsecPerMsPulse = k == GUIDE_RA ? calibration.raPulseMillisecondsPerArcsecond() :
0398                                         calibration.decPulseMillisecondsPerArcsecond();
0399         const double proportionalResponse = arcsecDrift * in_params.proportional_gain[k] * arcsecPerMsPulse;
0400         const double integralResponse = drift_integral[k] * in_params.integral_gain[k] * arcsecPerMsPulse;
0401         pulseLength = std::min(fabs(proportionalResponse + integralResponse), maxPulseMilliseconds);
0402 
0403         // calc direction
0404         // We do not send pulse if direction is disabled completely, or if direction in a specific axis (e.g. N or S) is disabled
0405         if (!in_params.enabled[k] || // This axis not enabled
0406                 // Positive direction of this axis not enabled.
0407                 (arcsecDrift > 0 && !in_params.enabled_axis1[k]) ||
0408                 // Negative direction of this axis not enabled.
0409                 (arcsecDrift < 0 && !in_params.enabled_axis2[k]))
0410         {
0411             pulseDirection = NO_DIR;
0412             pulseLength = 0;
0413         }
0414         else
0415         {
0416             // Check the min pulse value, and assign the direction.
0417             const double pulseArcSec = pulseConverter > 0 ? pulseLength / pulseConverter : 0;
0418             if (pulseArcSec >= in_params.min_pulse_arcsec[k])
0419             {
0420                 if (k == GUIDE_RA)
0421                     pulseDirection = arcsecDrift > 0 ? RA_DEC_DIR : RA_INC_DIR;
0422                 else
0423                     pulseDirection = arcsecDrift > 0 ? DEC_INC_DIR : DEC_DEC_DIR; // GUIDE_DEC.
0424             }
0425             else
0426                 pulseDirection = NO_DIR;
0427         }
0428 
0429     }
0430     updateOutParams(k, arcsecDrift, pulseLength, pulseDirection);
0431 }
0432 
0433 void cgmath::calculatePulses(Ekos::GuideState state, const std::pair<Seconds, Seconds> &timeStep)
0434 {
0435     const bool dithering = configureInParams(state);
0436 
0437     processAxis(GUIDE_RA, dithering, false, timeStep.first, "Guiding:");
0438     processAxis(GUIDE_DEC, dithering, false, timeStep.second, "Guiding:");
0439 
0440     qCDebug(KSTARS_EKOS_GUIDE)
0441             << QString("Guiding pulses: RA: %1ms %2  DEC: %3ms %4")
0442             .arg(out_params.pulse_length[GUIDE_RA]).arg(directionStr(out_params.pulse_dir[GUIDE_RA]))
0443             .arg(out_params.pulse_length[GUIDE_DEC]).arg(directionStr(out_params.pulse_dir[GUIDE_DEC]));
0444 
0445     outputGuideLog();
0446 }
0447 
0448 void cgmath::performProcessing(Ekos::GuideState state, QSharedPointer<FITSData> &imageData,
0449                                QSharedPointer<GuideView> &guideView,
0450                                const std::pair<Seconds, Seconds> &timeStep, GuideLog * logger)
0451 {
0452     if (suspended)
0453     {
0454         if (Options::gPGEnabled())
0455         {
0456             GuiderUtils::Vector guideStarPosition = findLocalStarPosition(imageData, guideView, false);
0457             if (guideStarPosition.x != -1 && !std::isnan(guideStarPosition.x))
0458             {
0459                 gpg->suspended(guideStarPosition, targetPosition,
0460                                usingSEPMultiStar() ? &guideStars : nullptr, calibration);
0461             }
0462         }
0463         // do nothing if suspended
0464         return;
0465     }
0466 
0467     GuiderUtils::Vector starPositionArcSec, targetPositionArcSec;
0468 
0469     // find guiding star location in the image
0470     starPosition = findLocalStarPosition(imageData, guideView, false);
0471 
0472     // If no star found, mark as lost star.
0473     if (starPosition.x == -1 || std::isnan(starPosition.x))
0474     {
0475         setLostStar(true);
0476         if (logger != nullptr && state == Ekos::GUIDE_GUIDING)
0477         {
0478             GuideLog::GuideData data;
0479             data.code = GuideLog::GuideData::NO_STAR_FOUND;
0480             data.type = GuideLog::GuideData::DROP;
0481             logger->addGuideData(data);
0482         }
0483         return;
0484     }
0485     else
0486         setLostStar(false);
0487 
0488     // Emit the detected star center
0489     QVector3D starCenter(starPosition.x, starPosition.y, 0);
0490     emit newStarPosition(starCenter, true);
0491 
0492     // If we're only calibrating, then we're done.
0493     if (state == Ekos::GUIDE_CALIBRATING)
0494         return;
0495 
0496     if (state == Ekos::GUIDE_GUIDING && (targetPosition.x <= 0.0 || targetPosition.y <= 0.0))
0497     {
0498         qCDebug(KSTARS_EKOS_GUIDE) << "Guiding with target 0.0 -- something's wrong!!!!!!!!!!!";
0499         for (int k = GUIDE_RA; k <= GUIDE_DEC; k++)
0500         {
0501             out_params.pulse_dir[k]  = NO_DIR;
0502             out_params.pulse_length[k] = 0;
0503             out_params.delta[k] = 0;
0504             setLostStar(true);
0505         }
0506         return;
0507     }
0508     // translate star coords into sky coord. system
0509 
0510     // convert from pixels into arcsecs
0511     starPositionArcSec    = calibration.convertToArcseconds(starPosition);
0512     targetPositionArcSec = calibration.convertToArcseconds(targetPosition);
0513 
0514     // Compute RA & DEC drift in arcseconds.
0515     const GuiderUtils::Vector star_xy_arcsec_drift = starPositionArcSec - targetPositionArcSec;
0516     const GuiderUtils::Vector star_drift = calibration.rotateToRaDec(star_xy_arcsec_drift);
0517 
0518     // both coords are ready for math processing
0519     // put coord to drift list
0520     // Note: if we're not guiding, these will be overwritten,
0521     // as driftUpto is only incremented when guiding.
0522     drift[GUIDE_RA][driftUpto[GUIDE_RA]]   = star_drift.x;
0523     drift[GUIDE_DEC][driftUpto[GUIDE_DEC]] = star_drift.y;
0524 
0525     qCDebug(KSTARS_EKOS_GUIDE)
0526             << QString("Star %1 %2 a-s %3 %4 Target %5 %6 a-s %7 %8 Drift: RA %9 DEC %10")
0527             .arg(starPosition.x, 0, 'f', 1)        .arg(starPosition.y, 0, 'f', 1)
0528             .arg(starPositionArcSec.x, 0, 'f', 1)  .arg(starPositionArcSec.y, 0, 'f', 1)
0529             .arg(targetPosition.x, 0, 'f', 1)      .arg(targetPosition.y, 0, 'f', 1)
0530             .arg(targetPositionArcSec.x, 0, 'f', 1).arg(targetPositionArcSec.y, 0, 'f', 1)
0531             .arg(star_drift.x, 0, 'f', 2)          .arg(star_drift.y, 0, 'f', 2);
0532 
0533     if (state == Ekos::GUIDE_GUIDING && usingSEPMultiStar())
0534     {
0535         double multiStarRADrift, multiStarDECDrift;
0536         if (guideStars.getDrift(sqrt(star_drift.x * star_drift.x + star_drift.y * star_drift.y),
0537                                 targetPosition.x, targetPosition.y,
0538                                 &multiStarRADrift, &multiStarDECDrift))
0539         {
0540             qCDebug(KSTARS_EKOS_GUIDE) << QString("MultiStar drift: RA %1 DEC %2")
0541                                        .arg(multiStarRADrift, 0, 'f', 2)
0542                                        .arg(multiStarDECDrift, 0, 'f', 2);
0543             drift[GUIDE_RA][driftUpto[GUIDE_RA]]   = multiStarRADrift;
0544             drift[GUIDE_DEC][driftUpto[GUIDE_DEC]] = multiStarDECDrift;
0545         }
0546         else
0547         {
0548             qCDebug(KSTARS_EKOS_GUIDE) << "MultiStar: failed, fell back to guide star";
0549         }
0550     }
0551 
0552     // driftUpto will change when the circular buffer is updated,
0553     // so save the values for logging.
0554     const double raDrift = drift[GUIDE_RA][driftUpto[GUIDE_RA]];
0555     const double decDrift = drift[GUIDE_DEC][driftUpto[GUIDE_DEC]];
0556 
0557     // make decision by axes
0558     calculatePulses(state, timeStep);
0559 
0560     if (state == Ekos::GUIDE_GUIDING)
0561     {
0562         calculateRmsError();
0563         emitStats();
0564         updateCircularBuffers();
0565     }
0566 
0567     if (logger != nullptr)
0568     {
0569         GuideLog::GuideData data;
0570         data.type = GuideLog::GuideData::MOUNT;
0571         // These are distances in pixels.
0572         // Note--these don't include the multistar algorithm, but the below ra/dec ones do.
0573         data.dx = starPosition.x - targetPosition.x;
0574         data.dy = starPosition.y - targetPosition.y;
0575         // Above computes position - reticle. Should the reticle-position, so negate.
0576         calibration.convertToPixels(-raDrift, -decDrift, &data.raDistance, &data.decDistance);
0577 
0578         const double raGuideFactor = out_params.pulse_dir[GUIDE_RA] == NO_DIR ?
0579                                      0 : (out_params.pulse_dir[GUIDE_RA] == RA_DEC_DIR ? -1.0 : 1.0);
0580         const double decGuideFactor = out_params.pulse_dir[GUIDE_DEC] == NO_DIR ?
0581                                       0 : (out_params.pulse_dir[GUIDE_DEC] == DEC_INC_DIR ? -1.0 : 1.0);
0582 
0583         // Phd2LogViewer wants these in pixels instead of arcseconds, so normalizing them, but
0584         // that will be wrong for non-square pixels. They should really accept arcsecond units.
0585         data.raGuideDistance = calibration.xPixelsPerArcsecond() * raGuideFactor * out_params.pulse_length[GUIDE_RA] /
0586                                calibration.raPulseMillisecondsPerArcsecond();
0587         data.decGuideDistance = calibration.yPixelsPerArcsecond() * decGuideFactor * out_params.pulse_length[GUIDE_DEC] /
0588                                 calibration.decPulseMillisecondsPerArcsecond();
0589 
0590         data.raDuration = out_params.pulse_dir[GUIDE_RA] == NO_DIR ? 0 : out_params.pulse_length[GUIDE_RA];
0591         data.raDirection = out_params.pulse_dir[GUIDE_RA];
0592         data.decDuration = out_params.pulse_dir[GUIDE_DEC] == NO_DIR ? 0 : out_params.pulse_length[GUIDE_DEC];
0593         data.decDirection = out_params.pulse_dir[GUIDE_DEC];
0594         data.code = GuideLog::GuideData::NO_ERRORS;
0595         data.snr = guideStars.getGuideStarSNR();
0596         data.mass = guideStars.getGuideStarMass();
0597         // Add SNR and MASS from SEP stars.
0598         logger->addGuideData(data);
0599     }
0600 }
0601 
0602 void cgmath::performDarkGuiding(Ekos::GuideState state, const std::pair<Seconds, Seconds> &timeStep)
0603 {
0604 
0605     const bool dithering = configureInParams(state);
0606     //out_params.sigma[GUIDE_RA] = 0;
0607 
0608     processAxis(GUIDE_RA, dithering, true, timeStep.first, "Dark Guiding:");
0609     qCDebug(KSTARS_EKOS_GUIDE)
0610             << QString("Dark Guiding pulses: RA: %1ms %2")
0611             .arg(out_params.pulse_length[GUIDE_RA]).arg(directionStr(out_params.pulse_dir[GUIDE_RA]));
0612 
0613 
0614     // Don't guide in DEC when dark guiding
0615     updateOutParams(GUIDE_DEC, 0, 0, NO_DIR);
0616 
0617     outputGuideLog();
0618 }
0619 
0620 void cgmath::emitStats()
0621 {
0622     double pulseRA = 0;
0623     if (out_params.pulse_dir[GUIDE_RA] == RA_DEC_DIR)
0624         pulseRA = out_params.pulse_length[GUIDE_RA];
0625     else if (out_params.pulse_dir[GUIDE_RA] == RA_INC_DIR)
0626         pulseRA = -out_params.pulse_length[GUIDE_RA];
0627     double pulseDEC = 0;
0628     if (out_params.pulse_dir[GUIDE_DEC] == DEC_DEC_DIR)
0629         pulseDEC = -out_params.pulse_length[GUIDE_DEC];
0630     else if (out_params.pulse_dir[GUIDE_DEC] == DEC_INC_DIR)
0631         pulseDEC = out_params.pulse_length[GUIDE_DEC];
0632 
0633     const bool hasGuidestars = usingSEPMultiStar();
0634     const double snr = hasGuidestars ? guideStars.getGuideStarSNR() : 0;
0635     const double skyBG = hasGuidestars ? guideStars.skybackground().mean : 0;
0636     const int numStars = hasGuidestars ? guideStars.skybackground().starsDetected : 0;  // wait for rob's release
0637 
0638     emit guideStats(-out_params.delta[GUIDE_RA], -out_params.delta[GUIDE_DEC],
0639                     pulseRA, pulseDEC, snr, skyBG, numStars);
0640 }
0641 
0642 void cgmath::calculateRmsError(void)
0643 {
0644     if (!do_statistics)
0645         return;
0646 
0647     if (iterationCounter == 0)
0648         return;
0649 
0650     int count = std::min(iterationCounter, static_cast<unsigned int>(CIRCULAR_BUFFER_SIZE));
0651     for (int k = GUIDE_RA; k <= GUIDE_DEC; k++)
0652     {
0653         double sqr_avg = 0;
0654         for (int i = 0; i < count; ++i)
0655             sqr_avg += drift[k][i] * drift[k][i];
0656 
0657         out_params.sigma[k] = sqrt(sqr_avg / (double)count);
0658     }
0659 }
0660 
0661 
0662 QVector3D cgmath::selectGuideStar(const QSharedPointer<FITSData> &imageData)
0663 {
0664     return guideStars.selectGuideStar(imageData);
0665 }
0666 
0667 double cgmath::getGuideStarSNR()
0668 {
0669     return guideStars.getGuideStarSNR();
0670 }
0671 
0672 //---------------------------------------------------------------------------------------
0673 cproc_in_params::cproc_in_params()
0674 {
0675     reset();
0676 }
0677 
0678 void cproc_in_params::reset(void)
0679 {
0680     average           = true;
0681 
0682     for (int k = GUIDE_RA; k <= GUIDE_DEC; k++)
0683     {
0684         enabled[k]          = true;
0685         integral_gain[k]    = 0;
0686         max_pulse_arcsec[k] = 5000;
0687         min_pulse_arcsec[k] = 0;
0688     }
0689 }
0690 
0691 cproc_out_params::cproc_out_params()
0692 {
0693     reset();
0694 }
0695 
0696 void cproc_out_params::reset(void)
0697 {
0698     for (int k = GUIDE_RA; k <= GUIDE_DEC; k++)
0699     {
0700         delta[k]        = 0;
0701         pulse_dir[k]    = NO_DIR;
0702         pulse_length[k] = 0;
0703         sigma[k]        = 0;
0704     }
0705 }
0706