87bool isEqual(
double a,
double b)
89 return std::abs(a - b) < 0.01;
96 qRegisterMetaType<Ekos::AlignState>(
"Ekos::AlignState");
97 qDBusRegisterMetaType<Ekos::AlignState>();
99 new AlignAdaptor(
this);
104 KStarsData::Instance()->clearTransientFOVs();
106 solverFOV.reset(
new FOV());
107 solverFOV->setName(
i18n(
"Solver FOV"));
108 solverFOV->setObjectName(
"solver_fov");
109 solverFOV->setLockCelestialPole(
true);
110 solverFOV->setColor(
KStars::Instance()->data()->colorScheme()->colorNamed(
"SolverFOVColor").name());
111 solverFOV->setProperty(
"visible",
false);
114 sensorFOV.reset(
new FOV());
115 sensorFOV->setObjectName(
"sensor_fov");
116 sensorFOV->setLockCelestialPole(
true);
117 sensorFOV->setProperty(
"visible", Options::showSensorFOV());
124 showFITSViewerB->setIcon(
129 toggleFullScreenB->setIcon(
133 connect(toggleFullScreenB, &
QPushButton::clicked,
this, &Ekos::Align::toggleAlignWidgetFullScreen);
138 m_AlignView.reset(
new AlignView(alignWidget, FITS_ALIGN));
140 m_AlignView->setBaseSize(alignWidget->size());
141 m_AlignView->createFloatingToolBar();
145 alignWidget->setLayout(vlayout);
149 captureAndSolve(
true);
162 gotoModeButtonGroup->setId(syncR, GOTO_SYNC);
163 gotoModeButtonGroup->setId(slewR, GOTO_SLEW);
164 gotoModeButtonGroup->setId(nothingR, GOTO_NOTHING);
167 m_DebounceTimer.setInterval(500);
168 m_DebounceTimer.setSingleShot(
true);
171 m_CurrentGotoMode = GOTO_SLEW;
172 gotoModeButtonGroup->button(m_CurrentGotoMode)->setChecked(
true);
174#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
176 [ = ](
int id,
bool toggled)
179 [ = ](
int id,
bool toggled)
183 this->m_CurrentGotoMode =
static_cast<GotoMode
>(id);
186 m_CaptureTimer.setSingleShot(
true);
187 m_CaptureTimer.setInterval(CAPTURE_RETRY_DELAY);
188 connect(&m_CaptureTimer, &
QTimer::timeout,
this, &Align::processCaptureTimeout);
189 m_AlignTimer.setSingleShot(
true);
190 m_AlignTimer.setInterval(Options::astrometryTimeout() * 1000);
191 connect(&m_AlignTimer, &
QTimer::timeout,
this, &Ekos::Align::checkAlignmentTimeout);
194 stopLayout->addWidget(pi.get());
196 rememberSolverWCS = Options::astrometrySolverWCS();
197 rememberAutoWCS = Options::autoWCS();
199 solverModeButtonGroup->setId(localSolverR, SOLVER_LOCAL);
200 solverModeButtonGroup->setId(remoteSolverR, SOLVER_REMOTE);
202 setSolverMode(Options::solverMode());
204#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
206 [ = ](
int id,
bool toggled)
209 [ = ](
int id,
bool toggled)
224 connect(
this, &Align::newStatus,
this, [
this](
AlignState state)
235 for (
auto &button : qButtons)
236 button->setAutoDefault(
false);
238 savedOptionsProfiles =
QDir(KSPaths::writableLocation(
240 if(
QFile(savedOptionsProfiles).exists())
241 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
243 m_StellarSolverProfiles = getDefaultAlignOptionsProfiles();
245 m_StellarSolver.reset(
new StellarSolver());
246 connect(m_StellarSolver.get(), &StellarSolver::logOutput,
this, &Align::appendLogText);
248 setupPolarAlignmentAssistant();
249 setupManualRotator();
250 setuptDarkProcessor();
252 setupSolutionTable();
256 loadGlobalSettings();
259 setupOpticalTrainManager();
264 if (m_StellarSolver.get() !=
nullptr)
265 disconnect(m_StellarSolver.get(), &StellarSolver::logOutput,
this, &Align::appendLogText);
267 if (alignWidget->parent() ==
nullptr)
268 toggleAlignWidgetFullScreen();
275 for (
auto &dirFile : dir.entryList())
278void Align::selectSolutionTableRow(
int row,
int column)
282 solutionTable->selectRow(row);
283 for (
int i = 0; i < alignPlot->itemCount(); i++)
307void Align::handleHorizontalPlotSizeChange()
309 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
313void Align::handleVerticalPlotSizeChange()
315 alignPlot->yAxis->setScaleRatio(alignPlot->xAxis, 1.0);
321 if (
event->oldSize().width() != -1)
324 handleHorizontalPlotSizeChange();
326 handleVerticalPlotSizeChange();
342 QString labelText = label->text();
343 int point = labelText.
toInt() - 1;
350 "<th colspan=\"2\">Object %1: %2</th>"
353 "<td>RA:</td><td>%3</td>"
356 "<td>DE:</td><td>%4</td>"
359 "<td>dRA:</td><td>%5</td>"
362 "<td>dDE:</td><td>%6</td>"
366 solutionTable->item(point, 2)->text(),
367 solutionTable->item(point, 0)->text(),
368 solutionTable->item(point, 1)->text(),
369 solutionTable->item(point, 4)->text(),
370 solutionTable->item(point, 5)->text()),
371 alignPlot, alignPlot->rect());
376void Align::buildTarget()
378 double accuracyRadius = alignAccuracyThreshold->value();
381 concentricRings->
data()->clear();
382 redTarget->
data()->clear();
383 yellowTarget->
data()->clear();
384 centralTarget->
data()->clear();
388 concentricRings =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
389 redTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
390 yellowTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
391 centralTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
393 const int pointCount = 200;
400 int circleRingPt = 0;
401 for (
int i = 0; i < pointCount; i++)
403 double theta = i /
static_cast<double>(pointCount) * 2 * M_PI;
405 for (
double ring = 1; ring < 8; ring++)
407 if (ring != 4 && ring != 6)
409 if (i % (9 -
static_cast<int>(ring)) == 0)
411 circleRings[circleRingPt] =
QCPCurveData(circleRingPt, accuracyRadius * ring * 0.25 * qCos(theta),
412 accuracyRadius * ring * 0.25 * qSin(theta));
418 circleCentral[i] =
QCPCurveData(i, accuracyRadius * qCos(theta), accuracyRadius * qSin(theta));
419 circleYellow[i] =
QCPCurveData(i, accuracyRadius * 1.5 * qCos(theta), accuracyRadius * 1.5 * qSin(theta));
420 circleRed[i] =
QCPCurveData(i, accuracyRadius * 2 * qCos(theta), accuracyRadius * 2 * qSin(theta));
427 concentricRings->
data()->set(circleRings,
true);
428 redTarget->
data()->set(circleRed,
true);
429 yellowTarget->
data()->set(circleYellow,
true);
430 centralTarget->
data()->set(circleCentral,
true);
443 if (alignPlot->size().width() > 0)
447void Align::slotAutoScaleGraph()
449 double accuracyRadius = alignAccuracyThreshold->value();
450 alignPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
451 alignPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
453 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
459void Align::slotClearAllSolutionPoints()
461 if (solutionTable->rowCount() == 0)
469 solutionTable->setRowCount(0);
470 alignPlot->graph(0)->data()->clear();
471 alignPlot->clearItems();
474 slotAutoScaleGraph();
478 KSMessageBox::Instance()->questionYesNo(
i18n(
"Are you sure you want to clear all of the solution points?"),
479 i18n(
"Clear Solution Points"), 60);
482void Align::slotRemoveSolutionPoint()
484 auto abstractItem = alignPlot->item(solutionTable->currentRow());
490 double point = item->position->key();
491 alignPlot->graph(0)->data()->remove(point);
495 alignPlot->removeItem(solutionTable->currentRow());
497 for (
int i = 0; i < alignPlot->itemCount(); i++)
499 auto oneItem = alignPlot->item(i);
507 solutionTable->removeRow(solutionTable->currentRow());
511void Align::slotMountModel()
515 m_MountModel =
new MountModel(
this);
517 connect(m_MountModel, &Ekos::MountModel::aborted,
this, [
this]()
519 if (m_Mount && m_Mount->isSlewing())
526 m_MountModel->
show();
546void Align::checkAlignmentTimeout()
548 if (m_SolveFromFile || ++solverIterations == MAXIMUM_SOLVER_ITERATIONS)
552 appendLogText(
i18n(
"Solver timed out."));
553 parser->stopSolver();
555 setAlignTableResult(ALIGN_RESULT_FAILED);
563 if (
sender() ==
nullptr && mode >= 0 && mode <= 1)
565 solverModeButtonGroup->button(mode)->setChecked(
true);
568 Options::setSolverMode(mode);
570 if (mode == SOLVER_REMOTE)
572 if (remoteParser.get() !=
nullptr && m_RemoteParserDevice !=
nullptr)
574 parser = remoteParser.get();
580 parser = remoteParser.get();
585 parser->setAlign(
this);
599 return m_Camera->getDeviceName();
630 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
631 if (targetChip ==
nullptr || (targetChip && targetChip->isCapturing()))
634 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser.get() !=
nullptr)
644 if (m_Camera && m_Camera == device)
657 connect(m_Camera, &ISD::ConcreteDevice::Connected,
this, [
this]()
659 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
660 controlBox->setEnabled(isConnected);
661 gotoBox->setEnabled(isConnected);
662 plateSolverOptionsGroup->setEnabled(isConnected);
663 tabWidget->setEnabled(isConnected);
665 connect(m_Camera, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
667 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
668 controlBox->setEnabled(isConnected);
669 gotoBox->setEnabled(isConnected);
670 plateSolverOptionsGroup->setEnabled(isConnected);
671 tabWidget->setEnabled(isConnected);
673 opticalTrainCombo->setEnabled(
true);
674 trainLabel->setEnabled(
true);
678 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
679 controlBox->setEnabled(isConnected);
680 gotoBox->setEnabled(isConnected);
681 plateSolverOptionsGroup->setEnabled(isConnected);
682 tabWidget->setEnabled(isConnected);
694 if (m_Mount && m_Mount == device)
707 connect(m_Mount, &ISD::ConcreteDevice::Connected,
this, [
this]()
709 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
710 controlBox->setEnabled(isConnected);
711 gotoBox->setEnabled(isConnected);
712 plateSolverOptionsGroup->setEnabled(isConnected);
713 tabWidget->setEnabled(isConnected);
715 connect(m_Mount, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
717 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
718 controlBox->setEnabled(isConnected);
719 gotoBox->setEnabled(isConnected);
720 plateSolverOptionsGroup->setEnabled(isConnected);
721 tabWidget->setEnabled(isConnected);
723 opticalTrainCombo->setEnabled(
true);
724 trainLabel->setEnabled(
true);
728 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
729 controlBox->setEnabled(isConnected);
730 gotoBox->setEnabled(isConnected);
731 plateSolverOptionsGroup->setEnabled(isConnected);
732 tabWidget->setEnabled(isConnected);
737 RUN_PAH(setCurrentTelescope(m_Mount));
740 connect(m_Mount, &ISD::Mount::Disconnected,
this, [
this]()
742 m_isRateSynced =
false;
751 if (m_Dome && m_Dome == device)
768 auto name = device->getDeviceName();
769 device->disconnect(
this);
772 if (m_Mount && m_Mount->getDeviceName() == name)
779 if (m_Dome && m_Dome->getDeviceName() == name)
786 if (m_Rotator && m_Rotator->getDeviceName() == name)
793 if (m_Camera && m_Camera->getDeviceName() == name)
802 if (m_RemoteParserDevice && m_RemoteParserDevice->getDeviceName() == name)
804 m_RemoteParserDevice.
clear();
808 if (m_FilterWheel && m_FilterWheel->getDeviceName() == name)
811 m_FilterWheel =
nullptr;
820 if (m_Mount ==
nullptr || m_Mount->isConnected() ==
false)
823 if (m_isRateSynced ==
false)
825 auto speed = m_Settings[
"PAHMountSpeed"];
826 auto slewRates = m_Mount->slewRates();
829 RUN_PAH(syncMountSpeed(speed.toString()));
831 else if (!slewRates.isEmpty())
833 RUN_PAH(syncMountSpeed(slewRates.last()));
836 m_isRateSynced = !slewRates.empty();
839 canSync = m_Mount->canSync();
841 if (canSync ==
false && syncR->isEnabled())
843 slewR->setChecked(
true);
844 appendLogText(
i18n(
"Mount does not support syncing."));
847 syncR->setEnabled(canSync);
849 if (m_FocalLength == -1 || m_Aperture == -1)
852 if (m_CameraPixelWidth != -1 && m_CameraPixelHeight != -1)
866 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
867 Q_ASSERT(targetChip);
870 uint8_t bit_depth = 8;
871 targetChip->getImageInfo(m_CameraWidth, m_CameraHeight, m_CameraPixelWidth, m_CameraPixelHeight, bit_depth);
873 setWCSEnabled(Options::astrometrySolverWCS());
875 int binx = 1, biny = 1;
876 alignBinning->setEnabled(targetChip->canBin());
877 if (targetChip->canBin())
879 alignBinning->blockSignals(
true);
881 targetChip->getMaxBin(&binx, &biny);
882 alignBinning->clear();
884 for (
int i = 0; i < binx; i++)
885 alignBinning->addItem(
QString(
"%1x%2").arg(i + 1).arg(i + 1));
887 auto binning = m_Settings[
"alignBinning"];
888 if (binning.isValid())
889 alignBinning->setCurrentText(binning.toString());
891 alignBinning->blockSignals(
false);
896 int roiW = 0, roiH = 0;
897 targetChip->getFrameMinMax(
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &roiW,
nullptr, &roiH);
900 if ( (roiW > 0 && roiW < m_CameraWidth) || (roiH > 0 && roiH < m_CameraHeight))
902 m_CameraWidth = roiW;
903 m_CameraHeight = roiH;
906 if (m_CameraPixelWidth > 0 && m_CameraPixelHeight > 0 && m_FocalLength > 0 && m_Aperture > 0)
914 if (m_Camera ==
nullptr)
917 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
918 if (targetChip ==
nullptr || (targetChip && targetChip->isCapturing()))
921 auto isoList = targetChip->getISOList();
924 if (isoList.isEmpty())
926 alignISO->setEnabled(
false);
930 alignISO->setEnabled(
true);
931 alignISO->addItems(isoList);
932 alignISO->setCurrentIndex(targetChip->getISOIndex());
936 if (m_Camera->hasGain())
938 double min, max, step, value;
939 m_Camera->getGainMinMaxStep(&min, &max, &step);
942 alignGainSpecialValue = min - step;
943 alignGain->setRange(alignGainSpecialValue, max);
944 alignGain->setSpecialValueText(
i18n(
"--"));
945 alignGain->setEnabled(
true);
946 alignGain->setSingleStep(step);
947 m_Camera->getGain(&value);
949 auto gain = m_Settings[
"alignGain"];
953 TargetCustomGainValue = gain.toDouble();
954 if (TargetCustomGainValue > 0)
955 alignGain->setValue(TargetCustomGainValue);
957 alignGain->setValue(alignGainSpecialValue);
959 alignGain->setReadOnly(m_Camera->getGainPermission() == IP_RO);
963 if (alignGain->value() > alignGainSpecialValue)
964 TargetCustomGainValue = alignGain->value();
968 alignGain->setEnabled(
false);
975 fov_scale = m_FOVPixelScale;
982 result << m_FOVWidth << m_FOVHeight << m_FOVPixelScale;
991 result << m_CameraWidth << m_CameraHeight << m_CameraPixelWidth << m_CameraPixelHeight;
1000 result << m_FocalLength << m_Aperture << m_Reducer;
1009 auto reducedFocalLength = m_Reducer * m_FocalLength;
1010 if (m_FocalRatio > 0)
1014 fov_w = 3600 * 2 * atan(m_CameraWidth * (m_CameraPixelWidth / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1015 fov_h = 3600 * 2 * atan(m_CameraHeight * (m_CameraPixelHeight / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1020 fov_w = 206264.8062470963552 * m_CameraWidth * m_CameraPixelWidth / 1000.0 / reducedFocalLength;
1021 fov_h = 206264.8062470963552 * m_CameraHeight * m_CameraPixelHeight / 1000.0 / reducedFocalLength;
1025 fov_scale = (fov_w * (alignBinning->currentIndex() + 1)) / m_CameraWidth;
1032void Align::calculateEffectiveFocalLength(
double newFOVW)
1034 if (newFOVW < 0 || isEqual(newFOVW, m_FOVWidth))
1037 auto reducedFocalLength = m_Reducer * m_FocalLength;
1038 double new_focal_length = 0;
1040 if (m_FocalRatio > 0)
1041 new_focal_length = ((m_CameraWidth * m_CameraPixelWidth / 1000.0) / tan(newFOVW / 2)) / 2;
1043 new_focal_length = ((m_CameraWidth * m_CameraPixelWidth / 1000.0) * 206264.8062470963552) / (newFOVW * 60.0);
1044 double focal_diff = std::fabs(new_focal_length - reducedFocalLength);
1048 m_EffectiveFocalLength = new_focal_length / m_Reducer;
1049 appendLogText(
i18n(
"Effective telescope focal length is updated to %1 mm.", m_EffectiveFocalLength));
1053void Align::calculateFOV()
1055 auto reducedFocalLength = m_Reducer * m_FocalLength;
1056 auto reducecdEffectiveFocalLength = m_Reducer * m_EffectiveFocalLength;
1057 auto reducedFocalRatio = m_Reducer * m_FocalRatio;
1059 if (m_FocalRatio > 0)
1063 m_FOVWidth = 3600 * 2 * atan(m_CameraWidth * (m_CameraPixelWidth / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1064 m_FOVHeight = 3600 * 2 * atan(m_CameraHeight * (m_CameraPixelHeight / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1069 m_FOVWidth = 206264.8062470963552 * m_CameraWidth * m_CameraPixelWidth / 1000.0 / reducedFocalLength;
1070 m_FOVHeight = 206264.8062470963552 * m_CameraHeight * m_CameraPixelHeight / 1000.0 / reducedFocalLength;
1076 m_FOVPixelScale = (m_FOVWidth * (alignBinning->currentIndex() + 1)) / m_CameraWidth;
1080 m_FOVHeight /= 60.0;
1082 double calculated_fov_x = m_FOVWidth;
1083 double calculated_fov_y = m_FOVHeight;
1085 QString calculatedFOV = (
QString(
"%1' x %2'").
arg(m_FOVWidth, 0,
'f', 1).
arg(m_FOVHeight, 0,
'f', 1));
1088 if (m_FOVWidth < 1 || m_FOVWidth > 60 * 180 || m_FOVHeight < 1 || m_FOVHeight > 60 * 180)
1091 i18n(
"Warning! The calculated field of view (%1) is out of bounds. Ensure the telescope focal length and camera pixel size are correct.",
1096 FocalLengthOut->setText(
QString(
"%1 (%2)").arg(reducedFocalLength, 0,
'f', 1).
1097 arg(m_EffectiveFocalLength > 0 ? reducecdEffectiveFocalLength : reducedFocalLength, 0,
'f', 1));
1099 if (m_FocalRatio > 0)
1100 FocalRatioOut->setText(
QString(
"%1 (%2)").arg(reducedFocalRatio, 0,
'f', 1).
1101 arg(m_EffectiveFocalLength > 0 ? reducecdEffectiveFocalLength / m_Aperture : reducedFocalRatio, 0,
1104 else if (m_Aperture > 0)
1105 FocalRatioOut->setText(
QString(
"%1 (%2)").arg(reducedFocalLength / m_Aperture, 0,
'f', 1).
1106 arg(m_EffectiveFocalLength > 0 ? reducecdEffectiveFocalLength / m_Aperture : reducedFocalLength / m_Aperture, 0,
1108 ReducerOut->setText(
QString(
"%1x").arg(m_Reducer, 0,
'f', 2));
1110 if (m_EffectiveFocalLength > 0)
1112 double focal_diff = std::fabs(m_EffectiveFocalLength - m_FocalLength);
1114 FocalLengthOut->setStyleSheet(
"color:green");
1115 else if (focal_diff < 15)
1116 FocalLengthOut->setStyleSheet(
"color:yellow");
1118 FocalLengthOut->setStyleSheet(
"color:red");
1126 if (m_FOVWidth == 0)
1130 i18n(
"<p>Effective field of view size in arcminutes.</p><p>Please capture and solve once to measure the effective FOV or enter the values manually.</p><p>Calculated FOV: %1</p>",
1132 m_FOVWidth = calculated_fov_x;
1133 m_FOVHeight = calculated_fov_y;
1134 m_EffectiveFOVPending =
true;
1138 m_EffectiveFOVPending =
false;
1139 FOVOut->setToolTip(
i18n(
"<p>Effective field of view size in arcminutes.</p>"));
1142 solverFOV->setSize(m_FOVWidth, m_FOVHeight);
1143 sensorFOV->setSize(m_FOVWidth, m_FOVHeight);
1145 sensorFOV->setName(m_Camera->getDeviceName());
1147 FOVOut->setText(
QString(
"%1' x %2'").arg(m_FOVWidth, 0,
'f', 1).arg(m_FOVHeight, 0,
'f', 1));
1150 const bool fovOK = ((m_FOVWidth + m_FOVHeight) / 2.0) > PAH_CUTOFF_FOV;
1151 if (m_PolarAlignmentAssistant !=
nullptr)
1152 m_PolarAlignmentAssistant->setEnabled(fovOK);
1154 if (opsAstrometry->kcfg_AstrometryUseImageScale->isChecked())
1156 int unitType = opsAstrometry->kcfg_AstrometryImageScaleUnits->currentIndex();
1161 double fov_low = qMin(m_FOVWidth / 60, m_FOVHeight / 60);
1162 double fov_high = qMax(m_FOVWidth / 60, m_FOVHeight / 60);
1163 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(fov_low);
1164 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(fov_high);
1166 Options::setAstrometryImageScaleLow(fov_low);
1167 Options::setAstrometryImageScaleHigh(fov_high);
1170 else if (unitType == 1)
1172 double fov_low = qMin(m_FOVWidth, m_FOVHeight);
1173 double fov_high = qMax(m_FOVWidth, m_FOVHeight);
1174 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(fov_low);
1175 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(fov_high);
1177 Options::setAstrometryImageScaleLow(fov_low);
1178 Options::setAstrometryImageScaleHigh(fov_high);
1183 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(m_FOVPixelScale * 0.9);
1184 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(m_FOVPixelScale * 1.1);
1187 Options::setAstrometryImageScaleLow(m_FOVPixelScale * 0.9);
1188 Options::setAstrometryImageScaleHigh(m_FOVPixelScale * 1.1);
1215 if (optionsMap.contains(
"noverify"))
1216 solver_args <<
"--no-verify";
1219 if (optionsMap.contains(
"resort"))
1220 solver_args <<
"--resort";
1223 if (optionsMap.contains(
"nofits2fits"))
1224 solver_args <<
"--no-fits2fits";
1227 if (optionsMap.contains(
"downsample"))
1228 solver_args <<
"--downsample" <<
QString::number(optionsMap.value(
"downsample", 2).toInt());
1231 if (optionsMap.contains(
"scaleL"))
1232 solver_args <<
"-L" <<
QString::number(optionsMap.value(
"scaleL").toDouble());
1235 if (optionsMap.contains(
"scaleH"))
1236 solver_args <<
"-H" <<
QString::number(optionsMap.value(
"scaleH").toDouble());
1239 if (optionsMap.contains(
"scaleUnits"))
1240 solver_args <<
"-u" << optionsMap.value(
"scaleUnits").toString();
1243 if (optionsMap.contains(
"ra"))
1244 solver_args <<
"-3" <<
QString::number(optionsMap.value(
"ra").toDouble());
1247 if (optionsMap.contains(
"de"))
1248 solver_args <<
"-4" <<
QString::number(optionsMap.value(
"de").toDouble());
1251 if (optionsMap.contains(
"radius"))
1252 solver_args <<
"-5" <<
QString::number(optionsMap.value(
"radius").toDouble());
1255 if (optionsMap.contains(
"custom"))
1256 solver_args << optionsMap.value(
"custom").toString();
1262void Align::generateFOVBounds(
double fov_w,
QString &fov_low,
QString &fov_high,
double tolerance)
1266 double lower_boundary = 1.0 - tolerance;
1267 double upper_boundary = 1.0 + tolerance;
1274 double fov_lower = fov_w * lower_boundary;
1275 double fov_upper = fov_w * upper_boundary;
1285 QVariantMap optionsMap;
1298 if (Options::astrometryUseNoVerify())
1299 optionsMap[
"noverify"] =
true;
1301 if (Options::astrometryUseResort())
1302 optionsMap[
"resort"] =
true;
1304 if (Options::astrometryUseNoFITS2FITS())
1305 optionsMap[
"nofits2fits"] =
true;
1307 if (data ==
nullptr)
1309 if (Options::astrometryUseDownsample())
1311 if (Options::astrometryAutoDownsample() && m_CameraWidth && m_CameraHeight)
1313 uint16_t w = m_CameraWidth / (alignBinning->currentIndex() + 1);
1314 optionsMap[
"downsample"] = getSolverDownsample(w);
1317 optionsMap[
"downsample"] = Options::astrometryDownsample();
1321 optionsMap[
"image_width"] = m_CameraWidth / (alignBinning->currentIndex() + 1);
1322 optionsMap[
"image_height"] = m_CameraHeight / (alignBinning->currentIndex() + 1);
1324 if (Options::astrometryUseImageScale() && m_FOVWidth > 0 && m_FOVHeight > 0)
1327 if (Options::astrometryImageScaleUnits() == 1)
1329 else if (Options::astrometryImageScaleUnits() == 2)
1331 if (Options::astrometryAutoUpdateImageScale())
1334 double fov_w = m_FOVWidth;
1337 if (Options::astrometryImageScaleUnits() == SSolver::DEG_WIDTH)
1342 else if (Options::astrometryImageScaleUnits() == SSolver::ARCSEC_PER_PIX)
1344 fov_w = m_FOVPixelScale;
1349 generateFOVBounds(fov_w, fov_low, fov_high, m_EffectiveFOVPending ? 0.3 : 0.05);
1351 optionsMap[
"scaleL"] = fov_low;
1352 optionsMap[
"scaleH"] = fov_high;
1353 optionsMap[
"scaleUnits"] = units;
1357 optionsMap[
"scaleL"] = Options::astrometryImageScaleLow();
1358 optionsMap[
"scaleH"] = Options::astrometryImageScaleHigh();
1359 optionsMap[
"scaleUnits"] = units;
1363 if (Options::astrometryUsePosition() && m_Mount !=
nullptr)
1365 double ra = 0, dec = 0;
1366 m_Mount->getEqCoords(&ra, &dec);
1368 optionsMap[
"ra"] = ra * 15.0;
1369 optionsMap[
"de"] = dec;
1370 optionsMap[
"radius"] = Options::astrometryRadius();
1377 data->getRecordValue(
"NAXIS1",
width);
1378 if (
width.isValid())
1380 optionsMap[
"downsample"] = getSolverDownsample(
width.toInt());
1383 optionsMap[
"downsample"] = Options::astrometryDownsample();
1387 data->getRecordValue(
"SCALE", pixscale);
1390 optionsMap[
"scaleL"] = 0.8 * pixscale.
toDouble();
1391 optionsMap[
"scaleH"] = 1.2 * pixscale.
toDouble();
1392 optionsMap[
"scaleUnits"] =
"app";
1397 data->getRecordValue(
"RA", ra);
1398 data->getRecordValue(
"DEC", de);
1403 optionsMap[
"radius"] = Options::astrometryRadius();
1407 if (Options::astrometryCustomOptions().isEmpty() ==
false)
1408 optionsMap[
"custom"] = Options::astrometryCustomOptions();
1419 if (m_TargetCoord.ra().degree() < 0)
1421 if (m_TelescopeCoord.isValid() ==
false)
1423 appendLogText(
i18n(
"Mount coordinates are invalid. Check mount connection and try again."));
1424 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"), KSNotification::Align,
1425 KSNotification::Alert);
1429 m_TargetCoord = m_TelescopeCoord;
1430 appendLogText(
i18n(
"Setting target to RA:%1 DEC:%2",
1431 m_TargetCoord.ra().toHMSString(
true), m_TargetCoord.dec().toDMSString(
true)));
1438 m_DestinationCoord = m_TargetCoord;
1440 qCDebug(KSTARS_EKOS_ALIGN) <<
"Capture&Solve - Target RA:" << m_TargetCoord.ra().toHMSString(
true)
1441 <<
" DE:" << m_TargetCoord.dec().toDMSString(
true);
1442 qCDebug(KSTARS_EKOS_ALIGN) <<
"Capture&Solve - Destination RA:" << m_DestinationCoord.ra().toHMSString(
true)
1443 <<
" DE:" << m_DestinationCoord.dec().toDMSString(
true);
1444 m_AlignTimer.
stop();
1445 m_CaptureTimer.
stop();
1447 if (m_Camera ==
nullptr)
1449 appendLogText(
i18n(
"Error: No camera detected."));
1453 if (m_Camera->isConnected() ==
false)
1455 appendLogText(
i18n(
"Error: lost connection to camera."));
1456 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"), KSNotification::Align,
1457 KSNotification::Alert);
1461 if (m_Camera->isBLOBEnabled() ==
false)
1463 m_Camera->setBLOBEnabled(
true);
1469 if (m_FocalLength == -1 || m_Aperture == -1)
1471 KSNotification::error(
1472 i18n(
"Telescope aperture and focal length are missing. Please check your optical train settings and try again."));
1476 if (m_CameraPixelWidth == -1 || m_CameraPixelHeight == -1)
1478 KSNotification::error(
i18n(
"CCD pixel size is missing. Please check your driver settings and try again."));
1482 if (m_FilterWheel !=
nullptr)
1484 if (m_FilterWheel->isConnected() ==
false)
1486 appendLogText(
i18n(
"Error: lost connection to filter wheel."));
1490 int targetPosition = alignFilter->currentIndex() + 1;
1492 if (targetPosition > 0 && targetPosition != currentFilterPosition)
1494 filterPositionPending =
true;
1496 m_FilterManager->setFilterPosition(targetPosition, FilterManager::NO_AUTOFOCUS_POLICY);
1502 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
1503 if (clientManager && clientManager->getBLOBMode(m_Camera->getDeviceName().
toLatin1().
constData(),
"CCD1") == B_NEVER)
1506 nullptr,
i18n(
"Image transfer is disabled for this camera. Would you like to enable it?")) ==
1509 clientManager->setBLOBMode(B_ONLY, m_Camera->getDeviceName().
toLatin1().
constData(),
"CCD1");
1510 clientManager->setBLOBMode(B_ONLY, m_Camera->getDeviceName().
toLatin1().
constData(),
"CCD2");
1518 double seqExpose = alignExposure->value();
1520 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1522 if (m_FocusState >= FOCUS_PROGRESS)
1524 appendLogText(
i18n(
"Cannot capture while focus module is busy. Retrying in %1 seconds...",
1525 CAPTURE_RETRY_DELAY / 1000));
1526 m_CaptureTimer.
start(CAPTURE_RETRY_DELAY);
1530 if (targetChip->isCapturing())
1532 appendLogText(
i18n(
"Cannot capture while CCD exposure is in progress. Retrying in %1 seconds...",
1533 CAPTURE_RETRY_DELAY / 1000));
1534 m_CaptureTimer.
start(CAPTURE_RETRY_DELAY);
1539 if (!m_SolveFromFile && m_Rotator && m_Rotator->absoluteAngleState() == IPS_BUSY)
1541 int TimeOut = CAPTURE_ROTATOR_DELAY;
1542 switch (m_CaptureTimeoutCounter)
1547 if ((absAngle = m_Rotator->getNumber(
"ABS_ROTATOR_ANGLE")->at(0)->getValue()))
1549 RotatorUtils::Instance()->startTimeFrame(absAngle);
1550 m_estimateRotatorTimeFrame =
true;
1551 appendLogText(
i18n(
"Cannot capture while rotator is busy: Time delay estimate started..."));
1553 m_CaptureTimer.
start(TimeOut);
1558 TimeOut = m_RotatorTimeFrame * 1000;
1563 TimeOut *= m_CaptureTimeoutCounter;
1564 m_estimateRotatorTimeFrame =
false;
1565 appendLogText(
i18n(
"Cannot capture while rotator is busy: Retrying in %1 seconds...", TimeOut / 1000));
1566 m_CaptureTimer.
start(TimeOut);
1572 m_AlignView->setBaseSize(alignWidget->size());
1573 m_AlignView->setProperty(
"suspended", (solverModeButtonGroup->checkedId() == SOLVER_LOCAL
1574 && alignDarkFrame->isChecked()));
1580 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser.get() !=
nullptr)
1582 if (m_RemoteParserDevice ==
nullptr)
1584 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
1590 auto activeDevices = m_RemoteParserDevice->getBaseDevice().getText(
"ACTIVE_DEVICES");
1593 auto activeCCD = activeDevices.findWidgetByName(
"ACTIVE_CCD");
1594 if (
QString(activeCCD->text) != m_Camera->getDeviceName())
1597 m_RemoteParserDevice->getClientManager()->sendNewProperty(activeDevices);
1604 solverTimer.
start();
1610 dir.setNameFilters(
QStringList() <<
"fits*" <<
"tmp.*");
1612 for (
auto &dirFile : dir.entryList())
1615 prepareCapture(targetChip);
1618 if (matchPAHStage(PAA::PAH_REFRESH))
1619 targetChip->capture(m_PolarAlignmentAssistant->getPAHExposureDuration());
1621 targetChip->capture(seqExpose);
1623 solveB->setEnabled(
false);
1624 loadSlewB->setEnabled(
false);
1625 stopB->setEnabled(
true);
1626 pi->startAnimation();
1628 RotatorGOTO =
false;
1631 emit newStatus(state);
1632 solverFOV->setProperty(
"visible",
true);
1635 if (matchPAHStage(PAA::PAH_REFRESH))
1638 appendLogText(
i18n(
"Capturing image..."));
1646 m_Mount->getEqCoords(&ra, &dec);
1647 if (!m_SolveFromFile)
1649 int currentRow = solutionTable->rowCount();
1650 solutionTable->insertRow(currentRow);
1651 for (
int i = 4; i < 6; i++)
1655 solutionTable->setItem(currentRow, i, disabledBox);
1659 RAReport->
setText(ScopeRAOut->text());
1662 solutionTable->setItem(currentRow, 0, RAReport);
1665 DECReport->
setText(ScopeDecOut->text());
1668 solutionTable->setItem(currentRow, 1, DECReport);
1670 double maxrad = 1.0;
1686 solutionTable->setItem(currentRow, 2, ObjNameReport);
1692 solutionTable->setCellWidget(currentRow, 3, alignIndicator);
1704 auto chip = data->property(
"chip");
1705 if (chip.isValid() && chip.toInt() == ISD::CameraChip::GUIDE_CCD)
1713 m_AlignView->loadData(data);
1717 m_ImageData.
reset();
1719 RUN_PAH(setImageData(m_ImageData));
1722 if (matchPAHStage(PAA::PAH_REFRESH))
1724 setCaptureComplete();
1728 appendLogText(
i18n(
"Image received."));
1731 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1734 if (alignDarkFrame->isChecked())
1736 int x,
y, w, h, binx = 1, biny = 1;
1737 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1738 targetChip->getFrame(&
x, &
y, &w, &h);
1739 targetChip->getBinning(&binx, &biny);
1741 uint16_t offsetX =
x / binx;
1742 uint16_t offsetY =
y / biny;
1744 m_DarkProcessor->denoise(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()),
1745 targetChip, m_ImageData, alignExposure->value(), offsetX, offsetY);
1749 setCaptureComplete();
1755 if (m_Camera->getUploadMode() == ISD::Camera::UPLOAD_LOCAL)
1757 rememberUploadMode = ISD::Camera::UPLOAD_LOCAL;
1758 m_Camera->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
1761 if (m_Camera->isFastExposureEnabled())
1763 m_RememberCameraFastExposure =
true;
1764 m_Camera->setFastExposureEnabled(
false);
1767 m_Camera->setEncodingFormat(
"FITS");
1768 targetChip->resetFrame();
1769 targetChip->setBatchMode(
false);
1770 targetChip->setCaptureMode(FITS_ALIGN);
1771 targetChip->setFrameType(FRAME_LIGHT);
1773 int bin = alignBinning->currentIndex() + 1;
1774 targetChip->setBinning(bin, bin);
1777 if (m_Camera->hasGain() && alignGain->isEnabled() && alignGain->value() > alignGainSpecialValue)
1778 m_Camera->setGain(alignGain->value());
1780 if (alignISO->currentIndex() >= 0)
1781 targetChip->setISOIndex(alignISO->currentIndex());
1785void Align::setCaptureComplete()
1787 if (matchPAHStage(PAA::PAH_REFRESH))
1789 emit newFrame(m_AlignView);
1790 m_PolarAlignmentAssistant->processPAHRefresh();
1794 emit newImage(m_AlignView);
1796 solverFOV->setImage(m_AlignView->getDisplayImage());
1799 if (Options::saveAlignImages())
1811 m_ImageData->saveImage(filename);
1819 gotoModeButtonGroup->button(mode)->setChecked(
true);
1820 m_CurrentGotoMode =
static_cast<GotoMode
>(mode);
1829 QStringList astrometryDataDirs = KSUtils::getAstrometryDataDirs();
1832 m_UsedScale =
false;
1833 m_UsedPosition =
false;
1838 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1840 if(Options::solverType() != SSolver::SOLVER_ASTAP
1841 && Options::solverType() != SSolver::SOLVER_WATNEYASTROMETRY)
1843 bool foundAnIndex =
false;
1844 for(
auto &dataDir : astrometryDataDirs)
1851 if(indexList.
count() > 0)
1852 foundAnIndex =
true;
1858 i18n(
"No index files were found on your system in the specified index file directories. Please download some index files or add the correct directory to the list."));
1860 if(alignSettings && m_IndexFilesPage)
1863 alignSettings->
show();
1867 if (m_StellarSolver->isRunning())
1868 m_StellarSolver->abort();
1870 m_ImageData = m_AlignView->imageData();
1871 m_StellarSolver->loadNewImageBuffer(m_ImageData->getStatistics(), m_ImageData->getImageBuffer());
1872 m_StellarSolver->setProperty(
"ProcessType", SSolver::SOLVE);
1873 m_StellarSolver->setProperty(
"ExtractorType", Options::solveSextractorType());
1874 m_StellarSolver->setProperty(
"SolverType", Options::solverType());
1875 connect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
1876 m_StellarSolver->setIndexFolderPaths(Options::astrometryIndexFolderList());
1878 SSolver::Parameters params;
1883 params = m_StellarSolverProfiles.
at(Options::solveOptionsProfile());
1885 catch (std::out_of_range
const &)
1887 params = m_StellarSolverProfiles[0];
1890 params.partition = Options::stellarSolverPartition();
1891 m_StellarSolver->setParameters(params);
1893 const SSolver::SolverType type =
static_cast<SSolver::SolverType
>(m_StellarSolver->property(
"SolverType").toInt());
1894 if(type == SSolver::SOLVER_LOCALASTROMETRY || type == SSolver::SOLVER_ASTAP || type == SSolver::SOLVER_WATNEYASTROMETRY)
1898 m_AlignView->saveImage(filename);
1899 m_StellarSolver->setProperty(
"FileToProcess", filename);
1900 ExternalProgramPaths externalPaths;
1901 externalPaths.sextractorBinaryPath = Options::sextractorBinary();
1902 externalPaths.solverPath = Options::astrometrySolverBinary();
1903 externalPaths.astapBinaryPath = Options::aSTAPExecutable();
1904 externalPaths.watneyBinaryPath = Options::watneyBinary();
1905 externalPaths.wcsPath = Options::astrometryWCSInfo();
1906 m_StellarSolver->setExternalFilePaths(externalPaths);
1909 m_StellarSolver->setProperty(
"AutoGenerateAstroConfig",
true);
1912 if(type == SSolver::SOLVER_ONLINEASTROMETRY )
1916 m_AlignView->saveImage(filename);
1918 m_StellarSolver->setProperty(
"FileToProcess", filename);
1919 m_StellarSolver->setProperty(
"AstrometryAPIKey", Options::astrometryAPIKey());
1920 m_StellarSolver->setProperty(
"AstrometryAPIURL", Options::astrometryAPIURL());
1923 bool useImageScale = Options::astrometryUseImageScale();
1924 if (useBlindScale == BLIND_ENGAGNED)
1926 useImageScale =
false;
1927 useBlindScale = BLIND_USED;
1928 appendLogText(
i18n(
"Solving with blind image scale..."));
1931 bool useImagePosition = Options::astrometryUsePosition();
1932 if (useBlindPosition == BLIND_ENGAGNED)
1934 useImagePosition =
false;
1935 useBlindPosition = BLIND_USED;
1936 appendLogText(
i18n(
"Solving with blind image position..."));
1939 if (m_SolveFromFile)
1941 FITSImage::Solution solution;
1942 m_ImageData->parseSolution(solution);
1944 if (useImageScale && solution.pixscale > 0)
1947 m_ScaleUsed = solution.pixscale;
1948 m_StellarSolver->setSearchScale(solution.pixscale * 0.8,
1949 solution.pixscale * 1.2,
1950 SSolver::ARCSEC_PER_PIX);
1953 m_StellarSolver->setProperty(
"UseScale",
false);
1955 if (useImagePosition && solution.ra > 0)
1957 m_UsedPosition =
true;
1958 m_RAUsed = solution.ra;
1959 m_DECUsed = solution.dec;
1960 m_StellarSolver->setSearchPositionInDegrees(solution.ra, solution.dec);
1963 m_StellarSolver->setProperty(
"UsePosition",
false);
1966 if (!m_ImageData->getRecordValue(
"PIERSIDE", value))
1968 appendLogText(
i18n(
"Loaded image does not have pierside information"));
1969 m_TargetPierside = ISD::Mount::PIER_UNKNOWN;
1973 appendLogText(
i18n(
"Loaded image was taken on pierside %1", value.
toString()));
1974 (value ==
"WEST") ? m_TargetPierside = ISD::Mount::PIER_WEST : m_TargetPierside = ISD::Mount::PIER_EAST;
1976 RotatorUtils::Instance()->Instance()->setImagePierside(m_TargetPierside);
1984 m_ScaleUsed = Options::astrometryImageScaleLow();
1986 SSolver::ScaleUnits units =
static_cast<SSolver::ScaleUnits
>(Options::astrometryImageScaleUnits());
1988 m_StellarSolver->setSearchScale(Options::astrometryImageScaleLow() * 0.8,
1989 Options::astrometryImageScaleHigh() * 1.2,
1993 m_StellarSolver->setProperty(
"UseScale",
false);
1995 if(useImagePosition)
1997 m_StellarSolver->setSearchPositionInDegrees(m_TelescopeCoord.ra().Degrees(), m_TelescopeCoord.dec().Degrees());
1998 m_UsedPosition =
true;
1999 m_RAUsed = m_TelescopeCoord.ra().Degrees();
2000 m_DECUsed = m_TelescopeCoord.dec().Degrees();
2003 m_StellarSolver->setProperty(
"UsePosition",
false);
2006 if(Options::alignmentLogging())
2011 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
2012 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
2013 if(Options::astrometryLogToFile())
2015 m_StellarSolver->setProperty(
"LogToFile",
true);
2016 m_StellarSolver->setProperty(
"LogFileName", Options::astrometryLogFilepath());
2021 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
2022 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
2025 SolverUtils::patchMultiAlgorithm(m_StellarSolver.get());
2028 m_StellarSolver->start();
2032 if (m_ImageData.
isNull())
2033 m_ImageData = m_AlignView->imageData();
2036 remoteParser->startSolver(m_ImageData->filename(),
generateRemoteArgs(m_ImageData),
false);
2041 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
2042 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
2043 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
2044 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
2045 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
2046 matchPAHStage(PAA::PAH_THIRD_SOLVE) ||
2047 nothingR->isChecked() ||
2052 solverTimer.
start();
2055 emit newStatus(state);
2058void Align::solverComplete()
2060 disconnect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
2061 if(!m_StellarSolver->solvingDone() || m_StellarSolver->failed())
2063 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
2064 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
2065 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
2066 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
2067 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
2068 matchPAHStage(PAA::PAH_THIRD_SOLVE))
2070 if (CHECK_PAH(processSolverFailure()))
2080 FITSImage::Solution solution = m_StellarSolver->getSolution();
2081 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false :
true;
2082 solverFinished(solution.orientation, solution.ra, solution.dec, solution.pixscale, eastToTheRight);
2088 pi->stopAnimation();
2089 stopB->setEnabled(
false);
2090 solveB->setEnabled(
true);
2092 sOrientation = orientation;
2096 double elapsed = solverTimer.
elapsed() / 1000.0;
2098 appendLogText(
i18n(
"Solver completed after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2100 m_AlignTimer.
stop();
2101 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && m_RemoteParserDevice && remoteParser.get())
2108 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2109 targetChip->getBinning(&binx, &biny);
2111 if (Options::alignmentLogging())
2113 QString parityString = eastToTheRight ?
"neg" :
"pos";
2114 appendLogText(
i18n(
"Solver RA (%1) DEC (%2) Orientation (%3) Pixel Scale (%4) Parity (%5)",
QString::number(ra,
'f', 5),
2120 if (!m_SolveFromFile &&
2121 (isEqual(m_FOVWidth, 0) || m_EffectiveFOVPending || std::fabs(pixscale - m_FOVPixelScale) > 0.005) &&
2124 double newFOVW = m_CameraWidth * pixscale / binx / 60.0;
2125 double newFOVH = m_CameraHeight * pixscale / biny / 60.0;
2127 calculateEffectiveFocalLength(newFOVW);
2128 saveNewEffectiveFOV(newFOVW, newFOVH);
2130 m_EffectiveFOVPending =
false;
2133 m_AlignCoord.setRA0(ra / 15.0);
2134 m_AlignCoord.setDec0(dec);
2137 m_AlignCoord.apparentCoord(
static_cast<long double>(J2000),
KStars::Instance()->data()->ut().djd());
2139 m_AlignCoord.EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
2142 if (!m_SolveFromFile)
2145 calculateAlignTargetDiff();
2151 double solverPA = KSUtils::rotationToPositionAngle(orientation);
2152 solverFOV->setCenter(m_AlignCoord);
2153 solverFOV->setPA(solverPA);
2154 solverFOV->setImageDisplay(Options::astrometrySolverOverlay());
2156 sensorFOV->setPA(solverPA);
2161 getFormattedCoords(m_AlignCoord.ra().Hours(), m_AlignCoord.dec().Degrees(), ra_dms, dec_dms);
2163 SolverRAOut->setText(ra_dms);
2164 SolverDecOut->setText(dec_dms);
2166 if (Options::astrometrySolverWCS())
2168 auto ccdRotation = m_Camera->getNumber(
"CCD_ROTATION");
2171 auto rotation = ccdRotation->findWidgetByName(
"CCD_ROTATION_VALUE");
2174 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
2175 rotation->setValue(orientation);
2176 clientManager->sendNewProperty(ccdRotation);
2178 if (m_wcsSynced ==
false)
2181 i18n(
"WCS information updated. Images captured from this point forward shall have valid WCS."));
2184 auto telescopeInfo = m_Mount->getNumber(
"TELESCOPE_INFO");
2186 clientManager->sendNewProperty(telescopeInfo);
2194 m_CaptureErrorCounter = 0;
2195 m_SlewErrorCounter = 0;
2196 m_CaptureTimeoutCounter = 0;
2199 i18n(
"Solution coordinates: RA (%1) DEC (%2) Telescope Coordinates: RA (%3) DEC (%4) Target Coordinates: RA (%5) DEC (%6)",
2200 m_AlignCoord.ra().toHMSString(),
2201 m_AlignCoord.dec().toDMSString(),
2202 m_TelescopeCoord.ra().toHMSString(),
2203 m_TelescopeCoord.dec().toDMSString(),
2204 m_TargetCoord.ra().toHMSString(),
2205 m_TargetCoord.dec().toDMSString()));
2207 if (!m_SolveFromFile && m_CurrentGotoMode == GOTO_SLEW)
2209 dms diffDeg(m_TargetDiffTotal / 3600.0);
2210 appendLogText(
i18n(
"Target is within %1 degrees of solution coordinates.", diffDeg.toDMSString()));
2213 if (rememberUploadMode != m_Camera->getUploadMode())
2214 m_Camera->setUploadMode(rememberUploadMode);
2217 if (m_RememberCameraFastExposure)
2219 m_RememberCameraFastExposure =
false;
2220 m_Camera->setFastExposureEnabled(
true);
2225 int currentRow = solutionTable->rowCount() - 1;
2226 if (!m_SolveFromFile)
2228 stopProgressAnimation();
2229 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
2232 if (m_Rotator !=
nullptr && m_Rotator->isConnected())
2234 if (
auto absAngle = m_Rotator->getNumber(
"ABS_ROTATOR_ANGLE"))
2237 sRawAngle = absAngle->at(0)->getValue();
2238 double OffsetAngle = RotatorUtils::Instance()->calcOffsetAngle(sRawAngle, solverPA);
2239 RotatorUtils::Instance()->updateOffset(OffsetAngle);
2241 auto reverseStatus =
"Unknown";
2242 auto reverseProperty = m_Rotator->
getSwitch(
"ROTATOR_REVERSE");
2243 if (reverseProperty)
2245 if (reverseProperty->at(0)->getState() == ISS_ON)
2246 reverseStatus =
"Reversed Direction";
2248 reverseStatus =
"Normal Direction";
2250 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << sRawAngle <<
"Rotator PA:" << solverPA
2251 <<
"Rotator Offset:" << OffsetAngle <<
"Direction:" << reverseStatus;
2253 emit newSolverResults(solverPA, ra, dec, pixscale);
2255 appendLogText(
i18n(
"Camera position angle is %1 degrees.", RotatorUtils::Instance()->calcCameraAngle(sRawAngle,
false)));
2262 {
"camera", m_Camera->getDeviceName()},
2263 {
"ra", SolverRAOut->text()},
2264 {
"de", SolverDecOut->text()},
2265 {
"dRA", m_TargetDiffRA},
2266 {
"dDE", m_TargetDiffDE},
2267 {
"targetDiff", m_TargetDiffTotal},
2270 {
"fov", FOVOut->text()},
2275 emit newStatus(state);
2276 solverIterations = 0;
2277 KSNotification::event(
QLatin1String(
"AlignSuccessful"),
i18n(
"Astrometry alignment completed successfully"),
2278 KSNotification::Align);
2280 switch (m_CurrentGotoMode)
2285 if (!m_SolveFromFile)
2287 stopProgressAnimation();
2288 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2289 solutionTable->setItem(currentRow, 3, statusReport.release());
2295 if (m_SolveFromFile || m_TargetDiffTotal >
static_cast<double>(alignAccuracyThreshold->value()))
2297 if (!m_SolveFromFile && ++solverIterations == MAXIMUM_SOLVER_ITERATIONS)
2299 appendLogText(
i18n(
"Maximum number of iterations reached. Solver failed."));
2301 if (!m_SolveFromFile)
2303 statusReport->setIcon(
QIcon(
":/icons/AlignFailure.svg"));
2304 solutionTable->setItem(currentRow, 3, statusReport.release());
2311 targetAccuracyNotMet =
true;
2313 if (!m_SolveFromFile)
2315 stopProgressAnimation();
2316 statusReport->setIcon(
QIcon(
":/icons/AlignWarning.svg"));
2317 solutionTable->setItem(currentRow, 3, statusReport.release());
2324 stopProgressAnimation();
2325 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2326 solutionTable->setItem(currentRow, 3, statusReport.release());
2328 appendLogText(
i18n(
"Target is within acceptable range."));
2332 if (!m_SolveFromFile)
2334 stopProgressAnimation();
2335 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2336 solutionTable->setItem(currentRow, 3, statusReport.release());
2341 solverFOV->setProperty(
"visible",
true);
2343 if (!matchPAHStage(PAA::PAH_IDLE))
2344 m_PolarAlignmentAssistant->processPAHStage(orientation, ra, dec, pixscale, eastToTheRight,
2345 m_StellarSolver->getSolutionHealpix(),
2346 m_StellarSolver->getSolutionIndexNumber());
2352 solveB->setEnabled(
false);
2353 loadSlewB->setEnabled(
false);
2359 emit newStatus(state);
2361 solveB->setEnabled(
true);
2362 loadSlewB->setEnabled(
true);
2370 if (Options::saveFailedAlignImages())
2378 extraFilenameInfo.
append(
QString(
"_s%1u%2").arg(m_ScaleUsed, 0,
'f', 3)
2379 .arg(Options::astrometryImageScaleUnits()));
2381 extraFilenameInfo.
append(
QString(
"_r%1_d%2").arg(m_RAUsed, 0,
'f', 5).arg(m_DECUsed, 0,
'f', 5));
2385 QString name =
"failed_align_frame_" + now.
toString(
"yyyy-MM-dd-HH-mm-ss") + extraFilenameInfo +
".fits";
2386 QString filename = path + QStringLiteral(
"/") + name;
2389 m_ImageData->saveImage(filename);
2390 appendLogText(
i18n(
"Saving failed solver image to %1", filename));
2397 if (Options::astrometryUseImageScale() && useBlindScale == BLIND_IDLE)
2399 appendLogText(
i18n(
"Solver failed. Retrying without scale constraint."));
2400 useBlindScale = BLIND_ENGAGNED;
2401 setAlignTableResult(ALIGN_RESULT_FAILED);
2407 if (Options::astrometryUsePosition() && useBlindPosition == BLIND_IDLE)
2409 appendLogText(
i18n(
"Solver failed. Retrying without position constraint."));
2410 useBlindPosition = BLIND_ENGAGNED;
2411 setAlignTableResult(ALIGN_RESULT_FAILED);
2417 appendLogText(
i18n(
"Solver Failed."));
2418 if(!Options::alignmentLogging())
2420 i18n(
"Please check you have sufficient stars in the image, the indicated FOV is correct, and the necessary index files are installed. Enable Alignment Logging in Setup Tab -> Logs to get detailed information on the failure."));
2422 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"),
2423 KSNotification::Align, KSNotification::Alert);
2426 pi->stopAnimation();
2427 stopB->setEnabled(
false);
2428 solveB->setEnabled(
true);
2429 loadSlewB->setEnabled(
true);
2431 m_AlignTimer.
stop();
2433 m_SolveFromFile =
false;
2434 solverIterations = 0;
2435 m_CaptureErrorCounter = 0;
2436 m_CaptureTimeoutCounter = 0;
2437 m_SlewErrorCounter = 0;
2440 emit newStatus(state);
2442 solverFOV->setProperty(
"visible",
false);
2444 setAlignTableResult(ALIGN_RESULT_FAILED);
2450 if (Options::astrometryUseRotator())
2452 if (m_SolveFromFile)
2454 m_TargetPositionAngle = solverFOV->PA();
2456 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file: Setting target PA to:" << m_TargetPositionAngle;
2460 currentRotatorPA = solverFOV->PA();
2461 if (std::isnan(m_TargetPositionAngle) ==
false)
2464 if (RotatorUtils::Instance()->Instance()->checkImageFlip() && (Options::astrometryFlipRotationAllowed()))
2467 sRawAngle = RotatorUtils::Instance()->calcRotatorAngle(m_TargetPositionAngle);
2468 m_TargetPositionAngle = RotatorUtils::Instance()->calcCameraAngle(sRawAngle,
true);
2469 RotatorUtils::Instance()->setImagePierside(ISD::Mount::PIER_UNKNOWN);
2472 if (m_Rotator !=
nullptr && m_Rotator->isConnected())
2474 if(fabs(KSUtils::rangePA(currentRotatorPA - m_TargetPositionAngle)) * 60 >
2475 Options::astrometryRotatorThreshold())
2478 emit newSolverResults(m_TargetPositionAngle, 0, 0, 0);
2479 appendLogText(
i18n(
"Setting camera position angle to %1 degrees ...", m_TargetPositionAngle));
2481 emit newStatus(state);
2486 appendLogText(
i18n(
"Camera position angle is within acceptable range."));
2488 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
2494 double current = currentRotatorPA;
2495 double target = m_TargetPositionAngle;
2497 double diff = KSUtils::rangePA(current - target);
2498 double threshold = Options::astrometryRotatorThreshold() / 60.0;
2500 appendLogText(
i18n(
"Current PA is %1; Target PA is %2; diff: %3", current, target, diff));
2502 emit manualRotatorChanged(current, target, threshold);
2504 m_ManualRotator->setRotatorDiff(current, target, diff);
2505 if (fabs(diff) > threshold)
2507 targetAccuracyNotMet =
true;
2508 m_ManualRotator->
show();
2509 m_ManualRotator->
raise();
2511 emit newStatus(state);
2516 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
2517 targetAccuracyNotMet =
false;
2528 m_CaptureTimer.
stop();
2529 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
2530 m_StellarSolver->abort();
2531 else if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser)
2532 remoteParser->stopSolver();
2534 pi->stopAnimation();
2535 stopB->setEnabled(
false);
2536 solveB->setEnabled(
true);
2537 loadSlewB->setEnabled(
true);
2539 m_SolveFromFile =
false;
2540 solverIterations = 0;
2541 m_CaptureErrorCounter = 0;
2542 m_CaptureTimeoutCounter = 0;
2543 m_SlewErrorCounter = 0;
2544 m_AlignTimer.
stop();
2549 if (rememberUploadMode != m_Camera->getUploadMode())
2550 m_Camera->setUploadMode(rememberUploadMode);
2553 if (m_RememberCameraFastExposure)
2555 m_RememberCameraFastExposure =
false;
2556 m_Camera->setFastExposureEnabled(
true);
2559 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2562 if (matchPAHStage(PAA::PAH_POST_REFRESH))
2564 if (targetChip->isCapturing())
2565 targetChip->abortExposure();
2567 appendLogText(
i18n(
"Refresh is complete."));
2571 if (targetChip->isCapturing())
2573 targetChip->abortExposure();
2574 appendLogText(
i18n(
"Capture aborted."));
2578 double elapsed = solverTimer.
elapsed() / 1000.0;
2580 appendLogText(
i18n(
"Solver aborted after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2585 emit newStatus(state);
2587 setAlignTableResult(ALIGN_RESULT_FAILED);
2592 int currentRow = solutionTable->rowCount() - 1;
2599 QWidget *indicator = solutionTable->cellWidget(currentRow, 3);
2600 if (indicator ==
nullptr)
2605void Align::stopProgressAnimation()
2608 if (progress_indicator !=
nullptr)
2616 result << sOrientation << sRA << sDEC;
2621void Align::appendLogText(
const QString &text)
2623 m_LogText.
insert(0,
i18nc(
"log entry; %1 is the date, %2 is the text",
"%1 %2",
2624 KStarsData::Instance()->lt().toString(
"yyyy-MM-ddThh:mm:ss"), text));
2626 qCInfo(KSTARS_EKOS_ALIGN) << text;
2631void Align::clearLog()
2639 if (prop.isNameMatch(
"EQUATORIAL_EOD_COORD") || prop.isNameMatch(
"EQUATORIAL_COORD"))
2641 auto nvp = prop.getNumber();
2644 getFormattedCoords(m_TelescopeCoord.ra().Hours(), m_TelescopeCoord.dec().Degrees(), ra_dms, dec_dms);
2646 ScopeRAOut->setText(ra_dms);
2647 ScopeDecOut->setText(dec_dms);
2653 m_wasSlewStarted =
false;
2662 if (m_wasSlewStarted && Options::astrometryAutoUpdatePosition())
2665 opsAstrometry->estRA->setText(ra_dms);
2666 opsAstrometry->estDec->setText(dec_dms);
2668 Options::setAstrometryPositionRA(nvp->np[0].value * 15);
2669 Options::setAstrometryPositionDE(nvp->np[1].value);
2675 if (m_Dome && m_Dome->isMoving())
2682 if (m_wasSlewStarted && matchPAHStage(PAA::PAH_FIND_CP))
2685 m_wasSlewStarted =
false;
2686 appendLogText(
i18n(
"Mount completed slewing near celestial pole. Capture again to verify."));
2689 m_PolarAlignmentAssistant->setPAHStage(PAA::PAH_FIRST_CAPTURE);
2700 m_wasSlewStarted =
false;
2702 if (m_CurrentGotoMode == GOTO_SLEW)
2709 appendLogText(
i18n(
"Mount is synced to solution coordinates."));
2715 i18n(
"Astrometry alignment completed successfully"), KSNotification::Align);
2717 emit newStatus(state);
2718 solverIterations = 0;
2719 loadSlewB->setEnabled(
true);
2726 if (!didSlewStart())
2729 qCDebug(KSTARS_EKOS_ALIGN) <<
"Mount slew planned, but not started slewing yet...";
2734 m_wasSlewStarted =
false;
2735 if (m_SolveFromFile)
2737 m_SolveFromFile =
false;
2738 m_TargetPositionAngle = solverFOV->PA();
2739 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file: Setting target PA to" << m_TargetPositionAngle;
2742 emit newStatus(state);
2744 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
2745 appendLogText(
i18n(
"Settling..."));
2746 m_resetCaptureTimeoutCounter =
true;
2747 m_CaptureTimer.
start(alignSettlingTime->value());
2750 else if (m_CurrentGotoMode == GOTO_SLEW || (m_MountModel && m_MountModel->isRunning()))
2752 if (targetAccuracyNotMet)
2753 appendLogText(
i18n(
"Slew complete. Target accuracy is not met, running solver again..."));
2755 appendLogText(
i18n(
"Slew complete. Solving Alignment Point. . ."));
2757 targetAccuracyNotMet =
false;
2760 emit newStatus(state);
2762 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
2763 appendLogText(
i18n(
"Settling..."));
2764 m_resetCaptureTimeoutCounter =
true;
2765 m_CaptureTimer.
start(alignSettlingTime->value());
2772 m_wasSlewStarted =
false;
2783 m_wasSlewStarted =
true;
2785 handleMountMotion();
2793 m_wasSlewStarted =
false;
2798 appendLogText(
i18n(
"Syncing failed."));
2800 appendLogText(
i18n(
"Slewing failed."));
2802 if (++m_SlewErrorCounter == 3)
2809 if (m_CurrentGotoMode == GOTO_SLEW)
2820 RUN_PAH(processMountRotation(m_TelescopeCoord.ra(), alignSettlingTime->value()));
2822 else if (prop.isNameMatch(
"ABS_ROTATOR_ANGLE"))
2824 auto nvp = prop.getNumber();
2825 double RAngle = nvp->np[0].value;
2826 currentRotatorPA = RotatorUtils::Instance()->calcCameraAngle(RAngle,
false);
2835 if (std::isnan(m_TargetPositionAngle) ==
false && state ==
ALIGN_ROTATING && nvp->s == IPS_OK)
2837 auto diff = fabs(RotatorUtils::Instance()->DiffPA(currentRotatorPA - m_TargetPositionAngle)) * 60;
2838 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << RAngle <<
"Current PA:" << currentRotatorPA
2839 <<
"Target PA:" << m_TargetPositionAngle <<
"Diff (arcmin):" << diff <<
"Offset:"
2840 << Options::pAOffset();
2842 if (diff <= Options::astrometryRotatorThreshold())
2844 appendLogText(
i18n(
"Rotator reached camera position angle."));
2855 if (nvp->s == IPS_OK)
2857 appendLogText(
i18n(
"Rotator failed to arrive at the requested position angle (Deviation %1 arcmin).", diff));
2859 emit newStatus(state);
2860 solveB->setEnabled(
true);
2861 loadSlewB->setEnabled(
true);
2866 else if (m_estimateRotatorTimeFrame)
2868 m_RotatorTimeFrame = RotatorUtils::Instance()->calcTimeFrame(RAngle);
2871 else if (prop.isNameMatch(
"DOME_MOTION"))
2874 if (domeReady ==
false && prop.getState() == IPS_OK)
2879 handleMountStatus();
2882 else if (prop.isNameMatch(
"TELESCOPE_MOTION_NS") || prop.isNameMatch(
"TELESCOPE_MOTION_WE"))
2884 switch (prop.getState())
2888 handleMountMotion();
2889 m_wasSlewStarted =
true;
2892 qCDebug(KSTARS_EKOS_ALIGN) <<
"Mount motion finished.";
2893 handleMountStatus();
2899void Align::handleMountMotion()
2903 if (matchPAHStage(PAA::PAH_IDLE))
2906 appendLogText(
i18n(
"Slew detected, suspend solving..."));
2914void Align::handleMountStatus()
2916 auto nvp = m_Mount->getNumber(m_Mount->isJ2000() ?
"EQUATORIAL_COORD" :
2917 "EQUATORIAL_EOD_COORD");
2926 if (m_SolveFromFile)
2931 m_DestinationCoord = m_AlignCoord;
2932 m_TargetCoord = m_AlignCoord;
2934 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file. Setting Target Coordinates align coords. RA:"
2935 << m_TargetCoord.ra().toHMSString()
2936 <<
"DE:" << m_TargetCoord.dec().toDMSString();
2940 else if (m_CurrentGotoMode == GOTO_SYNC)
2942 else if (m_CurrentGotoMode == GOTO_SLEW)
2950 if (m_Mount->Sync(&m_AlignCoord))
2952 emit newStatus(state);
2954 i18n(
"Syncing to RA (%1) DEC (%2)", m_AlignCoord.ra().toHMSString(), m_AlignCoord.dec().toDMSString()));
2959 emit newStatus(state);
2960 appendLogText(
i18n(
"Syncing failed."));
2967 emit newStatus(state);
2975 if (m_Mount->Slew(&m_TargetCoord))
2977 slewStartTimer.
start();
2978 appendLogText(
i18n(
"Slewing to target coordinates: RA (%1) DEC (%2).", m_TargetCoord.ra().toHMSString(),
2979 m_TargetCoord.dec().toDMSString()));
2983 appendLogText(
i18n(
"Slewing to target coordinates: RA (%1) DEC (%2) is rejected. (see notification)",
2984 m_TargetCoord.ra().toHMSString(),
2985 m_TargetCoord.dec().toDMSString()));
2987 emit newStatus(state);
2988 solveB->setEnabled(
true);
2989 loadSlewB->setEnabled(
true);
2995 if (canSync && !m_SolveFromFile)
2999 if (Ekos::Manager::Instance()->getCurrentJobName().isEmpty())
3001 KSNotification::event(
QLatin1String(
"EkosSchedulerTelescopeSynced"),
3002 i18n(
"Ekos job (%1) - Telescope synced",
3003 Ekos::Manager::Instance()->getCurrentJobName()));
3007 if (Options::astrometryDifferentialSlewing())
3011 m_TargetCoord.setRA(m_TargetCoord.ra() - m_AlignCoord.ra().deltaAngle(m_DestinationCoord.ra()));
3012 m_TargetCoord.setDec(m_TargetCoord.dec() - m_AlignCoord.dec().deltaAngle(m_DestinationCoord.dec()));
3013 qCDebug(KSTARS_EKOS_ALIGN) <<
"Differential slew - Target RA:" << m_TargetCoord.ra().toHMSString()
3014 <<
" DE:" << m_TargetCoord.dec().toDMSString();
3026void Align::getFormattedCoords(
double ra,
double dec,
QString &ra_str,
QString &dec_str)
3033 .
arg(ra_s.hour(), 2, 10,
QChar(
'0'))
3034 .
arg(ra_s.minute(), 2, 10,
QChar(
'0'))
3035 .
arg(ra_s.second(), 2, 10,
QChar(
'0'));
3036 if (dec_s.Degrees() < 0)
3037 dec_str =
QString(
"-%1:%2:%3")
3038 .
arg(abs(dec_s.degree()), 2, 10,
QChar(
'0'))
3039 .
arg(abs(dec_s.arcmin()), 2, 10,
QChar(
'0'))
3040 .
arg(dec_s.arcsec(), 2, 10,
QChar(
'0'));
3043 .
arg(dec_s.degree(), 2, 10,
QChar(
'0'))
3044 .
arg(dec_s.arcmin(), 2, 10,
QChar(
'0'))
3045 .
arg(dec_s.arcsec(), 2, 10,
QChar(
'0'));
3052 "Images (*.fits *.fits.fz *.fit *.fts *.xisf "
3053 "*.jpg *.jpeg *.png *.gif *.bmp "
3054 "*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw *.orf)");
3060 if (fileInfo.
exists() ==
false)
3065 RotatorGOTO =
false;
3067 m_SolveFromFile =
true;
3069 if (m_PolarAlignmentAssistant)
3070 m_PolarAlignmentAssistant->stopPAHProcess();
3072 slewR->setChecked(
true);
3073 m_CurrentGotoMode = GOTO_SLEW;
3075 solveB->setEnabled(
false);
3076 loadSlewB->setEnabled(
false);
3077 stopB->setEnabled(
true);
3078 pi->startAnimation();
3080 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && m_RemoteParserDevice ==
nullptr)
3082 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
3086 m_ImageData.
clear();
3088 m_AlignView->loadFile(fileURL);
3097 RotatorGOTO =
false;
3098 m_SolveFromFile =
true;
3099 RUN_PAH(stopPAHProcess());
3100 slewR->setChecked(
true);
3101 m_CurrentGotoMode = GOTO_SLEW;
3102 solveB->setEnabled(
false);
3103 loadSlewB->setEnabled(
false);
3104 stopB->setEnabled(
true);
3105 pi->startAnimation();
3109 m_ImageData.
clear();
3112 data->setExtension(extension);
3113 data->loadFromBuffer(image);
3114 m_AlignView->loadData(data);
3121 alignExposure->setValue(value);
3129 alignBinning->blockSignals(
true);
3130 alignBinning->setCurrentIndex(binIndex);
3131 alignBinning->blockSignals(
false);
3135 if (Options::astrometryImageScaleUnits() == SSolver::ARCSEC_PER_PIX)
3141 if (m_FilterWheel && m_FilterWheel == device)
3150 m_FilterWheel = device;
3154 connect(m_FilterWheel, &ISD::ConcreteDevice::Connected,
this, [
this]()
3156 FilterPosLabel->setEnabled(
true);
3157 alignFilter->setEnabled(
true);
3159 connect(m_FilterWheel, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
3161 FilterPosLabel->setEnabled(
false);
3162 alignFilter->setEnabled(
false);
3166 auto isConnected = m_FilterWheel && m_FilterWheel->isConnected();
3167 FilterPosLabel->setEnabled(isConnected);
3168 alignFilter->setEnabled(isConnected);
3177 return m_FilterWheel->getDeviceName();
3186 alignFilter->setCurrentText(filter);
3196 return alignFilter->currentText();
3201 alignFilter->
clear();
3205 FilterPosLabel->setEnabled(
false);
3206 alignFilter->setEnabled(
false);
3210 auto isConnected = m_FilterWheel->isConnected();
3211 FilterPosLabel->setEnabled(isConnected);
3212 alignFilter->setEnabled(alignUseCurrentFilter->isChecked() ==
false);
3214 setupFilterManager();
3216 alignFilter->addItems(m_FilterManager->getFilterLabels());
3217 currentFilterPosition = m_FilterManager->getFilterPosition();
3219 if (alignUseCurrentFilter->isChecked())
3222 alignFilter->setCurrentIndex(currentFilterPosition - 1);
3227 auto filter = m_Settings[
"alignFilter"];
3228 if (filter.isValid())
3229 alignFilter->setCurrentText(filter.toString());
3235 if ((Manager::Instance()->existRotatorController()) && (!m_Rotator || !(Device == m_Rotator)))
3237 rotatorB->setEnabled(
false);
3241 m_RotatorControlPanel->close();
3246 if (Manager::Instance()->getRotatorController(m_Rotator->getDeviceName(), m_RotatorControlPanel))
3251 m_RotatorControlPanel->show();
3252 m_RotatorControlPanel->raise();
3254 rotatorB->setEnabled(
true);
3260void Align::setWCSEnabled(
bool enable)
3265 auto wcsControl = m_Camera->
getSwitch(
"WCS_CONTROL");
3270 auto wcs_enable = wcsControl->findWidgetByName(
"WCS_ENABLE");
3271 auto wcs_disable = wcsControl->findWidgetByName(
"WCS_DISABLE");
3273 if (!wcs_enable || !wcs_disable)
3276 if ((wcs_enable->getState() == ISS_ON && enable) || (wcs_disable->getState() == ISS_ON && !enable))
3279 wcsControl->reset();
3282 appendLogText(
i18n(
"World Coordinate System (WCS) is enabled."));
3283 wcs_enable->setState(ISS_ON);
3287 appendLogText(
i18n(
"World Coordinate System (WCS) is disabled."));
3288 wcs_disable->setState(ISS_ON);
3289 m_wcsSynced =
false;
3292 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
3294 clientManager->sendNewProperty(wcsControl);
3299 INDI_UNUSED(targetChip);
3300 INDI_UNUSED(remaining);
3302 if (state == IPS_ALERT)
3304 if (++m_CaptureErrorCounter == 3 && !matchPAHStage(PolarAlignmentAssistant::PAH_REFRESH))
3306 appendLogText(
i18n(
"Capture error. Aborting..."));
3311 appendLogText(
i18n(
"Restarting capture attempt #%1", m_CaptureErrorCounter));
3312 setAlignTableResult(ALIGN_RESULT_FAILED);
3317void Align::setAlignTableResult(AlignResult result)
3324 if (progress_indicator ==
nullptr || ! progress_indicator->
isAnimated())
3326 stopProgressAnimation();
3331 case ALIGN_RESULT_SUCCESS:
3332 icon =
QIcon(
":/icons/AlignSuccess.svg");
3335 case ALIGN_RESULT_WARNING:
3336 icon =
QIcon(
":/icons/AlignWarning.svg");
3339 case ALIGN_RESULT_FAILED:
3341 icon =
QIcon(
":/icons/AlignFailure.svg");
3344 int currentRow = solutionTable->rowCount() - 1;
3345 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
3349 solutionTable->setItem(currentRow, 3, statusReport);
3352void Align::setFocusStatus(Ekos::FocusState state)
3354 m_FocusState = state;
3357uint8_t Align::getSolverDownsample(uint16_t binnedW)
3359 uint8_t downsample = Options::astrometryDownsample();
3361 if (!Options::astrometryAutoDownsample())
3364 while (downsample < 8)
3366 if (binnedW / downsample <= 1024)
3375void Align::setCaptureStatus(CaptureState newState)
3380 if (m_Mount && m_Mount->hasAlignmentModel() && Options::resetMountModelAfterMeridian())
3382 qCDebug(KSTARS_EKOS_ALIGN) <<
"Post meridian flip mount model reset" << (m_Mount->clearAlignmentModel() ?
3383 "successful." :
"failed.");
3385 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
3386 appendLogText(
i18n(
"Settling..."));
3387 m_resetCaptureTimeoutCounter =
true;
3388 m_CaptureTimer.
start(alignSettlingTime->value());
3394 if (std::isnan(m_TargetPositionAngle) ==
false)
3395 m_TargetPositionAngle = KSUtils::rangePA(m_TargetPositionAngle + 180.0);
3401 m_CaptureState = newState;
3404void Align::showFITSViewer()
3406 static int lastFVTabID = -1;
3413 fv->loadData(m_ImageData, url, &lastFVTabID);
3414 connect(fv.
get(), &FITSViewer::terminated,
this, [
this]()
3419 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) ==
false)
3420 fv->loadData(m_ImageData, url, &lastFVTabID);
3426void Align::toggleAlignWidgetFullScreen()
3428 if (alignWidget->parent() ==
nullptr)
3430 alignWidget->setParent(
this);
3431 rightLayout->insertWidget(0, alignWidget);
3432 alignWidget->showNormal();
3436 alignWidget->setParent(
nullptr);
3437 alignWidget->setWindowTitle(
i18nc(
"@title:window",
"Align Frame"));
3439 alignWidget->showMaximized();
3440 alignWidget->show();
3444void Align::setMountStatus(ISD::Mount::Status newState)
3448 case ISD::Mount::MOUNT_PARKED:
3449 solveB->setEnabled(
false);
3450 loadSlewB->setEnabled(
false);
3452 case ISD::Mount::MOUNT_IDLE:
3453 solveB->setEnabled(
true);
3454 loadSlewB->setEnabled(
true);
3456 case ISD::Mount::MOUNT_PARKING:
3457 solveB->setEnabled(
false);
3458 loadSlewB->setEnabled(
false);
3460 case ISD::Mount::MOUNT_SLEWING:
3461 case ISD::Mount::MOUNT_MOVING:
3462 solveB->setEnabled(
false);
3463 loadSlewB->setEnabled(
false);
3469 solveB->setEnabled(
true);
3470 if (matchPAHStage(PAA::PAH_IDLE))
3472 loadSlewB->setEnabled(
true);
3478 RUN_PAH(setMountStatus(newState));
3483 m_RemoteParserDevice = device;
3485 remoteSolverR->setEnabled(
true);
3486 if (remoteParser.get() !=
nullptr)
3488 remoteParser->setAstrometryDevice(m_RemoteParserDevice);
3496 solverFOV->setImageDisplay(Options::astrometrySolverWCS());
3497 m_AlignTimer.
setInterval(Options::astrometryTimeout() * 1000);
3499 m_RotatorControlPanel->updateFlipPolicy(Options::astrometryFlipRotationAllowed());
3502void Align::setupOptions()
3510 opsAlign =
new OpsAlign(
this);
3516 opsPrograms =
new OpsPrograms(
this);
3517 page = dialog->
addPage(opsPrograms,
i18n(
"External & Online Programs"));
3520 opsAstrometry =
new OpsAstrometry(
this);
3521 page = dialog->
addPage(opsAstrometry,
i18n(
"Scale & Position"));
3522 page->
setIcon(
QIcon(
":/icons/center_telescope_red.svg"));
3524 optionsProfileEditor =
new StellarSolverProfileEditor(
this, Ekos::AlignProfiles, dialog);
3525 page = dialog->
addPage(optionsProfileEditor,
i18n(
"Align Options Profiles Editor"));
3526 connect(optionsProfileEditor, &StellarSolverProfileEditor::optionsProfilesUpdated,
this, [
this]()
3528 if(
QFile(savedOptionsProfiles).exists())
3529 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
3531 m_StellarSolverProfiles = getDefaultAlignOptionsProfiles();
3532 opsAlign->reloadOptionsProfiles();
3536 connect(opsAlign, &OpsAlign::needToLoadProfile,
this, [
this, dialog, page](
const QString & profile)
3538 optionsProfileEditor->loadProfile(profile);
3542 opsAstrometryIndexFiles =
new OpsAstrometryIndexFiles(
this);
3543 connect(opsAstrometryIndexFiles, &OpsAstrometryIndexFiles::newDownloadProgress,
this, &Align::newDownloadProgress);
3544 m_IndexFilesPage = dialog->
addPage(opsAstrometryIndexFiles,
i18n(
"Index Files"));
3548void Align::setupSolutionTable()
3552 clearAllSolutionsB->setIcon(
3559 exportSolutionsCSV->setIcon(
3574void Align::setupPlot()
3576 double accuracyRadius = alignAccuracyThreshold->value();
3579 alignPlot->setSelectionTolerance(10);
3590 alignPlot->xAxis->setTickLabelColor(
Qt::white);
3591 alignPlot->yAxis->setTickLabelColor(
Qt::white);
3593 alignPlot->xAxis->setLabelColor(
Qt::white);
3594 alignPlot->yAxis->setLabelColor(
Qt::white);
3596 alignPlot->xAxis->setLabelFont(
QFont(
font().family(), 10));
3597 alignPlot->yAxis->setLabelFont(
QFont(
font().family(), 10));
3599 alignPlot->xAxis->setLabelPadding(2);
3600 alignPlot->yAxis->setLabelPadding(2);
3609 alignPlot->xAxis->setLabel(
i18n(
"dRA (arcsec)"));
3610 alignPlot->yAxis->setLabel(
i18n(
"dDE (arcsec)"));
3612 alignPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
3613 alignPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
3618 alignPlot->addGraph();
3628 alignPlot->resize(190, 190);
3629 alignPlot->replot();
3632void Align::setupFilterManager()
3635 if (m_FilterManager)
3636 m_FilterManager->disconnect(
this);
3639 Ekos::Manager::Instance()->createFilterManager(m_FilterWheel);
3642 Ekos::Manager::Instance()->getFilterManager(m_FilterWheel->getDeviceName(), m_FilterManager);
3644 connect(m_FilterManager.
get(), &FilterManager::ready,
this, [
this]()
3646 if (filterPositionPending)
3648 m_FocusState = FOCUS_IDLE;
3649 filterPositionPending = false;
3650 captureAndSolve(false);
3654 connect(m_FilterManager.get(), &FilterManager::failed,
this, [
this]()
3656 appendLogText(i18n(
"Filter operation failed."));
3661 connect(m_FilterManager.get(), &FilterManager::newStatus,
this, [
this](Ekos::FilterState filterState)
3663 if (filterPositionPending)
3665 switch (filterState)
3668 appendLogText(i18n(
"Changing focus offset by %1 steps...", m_FilterManager->getTargetFilterOffset()));
3673 const int filterComboIndex = m_FilterManager->getTargetFilterPosition() - 1;
3674 if (filterComboIndex >= 0 && filterComboIndex < alignFilter->count())
3675 appendLogText(i18n(
"Changing filter to %1...", alignFilter->itemText(filterComboIndex)));
3679 case FILTER_AUTOFOCUS:
3680 appendLogText(i18n(
"Auto focus on filter change..."));
3689 connect(m_FilterManager.get(), &FilterManager::labelsChanged,
this, &Align::checkFilter);
3690 connect(m_FilterManager.get(), &FilterManager::positionChanged,
this, &Align::checkFilter);
3693QVariantMap Align::getEffectiveFOV()
3695 KStarsData::Instance()->
userdb()->GetAllEffectiveFOVs(effectiveFOVs);
3697 m_FOVWidth = m_FOVHeight = 0;
3699 for (
auto &map : effectiveFOVs)
3701 if (map[
"Profile"].
toString() == m_ActiveProfile->name && map[
"Train"].toString() == opticalTrain())
3703 if (isEqual(map[
"Width"].toInt(), m_CameraWidth) &&
3704 isEqual(map[
"Height"].toInt(), m_CameraHeight) &&
3705 isEqual(map[
"PixelW"].toDouble(), m_CameraPixelWidth) &&
3706 isEqual(map[
"PixelH"].toDouble(), m_CameraPixelHeight) &&
3707 isEqual(map[
"FocalLength"].toDouble(), m_FocalLength) &&
3708 isEqual(map[
"FocalRedcuer"].toDouble(), m_Reducer) &&
3709 isEqual(map[
"FocalRatio"].toDouble(), m_FocalRatio))
3711 m_FOVWidth =
map[
"FovW"].toDouble();
3712 m_FOVHeight =
map[
"FovH"].toDouble();
3718 return QVariantMap();
3721void Align::saveNewEffectiveFOV(
double newFOVW,
double newFOVH)
3723 if (newFOVW < 0 || newFOVH < 0 || (isEqual(newFOVW, m_FOVWidth) && isEqual(newFOVH, m_FOVHeight)))
3726 QVariantMap effectiveMap = getEffectiveFOV();
3729 if (effectiveMap.isEmpty() ==
false)
3730 KStarsData::Instance()->
userdb()->DeleteEffectiveFOV(effectiveMap[
"id"].
toString());
3733 if (newFOVW == 0.0 && newFOVH == 0.0)
3739 effectiveMap[
"Profile"] = m_ActiveProfile->name;
3740 effectiveMap[
"Train"] = opticalTrainCombo->currentText();
3741 effectiveMap[
"Width"] = m_CameraWidth;
3742 effectiveMap[
"Height"] = m_CameraHeight;
3743 effectiveMap[
"PixelW"] = m_CameraPixelWidth;
3744 effectiveMap[
"PixelH"] = m_CameraPixelHeight;
3745 effectiveMap[
"FocalLength"] = m_FocalLength;
3746 effectiveMap[
"FocalReducer"] = m_Reducer;
3747 effectiveMap[
"FocalRatio"] = m_FocalRatio;
3748 effectiveMap[
"FovW"] = newFOVW;
3749 effectiveMap[
"FovH"] = newFOVH;
3751 KStarsData::Instance()->
userdb()->AddEffectiveFOV(effectiveMap);
3757void Align::zoomAlignView()
3759 m_AlignView->ZoomDefault();
3765 emit newFrame(m_AlignView);
3769void Align::setAlignZoom(
double scale)
3772 m_AlignView->ZoomIn();
3774 m_AlignView->ZoomOut();
3780 emit newFrame(m_AlignView);
3784void Align::syncFOV()
3786 QString newFOV = FOVOut->text();
3789 if (
match.hasMatch())
3791 double newFOVW =
match.captured(1).toDouble();
3792 double newFOVH =
match.captured(2).toDouble();
3795 saveNewEffectiveFOV(newFOVW, newFOVH);
3797 FOVOut->setStyleSheet(
QString());
3801 KSNotification::error(
i18n(
"Invalid FOV."));
3802 FOVOut->setStyleSheet(
"background-color:red");
3807bool Align::didSlewStart()
3809 if (m_wasSlewStarted)
3811 if (slewStartTimer.isValid() && slewStartTimer.elapsed() > MAX_WAIT_FOR_SLEW_START_MSEC)
3813 qCDebug(KSTARS_EKOS_ALIGN) <<
"Slew timed out...waited > 10s, it must have started already.";
3819void Align::setTargetCoords(
double ra0,
double de0)
3823 target.setDec0(de0);
3824 target.updateCoordsNow(KStarsData::Instance()->updateNum());
3828void Align::setTarget(
const SkyPoint &targetCoord)
3830 m_TargetCoord = targetCoord;
3831 qCInfo(KSTARS_EKOS_ALIGN) <<
"Target coordinates updated to JNow RA:" << m_TargetCoord.ra().toHMSString()
3832 <<
"DE:" << m_TargetCoord.dec().toDMSString();
3837 return QList<double>() << m_TargetCoord.ra0().Hours() << m_TargetCoord.dec0().Degrees();
3840void Align::setTargetPositionAngle(
double value)
3842 m_TargetPositionAngle = value;
3843 qCDebug(KSTARS_EKOS_ALIGN) <<
"Target PA updated to: " << m_TargetPositionAngle;
3846void Align::calculateAlignTargetDiff()
3848 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
3849 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
3850 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
3851 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
3852 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
3853 matchPAHStage(PAA::PAH_THIRD_SOLVE) ||
3854 nothingR->isChecked() ||
3858 if (!Options::astrometryDifferentialSlewing())
3860 m_TargetDiffRA = (m_AlignCoord.ra().deltaAngle(m_TargetCoord.ra())).Degrees() * 3600;
3861 m_TargetDiffDE = (m_AlignCoord.dec().deltaAngle(m_TargetCoord.dec())).Degrees() * 3600;
3865 m_TargetDiffRA = (m_AlignCoord.ra().deltaAngle(m_DestinationCoord.ra())).Degrees() * 3600;
3866 m_TargetDiffDE = (m_AlignCoord.dec().deltaAngle(m_DestinationCoord.dec())).Degrees() * 3600;
3867 qCDebug(KSTARS_EKOS_ALIGN) <<
"Differential slew - Solution RA:" << m_AlignCoord.ra().toHMSString()
3868 <<
" DE:" << m_AlignCoord.dec().toDMSString();
3869 qCDebug(KSTARS_EKOS_ALIGN) <<
"Differential slew - Destination RA:" << m_DestinationCoord.ra().toHMSString()
3870 <<
" DE:" << m_DestinationCoord.dec().toDMSString();
3873 m_TargetDiffTotal = sqrt(m_TargetDiffRA * m_TargetDiffRA + m_TargetDiffDE * m_TargetDiffDE);
3875 errOut->setText(
QString(
"%1 arcsec. RA:%2 DE:%3").arg(
3879 if (m_TargetDiffTotal <=
static_cast<double>(alignAccuracyThreshold->value()))
3880 errOut->setStyleSheet(
"color:green");
3881 else if (m_TargetDiffTotal < 1.5 * alignAccuracyThreshold->value())
3882 errOut->setStyleSheet(
"color:yellow");
3884 errOut->setStyleSheet(
"color:red");
3887 int currentRow = solutionTable->rowCount() - 1;
3894 solutionTable->setItem(currentRow, 4, dRAReport);
3903 solutionTable->setItem(currentRow, 5, dDECReport);
3906 double raPlot = m_TargetDiffRA;
3907 double decPlot = m_TargetDiffDE;
3908 alignPlot->graph(0)->addData(raPlot, decPlot);
3914 textLabel->position->
setCoords(raPlot, decPlot);
3922 if (!alignPlot->xAxis->range().contains(m_TargetDiffRA))
3924 alignPlot->graph(0)->rescaleKeyAxis(
true);
3925 alignPlot->yAxis->setScaleRatio(alignPlot->xAxis, 1.0);
3927 if (!alignPlot->yAxis->range().contains(m_TargetDiffDE))
3929 alignPlot->graph(0)->rescaleValueAxis(
true);
3930 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
3932 alignPlot->replot();
3938 for (
auto ¶m : m_StellarSolverProfiles)
3939 profiles << param.listName;
3944void Align::exportSolutionPoints()
3946 if (solutionTable->rowCount() == 0)
3951 "CSV File (*.csv)");
3962 i18n(
"A file named \"%1\" already exists. "
3973 KSNotification::sorry(message,
i18n(
"Invalid URL"));
3981 QString message =
i18n(
"Unable to write to file %1", path);
3982 KSNotification::sorry(message,
i18n(
"Could Not Open File"));
3990 outstream <<
"RA (J" << epoch <<
"),DE (J" << epoch
3991 <<
"),RA (degrees),DE (degrees),Name,RA Error (arcsec),DE Error (arcsec)" <<
Qt::endl;
3993 for (
int i = 0; i < solutionTable->rowCount(); i++)
4001 if (!raCell || !deCell || !objNameCell || !raErrorCell || !deErrorCell)
4003 KSNotification::sorry(
i18n(
"Error in table structure."));
4012 emit newLog(
i18n(
"Solution Points Saved as: %1", path));
4016void Align::setupPolarAlignmentAssistant()
4019 m_PolarAlignmentAssistant =
new PolarAlignmentAssistant(
this, m_AlignView);
4020 connect(m_PolarAlignmentAssistant, &Ekos::PAA::captureAndSolve,
this, [
this]()
4022 captureAndSolve(
true);
4024 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newAlignTableResult,
this, &Ekos::Align::setAlignTableResult);
4025 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newFrame,
this, &Ekos::Align::newFrame);
4026 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newPAHStage,
this, &Ekos::Align::processPAHStage);
4027 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newLog,
this, &Ekos::Align::appendLogText);
4029 tabWidget->addTab(m_PolarAlignmentAssistant,
i18n(
"Polar Alignment"));
4032void Align::setupManualRotator()
4034 if (m_ManualRotator)
4037 m_ManualRotator =
new ManualRotator(
this);
4038 connect(m_ManualRotator, &Ekos::ManualRotator::captureAndSolve,
this, [
this]()
4040 captureAndSolve(
false);
4046 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
4048 if (state > ALIGN_COMPLETE)
4053void Align::setuptDarkProcessor()
4055 if (m_DarkProcessor)
4058 m_DarkProcessor =
new DarkProcessor(
this);
4059 connect(m_DarkProcessor, &DarkProcessor::newLog,
this, &Ekos::Align::appendLogText);
4060 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted,
this, [
this](
bool completed)
4062 alignDarkFrame->setChecked(completed);
4063 m_AlignView->setProperty(
"suspended",
false);
4066 m_AlignView->rescale(ZOOM_KEEP_LEVEL);
4067 m_AlignView->updateFrame();
4069 setCaptureComplete();
4073void Align::processPAHStage(
int stage)
4081 if (m_StellarSolver && m_StellarSolver->isRunning())
4082 m_StellarSolver->abort();
4084 case PAA::PAH_POST_REFRESH:
4086 Options::setAstrometrySolverWCS(rememberSolverWCS);
4087 Options::setAutoWCS(rememberAutoWCS);
4092 case PAA::PAH_FIRST_CAPTURE:
4093 nothingR->setChecked(
true);
4094 m_CurrentGotoMode = GOTO_NOTHING;
4095 loadSlewB->setEnabled(
false);
4097 rememberSolverWCS = Options::astrometrySolverWCS();
4098 rememberAutoWCS = Options::autoWCS();
4100 Options::setAutoWCS(
false);
4101 Options::setAstrometrySolverWCS(
true);
4103 case PAA::PAH_SECOND_CAPTURE:
4104 case PAA::PAH_THIRD_CAPTURE:
4105 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
4106 emit newLog(
i18n(
"Settling..."));
4107 m_CaptureTimer.start(alignSettlingTime->value());
4114 emit newPAAStage(stage);
4117bool Align::matchPAHStage(uint32_t stage)
4119 return m_PolarAlignmentAssistant && m_PolarAlignmentAssistant->getPAHStage() == stage;
4122void Align::toggleManualRotator(
bool toggled)
4126 m_ManualRotator->show();
4127 m_ManualRotator->raise();
4130 m_ManualRotator->close();
4133void Align::setupOpticalTrainManager()
4135 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated,
this, &Align::refreshOpticalTrain);
4138 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
4142 ProfileSettings::Instance()->setOneSetting(ProfileSettings::AlignOpticalTrain,
4143 OpticalTrainManager::Instance()->
id(opticalTrainCombo->itemText(index)));
4144 refreshOpticalTrain();
4145 emit trainChanged();
4149void Align::refreshOpticalTrain()
4151 opticalTrainCombo->blockSignals(
true);
4152 opticalTrainCombo->clear();
4153 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
4154 trainB->setEnabled(
true);
4156 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::AlignOpticalTrain);
4160 auto id = trainID.
toUInt();
4163 if (OpticalTrainManager::Instance()->exists(
id) ==
false)
4165 qCWarning(KSTARS_EKOS_ALIGN) <<
"Optical train doesn't exist for id" << id;
4166 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
4169 auto name = OpticalTrainManager::Instance()->name(
id);
4171 opticalTrainCombo->setCurrentText(name);
4173 auto scope = OpticalTrainManager::Instance()->getScope(name);
4174 m_FocalLength = scope[
"focal_length"].
toDouble(-1);
4175 m_Aperture = scope[
"aperture"].toDouble(-1);
4176 m_FocalRatio = scope[
"focal_ratio"].toDouble(-1);
4177 m_Reducer = OpticalTrainManager::Instance()->getReducer(name);
4180 if (m_Aperture < 0 && m_FocalRatio > 0)
4181 m_Aperture = m_FocalLength / m_FocalRatio;
4183 auto mount = OpticalTrainManager::Instance()->getMount(name);
4186 auto camera = OpticalTrainManager::Instance()->getCamera(name);
4189 camera->setScopeInfo(m_FocalLength * m_Reducer, m_Aperture);
4190 opticalTrainCombo->setToolTip(
QString(
"%1 @ %2").arg(camera->getDeviceName(), scope[
"name"].toString()));
4194 syncTelescopeInfo();
4196 auto filterWheel = OpticalTrainManager::Instance()->getFilterWheel(name);
4197 setFilterWheel(filterWheel);
4199 auto rotator = OpticalTrainManager::Instance()->getRotator(name);
4200 setRotator(rotator);
4203 OpticalTrainSettings::Instance()->setOpticalTrainID(
id);
4204 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Align);
4205 if (settings.isValid())
4207 auto map = settings.toJsonObject().toVariantMap();
4208 if (map != m_Settings)
4211 setAllSettings(map);
4215 m_Settings = m_GlobalSettings;
4218 Options::setTelescopeFocalLength(m_FocalLength);
4219 Options::setCameraPixelWidth(m_CameraPixelWidth);
4220 Options::setCameraPixelHeight(m_CameraPixelHeight);
4221 Options::setCameraWidth(m_CameraWidth);
4222 Options::setCameraHeight(m_CameraHeight);
4225 opticalTrainCombo->blockSignals(
false);
4228void Align::syncSettings()
4239 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
4242 value = dsb->
value();
4245 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
4248 value = sb->
value();
4250 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
4255 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
4260 else if ( (cradio = qobject_cast<QRadioButton*>(sender())))
4266 m_Settings.remove(key);
4273 Options::self()->setProperty(key.
toLatin1(), value);
4275 m_Settings[key] = value;
4276 m_GlobalSettings[key] = value;
4277 m_DebounceTimer.start();
4283void Align::settleSettings()
4285 emit settingsUpdated(getAllSettings());
4287 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()));
4288 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Align, m_Settings);
4291void Align::loadGlobalSettings()
4296 QVariantMap settings;
4298 for (
auto &oneWidget : findChildren<QComboBox*>())
4300 if (oneWidget->objectName() ==
"opticalTrainCombo")
4303 key = oneWidget->objectName();
4304 value = Options::self()->property(key.
toLatin1());
4305 if (value.
isValid() && oneWidget->count() > 0)
4307 oneWidget->setCurrentText(value.
toString());
4308 settings[key] = value;
4313 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4315 key = oneWidget->objectName();
4316 value = Options::self()->property(key.
toLatin1());
4319 oneWidget->setValue(value.
toDouble());
4320 settings[key] = value;
4325 for (
auto &oneWidget : findChildren<QSpinBox*>())
4327 key = oneWidget->objectName();
4328 value = Options::self()->property(key.
toLatin1());
4331 oneWidget->setValue(value.
toInt());
4332 settings[key] = value;
4337 for (
auto &oneWidget : findChildren<QCheckBox*>())
4339 key = oneWidget->objectName();
4340 value = Options::self()->property(key.
toLatin1());
4343 oneWidget->setChecked(value.
toBool());
4344 settings[key] = value;
4349 for (
auto &oneWidget : findChildren<QRadioButton*>())
4351 key = oneWidget->objectName();
4352 value = Options::self()->property(key.
toLatin1());
4355 oneWidget->setChecked(value.
toBool());
4356 settings[key] = value;
4360 m_GlobalSettings = m_Settings = settings;
4364void Align::connectSettings()
4367 for (
auto &oneWidget : findChildren<QComboBox*>())
4371 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4375 for (
auto &oneWidget : findChildren<QSpinBox*>())
4379 for (
auto &oneWidget : findChildren<QCheckBox*>())
4383 for (
auto &oneWidget : findChildren<QRadioButton*>())
4387 disconnect(opticalTrainCombo, QOverload<int>::of(&
QComboBox::activated),
this, &Ekos::Align::syncSettings);
4390void Align::disconnectSettings()
4393 for (
auto &oneWidget : findChildren<QComboBox*>())
4394 disconnect(oneWidget, QOverload<int>::of(&
QComboBox::activated),
this, &Ekos::Align::syncSettings);
4397 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4401 for (
auto &oneWidget : findChildren<QSpinBox*>())
4405 for (
auto &oneWidget : findChildren<QCheckBox*>())
4409 for (
auto &oneWidget : findChildren<QRadioButton*>())
4417QVariantMap Align::getAllSettings()
const
4419 QVariantMap settings;
4422 for (
auto &oneWidget : findChildren<QComboBox*>())
4423 settings.insert(oneWidget->objectName(), oneWidget->currentText());
4426 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4427 settings.insert(oneWidget->objectName(), oneWidget->value());
4430 for (
auto &oneWidget : findChildren<QSpinBox*>())
4431 settings.insert(oneWidget->objectName(), oneWidget->value());
4434 for (
auto &oneWidget : findChildren<QCheckBox*>())
4435 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
4438 for (
auto &oneWidget : findChildren<QRadioButton*>())
4439 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
4447void Align::setAllSettings(
const QVariantMap &settings)
4451 disconnectSettings();
4453 for (
auto &name : settings.keys())
4456 auto comboBox = findChild<QComboBox*>(name);
4459 syncControl(settings, name, comboBox);
4464 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
4467 syncControl(settings, name, doubleSpinBox);
4472 auto spinBox = findChild<QSpinBox*>(name);
4475 syncControl(settings, name, spinBox);
4480 auto checkbox = findChild<QCheckBox*>(name);
4483 syncControl(settings, name, checkbox);
4488 auto radioButton = findChild<QRadioButton*>(name);
4491 syncControl(settings, name, radioButton);
4497 for (
auto &key : settings.keys())
4499 auto value = settings[key];
4501 Options::self()->setProperty(key.
toLatin1(), value);
4502 Options::self()->save();
4504 m_Settings[key] = value;
4505 m_GlobalSettings[key] = value;
4508 emit settingsUpdated(getAllSettings());
4511 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()));
4512 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Align, m_Settings);
4521bool Align::syncControl(
const QVariantMap &settings,
const QString &key,
QWidget * widget)
4530 if ((pSB = qobject_cast<QSpinBox *>(widget)))
4532 const int value = settings[key].toInt(&ok);
4539 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
4541 const double value = settings[key].toDouble(&ok);
4548 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
4550 const bool value = settings[key].toBool();
4556 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
4558 const QString value = settings[key].toString();
4562 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
4564 const bool value = settings[key].toBool();
4566 pRadioButton->
click();
4573void Align::setState(AlignState value)
4575 qCDebug(KSTARS_EKOS_ALIGN) <<
"Align state changed to" << getAlignStatusString(value);
4579void Align::processCaptureTimeout()
4581 if (m_CaptureTimeoutCounter++ > 3)
4583 appendLogText(
i18n(
"Capture timed out."));
4584 m_CaptureTimer.stop();
4589 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
4590 if (targetChip->isCapturing())
4592 appendLogText(
i18n(
"Capturing still running, Retrying in %1 seconds...", m_CaptureTimer.interval() / 500));
4593 targetChip->abortExposure();
4594 m_CaptureTimer.start( m_CaptureTimer.interval() * 2);
4598 setAlignTableResult(ALIGN_RESULT_FAILED);
4599 if (m_resetCaptureTimeoutCounter)
4601 m_resetCaptureTimeoutCounter =
false;
4602 m_CaptureTimeoutCounter = 0;
4604 captureAndSolve(
false);