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);
112 KStarsData::Instance()->addTransientFOV(solverFOV);
114 sensorFOV.reset(
new FOV());
115 sensorFOV->setObjectName(
"sensor_fov");
116 sensorFOV->setLockCelestialPole(
true);
117 sensorFOV->setProperty(
"visible", Options::showSensorFOV());
118 KStarsData::Instance()->addTransientFOV(sensorFOV);
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)
467 KSMessageBox::Instance()->disconnect(
this);
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);
592 parser->disconnect();
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)
651 m_Camera->disconnect(
this);
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)
701 m_Mount->disconnect(
this);
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)
755 m_Dome->disconnect(
this);
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)
788 m_Rotator->disconnect(
this);
793 if (m_Camera && m_Camera->getDeviceName() == name)
795 m_Camera->disconnect(
this);
802 if (m_RemoteParserDevice && m_RemoteParserDevice->getDeviceName() == name)
804 m_RemoteParserDevice.clear();
808 if (m_FilterWheel && m_FilterWheel->getDeviceName() == name)
810 m_FilterWheel->disconnect(
this);
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;
996QList<double> Align::telescopeInfo()
998 QList<double> result;
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 (Options::astrometryUseImageScale())
1156 auto unitType = Options::astrometryImageScaleUnits();
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 auto 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);
1538 if (m_Dome && m_Dome->isMoving())
1540 qCWarning(KSTARS_EKOS_ALIGN) <<
"Cannot capture while dome is in motion. Retrying in" << CAPTURE_RETRY_DELAY / 1000 <<
1542 m_CaptureTimer.start(CAPTURE_RETRY_DELAY);
1547 if (!m_SolveFromFile && m_Rotator && m_Rotator->absoluteAngleState() == IPS_BUSY)
1549 int TimeOut = CAPTURE_ROTATOR_DELAY;
1550 switch (m_CaptureTimeoutCounter)
1555 if ((absAngle = m_Rotator->getNumber(
"ABS_ROTATOR_ANGLE")->at(0)->getValue()))
1557 RotatorUtils::Instance()->startTimeFrame(absAngle);
1558 m_estimateRotatorTimeFrame =
true;
1559 appendLogText(
i18n(
"Cannot capture while rotator is busy: Time delay estimate started..."));
1561 m_CaptureTimer.start(TimeOut);
1566 TimeOut = m_RotatorTimeFrame * 1000;
1571 TimeOut *= m_CaptureTimeoutCounter;
1572 m_estimateRotatorTimeFrame =
false;
1573 appendLogText(
i18n(
"Cannot capture while rotator is busy: Retrying in %1 seconds...", TimeOut / 1000));
1574 m_CaptureTimer.start(TimeOut);
1580 m_AlignView->setBaseSize(alignWidget->size());
1581 m_AlignView->setProperty(
"suspended", (solverModeButtonGroup->checkedId() == SOLVER_LOCAL
1582 && alignDarkFrame->isChecked()));
1588 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser.get() !=
nullptr)
1590 if (m_RemoteParserDevice ==
nullptr)
1592 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
1598 auto activeDevices = m_RemoteParserDevice->getBaseDevice().getText(
"ACTIVE_DEVICES");
1601 auto activeCCD = activeDevices.findWidgetByName(
"ACTIVE_CCD");
1602 if (
QString(activeCCD->text) != m_Camera->getDeviceName())
1604 activeCCD->setText(m_Camera->getDeviceName().toLatin1().constData());
1605 m_RemoteParserDevice->getClientManager()->sendNewProperty(activeDevices);
1612 solverTimer.start();
1618 dir.setNameFilters(
QStringList() <<
"fits*" <<
"tmp.*");
1620 for (
auto &dirFile : dir.entryList())
1623 prepareCapture(targetChip);
1626 if (matchPAHStage(PAA::PAH_REFRESH))
1627 targetChip->capture(m_PolarAlignmentAssistant->getPAHExposureDuration());
1629 targetChip->capture(seqExpose);
1631 solveB->setEnabled(
false);
1632 loadSlewB->setEnabled(
false);
1633 stopB->setEnabled(
true);
1634 pi->startAnimation();
1636 RotatorGOTO =
false;
1639 emit newStatus(state);
1640 solverFOV->setProperty(
"visible",
true);
1643 if (matchPAHStage(PAA::PAH_REFRESH))
1646 appendLogText(
i18n(
"Capturing image..."));
1654 m_Mount->getEqCoords(&ra, &dec);
1655 if (!m_SolveFromFile)
1657 int currentRow = solutionTable->rowCount();
1658 solutionTable->insertRow(currentRow);
1659 for (
int i = 4; i < 6; i++)
1663 solutionTable->setItem(currentRow, i, disabledBox);
1667 RAReport->
setText(ScopeRAOut->text());
1670 solutionTable->setItem(currentRow, 0, RAReport);
1673 DECReport->
setText(ScopeDecOut->text());
1676 solutionTable->setItem(currentRow, 1, DECReport);
1678 double maxrad = 1.0;
1680 KStarsData::Instance()->skyComposite()->objectNearest(
new SkyPoint(
dms(ra * 15),
dms(dec)), maxrad);
1694 solutionTable->setItem(currentRow, 2, ObjNameReport);
1700 solutionTable->setCellWidget(currentRow, 3, alignIndicator);
1712 auto chip = data->property(
"chip");
1713 if (chip.isValid() && chip.toInt() == ISD::CameraChip::GUIDE_CCD)
1721 m_AlignView->loadData(data);
1725 m_ImageData.reset();
1727 RUN_PAH(setImageData(m_ImageData));
1730 if (matchPAHStage(PAA::PAH_REFRESH))
1732 setCaptureComplete();
1736 appendLogText(
i18n(
"Image received."));
1739 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1742 if (alignDarkFrame->isChecked())
1744 int x,
y, w, h, binx = 1, biny = 1;
1745 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1746 targetChip->getFrame(&
x, &
y, &w, &h);
1747 targetChip->getBinning(&binx, &biny);
1749 uint16_t offsetX =
x / binx;
1750 uint16_t offsetY =
y / biny;
1752 m_DarkProcessor->denoise(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()),
1753 targetChip, m_ImageData, alignExposure->value(), offsetX, offsetY);
1757 setCaptureComplete();
1763 if (m_Camera->getUploadMode() == ISD::Camera::UPLOAD_REMOTE)
1765 rememberUploadMode = ISD::Camera::UPLOAD_REMOTE;
1766 m_Camera->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
1769 if (m_Camera->isFastExposureEnabled())
1771 m_RememberCameraFastExposure =
true;
1772 m_Camera->setFastExposureEnabled(
false);
1775 m_Camera->setEncodingFormat(
"FITS");
1776 targetChip->resetFrame();
1777 targetChip->setBatchMode(
false);
1778 targetChip->setCaptureMode(FITS_ALIGN);
1779 targetChip->setFrameType(FRAME_LIGHT);
1781 int bin = alignBinning->currentIndex() + 1;
1782 targetChip->setBinning(bin, bin);
1785 if (m_Camera->hasGain() && alignGain->isEnabled() && alignGain->value() > alignGainSpecialValue)
1786 m_Camera->setGain(alignGain->value());
1788 if (alignISO->currentIndex() >= 0)
1789 targetChip->setISOIndex(alignISO->currentIndex());
1793void Align::setCaptureComplete()
1795 if (matchPAHStage(PAA::PAH_REFRESH))
1797 emit newFrame(m_AlignView);
1798 m_PolarAlignmentAssistant->processPAHRefresh();
1802 emit newImage(m_AlignView);
1804 solverFOV->setImage(m_AlignView->getDisplayImage());
1807 if (Options::saveAlignImages())
1810 QDateTime now = KStarsData::Instance()->
lt();
1816 QString
name =
"align_frame_" + now.
toString(
"HH-mm-ss") +
".fits";
1817 QString filename =
path + QStringLiteral(
"/") +
name;
1819 m_ImageData->saveImage(filename);
1827 gotoModeButtonGroup->button(mode)->setChecked(
true);
1828 m_CurrentGotoMode =
static_cast<GotoMode
>(mode);
1837 QStringList astrometryDataDirs = KSUtils::getAstrometryDataDirs();
1840 m_UsedScale =
false;
1841 m_UsedPosition =
false;
1846 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1848 if(Options::solverType() != SSolver::SOLVER_ASTAP
1849 && Options::solverType() != SSolver::SOLVER_WATNEYASTROMETRY)
1851 bool foundAnIndex =
false;
1852 for(
auto &dataDir : astrometryDataDirs)
1859 if(indexList.
count() > 0)
1860 foundAnIndex =
true;
1866 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."));
1868 if(alignSettings && m_IndexFilesPage)
1870 alignSettings->setCurrentPage(m_IndexFilesPage);
1871 alignSettings->show();
1875 if (m_StellarSolver->isRunning())
1876 m_StellarSolver->abort();
1878 m_ImageData = m_AlignView->imageData();
1879 m_StellarSolver->loadNewImageBuffer(m_ImageData->getStatistics(), m_ImageData->getImageBuffer());
1880 m_StellarSolver->setProperty(
"ProcessType", SSolver::SOLVE);
1881 m_StellarSolver->setProperty(
"ExtractorType", Options::solveSextractorType());
1882 m_StellarSolver->setProperty(
"SolverType", Options::solverType());
1883 connect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
1884 m_StellarSolver->setIndexFolderPaths(Options::astrometryIndexFolderList());
1886 SSolver::Parameters params;
1891 params = m_StellarSolverProfiles.at(Options::solveOptionsProfile());
1893 catch (std::out_of_range
const &)
1895 params = m_StellarSolverProfiles[0];
1898 params.partition = Options::stellarSolverPartition();
1899 m_StellarSolver->setParameters(params);
1901 const SSolver::SolverType type =
static_cast<SSolver::SolverType
>(m_StellarSolver->property(
"SolverType").toInt());
1902 if(type == SSolver::SOLVER_LOCALASTROMETRY || type == SSolver::SOLVER_ASTAP || type == SSolver::SOLVER_WATNEYASTROMETRY)
1906 m_AlignView->saveImage(filename);
1907 m_StellarSolver->setProperty(
"FileToProcess", filename);
1908 ExternalProgramPaths externalPaths;
1909 externalPaths.sextractorBinaryPath = Options::sextractorBinary();
1910 externalPaths.solverPath = Options::astrometrySolverBinary();
1911 externalPaths.astapBinaryPath = Options::aSTAPExecutable();
1912 externalPaths.watneyBinaryPath = Options::watneyBinary();
1913 externalPaths.wcsPath = Options::astrometryWCSInfo();
1914 m_StellarSolver->setExternalFilePaths(externalPaths);
1917 m_StellarSolver->setProperty(
"AutoGenerateAstroConfig",
true);
1920 if(type == SSolver::SOLVER_ONLINEASTROMETRY )
1924 m_AlignView->saveImage(filename);
1926 m_StellarSolver->setProperty(
"FileToProcess", filename);
1927 m_StellarSolver->setProperty(
"AstrometryAPIKey", Options::astrometryAPIKey());
1928 m_StellarSolver->setProperty(
"AstrometryAPIURL", Options::astrometryAPIURL());
1931 bool useImageScale = Options::astrometryUseImageScale();
1932 if (useBlindScale == BLIND_ENGAGNED)
1934 useImageScale =
false;
1935 useBlindScale = BLIND_USED;
1936 appendLogText(
i18n(
"Solving with blind image scale..."));
1939 bool useImagePosition = Options::astrometryUsePosition();
1940 if (useBlindPosition == BLIND_ENGAGNED)
1942 useImagePosition =
false;
1943 useBlindPosition = BLIND_USED;
1944 appendLogText(
i18n(
"Solving with blind image position..."));
1947 if (m_SolveFromFile)
1949 FITSImage::Solution solution;
1950 m_ImageData->parseSolution(solution);
1952 if (useImageScale && solution.pixscale > 0)
1955 m_ScaleUsed = solution.pixscale;
1956 m_StellarSolver->setSearchScale(solution.pixscale * 0.8,
1957 solution.pixscale * 1.2,
1958 SSolver::ARCSEC_PER_PIX);
1961 m_StellarSolver->setProperty(
"UseScale",
false);
1963 if (useImagePosition && solution.ra > 0)
1965 m_UsedPosition =
true;
1966 m_RAUsed = solution.ra;
1967 m_DECUsed = solution.dec;
1968 m_StellarSolver->setSearchPositionInDegrees(solution.ra, solution.dec);
1971 m_StellarSolver->setProperty(
"UsePosition",
false);
1974 if (!m_ImageData->getRecordValue(
"PIERSIDE", value))
1976 appendLogText(
i18n(
"Loaded image does not have pierside information"));
1977 m_TargetPierside = ISD::Mount::PIER_UNKNOWN;
1981 appendLogText(
i18n(
"Loaded image was taken on pierside %1", value.
toString()));
1982 (value ==
"WEST") ? m_TargetPierside = ISD::Mount::PIER_WEST : m_TargetPierside = ISD::Mount::PIER_EAST;
1984 RotatorUtils::Instance()->Instance()->setImagePierside(m_TargetPierside);
1992 m_ScaleUsed = Options::astrometryImageScaleLow();
1994 SSolver::ScaleUnits units =
static_cast<SSolver::ScaleUnits
>(Options::astrometryImageScaleUnits());
1996 m_StellarSolver->setSearchScale(Options::astrometryImageScaleLow() * 0.8,
1997 Options::astrometryImageScaleHigh() * 1.2,
2001 m_StellarSolver->setProperty(
"UseScale",
false);
2003 if(useImagePosition)
2005 m_StellarSolver->setSearchPositionInDegrees(m_TelescopeCoord.ra().Degrees(), m_TelescopeCoord.dec().Degrees());
2006 m_UsedPosition =
true;
2007 m_RAUsed = m_TelescopeCoord.ra().Degrees();
2008 m_DECUsed = m_TelescopeCoord.dec().Degrees();
2011 m_StellarSolver->setProperty(
"UsePosition",
false);
2014 if(Options::alignmentLogging())
2019 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
2020 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
2021 if(Options::astrometryLogToFile())
2023 m_StellarSolver->setProperty(
"LogToFile",
true);
2024 m_StellarSolver->setProperty(
"LogFileName", Options::astrometryLogFilepath());
2029 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
2030 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
2033 SolverUtils::patchMultiAlgorithm(m_StellarSolver.get());
2036 m_StellarSolver->start();
2040 if (m_ImageData.isNull())
2041 m_ImageData = m_AlignView->imageData();
2044 remoteParser->startSolver(m_ImageData->filename(),
generateRemoteArgs(m_ImageData),
false);
2049 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
2050 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
2051 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
2052 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
2053 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
2054 matchPAHStage(PAA::PAH_THIRD_SOLVE) ||
2055 nothingR->isChecked() ||
2060 solverTimer.start();
2063 emit newStatus(state);
2066void Align::solverComplete()
2068 disconnect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
2069 if(!m_StellarSolver->solvingDone() || m_StellarSolver->failed())
2071 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
2072 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
2073 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
2074 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
2075 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
2076 matchPAHStage(PAA::PAH_THIRD_SOLVE))
2078 if (CHECK_PAH(processSolverFailure()))
2088 FITSImage::Solution solution = m_StellarSolver->getSolution();
2089 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false :
true;
2090 solverFinished(solution.orientation, solution.ra, solution.dec, solution.pixscale, eastToTheRight);
2096 pi->stopAnimation();
2097 stopB->setEnabled(
false);
2098 solveB->setEnabled(
true);
2100 sOrientation = orientation;
2104 double elapsed = solverTimer.elapsed() / 1000.0;
2106 appendLogText(
i18n(
"Solver completed after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2108 m_AlignTimer.stop();
2109 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && m_RemoteParserDevice && remoteParser.get())
2116 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2117 targetChip->getBinning(&binx, &biny);
2119 if (Options::alignmentLogging())
2121 QString parityString = eastToTheRight ?
"neg" :
"pos";
2122 appendLogText(
i18n(
"Solver RA (%1) DEC (%2) Orientation (%3) Pixel Scale (%4) Parity (%5)",
QString::number(ra,
'f', 5),
2128 if (!m_SolveFromFile &&
2129 (isEqual(m_FOVWidth, 0) || m_EffectiveFOVPending || std::fabs(pixscale - m_FOVPixelScale) > 0.005) &&
2132 double newFOVW = m_CameraWidth * pixscale / binx / 60.0;
2133 double newFOVH = m_CameraHeight * pixscale / biny / 60.0;
2135 calculateEffectiveFocalLength(newFOVW);
2136 saveNewEffectiveFOV(newFOVW, newFOVH);
2138 m_EffectiveFOVPending =
false;
2141 m_AlignCoord.setRA0(ra / 15.0);
2142 m_AlignCoord.setDec0(dec);
2145 m_AlignCoord.apparentCoord(
static_cast<long double>(J2000),
KStars::Instance()->data()->ut().djd());
2147 m_AlignCoord.EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
2150 if (!m_SolveFromFile)
2153 calculateAlignTargetDiff();
2159 double solverPA = KSUtils::rotationToPositionAngle(orientation);
2160 solverFOV->setCenter(m_AlignCoord);
2161 solverFOV->setPA(solverPA);
2162 solverFOV->setImageDisplay(Options::astrometrySolverOverlay());
2164 sensorFOV->setPA(solverPA);
2169 getFormattedCoords(m_AlignCoord.ra().Hours(), m_AlignCoord.dec().Degrees(), ra_dms, dec_dms);
2171 SolverRAOut->setText(ra_dms);
2172 SolverDecOut->setText(dec_dms);
2174 if (Options::astrometrySolverWCS())
2176 auto ccdRotation = m_Camera->getNumber(
"CCD_ROTATION");
2179 auto rotation = ccdRotation->findWidgetByName(
"CCD_ROTATION_VALUE");
2182 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
2183 rotation->setValue(orientation);
2184 clientManager->sendNewProperty(ccdRotation);
2186 if (m_wcsSynced ==
false)
2189 i18n(
"WCS information updated. Images captured from this point forward shall have valid WCS."));
2192 auto telescopeInfo = m_Mount->getNumber(
"TELESCOPE_INFO");
2194 clientManager->sendNewProperty(telescopeInfo);
2202 m_CaptureErrorCounter = 0;
2203 m_SlewErrorCounter = 0;
2204 m_CaptureTimeoutCounter = 0;
2207 i18n(
"Solution coordinates: RA (%1) DEC (%2) Telescope Coordinates: RA (%3) DEC (%4) Target Coordinates: RA (%5) DEC (%6)",
2208 m_AlignCoord.ra().toHMSString(),
2209 m_AlignCoord.dec().toDMSString(),
2210 m_TelescopeCoord.ra().toHMSString(),
2211 m_TelescopeCoord.dec().toDMSString(),
2212 m_TargetCoord.ra().toHMSString(),
2213 m_TargetCoord.dec().toDMSString()));
2215 if (!m_SolveFromFile && m_CurrentGotoMode == GOTO_SLEW)
2217 dms diffDeg(m_TargetDiffTotal / 3600.0);
2218 appendLogText(
i18n(
"Target is within %1 degrees of solution coordinates.", diffDeg.
toDMSString()));
2221 if (rememberUploadMode != m_Camera->getUploadMode())
2222 m_Camera->setUploadMode(rememberUploadMode);
2225 if (m_RememberCameraFastExposure)
2227 m_RememberCameraFastExposure =
false;
2228 m_Camera->setFastExposureEnabled(
true);
2233 int currentRow = solutionTable->rowCount() - 1;
2234 if (!m_SolveFromFile)
2236 stopProgressAnimation();
2237 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
2240 if (m_Rotator !=
nullptr && m_Rotator->isConnected())
2242 if (
auto absAngle = m_Rotator->getNumber(
"ABS_ROTATOR_ANGLE"))
2245 sRawAngle = absAngle->at(0)->getValue();
2246 double OffsetAngle = RotatorUtils::Instance()->calcOffsetAngle(sRawAngle, solverPA);
2247 RotatorUtils::Instance()->updateOffset(OffsetAngle);
2249 auto reverseStatus =
"Unknown";
2250 auto reverseProperty = m_Rotator->getSwitch(
"ROTATOR_REVERSE");
2251 if (reverseProperty)
2253 if (reverseProperty->at(0)->getState() == ISS_ON)
2254 reverseStatus =
"Reversed Direction";
2256 reverseStatus =
"Normal Direction";
2258 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << sRawAngle <<
"Rotator PA:" << solverPA
2259 <<
"Rotator Offset:" << OffsetAngle <<
"Direction:" << reverseStatus;
2261 emit newSolverResults(solverPA, ra, dec, pixscale);
2263 appendLogText(
i18n(
"Camera position angle is %1 degrees.", RotatorUtils::Instance()->calcCameraAngle(sRawAngle,
false)));
2270 {
"camera", m_Camera->getDeviceName()},
2271 {
"ra", SolverRAOut->text()},
2272 {
"de", SolverDecOut->text()},
2273 {
"dRA", m_TargetDiffRA},
2274 {
"dDE", m_TargetDiffDE},
2275 {
"targetDiff", m_TargetDiffTotal},
2278 {
"fov", FOVOut->text()},
2283 emit newStatus(state);
2284 solverIterations = 0;
2285 KSNotification::event(
QLatin1String(
"AlignSuccessful"),
i18n(
"Astrometry alignment completed successfully"),
2286 KSNotification::Align);
2288 switch (m_CurrentGotoMode)
2293 if (!m_SolveFromFile)
2295 stopProgressAnimation();
2296 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2297 solutionTable->setItem(currentRow, 3, statusReport.release());
2303 if (m_SolveFromFile || m_TargetDiffTotal >
static_cast<double>(alignAccuracyThreshold->value()))
2305 if (!m_SolveFromFile && ++solverIterations == MAXIMUM_SOLVER_ITERATIONS)
2307 appendLogText(
i18n(
"Maximum number of iterations reached. Solver failed."));
2309 if (!m_SolveFromFile)
2311 statusReport->setIcon(
QIcon(
":/icons/AlignFailure.svg"));
2312 solutionTable->setItem(currentRow, 3, statusReport.release());
2319 targetAccuracyNotMet =
true;
2321 if (!m_SolveFromFile)
2323 stopProgressAnimation();
2324 statusReport->setIcon(
QIcon(
":/icons/AlignWarning.svg"));
2325 solutionTable->setItem(currentRow, 3, statusReport.release());
2332 stopProgressAnimation();
2333 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2334 solutionTable->setItem(currentRow, 3, statusReport.release());
2336 appendLogText(
i18n(
"Target is within acceptable range."));
2340 if (!m_SolveFromFile)
2342 stopProgressAnimation();
2343 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2344 solutionTable->setItem(currentRow, 3, statusReport.release());
2349 solverFOV->setProperty(
"visible",
true);
2351 if (!matchPAHStage(PAA::PAH_IDLE))
2352 m_PolarAlignmentAssistant->processPAHStage(orientation, ra, dec, pixscale, eastToTheRight,
2353 m_StellarSolver->getSolutionHealpix(),
2354 m_StellarSolver->getSolutionIndexNumber());
2360 solveB->setEnabled(
false);
2361 loadSlewB->setEnabled(
false);
2367 emit newStatus(state);
2369 solveB->setEnabled(
true);
2370 loadSlewB->setEnabled(
true);
2378 if (Options::saveFailedAlignImages())
2381 QDateTime now = KStarsData::Instance()->lt();
2386 extraFilenameInfo.
append(
QString(
"_s%1u%2").arg(m_ScaleUsed, 0,
'f', 3)
2387 .arg(Options::astrometryImageScaleUnits()));
2389 extraFilenameInfo.
append(
QString(
"_r%1_d%2").arg(m_RAUsed, 0,
'f', 5).arg(m_DECUsed, 0,
'f', 5));
2393 QString name =
"failed_align_frame_" + now.
toString(
"yyyy-MM-dd-HH-mm-ss") + extraFilenameInfo +
".fits";
2394 QString filename = path + QStringLiteral(
"/") + name;
2397 m_ImageData->saveImage(filename);
2398 appendLogText(
i18n(
"Saving failed solver image to %1", filename));
2405 if (Options::astrometryUseImageScale() && useBlindScale == BLIND_IDLE)
2407 appendLogText(
i18n(
"Solver failed. Retrying without scale constraint."));
2408 useBlindScale = BLIND_ENGAGNED;
2409 setAlignTableResult(ALIGN_RESULT_FAILED);
2415 if (Options::astrometryUsePosition() && useBlindPosition == BLIND_IDLE)
2417 appendLogText(
i18n(
"Solver failed. Retrying without position constraint."));
2418 useBlindPosition = BLIND_ENGAGNED;
2419 setAlignTableResult(ALIGN_RESULT_FAILED);
2425 appendLogText(
i18n(
"Solver Failed."));
2426 if(!Options::alignmentLogging())
2428 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."));
2430 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"),
2431 KSNotification::Align, KSNotification::Alert);
2434 pi->stopAnimation();
2435 stopB->setEnabled(
false);
2436 solveB->setEnabled(
true);
2437 loadSlewB->setEnabled(
true);
2439 m_AlignTimer.stop();
2441 m_SolveFromFile =
false;
2442 solverIterations = 0;
2443 m_CaptureErrorCounter = 0;
2444 m_CaptureTimeoutCounter = 0;
2445 m_SlewErrorCounter = 0;
2448 emit newStatus(state);
2450 solverFOV->setProperty(
"visible",
false);
2452 setAlignTableResult(ALIGN_RESULT_FAILED);
2458 if (Options::astrometryUseRotator())
2460 if (m_SolveFromFile)
2462 m_TargetPositionAngle = solverFOV->PA();
2464 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file: Setting target PA to:" << m_TargetPositionAngle;
2468 currentRotatorPA = solverFOV->PA();
2469 if (std::isnan(m_TargetPositionAngle) ==
false)
2472 if (RotatorUtils::Instance()->Instance()->checkImageFlip() && (Options::astrometryFlipRotationAllowed()))
2475 sRawAngle = RotatorUtils::Instance()->calcRotatorAngle(m_TargetPositionAngle);
2476 m_TargetPositionAngle = RotatorUtils::Instance()->calcCameraAngle(sRawAngle,
true);
2477 RotatorUtils::Instance()->setImagePierside(ISD::Mount::PIER_UNKNOWN);
2480 if (m_Rotator !=
nullptr && m_Rotator->isConnected())
2482 if(fabs(KSUtils::rangePA(currentRotatorPA - m_TargetPositionAngle)) * 60 >
2483 Options::astrometryRotatorThreshold())
2486 emit newSolverResults(m_TargetPositionAngle, 0, 0, 0);
2487 appendLogText(
i18n(
"Setting camera position angle to %1 degrees ...", m_TargetPositionAngle));
2489 emit newStatus(state);
2494 appendLogText(
i18n(
"Camera position angle is within acceptable range."));
2496 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
2502 double current = currentRotatorPA;
2503 double target = m_TargetPositionAngle;
2505 double diff = KSUtils::rangePA(current - target);
2506 double threshold = Options::astrometryRotatorThreshold() / 60.0;
2508 appendLogText(
i18n(
"Current PA is %1; Target PA is %2; diff: %3", current, target, diff));
2510 emit manualRotatorChanged(current, target, threshold);
2512 m_ManualRotator->setRotatorDiff(current, target, diff);
2513 if (fabs(diff) > threshold)
2515 targetAccuracyNotMet =
true;
2516 m_ManualRotator->show();
2517 m_ManualRotator->raise();
2519 emit newStatus(state);
2524 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
2525 targetAccuracyNotMet =
false;
2536 m_CaptureTimer.stop();
2537 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
2538 m_StellarSolver->abort();
2539 else if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser)
2540 remoteParser->stopSolver();
2542 pi->stopAnimation();
2543 stopB->setEnabled(
false);
2544 solveB->setEnabled(
true);
2545 loadSlewB->setEnabled(
true);
2547 m_SolveFromFile =
false;
2548 solverIterations = 0;
2549 m_CaptureErrorCounter = 0;
2550 m_CaptureTimeoutCounter = 0;
2551 m_SlewErrorCounter = 0;
2552 m_AlignTimer.stop();
2557 if (rememberUploadMode != m_Camera->getUploadMode())
2558 m_Camera->setUploadMode(rememberUploadMode);
2561 if (m_RememberCameraFastExposure)
2563 m_RememberCameraFastExposure =
false;
2564 m_Camera->setFastExposureEnabled(
true);
2567 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2570 if (matchPAHStage(PAA::PAH_POST_REFRESH))
2572 if (targetChip->isCapturing())
2573 targetChip->abortExposure();
2575 appendLogText(
i18n(
"Refresh is complete."));
2579 if (targetChip->isCapturing())
2581 targetChip->abortExposure();
2582 appendLogText(
i18n(
"Capture aborted."));
2586 double elapsed = solverTimer.elapsed() / 1000.0;
2588 appendLogText(
i18n(
"Solver aborted after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2593 emit newStatus(state);
2595 setAlignTableResult(ALIGN_RESULT_FAILED);
2600 int currentRow = solutionTable->rowCount() - 1;
2607 QWidget *indicator = solutionTable->cellWidget(currentRow, 3);
2608 if (indicator ==
nullptr)
2613void Align::stopProgressAnimation()
2616 if (progress_indicator !=
nullptr)
2624 result << sOrientation << sRA << sDEC;
2629void Align::appendLogText(
const QString &text)
2631 m_LogText.
insert(0,
i18nc(
"log entry; %1 is the date, %2 is the text",
"%1 %2",
2632 KStarsData::Instance()->lt().toString(
"yyyy-MM-ddThh:mm:ss"), text));
2634 qCInfo(KSTARS_EKOS_ALIGN) << text;
2639void Align::clearLog()
2647 if (prop.isNameMatch(
"EQUATORIAL_EOD_COORD") || prop.isNameMatch(
"EQUATORIAL_COORD"))
2649 auto nvp = prop.getNumber();
2652 getFormattedCoords(m_TelescopeCoord.ra().Hours(), m_TelescopeCoord.dec().Degrees(), ra_dms, dec_dms);
2654 ScopeRAOut->setText(ra_dms);
2655 ScopeDecOut->setText(dec_dms);
2661 m_wasSlewStarted =
false;
2670 if (m_wasSlewStarted && Options::astrometryAutoUpdatePosition())
2673 opsAstrometry->estRA->setText(ra_dms);
2674 opsAstrometry->estDec->setText(dec_dms);
2676 Options::setAstrometryPositionRA(nvp->np[0].value * 15);
2677 Options::setAstrometryPositionDE(nvp->np[1].value);
2683 if (m_wasSlewStarted && matchPAHStage(PAA::PAH_FIND_CP))
2686 m_wasSlewStarted =
false;
2687 appendLogText(
i18n(
"Mount completed slewing near celestial pole. Capture again to verify."));
2690 m_PolarAlignmentAssistant->setPAHStage(PAA::PAH_FIRST_CAPTURE);
2701 m_wasSlewStarted =
false;
2703 if (m_CurrentGotoMode == GOTO_SLEW)
2710 appendLogText(
i18n(
"Mount is synced to solution coordinates."));
2716 i18n(
"Astrometry alignment completed successfully"), KSNotification::Align);
2718 emit newStatus(state);
2719 solverIterations = 0;
2720 loadSlewB->setEnabled(
true);
2727 if (!didSlewStart())
2730 qCDebug(KSTARS_EKOS_ALIGN) <<
"Mount slew planned, but not started slewing yet...";
2735 m_wasSlewStarted =
false;
2736 if (m_SolveFromFile)
2738 m_SolveFromFile =
false;
2739 m_TargetPositionAngle = solverFOV->PA();
2740 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file: Setting target PA to" << m_TargetPositionAngle;
2743 emit newStatus(state);
2745 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
2746 appendLogText(
i18n(
"Settling..."));
2747 m_resetCaptureTimeoutCounter =
true;
2748 m_CaptureTimer.start(alignSettlingTime->value());
2751 else if (m_CurrentGotoMode == GOTO_SLEW || (m_MountModel && m_MountModel->isRunning()))
2753 if (targetAccuracyNotMet)
2754 appendLogText(
i18n(
"Slew complete. Target accuracy is not met, running solver again..."));
2756 appendLogText(
i18n(
"Slew complete. Solving Alignment Point. . ."));
2758 targetAccuracyNotMet =
false;
2761 emit newStatus(state);
2763 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
2764 appendLogText(
i18n(
"Settling..."));
2765 m_resetCaptureTimeoutCounter =
true;
2766 m_CaptureTimer.start(alignSettlingTime->value());
2773 m_wasSlewStarted =
false;
2784 m_wasSlewStarted =
true;
2786 handleMountMotion();
2794 m_wasSlewStarted =
false;
2799 appendLogText(
i18n(
"Syncing failed."));
2801 appendLogText(
i18n(
"Slewing failed."));
2803 if (++m_SlewErrorCounter == 3)
2810 if (m_CurrentGotoMode == GOTO_SLEW)
2821 RUN_PAH(processMountRotation(m_TelescopeCoord.ra(), alignSettlingTime->value()));
2823 else if (prop.isNameMatch(
"ABS_ROTATOR_ANGLE"))
2825 auto nvp = prop.getNumber();
2826 double RAngle = nvp->np[0].value;
2827 currentRotatorPA = RotatorUtils::Instance()->calcCameraAngle(RAngle,
false);
2836 if (std::isnan(m_TargetPositionAngle) ==
false && state ==
ALIGN_ROTATING && nvp->s == IPS_OK)
2838 auto diff = fabs(RotatorUtils::Instance()->DiffPA(currentRotatorPA - m_TargetPositionAngle)) * 60;
2839 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << RAngle <<
"Current PA:" << currentRotatorPA
2840 <<
"Target PA:" << m_TargetPositionAngle <<
"Diff (arcmin):" << diff <<
"Offset:"
2841 << Options::pAOffset();
2843 if (diff <= Options::astrometryRotatorThreshold())
2845 appendLogText(
i18n(
"Rotator reached camera position angle."));
2856 if (nvp->s == IPS_OK)
2858 appendLogText(
i18n(
"Rotator failed to arrive at the requested position angle (Deviation %1 arcmin).", diff));
2860 emit newStatus(state);
2861 solveB->setEnabled(
true);
2862 loadSlewB->setEnabled(
true);
2867 else if (m_estimateRotatorTimeFrame)
2869 m_RotatorTimeFrame = RotatorUtils::Instance()->calcTimeFrame(RAngle);
2872 else if (prop.isNameMatch(
"TELESCOPE_MOTION_NS") || prop.isNameMatch(
"TELESCOPE_MOTION_WE"))
2874 switch (prop.getState())
2878 handleMountMotion();
2879 m_wasSlewStarted =
true;
2882 qCDebug(KSTARS_EKOS_ALIGN) <<
"Mount motion finished.";
2883 handleMountStatus();
2889void Align::handleMountMotion()
2893 if (matchPAHStage(PAA::PAH_IDLE))
2896 appendLogText(
i18n(
"Slew detected, suspend solving..."));
2904void Align::handleMountStatus()
2906 auto nvp = m_Mount->getNumber(m_Mount->isJ2000() ?
"EQUATORIAL_COORD" :
2907 "EQUATORIAL_EOD_COORD");
2916 if (m_SolveFromFile)
2921 m_DestinationCoord = m_AlignCoord;
2922 m_TargetCoord = m_AlignCoord;
2924 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file. Setting Target Coordinates align coords. RA:"
2925 << m_TargetCoord.ra().toHMSString()
2926 <<
"DE:" << m_TargetCoord.dec().toDMSString();
2930 else if (m_CurrentGotoMode == GOTO_SYNC)
2932 else if (m_CurrentGotoMode == GOTO_SLEW)
2940 if (m_Mount->Sync(&m_AlignCoord))
2942 emit newStatus(state);
2944 i18n(
"Syncing to RA (%1) DEC (%2)", m_AlignCoord.ra().toHMSString(), m_AlignCoord.dec().toDMSString()));
2949 emit newStatus(state);
2950 appendLogText(
i18n(
"Syncing failed."));
2957 emit newStatus(state);
2965 if (m_Mount->Slew(&m_TargetCoord))
2967 slewStartTimer.start();
2968 appendLogText(
i18n(
"Slewing to target coordinates: RA (%1) DEC (%2).", m_TargetCoord.ra().toHMSString(),
2969 m_TargetCoord.dec().toDMSString()));
2973 appendLogText(
i18n(
"Slewing to target coordinates: RA (%1) DEC (%2) is rejected. (see notification)",
2974 m_TargetCoord.ra().toHMSString(),
2975 m_TargetCoord.dec().toDMSString()));
2977 emit newStatus(state);
2978 solveB->setEnabled(
true);
2979 loadSlewB->setEnabled(
true);
2985 if (canSync && !m_SolveFromFile)
2989 if (Ekos::Manager::Instance()->getCurrentJobName().isEmpty())
2991 KSNotification::event(
QLatin1String(
"EkosSchedulerTelescopeSynced"),
2992 i18n(
"Ekos job (%1) - Telescope synced",
2993 Ekos::Manager::Instance()->getCurrentJobName()));
2997 if (Options::astrometryDifferentialSlewing())
3001 m_TargetCoord.setRA(m_TargetCoord.ra() - m_AlignCoord.ra().deltaAngle(m_DestinationCoord.ra()));
3002 m_TargetCoord.setDec(m_TargetCoord.dec() - m_AlignCoord.dec().deltaAngle(m_DestinationCoord.dec()));
3003 qCDebug(KSTARS_EKOS_ALIGN) <<
"Differential slew - Target RA:" << m_TargetCoord.ra().toHMSString()
3004 <<
" DE:" << m_TargetCoord.dec().toDMSString();
3016void Align::getFormattedCoords(
double ra,
double dec,
QString &ra_str,
QString &dec_str)
3027 dec_str =
QString(
"-%1:%2:%3")
3042 "Images (*.fits *.fits.fz *.fit *.fts *.xisf "
3043 "*.jpg *.jpeg *.png *.gif *.bmp "
3044 "*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw *.orf)");
3050 if (fileInfo.
exists() ==
false)
3055 RotatorGOTO =
false;
3057 m_SolveFromFile =
true;
3059 if (m_PolarAlignmentAssistant)
3060 m_PolarAlignmentAssistant->stopPAHProcess();
3062 slewR->setChecked(
true);
3063 m_CurrentGotoMode = GOTO_SLEW;
3065 solveB->setEnabled(
false);
3066 loadSlewB->setEnabled(
false);
3067 stopB->setEnabled(
true);
3068 pi->startAnimation();
3070 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && m_RemoteParserDevice ==
nullptr)
3072 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
3076 m_ImageData.
clear();
3078 m_AlignView->loadFile(fileURL);
3087 RotatorGOTO =
false;
3088 m_SolveFromFile =
true;
3089 RUN_PAH(stopPAHProcess());
3090 slewR->setChecked(
true);
3091 m_CurrentGotoMode = GOTO_SLEW;
3092 solveB->setEnabled(
false);
3093 loadSlewB->setEnabled(
false);
3094 stopB->setEnabled(
true);
3095 pi->startAnimation();
3099 m_ImageData.clear();
3102 data->setExtension(extension);
3103 data->loadFromBuffer(image);
3104 m_AlignView->loadData(data);
3111 alignExposure->setValue(value);
3119 alignBinning->blockSignals(
true);
3120 alignBinning->setCurrentIndex(binIndex);
3121 alignBinning->blockSignals(
false);
3125 if (Options::astrometryImageScaleUnits() == SSolver::ARCSEC_PER_PIX)
3131 if (m_FilterWheel && m_FilterWheel == device)
3138 m_FilterWheel->disconnect(
this);
3140 m_FilterWheel = device;
3144 connect(m_FilterWheel, &ISD::ConcreteDevice::Connected,
this, [
this]()
3146 FilterPosLabel->setEnabled(
true);
3147 alignFilter->setEnabled(
true);
3149 connect(m_FilterWheel, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
3151 FilterPosLabel->setEnabled(
false);
3152 alignFilter->setEnabled(
false);
3156 auto isConnected = m_FilterWheel && m_FilterWheel->isConnected();
3157 FilterPosLabel->setEnabled(isConnected);
3158 alignFilter->setEnabled(isConnected);
3167 return m_FilterWheel->getDeviceName();
3176 alignFilter->setCurrentText(filter);
3186 return alignFilter->currentText();
3191 alignFilter->
clear();
3195 FilterPosLabel->setEnabled(
false);
3196 alignFilter->setEnabled(
false);
3200 auto isConnected = m_FilterWheel->isConnected();
3201 FilterPosLabel->setEnabled(isConnected);
3202 alignFilter->setEnabled(alignUseCurrentFilter->isChecked() ==
false);
3204 setupFilterManager();
3206 alignFilter->addItems(m_FilterManager->getFilterLabels());
3207 currentFilterPosition = m_FilterManager->getFilterPosition();
3209 if (alignUseCurrentFilter->isChecked())
3212 alignFilter->setCurrentIndex(currentFilterPosition - 1);
3217 auto filter = m_Settings[
"alignFilter"];
3218 if (filter.isValid())
3219 alignFilter->setCurrentText(filter.toString());
3225 if ((Manager::Instance()->existRotatorController()) && (!m_Rotator || !(Device == m_Rotator)))
3227 rotatorB->setEnabled(
false);
3230 m_Rotator->disconnect(
this);
3231 m_RotatorControlPanel->close();
3236 if (Manager::Instance()->getRotatorController(m_Rotator->getDeviceName(), m_RotatorControlPanel))
3241 m_RotatorControlPanel->show();
3242 m_RotatorControlPanel->raise();
3244 rotatorB->setEnabled(
true);
3250void Align::setWCSEnabled(
bool enable)
3255 auto wcsControl = m_Camera->
getSwitch(
"WCS_CONTROL");
3260 auto wcs_enable = wcsControl->findWidgetByName(
"WCS_ENABLE");
3261 auto wcs_disable = wcsControl->findWidgetByName(
"WCS_DISABLE");
3263 if (!wcs_enable || !wcs_disable)
3266 if ((wcs_enable->getState() == ISS_ON && enable) || (wcs_disable->getState() == ISS_ON && !enable))
3269 wcsControl->reset();
3272 appendLogText(
i18n(
"World Coordinate System (WCS) is enabled."));
3273 wcs_enable->setState(ISS_ON);
3277 appendLogText(
i18n(
"World Coordinate System (WCS) is disabled."));
3278 wcs_disable->setState(ISS_ON);
3279 m_wcsSynced =
false;
3282 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
3284 clientManager->sendNewProperty(wcsControl);
3289 INDI_UNUSED(targetChip);
3290 INDI_UNUSED(remaining);
3292 if (state == IPS_ALERT)
3294 if (++m_CaptureErrorCounter == 3 && !matchPAHStage(PolarAlignmentAssistant::PAH_REFRESH))
3296 appendLogText(
i18n(
"Capture error. Aborting..."));
3301 appendLogText(
i18n(
"Restarting capture attempt #%1", m_CaptureErrorCounter));
3302 setAlignTableResult(ALIGN_RESULT_FAILED);
3307void Align::setAlignTableResult(AlignResult result)
3314 if (progress_indicator ==
nullptr || ! progress_indicator->
isAnimated())
3316 stopProgressAnimation();
3321 case ALIGN_RESULT_SUCCESS:
3322 icon =
QIcon(
":/icons/AlignSuccess.svg");
3325 case ALIGN_RESULT_WARNING:
3326 icon =
QIcon(
":/icons/AlignWarning.svg");
3329 case ALIGN_RESULT_FAILED:
3331 icon =
QIcon(
":/icons/AlignFailure.svg");
3334 int currentRow = solutionTable->rowCount() - 1;
3335 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
3339 solutionTable->setItem(currentRow, 3, statusReport);
3342void Align::setFocusStatus(Ekos::FocusState state)
3344 m_FocusState = state;
3347uint8_t Align::getSolverDownsample(uint16_t binnedW)
3349 uint8_t downsample = Options::astrometryDownsample();
3351 if (!Options::astrometryAutoDownsample())
3354 while (downsample < 8)
3356 if (binnedW / downsample <= 1024)
3370 if (m_Mount && m_Mount->hasAlignmentModel() && Options::resetMountModelAfterMeridian())
3372 qCDebug(KSTARS_EKOS_ALIGN) <<
"Post meridian flip mount model reset" << (m_Mount->clearAlignmentModel() ?
3373 "successful." :
"failed.");
3375 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
3376 appendLogText(
i18n(
"Settling..."));
3377 m_resetCaptureTimeoutCounter =
true;
3378 m_CaptureTimer.start(alignSettlingTime->value());
3384 if (std::isnan(m_TargetPositionAngle) ==
false)
3385 m_TargetPositionAngle = KSUtils::rangePA(m_TargetPositionAngle + 180.0);
3391 m_CaptureState = newState;
3394void Align::showFITSViewer()
3396 static int lastFVTabID = -1;
3403 fv->loadData(m_ImageData, url, &lastFVTabID);
3404 connect(fv.get(), &FITSViewer::terminated,
this, [
this]()
3409 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) ==
false)
3410 fv->loadData(m_ImageData, url, &lastFVTabID);
3416void Align::toggleAlignWidgetFullScreen()
3418 if (alignWidget->parent() ==
nullptr)
3420 alignWidget->setParent(
this);
3421 rightLayout->insertWidget(0, alignWidget);
3422 alignWidget->showNormal();
3426 alignWidget->setParent(
nullptr);
3427 alignWidget->setWindowTitle(
i18nc(
"@title:window",
"Align Frame"));
3429 alignWidget->showMaximized();
3430 alignWidget->show();
3434void Align::setMountStatus(ISD::Mount::Status newState)
3438 case ISD::Mount::MOUNT_PARKED:
3439 solveB->setEnabled(
false);
3440 loadSlewB->setEnabled(
false);
3442 case ISD::Mount::MOUNT_IDLE:
3443 solveB->setEnabled(
true);
3444 loadSlewB->setEnabled(
true);
3446 case ISD::Mount::MOUNT_PARKING:
3447 solveB->setEnabled(
false);
3448 loadSlewB->setEnabled(
false);
3450 case ISD::Mount::MOUNT_SLEWING:
3451 case ISD::Mount::MOUNT_MOVING:
3452 solveB->setEnabled(
false);
3453 loadSlewB->setEnabled(
false);
3459 solveB->setEnabled(
true);
3460 if (matchPAHStage(PAA::PAH_IDLE))
3462 loadSlewB->setEnabled(
true);
3468 RUN_PAH(setMountStatus(newState));
3473 m_RemoteParserDevice = device;
3475 remoteSolverR->setEnabled(
true);
3476 if (remoteParser.get() !=
nullptr)
3478 remoteParser->setAstrometryDevice(m_RemoteParserDevice);
3486 solverFOV->setImageDisplay(Options::astrometrySolverWCS());
3487 m_AlignTimer.setInterval(Options::astrometryTimeout() * 1000);
3489 m_RotatorControlPanel->updateFlipPolicy(Options::astrometryFlipRotationAllowed());
3492void Align::setupOptions()
3500 opsAlign =
new OpsAlign(
this);
3506 opsPrograms =
new OpsPrograms(
this);
3507 page = dialog->
addPage(opsPrograms,
i18n(
"External & Online Programs"));
3510 opsAstrometry =
new OpsAstrometry(
this);
3511 page = dialog->
addPage(opsAstrometry,
i18n(
"Scale & Position"));
3512 page->
setIcon(
QIcon(
":/icons/center_telescope_red.svg"));
3514 optionsProfileEditor =
new StellarSolverProfileEditor(
this, Ekos::AlignProfiles, dialog);
3515 page = dialog->
addPage(optionsProfileEditor,
i18n(
"Align Options Profiles Editor"));
3516 connect(optionsProfileEditor, &StellarSolverProfileEditor::optionsProfilesUpdated,
this, [
this]()
3518 if(
QFile(savedOptionsProfiles).exists())
3519 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
3521 m_StellarSolverProfiles = getDefaultAlignOptionsProfiles();
3522 opsAlign->reloadOptionsProfiles();
3526 connect(opsAlign, &OpsAlign::needToLoadProfile,
this, [
this, dialog, page](
const QString & profile)
3528 optionsProfileEditor->loadProfile(profile);
3529 dialog->setCurrentPage(page);
3532 opsAstrometryIndexFiles =
new OpsAstrometryIndexFiles(
this);
3533 connect(opsAstrometryIndexFiles, &OpsAstrometryIndexFiles::newDownloadProgress,
this, &Align::newDownloadProgress);
3534 m_IndexFilesPage = dialog->
addPage(opsAstrometryIndexFiles,
i18n(
"Index Files"));
3538void Align::setupSolutionTable()
3542 clearAllSolutionsB->setIcon(
3549 exportSolutionsCSV->setIcon(
3564void Align::setupPlot()
3566 double accuracyRadius = alignAccuracyThreshold->value();
3568 alignPlot->setBackground(QBrush(
Qt::black));
3569 alignPlot->setSelectionTolerance(10);
3571 alignPlot->xAxis->setBasePen(QPen(
Qt::white, 1));
3572 alignPlot->yAxis->setBasePen(QPen(
Qt::white, 1));
3574 alignPlot->xAxis->setTickPen(QPen(
Qt::white, 1));
3575 alignPlot->yAxis->setTickPen(QPen(
Qt::white, 1));
3577 alignPlot->xAxis->setSubTickPen(QPen(
Qt::white, 1));
3578 alignPlot->yAxis->setSubTickPen(QPen(
Qt::white, 1));
3580 alignPlot->xAxis->setTickLabelColor(
Qt::white);
3581 alignPlot->yAxis->setTickLabelColor(
Qt::white);
3583 alignPlot->xAxis->setLabelColor(
Qt::white);
3584 alignPlot->yAxis->setLabelColor(
Qt::white);
3586 alignPlot->xAxis->setLabelFont(QFont(
font().family(), 10));
3587 alignPlot->yAxis->setLabelFont(QFont(
font().family(), 10));
3589 alignPlot->xAxis->setLabelPadding(2);
3590 alignPlot->yAxis->setLabelPadding(2);
3592 alignPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1,
Qt::DotLine));
3593 alignPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1,
Qt::DotLine));
3594 alignPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1,
Qt::DotLine));
3595 alignPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1,
Qt::DotLine));
3596 alignPlot->xAxis->grid()->setZeroLinePen(QPen(
Qt::yellow));
3597 alignPlot->yAxis->grid()->setZeroLinePen(QPen(
Qt::yellow));
3599 alignPlot->xAxis->setLabel(
i18n(
"dRA (arcsec)"));
3600 alignPlot->yAxis->setLabel(
i18n(
"dDE (arcsec)"));
3602 alignPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
3603 alignPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
3608 alignPlot->addGraph();
3618 alignPlot->resize(190, 190);
3619 alignPlot->replot();
3622void Align::setupFilterManager()
3625 if (m_FilterManager)
3626 m_FilterManager->disconnect(
this);
3629 Ekos::Manager::Instance()->createFilterManager(m_FilterWheel);
3632 Ekos::Manager::Instance()->getFilterManager(m_FilterWheel->getDeviceName(), m_FilterManager);
3634 connect(m_FilterManager.get(), &FilterManager::ready,
this, [
this]()
3636 if (filterPositionPending)
3638 m_FocusState = FOCUS_IDLE;
3639 filterPositionPending = false;
3640 captureAndSolve(false);
3644 connect(m_FilterManager.get(), &FilterManager::failed,
this, [
this]()
3646 if (filterPositionPending)
3648 appendLogText(i18n(
"Filter operation failed."));
3653 connect(m_FilterManager.get(), &FilterManager::newStatus,
this, [
this](Ekos::FilterState filterState)
3655 if (filterPositionPending)
3657 switch (filterState)
3660 appendLogText(i18n(
"Changing focus offset by %1 steps...", m_FilterManager->getTargetFilterOffset()));
3665 const int filterComboIndex = m_FilterManager->getTargetFilterPosition() - 1;
3666 if (filterComboIndex >= 0 && filterComboIndex < alignFilter->count())
3667 appendLogText(i18n(
"Changing filter to %1...", alignFilter->itemText(filterComboIndex)));
3671 case FILTER_AUTOFOCUS:
3672 appendLogText(i18n(
"Auto focus on filter change..."));
3681 connect(m_FilterManager.get(), &FilterManager::labelsChanged,
this, &Align::checkFilter);
3682 connect(m_FilterManager.get(), &FilterManager::positionChanged,
this, &Align::checkFilter);
3685QVariantMap Align::getEffectiveFOV()
3687 KStarsData::Instance()->userdb()->GetAllEffectiveFOVs(effectiveFOVs);
3689 m_FOVWidth = m_FOVHeight = 0;
3691 for (
auto &map : effectiveFOVs)
3693 if (map[
"Profile"].
toString() == m_ActiveProfile->name && map[
"Train"].toString() == opticalTrain())
3695 if (isEqual(map[
"Width"].toInt(), m_CameraWidth) &&
3696 isEqual(map[
"Height"].toInt(), m_CameraHeight) &&
3697 isEqual(map[
"PixelW"].toDouble(), m_CameraPixelWidth) &&
3698 isEqual(map[
"PixelH"].toDouble(), m_CameraPixelHeight) &&
3699 isEqual(map[
"FocalLength"].toDouble(), m_FocalLength) &&
3700 isEqual(map[
"FocalRedcuer"].toDouble(), m_Reducer) &&
3701 isEqual(map[
"FocalRatio"].toDouble(), m_FocalRatio))
3703 m_FOVWidth =
map[
"FovW"].toDouble();
3704 m_FOVHeight =
map[
"FovH"].toDouble();
3710 return QVariantMap();
3713void Align::saveNewEffectiveFOV(
double newFOVW,
double newFOVH)
3715 if (newFOVW < 0 || newFOVH < 0 || (isEqual(newFOVW, m_FOVWidth) && isEqual(newFOVH, m_FOVHeight)))
3718 QVariantMap effectiveMap = getEffectiveFOV();
3721 if (effectiveMap.isEmpty() ==
false)
3722 KStarsData::Instance()->userdb()->DeleteEffectiveFOV(effectiveMap[
"id"].
toString());
3725 if (newFOVW == 0.0 && newFOVH == 0.0)
3731 effectiveMap[
"Profile"] = m_ActiveProfile->name;
3732 effectiveMap[
"Train"] = opticalTrainCombo->currentText();
3733 effectiveMap[
"Width"] = m_CameraWidth;
3734 effectiveMap[
"Height"] = m_CameraHeight;
3735 effectiveMap[
"PixelW"] = m_CameraPixelWidth;
3736 effectiveMap[
"PixelH"] = m_CameraPixelHeight;
3737 effectiveMap[
"FocalLength"] = m_FocalLength;
3738 effectiveMap[
"FocalReducer"] = m_Reducer;
3739 effectiveMap[
"FocalRatio"] = m_FocalRatio;
3740 effectiveMap[
"FovW"] = newFOVW;
3741 effectiveMap[
"FovH"] = newFOVH;
3743 KStarsData::Instance()->userdb()->AddEffectiveFOV(effectiveMap);
3749void Align::zoomAlignView()
3751 m_AlignView->ZoomDefault();
3757 emit newFrame(m_AlignView);
3761void Align::setAlignZoom(
double scale)
3764 m_AlignView->ZoomIn();
3766 m_AlignView->ZoomOut();
3772 emit newFrame(m_AlignView);
3776void Align::syncFOV()
3778 QString newFOV = FOVOut->text();
3781 if (
match.hasMatch())
3783 double newFOVW =
match.captured(1).toDouble();
3784 double newFOVH =
match.captured(2).toDouble();
3787 saveNewEffectiveFOV(newFOVW, newFOVH);
3789 FOVOut->setStyleSheet(
QString());
3793 KSNotification::error(
i18n(
"Invalid FOV."));
3794 FOVOut->setStyleSheet(
"background-color:red");
3799bool Align::didSlewStart()
3801 if (m_wasSlewStarted)
3803 if (slewStartTimer.isValid() && slewStartTimer.elapsed() > MAX_WAIT_FOR_SLEW_START_MSEC)
3805 qCDebug(KSTARS_EKOS_ALIGN) <<
"Slew timed out...waited > 10s, it must have started already.";
3822 m_TargetCoord = targetCoord;
3823 qCInfo(KSTARS_EKOS_ALIGN) <<
"Target coordinates updated to JNow RA:" << m_TargetCoord.
ra().
toHMSString()
3824 <<
"DE:" << m_TargetCoord.dec().toDMSString();
3829 return QList<double>() << m_TargetCoord.ra0().Hours() << m_TargetCoord.dec0().Degrees();
3832void Align::setTargetPositionAngle(
double value)
3834 m_TargetPositionAngle = value;
3835 qCDebug(KSTARS_EKOS_ALIGN) <<
"Target PA updated to: " << m_TargetPositionAngle;
3838void Align::calculateAlignTargetDiff()
3840 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
3841 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
3842 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
3843 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
3844 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
3845 matchPAHStage(PAA::PAH_THIRD_SOLVE) ||
3846 nothingR->isChecked() ||
3850 if (!Options::astrometryDifferentialSlewing())
3852 m_TargetDiffRA = (m_AlignCoord.ra().deltaAngle(m_TargetCoord.ra())).Degrees() * 3600;
3853 m_TargetDiffDE = (m_AlignCoord.dec().deltaAngle(m_TargetCoord.dec())).Degrees() * 3600;
3857 m_TargetDiffRA = (m_AlignCoord.ra().deltaAngle(m_DestinationCoord.ra())).Degrees() * 3600;
3858 m_TargetDiffDE = (m_AlignCoord.dec().deltaAngle(m_DestinationCoord.dec())).Degrees() * 3600;
3859 qCDebug(KSTARS_EKOS_ALIGN) <<
"Differential slew - Solution RA:" << m_AlignCoord.ra().toHMSString()
3860 <<
" DE:" << m_AlignCoord.dec().toDMSString();
3861 qCDebug(KSTARS_EKOS_ALIGN) <<
"Differential slew - Destination RA:" << m_DestinationCoord.ra().toHMSString()
3862 <<
" DE:" << m_DestinationCoord.dec().toDMSString();
3865 m_TargetDiffTotal = sqrt(m_TargetDiffRA * m_TargetDiffRA + m_TargetDiffDE * m_TargetDiffDE);
3867 errOut->setText(
QString(
"%1 arcsec. RA:%2 DE:%3").arg(
3871 if (m_TargetDiffTotal <=
static_cast<double>(alignAccuracyThreshold->value()))
3872 errOut->setStyleSheet(
"color:green");
3873 else if (m_TargetDiffTotal < 1.5 * alignAccuracyThreshold->value())
3874 errOut->setStyleSheet(
"color:yellow");
3876 errOut->setStyleSheet(
"color:red");
3879 int currentRow = solutionTable->rowCount() - 1;
3886 solutionTable->setItem(currentRow, 4, dRAReport);
3895 solutionTable->setItem(currentRow, 5, dDECReport);
3898 double raPlot = m_TargetDiffRA;
3899 double decPlot = m_TargetDiffDE;
3900 alignPlot->graph(0)->addData(raPlot, decPlot);
3906 textLabel->position->
setCoords(raPlot, decPlot);
3914 if (!alignPlot->xAxis->range().contains(m_TargetDiffRA))
3916 alignPlot->graph(0)->rescaleKeyAxis(
true);
3917 alignPlot->yAxis->setScaleRatio(alignPlot->xAxis, 1.0);
3919 if (!alignPlot->yAxis->range().contains(m_TargetDiffDE))
3921 alignPlot->graph(0)->rescaleValueAxis(
true);
3922 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
3924 alignPlot->replot();
3930 for (
auto ¶m : m_StellarSolverProfiles)
3931 profiles << param.listName;
3936void Align::exportSolutionPoints()
3938 if (solutionTable->rowCount() == 0)
3943 "CSV File (*.csv)");
3954 i18n(
"A file named \"%1\" already exists. "
3965 KSNotification::sorry(message,
i18n(
"Invalid URL"));
3973 QString message =
i18n(
"Unable to write to file %1", path);
3974 KSNotification::sorry(message,
i18n(
"Could Not Open File"));
3982 outstream <<
"RA (J" << epoch <<
"),DE (J" << epoch
3983 <<
"),RA (degrees),DE (degrees),Name,RA Error (arcsec),DE Error (arcsec)" <<
Qt::endl;
3985 for (
int i = 0; i < solutionTable->rowCount(); i++)
3993 if (!raCell || !deCell || !objNameCell || !raErrorCell || !deErrorCell)
3995 KSNotification::sorry(
i18n(
"Error in table structure."));
4004 emit newLog(
i18n(
"Solution Points Saved as: %1", path));
4008void Align::setupPolarAlignmentAssistant()
4011 m_PolarAlignmentAssistant =
new PolarAlignmentAssistant(
this, m_AlignView);
4012 connect(m_PolarAlignmentAssistant, &Ekos::PAA::captureAndSolve,
this, [
this]()
4014 captureAndSolve(
true);
4016 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newAlignTableResult,
this, &Ekos::Align::setAlignTableResult);
4017 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newFrame,
this, &Ekos::Align::newFrame);
4018 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newPAHStage,
this, &Ekos::Align::processPAHStage);
4019 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newLog,
this, &Ekos::Align::appendLogText);
4021 tabWidget->addTab(m_PolarAlignmentAssistant,
i18n(
"Polar Alignment"));
4024void Align::setupManualRotator()
4026 if (m_ManualRotator)
4029 m_ManualRotator =
new ManualRotator(
this);
4030 connect(m_ManualRotator, &Ekos::ManualRotator::captureAndSolve,
this, [
this]()
4032 captureAndSolve(
false);
4038 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
4040 if (state > ALIGN_COMPLETE)
4045void Align::setuptDarkProcessor()
4047 if (m_DarkProcessor)
4050 m_DarkProcessor =
new DarkProcessor(
this);
4051 connect(m_DarkProcessor, &DarkProcessor::newLog,
this, &Ekos::Align::appendLogText);
4052 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted,
this, [
this](
bool completed)
4054 alignDarkFrame->setChecked(completed);
4055 m_AlignView->setProperty(
"suspended",
false);
4058 m_AlignView->rescale(ZOOM_KEEP_LEVEL);
4059 m_AlignView->updateFrame();
4061 setCaptureComplete();
4065void Align::processPAHStage(
int stage)
4073 if (m_StellarSolver && m_StellarSolver->isRunning())
4074 m_StellarSolver->abort();
4076 case PAA::PAH_POST_REFRESH:
4078 Options::setAstrometrySolverWCS(rememberSolverWCS);
4079 Options::setAutoWCS(rememberAutoWCS);
4084 case PAA::PAH_FIRST_CAPTURE:
4085 nothingR->setChecked(
true);
4086 m_CurrentGotoMode = GOTO_NOTHING;
4087 loadSlewB->setEnabled(
false);
4089 rememberSolverWCS = Options::astrometrySolverWCS();
4090 rememberAutoWCS = Options::autoWCS();
4092 Options::setAutoWCS(
false);
4093 Options::setAstrometrySolverWCS(
true);
4095 case PAA::PAH_SECOND_CAPTURE:
4096 case PAA::PAH_THIRD_CAPTURE:
4097 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
4098 emit newLog(
i18n(
"Settling..."));
4099 m_CaptureTimer.start(alignSettlingTime->value());
4106 emit newPAAStage(stage);
4109bool Align::matchPAHStage(uint32_t stage)
4111 return m_PolarAlignmentAssistant && m_PolarAlignmentAssistant->getPAHStage() == stage;
4114void Align::toggleManualRotator(
bool toggled)
4118 m_ManualRotator->show();
4119 m_ManualRotator->raise();
4122 m_ManualRotator->close();
4125void Align::setupOpticalTrainManager()
4127 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated,
this, &Align::refreshOpticalTrain);
4130 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
4134 ProfileSettings::Instance()->setOneSetting(ProfileSettings::AlignOpticalTrain,
4135 OpticalTrainManager::Instance()->
id(opticalTrainCombo->itemText(index)));
4136 refreshOpticalTrain();
4137 emit trainChanged();
4141void Align::refreshOpticalTrain()
4143 opticalTrainCombo->blockSignals(
true);
4144 opticalTrainCombo->clear();
4145 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
4146 trainB->setEnabled(
true);
4148 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::AlignOpticalTrain);
4152 auto id = trainID.
toUInt();
4155 if (OpticalTrainManager::Instance()->exists(
id) ==
false)
4157 qCWarning(KSTARS_EKOS_ALIGN) <<
"Optical train doesn't exist for id" << id;
4158 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
4161 auto name = OpticalTrainManager::Instance()->name(
id);
4163 opticalTrainCombo->setCurrentText(name);
4165 auto scope = OpticalTrainManager::Instance()->getScope(name);
4166 m_FocalLength = scope[
"focal_length"].
toDouble(-1);
4167 m_Aperture = scope[
"aperture"].toDouble(-1);
4168 m_FocalRatio = scope[
"focal_ratio"].toDouble(-1);
4169 m_Reducer = OpticalTrainManager::Instance()->getReducer(name);
4172 if (m_Aperture < 0 && m_FocalRatio > 0)
4173 m_Aperture = m_FocalLength / m_FocalRatio;
4175 auto mount = OpticalTrainManager::Instance()->getMount(name);
4178 auto camera = OpticalTrainManager::Instance()->getCamera(name);
4181 camera->setScopeInfo(m_FocalLength * m_Reducer, m_Aperture);
4182 opticalTrainCombo->setToolTip(
QString(
"%1 @ %2").arg(camera->getDeviceName(), scope[
"name"].toString()));
4186 syncTelescopeInfo();
4188 auto filterWheel = OpticalTrainManager::Instance()->getFilterWheel(name);
4189 setFilterWheel(filterWheel);
4191 auto rotator = OpticalTrainManager::Instance()->getRotator(name);
4192 setRotator(rotator);
4195 OpticalTrainSettings::Instance()->setOpticalTrainID(
id);
4196 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Align);
4197 if (settings.isValid())
4199 auto map = settings.toJsonObject().toVariantMap();
4200 if (map != m_Settings)
4203 setAllSettings(map);
4207 m_Settings = m_GlobalSettings;
4210 Options::setTelescopeFocalLength(m_FocalLength);
4211 Options::setCameraPixelWidth(m_CameraPixelWidth);
4212 Options::setCameraPixelHeight(m_CameraPixelHeight);
4213 Options::setCameraWidth(m_CameraWidth);
4214 Options::setCameraHeight(m_CameraHeight);
4217 opticalTrainCombo->blockSignals(
false);
4220void Align::syncSettings()
4231 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
4234 value = dsb->
value();
4237 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
4240 value = sb->
value();
4242 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
4247 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
4252 else if ( (cradio = qobject_cast<QRadioButton*>(sender())))
4258 m_Settings.remove(key);
4265 Options::self()->setProperty(key.
toLatin1(), value);
4267 m_Settings[key] = value;
4268 m_GlobalSettings[key] = value;
4269 m_DebounceTimer.start();
4277 emit settingsUpdated(getAllSettings());
4279 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()));
4280 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Align, m_Settings);
4283void Align::loadGlobalSettings()
4288 QVariantMap settings;
4290 for (
auto &oneWidget : findChildren<QComboBox*>())
4292 if (oneWidget->objectName() ==
"opticalTrainCombo")
4295 key = oneWidget->objectName();
4296 value = Options::self()->property(key.
toLatin1());
4297 if (value.
isValid() && oneWidget->count() > 0)
4299 oneWidget->setCurrentText(value.
toString());
4300 settings[key] = value;
4305 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4307 key = oneWidget->objectName();
4308 value = Options::self()->property(key.
toLatin1());
4311 oneWidget->setValue(value.
toDouble());
4312 settings[key] = value;
4317 for (
auto &oneWidget : findChildren<QSpinBox*>())
4319 key = oneWidget->objectName();
4320 value = Options::self()->property(key.
toLatin1());
4323 oneWidget->setValue(value.
toInt());
4324 settings[key] = value;
4329 for (
auto &oneWidget : findChildren<QCheckBox*>())
4331 key = oneWidget->objectName();
4332 value = Options::self()->property(key.
toLatin1());
4335 oneWidget->setChecked(value.
toBool());
4336 settings[key] = value;
4341 for (
auto &oneWidget : findChildren<QRadioButton*>())
4343 key = oneWidget->objectName();
4344 value = Options::self()->property(key.
toLatin1());
4347 oneWidget->setChecked(value.
toBool());
4348 settings[key] = value;
4352 m_GlobalSettings = m_Settings = settings;
4356void Align::connectSettings()
4359 for (
auto &oneWidget : findChildren<QComboBox*>())
4363 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4367 for (
auto &oneWidget : findChildren<QSpinBox*>())
4371 for (
auto &oneWidget : findChildren<QCheckBox*>())
4375 for (
auto &oneWidget : findChildren<QRadioButton*>())
4379 disconnect(opticalTrainCombo, QOverload<int>::of(&
QComboBox::activated),
this, &Ekos::Align::syncSettings);
4382void Align::disconnectSettings()
4385 for (
auto &oneWidget : findChildren<QComboBox*>())
4386 disconnect(oneWidget, QOverload<int>::of(&
QComboBox::activated),
this, &Ekos::Align::syncSettings);
4389 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4393 for (
auto &oneWidget : findChildren<QSpinBox*>())
4397 for (
auto &oneWidget : findChildren<QCheckBox*>())
4401 for (
auto &oneWidget : findChildren<QRadioButton*>())
4409QVariantMap Align::getAllSettings()
const
4411 QVariantMap settings;
4414 for (
auto &oneWidget : findChildren<QComboBox*>())
4415 settings.insert(oneWidget->objectName(), oneWidget->currentText());
4418 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4419 settings.insert(oneWidget->objectName(), oneWidget->value());
4422 for (
auto &oneWidget : findChildren<QSpinBox*>())
4423 settings.insert(oneWidget->objectName(), oneWidget->value());
4426 for (
auto &oneWidget : findChildren<QCheckBox*>())
4427 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
4430 for (
auto &oneWidget : findChildren<QRadioButton*>())
4431 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
4439void Align::setAllSettings(
const QVariantMap &settings)
4443 disconnectSettings();
4445 for (
auto &name : settings.keys())
4448 auto comboBox = findChild<QComboBox*>(name);
4451 syncControl(settings, name, comboBox);
4456 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
4459 syncControl(settings, name, doubleSpinBox);
4464 auto spinBox = findChild<QSpinBox*>(name);
4467 syncControl(settings, name, spinBox);
4472 auto checkbox = findChild<QCheckBox*>(name);
4475 syncControl(settings, name, checkbox);
4480 auto radioButton = findChild<QRadioButton*>(name);
4483 syncControl(settings, name, radioButton);
4489 for (
auto &key : settings.keys())
4491 auto value = settings[key];
4493 Options::self()->setProperty(key.
toLatin1(), value);
4494 Options::self()->save();
4496 m_Settings[key] = value;
4497 m_GlobalSettings[key] = value;
4500 emit settingsUpdated(getAllSettings());
4503 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()));
4504 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Align, m_Settings);
4513bool Align::syncControl(
const QVariantMap &settings,
const QString &key,
QWidget * widget)
4522 if ((pSB = qobject_cast<QSpinBox *>(widget)))
4524 const int value = settings[key].
toInt(&ok);
4531 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
4533 const double value = settings[key].toDouble(&ok);
4540 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
4542 const bool value = settings[key].toBool();
4548 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
4550 const QString value = settings[key].toString();
4554 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
4556 const bool value = settings[key].toBool();
4565void Align::setState(AlignState value)
4567 qCDebug(KSTARS_EKOS_ALIGN) <<
"Align state changed to" << getAlignStatusString(value);
4571void Align::processCaptureTimeout()
4573 if (m_CaptureTimeoutCounter++ > 3)
4575 appendLogText(
i18n(
"Capture timed out."));
4576 m_CaptureTimer.stop();
4581 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
4582 if (targetChip->isCapturing())
4584 appendLogText(
i18n(
"Capturing still running, Retrying in %1 seconds...", m_CaptureTimer.interval() / 500));
4585 targetChip->abortExposure();
4586 m_CaptureTimer.start( m_CaptureTimer.interval() * 2);
4590 setAlignTableResult(ALIGN_RESULT_FAILED);
4591 if (m_resetCaptureTimeoutCounter)
4593 m_resetCaptureTimeoutCounter =
false;
4594 m_CaptureTimeoutCounter = 0;
4596 captureAndSolve(
false);