7#include "calibration.h"
9#include "ekos_guide_debug.h"
11#include "indi/indimount.h"
13#define CAL_VERSION "Cal v1.1"
14#define CAL_VERSION_1_0 "Cal v1.0"
16Calibration::Calibration()
18 ROT_Z = GuiderUtils::Matrix(0);
22void Calibration::setAngle(
double rotationAngle)
24 angle = rotationAngle;
28 ROT_Z = GuiderUtils::RotateZ(-M_PI * angle / 180.0);
31void Calibration::setParameters(
double ccd_pix_width,
double ccd_pix_height,
34 ISD::Mount::PierSide currentPierSide,
35 const dms &mountRa,
const dms &mountDec)
37 focalMm = focalLengthMm;
38 ccd_pixel_width = ccd_pix_width;
39 ccd_pixel_height = ccd_pix_height;
40 calibrationRA = mountRa;
41 calibrationDEC = mountDec;
44 subBinXused = subBinX;
45 subBinYused = subBinY;
46 calibrationPierSide = currentPierSide;
49void Calibration::setBinningUsed(
int x,
int y)
55void Calibration::setRaPulseMsPerArcsecond(
double rate)
57 raPulseMsPerArcsecond = rate;
60void Calibration::setDecPulseMsPerArcsecond(
double rate)
62 decPulseMsPerArcsecond = rate;
65GuiderUtils::Vector Calibration::convertToArcseconds(
const GuiderUtils::Vector &input)
const
67 GuiderUtils::Vector arcseconds;
68 arcseconds.x = input.x * xArcsecondsPerPixel();
69 arcseconds.y = input.y * yArcsecondsPerPixel();
73GuiderUtils::Vector Calibration::convertToPixels(
const GuiderUtils::Vector &input)
const
75 GuiderUtils::Vector arcseconds;
76 arcseconds.x = input.x / xArcsecondsPerPixel();
77 arcseconds.y = input.y / yArcsecondsPerPixel();
81void Calibration::convertToPixels(
double xArcseconds,
double yArcseconds,
82 double *xPixel,
double *yPixel)
const
84 *xPixel = xArcseconds / xArcsecondsPerPixel();
85 *yPixel = yArcseconds / yArcsecondsPerPixel();
88GuiderUtils::Vector Calibration::rotateToRaDec(
const GuiderUtils::Vector &input)
const
90 GuiderUtils::Vector in;
96void Calibration::rotateToRaDec(
double dx,
double dy,
97 double *ra,
double *dec)
const
99 GuiderUtils::Vector input;
102 GuiderUtils::Vector out = rotateToRaDec(input);
107double Calibration::binFactor()
const
109 return static_cast<double>(subBinXused) /
static_cast<double>(subBinX);
112double Calibration::inverseBinFactor()
const
114 return 1.0 / binFactor();
117double Calibration::xArcsecondsPerPixel()
const
120 return binFactor() * (206264.806 * ccd_pixel_width * subBinX) / focalMm;
123double Calibration::yArcsecondsPerPixel()
const
125 return binFactor() * (206264.806 * ccd_pixel_height * subBinY) / focalMm;
128double Calibration::xPixelsPerArcsecond()
const
130 return inverseBinFactor() * (focalMm / (206264.806 * ccd_pixel_width * subBinX));
133double Calibration::yPixelsPerArcsecond()
const
135 return inverseBinFactor() * (focalMm / (206264.806 * ccd_pixel_height * subBinY));
138double Calibration::raPulseMillisecondsPerArcsecond()
const
140 return raPulseMsPerArcsecond;
143double Calibration::decPulseMillisecondsPerArcsecond()
const
145 return decPulseMsPerArcsecond;
148double Calibration::calculateRotation(
double x,
double y)
154 if ((!GuiderUtils::Vector(x, y, 0)) < 1)
158 if (fabs(x) < fabs(y) / 1000000.0)
160 phi = y > 0 ? 90.0 : 270;
164 phi = 180.0 / M_PI * atan2(y, x);
172bool Calibration::calculate1D(
double start_x,
double start_y,
173 double end_x,
double end_y,
int RATotalPulse)
175 return calculate1D(end_x - start_x, end_y - start_y, RATotalPulse);
178bool Calibration::calculate1D(
double dx,
double dy,
int RATotalPulse)
180 const double arcSecondsX = dx * xArcsecondsPerPixel();
181 const double arcSecondsY = dy * yArcsecondsPerPixel();
182 const double arcSeconds = std::hypot(arcSecondsX, arcSecondsY);
183 if (arcSeconds < .1 || RATotalPulse <= 0)
185 qCDebug(KSTARS_EKOS_GUIDE)
186 << QString(
"Bad input to calculate1D: ra %1 %2 total pulse %3")
187 .arg(dx).arg(dy).arg(RATotalPulse);
191 const double phi = calculateRotation(arcSecondsX, arcSecondsY);
196 calibrationAngle = phi;
197 calibrationAngleRA = phi;
198 calibrationAngleDEC = -1;
199 decSwap = calibrationDecSwap =
false;
201 if (RATotalPulse > 0)
202 setRaPulseMsPerArcsecond(RATotalPulse / arcSeconds);
204 if (raPulseMillisecondsPerArcsecond() > 10000)
206 qCDebug(KSTARS_EKOS_GUIDE)
207 <<
"Calibration computed unreasonable pulse-milliseconds-per-arcsecond: "
208 << raPulseMillisecondsPerArcsecond() <<
" & " << decPulseMillisecondsPerArcsecond();
215bool Calibration::calculate2D(
216 double start_ra_x,
double start_ra_y,
double end_ra_x,
double end_ra_y,
217 double start_dec_x,
double start_dec_y,
double end_dec_x,
double end_dec_y,
218 bool *reverse_dec_dir,
int RATotalPulse,
int DETotalPulse)
220 return calculate2D((end_ra_x - start_ra_x),
221 (end_ra_y - start_ra_y),
222 (end_dec_x - start_dec_x),
223 (end_dec_y - start_dec_y),
224 reverse_dec_dir, RATotalPulse, DETotalPulse);
226bool Calibration::calculate2D(
227 double ra_dx,
double ra_dy,
double dec_dx,
double dec_dy,
228 bool *reverse_dec_dir,
int RATotalPulse,
int DETotalPulse)
230 const double raArcsecondsdX = ra_dx * xArcsecondsPerPixel();
231 const double raArcsecondsdY = ra_dy * yArcsecondsPerPixel();
232 const double decArcsecondsdX = dec_dx * xArcsecondsPerPixel();
233 const double decArcsecondsdY = dec_dy * yArcsecondsPerPixel();
234 const double raArcseconds = std::hypot(raArcsecondsdX, raArcsecondsdY);
235 const double decArcseconds = std::hypot(decArcsecondsdX, decArcsecondsdY);
236 if (raArcseconds < .1 || decArcseconds < .1 || RATotalPulse <= 0 || DETotalPulse <= 0)
238 qCDebug(KSTARS_EKOS_GUIDE)
239 << QString(
"Bad input to calculate2D: ra %1 %2 dec %3 %4 total pulses %5 %6")
240 .arg(ra_dx).arg(ra_dy).arg(dec_dx).arg(dec_dy).arg(RATotalPulse).arg(DETotalPulse);
248 GuiderUtils::Vector ra_vect = GuiderUtils::Normalize(GuiderUtils::Vector(raArcsecondsdX, raArcsecondsdY, 0));
249 GuiderUtils::Vector dec_vect = GuiderUtils::Normalize(GuiderUtils::Vector(decArcsecondsdX, decArcsecondsdY, 0));
251 GuiderUtils::Vector dec_vect_rotated_CCW = dec_vect * GuiderUtils::RotateZ(M_PI / 2);
252 GuiderUtils::Vector dec_vect_rotated_CW = dec_vect * GuiderUtils::RotateZ(-M_PI / 2);
254 double scalar_product_CCW = dec_vect_rotated_CCW & ra_vect;
255 double scalar_product_CW = dec_vect_rotated_CW & ra_vect;
257 bool ra_dec_is_CW_system = scalar_product_CCW > scalar_product_CW ? true :
false;
259 phi_ra = calculateRotation(raArcsecondsdX, raArcsecondsdY);
263 phi_dec = calculateRotation(decArcsecondsdX, decArcsecondsdY);
268 calibrationAngleRA = phi_ra;
269 calibrationAngleDEC = phi_dec;
271 if (ra_dec_is_CW_system)
281 if (fabs(phi_dec - phi_ra) > 180)
283 if (phi_ra > phi_dec)
290 phi = (phi_ra + phi_dec) / 2;
298 calibrationAngle = phi;
302 *reverse_dec_dir = decSwap = ra_dec_is_CW_system;
303 calibrationDecSwap = decSwap;
305 if (RATotalPulse > 0)
306 setRaPulseMsPerArcsecond(RATotalPulse / raArcseconds);
308 if (DETotalPulse > 0)
309 setDecPulseMsPerArcsecond(DETotalPulse / decArcseconds);
312 if (raPulseMillisecondsPerArcsecond() > 10000 || decPulseMillisecondsPerArcsecond() > 10000)
314 qCDebug(KSTARS_EKOS_GUIDE)
315 <<
"Calibration computed unreasonable pulse-milliseconds-per-arcsecond: "
316 << raPulseMillisecondsPerArcsecond() <<
" & " << decPulseMillisecondsPerArcsecond();
320 qCDebug(KSTARS_EKOS_GUIDE) << QString(
"Set RA ms/as = %1ms / %2as = %3. DEC: %4ms / %5px = %6.")
321 .arg(RATotalPulse).arg(raArcseconds).arg(raPulseMillisecondsPerArcsecond())
322 .arg(DETotalPulse).arg(decArcseconds).arg(decPulseMillisecondsPerArcsecond());
327void Calibration::computeDrift(
const GuiderUtils::Vector &detection,
const GuiderUtils::Vector &reference,
328 double *raDrift,
double *decDrift)
const
330 GuiderUtils::Vector drift = detection - reference;
331 drift = rotateToRaDec(drift);
337void Calibration::setDeclinationSwapEnabled(
bool value)
339 qCDebug(KSTARS_EKOS_GUIDE) << QString(
"decSwap set to %1, was %2, cal %3")
340 .arg(value ?
"T" :
"F")
341 .arg(decSwap ?
"T" :
"F")
342 .arg(calibrationDecSwap ?
"T" :
"F");
346QString Calibration::serialize()
const
349 QString dateStr = now.
toString(
"yyyy-MM-dd hh:mm:ss");
350 QString raStr = std::isnan(calibrationRA.Degrees()) ?
"" : calibrationRA.toDMSString(
false,
true,
true);
351 QString decStr = std::isnan(calibrationDEC.Degrees()) ?
"" : calibrationDEC.toHMSString(
true,
true);
353 QString(
"%16,bx=%1,by=%2,pw=%3,ph=%4,fl=%5,ang=%6,angR=%7,angD=%8,"
354 "ramspas=%9,decmspas=%10,swap=%11,ra=%12,dec=%13,side=%14,when=%15,calEnd")
355 .
arg(subBinX).
arg(subBinY).
arg(ccd_pixel_width).
arg(ccd_pixel_height)
356 .
arg(focalMm).
arg(calibrationAngle).
arg(calibrationAngleRA)
357 .
arg(calibrationAngleDEC).
arg(raPulseMsPerArcsecond)
358 .
arg(decPulseMsPerArcsecond).
arg(calibrationDecSwap ? 1 : 0)
359 .
arg(raStr).
arg(decStr).
arg(
static_cast<int>(calibrationPierSide)).
arg(dateStr).
arg(CAL_VERSION);
368 *result = ref.
mid(
id.size());
372bool parseDouble(
const QString &ref,
const QString &
id,
double *result)
384 *result = ref.
mid(
id.size()).
toInt(&ok);
389bool Calibration::restore(
const QString &encoding)
391 QStringList items = encoding.
split(
',');
392 if (items.
size() != 17)
return false;
395 bool fixOldCalibration =
false;
396 if (items[i] == CAL_VERSION_1_0)
398 fixOldCalibration =
true;
400 else if (items[i] == CAL_VERSION) fixOldCalibration =
false;
403 if (!parseInt(items[++i],
"bx=", &subBinX))
return false;
404 if (!parseInt(items[++i],
"by=", &subBinY))
return false;
405 if (!parseDouble(items[++i],
"pw=", &ccd_pixel_width))
return false;
406 if (!parseDouble(items[++i],
"ph=", &ccd_pixel_height))
return false;
407 if (!parseDouble(items[++i],
"fl=", &focalMm))
return false;
408 if (!parseDouble(items[++i],
"ang=", &calibrationAngle))
return false;
409 setAngle(calibrationAngle);
410 if (!parseDouble(items[++i],
"angR=", &calibrationAngleRA))
return false;
411 if (!parseDouble(items[++i],
"angD=", &calibrationAngleDEC))
return false;
415 if (!parseDouble(items[++i],
"ramspas=", &raPulseMsPerArcsecond))
418 double raPulseMsPerPixel;
419 if (parseDouble(items[i],
"ramspp=", &raPulseMsPerPixel) && (xArcsecondsPerPixel() > 0))
422 raPulseMsPerArcsecond = raPulseMsPerPixel / xArcsecondsPerPixel();
426 if (!parseDouble(items[++i],
"decmspas=", &decPulseMsPerArcsecond))
429 double decPulseMsPerPixel;
430 if (parseDouble(items[i],
"decmspp=", &decPulseMsPerPixel) && (yArcsecondsPerPixel() > 0))
433 decPulseMsPerArcsecond = decPulseMsPerPixel / yArcsecondsPerPixel();
439 if (!parseInt(items[++i],
"swap=", &tempInt))
return false;
440 decSwap =
static_cast<bool>(tempInt);
441 if (fixOldCalibration)
443 qCDebug(KSTARS_EKOS_GUIDE) << QString(
"Modifying v1.0 calibration");
446 calibrationDecSwap = decSwap;
448 if (!parseString(items[++i],
"ra=", &tempStr))
return false;
451 if (!parseString(items[++i],
"dec=", &tempStr))
return false;
453 if (!parseInt(items[++i],
"side=", &tempInt))
return false;
454 calibrationPierSide =
static_cast<ISD::Mount::PierSide
>(tempInt);
455 if (!parseString(items[++i],
"when=", &tempStr))
return false;
457 if (items[++i] !=
"calEnd")
return false;
462void Calibration::save()
const
464 QString encoding = serialize();
465 Options::setSerializedCalibration(encoding);
466 qCDebug(KSTARS_EKOS_GUIDE) << QString(
"Saved calibration: %1").arg(encoding);
469bool Calibration::restore(ISD::Mount::PierSide currentPierSide,
470 bool reverseDecOnPierChange,
int currentBinX,
int currentBinY,
471 const dms *declination)
473 return restore(Options::serializedCalibration(), currentPierSide,
474 reverseDecOnPierChange, currentBinX, currentBinY, declination);
477double Calibration::correctRA(
double raMsPerArcsec,
const dms &calibrationDec,
const dms ¤tDec)
479 constexpr double MAX_DEC = 60.0;
481 if (std::isnan(calibrationDec.
Degrees()) || std::isnan(currentDec.
Degrees()))
483 qCDebug(KSTARS_EKOS_GUIDE) << QString(
"Did not convert calibration RA rate. Input DEC invalid");
484 return raMsPerArcsec;
486 if ((calibrationDec.
Degrees() > MAX_DEC) || (calibrationDec.
Degrees() < -MAX_DEC))
488 qCDebug(KSTARS_EKOS_GUIDE) << QString(
"Didn't correct calibration RA rate. Calibration DEC %1 too close to pole")
490 return raMsPerArcsec;
492 const double toRadians = M_PI / 180.0;
493 const double numer = std::cos(currentDec.
Degrees() * toRadians);
494 const double denom = std::cos(calibrationDec.
Degrees() * toRadians);
495 if (raMsPerArcsec == 0)
return raMsPerArcsec;
496 const double rate = 1.0 / raMsPerArcsec;
497 if (denom == 0)
return raMsPerArcsec;
498 const double adjustedRate = numer * rate / denom;
499 if (adjustedRate == 0)
return raMsPerArcsec;
500 const double adjustedMsPerArcsecond = 1.0 / adjustedRate;
501 qCDebug(KSTARS_EKOS_GUIDE) << QString(
"Corrected calibration RA rate. %1 --> %2. Calibration DEC %3 current DEC %4.")
506 return adjustedMsPerArcsecond;
509bool Calibration::restore(
const QString &encoding, ISD::Mount::PierSide currentPierSide,
510 bool reverseDecOnPierChange,
int currentBinX,
int currentBinY,
511 const dms *currentDeclination)
514 if (!restore(encoding))
516 qCDebug(KSTARS_EKOS_GUIDE) <<
"Could not restore calibration--couldn't read.";
521 if (calibrationPierSide == ISD::Mount::PIER_UNKNOWN ||
522 currentPierSide == ISD::Mount::PIER_UNKNOWN)
524 qCDebug(KSTARS_EKOS_GUIDE) <<
"Could not restore calibration--pier side unknown.";
528 if (currentDeclination !=
nullptr)
529 raPulseMsPerArcsecond = correctRA(raPulseMsPerArcsecond, calibrationDEC, *currentDeclination);
531 subBinXused = currentBinX;
532 subBinYused = currentBinY;
535 if (currentPierSide == calibrationPierSide)
537 qCDebug(KSTARS_EKOS_GUIDE) <<
"Restored calibration--same pier side. Encoding:" << encoding;
542 angle = angle + 180.0;
543 while (angle >= 360.0)
544 angle = angle - 360.0;
546 angle = angle + 360.0;
554 if (!reverseDecOnPierChange)
555 decSwap = !calibrationDecSwap;
557 qCDebug(KSTARS_EKOS_GUIDE)
558 << QString(
"Restored calibration--flipped angles. Angle %1, swap %2 ms/as: %3 %4. Encoding: %5")
559 .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 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)