83bool isEqual(
double a,
double b)
85 return std::abs(a - b) < 0.01;
92 qRegisterMetaType<Ekos::AlignState>(
"Ekos::AlignState");
93 qDBusRegisterMetaType<Ekos::AlignState>();
95 new AlignAdaptor(
this);
100 KStarsData::Instance()->clearTransientFOVs();
102 solverFOV.reset(
new FOV());
103 solverFOV->setName(
i18n(
"Solver FOV"));
104 solverFOV->setObjectName(
"solver_fov");
105 solverFOV->setLockCelestialPole(
true);
106 solverFOV->setColor(
KStars::Instance()->data()->colorScheme()->colorNamed(
"SolverFOVColor").name());
107 solverFOV->setProperty(
"visible",
false);
110 sensorFOV.reset(
new FOV());
111 sensorFOV->setObjectName(
"sensor_fov");
112 sensorFOV->setLockCelestialPole(
true);
113 sensorFOV->setProperty(
"visible", Options::showSensorFOV());
120 showFITSViewerB->setIcon(
125 toggleFullScreenB->setIcon(
129 connect(toggleFullScreenB, &
QPushButton::clicked,
this, &Ekos::Align::toggleAlignWidgetFullScreen);
134 m_AlignView.reset(
new AlignView(alignWidget, FITS_ALIGN));
136 m_AlignView->setBaseSize(alignWidget->size());
137 m_AlignView->createFloatingToolBar();
141 alignWidget->setLayout(vlayout);
145 captureAndSolve(
true);
158 gotoModeButtonGroup->setId(syncR, GOTO_SYNC);
159 gotoModeButtonGroup->setId(slewR, GOTO_SLEW);
160 gotoModeButtonGroup->setId(nothingR, GOTO_NOTHING);
163 m_DebounceTimer.setInterval(500);
164 m_DebounceTimer.setSingleShot(
true);
167 m_CurrentGotoMode = GOTO_SLEW;
168 gotoModeButtonGroup->button(m_CurrentGotoMode)->setChecked(
true);
170#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
172 [ = ](
int id,
bool toggled)
175 [ = ](
int id,
bool toggled)
179 this->m_CurrentGotoMode =
static_cast<GotoMode
>(
id);
182 m_CaptureTimer.setSingleShot(
true);
183 m_CaptureTimer.setInterval(CAPTURE_RETRY_DELAY);
184 connect(&m_CaptureTimer, &
QTimer::timeout,
this, &Align::processCaptureTimeout);
185 m_AlignTimer.setSingleShot(
true);
186 m_AlignTimer.setInterval(Options::astrometryTimeout() * 1000);
187 connect(&m_AlignTimer, &
QTimer::timeout,
this, &Ekos::Align::checkAlignmentTimeout);
190 stopLayout->addWidget(pi.get());
192 rememberSolverWCS = Options::astrometrySolverWCS();
193 rememberAutoWCS = Options::autoWCS();
195 solverModeButtonGroup->setId(localSolverR, SOLVER_LOCAL);
196 solverModeButtonGroup->setId(remoteSolverR, SOLVER_REMOTE);
198 setSolverMode(Options::solverMode());
200#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
202 [ = ](
int id,
bool toggled)
205 [ = ](
int id,
bool toggled)
220 connect(
this, &Align::newStatus,
this, [
this](
AlignState state)
231 for (
auto &button : qButtons)
232 button->setAutoDefault(
false);
234 savedOptionsProfiles =
QDir(KSPaths::writableLocation(
236 if(
QFile(savedOptionsProfiles).exists())
237 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
239 m_StellarSolverProfiles = getDefaultAlignOptionsProfiles();
241 m_StellarSolver.reset(
new StellarSolver());
242 connect(m_StellarSolver.get(), &StellarSolver::logOutput,
this, &Align::appendLogText);
244 setupPolarAlignmentAssistant();
245 setupManualRotator();
246 setuptDarkProcessor();
248 setupSolutionTable();
252 loadGlobalSettings();
255 setupOpticalTrainManager();
260 if (m_StellarSolver.get() !=
nullptr)
261 disconnect(m_StellarSolver.get(), &StellarSolver::logOutput,
this, &Align::appendLogText);
263 if (alignWidget->parent() ==
nullptr)
264 toggleAlignWidgetFullScreen();
271 for (
auto &dirFile : dir.entryList())
274void Align::selectSolutionTableRow(
int row,
int column)
278 solutionTable->selectRow(row);
279 for (
int i = 0; i < alignPlot->itemCount(); i++)
303void Align::handleHorizontalPlotSizeChange()
305 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
309void Align::handleVerticalPlotSizeChange()
311 alignPlot->yAxis->setScaleRatio(alignPlot->xAxis, 1.0);
317 if (
event->oldSize().width() != -1)
320 handleHorizontalPlotSizeChange();
322 handleVerticalPlotSizeChange();
338 QString labelText = label->text();
339 int point = labelText.
toInt() - 1;
346 "<th colspan=\"2\">Object %1: %2</th>"
349 "<td>RA:</td><td>%3</td>"
352 "<td>DE:</td><td>%4</td>"
355 "<td>dRA:</td><td>%5</td>"
358 "<td>dDE:</td><td>%6</td>"
362 solutionTable->item(point, 2)->text(),
363 solutionTable->item(point, 0)->text(),
364 solutionTable->item(point, 1)->text(),
365 solutionTable->item(point, 4)->text(),
366 solutionTable->item(point, 5)->text()),
367 alignPlot, alignPlot->rect());
372void Align::buildTarget()
374 double accuracyRadius = alignAccuracyThreshold->value();
377 concentricRings->
data()->clear();
378 redTarget->
data()->clear();
379 yellowTarget->
data()->clear();
380 centralTarget->
data()->clear();
384 concentricRings =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
385 redTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
386 yellowTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
387 centralTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
389 const int pointCount = 200;
396 int circleRingPt = 0;
397 for (
int i = 0; i < pointCount; i++)
399 double theta = i /
static_cast<double>(pointCount) * 2 * M_PI;
401 for (
double ring = 1; ring < 8; ring++)
403 if (ring != 4 && ring != 6)
405 if (i % (9 -
static_cast<int>(ring)) == 0)
407 circleRings[circleRingPt] =
QCPCurveData(circleRingPt, accuracyRadius * ring * 0.25 * qCos(theta),
408 accuracyRadius * ring * 0.25 * qSin(theta));
414 circleCentral[i] =
QCPCurveData(i, accuracyRadius * qCos(theta), accuracyRadius * qSin(theta));
415 circleYellow[i] =
QCPCurveData(i, accuracyRadius * 1.5 * qCos(theta), accuracyRadius * 1.5 * qSin(theta));
416 circleRed[i] =
QCPCurveData(i, accuracyRadius * 2 * qCos(theta), accuracyRadius * 2 * qSin(theta));
423 concentricRings->
data()->set(circleRings,
true);
424 redTarget->
data()->set(circleRed,
true);
425 yellowTarget->
data()->set(circleYellow,
true);
426 centralTarget->
data()->set(circleCentral,
true);
439 if (alignPlot->size().width() > 0)
443void Align::slotAutoScaleGraph()
445 double accuracyRadius = alignAccuracyThreshold->value();
446 alignPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
447 alignPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
449 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
455void Align::slotClearAllSolutionPoints()
457 if (solutionTable->rowCount() == 0)
465 solutionTable->setRowCount(0);
466 alignPlot->graph(0)->data()->clear();
467 alignPlot->clearItems();
470 slotAutoScaleGraph();
474 KSMessageBox::Instance()->questionYesNo(
i18n(
"Are you sure you want to clear all of the solution points?"),
475 i18n(
"Clear Solution Points"), 60);
478void Align::slotRemoveSolutionPoint()
480 auto abstractItem = alignPlot->item(solutionTable->currentRow());
486 double point = item->position->key();
487 alignPlot->graph(0)->data()->remove(point);
491 alignPlot->removeItem(solutionTable->currentRow());
493 for (
int i = 0; i < alignPlot->itemCount(); i++)
495 auto oneItem = alignPlot->item(i);
503 solutionTable->removeRow(solutionTable->currentRow());
507void Align::slotMountModel()
511 m_MountModel =
new MountModel(
this);
513 connect(m_MountModel, &Ekos::MountModel::aborted,
this, [
this]()
515 if (m_Mount && m_Mount->isSlewing())
522 m_MountModel->
show();
542void Align::checkAlignmentTimeout()
544 if (m_SolveFromFile || ++solverIterations == MAXIMUM_SOLVER_ITERATIONS)
548 appendLogText(
i18n(
"Solver timed out."));
549 parser->stopSolver();
551 setAlignTableResult(ALIGN_RESULT_FAILED);
559 if (
sender() ==
nullptr && mode >= 0 && mode <= 1)
561 solverModeButtonGroup->button(mode)->setChecked(
true);
564 Options::setSolverMode(mode);
566 if (mode == SOLVER_REMOTE)
568 if (remoteParser.get() !=
nullptr && m_RemoteParserDevice !=
nullptr)
570 parser = remoteParser.get();
576 parser = remoteParser.get();
581 parser->setAlign(
this);
595 return m_Camera->getDeviceName();
626 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
627 if (targetChip ==
nullptr || (targetChip && targetChip->isCapturing()))
630 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser.get() !=
nullptr)
640 if (m_Camera && m_Camera == device)
653 connect(m_Camera, &ISD::ConcreteDevice::Connected,
this, [
this]()
655 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
656 controlBox->setEnabled(isConnected);
657 gotoBox->setEnabled(isConnected);
658 plateSolverOptionsGroup->setEnabled(isConnected);
659 tabWidget->setEnabled(isConnected);
661 connect(m_Camera, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
663 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
664 controlBox->setEnabled(isConnected);
665 gotoBox->setEnabled(isConnected);
666 plateSolverOptionsGroup->setEnabled(isConnected);
667 tabWidget->setEnabled(isConnected);
669 opticalTrainCombo->setEnabled(
true);
670 trainLabel->setEnabled(
true);
674 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
675 controlBox->setEnabled(isConnected);
676 gotoBox->setEnabled(isConnected);
677 plateSolverOptionsGroup->setEnabled(isConnected);
678 tabWidget->setEnabled(isConnected);
690 if (m_Mount && m_Mount == device)
703 connect(m_Mount, &ISD::ConcreteDevice::Connected,
this, [
this]()
705 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
706 controlBox->setEnabled(isConnected);
707 gotoBox->setEnabled(isConnected);
708 plateSolverOptionsGroup->setEnabled(isConnected);
709 tabWidget->setEnabled(isConnected);
711 connect(m_Mount, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
713 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
714 controlBox->setEnabled(isConnected);
715 gotoBox->setEnabled(isConnected);
716 plateSolverOptionsGroup->setEnabled(isConnected);
717 tabWidget->setEnabled(isConnected);
719 opticalTrainCombo->setEnabled(
true);
720 trainLabel->setEnabled(
true);
724 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
725 controlBox->setEnabled(isConnected);
726 gotoBox->setEnabled(isConnected);
727 plateSolverOptionsGroup->setEnabled(isConnected);
728 tabWidget->setEnabled(isConnected);
733 RUN_PAH(setCurrentTelescope(m_Mount));
736 connect(m_Mount, &ISD::Mount::Disconnected,
this, [
this]()
738 m_isRateSynced =
false;
747 if (m_Dome && m_Dome == device)
764 auto name = device->getDeviceName();
765 device->disconnect(
this);
768 if (m_Mount && m_Mount->getDeviceName() == name)
775 if (m_Dome && m_Dome->getDeviceName() == name)
782 if (m_Rotator && m_Rotator->getDeviceName() == name)
789 if (m_Camera && m_Camera->getDeviceName() == name)
798 if (m_RemoteParserDevice && m_RemoteParserDevice->getDeviceName() == name)
800 m_RemoteParserDevice.
clear();
804 if (m_FilterWheel && m_FilterWheel->getDeviceName() == name)
807 m_FilterWheel =
nullptr;
816 if (m_Mount ==
nullptr || m_Mount->isConnected() ==
false)
819 if (m_isRateSynced ==
false)
821 auto speed = m_Settings[
"PAHMountSpeed"];
822 auto slewRates = m_Mount->slewRates();
825 RUN_PAH(syncMountSpeed(speed.toString()));
827 else if (!slewRates.isEmpty())
829 RUN_PAH(syncMountSpeed(slewRates.last()));
832 m_isRateSynced = !slewRates.empty();
835 canSync = m_Mount->canSync();
837 if (canSync ==
false && syncR->isEnabled())
839 slewR->setChecked(
true);
840 appendLogText(
i18n(
"Mount does not support syncing."));
843 syncR->setEnabled(canSync);
845 if (m_FocalLength == -1 || m_Aperture == -1)
848 if (m_CameraPixelWidth != -1 && m_CameraPixelHeight != -1)
862 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
863 Q_ASSERT(targetChip);
866 uint8_t bit_depth = 8;
867 targetChip->getImageInfo(m_CameraWidth, m_CameraHeight, m_CameraPixelWidth, m_CameraPixelHeight, bit_depth);
869 setWCSEnabled(Options::astrometrySolverWCS());
871 int binx = 1, biny = 1;
872 alignBinning->setEnabled(targetChip->canBin());
873 if (targetChip->canBin())
875 alignBinning->blockSignals(
true);
877 targetChip->getMaxBin(&binx, &biny);
878 alignBinning->clear();
880 for (
int i = 0; i < binx; i++)
881 alignBinning->addItem(
QString(
"%1x%2").arg(i + 1).arg(i + 1));
883 auto binning = m_Settings[
"alignBinning"];
884 if (binning.isValid())
885 alignBinning->setCurrentText(binning.toString());
887 alignBinning->blockSignals(
false);
892 int roiW = 0, roiH = 0;
893 targetChip->getFrameMinMax(
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &roiW,
nullptr, &roiH);
896 if ( (roiW > 0 && roiW < m_CameraWidth) || (roiH > 0 && roiH < m_CameraHeight))
898 m_CameraWidth = roiW;
899 m_CameraHeight = roiH;
902 if (m_CameraPixelWidth > 0 && m_CameraPixelHeight > 0 && m_FocalLength > 0 && m_Aperture > 0)
910 if (m_Camera ==
nullptr)
913 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
914 if (targetChip ==
nullptr || (targetChip && targetChip->isCapturing()))
917 auto isoList = targetChip->getISOList();
920 if (isoList.isEmpty())
922 alignISO->setEnabled(
false);
926 alignISO->setEnabled(
true);
927 alignISO->addItems(isoList);
928 alignISO->setCurrentIndex(targetChip->getISOIndex());
932 if (m_Camera->hasGain())
934 double min, max, step, value;
935 m_Camera->getGainMinMaxStep(&min, &max, &step);
938 alignGainSpecialValue = min - step;
939 alignGain->setRange(alignGainSpecialValue, max);
940 alignGain->setSpecialValueText(
i18n(
"--"));
941 alignGain->setEnabled(
true);
942 alignGain->setSingleStep(step);
943 m_Camera->getGain(&value);
945 auto gain = m_Settings[
"alignGain"];
949 TargetCustomGainValue = gain.toDouble();
950 if (TargetCustomGainValue > 0)
951 alignGain->setValue(TargetCustomGainValue);
953 alignGain->setValue(alignGainSpecialValue);
955 alignGain->setReadOnly(m_Camera->getGainPermission() == IP_RO);
959 if (alignGain->value() > alignGainSpecialValue)
960 TargetCustomGainValue = alignGain->value();
964 alignGain->setEnabled(
false);
971 fov_scale = m_FOVPixelScale;
978 result << m_FOVWidth << m_FOVHeight << m_FOVPixelScale;
987 result << m_CameraWidth << m_CameraHeight << m_CameraPixelWidth << m_CameraPixelHeight;
996 result << m_FocalLength << m_Aperture << m_Reducer;
1005 auto reducedFocalLength = m_Reducer * m_FocalLength;
1006 if (m_FocalRatio > 0)
1010 fov_w = 3600 * 2 * atan(m_CameraWidth * (m_CameraPixelWidth / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1011 fov_h = 3600 * 2 * atan(m_CameraHeight * (m_CameraPixelHeight / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1016 fov_w = 206264.8062470963552 * m_CameraWidth * m_CameraPixelWidth / 1000.0 / reducedFocalLength;
1017 fov_h = 206264.8062470963552 * m_CameraHeight * m_CameraPixelHeight / 1000.0 / reducedFocalLength;
1021 fov_scale = (fov_w * (alignBinning->currentIndex() + 1)) / m_CameraWidth;
1028void Align::calculateEffectiveFocalLength(
double newFOVW)
1030 if (newFOVW < 0 || isEqual(newFOVW, m_FOVWidth))
1033 auto reducedFocalLength = m_Reducer * m_FocalLength;
1034 double new_focal_length = 0;
1036 if (m_FocalRatio > 0)
1037 new_focal_length = ((m_CameraWidth * m_CameraPixelWidth / 1000.0) / tan(newFOVW / 2)) / 2;
1039 new_focal_length = ((m_CameraWidth * m_CameraPixelWidth / 1000.0) * 206264.8062470963552) / (newFOVW * 60.0);
1040 double focal_diff = std::fabs(new_focal_length - reducedFocalLength);
1044 m_EffectiveFocalLength = new_focal_length / m_Reducer;
1045 appendLogText(
i18n(
"Effective telescope focal length is updated to %1 mm.", m_EffectiveFocalLength));
1049void Align::calculateFOV()
1051 auto reducedFocalLength = m_Reducer * m_FocalLength;
1052 auto reducecdEffectiveFocalLength = m_Reducer * m_EffectiveFocalLength;
1053 auto reducedFocalRatio = m_Reducer * m_FocalRatio;
1055 if (m_FocalRatio > 0)
1059 m_FOVWidth = 3600 * 2 * atan(m_CameraWidth * (m_CameraPixelWidth / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1060 m_FOVHeight = 3600 * 2 * atan(m_CameraHeight * (m_CameraPixelHeight / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1065 m_FOVWidth = 206264.8062470963552 * m_CameraWidth * m_CameraPixelWidth / 1000.0 / reducedFocalLength;
1066 m_FOVHeight = 206264.8062470963552 * m_CameraHeight * m_CameraPixelHeight / 1000.0 / reducedFocalLength;
1072 m_FOVPixelScale = (m_FOVWidth * (alignBinning->currentIndex() + 1)) / m_CameraWidth;
1076 m_FOVHeight /= 60.0;
1078 double calculated_fov_x = m_FOVWidth;
1079 double calculated_fov_y = m_FOVHeight;
1081 QString calculatedFOV = (
QString(
"%1' x %2'").
arg(m_FOVWidth, 0,
'f', 1).
arg(m_FOVHeight, 0,
'f', 1));
1084 if (m_FOVWidth < 1 || m_FOVWidth > 60 * 180 || m_FOVHeight < 1 || m_FOVHeight > 60 * 180)
1087 i18n(
"Warning! The calculated field of view (%1) is out of bounds. Ensure the telescope focal length and camera pixel size are correct.",
1092 FocalLengthOut->setText(
QString(
"%1 (%2)").arg(reducedFocalLength, 0,
'f', 1).
1093 arg(m_EffectiveFocalLength > 0 ? reducecdEffectiveFocalLength : reducedFocalLength, 0,
'f', 1));
1095 if (m_FocalRatio > 0)
1096 FocalRatioOut->setText(
QString(
"%1 (%2)").arg(reducedFocalRatio, 0,
'f', 1).
1097 arg(m_EffectiveFocalLength > 0 ? reducecdEffectiveFocalLength / m_Aperture : reducedFocalRatio, 0,
1100 else if (m_Aperture > 0)
1101 FocalRatioOut->setText(
QString(
"%1 (%2)").arg(reducedFocalLength / m_Aperture, 0,
'f', 1).
1102 arg(m_EffectiveFocalLength > 0 ? reducecdEffectiveFocalLength / m_Aperture : reducedFocalLength / m_Aperture, 0,
1104 ReducerOut->setText(
QString(
"%1x").arg(m_Reducer, 0,
'f', 2));
1106 if (m_EffectiveFocalLength > 0)
1108 double focal_diff = std::fabs(m_EffectiveFocalLength - m_FocalLength);
1110 FocalLengthOut->setStyleSheet(
"color:green");
1111 else if (focal_diff < 15)
1112 FocalLengthOut->setStyleSheet(
"color:yellow");
1114 FocalLengthOut->setStyleSheet(
"color:red");
1122 if (m_FOVWidth == 0)
1126 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>",
1128 m_FOVWidth = calculated_fov_x;
1129 m_FOVHeight = calculated_fov_y;
1130 m_EffectiveFOVPending =
true;
1134 m_EffectiveFOVPending =
false;
1135 FOVOut->setToolTip(
i18n(
"<p>Effective field of view size in arcminutes.</p>"));
1138 solverFOV->setSize(m_FOVWidth, m_FOVHeight);
1139 sensorFOV->setSize(m_FOVWidth, m_FOVHeight);
1141 sensorFOV->setName(m_Camera->getDeviceName());
1143 FOVOut->setText(
QString(
"%1' x %2'").arg(m_FOVWidth, 0,
'f', 1).arg(m_FOVHeight, 0,
'f', 1));
1146 const bool fovOK = ((m_FOVWidth + m_FOVHeight) / 2.0) > PAH_CUTOFF_FOV;
1147 if (m_PolarAlignmentAssistant !=
nullptr)
1148 m_PolarAlignmentAssistant->setEnabled(fovOK);
1150 if (opsAstrometry->kcfg_AstrometryUseImageScale->isChecked())
1152 int unitType = opsAstrometry->kcfg_AstrometryImageScaleUnits->currentIndex();
1157 double fov_low = qMin(m_FOVWidth / 60, m_FOVHeight / 60);
1158 double fov_high = qMax(m_FOVWidth / 60, m_FOVHeight / 60);
1159 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(fov_low);
1160 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(fov_high);
1162 Options::setAstrometryImageScaleLow(fov_low);
1163 Options::setAstrometryImageScaleHigh(fov_high);
1166 else if (unitType == 1)
1168 double fov_low = qMin(m_FOVWidth, m_FOVHeight);
1169 double fov_high = qMax(m_FOVWidth, m_FOVHeight);
1170 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(fov_low);
1171 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(fov_high);
1173 Options::setAstrometryImageScaleLow(fov_low);
1174 Options::setAstrometryImageScaleHigh(fov_high);
1179 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(m_FOVPixelScale * 0.9);
1180 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(m_FOVPixelScale * 1.1);
1183 Options::setAstrometryImageScaleLow(m_FOVPixelScale * 0.9);
1184 Options::setAstrometryImageScaleHigh(m_FOVPixelScale * 1.1);
1211 if (optionsMap.contains(
"noverify"))
1212 solver_args <<
"--no-verify";
1215 if (optionsMap.contains(
"resort"))
1216 solver_args <<
"--resort";
1219 if (optionsMap.contains(
"nofits2fits"))
1220 solver_args <<
"--no-fits2fits";
1223 if (optionsMap.contains(
"downsample"))
1224 solver_args <<
"--downsample" <<
QString::number(optionsMap.value(
"downsample", 2).toInt());
1227 if (optionsMap.contains(
"scaleL"))
1228 solver_args <<
"-L" <<
QString::number(optionsMap.value(
"scaleL").toDouble());
1231 if (optionsMap.contains(
"scaleH"))
1232 solver_args <<
"-H" <<
QString::number(optionsMap.value(
"scaleH").toDouble());
1235 if (optionsMap.contains(
"scaleUnits"))
1236 solver_args <<
"-u" << optionsMap.value(
"scaleUnits").toString();
1239 if (optionsMap.contains(
"ra"))
1240 solver_args <<
"-3" <<
QString::number(optionsMap.value(
"ra").toDouble());
1243 if (optionsMap.contains(
"de"))
1244 solver_args <<
"-4" <<
QString::number(optionsMap.value(
"de").toDouble());
1247 if (optionsMap.contains(
"radius"))
1248 solver_args <<
"-5" <<
QString::number(optionsMap.value(
"radius").toDouble());
1251 if (optionsMap.contains(
"custom"))
1252 solver_args << optionsMap.value(
"custom").toString();
1258void Align::generateFOVBounds(
double fov_w,
QString &fov_low,
QString &fov_high,
double tolerance)
1262 double lower_boundary = 1.0 - tolerance;
1263 double upper_boundary = 1.0 + tolerance;
1270 double fov_lower = fov_w * lower_boundary;
1271 double fov_upper = fov_w * upper_boundary;
1281 QVariantMap optionsMap;
1294 if (Options::astrometryUseNoVerify())
1295 optionsMap[
"noverify"] =
true;
1297 if (Options::astrometryUseResort())
1298 optionsMap[
"resort"] =
true;
1300 if (Options::astrometryUseNoFITS2FITS())
1301 optionsMap[
"nofits2fits"] =
true;
1303 if (data ==
nullptr)
1305 if (Options::astrometryUseDownsample())
1307 if (Options::astrometryAutoDownsample() && m_CameraWidth && m_CameraHeight)
1309 uint16_t w = m_CameraWidth / (alignBinning->currentIndex() + 1);
1310 optionsMap[
"downsample"] = getSolverDownsample(w);
1313 optionsMap[
"downsample"] = Options::astrometryDownsample();
1317 optionsMap[
"image_width"] = m_CameraWidth / (alignBinning->currentIndex() + 1);
1318 optionsMap[
"image_height"] = m_CameraHeight / (alignBinning->currentIndex() + 1);
1320 if (Options::astrometryUseImageScale() && m_FOVWidth > 0 && m_FOVHeight > 0)
1323 if (Options::astrometryImageScaleUnits() == 1)
1325 else if (Options::astrometryImageScaleUnits() == 2)
1327 if (Options::astrometryAutoUpdateImageScale())
1330 double fov_w = m_FOVWidth;
1333 if (Options::astrometryImageScaleUnits() == SSolver::DEG_WIDTH)
1338 else if (Options::astrometryImageScaleUnits() == SSolver::ARCSEC_PER_PIX)
1340 fov_w = m_FOVPixelScale;
1345 generateFOVBounds(fov_w, fov_low, fov_high, m_EffectiveFOVPending ? 0.3 : 0.05);
1347 optionsMap[
"scaleL"] = fov_low;
1348 optionsMap[
"scaleH"] = fov_high;
1349 optionsMap[
"scaleUnits"] = units;
1353 optionsMap[
"scaleL"] = Options::astrometryImageScaleLow();
1354 optionsMap[
"scaleH"] = Options::astrometryImageScaleHigh();
1355 optionsMap[
"scaleUnits"] = units;
1359 if (Options::astrometryUsePosition() && m_Mount !=
nullptr)
1361 double ra = 0, dec = 0;
1362 m_Mount->getEqCoords(&ra, &dec);
1364 optionsMap[
"ra"] = ra * 15.0;
1365 optionsMap[
"de"] = dec;
1366 optionsMap[
"radius"] = Options::astrometryRadius();
1373 data->getRecordValue(
"NAXIS1",
width);
1374 if (
width.isValid())
1376 optionsMap[
"downsample"] = getSolverDownsample(
width.toInt());
1379 optionsMap[
"downsample"] = Options::astrometryDownsample();
1383 data->getRecordValue(
"SCALE", pixscale);
1386 optionsMap[
"scaleL"] = 0.8 * pixscale.
toDouble();
1387 optionsMap[
"scaleH"] = 1.2 * pixscale.
toDouble();
1388 optionsMap[
"scaleUnits"] =
"app";
1393 data->getRecordValue(
"RA", ra);
1394 data->getRecordValue(
"DEC", de);
1399 optionsMap[
"radius"] = Options::astrometryRadius();
1403 if (Options::astrometryCustomOptions().isEmpty() ==
false)
1404 optionsMap[
"custom"] = Options::astrometryCustomOptions();
1415 if (m_TargetCoord.
ra().
degree() < 0)
1417 m_TargetCoord = m_TelescopeCoord;
1418 appendLogText(
i18n(
"Setting target to RA:%1 DEC:%2",
1426 m_DestinationCoord = m_TargetCoord;
1428 qCDebug(KSTARS_EKOS_ALIGN) <<
"Capture&Solve - Target RA:" << m_TargetCoord.
ra().
toHMSString(
true)
1430 qCDebug(KSTARS_EKOS_ALIGN) <<
"Capture&Solve - Destination RA:" << m_DestinationCoord.
ra().
toHMSString(
true)
1432 m_AlignTimer.
stop();
1433 m_CaptureTimer.
stop();
1435 if (m_Camera ==
nullptr)
1437 appendLogText(
i18n(
"Error: No camera detected."));
1441 if (m_Camera->isConnected() ==
false)
1443 appendLogText(
i18n(
"Error: lost connection to camera."));
1444 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"), KSNotification::Align,
1445 KSNotification::Alert);
1449 if (m_Camera->isBLOBEnabled() ==
false)
1451 m_Camera->setBLOBEnabled(
true);
1457 if (m_FocalLength == -1 || m_Aperture == -1)
1459 KSNotification::error(
1460 i18n(
"Telescope aperture and focal length are missing. Please check your optical train settings and try again."));
1464 if (m_CameraPixelWidth == -1 || m_CameraPixelHeight == -1)
1466 KSNotification::error(
i18n(
"CCD pixel size is missing. Please check your driver settings and try again."));
1470 if (m_FilterWheel !=
nullptr)
1472 if (m_FilterWheel->isConnected() ==
false)
1474 appendLogText(
i18n(
"Error: lost connection to filter wheel."));
1478 int targetPosition = alignFilter->currentIndex() + 1;
1480 if (targetPosition > 0 && targetPosition != currentFilterPosition)
1482 filterPositionPending =
true;
1484 m_FilterManager->setFilterPosition(targetPosition, FilterManager::NO_AUTOFOCUS_POLICY);
1490 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
1491 if (clientManager && clientManager->getBLOBMode(m_Camera->getDeviceName().
toLatin1().
constData(),
"CCD1") == B_NEVER)
1493 if (KMessageBox::questionYesNo(
1494 nullptr,
i18n(
"Image transfer is disabled for this camera. Would you like to enable it?")) ==
1497 clientManager->setBLOBMode(B_ONLY, m_Camera->getDeviceName().
toLatin1().
constData(),
"CCD1");
1498 clientManager->setBLOBMode(B_ONLY, m_Camera->getDeviceName().
toLatin1().
constData(),
"CCD2");
1506 double seqExpose = alignExposure->value();
1508 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1510 if (m_FocusState >= FOCUS_PROGRESS)
1512 appendLogText(
i18n(
"Cannot capture while focus module is busy. Retrying in %1 seconds...",
1513 CAPTURE_RETRY_DELAY / 1000));
1514 m_CaptureTimer.
start(CAPTURE_RETRY_DELAY);
1518 if (targetChip->isCapturing())
1520 appendLogText(
i18n(
"Cannot capture while CCD exposure is in progress. Retrying in %1 seconds...",
1521 CAPTURE_RETRY_DELAY / 1000));
1522 m_CaptureTimer.
start(CAPTURE_RETRY_DELAY);
1527 if (!m_SolveFromFile && m_Rotator && m_Rotator->absoluteAngleState() == IPS_BUSY)
1529 int TimeOut = CAPTURE_ROTATOR_DELAY;
1530 switch (m_CaptureTimeoutCounter)
1535 if ((absAngle = m_Rotator->getNumber(
"ABS_ROTATOR_ANGLE")->at(0)->getValue()))
1537 RotatorUtils::Instance()->startTimeFrame(absAngle);
1538 m_estimateRotatorTimeFrame =
true;
1539 appendLogText(
i18n(
"Cannot capture while rotator is busy: Time delay estimate started..."));
1541 m_CaptureTimer.
start(TimeOut);
1546 TimeOut = m_RotatorTimeFrame * 1000;
1551 TimeOut *= m_CaptureTimeoutCounter;
1552 m_estimateRotatorTimeFrame =
false;
1553 appendLogText(
i18n(
"Cannot capture while rotator is busy: Retrying in %1 seconds...", TimeOut / 1000));
1554 m_CaptureTimer.
start(TimeOut);
1560 m_AlignView->setBaseSize(alignWidget->size());
1561 m_AlignView->setProperty(
"suspended", (solverModeButtonGroup->checkedId() == SOLVER_LOCAL
1562 && alignDarkFrame->isChecked()));
1568 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser.get() !=
nullptr)
1570 if (m_RemoteParserDevice ==
nullptr)
1572 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
1578 auto activeDevices = m_RemoteParserDevice->getBaseDevice().getText(
"ACTIVE_DEVICES");
1581 auto activeCCD = activeDevices.findWidgetByName(
"ACTIVE_CCD");
1582 if (
QString(activeCCD->text) != m_Camera->getDeviceName())
1585 m_RemoteParserDevice->getClientManager()->sendNewProperty(activeDevices);
1592 solverTimer.
start();
1598 dir.setNameFilters(
QStringList() <<
"fits*" <<
"tmp.*");
1600 for (
auto &dirFile : dir.entryList())
1603 prepareCapture(targetChip);
1606 if (matchPAHStage(PAA::PAH_REFRESH))
1607 targetChip->capture(m_PolarAlignmentAssistant->getPAHExposureDuration());
1609 targetChip->capture(seqExpose);
1611 solveB->setEnabled(
false);
1612 loadSlewB->setEnabled(
false);
1613 stopB->setEnabled(
true);
1614 pi->startAnimation();
1616 RotatorGOTO =
false;
1619 emit newStatus(state);
1620 solverFOV->setProperty(
"visible",
true);
1623 if (matchPAHStage(PAA::PAH_REFRESH))
1626 appendLogText(
i18n(
"Capturing image..."));
1634 m_Mount->getEqCoords(&ra, &dec);
1635 if (!m_SolveFromFile)
1637 int currentRow = solutionTable->rowCount();
1638 solutionTable->insertRow(currentRow);
1639 for (
int i = 4; i < 6; i++)
1643 solutionTable->setItem(currentRow, i, disabledBox);
1647 RAReport->
setText(ScopeRAOut->text());
1650 solutionTable->setItem(currentRow, 0, RAReport);
1653 DECReport->
setText(ScopeDecOut->text());
1656 solutionTable->setItem(currentRow, 1, DECReport);
1658 double maxrad = 1.0;
1674 solutionTable->setItem(currentRow, 2, ObjNameReport);
1680 solutionTable->setCellWidget(currentRow, 3, alignIndicator);
1692 auto chip = data->property(
"chip");
1693 if (chip.isValid() && chip.toInt() == ISD::CameraChip::GUIDE_CCD)
1701 m_AlignView->loadData(data);
1705 m_ImageData.
reset();
1707 RUN_PAH(setImageData(m_ImageData));
1710 if (matchPAHStage(PAA::PAH_REFRESH))
1712 setCaptureComplete();
1716 appendLogText(
i18n(
"Image received."));
1719 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1722 if (alignDarkFrame->isChecked())
1724 int x,
y, w, h, binx = 1, biny = 1;
1725 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1726 targetChip->getFrame(&
x, &
y, &w, &h);
1727 targetChip->getBinning(&binx, &biny);
1729 uint16_t offsetX =
x / binx;
1730 uint16_t offsetY =
y / biny;
1732 m_DarkProcessor->denoise(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()),
1733 targetChip, m_ImageData, alignExposure->value(), offsetX, offsetY);
1737 setCaptureComplete();
1743 if (m_Camera->getUploadMode() == ISD::Camera::UPLOAD_LOCAL)
1745 rememberUploadMode = ISD::Camera::UPLOAD_LOCAL;
1746 m_Camera->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
1749 if (m_Camera->isFastExposureEnabled())
1751 m_RememberCameraFastExposure =
true;
1752 m_Camera->setFastExposureEnabled(
false);
1755 m_Camera->setEncodingFormat(
"FITS");
1756 targetChip->resetFrame();
1757 targetChip->setBatchMode(
false);
1758 targetChip->setCaptureMode(FITS_ALIGN);
1759 targetChip->setFrameType(FRAME_LIGHT);
1761 int bin = alignBinning->currentIndex() + 1;
1762 targetChip->setBinning(bin, bin);
1765 if (m_Camera->hasGain() && alignGain->isEnabled() && alignGain->value() > alignGainSpecialValue)
1766 m_Camera->setGain(alignGain->value());
1768 if (alignISO->currentIndex() >= 0)
1769 targetChip->setISOIndex(alignISO->currentIndex());
1773void Align::setCaptureComplete()
1775 if (matchPAHStage(PAA::PAH_REFRESH))
1777 emit newFrame(m_AlignView);
1778 m_PolarAlignmentAssistant->processPAHRefresh();
1782 emit newImage(m_AlignView);
1784 solverFOV->setImage(m_AlignView->getDisplayImage());
1787 if (Options::saveAlignImages())
1799 m_ImageData->saveImage(filename);
1807 gotoModeButtonGroup->button(mode)->setChecked(
true);
1808 m_CurrentGotoMode =
static_cast<GotoMode
>(mode);
1817 QStringList astrometryDataDirs = KSUtils::getAstrometryDataDirs();
1820 m_UsedScale =
false;
1821 m_UsedPosition =
false;
1826 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1828 if(Options::solverType() != SSolver::SOLVER_ASTAP
1829 && Options::solverType() != SSolver::SOLVER_WATNEYASTROMETRY)
1831 bool foundAnIndex =
false;
1832 for(
auto &dataDir : astrometryDataDirs)
1839 if(indexList.
count() > 0)
1840 foundAnIndex =
true;
1846 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."));
1848 if(alignSettings && m_IndexFilesPage)
1851 alignSettings->
show();
1855 if (m_StellarSolver->isRunning())
1856 m_StellarSolver->abort();
1858 m_ImageData = m_AlignView->imageData();
1859 m_StellarSolver->loadNewImageBuffer(m_ImageData->getStatistics(), m_ImageData->getImageBuffer());
1860 m_StellarSolver->setProperty(
"ProcessType", SSolver::SOLVE);
1861 m_StellarSolver->setProperty(
"ExtractorType", Options::solveSextractorType());
1862 m_StellarSolver->setProperty(
"SolverType", Options::solverType());
1863 connect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
1864 m_StellarSolver->setIndexFolderPaths(Options::astrometryIndexFolderList());
1866 auto params = m_StellarSolverProfiles.
at(Options::solveOptionsProfile());
1867 params.partition = Options::stellarSolverPartition();
1868 m_StellarSolver->setParameters(params);
1870 const SSolver::SolverType type =
static_cast<SSolver::SolverType
>(m_StellarSolver->property(
"SolverType").toInt());
1871 if(type == SSolver::SOLVER_LOCALASTROMETRY || type == SSolver::SOLVER_ASTAP || type == SSolver::SOLVER_WATNEYASTROMETRY)
1875 m_AlignView->saveImage(filename);
1876 m_StellarSolver->setProperty(
"FileToProcess", filename);
1877 ExternalProgramPaths externalPaths;
1878 externalPaths.sextractorBinaryPath = Options::sextractorBinary();
1879 externalPaths.solverPath = Options::astrometrySolverBinary();
1880 externalPaths.astapBinaryPath = Options::aSTAPExecutable();
1881 externalPaths.watneyBinaryPath = Options::watneyBinary();
1882 externalPaths.wcsPath = Options::astrometryWCSInfo();
1883 m_StellarSolver->setExternalFilePaths(externalPaths);
1886 m_StellarSolver->setProperty(
"AutoGenerateAstroConfig",
true);
1889 if(type == SSolver::SOLVER_ONLINEASTROMETRY )
1893 m_AlignView->saveImage(filename);
1895 m_StellarSolver->setProperty(
"FileToProcess", filename);
1896 m_StellarSolver->setProperty(
"AstrometryAPIKey", Options::astrometryAPIKey());
1897 m_StellarSolver->setProperty(
"AstrometryAPIURL", Options::astrometryAPIURL());
1900 bool useImageScale = Options::astrometryUseImageScale();
1901 if (useBlindScale == BLIND_ENGAGNED)
1903 useImageScale =
false;
1904 useBlindScale = BLIND_USED;
1905 appendLogText(
i18n(
"Solving with blind image scale..."));
1908 bool useImagePosition = Options::astrometryUsePosition();
1909 if (useBlindPosition == BLIND_ENGAGNED)
1911 useImagePosition =
false;
1912 useBlindPosition = BLIND_USED;
1913 appendLogText(
i18n(
"Solving with blind image position..."));
1916 if (m_SolveFromFile)
1918 FITSImage::Solution solution;
1919 m_ImageData->parseSolution(solution);
1921 if (useImageScale && solution.pixscale > 0)
1924 m_ScaleUsed = solution.pixscale;
1925 m_StellarSolver->setSearchScale(solution.pixscale * 0.8,
1926 solution.pixscale * 1.2,
1927 SSolver::ARCSEC_PER_PIX);
1930 m_StellarSolver->setProperty(
"UseScale",
false);
1932 if (useImagePosition && solution.ra > 0)
1934 m_UsedPosition =
true;
1935 m_RAUsed = solution.ra;
1936 m_DECUsed = solution.dec;
1937 m_StellarSolver->setSearchPositionInDegrees(solution.ra, solution.dec);
1940 m_StellarSolver->setProperty(
"UsePosition",
false);
1943 if (!m_ImageData->getRecordValue(
"PIERSIDE", value))
1945 appendLogText(
i18n(
"Loaded image does not have pierside information"));
1946 m_TargetPierside = ISD::Mount::PIER_UNKNOWN;
1950 appendLogText(
i18n(
"Loaded image was taken on pierside %1", value.
toString()));
1951 (value ==
"WEST") ? m_TargetPierside = ISD::Mount::PIER_WEST : m_TargetPierside = ISD::Mount::PIER_EAST;
1953 RotatorUtils::Instance()->Instance()->setImagePierside(m_TargetPierside);
1961 m_ScaleUsed = Options::astrometryImageScaleLow();
1963 SSolver::ScaleUnits units =
static_cast<SSolver::ScaleUnits
>(Options::astrometryImageScaleUnits());
1965 m_StellarSolver->setSearchScale(Options::astrometryImageScaleLow() * 0.8,
1966 Options::astrometryImageScaleHigh() * 1.2,
1970 m_StellarSolver->setProperty(
"UseScale",
false);
1972 if(useImagePosition)
1974 m_StellarSolver->setSearchPositionInDegrees(m_TelescopeCoord.
ra().
Degrees(), m_TelescopeCoord.
dec().
Degrees());
1975 m_UsedPosition =
true;
1976 m_RAUsed = m_TelescopeCoord.
ra().
Degrees();
1977 m_DECUsed = m_TelescopeCoord.
dec().
Degrees();
1980 m_StellarSolver->setProperty(
"UsePosition",
false);
1983 if(Options::alignmentLogging())
1988 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
1989 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
1990 if(Options::astrometryLogToFile())
1992 m_StellarSolver->setProperty(
"LogToFile",
true);
1993 m_StellarSolver->setProperty(
"LogFileName", Options::astrometryLogFilepath());
1998 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
1999 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
2002 SolverUtils::patchMultiAlgorithm(m_StellarSolver.get());
2005 m_StellarSolver->start();
2009 if (m_ImageData.
isNull())
2010 m_ImageData = m_AlignView->imageData();
2013 remoteParser->startSolver(m_ImageData->filename(),
generateRemoteArgs(m_ImageData),
false);
2018 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
2019 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
2020 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
2021 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
2022 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
2023 matchPAHStage(PAA::PAH_THIRD_SOLVE) ||
2024 nothingR->isChecked() ||
2029 solverTimer.
start();
2032 emit newStatus(state);
2035void Align::solverComplete()
2037 disconnect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
2038 if(!m_StellarSolver->solvingDone() || m_StellarSolver->failed())
2040 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
2041 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
2042 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
2043 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
2044 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
2045 matchPAHStage(PAA::PAH_THIRD_SOLVE))
2047 if (CHECK_PAH(processSolverFailure()))
2057 FITSImage::Solution solution = m_StellarSolver->getSolution();
2058 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false :
true;
2059 solverFinished(solution.orientation, solution.ra, solution.dec, solution.pixscale, eastToTheRight);
2065 pi->stopAnimation();
2066 stopB->setEnabled(
false);
2067 solveB->setEnabled(
true);
2069 sOrientation = orientation;
2073 double elapsed = solverTimer.
elapsed() / 1000.0;
2075 appendLogText(
i18n(
"Solver completed after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2077 m_AlignTimer.
stop();
2078 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && m_RemoteParserDevice && remoteParser.get())
2085 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2086 targetChip->getBinning(&binx, &biny);
2088 if (Options::alignmentLogging())
2090 QString parityString = eastToTheRight ?
"neg" :
"pos";
2091 appendLogText(
i18n(
"Solver RA (%1) DEC (%2) Orientation (%3) Pixel Scale (%4) Parity (%5)",
QString::number(ra,
'f', 5),
2097 if (!m_SolveFromFile &&
2098 (isEqual(m_FOVWidth, 0) || m_EffectiveFOVPending || std::fabs(pixscale - m_FOVPixelScale) > 0.005) &&
2101 double newFOVW = m_CameraWidth * pixscale / binx / 60.0;
2102 double newFOVH = m_CameraHeight * pixscale / biny / 60.0;
2104 calculateEffectiveFocalLength(newFOVW);
2105 saveNewEffectiveFOV(newFOVW, newFOVH);
2107 m_EffectiveFOVPending =
false;
2110 m_AlignCoord.
setRA0(ra / 15.0);
2116 m_AlignCoord.
EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
2119 if (!m_SolveFromFile)
2122 calculateAlignTargetDiff();
2128 double solverPA = KSUtils::rotationToPositionAngle(orientation);
2129 solverFOV->setCenter(m_AlignCoord);
2130 solverFOV->setPA(solverPA);
2131 solverFOV->setImageDisplay(Options::astrometrySolverOverlay());
2133 sensorFOV->setPA(solverPA);
2138 getFormattedCoords(m_AlignCoord.
ra().
Hours(), m_AlignCoord.
dec().
Degrees(), ra_dms, dec_dms);
2140 SolverRAOut->setText(ra_dms);
2141 SolverDecOut->setText(dec_dms);
2143 if (Options::astrometrySolverWCS())
2145 auto ccdRotation = m_Camera->getNumber(
"CCD_ROTATION");
2148 auto rotation = ccdRotation->findWidgetByName(
"CCD_ROTATION_VALUE");
2151 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
2152 rotation->setValue(orientation);
2153 clientManager->sendNewProperty(ccdRotation);
2155 if (m_wcsSynced ==
false)
2158 i18n(
"WCS information updated. Images captured from this point forward shall have valid WCS."));
2161 auto telescopeInfo = m_Mount->getNumber(
"TELESCOPE_INFO");
2163 clientManager->sendNewProperty(telescopeInfo);
2171 m_CaptureErrorCounter = 0;
2172 m_SlewErrorCounter = 0;
2173 m_CaptureTimeoutCounter = 0;
2176 i18n(
"Solution coordinates: RA (%1) DEC (%2) Telescope Coordinates: RA (%3) DEC (%4) Target Coordinates: RA (%5) DEC (%6)",
2184 if (!m_SolveFromFile && m_CurrentGotoMode == GOTO_SLEW)
2186 dms diffDeg(m_TargetDiffTotal / 3600.0);
2187 appendLogText(
i18n(
"Target is within %1 degrees of solution coordinates.", diffDeg.
toDMSString()));
2190 if (rememberUploadMode != m_Camera->getUploadMode())
2191 m_Camera->setUploadMode(rememberUploadMode);
2194 if (m_RememberCameraFastExposure)
2196 m_RememberCameraFastExposure =
false;
2197 m_Camera->setFastExposureEnabled(
true);
2202 int currentRow = solutionTable->rowCount() - 1;
2203 if (!m_SolveFromFile)
2205 stopProgressAnimation();
2206 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
2209 if (m_Rotator !=
nullptr && m_Rotator->isConnected())
2211 if (
auto absAngle = m_Rotator->getNumber(
"ABS_ROTATOR_ANGLE"))
2214 sRawAngle = absAngle->at(0)->getValue();
2215 double OffsetAngle = RotatorUtils::Instance()->calcOffsetAngle(sRawAngle, solverPA);
2216 RotatorUtils::Instance()->updateOffset(OffsetAngle);
2218 auto reverseStatus =
"Unknown";
2219 auto reverseProperty = m_Rotator->
getSwitch(
"ROTATOR_REVERSE");
2220 if (reverseProperty)
2222 if (reverseProperty->at(0)->getState() == ISS_ON)
2223 reverseStatus =
"Reversed Direction";
2225 reverseStatus =
"Normal Direction";
2227 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << sRawAngle <<
"Rotator PA:" << solverPA
2228 <<
"Rotator Offset:" << OffsetAngle <<
"Direction:" << reverseStatus;
2230 emit newSolverResults(solverPA, ra, dec, pixscale);
2232 appendLogText(
i18n(
"Camera position angle is %1 degrees.", RotatorUtils::Instance()->calcCameraAngle(sRawAngle,
false)));
2239 {
"camera", m_Camera->getDeviceName()},
2240 {
"ra", SolverRAOut->text()},
2241 {
"de", SolverDecOut->text()},
2242 {
"dRA", m_TargetDiffRA},
2243 {
"dDE", m_TargetDiffDE},
2244 {
"targetDiff", m_TargetDiffTotal},
2247 {
"fov", FOVOut->text()},
2252 emit newStatus(state);
2253 solverIterations = 0;
2254 KSNotification::event(
QLatin1String(
"AlignSuccessful"),
i18n(
"Astrometry alignment completed successfully"),
2255 KSNotification::Align);
2257 switch (m_CurrentGotoMode)
2262 if (!m_SolveFromFile)
2264 stopProgressAnimation();
2265 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2266 solutionTable->setItem(currentRow, 3, statusReport.release());
2272 if (m_SolveFromFile || m_TargetDiffTotal >
static_cast<double>(alignAccuracyThreshold->value()))
2274 if (!m_SolveFromFile && ++solverIterations == MAXIMUM_SOLVER_ITERATIONS)
2276 appendLogText(
i18n(
"Maximum number of iterations reached. Solver failed."));
2278 if (!m_SolveFromFile)
2280 statusReport->setIcon(
QIcon(
":/icons/AlignFailure.svg"));
2281 solutionTable->setItem(currentRow, 3, statusReport.release());
2288 targetAccuracyNotMet =
true;
2290 if (!m_SolveFromFile)
2292 stopProgressAnimation();
2293 statusReport->setIcon(
QIcon(
":/icons/AlignWarning.svg"));
2294 solutionTable->setItem(currentRow, 3, statusReport.release());
2301 stopProgressAnimation();
2302 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2303 solutionTable->setItem(currentRow, 3, statusReport.release());
2305 appendLogText(
i18n(
"Target is within acceptable range."));
2309 if (!m_SolveFromFile)
2311 stopProgressAnimation();
2312 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2313 solutionTable->setItem(currentRow, 3, statusReport.release());
2318 solverFOV->setProperty(
"visible",
true);
2320 if (!matchPAHStage(PAA::PAH_IDLE))
2321 m_PolarAlignmentAssistant->processPAHStage(orientation, ra, dec, pixscale, eastToTheRight,
2322 m_StellarSolver->getSolutionHealpix(),
2323 m_StellarSolver->getSolutionIndexNumber());
2329 solveB->setEnabled(
false);
2330 loadSlewB->setEnabled(
false);
2336 emit newStatus(state);
2338 solveB->setEnabled(
true);
2339 loadSlewB->setEnabled(
true);
2347 if (Options::saveFailedAlignImages())
2355 extraFilenameInfo.
append(
QString(
"_s%1u%2").arg(m_ScaleUsed, 0,
'f', 3)
2356 .arg(Options::astrometryImageScaleUnits()));
2358 extraFilenameInfo.
append(
QString(
"_r%1_d%2").arg(m_RAUsed, 0,
'f', 5).arg(m_DECUsed, 0,
'f', 5));
2362 QString name =
"failed_align_frame_" + now.
toString(
"yyyy-MM-dd-HH-mm-ss") + extraFilenameInfo +
".fits";
2363 QString filename = path + QStringLiteral(
"/") + name;
2366 m_ImageData->saveImage(filename);
2367 appendLogText(
i18n(
"Saving failed solver image to %1", filename));
2374 if (Options::astrometryUseImageScale() && useBlindScale == BLIND_IDLE)
2376 appendLogText(
i18n(
"Solver failed. Retrying without scale constraint."));
2377 useBlindScale = BLIND_ENGAGNED;
2378 setAlignTableResult(ALIGN_RESULT_FAILED);
2384 if (Options::astrometryUsePosition() && useBlindPosition == BLIND_IDLE)
2386 appendLogText(
i18n(
"Solver failed. Retrying without position constraint."));
2387 useBlindPosition = BLIND_ENGAGNED;
2388 setAlignTableResult(ALIGN_RESULT_FAILED);
2394 appendLogText(
i18n(
"Solver Failed."));
2395 if(!Options::alignmentLogging())
2397 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."));
2399 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"),
2400 KSNotification::Align, KSNotification::Alert);
2403 pi->stopAnimation();
2404 stopB->setEnabled(
false);
2405 solveB->setEnabled(
true);
2406 loadSlewB->setEnabled(
true);
2408 m_AlignTimer.
stop();
2410 m_SolveFromFile =
false;
2411 solverIterations = 0;
2412 m_CaptureErrorCounter = 0;
2413 m_CaptureTimeoutCounter = 0;
2414 m_SlewErrorCounter = 0;
2417 emit newStatus(state);
2419 solverFOV->setProperty(
"visible",
false);
2421 setAlignTableResult(ALIGN_RESULT_FAILED);
2427 if (Options::astrometryUseRotator())
2429 if (m_SolveFromFile)
2431 m_TargetPositionAngle = solverFOV->PA();
2433 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file: Setting target PA to:" << m_TargetPositionAngle;
2437 currentRotatorPA = solverFOV->PA();
2438 if (std::isnan(m_TargetPositionAngle) ==
false)
2441 if (RotatorUtils::Instance()->Instance()->checkImageFlip() && (Options::astrometryFlipRotationAllowed()))
2444 sRawAngle = RotatorUtils::Instance()->calcRotatorAngle(m_TargetPositionAngle);
2445 m_TargetPositionAngle = RotatorUtils::Instance()->calcCameraAngle(sRawAngle,
true);
2446 RotatorUtils::Instance()->setImagePierside(ISD::Mount::PIER_UNKNOWN);
2449 if (m_Rotator !=
nullptr && m_Rotator->isConnected())
2451 if(fabs(KSUtils::rangePA(currentRotatorPA - m_TargetPositionAngle)) * 60 >
2452 Options::astrometryRotatorThreshold())
2455 emit newSolverResults(m_TargetPositionAngle, 0, 0, 0);
2456 appendLogText(
i18n(
"Setting camera position angle to %1 degrees ...", m_TargetPositionAngle));
2458 emit newStatus(state);
2463 appendLogText(
i18n(
"Camera position angle is within acceptable range."));
2465 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
2471 double current = currentRotatorPA;
2472 double target = m_TargetPositionAngle;
2474 double diff = KSUtils::rangePA(current - target);
2475 double threshold = Options::astrometryRotatorThreshold() / 60.0;
2477 appendLogText(
i18n(
"Current PA is %1; Target PA is %2; diff: %3", current, target, diff));
2479 emit manualRotatorChanged(current, target, threshold);
2481 m_ManualRotator->setRotatorDiff(current, target, diff);
2482 if (fabs(diff) > threshold)
2484 targetAccuracyNotMet =
true;
2485 m_ManualRotator->
show();
2486 m_ManualRotator->
raise();
2488 emit newStatus(state);
2493 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
2494 targetAccuracyNotMet =
false;
2505 m_CaptureTimer.
stop();
2506 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
2507 m_StellarSolver->abort();
2508 else if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser)
2509 remoteParser->stopSolver();
2511 pi->stopAnimation();
2512 stopB->setEnabled(
false);
2513 solveB->setEnabled(
true);
2514 loadSlewB->setEnabled(
true);
2516 m_SolveFromFile =
false;
2517 solverIterations = 0;
2518 m_CaptureErrorCounter = 0;
2519 m_CaptureTimeoutCounter = 0;
2520 m_SlewErrorCounter = 0;
2521 m_AlignTimer.
stop();
2526 if (rememberUploadMode != m_Camera->getUploadMode())
2527 m_Camera->setUploadMode(rememberUploadMode);
2530 if (m_RememberCameraFastExposure)
2532 m_RememberCameraFastExposure =
false;
2533 m_Camera->setFastExposureEnabled(
true);
2536 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2539 if (matchPAHStage(PAA::PAH_POST_REFRESH))
2541 if (targetChip->isCapturing())
2542 targetChip->abortExposure();
2544 appendLogText(
i18n(
"Refresh is complete."));
2548 if (targetChip->isCapturing())
2550 targetChip->abortExposure();
2551 appendLogText(
i18n(
"Capture aborted."));
2555 double elapsed = solverTimer.
elapsed() / 1000.0;
2557 appendLogText(
i18n(
"Solver aborted after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2562 emit newStatus(state);
2564 setAlignTableResult(ALIGN_RESULT_FAILED);
2569 int currentRow = solutionTable->rowCount() - 1;
2576 QWidget *indicator = solutionTable->cellWidget(currentRow, 3);
2577 if (indicator ==
nullptr)
2582void Align::stopProgressAnimation()
2585 if (progress_indicator !=
nullptr)
2593 result << sOrientation << sRA << sDEC;
2598void Align::appendLogText(
const QString &text)
2600 m_LogText.
insert(0,
i18nc(
"log entry; %1 is the date, %2 is the text",
"%1 %2",
2601 KStarsData::Instance()->lt().toString(
"yyyy-MM-ddThh:mm:ss"), text));
2603 qCInfo(KSTARS_EKOS_ALIGN) << text;
2608void Align::clearLog()
2616 if (prop.isNameMatch(
"EQUATORIAL_EOD_COORD") || prop.isNameMatch(
"EQUATORIAL_COORD"))
2618 auto nvp = prop.getNumber();
2621 getFormattedCoords(m_TelescopeCoord.
ra().
Hours(), m_TelescopeCoord.
dec().
Degrees(), ra_dms, dec_dms);
2623 ScopeRAOut->setText(ra_dms);
2624 ScopeDecOut->setText(dec_dms);
2630 m_wasSlewStarted =
false;
2639 if (m_wasSlewStarted && Options::astrometryAutoUpdatePosition())
2642 opsAstrometry->estRA->setText(ra_dms);
2643 opsAstrometry->estDec->setText(dec_dms);
2645 Options::setAstrometryPositionRA(nvp->np[0].value * 15);
2646 Options::setAstrometryPositionDE(nvp->np[1].value);
2652 if (m_Dome && m_Dome->isMoving())
2659 if (m_wasSlewStarted && matchPAHStage(PAA::PAH_FIND_CP))
2662 m_wasSlewStarted =
false;
2663 appendLogText(
i18n(
"Mount completed slewing near celestial pole. Capture again to verify."));
2666 m_PolarAlignmentAssistant->setPAHStage(PAA::PAH_FIRST_CAPTURE);
2677 m_wasSlewStarted =
false;
2679 if (m_CurrentGotoMode == GOTO_SLEW)
2686 appendLogText(
i18n(
"Mount is synced to solution coordinates."));
2692 i18n(
"Astrometry alignment completed successfully"), KSNotification::Align);
2694 emit newStatus(state);
2695 solverIterations = 0;
2696 loadSlewB->setEnabled(
true);
2703 if (!didSlewStart())
2706 qCDebug(KSTARS_EKOS_ALIGN) <<
"Mount slew planned, but not started slewing yet...";
2711 m_wasSlewStarted =
false;
2712 if (m_SolveFromFile)
2714 m_SolveFromFile =
false;
2715 m_TargetPositionAngle = solverFOV->PA();
2716 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file: Setting target PA to" << m_TargetPositionAngle;
2719 emit newStatus(state);
2721 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
2722 appendLogText(
i18n(
"Settling..."));
2723 m_resetCaptureTimeoutCounter =
true;
2724 m_CaptureTimer.
start(alignSettlingTime->value());
2727 else if (m_CurrentGotoMode == GOTO_SLEW || (m_MountModel && m_MountModel->isRunning()))
2729 if (targetAccuracyNotMet)
2730 appendLogText(
i18n(
"Slew complete. Target accuracy is not met, running solver again..."));
2732 appendLogText(
i18n(
"Slew complete. Solving Alignment Point. . ."));
2734 targetAccuracyNotMet =
false;
2737 emit newStatus(state);
2739 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
2740 appendLogText(
i18n(
"Settling..."));
2741 m_resetCaptureTimeoutCounter =
true;
2742 m_CaptureTimer.
start(alignSettlingTime->value());
2749 m_wasSlewStarted =
false;
2760 m_wasSlewStarted =
true;
2762 handleMountMotion();
2770 m_wasSlewStarted =
false;
2775 appendLogText(
i18n(
"Syncing failed."));
2777 appendLogText(
i18n(
"Slewing failed."));
2779 if (++m_SlewErrorCounter == 3)
2786 if (m_CurrentGotoMode == GOTO_SLEW)
2797 RUN_PAH(processMountRotation(m_TelescopeCoord.
ra(), alignSettlingTime->value()));
2799 else if (prop.isNameMatch(
"ABS_ROTATOR_ANGLE"))
2801 auto nvp = prop.getNumber();
2802 double RAngle = nvp->np[0].value;
2803 currentRotatorPA = RotatorUtils::Instance()->calcCameraAngle(RAngle,
false);
2812 if (std::isnan(m_TargetPositionAngle) ==
false && state ==
ALIGN_ROTATING && nvp->s == IPS_OK)
2814 auto diff = fabs(RotatorUtils::Instance()->DiffPA(currentRotatorPA - m_TargetPositionAngle)) * 60;
2815 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << RAngle <<
"Current PA:" << currentRotatorPA
2816 <<
"Target PA:" << m_TargetPositionAngle <<
"Diff (arcmin):" << diff <<
"Offset:"
2817 << Options::pAOffset();
2819 if (diff <= Options::astrometryRotatorThreshold())
2821 appendLogText(
i18n(
"Rotator reached camera position angle."));
2832 if (nvp->s == IPS_OK)
2834 appendLogText(i18n(
"Rotator failed to arrive at the requested position angle (Deviation %1 arcmin).", diff));
2835 setState(ALIGN_FAILED);
2836 emit newStatus(state);
2837 solveB->setEnabled(true);
2838 loadSlewB->setEnabled(true);
2843 else if (m_estimateRotatorTimeFrame)
2845 m_RotatorTimeFrame = RotatorUtils::Instance()->calcTimeFrame(RAngle);
2848 else if (prop.isNameMatch(
"DOME_MOTION"))
2851 if (domeReady ==
false && prop.getState() == IPS_OK)
2856 handleMountStatus();
2859 else if (prop.isNameMatch(
"TELESCOPE_MOTION_NS") || prop.isNameMatch(
"TELESCOPE_MOTION_WE"))
2861 switch (prop.getState())
2865 handleMountMotion();
2866 m_wasSlewStarted =
true;
2869 qCDebug(KSTARS_EKOS_ALIGN) <<
"Mount motion finished.";
2870 handleMountStatus();
2876void Align::handleMountMotion()
2880 if (matchPAHStage(PAA::PAH_IDLE))
2883 appendLogText(
i18n(
"Slew detected, suspend solving..."));
2891void Align::handleMountStatus()
2893 auto nvp = m_Mount->getNumber(m_Mount->isJ2000() ?
"EQUATORIAL_COORD" :
2894 "EQUATORIAL_EOD_COORD");
2903 if (m_SolveFromFile)
2908 m_DestinationCoord = m_AlignCoord;
2909 m_TargetCoord = m_AlignCoord;
2911 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file. Setting Target Coordinates align coords. RA:"
2917 else if (m_CurrentGotoMode == GOTO_SYNC)
2919 else if (m_CurrentGotoMode == GOTO_SLEW)
2927 if (m_Mount->Sync(&m_AlignCoord))
2929 emit newStatus(state);
2936 emit newStatus(state);
2937 appendLogText(
i18n(
"Syncing failed."));
2944 emit newStatus(state);
2952 if (m_Mount->Slew(&m_TargetCoord))
2954 slewStartTimer.
start();
2955 appendLogText(
i18n(
"Slewing to target coordinates: RA (%1) DEC (%2).", m_TargetCoord.
ra().
toHMSString(),
2960 appendLogText(
i18n(
"Slewing to target coordinates: RA (%1) DEC (%2) is rejected. (see notification)",
2964 emit newStatus(state);
2965 solveB->setEnabled(
true);
2966 loadSlewB->setEnabled(
true);
2972 if (canSync && !m_SolveFromFile)
2976 if (Ekos::Manager::Instance()->getCurrentJobName().isEmpty())
2978 KSNotification::event(
QLatin1String(
"EkosSchedulerTelescopeSynced"),
2979 i18n(
"Ekos job (%1) - Telescope synced",
2980 Ekos::Manager::Instance()->getCurrentJobName()));
2984 if (Options::astrometryDifferentialSlewing())
2990 qCDebug(KSTARS_EKOS_ALIGN) <<
"Differential slew - Target RA:" << m_TargetCoord.
ra().
toHMSString()
3003void Align::getFormattedCoords(
double ra,
double dec,
QString &ra_str,
QString &dec_str)
3014 dec_str =
QString(
"-%1:%2:%3")
3029 "Images (*.fits *.fits.fz *.fit *.fts *.xisf "
3030 "*.jpg *.jpeg *.png *.gif *.bmp "
3031 "*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw *.orf)");
3037 if (fileInfo.
exists() ==
false)
3042 RotatorGOTO =
false;
3044 m_SolveFromFile =
true;
3046 if (m_PolarAlignmentAssistant)
3047 m_PolarAlignmentAssistant->stopPAHProcess();
3049 slewR->setChecked(
true);
3050 m_CurrentGotoMode = GOTO_SLEW;
3052 solveB->setEnabled(
false);
3053 loadSlewB->setEnabled(
false);
3054 stopB->setEnabled(
true);
3055 pi->startAnimation();
3057 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && m_RemoteParserDevice ==
nullptr)
3059 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
3063 m_ImageData.
clear();
3065 m_AlignView->loadFile(fileURL);
3074 RotatorGOTO =
false;
3075 m_SolveFromFile =
true;
3076 RUN_PAH(stopPAHProcess());
3077 slewR->setChecked(
true);
3078 m_CurrentGotoMode = GOTO_SLEW;
3079 solveB->setEnabled(
false);
3080 loadSlewB->setEnabled(
false);
3081 stopB->setEnabled(
true);
3082 pi->startAnimation();
3086 m_ImageData.
clear();
3089 data->setExtension(extension);
3090 data->loadFromBuffer(image);
3091 m_AlignView->loadData(data);
3098 alignExposure->setValue(value);
3106 alignBinning->blockSignals(
true);
3107 alignBinning->setCurrentIndex(binIndex);
3108 alignBinning->blockSignals(
false);
3112 if (Options::astrometryImageScaleUnits() == SSolver::ARCSEC_PER_PIX)
3118 if (m_FilterWheel && m_FilterWheel == device)
3127 m_FilterWheel = device;
3131 connect(m_FilterWheel, &ISD::ConcreteDevice::Connected,
this, [
this]()
3133 FilterPosLabel->setEnabled(
true);
3134 alignFilter->setEnabled(
true);
3136 connect(m_FilterWheel, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
3138 FilterPosLabel->setEnabled(
false);
3139 alignFilter->setEnabled(
false);
3143 auto isConnected = m_FilterWheel && m_FilterWheel->isConnected();
3144 FilterPosLabel->setEnabled(isConnected);
3145 alignFilter->setEnabled(isConnected);
3154 return m_FilterWheel->getDeviceName();
3163 alignFilter->setCurrentText(filter);
3173 return alignFilter->currentText();
3178 alignFilter->
clear();
3182 FilterPosLabel->setEnabled(
false);
3183 alignFilter->setEnabled(
false);
3187 auto isConnected = m_FilterWheel->isConnected();
3188 FilterPosLabel->setEnabled(isConnected);
3189 alignFilter->setEnabled(alignUseCurrentFilter->isChecked() ==
false);
3191 setupFilterManager();
3193 alignFilter->addItems(m_FilterManager->getFilterLabels());
3194 currentFilterPosition = m_FilterManager->getFilterPosition();
3196 if (alignUseCurrentFilter->isChecked())
3199 alignFilter->setCurrentIndex(currentFilterPosition - 1);
3204 auto filter = m_Settings[
"alignFilter"];
3205 if (filter.isValid())
3206 alignFilter->setCurrentText(filter.toString());
3212 if ((Manager::Instance()->existRotatorController()) && (!m_Rotator || !(Device == m_Rotator)))
3214 rotatorB->setEnabled(
false);
3218 m_RotatorControlPanel->close();
3223 if (Manager::Instance()->getRotatorController(m_Rotator->getDeviceName(), m_RotatorControlPanel))
3228 m_RotatorControlPanel->show();
3229 m_RotatorControlPanel->raise();
3231 rotatorB->setEnabled(
true);
3237void Align::setWCSEnabled(
bool enable)
3242 auto wcsControl = m_Camera->
getSwitch(
"WCS_CONTROL");
3247 auto wcs_enable = wcsControl->findWidgetByName(
"WCS_ENABLE");
3248 auto wcs_disable = wcsControl->findWidgetByName(
"WCS_DISABLE");
3250 if (!wcs_enable || !wcs_disable)
3253 if ((wcs_enable->getState() == ISS_ON && enable) || (wcs_disable->getState() == ISS_ON && !enable))
3256 wcsControl->reset();
3259 appendLogText(
i18n(
"World Coordinate System (WCS) is enabled."));
3260 wcs_enable->setState(ISS_ON);
3264 appendLogText(
i18n(
"World Coordinate System (WCS) is disabled."));
3265 wcs_disable->setState(ISS_ON);
3266 m_wcsSynced =
false;
3269 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
3271 clientManager->sendNewProperty(wcsControl);
3276 INDI_UNUSED(targetChip);
3277 INDI_UNUSED(remaining);
3279 if (state == IPS_ALERT)
3281 if (++m_CaptureErrorCounter == 3 && !matchPAHStage(PolarAlignmentAssistant::PAH_REFRESH))
3283 appendLogText(
i18n(
"Capture error. Aborting..."));
3288 appendLogText(
i18n(
"Restarting capture attempt #%1", m_CaptureErrorCounter));
3289 setAlignTableResult(ALIGN_RESULT_FAILED);
3294void Align::setAlignTableResult(AlignResult result)
3301 if (progress_indicator ==
nullptr || ! progress_indicator->
isAnimated())
3303 stopProgressAnimation();
3308 case ALIGN_RESULT_SUCCESS:
3309 icon =
QIcon(
":/icons/AlignSuccess.svg");
3312 case ALIGN_RESULT_WARNING:
3313 icon =
QIcon(
":/icons/AlignWarning.svg");
3316 case ALIGN_RESULT_FAILED:
3318 icon =
QIcon(
":/icons/AlignFailure.svg");
3321 int currentRow = solutionTable->rowCount() - 1;
3322 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
3326 solutionTable->setItem(currentRow, 3, statusReport);
3329void Align::setFocusStatus(Ekos::FocusState state)
3331 m_FocusState = state;
3334uint8_t Align::getSolverDownsample(uint16_t binnedW)
3336 uint8_t downsample = Options::astrometryDownsample();
3338 if (!Options::astrometryAutoDownsample())
3341 while (downsample < 8)
3343 if (binnedW / downsample <= 1024)
3352void Align::setCaptureStatus(CaptureState newState)
3357 if (m_Mount && m_Mount->hasAlignmentModel() && Options::resetMountModelAfterMeridian())
3359 qCDebug(KSTARS_EKOS_ALIGN) <<
"Post meridian flip mount model reset" << (m_Mount->clearAlignmentModel() ?
3360 "successful." :
"failed.");
3362 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
3363 appendLogText(
i18n(
"Settling..."));
3364 m_resetCaptureTimeoutCounter =
true;
3365 m_CaptureTimer.
start(alignSettlingTime->value());
3371 if (std::isnan(m_TargetPositionAngle) ==
false)
3372 m_TargetPositionAngle = KSUtils::rangePA(m_TargetPositionAngle + 180.0);
3378 m_CaptureState = newState;
3381void Align::showFITSViewer()
3383 static int lastFVTabID = -1;
3390 fv->loadData(m_ImageData, url, &lastFVTabID);
3391 connect(fv.
get(), &FITSViewer::terminated,
this, [
this]()
3396 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) ==
false)
3397 fv->loadData(m_ImageData, url, &lastFVTabID);
3403void Align::toggleAlignWidgetFullScreen()
3405 if (alignWidget->parent() ==
nullptr)
3407 alignWidget->setParent(
this);
3408 rightLayout->insertWidget(0, alignWidget);
3409 alignWidget->showNormal();
3413 alignWidget->setParent(
nullptr);
3414 alignWidget->setWindowTitle(
i18nc(
"@title:window",
"Align Frame"));
3416 alignWidget->showMaximized();
3417 alignWidget->show();
3421void Align::setMountStatus(ISD::Mount::Status newState)
3425 case ISD::Mount::MOUNT_PARKED:
3426 solveB->setEnabled(
false);
3427 loadSlewB->setEnabled(
false);
3429 case ISD::Mount::MOUNT_IDLE:
3430 solveB->setEnabled(
true);
3431 loadSlewB->setEnabled(
true);
3433 case ISD::Mount::MOUNT_PARKING:
3434 solveB->setEnabled(
false);
3435 loadSlewB->setEnabled(
false);
3437 case ISD::Mount::MOUNT_SLEWING:
3438 case ISD::Mount::MOUNT_MOVING:
3439 solveB->setEnabled(
false);
3440 loadSlewB->setEnabled(
false);
3446 solveB->setEnabled(
true);
3447 if (matchPAHStage(PAA::PAH_IDLE))
3449 loadSlewB->setEnabled(
true);
3455 RUN_PAH(setMountStatus(newState));
3460 m_RemoteParserDevice = device;
3462 remoteSolverR->setEnabled(
true);
3463 if (remoteParser.get() !=
nullptr)
3465 remoteParser->setAstrometryDevice(m_RemoteParserDevice);
3473 solverFOV->setImageDisplay(Options::astrometrySolverWCS());
3474 m_AlignTimer.
setInterval(Options::astrometryTimeout() * 1000);
3476 m_RotatorControlPanel->updateFlipPolicy(Options::astrometryFlipRotationAllowed());
3479void Align::setupOptions()
3487 opsAlign =
new OpsAlign(
this);
3493 opsPrograms =
new OpsPrograms(
this);
3494 page = dialog->
addPage(opsPrograms,
i18n(
"External & Online Programs"));
3497 opsAstrometry =
new OpsAstrometry(
this);
3498 page = dialog->
addPage(opsAstrometry,
i18n(
"Scale & Position"));
3499 page->
setIcon(
QIcon(
":/icons/center_telescope_red.svg"));
3501 optionsProfileEditor =
new StellarSolverProfileEditor(
this, Ekos::AlignProfiles, dialog);
3502 page = dialog->
addPage(optionsProfileEditor,
i18n(
"Align Options Profiles Editor"));
3503 connect(optionsProfileEditor, &StellarSolverProfileEditor::optionsProfilesUpdated,
this, [
this]()
3505 if(
QFile(savedOptionsProfiles).exists())
3506 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
3508 m_StellarSolverProfiles = getDefaultAlignOptionsProfiles();
3509 opsAlign->reloadOptionsProfiles();
3513 connect(opsAlign, &OpsAlign::needToLoadProfile,
this, [
this, dialog, page](
const QString & profile)
3515 optionsProfileEditor->loadProfile(profile);
3519 opsAstrometryIndexFiles =
new OpsAstrometryIndexFiles(
this);
3520 m_IndexFilesPage = dialog->
addPage(opsAstrometryIndexFiles,
i18n(
"Index Files"));
3524void Align::setupSolutionTable()
3528 clearAllSolutionsB->setIcon(
3535 exportSolutionsCSV->setIcon(
3550void Align::setupPlot()
3552 double accuracyRadius = alignAccuracyThreshold->value();
3555 alignPlot->setSelectionTolerance(10);
3566 alignPlot->xAxis->setTickLabelColor(
Qt::white);
3567 alignPlot->yAxis->setTickLabelColor(
Qt::white);
3569 alignPlot->xAxis->setLabelColor(
Qt::white);
3570 alignPlot->yAxis->setLabelColor(
Qt::white);
3572 alignPlot->xAxis->setLabelFont(
QFont(
font().family(), 10));
3573 alignPlot->yAxis->setLabelFont(
QFont(
font().family(), 10));
3575 alignPlot->xAxis->setLabelPadding(2);
3576 alignPlot->yAxis->setLabelPadding(2);
3585 alignPlot->xAxis->setLabel(
i18n(
"dRA (arcsec)"));
3586 alignPlot->yAxis->setLabel(
i18n(
"dDE (arcsec)"));
3588 alignPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
3589 alignPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
3594 alignPlot->addGraph();
3604 alignPlot->resize(190, 190);
3605 alignPlot->replot();
3608void Align::setupFilterManager()
3611 if (m_FilterManager)
3612 m_FilterManager->disconnect(
this);
3615 Ekos::Manager::Instance()->createFilterManager(m_FilterWheel);
3618 Ekos::Manager::Instance()->getFilterManager(m_FilterWheel->getDeviceName(), m_FilterManager);
3620 connect(m_FilterManager.
get(), &FilterManager::ready,
this, [
this]()
3622 if (filterPositionPending)
3624 m_FocusState = FOCUS_IDLE;
3625 filterPositionPending = false;
3626 captureAndSolve(false);
3630 connect(m_FilterManager.get(), &FilterManager::failed,
this, [
this]()
3632 appendLogText(i18n(
"Filter operation failed."));
3637 connect(m_FilterManager.get(), &FilterManager::newStatus,
this, [
this](Ekos::FilterState filterState)
3639 if (filterPositionPending)
3641 switch (filterState)
3644 appendLogText(i18n(
"Changing focus offset by %1 steps...", m_FilterManager->getTargetFilterOffset()));
3649 const int filterComboIndex = m_FilterManager->getTargetFilterPosition() - 1;
3650 if (filterComboIndex >= 0 && filterComboIndex < alignFilter->count())
3651 appendLogText(i18n(
"Changing filter to %1...", alignFilter->itemText(filterComboIndex)));
3655 case FILTER_AUTOFOCUS:
3656 appendLogText(i18n(
"Auto focus on filter change..."));
3665 connect(m_FilterManager.get(), &FilterManager::labelsChanged,
this, &Align::checkFilter);
3666 connect(m_FilterManager.get(), &FilterManager::positionChanged,
this, &Align::checkFilter);
3669QVariantMap Align::getEffectiveFOV()
3671 KStarsData::Instance()->
userdb()->GetAllEffectiveFOVs(effectiveFOVs);
3673 m_FOVWidth = m_FOVHeight = 0;
3675 for (
auto &map : effectiveFOVs)
3677 if (map[
"Profile"].
toString() == m_ActiveProfile->name && map[
"Train"].toString() == opticalTrain())
3679 if (isEqual(map[
"Width"].toInt(), m_CameraWidth) &&
3680 isEqual(map[
"Height"].toInt(), m_CameraHeight) &&
3681 isEqual(map[
"PixelW"].toDouble(), m_CameraPixelWidth) &&
3682 isEqual(map[
"PixelH"].toDouble(), m_CameraPixelHeight) &&
3683 isEqual(map[
"FocalLength"].toDouble(), m_FocalLength) &&
3684 isEqual(map[
"FocalRedcuer"].toDouble(), m_Reducer) &&
3685 isEqual(map[
"FocalRatio"].toDouble(), m_FocalRatio))
3687 m_FOVWidth =
map[
"FovW"].toDouble();
3688 m_FOVHeight =
map[
"FovH"].toDouble();
3694 return QVariantMap();
3697void Align::saveNewEffectiveFOV(
double newFOVW,
double newFOVH)
3699 if (newFOVW < 0 || newFOVH < 0 || (isEqual(newFOVW, m_FOVWidth) && isEqual(newFOVH, m_FOVHeight)))
3702 QVariantMap effectiveMap = getEffectiveFOV();
3705 if (effectiveMap.isEmpty() ==
false)
3706 KStarsData::Instance()->
userdb()->DeleteEffectiveFOV(effectiveMap[
"id"].
toString());
3709 if (newFOVW == 0.0 && newFOVH == 0.0)
3715 effectiveMap[
"Profile"] = m_ActiveProfile->name;
3716 effectiveMap[
"Train"] = opticalTrainCombo->currentText();
3717 effectiveMap[
"Width"] = m_CameraWidth;
3718 effectiveMap[
"Height"] = m_CameraHeight;
3719 effectiveMap[
"PixelW"] = m_CameraPixelWidth;
3720 effectiveMap[
"PixelH"] = m_CameraPixelHeight;
3721 effectiveMap[
"FocalLength"] = m_FocalLength;
3722 effectiveMap[
"FocalReducer"] = m_Reducer;
3723 effectiveMap[
"FocalRatio"] = m_FocalRatio;
3724 effectiveMap[
"FovW"] = newFOVW;
3725 effectiveMap[
"FovH"] = newFOVH;
3727 KStarsData::Instance()->
userdb()->AddEffectiveFOV(effectiveMap);
3733void Align::zoomAlignView()
3735 m_AlignView->ZoomDefault();
3741 emit newFrame(m_AlignView);
3745void Align::setAlignZoom(
double scale)
3748 m_AlignView->ZoomIn();
3750 m_AlignView->ZoomOut();
3756 emit newFrame(m_AlignView);
3760void Align::syncFOV()
3762 QString newFOV = FOVOut->text();
3765 if (
match.hasMatch())
3767 double newFOVW =
match.captured(1).toDouble();
3768 double newFOVH =
match.captured(2).toDouble();
3771 saveNewEffectiveFOV(newFOVW, newFOVH);
3773 FOVOut->setStyleSheet(
QString());
3777 KSNotification::error(
i18n(
"Invalid FOV."));
3778 FOVOut->setStyleSheet(
"background-color:red");
3783bool Align::didSlewStart()
3785 if (m_wasSlewStarted)
3787 if (slewStartTimer.isValid() && slewStartTimer.elapsed() > MAX_WAIT_FOR_SLEW_START_MSEC)
3789 qCDebug(KSTARS_EKOS_ALIGN) <<
"Slew timed out...waited > 10s, it must have started already.";
3795void Align::setTargetCoords(
double ra0,
double de0)
3806 m_TargetCoord = targetCoord;
3807 qCInfo(KSTARS_EKOS_ALIGN) <<
"Target coordinates updated to JNow RA:" << m_TargetCoord.
ra().
toHMSString()
3808 <<
"DE:" << m_TargetCoord.dec().toDMSString();
3813 return QList<double>() << m_TargetCoord.ra0().Hours() << m_TargetCoord.dec0().Degrees();
3816void Align::setTargetPositionAngle(
double value)
3818 m_TargetPositionAngle = value;
3819 qCDebug(KSTARS_EKOS_ALIGN) <<
"Target PA updated to: " << m_TargetPositionAngle;
3822void Align::calculateAlignTargetDiff()
3824 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
3825 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
3826 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
3827 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
3828 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
3829 matchPAHStage(PAA::PAH_THIRD_SOLVE) ||
3830 nothingR->isChecked() ||
3834 if (!Options::astrometryDifferentialSlewing())
3836 m_TargetDiffRA = (m_AlignCoord.ra().deltaAngle(m_TargetCoord.ra())).Degrees() * 3600;
3837 m_TargetDiffDE = (m_AlignCoord.dec().deltaAngle(m_TargetCoord.dec())).Degrees() * 3600;
3841 m_TargetDiffRA = (m_AlignCoord.ra().deltaAngle(m_DestinationCoord.ra())).Degrees() * 3600;
3842 m_TargetDiffDE = (m_AlignCoord.dec().deltaAngle(m_DestinationCoord.dec())).Degrees() * 3600;
3843 qCDebug(KSTARS_EKOS_ALIGN) <<
"Differential slew - Solution RA:" << m_AlignCoord.ra().toHMSString()
3844 <<
" DE:" << m_AlignCoord.dec().toDMSString();
3845 qCDebug(KSTARS_EKOS_ALIGN) <<
"Differential slew - Destination RA:" << m_DestinationCoord.ra().toHMSString()
3846 <<
" DE:" << m_DestinationCoord.dec().toDMSString();
3849 m_TargetDiffTotal = sqrt(m_TargetDiffRA * m_TargetDiffRA + m_TargetDiffDE * m_TargetDiffDE);
3851 errOut->setText(
QString(
"%1 arcsec. RA:%2 DE:%3").arg(
3855 if (m_TargetDiffTotal <=
static_cast<double>(alignAccuracyThreshold->value()))
3856 errOut->setStyleSheet(
"color:green");
3857 else if (m_TargetDiffTotal < 1.5 * alignAccuracyThreshold->value())
3858 errOut->setStyleSheet(
"color:yellow");
3860 errOut->setStyleSheet(
"color:red");
3863 int currentRow = solutionTable->rowCount() - 1;
3870 solutionTable->setItem(currentRow, 4, dRAReport);
3879 solutionTable->setItem(currentRow, 5, dDECReport);
3882 double raPlot = m_TargetDiffRA;
3883 double decPlot = m_TargetDiffDE;
3884 alignPlot->graph(0)->addData(raPlot, decPlot);
3890 textLabel->position->
setCoords(raPlot, decPlot);
3898 if (!alignPlot->xAxis->range().contains(m_TargetDiffRA))
3900 alignPlot->graph(0)->rescaleKeyAxis(
true);
3901 alignPlot->yAxis->setScaleRatio(alignPlot->xAxis, 1.0);
3903 if (!alignPlot->yAxis->range().contains(m_TargetDiffDE))
3905 alignPlot->graph(0)->rescaleValueAxis(
true);
3906 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
3908 alignPlot->replot();
3914 for (
auto ¶m : m_StellarSolverProfiles)
3915 profiles << param.listName;
3920void Align::exportSolutionPoints()
3922 if (solutionTable->rowCount() == 0)
3927 "CSV File (*.csv)");
3938 i18n(
"A file named \"%1\" already exists. "
3949 KSNotification::sorry(message,
i18n(
"Invalid URL"));
3957 QString message =
i18n(
"Unable to write to file %1", path);
3958 KSNotification::sorry(message,
i18n(
"Could Not Open File"));
3966 outstream <<
"RA (J" << epoch <<
"),DE (J" << epoch
3967 <<
"),RA (degrees),DE (degrees),Name,RA Error (arcsec),DE Error (arcsec)" <<
Qt::endl;
3969 for (
int i = 0; i < solutionTable->rowCount(); i++)
3977 if (!raCell || !deCell || !objNameCell || !raErrorCell || !deErrorCell)
3979 KSNotification::sorry(
i18n(
"Error in table structure."));
3988 emit newLog(
i18n(
"Solution Points Saved as: %1", path));
3992void Align::setupPolarAlignmentAssistant()
3995 m_PolarAlignmentAssistant =
new PolarAlignmentAssistant(
this, m_AlignView);
3996 connect(m_PolarAlignmentAssistant, &Ekos::PAA::captureAndSolve,
this, [
this]()
3998 captureAndSolve(
true);
4000 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newAlignTableResult,
this, &Ekos::Align::setAlignTableResult);
4001 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newFrame,
this, &Ekos::Align::newFrame);
4002 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newPAHStage,
this, &Ekos::Align::processPAHStage);
4003 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newLog,
this, &Ekos::Align::appendLogText);
4005 tabWidget->addTab(m_PolarAlignmentAssistant,
i18n(
"Polar Alignment"));
4008void Align::setupManualRotator()
4010 if (m_ManualRotator)
4013 m_ManualRotator =
new ManualRotator(
this);
4014 connect(m_ManualRotator, &Ekos::ManualRotator::captureAndSolve,
this, [
this]()
4016 captureAndSolve(
false);
4022 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
4027void Align::setuptDarkProcessor()
4029 if (m_DarkProcessor)
4032 m_DarkProcessor =
new DarkProcessor(
this);
4033 connect(m_DarkProcessor, &DarkProcessor::newLog,
this, &Ekos::Align::appendLogText);
4034 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted,
this, [
this](
bool completed)
4036 alignDarkFrame->setChecked(completed);
4037 m_AlignView->setProperty(
"suspended",
false);
4040 m_AlignView->rescale(ZOOM_KEEP_LEVEL);
4041 m_AlignView->updateFrame();
4043 setCaptureComplete();
4047void Align::processPAHStage(
int stage)
4055 if (m_StellarSolver && m_StellarSolver->isRunning())
4056 m_StellarSolver->abort();
4058 case PAA::PAH_POST_REFRESH:
4060 Options::setAstrometrySolverWCS(rememberSolverWCS);
4061 Options::setAutoWCS(rememberAutoWCS);
4066 case PAA::PAH_FIRST_CAPTURE:
4067 nothingR->setChecked(
true);
4068 m_CurrentGotoMode = GOTO_NOTHING;
4069 loadSlewB->setEnabled(
false);
4071 rememberSolverWCS = Options::astrometrySolverWCS();
4072 rememberAutoWCS = Options::autoWCS();
4074 Options::setAutoWCS(
false);
4075 Options::setAstrometrySolverWCS(
true);
4077 case PAA::PAH_SECOND_CAPTURE:
4078 case PAA::PAH_THIRD_CAPTURE:
4079 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
4080 emit newLog(
i18n(
"Settling..."));
4081 m_CaptureTimer.start(alignSettlingTime->value());
4088 emit newPAAStage(stage);
4091bool Align::matchPAHStage(uint32_t stage)
4093 return m_PolarAlignmentAssistant && m_PolarAlignmentAssistant->getPAHStage() == stage;
4096void Align::toggleManualRotator(
bool toggled)
4100 m_ManualRotator->show();
4101 m_ManualRotator->raise();
4104 m_ManualRotator->close();
4107void Align::setupOpticalTrainManager()
4109 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated,
this, &Align::refreshOpticalTrain);
4112 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
4116 ProfileSettings::Instance()->setOneSetting(ProfileSettings::AlignOpticalTrain,
4117 OpticalTrainManager::Instance()->
id(opticalTrainCombo->itemText(index)));
4118 refreshOpticalTrain();
4119 emit trainChanged();
4123void Align::refreshOpticalTrain()
4125 opticalTrainCombo->blockSignals(
true);
4126 opticalTrainCombo->clear();
4127 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
4128 trainB->setEnabled(
true);
4130 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::AlignOpticalTrain);
4134 auto id = trainID.
toUInt();
4137 if (OpticalTrainManager::Instance()->exists(
id) ==
false)
4139 qCWarning(KSTARS_EKOS_ALIGN) <<
"Optical train doesn't exist for id" << id;
4140 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
4143 auto name = OpticalTrainManager::Instance()->name(
id);
4145 opticalTrainCombo->setCurrentText(name);
4147 auto scope = OpticalTrainManager::Instance()->getScope(name);
4148 m_FocalLength = scope[
"focal_length"].
toDouble(-1);
4149 m_Aperture = scope[
"aperture"].toDouble(-1);
4150 m_FocalRatio = scope[
"focal_ratio"].toDouble(-1);
4151 m_Reducer = OpticalTrainManager::Instance()->getReducer(name);
4154 if (m_Aperture < 0 && m_FocalRatio > 0)
4155 m_Aperture = m_FocalLength / m_FocalRatio;
4157 auto mount = OpticalTrainManager::Instance()->getMount(name);
4160 auto camera = OpticalTrainManager::Instance()->getCamera(name);
4163 camera->setScopeInfo(m_FocalLength * m_Reducer, m_Aperture);
4164 opticalTrainCombo->setToolTip(
QString(
"%1 @ %2").arg(camera->getDeviceName(), scope[
"name"].toString()));
4168 syncTelescopeInfo();
4170 auto filterWheel = OpticalTrainManager::Instance()->getFilterWheel(name);
4171 setFilterWheel(filterWheel);
4173 auto rotator = OpticalTrainManager::Instance()->getRotator(name);
4174 setRotator(rotator);
4177 OpticalTrainSettings::Instance()->setOpticalTrainID(
id);
4178 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Align);
4179 if (settings.isValid())
4181 auto map = settings.toJsonObject().toVariantMap();
4182 if (map != m_Settings)
4185 setAllSettings(map);
4189 m_Settings = m_GlobalSettings;
4192 Options::setTelescopeFocalLength(m_FocalLength);
4193 Options::setCameraPixelWidth(m_CameraPixelWidth);
4194 Options::setCameraPixelHeight(m_CameraPixelHeight);
4195 Options::setCameraWidth(m_CameraWidth);
4196 Options::setCameraHeight(m_CameraHeight);
4199 opticalTrainCombo->blockSignals(
false);
4202void Align::syncSettings()
4213 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
4216 value = dsb->
value();
4219 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
4222 value = sb->
value();
4224 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
4229 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
4234 else if ( (cradio = qobject_cast<QRadioButton*>(sender())))
4240 m_Settings.remove(key);
4247 Options::self()->setProperty(key.
toLatin1(), value);
4249 m_Settings[key] = value;
4250 m_GlobalSettings[key] = value;
4251 m_DebounceTimer.start();
4257void Align::settleSettings()
4259 emit settingsUpdated(getAllSettings());
4261 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()));
4262 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Align, m_Settings);
4265void Align::loadGlobalSettings()
4270 QVariantMap settings;
4272 for (
auto &oneWidget : findChildren<
QComboBox*>())
4274 if (oneWidget->objectName() ==
"opticalTrainCombo")
4277 key = oneWidget->objectName();
4278 value = Options::self()->property(key.
toLatin1());
4279 if (value.
isValid() && oneWidget->count() > 0)
4281 oneWidget->setCurrentText(value.
toString());
4282 settings[key] = value;
4289 key = oneWidget->objectName();
4290 value = Options::self()->property(key.
toLatin1());
4294 settings[key] = value;
4299 for (
auto &oneWidget : findChildren<
QSpinBox*>())
4301 key = oneWidget->objectName();
4302 value = Options::self()->property(key.
toLatin1());
4306 settings[key] = value;
4311 for (
auto &oneWidget : findChildren<
QCheckBox*>())
4313 key = oneWidget->objectName();
4314 value = Options::self()->property(key.
toLatin1());
4317 oneWidget->setChecked(value.
toBool());
4318 settings[key] = value;
4325 key = oneWidget->objectName();
4326 value = Options::self()->property(key.
toLatin1());
4329 oneWidget->setChecked(value.
toBool());
4330 settings[key] = value;
4334 m_GlobalSettings = m_Settings = settings;
4338void Align::connectSettings()
4341 for (
auto &oneWidget : findChildren<
QComboBox*>())
4342 connect(oneWidget, QOverload<int>::of(&
QComboBox::activated), this, &
Ekos::Align::syncSettings);
4349 for (
auto &oneWidget : findChildren<
QSpinBox*>())
4353 for (
auto &oneWidget : findChildren<
QCheckBox*>())
4361 disconnect(opticalTrainCombo, QOverload<int>::of(&
QComboBox::activated),
this, &Ekos::Align::syncSettings);
4364void Align::disconnectSettings()
4367 for (
auto &oneWidget : findChildren<
QComboBox*>())
4368 disconnect(oneWidget, QOverload<int>::of(&
QComboBox::activated), this, &
Ekos::Align::syncSettings);
4372 disconnect(oneWidget, &
QDoubleSpinBox::editingFinished, this, &
Ekos::Align::syncSettings);
4375 for (
auto &oneWidget : findChildren<
QSpinBox*>())
4376 disconnect(oneWidget, &
QSpinBox::editingFinished, this, &
Ekos::Align::syncSettings);
4379 for (
auto &oneWidget : findChildren<
QCheckBox*>())
4380 disconnect(oneWidget, &
QCheckBox::toggled, this, &
Ekos::Align::syncSettings);
4384 disconnect(oneWidget, &
QCheckBox::toggled, this, &
Ekos::Align::syncSettings);
4391QVariantMap Align::getAllSettings()
const
4393 QVariantMap settings;
4396 for (
auto &oneWidget : findChildren<
QComboBox*>())
4397 settings.
insert(oneWidget->objectName(), oneWidget->currentText());
4401 settings.
insert(oneWidget->objectName(), oneWidget->value());
4404 for (
auto &oneWidget : findChildren<
QSpinBox*>())
4405 settings.
insert(oneWidget->objectName(), oneWidget->value());
4408 for (
auto &oneWidget : findChildren<
QCheckBox*>())
4409 settings.
insert(oneWidget->objectName(), oneWidget->isChecked());
4413 settings.
insert(oneWidget->objectName(), oneWidget->isChecked());
4421void Align::setAllSettings(
const QVariantMap &settings)
4425 disconnectSettings();
4427 for (
auto &name : settings.keys())
4430 auto comboBox = findChild<QComboBox*>(name);
4433 syncControl(settings, name, comboBox);
4438 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
4441 syncControl(settings, name, doubleSpinBox);
4446 auto spinBox = findChild<QSpinBox*>(name);
4449 syncControl(settings, name, spinBox);
4454 auto checkbox = findChild<QCheckBox*>(name);
4457 syncControl(settings, name, checkbox);
4462 auto radioButton = findChild<QRadioButton*>(name);
4465 syncControl(settings, name, radioButton);
4471 for (
auto &key : settings.keys())
4473 auto value = settings[key];
4475 Options::self()->setProperty(key.
toLatin1(), value);
4476 Options::self()->save();
4478 m_Settings[key] = value;
4479 m_GlobalSettings[key] = value;
4482 emit settingsUpdated(getAllSettings());
4485 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()));
4486 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Align, m_Settings);
4495bool Align::syncControl(
const QVariantMap &settings,
const QString &key,
QWidget * widget)
4504 if ((pSB = qobject_cast<QSpinBox *>(widget)))
4506 const int value = settings[key].toInt(&ok);
4513 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
4515 const double value = settings[key].toDouble(&ok);
4522 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
4524 const bool value = settings[key].toBool();
4530 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
4532 const QString value = settings[key].toString();
4536 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
4538 const bool value = settings[key].toBool();
4540 pRadioButton->
click();
4547void Align::setState(AlignState value)
4549 qCDebug(KSTARS_EKOS_ALIGN) <<
"Align state changed to" << getAlignStatusString(value);
4553void Align::processCaptureTimeout()
4555 if (m_CaptureTimeoutCounter++ > 3)
4557 appendLogText(
i18n(
"Capture timed out."));
4558 m_CaptureTimer.stop();
4563 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD :
ISD::CameraChip::PRIMARY_CCD);
4564 if (targetChip->isCapturing())
4566 appendLogText(
i18n(
"Capturing still running, Retrying in %1 seconds...", m_CaptureTimer.interval() / 500));
4567 targetChip->abortExposure();
4568 m_CaptureTimer.start( m_CaptureTimer.interval() * 2);
4572 setAlignTableResult(ALIGN_RESULT_FAILED);
4573 if (m_resetCaptureTimeoutCounter)
4575 m_resetCaptureTimeoutCounter =
false;
4576 m_CaptureTimeoutCounter = 0;
4578 captureAndSolve(
false);