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

0001 /*
0002     SPDX-FileCopyrightText: 2020 Hy Murveit <hy@murveit.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "calibration.h"
0008 #include "Options.h"
0009 #include "ekos_guide_debug.h"
0010 #include <QStringRef>
0011 #include <QDateTime>
0012 #include "indi/indimount.h"
0013 
0014 Calibration::Calibration()
0015 {
0016     ROT_Z = GuiderUtils::Matrix(0);
0017 }
0018 
0019 void Calibration::setAngle(double rotationAngle)
0020 {
0021     angle = rotationAngle;
0022     ROT_Z = GuiderUtils::RotateZ(-M_PI * angle / 180.0);
0023 }
0024 
0025 void Calibration::setParameters(double ccd_pix_width, double ccd_pix_height,
0026                                 double focalLengthMm,
0027                                 int binX, int binY,
0028                                 ISD::Mount::PierSide currentPierSide,
0029                                 const dms &mountRa, const dms &mountDec)
0030 {
0031     focalMm             = focalLengthMm;
0032     ccd_pixel_width     = ccd_pix_width;
0033     ccd_pixel_height    = ccd_pix_height;
0034     calibrationRA       = mountRa;
0035     calibrationDEC      = mountDec;
0036     subBinX             = binX;
0037     subBinY             = binY;
0038     subBinXused         = subBinX;
0039     subBinYused         = subBinY;
0040     calibrationPierSide = currentPierSide;
0041 }
0042 
0043 void Calibration::setBinningUsed(int x, int y)
0044 {
0045     subBinXused         = x;
0046     subBinYused         = y;
0047 }
0048 
0049 void Calibration::setRaPulseMsPerArcsecond(double rate)
0050 {
0051     raPulseMsPerArcsecond = rate;
0052 }
0053 
0054 void Calibration::setDecPulseMsPerArcsecond(double rate)
0055 {
0056     decPulseMsPerArcsecond = rate;
0057 }
0058 
0059 GuiderUtils::Vector Calibration::convertToArcseconds(const GuiderUtils::Vector &input) const
0060 {
0061     GuiderUtils::Vector arcseconds;
0062     arcseconds.x = input.x * xArcsecondsPerPixel();
0063     arcseconds.y = input.y * yArcsecondsPerPixel();
0064     return arcseconds;
0065 }
0066 
0067 GuiderUtils::Vector Calibration::convertToPixels(const GuiderUtils::Vector &input) const
0068 {
0069     GuiderUtils::Vector arcseconds;
0070     arcseconds.x = input.x / xArcsecondsPerPixel();
0071     arcseconds.y = input.y / yArcsecondsPerPixel();
0072     return arcseconds;
0073 }
0074 
0075 void Calibration::convertToPixels(double xArcseconds, double yArcseconds,
0076                                   double *xPixel, double *yPixel) const
0077 {
0078     *xPixel = xArcseconds / xArcsecondsPerPixel();
0079     *yPixel = yArcseconds / yArcsecondsPerPixel();
0080 }
0081 
0082 GuiderUtils::Vector Calibration::rotateToRaDec(const GuiderUtils::Vector &input) const
0083 {
0084     GuiderUtils::Vector in;
0085     in.x = input.x;
0086     in.y = -input.y;
0087     return (in * ROT_Z);
0088 }
0089 
0090 void Calibration::rotateToRaDec(double dx, double dy,
0091                                 double *ra, double *dec) const
0092 {
0093     GuiderUtils::Vector input;
0094     input.x = dx;
0095     input.y = dy;
0096     GuiderUtils::Vector out = rotateToRaDec(input);
0097     *ra = out.x;
0098     *dec = out.y;
0099 }
0100 
0101 double Calibration::binFactor() const
0102 {
0103     return static_cast<double>(subBinXused) / static_cast<double>(subBinX);
0104 }
0105 
0106 double Calibration::inverseBinFactor() const
0107 {
0108     return 1.0 / binFactor();
0109 }
0110 
0111 double Calibration::xArcsecondsPerPixel() const
0112 {
0113     // arcs = 3600*180/pi * (pix*ccd_pix_sz) / focal_len
0114     return binFactor() * (206264.806 * ccd_pixel_width * subBinX) / focalMm;
0115 }
0116 
0117 double Calibration::yArcsecondsPerPixel() const
0118 {
0119     return binFactor() * (206264.806 * ccd_pixel_height * subBinY) / focalMm;
0120 }
0121 
0122 double Calibration::xPixelsPerArcsecond() const
0123 {
0124     return inverseBinFactor() * (focalMm / (206264.806 * ccd_pixel_width * subBinX));
0125 }
0126 
0127 double Calibration::yPixelsPerArcsecond() const
0128 {
0129     return inverseBinFactor() * (focalMm / (206264.806 * ccd_pixel_height * subBinY));
0130 }
0131 
0132 double Calibration::raPulseMillisecondsPerArcsecond() const
0133 {
0134     return raPulseMsPerArcsecond;
0135 }
0136 
0137 double Calibration::decPulseMillisecondsPerArcsecond() const
0138 {
0139     return decPulseMsPerArcsecond;
0140 }
0141 
0142 double Calibration::calculateRotation(double x, double y)
0143 {
0144     double phi;
0145 
0146     y = -y;
0147 
0148     //if( (!GuiderUtils::Vector(delta_x, delta_y, 0)) < 2.5 )
0149     // JM 2015-12-10: Lower threshold to 1 pixel
0150     if ((!GuiderUtils::Vector(x, y, 0)) < 1)
0151         return -1;
0152 
0153     // 90 or 270 degrees
0154     if (fabs(x) < fabs(y) / 1000000.0)
0155     {
0156         phi = y > 0 ? 90.0 : 270;
0157     }
0158     else
0159     {
0160         phi = 180.0 / M_PI * atan2(y, x);
0161         if (phi < 0)
0162             phi += 360.0;
0163     }
0164 
0165     return phi;
0166 }
0167 
0168 bool Calibration::calculate1D(double start_x, double start_y,
0169                               double end_x, double end_y, int RATotalPulse)
0170 {
0171     return calculate1D(end_x - start_x, end_y - start_y, RATotalPulse);
0172 }
0173 
0174 bool Calibration::calculate1D(double dx, double dy, int RATotalPulse)
0175 {
0176     const double arcSecondsX = dx * xArcsecondsPerPixel();
0177     const double arcSecondsY = dy * yArcsecondsPerPixel();
0178     const double arcSeconds = std::hypot(arcSecondsX, arcSecondsY);
0179     if (arcSeconds < .1 || RATotalPulse <= 0)
0180     {
0181         qCDebug(KSTARS_EKOS_GUIDE)
0182                 << QString("Bad input to calculate1D: ra %1 %2 total pulse %3")
0183                 .arg(dx).arg(dy).arg(RATotalPulse);
0184         return false;
0185     }
0186 
0187     const double phi = calculateRotation(arcSecondsX, arcSecondsY);
0188     if (phi < 0)
0189         return false;
0190 
0191     setAngle(phi);
0192     calibrationAngle = phi;
0193     calibrationAngleRA = phi;
0194     calibrationAngleDEC = -1;
0195     decSwap = calibrationDecSwap = false;
0196 
0197     if (RATotalPulse > 0)
0198         setRaPulseMsPerArcsecond(RATotalPulse / arcSeconds);
0199 
0200     if (raPulseMillisecondsPerArcsecond() > 10000)
0201     {
0202         qCDebug(KSTARS_EKOS_GUIDE)
0203                 << "Calibration computed unreasonable pulse-milliseconds-per-arcsecond: "
0204                 << raPulseMillisecondsPerArcsecond() << " & " << decPulseMillisecondsPerArcsecond();
0205     }
0206 
0207     initialized = true;
0208     return true;
0209 }
0210 
0211 bool Calibration::calculate2D(
0212     double start_ra_x, double start_ra_y, double end_ra_x, double end_ra_y,
0213     double start_dec_x, double start_dec_y, double end_dec_x, double end_dec_y,
0214     bool *swap_dec, int RATotalPulse, int DETotalPulse)
0215 {
0216     return calculate2D((end_ra_x - start_ra_x),
0217                        (end_ra_y - start_ra_y),
0218                        (end_dec_x - start_dec_x),
0219                        (end_dec_y - start_dec_y),
0220                        swap_dec, RATotalPulse, DETotalPulse);
0221 }
0222 bool Calibration::calculate2D(
0223     double ra_dx, double ra_dy, double dec_dx, double dec_dy,
0224     bool *swap_dec, int RATotalPulse, int DETotalPulse)
0225 {
0226     const double raArcsecondsX = ra_dx * xArcsecondsPerPixel();
0227     const double raArcsecondsY = ra_dy * yArcsecondsPerPixel();
0228     const double decArcsecondsX = dec_dx * xArcsecondsPerPixel();
0229     const double decArcsecondsY = dec_dy * yArcsecondsPerPixel();
0230     const double raArcseconds = std::hypot(raArcsecondsX, raArcsecondsY);
0231     const double decArcseconds = std::hypot(decArcsecondsX, decArcsecondsY);
0232     if (raArcseconds < .1 || decArcseconds < .1 || RATotalPulse <= 0 || DETotalPulse <= 0)
0233     {
0234         qCDebug(KSTARS_EKOS_GUIDE)
0235                 << QString("Bad input to calculate2D: ra %1 %2 dec %3 %4 total pulses %5 %6")
0236                 .arg(ra_dx).arg(ra_dy).arg(dec_dx).arg(dec_dy).arg(RATotalPulse).arg(DETotalPulse);
0237         return false;
0238     }
0239 
0240     double phi_ra  = 0; // angle calculated by GUIDE_RA drift
0241     double phi_dec = 0; // angle calculated by GUIDE_DEC drift
0242     double phi     = 0;
0243 
0244     GuiderUtils::Vector ra_vect  = GuiderUtils::Normalize(GuiderUtils::Vector(raArcsecondsX, -raArcsecondsY, 0));
0245     GuiderUtils::Vector dec_vect = GuiderUtils::Normalize(GuiderUtils::Vector(decArcsecondsX, -decArcsecondsY, 0));
0246 
0247     GuiderUtils::Vector try_increase = dec_vect * GuiderUtils::RotateZ(M_PI / 2);
0248     GuiderUtils::Vector try_decrease = dec_vect * GuiderUtils::RotateZ(-M_PI / 2);
0249 
0250     double cos_increase = try_increase & ra_vect;
0251     double cos_decrease = try_decrease & ra_vect;
0252 
0253     bool do_increase = cos_increase > cos_decrease ? true : false;
0254 
0255     phi_ra = calculateRotation(raArcsecondsX, raArcsecondsY);
0256     if (phi_ra < 0)
0257         return false;
0258 
0259     phi_dec = calculateRotation(decArcsecondsX, decArcsecondsY);
0260     if (phi_dec < 0)
0261         return false;
0262 
0263     // Store the calibration angles.
0264     calibrationAngleRA = phi_ra;
0265     calibrationAngleDEC = phi_dec;
0266 
0267     if (do_increase)
0268         phi_dec += 90;
0269     else
0270         phi_dec -= 90;
0271 
0272     if (phi_dec > 360)
0273         phi_dec -= 360.0;
0274     if (phi_dec < 0)
0275         phi_dec += 360.0;
0276 
0277     if (fabs(phi_dec - phi_ra) > 180)
0278     {
0279         if (phi_ra > phi_dec)
0280             phi_ra -= 360;
0281         else
0282             phi_dec -= 360;
0283     }
0284 
0285     // average angles
0286     phi = (phi_ra + phi_dec) / 2;
0287     if (phi < 0)
0288         phi += 360.0;
0289 
0290     // setAngle sets the angle we'll use when guiding.
0291     // calibrationAngle is the saved angle to be stored.
0292     // They're the same now, but if we flip pier sides, angle may change.
0293     setAngle(phi);
0294     calibrationAngle = phi;
0295 
0296     // check DEC
0297     if (swap_dec)
0298         *swap_dec = decSwap = do_increase ? false : true;
0299     calibrationDecSwap = decSwap;
0300 
0301     if (RATotalPulse > 0)
0302         setRaPulseMsPerArcsecond(RATotalPulse / raArcseconds);
0303 
0304     if (DETotalPulse > 0)
0305         setDecPulseMsPerArcsecond(DETotalPulse / decArcseconds);
0306 
0307     // Check for unreasonable values.
0308     if (raPulseMillisecondsPerArcsecond() > 10000 || decPulseMillisecondsPerArcsecond() > 10000)
0309     {
0310         qCDebug(KSTARS_EKOS_GUIDE)
0311                 << "Calibration computed unreasonable pulse-milliseconds-per-arcsecond: "
0312                 << raPulseMillisecondsPerArcsecond() << " & " << decPulseMillisecondsPerArcsecond();
0313         return false;
0314     }
0315 
0316     qCDebug(KSTARS_EKOS_GUIDE) << QString("Set RA ms/as = %1ms / %2as = %3. DEC: %4ms / %5px = %6.")
0317                                .arg(RATotalPulse).arg(raArcseconds).arg(raPulseMillisecondsPerArcsecond())
0318                                .arg(DETotalPulse).arg(decArcseconds).arg(decPulseMillisecondsPerArcsecond());
0319     initialized = true;
0320     return true;
0321 }
0322 
0323 void Calibration::computeDrift(const GuiderUtils::Vector &detection, const GuiderUtils::Vector &reference,
0324                                double *raDrift, double *decDrift) const
0325 {
0326     GuiderUtils::Vector drift = detection - reference;
0327     drift = rotateToRaDec(drift);
0328     *raDrift   = drift.x;
0329     *decDrift = drift.y;
0330 }
0331 
0332 // Not sure why we allow this.
0333 void Calibration::setDeclinationSwapEnabled(bool value)
0334 {
0335     qCDebug(KSTARS_EKOS_GUIDE) << QString("decSwap set to %1, was %2, cal %3")
0336                                .arg(value ? "T" : "F")
0337                                .arg(decSwap ? "T" : "F")
0338                                .arg(calibrationDecSwap ? "T" : "F");
0339     decSwap = value;
0340 }
0341 QString Calibration::serialize() const
0342 {
0343     QDateTime now = QDateTime::currentDateTime();
0344     QString dateStr = now.toString("yyyy-MM-dd hh:mm:ss");
0345     QString raStr = std::isnan(calibrationRA.Degrees()) ? "" : calibrationRA.toDMSString(false, true, true);
0346     QString decStr = std::isnan(calibrationDEC.Degrees()) ? "" : calibrationDEC.toHMSString(true, true);
0347     QString s =
0348         QString("Cal v1.0,bx=%1,by=%2,pw=%3,ph=%4,fl=%5,ang=%6,angR=%7,angD=%8,"
0349                 "ramspas=%9,decmspas=%10,swap=%11,ra=%12,dec=%13,side=%14,when=%15,calEnd")
0350         .arg(subBinX).arg(subBinY).arg(ccd_pixel_width).arg(ccd_pixel_height)
0351         .arg(focalMm).arg(calibrationAngle).arg(calibrationAngleRA)
0352         .arg(calibrationAngleDEC).arg(raPulseMsPerArcsecond)
0353         .arg(decPulseMsPerArcsecond).arg(calibrationDecSwap ? 1 : 0)
0354         .arg(raStr).arg(decStr).arg(static_cast<int>(calibrationPierSide)).arg(dateStr);
0355     return s;
0356 }
0357 
0358 namespace
0359 {
0360 bool parseString(const QStringRef &ref, const QString &id, QString *result)
0361 {
0362     if (!ref.startsWith(id)) return false;
0363     *result = ref.mid(id.size()).toString();
0364     return true;
0365 }
0366 
0367 bool parseDouble(const QStringRef &ref, const QString &id, double *result)
0368 {
0369     bool ok;
0370     if (!ref.startsWith(id)) return false;
0371     *result = ref.mid(id.size()).toDouble(&ok);
0372     return ok;
0373 }
0374 
0375 bool parseInt(const QStringRef &ref, const QString &id, int *result)
0376 {
0377     bool ok;
0378     if (!ref.startsWith(id)) return false;
0379     *result = ref.mid(id.size()).toInt(&ok);
0380     return ok;
0381 }
0382 }  // namespace
0383 
0384 bool Calibration::restore(const QString &encoding)
0385 {
0386     QVector<QStringRef> items = encoding.splitRef(',');
0387     if (items.size() != 17) return false;
0388     int i = 0;
0389     if (items[i] != "Cal v1.0") return false;
0390     if (!parseInt(items[++i], "bx=", &subBinX)) return false;
0391     if (!parseInt(items[++i], "by=", &subBinY)) return false;
0392     if (!parseDouble(items[++i], "pw=", &ccd_pixel_width)) return false;
0393     if (!parseDouble(items[++i], "ph=", &ccd_pixel_height)) return false;
0394     if (!parseDouble(items[++i], "fl=", &focalMm)) return false;
0395     if (!parseDouble(items[++i], "ang=", &calibrationAngle)) return false;
0396     setAngle(calibrationAngle);
0397     if (!parseDouble(items[++i], "angR=", &calibrationAngleRA)) return false;
0398     if (!parseDouble(items[++i], "angD=", &calibrationAngleDEC)) return false;
0399 
0400     // Switched from storing raPulseMsPerPixel to ...PerArcsecond and similar for DEC.
0401     // The below allows back-compatibility for older stored calibrations.
0402     if (!parseDouble(items[++i], "ramspas=", &raPulseMsPerArcsecond))
0403     {
0404         // Try the old version
0405         double raPulseMsPerPixel;
0406         if (parseDouble(items[i], "ramspp=", &raPulseMsPerPixel) && (xArcsecondsPerPixel() > 0))
0407         {
0408             // The previous calibration only worked on square pixels.
0409             raPulseMsPerArcsecond = raPulseMsPerPixel / xArcsecondsPerPixel();
0410         }
0411         else return false;
0412     }
0413     if (!parseDouble(items[++i], "decmspas=", &decPulseMsPerArcsecond))
0414     {
0415         // Try the old version
0416         double decPulseMsPerPixel;
0417         if (parseDouble(items[i], "decmspp=", &decPulseMsPerPixel) && (yArcsecondsPerPixel() > 0))
0418         {
0419             // The previous calibration only worked on square pixels.
0420             decPulseMsPerArcsecond = decPulseMsPerPixel / yArcsecondsPerPixel();
0421         }
0422         else return false;
0423     }
0424 
0425     int tempInt;
0426     if (!parseInt(items[++i], "swap=", &tempInt)) return false;
0427     decSwap = static_cast<bool>(tempInt);
0428     calibrationDecSwap = decSwap;
0429     QString tempStr;
0430     if (!parseString(items[++i], "ra=", &tempStr)) return false;
0431     dms nullDms;
0432     calibrationRA = tempStr.size() == 0 ? nullDms : dms::fromString(tempStr, true);
0433     if (!parseString(items[++i], "dec=", &tempStr)) return false;
0434     calibrationDEC = tempStr.size() == 0 ? nullDms : dms::fromString(tempStr, false);
0435     if (!parseInt(items[++i], "side=", &tempInt)) return false;
0436     calibrationPierSide = static_cast<ISD::Mount::PierSide>(tempInt);
0437     if (!parseString(items[++i], "when=", &tempStr)) return false;
0438     // Don't keep the time. It's just for reference.
0439     if (items[++i] != "calEnd") return false;
0440     initialized = true;
0441     return true;
0442 }
0443 
0444 void Calibration::save() const
0445 {
0446     QString encoding = serialize();
0447     Options::setSerializedCalibration(encoding);
0448     qCDebug(KSTARS_EKOS_GUIDE) << QString("Saved calibration: %1").arg(encoding);
0449 }
0450 
0451 bool Calibration::restore(ISD::Mount::PierSide currentPierSide,
0452                           bool reverseDecOnPierChange, int currentBinX, int currentBinY,
0453                           const dms *declination)
0454 {
0455     return restore(Options::serializedCalibration(), currentPierSide,
0456                    reverseDecOnPierChange, currentBinX, currentBinY, declination);
0457 }
0458 
0459 double Calibration::correctRA(double raMsPerArcsec, const dms &calibrationDec, const dms &currentDec)
0460 {
0461     constexpr double MAX_DEC = 60.0;
0462     // Don't use uninitialized dms values.
0463     if (std::isnan(calibrationDec.Degrees()) || std::isnan(currentDec.Degrees()))
0464     {
0465         qCDebug(KSTARS_EKOS_GUIDE) << QString("Did not convert calibration RA rate. Input DEC invalid");
0466         return raMsPerArcsec;
0467     }
0468     if ((calibrationDec.Degrees() > MAX_DEC) || (calibrationDec.Degrees() < -MAX_DEC))
0469     {
0470         qCDebug(KSTARS_EKOS_GUIDE) << QString("Didn't correct calibration RA rate. Calibration DEC %1 too close to pole")
0471                                    .arg(QString::number(calibrationDec.Degrees(), 'f', 1));
0472         return raMsPerArcsec;
0473     }
0474     const double toRadians = M_PI / 180.0;
0475     const double numer = std::cos(currentDec.Degrees() * toRadians);
0476     const double denom = std::cos(calibrationDec.Degrees() * toRadians);
0477     if (raMsPerArcsec == 0) return raMsPerArcsec;
0478     const double rate = 1.0 / raMsPerArcsec;
0479     if (denom == 0) return raMsPerArcsec;
0480     const double adjustedRate = numer * rate / denom;
0481     if (adjustedRate == 0) return raMsPerArcsec;
0482     const double adjustedMsPerArcsecond = 1.0 / adjustedRate;
0483     qCDebug(KSTARS_EKOS_GUIDE) << QString("Corrected calibration RA rate. %1 --> %2. Calibration DEC %3 current DEC %4.")
0484                                .arg(QString::number(raMsPerArcsec, 'f', 1))
0485                                .arg(QString::number(adjustedMsPerArcsecond, 'f', 1))
0486                                .arg(QString::number(calibrationDec.Degrees(), 'f', 1))
0487                                .arg(QString::number(currentDec.Degrees(), 'f', 1));
0488     return adjustedMsPerArcsecond;
0489 }
0490 
0491 bool Calibration::restore(const QString &encoding, ISD::Mount::PierSide currentPierSide,
0492                           bool reverseDecOnPierChange, int currentBinX, int currentBinY,
0493                           const dms *currentDeclination)
0494 {
0495     // Fail if we couldn't read the calibration.
0496     if (!restore(encoding))
0497     {
0498         qCDebug(KSTARS_EKOS_GUIDE) << "Could not restore calibration--couldn't read.";
0499         return false;
0500     }
0501     // We don't restore calibrations where either the calibration or the current pier side
0502     // is unknown.
0503     if (calibrationPierSide == ISD::Mount::PIER_UNKNOWN ||
0504             currentPierSide == ISD::Mount::PIER_UNKNOWN)
0505     {
0506         qCDebug(KSTARS_EKOS_GUIDE) << "Could not restore calibration--pier side unknown.";
0507         return false;
0508     }
0509 
0510     if (currentDeclination != nullptr)
0511         raPulseMsPerArcsecond = correctRA(raPulseMsPerArcsecond, calibrationDEC, *currentDeclination);
0512 
0513     subBinXused = currentBinX;
0514     subBinYused = currentBinY;
0515 
0516     // Succeed if the calibration was on the same side of the pier as we're currently using.
0517     if (currentPierSide == calibrationPierSide)
0518     {
0519         qCDebug(KSTARS_EKOS_GUIDE) << "Restored calibration--same pier side. Encoding:" << encoding;
0520         return true;
0521     }
0522 
0523     // Otherwise, swap the angles and succeed.
0524     angle = angle + 180.0;
0525     while (angle >= 360.0)
0526         angle = angle - 360.0;
0527     while (angle < 0.0)
0528         angle = angle + 360.0;
0529     // Although angle is already set, we do this to set the rotation matrix.
0530     setAngle(angle);
0531 
0532     // Note the negation in testing the option.
0533     // Since above the angle rotated by 180 degrees, both RA and DEC have already reversed.
0534     // If the user says not to reverse DEC, then we re-reverse by setting decSwap.
0535     // Doing it this way makes the option consistent with other programs the user may be familiar with (PHD2).
0536     if (!reverseDecOnPierChange)
0537         decSwap = !calibrationDecSwap;
0538 
0539     qCDebug(KSTARS_EKOS_GUIDE)
0540             << QString("Restored calibration--flipped angles. Angle %1, swap %2 ms/as: %3 %4. Encoding: %5")
0541             .arg(angle).arg(decSwap ? "T" : "F").arg(raPulseMsPerArcsecond).arg(decPulseMsPerArcsecond).arg(encoding);
0542     return true;
0543 }