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 ¤tDec) 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 }