7#include "calibration.h"
9#include "ekos_guide_debug.h"
11#include "indi/indimount.h"
13Calibration::Calibration()
15 ROT_Z = GuiderUtils::Matrix(0);
18void Calibration::setAngle(
double rotationAngle)
20 angle = rotationAngle;
21 ROT_Z = GuiderUtils::RotateZ(-M_PI * angle / 180.0);
24void Calibration::setParameters(
double ccd_pix_width,
double ccd_pix_height,
27 ISD::Mount::PierSide currentPierSide,
28 const dms &mountRa,
const dms &mountDec)
30 focalMm = focalLengthMm;
31 ccd_pixel_width = ccd_pix_width;
32 ccd_pixel_height = ccd_pix_height;
33 calibrationRA = mountRa;
34 calibrationDEC = mountDec;
37 subBinXused = subBinX;
38 subBinYused = subBinY;
39 calibrationPierSide = currentPierSide;
42void Calibration::setBinningUsed(
int x,
int y)
48void Calibration::setRaPulseMsPerArcsecond(
double rate)
50 raPulseMsPerArcsecond = rate;
53void Calibration::setDecPulseMsPerArcsecond(
double rate)
55 decPulseMsPerArcsecond = rate;
58GuiderUtils::Vector Calibration::convertToArcseconds(
const GuiderUtils::Vector &input)
const
60 GuiderUtils::Vector arcseconds;
61 arcseconds.x = input.x * xArcsecondsPerPixel();
62 arcseconds.y = input.y * yArcsecondsPerPixel();
66GuiderUtils::Vector Calibration::convertToPixels(
const GuiderUtils::Vector &input)
const
68 GuiderUtils::Vector arcseconds;
69 arcseconds.x = input.x / xArcsecondsPerPixel();
70 arcseconds.y = input.y / yArcsecondsPerPixel();
74void Calibration::convertToPixels(
double xArcseconds,
double yArcseconds,
75 double *xPixel,
double *yPixel)
const
77 *xPixel = xArcseconds / xArcsecondsPerPixel();
78 *yPixel = yArcseconds / yArcsecondsPerPixel();
81GuiderUtils::Vector Calibration::rotateToRaDec(
const GuiderUtils::Vector &input)
const
83 GuiderUtils::Vector in;
89void Calibration::rotateToRaDec(
double dx,
double dy,
90 double *ra,
double *dec)
const
92 GuiderUtils::Vector input;
95 GuiderUtils::Vector out = rotateToRaDec(input);
100double Calibration::binFactor()
const
102 return static_cast<double>(subBinXused) /
static_cast<double>(subBinX);
105double Calibration::inverseBinFactor()
const
107 return 1.0 / binFactor();
110double Calibration::xArcsecondsPerPixel()
const
113 return binFactor() * (206264.806 * ccd_pixel_width * subBinX) / focalMm;
116double Calibration::yArcsecondsPerPixel()
const
118 return binFactor() * (206264.806 * ccd_pixel_height * subBinY) / focalMm;
121double Calibration::xPixelsPerArcsecond()
const
123 return inverseBinFactor() * (focalMm / (206264.806 * ccd_pixel_width * subBinX));
126double Calibration::yPixelsPerArcsecond()
const
128 return inverseBinFactor() * (focalMm / (206264.806 * ccd_pixel_height * subBinY));
131double Calibration::raPulseMillisecondsPerArcsecond()
const
133 return raPulseMsPerArcsecond;
136double Calibration::decPulseMillisecondsPerArcsecond()
const
138 return decPulseMsPerArcsecond;
141double Calibration::calculateRotation(
double x,
double y)
149 if ((!GuiderUtils::Vector(x, y, 0)) < 1)
153 if (fabs(x) < fabs(y) / 1000000.0)
155 phi = y > 0 ? 90.0 : 270;
159 phi = 180.0 / M_PI * atan2(y, x);
167bool Calibration::calculate1D(
double start_x,
double start_y,
168 double end_x,
double end_y,
int RATotalPulse)
170 return calculate1D(end_x - start_x, end_y - start_y, RATotalPulse);
173bool Calibration::calculate1D(
double dx,
double dy,
int RATotalPulse)
175 const double arcSecondsX = dx * xArcsecondsPerPixel();
176 const double arcSecondsY = dy * yArcsecondsPerPixel();
177 const double arcSeconds = std::hypot(arcSecondsX, arcSecondsY);
178 if (arcSeconds < .1 || RATotalPulse <= 0)
180 qCDebug(KSTARS_EKOS_GUIDE)
181 <<
QString(
"Bad input to calculate1D: ra %1 %2 total pulse %3")
186 const double phi = calculateRotation(arcSecondsX, arcSecondsY);
191 calibrationAngle = phi;
192 calibrationAngleRA = phi;
193 calibrationAngleDEC = -1;
194 decSwap = calibrationDecSwap =
false;
196 if (RATotalPulse > 0)
197 setRaPulseMsPerArcsecond(RATotalPulse / arcSeconds);
199 if (raPulseMillisecondsPerArcsecond() > 10000)
201 qCDebug(KSTARS_EKOS_GUIDE)
202 <<
"Calibration computed unreasonable pulse-milliseconds-per-arcsecond: "
203 << raPulseMillisecondsPerArcsecond() <<
" & " << decPulseMillisecondsPerArcsecond();
210bool Calibration::calculate2D(
211 double start_ra_x,
double start_ra_y,
double end_ra_x,
double end_ra_y,
212 double start_dec_x,
double start_dec_y,
double end_dec_x,
double end_dec_y,
213 bool *swap_dec,
int RATotalPulse,
int DETotalPulse)
215 return calculate2D((end_ra_x - start_ra_x),
216 (end_ra_y - start_ra_y),
217 (end_dec_x - start_dec_x),
218 (end_dec_y - start_dec_y),
219 swap_dec, RATotalPulse, DETotalPulse);
221bool Calibration::calculate2D(
222 double ra_dx,
double ra_dy,
double dec_dx,
double dec_dy,
223 bool *swap_dec,
int RATotalPulse,
int DETotalPulse)
225 const double raArcsecondsX = ra_dx * xArcsecondsPerPixel();
226 const double raArcsecondsY = ra_dy * yArcsecondsPerPixel();
227 const double decArcsecondsX = dec_dx * xArcsecondsPerPixel();
228 const double decArcsecondsY = dec_dy * yArcsecondsPerPixel();
229 const double raArcseconds = std::hypot(raArcsecondsX, raArcsecondsY);
230 const double decArcseconds = std::hypot(decArcsecondsX, decArcsecondsY);
231 if (raArcseconds < .1 || decArcseconds < .1 || RATotalPulse <= 0 || DETotalPulse <= 0)
233 qCDebug(KSTARS_EKOS_GUIDE)
234 <<
QString(
"Bad input to calculate2D: ra %1 %2 dec %3 %4 total pulses %5 %6")
243 GuiderUtils::Vector ra_vect = GuiderUtils::Normalize(GuiderUtils::Vector(raArcsecondsX, -raArcsecondsY, 0));
244 GuiderUtils::Vector dec_vect = GuiderUtils::Normalize(GuiderUtils::Vector(decArcsecondsX, -decArcsecondsY, 0));
246 GuiderUtils::Vector try_increase = dec_vect * GuiderUtils::RotateZ(M_PI / 2);
247 GuiderUtils::Vector try_decrease = dec_vect * GuiderUtils::RotateZ(-M_PI / 2);
249 double cos_increase = try_increase & ra_vect;
250 double cos_decrease = try_decrease & ra_vect;
252 bool do_increase = cos_increase > cos_decrease ? true :
false;
254 phi_ra = calculateRotation(raArcsecondsX, raArcsecondsY);
258 phi_dec = calculateRotation(decArcsecondsX, decArcsecondsY);
263 calibrationAngleRA = phi_ra;
264 calibrationAngleDEC = phi_dec;
276 if (fabs(phi_dec - phi_ra) > 180)
278 if (phi_ra > phi_dec)
285 phi = (phi_ra + phi_dec) / 2;
293 calibrationAngle = phi;
297 *swap_dec = decSwap = do_increase ? false :
true;
298 calibrationDecSwap = decSwap;
300 if (RATotalPulse > 0)
301 setRaPulseMsPerArcsecond(RATotalPulse / raArcseconds);
303 if (DETotalPulse > 0)
304 setDecPulseMsPerArcsecond(DETotalPulse / decArcseconds);
307 if (raPulseMillisecondsPerArcsecond() > 10000 || decPulseMillisecondsPerArcsecond() > 10000)
309 qCDebug(KSTARS_EKOS_GUIDE)
310 <<
"Calibration computed unreasonable pulse-milliseconds-per-arcsecond: "
311 << raPulseMillisecondsPerArcsecond() <<
" & " << decPulseMillisecondsPerArcsecond();
315 qCDebug(KSTARS_EKOS_GUIDE) <<
QString(
"Set RA ms/as = %1ms / %2as = %3. DEC: %4ms / %5px = %6.")
316 .
arg(RATotalPulse).
arg(raArcseconds).
arg(raPulseMillisecondsPerArcsecond())
317 .
arg(DETotalPulse).
arg(decArcseconds).
arg(decPulseMillisecondsPerArcsecond());
322void Calibration::computeDrift(
const GuiderUtils::Vector &detection,
const GuiderUtils::Vector &reference,
323 double *raDrift,
double *decDrift)
const
325 GuiderUtils::Vector drift = detection - reference;
326 drift = rotateToRaDec(drift);
332void Calibration::setDeclinationSwapEnabled(
bool value)
334 qCDebug(KSTARS_EKOS_GUIDE) <<
QString(
"decSwap set to %1, was %2, cal %3")
335 .
arg(value ?
"T" :
"F")
336 .
arg(decSwap ?
"T" :
"F")
337 .
arg(calibrationDecSwap ?
"T" :
"F");
340QString Calibration::serialize()
const
347 QString(
"Cal v1.0,bx=%1,by=%2,pw=%3,ph=%4,fl=%5,ang=%6,angR=%7,angD=%8,"
348 "ramspas=%9,decmspas=%10,swap=%11,ra=%12,dec=%13,side=%14,when=%15,calEnd")
349 .
arg(subBinX).
arg(subBinY).
arg(ccd_pixel_width).
arg(ccd_pixel_height)
350 .
arg(focalMm).
arg(calibrationAngle).
arg(calibrationAngleRA)
351 .
arg(calibrationAngleDEC).
arg(raPulseMsPerArcsecond)
352 .
arg(decPulseMsPerArcsecond).
arg(calibrationDecSwap ? 1 : 0)
353 .
arg(raStr).
arg(decStr).
arg(
static_cast<int>(calibrationPierSide)).
arg(dateStr);
362 *result = ref.
mid(
id.size());
366bool parseDouble(
const QString &ref,
const QString &
id,
double *result)
378 *result = ref.
mid(
id.size()).
toInt(&ok);
383bool Calibration::restore(
const QString &encoding)
386 if (items.
size() != 17)
return false;
388 if (items[i] !=
"Cal v1.0")
return false;
389 if (!parseInt(items[++i],
"bx=", &subBinX))
return false;
390 if (!parseInt(items[++i],
"by=", &subBinY))
return false;
391 if (!parseDouble(items[++i],
"pw=", &ccd_pixel_width))
return false;
392 if (!parseDouble(items[++i],
"ph=", &ccd_pixel_height))
return false;
393 if (!parseDouble(items[++i],
"fl=", &focalMm))
return false;
394 if (!parseDouble(items[++i],
"ang=", &calibrationAngle))
return false;
395 setAngle(calibrationAngle);
396 if (!parseDouble(items[++i],
"angR=", &calibrationAngleRA))
return false;
397 if (!parseDouble(items[++i],
"angD=", &calibrationAngleDEC))
return false;
401 if (!parseDouble(items[++i],
"ramspas=", &raPulseMsPerArcsecond))
404 double raPulseMsPerPixel;
405 if (parseDouble(items[i],
"ramspp=", &raPulseMsPerPixel) && (xArcsecondsPerPixel() > 0))
408 raPulseMsPerArcsecond = raPulseMsPerPixel / xArcsecondsPerPixel();
412 if (!parseDouble(items[++i],
"decmspas=", &decPulseMsPerArcsecond))
415 double decPulseMsPerPixel;
416 if (parseDouble(items[i],
"decmspp=", &decPulseMsPerPixel) && (yArcsecondsPerPixel() > 0))
419 decPulseMsPerArcsecond = decPulseMsPerPixel / yArcsecondsPerPixel();
425 if (!parseInt(items[++i],
"swap=", &tempInt))
return false;
426 decSwap =
static_cast<bool>(tempInt);
427 calibrationDecSwap = decSwap;
429 if (!parseString(items[++i],
"ra=", &tempStr))
return false;
432 if (!parseString(items[++i],
"dec=", &tempStr))
return false;
434 if (!parseInt(items[++i],
"side=", &tempInt))
return false;
435 calibrationPierSide =
static_cast<ISD::Mount::PierSide
>(tempInt);
436 if (!parseString(items[++i],
"when=", &tempStr))
return false;
438 if (items[++i] !=
"calEnd")
return false;
443void Calibration::save()
const
445 QString encoding = serialize();
446 Options::setSerializedCalibration(encoding);
447 qCDebug(KSTARS_EKOS_GUIDE) <<
QString(
"Saved calibration: %1").
arg(encoding);
450bool Calibration::restore(ISD::Mount::PierSide currentPierSide,
451 bool reverseDecOnPierChange,
int currentBinX,
int currentBinY,
452 const dms *declination)
454 return restore(Options::serializedCalibration(), currentPierSide,
455 reverseDecOnPierChange, currentBinX, currentBinY, declination);
458double Calibration::correctRA(
double raMsPerArcsec,
const dms &calibrationDec,
const dms ¤tDec)
460 constexpr double MAX_DEC = 60.0;
462 if (std::isnan(calibrationDec.
Degrees()) || std::isnan(currentDec.
Degrees()))
464 qCDebug(KSTARS_EKOS_GUIDE) <<
QString(
"Did not convert calibration RA rate. Input DEC invalid");
465 return raMsPerArcsec;
467 if ((calibrationDec.
Degrees() > MAX_DEC) || (calibrationDec.
Degrees() < -MAX_DEC))
469 qCDebug(KSTARS_EKOS_GUIDE) <<
QString(
"Didn't correct calibration RA rate. Calibration DEC %1 too close to pole")
471 return raMsPerArcsec;
473 const double toRadians = M_PI / 180.0;
474 const double numer = std::cos(currentDec.
Degrees() * toRadians);
475 const double denom = std::cos(calibrationDec.
Degrees() * toRadians);
476 if (raMsPerArcsec == 0)
return raMsPerArcsec;
477 const double rate = 1.0 / raMsPerArcsec;
478 if (denom == 0)
return raMsPerArcsec;
479 const double adjustedRate = numer * rate / denom;
480 if (adjustedRate == 0)
return raMsPerArcsec;
481 const double adjustedMsPerArcsecond = 1.0 / adjustedRate;
482 qCDebug(KSTARS_EKOS_GUIDE) <<
QString(
"Corrected calibration RA rate. %1 --> %2. Calibration DEC %3 current DEC %4.")
487 return adjustedMsPerArcsecond;
490bool Calibration::restore(
const QString &encoding, ISD::Mount::PierSide currentPierSide,
491 bool reverseDecOnPierChange,
int currentBinX,
int currentBinY,
492 const dms *currentDeclination)
495 if (!restore(encoding))
497 qCDebug(KSTARS_EKOS_GUIDE) <<
"Could not restore calibration--couldn't read.";
502 if (calibrationPierSide == ISD::Mount::PIER_UNKNOWN ||
503 currentPierSide == ISD::Mount::PIER_UNKNOWN)
505 qCDebug(KSTARS_EKOS_GUIDE) <<
"Could not restore calibration--pier side unknown.";
509 if (currentDeclination !=
nullptr)
510 raPulseMsPerArcsecond = correctRA(raPulseMsPerArcsecond, calibrationDEC, *currentDeclination);
512 subBinXused = currentBinX;
513 subBinYused = currentBinY;
516 if (currentPierSide == calibrationPierSide)
518 qCDebug(KSTARS_EKOS_GUIDE) <<
"Restored calibration--same pier side. Encoding:" << encoding;
523 angle = angle + 180.0;
524 while (angle >= 360.0)
525 angle = angle - 360.0;
527 angle = angle + 360.0;
535 if (!reverseDecOnPierChange)
536 decSwap = !calibrationDecSwap;
538 qCDebug(KSTARS_EKOS_GUIDE)
539 <<
QString(
"Restored calibration--flipped angles. Angle %1, swap %2 ms/as: %3 %4. Encoding: %5")
540 .
arg(angle).
arg(decSwap ?
"T" :
"F").
arg(raPulseMsPerArcsecond).
arg(decPulseMsPerArcsecond).
arg(encoding);
An angle, stored as degrees, but expressible in many ways.
static dms fromString(const QString &s, bool deg)
Static function to create a DMS object from a QString.
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
const QString toHMSString(const bool machineReadable=false, const bool highPrecision=false) const
const double & Degrees() const
QDateTime currentDateTime()
QString toString(QStringView format, QCalendar cal) const const
qsizetype size() const const
QString arg(Args &&... args) const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
double toDouble(bool *ok) const const
int toInt(bool *ok, int base) const const
QTextStream & dec(QTextStream &stream)