11 #include "alignadaptor.h"
12 #include "alignview.h"
13 #include <ekos_align_debug.h>
18 #include "opsprograms.h"
19 #include "opsastrometry.h"
20 #include "opsastrometryindexfiles.h"
23 #include "mountmodel.h"
24 #include "polaralignmentassistant.h"
25 #include "remoteastrometryparser.h"
26 #include "manualrotator.h"
29 #include "fitsviewer/fitsdata.h"
32 #include "auxiliary/QProgressIndicator.h"
33 #include "auxiliary/ksmessagebox.h"
34 #include "ekos/auxiliary/darkprocessor.h"
35 #include "ekos/auxiliary/filtermanager.h"
36 #include "ekos/auxiliary/stellarsolverprofileeditor.h"
37 #include "ekos/auxiliary/profilesettings.h"
38 #include "ekos/auxiliary/opticaltrainmanager.h"
39 #include "ekos/auxiliary/opticaltrainsettings.h"
40 #include "ksnotification.h"
45 #include "kstarsdata.h"
46 #include "skymapcomposite.h"
47 #include "ekos/auxiliary/solverutils.h"
48 #include "ekos/auxiliary/rotatorutils.h"
51 #include "ekos/manager.h"
52 #include "indi/indidome.h"
53 #include "indi/clientmanager.h"
54 #include "indi/driverinfo.h"
55 #include "indi/indifilterwheel.h"
56 #include "indi/indirotator.h"
57 #include "profileinfo.h"
60 #include <KActionCollection>
61 #include <basedevice.h>
68 #define MAXIMUM_SOLVER_ITERATIONS 10
69 #define CAPTURE_RETRY_DELAY 10000
70 #define PAH_CUTOFF_FOV 10 // Minimum FOV width in arcminutes for PAH to work
71 #define CHECK_PAH(x) \
72 m_PolarAlignmentAssistant && m_PolarAlignmentAssistant->x
74 if (m_PolarAlignmentAssistant) m_PolarAlignmentAssistant->x
81 bool isEqual(
double a,
double b)
83 return std::abs(a - b) < 0.01;
90 qRegisterMetaType<Ekos::AlignState>(
"Ekos::AlignState");
91 qDBusRegisterMetaType<Ekos::AlignState>();
93 new AlignAdaptor(
this);
98 KStarsData::Instance()->clearTransientFOVs();
100 solverFOV.reset(
new FOV());
101 solverFOV->setName(
i18n(
"Solver FOV"));
102 solverFOV->setObjectName(
"solver_fov");
103 solverFOV->setLockCelestialPole(
true);
104 solverFOV->setColor(
KStars::Instance()->data()->colorScheme()->colorNamed(
"SolverFOVColor").name());
105 solverFOV->setProperty(
"visible",
false);
108 sensorFOV.reset(
new FOV());
109 sensorFOV->setObjectName(
"sensor_fov");
110 sensorFOV->setLockCelestialPole(
true);
111 sensorFOV->setProperty(
"visible", Options::showSensorFOV());
118 showFITSViewerB->setIcon(
123 toggleFullScreenB->setIcon(
127 connect(toggleFullScreenB, &
QPushButton::clicked,
this, &Ekos::Align::toggleAlignWidgetFullScreen);
132 m_AlignView.reset(
new AlignView(alignWidget, FITS_ALIGN));
134 m_AlignView->setBaseSize(alignWidget->size());
135 m_AlignView->createFloatingToolBar();
139 alignWidget->setLayout(vlayout);
152 gotoModeButtonGroup->setId(syncR, GOTO_SYNC);
153 gotoModeButtonGroup->setId(slewR, GOTO_SLEW);
154 gotoModeButtonGroup->setId(nothingR, GOTO_NOTHING);
156 m_CurrentGotoMode =
static_cast<GotoMode
>(Options::solverGotoOption());
157 gotoModeButtonGroup->button(m_CurrentGotoMode)->setChecked(
true);
159 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
161 [ = ](
int id,
bool toggled)
164 [ = ](
int id,
bool toggled)
168 this->m_CurrentGotoMode =
static_cast<GotoMode
>(
id);
171 m_CaptureTimer.setSingleShot(
true);
172 m_CaptureTimer.setInterval(CAPTURE_RETRY_DELAY);
173 connect(&m_CaptureTimer, &
QTimer::timeout,
this, &Align::processCaptureTimeout);
174 m_AlignTimer.setSingleShot(
true);
175 m_AlignTimer.setInterval(Options::astrometryTimeout() * 1000);
176 connect(&m_AlignTimer, &
QTimer::timeout,
this, &Ekos::Align::checkAlignmentTimeout);
179 stopLayout->addWidget(pi.get());
181 rememberSolverWCS = Options::astrometrySolverWCS();
182 rememberAutoWCS = Options::autoWCS();
184 solverModeButtonGroup->setId(localSolverR, SOLVER_LOCAL);
185 solverModeButtonGroup->setId(remoteSolverR, SOLVER_REMOTE);
187 setSolverMode(Options::solverMode());
189 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
191 [ = ](
int id,
bool toggled)
194 [ = ](
int id,
bool toggled)
209 connect(
this, &Align::newStatus,
this, [
this](
AlignState state)
220 for (
auto &button : qButtons)
221 button->setAutoDefault(
false);
223 savedOptionsProfiles =
QDir(KSPaths::writableLocation(
225 if(
QFile(savedOptionsProfiles).exists())
226 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
228 m_StellarSolverProfiles = getDefaultAlignOptionsProfiles();
230 m_StellarSolver.reset(
new StellarSolver());
231 connect(m_StellarSolver.get(), &StellarSolver::logOutput,
this, &Align::appendLogText);
233 setupPolarAlignmentAssistant();
234 setupManualRotator();
235 setuptDarkProcessor();
237 setupSolutionTable();
241 loadGlobalSettings();
244 setupOpticalTrainManager();
249 if (m_StellarSolver.get() !=
nullptr)
250 disconnect(m_StellarSolver.get(), &StellarSolver::logOutput,
this, &Align::appendLogText);
252 if (alignWidget->parent() ==
nullptr)
253 toggleAlignWidgetFullScreen();
260 for (
auto &dirFile : dir.entryList())
263 void Align::selectSolutionTableRow(
int row,
int column)
267 solutionTable->selectRow(row);
268 for (
int i = 0; i < alignPlot->itemCount(); i++)
273 QCPItemText *item = qobject_cast<QCPItemText *>(abstractItem);
292 void Align::handleHorizontalPlotSizeChange()
294 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
298 void Align::handleVerticalPlotSizeChange()
300 alignPlot->yAxis->setScaleRatio(alignPlot->xAxis, 1.0);
306 if (
event->oldSize().width() != -1)
309 handleHorizontalPlotSizeChange();
311 handleVerticalPlotSizeChange();
324 QCPItemText *label = qobject_cast<QCPItemText *>(item);
327 QString labelText = label->text();
328 int point = labelText.
toInt() - 1;
335 "<th colspan=\"2\">Object %1: %2</th>"
338 "<td>RA:</td><td>%3</td>"
341 "<td>DE:</td><td>%4</td>"
344 "<td>dRA:</td><td>%5</td>"
347 "<td>dDE:</td><td>%6</td>"
351 solutionTable->item(point, 2)->text(),
352 solutionTable->item(point, 0)->text(),
353 solutionTable->item(point, 1)->text(),
354 solutionTable->item(point, 4)->text(),
355 solutionTable->item(point, 5)->text()),
356 alignPlot, alignPlot->rect());
361 void Align::buildTarget()
363 double accuracyRadius = alignAccuracyThreshold->value();
366 concentricRings->
data()->clear();
367 redTarget->
data()->clear();
368 yellowTarget->
data()->clear();
369 centralTarget->
data()->clear();
373 concentricRings =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
374 redTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
375 yellowTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
376 centralTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
378 const int pointCount = 200;
385 int circleRingPt = 0;
386 for (
int i = 0; i < pointCount; i++)
388 double theta = i /
static_cast<double>(pointCount) * 2 * M_PI;
390 for (
double ring = 1; ring < 8; ring++)
392 if (ring != 4 && ring != 6)
394 if (i % (9 -
static_cast<int>(ring)) == 0)
396 circleRings[circleRingPt] =
QCPCurveData(circleRingPt, accuracyRadius * ring * 0.25 * qCos(theta),
397 accuracyRadius * ring * 0.25 * qSin(theta));
403 circleCentral[i] =
QCPCurveData(i, accuracyRadius * qCos(theta), accuracyRadius * qSin(theta));
404 circleYellow[i] =
QCPCurveData(i, accuracyRadius * 1.5 * qCos(theta), accuracyRadius * 1.5 * qSin(theta));
405 circleRed[i] =
QCPCurveData(i, accuracyRadius * 2 * qCos(theta), accuracyRadius * 2 * qSin(theta));
412 concentricRings->
data()->set(circleRings,
true);
413 redTarget->
data()->set(circleRed,
true);
414 yellowTarget->
data()->set(circleYellow,
true);
415 centralTarget->
data()->set(circleCentral,
true);
428 if (alignPlot->size().width() > 0)
432 void Align::slotAutoScaleGraph()
434 double accuracyRadius = alignAccuracyThreshold->value();
435 alignPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
436 alignPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
438 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
444 void Align::slotClearAllSolutionPoints()
446 if (solutionTable->rowCount() == 0)
454 solutionTable->setRowCount(0);
455 alignPlot->graph(0)->data()->clear();
456 alignPlot->clearItems();
459 slotAutoScaleGraph();
463 KSMessageBox::Instance()->questionYesNo(
i18n(
"Are you sure you want to clear all of the solution points?"),
464 i18n(
"Clear Solution Points"), 60);
467 void Align::slotRemoveSolutionPoint()
469 auto abstractItem = alignPlot->item(solutionTable->currentRow());
472 auto item = qobject_cast<QCPItemText *>(abstractItem);
475 double point = item->position->key();
476 alignPlot->graph(0)->data()->remove(point);
480 alignPlot->removeItem(solutionTable->currentRow());
482 for (
int i = 0; i < alignPlot->itemCount(); i++)
484 auto oneItem = alignPlot->item(i);
487 auto item = qobject_cast<QCPItemText *>(oneItem);
492 solutionTable->removeRow(solutionTable->currentRow());
496 void Align::slotMountModel()
500 m_MountModel =
new MountModel(
this);
502 connect(m_MountModel, &Ekos::MountModel::aborted,
this, [
this]()
504 if (m_Mount && m_Mount->isSlewing())
511 m_MountModel->show();
531 void Align::checkAlignmentTimeout()
533 if (m_SolveFromFile || ++solverIterations == MAXIMUM_SOLVER_ITERATIONS)
537 appendLogText(
i18n(
"Solver timed out."));
538 parser->stopSolver();
540 setAlignTableResult(ALIGN_RESULT_FAILED);
548 if (
sender() ==
nullptr && mode >= 0 && mode <= 1)
550 solverModeButtonGroup->button(mode)->setChecked(
true);
553 Options::setSolverMode(mode);
555 if (mode == SOLVER_REMOTE)
557 if (remoteParser.get() !=
nullptr && m_RemoteParserDevice !=
nullptr)
559 parser = remoteParser.get();
565 parser = remoteParser.get();
570 parser->setAlign(
this);
584 return m_Camera->getDeviceName();
615 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
616 if (targetChip ==
nullptr || (targetChip && targetChip->isCapturing()))
619 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser.get() !=
nullptr)
629 if (m_Camera && m_Camera == device)
642 connect(m_Camera, &ISD::ConcreteDevice::Connected,
this, [
this]()
644 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
645 controlBox->setEnabled(isConnected);
646 gotoBox->setEnabled(isConnected);
647 plateSolverOptionsGroup->setEnabled(isConnected);
648 tabWidget->setEnabled(isConnected);
650 connect(m_Camera, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
652 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
653 controlBox->setEnabled(isConnected);
654 gotoBox->setEnabled(isConnected);
655 plateSolverOptionsGroup->setEnabled(isConnected);
656 tabWidget->setEnabled(isConnected);
658 opticalTrainCombo->setEnabled(
true);
659 trainLabel->setEnabled(
true);
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);
679 if (m_Mount && m_Mount == device)
692 connect(m_Mount, &ISD::ConcreteDevice::Connected,
this, [
this]()
694 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
695 controlBox->setEnabled(isConnected);
696 gotoBox->setEnabled(isConnected);
697 plateSolverOptionsGroup->setEnabled(isConnected);
698 tabWidget->setEnabled(isConnected);
700 connect(m_Mount, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
702 auto isConnected = m_Camera && m_Camera->isConnected() && m_Mount && m_Mount->isConnected();
703 controlBox->setEnabled(isConnected);
704 gotoBox->setEnabled(isConnected);
705 plateSolverOptionsGroup->setEnabled(isConnected);
706 tabWidget->setEnabled(isConnected);
708 opticalTrainCombo->setEnabled(
true);
709 trainLabel->setEnabled(
true);
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);
722 RUN_PAH(setCurrentTelescope(m_Mount));
725 connect(m_Mount, &ISD::Mount::Disconnected,
this, [
this]()
727 m_isRateSynced =
false;
736 if (m_Dome && m_Dome == device)
753 auto name = device->getDeviceName();
754 device->disconnect(
this);
757 if (m_Mount && m_Mount->getDeviceName() == name)
764 if (m_Dome && m_Dome->getDeviceName() == name)
771 if (m_Rotator && m_Rotator->getDeviceName() == name)
778 if (m_Camera && m_Camera->getDeviceName() == name)
787 if (m_RemoteParserDevice && m_RemoteParserDevice->getDeviceName() == name)
789 m_RemoteParserDevice.
clear();
793 if (m_FilterWheel && m_FilterWheel->getDeviceName() == name)
795 m_FilterWheel->disconnect(
this);
796 m_FilterWheel =
nullptr;
805 if (m_Mount ==
nullptr || m_Mount->isConnected() ==
false)
808 if (m_isRateSynced ==
false)
810 auto speed = m_Settings[
"PAHMountSpeed"];
811 auto slewRates = m_Mount->slewRates();
814 RUN_PAH(syncMountSpeed(speed.toString()));
816 else if (!slewRates.isEmpty())
818 RUN_PAH(syncMountSpeed(slewRates.last()));
821 m_isRateSynced = !slewRates.empty();
824 canSync = m_Mount->canSync();
826 if (canSync ==
false && syncR->isEnabled())
828 slewR->setChecked(
true);
829 appendLogText(
i18n(
"Mount does not support syncing."));
832 syncR->setEnabled(canSync);
834 if (m_FocalLength == -1 || m_Aperture == -1)
837 if (m_CameraPixelWidth != -1 && m_CameraPixelHeight != -1)
851 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
852 Q_ASSERT(targetChip);
855 uint8_t bit_depth = 8;
856 targetChip->getImageInfo(m_CameraWidth, m_CameraHeight, m_CameraPixelWidth, m_CameraPixelHeight, bit_depth);
858 setWCSEnabled(Options::astrometrySolverWCS());
860 int binx = 1, biny = 1;
861 alignBinning->setEnabled(targetChip->canBin());
862 if (targetChip->canBin())
864 alignBinning->blockSignals(
true);
866 targetChip->getMaxBin(&binx, &biny);
867 alignBinning->clear();
869 for (
int i = 0; i < binx; i++)
870 alignBinning->addItem(
QString(
"%1x%2").arg(i + 1).arg(i + 1));
872 auto binning = m_Settings[
"alignBinning"];
873 if (binning.isValid())
874 alignBinning->setCurrentText(binning.toString());
876 alignBinning->blockSignals(
false);
881 int roiW = 0, roiH = 0;
882 targetChip->getFrameMinMax(
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &roiW,
nullptr, &roiH);
885 if ( (roiW > 0 && roiW < m_CameraWidth) || (roiH > 0 && roiH < m_CameraHeight))
887 m_CameraWidth = roiW;
888 m_CameraHeight = roiH;
891 if (m_CameraPixelWidth > 0 && m_CameraPixelHeight > 0 && m_FocalLength > 0 && m_Aperture > 0)
899 if (m_Camera ==
nullptr)
902 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
903 if (targetChip ==
nullptr || (targetChip && targetChip->isCapturing()))
906 auto isoList = targetChip->getISOList();
909 if (isoList.isEmpty())
911 alignISO->setEnabled(
false);
915 alignISO->setEnabled(
true);
916 alignISO->addItems(isoList);
917 alignISO->setCurrentIndex(targetChip->getISOIndex());
921 if (m_Camera->hasGain())
923 double min, max, step, value;
924 m_Camera->getGainMinMaxStep(&min, &max, &step);
927 alignGainSpecialValue = min - step;
928 alignGain->setRange(alignGainSpecialValue, max);
929 alignGain->setSpecialValueText(
i18n(
"--"));
930 alignGain->setEnabled(
true);
931 alignGain->setSingleStep(step);
932 m_Camera->getGain(&value);
934 auto gain = m_Settings[
"alignGain"];
938 TargetCustomGainValue = gain.toDouble();
939 if (TargetCustomGainValue > 0)
940 alignGain->setValue(TargetCustomGainValue);
942 alignGain->setValue(alignGainSpecialValue);
944 alignGain->setReadOnly(m_Camera->getGainPermission() == IP_RO);
948 if (alignGain->value() > alignGainSpecialValue)
949 TargetCustomGainValue = alignGain->value();
953 alignGain->setEnabled(
false);
960 fov_scale = m_FOVPixelScale;
967 result << m_FOVWidth << m_FOVHeight << m_FOVPixelScale;
976 result << m_CameraWidth << m_CameraHeight << m_CameraPixelWidth << m_CameraPixelHeight;
985 result << m_FocalLength << m_Aperture << m_Reducer;
994 auto reducedFocalLength = m_Reducer * m_FocalLength;
995 if (m_FocalRatio > 0)
999 fov_w = 3600 * 2 * atan(m_CameraWidth * (m_CameraPixelWidth / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1000 fov_h = 3600 * 2 * atan(m_CameraHeight * (m_CameraPixelHeight / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1005 fov_w = 206264.8062470963552 * m_CameraWidth * m_CameraPixelWidth / 1000.0 / reducedFocalLength;
1006 fov_h = 206264.8062470963552 * m_CameraHeight * m_CameraPixelHeight / 1000.0 / reducedFocalLength;
1010 fov_scale = (fov_w * (alignBinning->currentIndex() + 1)) / m_CameraWidth;
1017 void Align::calculateEffectiveFocalLength(
double newFOVW)
1019 if (newFOVW < 0 || isEqual(newFOVW, m_FOVWidth))
1022 auto reducedFocalLength = m_Reducer * m_FocalLength;
1023 double new_focal_length = 0;
1025 if (m_FocalRatio > 0)
1026 new_focal_length = ((m_CameraWidth * m_CameraPixelWidth / 1000.0) / tan(newFOVW / 2)) / 2;
1028 new_focal_length = ((m_CameraWidth * m_CameraPixelWidth / 1000.0) * 206264.8062470963552) / (newFOVW * 60.0);
1029 double focal_diff = std::fabs(new_focal_length - reducedFocalLength);
1033 m_EffectiveFocalLength = new_focal_length / m_Reducer;
1034 appendLogText(
i18n(
"Effective telescope focal length is updated to %1 mm.", m_EffectiveFocalLength));
1038 void Align::calculateFOV()
1040 auto reducedFocalLength = m_Reducer * m_FocalLength;
1041 auto reducecdEffectiveFocalLength = m_Reducer * m_EffectiveFocalLength;
1042 auto reducedFocalRatio = m_Reducer * m_FocalRatio;
1044 if (m_FocalRatio > 0)
1048 m_FOVWidth = 3600 * 2 * atan(m_CameraWidth * (m_CameraPixelWidth / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1049 m_FOVHeight = 3600 * 2 * atan(m_CameraHeight * (m_CameraPixelHeight / 1000.0) / (2 * reducedFocalLength)) /
dms::DegToRad;
1054 m_FOVWidth = 206264.8062470963552 * m_CameraWidth * m_CameraPixelWidth / 1000.0 / reducedFocalLength;
1055 m_FOVHeight = 206264.8062470963552 * m_CameraHeight * m_CameraPixelHeight / 1000.0 / reducedFocalLength;
1061 m_FOVPixelScale = (m_FOVWidth * (alignBinning->currentIndex() + 1)) / m_CameraWidth;
1065 m_FOVHeight /= 60.0;
1067 double calculated_fov_x = m_FOVWidth;
1068 double calculated_fov_y = m_FOVHeight;
1070 QString calculatedFOV = (
QString(
"%1' x %2'").
arg(m_FOVWidth, 0,
'f', 1).
arg(m_FOVHeight, 0,
'f', 1));
1073 if (m_FOVWidth < 1 || m_FOVWidth > 60 * 180 || m_FOVHeight < 1 || m_FOVHeight > 60 * 180)
1076 i18n(
"Warning! The calculated field of view (%1) is out of bounds. Ensure the telescope focal length and camera pixel size are correct.",
1081 FocalLengthOut->setText(
QString(
"%1 (%2)").arg(reducedFocalLength, 0,
'f', 1).
1082 arg(m_EffectiveFocalLength > 0 ? reducecdEffectiveFocalLength : reducedFocalLength, 0,
'f', 1));
1084 if (m_FocalRatio > 0)
1085 FocalRatioOut->setText(
QString(
"%1 (%2)").arg(reducedFocalRatio, 0,
'f', 1).
1086 arg(m_EffectiveFocalLength > 0 ? reducecdEffectiveFocalLength / m_Aperture : reducedFocalRatio, 0,
1089 else if (m_Aperture > 0)
1090 FocalRatioOut->setText(
QString(
"%1 (%2)").arg(reducedFocalLength / m_Aperture, 0,
'f', 1).
1091 arg(m_EffectiveFocalLength > 0 ? reducecdEffectiveFocalLength / m_Aperture : reducedFocalLength / m_Aperture, 0,
1093 ReducerOut->setText(
QString(
"%1x").arg(m_Reducer, 0,
'f', 2));
1095 if (m_EffectiveFocalLength > 0)
1097 double focal_diff = std::fabs(m_EffectiveFocalLength - m_FocalLength);
1099 FocalLengthOut->setStyleSheet(
"color:green");
1100 else if (focal_diff < 15)
1101 FocalLengthOut->setStyleSheet(
"color:yellow");
1103 FocalLengthOut->setStyleSheet(
"color:red");
1111 if (m_FOVWidth == 0)
1115 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>",
1117 m_FOVWidth = calculated_fov_x;
1118 m_FOVHeight = calculated_fov_y;
1119 m_EffectiveFOVPending =
true;
1123 m_EffectiveFOVPending =
false;
1124 FOVOut->setToolTip(
i18n(
"<p>Effective field of view size in arcminutes.</p>"));
1127 solverFOV->setSize(m_FOVWidth, m_FOVHeight);
1128 sensorFOV->setSize(m_FOVWidth, m_FOVHeight);
1130 sensorFOV->setName(m_Camera->getDeviceName());
1132 FOVOut->setText(
QString(
"%1' x %2'").arg(m_FOVWidth, 0,
'f', 1).arg(m_FOVHeight, 0,
'f', 1));
1135 const bool fovOK = ((m_FOVWidth + m_FOVHeight) / 2.0) > PAH_CUTOFF_FOV;
1136 if (m_PolarAlignmentAssistant !=
nullptr)
1137 m_PolarAlignmentAssistant->setEnabled(fovOK);
1139 if (opsAstrometry->kcfg_AstrometryUseImageScale->isChecked())
1141 int unitType = opsAstrometry->kcfg_AstrometryImageScaleUnits->currentIndex();
1146 double fov_low = qMin(m_FOVWidth / 60, m_FOVHeight / 60);
1147 double fov_high = qMax(m_FOVWidth / 60, m_FOVHeight / 60);
1148 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(fov_low);
1149 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(fov_high);
1151 Options::setAstrometryImageScaleLow(fov_low);
1152 Options::setAstrometryImageScaleHigh(fov_high);
1155 else if (unitType == 1)
1157 double fov_low = qMin(m_FOVWidth, m_FOVHeight);
1158 double fov_high = qMax(m_FOVWidth, m_FOVHeight);
1159 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(fov_low);
1160 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(fov_high);
1162 Options::setAstrometryImageScaleLow(fov_low);
1163 Options::setAstrometryImageScaleHigh(fov_high);
1168 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(m_FOVPixelScale * 0.9);
1169 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(m_FOVPixelScale * 1.1);
1172 Options::setAstrometryImageScaleLow(m_FOVPixelScale * 0.9);
1173 Options::setAstrometryImageScaleHigh(m_FOVPixelScale * 1.1);
1200 if (optionsMap.contains(
"noverify"))
1201 solver_args <<
"--no-verify";
1204 if (optionsMap.contains(
"resort"))
1205 solver_args <<
"--resort";
1208 if (optionsMap.contains(
"nofits2fits"))
1209 solver_args <<
"--no-fits2fits";
1212 if (optionsMap.contains(
"downsample"))
1213 solver_args <<
"--downsample" <<
QString::number(optionsMap.value(
"downsample", 2).toInt());
1216 if (optionsMap.contains(
"scaleL"))
1217 solver_args <<
"-L" <<
QString::number(optionsMap.value(
"scaleL").toDouble());
1220 if (optionsMap.contains(
"scaleH"))
1221 solver_args <<
"-H" <<
QString::number(optionsMap.value(
"scaleH").toDouble());
1224 if (optionsMap.contains(
"scaleUnits"))
1225 solver_args <<
"-u" << optionsMap.
value(
"scaleUnits").toString();
1228 if (optionsMap.contains(
"ra"))
1229 solver_args <<
"-3" <<
QString::number(optionsMap.value(
"ra").toDouble());
1232 if (optionsMap.contains(
"de"))
1233 solver_args <<
"-4" <<
QString::number(optionsMap.value(
"de").toDouble());
1236 if (optionsMap.contains(
"radius"))
1237 solver_args <<
"-5" <<
QString::number(optionsMap.value(
"radius").toDouble());
1240 if (optionsMap.contains(
"custom"))
1241 solver_args << optionsMap.
value(
"custom").toString();
1247 void Align::generateFOVBounds(
double fov_w,
QString &fov_low,
QString &fov_high,
double tolerance)
1251 double lower_boundary = 1.0 - tolerance;
1252 double upper_boundary = 1.0 + tolerance;
1259 double fov_lower = fov_w * lower_boundary;
1260 double fov_upper = fov_w * upper_boundary;
1270 QVariantMap optionsMap;
1283 if (Options::astrometryUseNoVerify())
1284 optionsMap[
"noverify"] =
true;
1286 if (Options::astrometryUseResort())
1287 optionsMap[
"resort"] =
true;
1289 if (Options::astrometryUseNoFITS2FITS())
1290 optionsMap[
"nofits2fits"] =
true;
1292 if (data ==
nullptr)
1294 if (Options::astrometryUseDownsample())
1296 if (Options::astrometryAutoDownsample() && m_CameraWidth && m_CameraHeight)
1298 uint16_t w = m_CameraWidth / (alignBinning->currentIndex() + 1);
1299 optionsMap[
"downsample"] = getSolverDownsample(w);
1302 optionsMap[
"downsample"] = Options::astrometryDownsample();
1306 optionsMap[
"image_width"] = m_CameraWidth / (alignBinning->currentIndex() + 1);
1307 optionsMap[
"image_height"] = m_CameraHeight / (alignBinning->currentIndex() + 1);
1309 if (Options::astrometryUseImageScale() && m_FOVWidth > 0 && m_FOVHeight > 0)
1312 if (Options::astrometryImageScaleUnits() == 1)
1314 else if (Options::astrometryImageScaleUnits() == 2)
1316 if (Options::astrometryAutoUpdateImageScale())
1319 double fov_w = m_FOVWidth;
1322 if (Options::astrometryImageScaleUnits() == SSolver::DEG_WIDTH)
1327 else if (Options::astrometryImageScaleUnits() == SSolver::ARCSEC_PER_PIX)
1329 fov_w = m_FOVPixelScale;
1334 generateFOVBounds(fov_w, fov_low, fov_high, m_EffectiveFOVPending ? 0.3 : 0.05);
1336 optionsMap[
"scaleL"] = fov_low;
1337 optionsMap[
"scaleH"] = fov_high;
1338 optionsMap[
"scaleUnits"] = units;
1342 optionsMap[
"scaleL"] = Options::astrometryImageScaleLow();
1343 optionsMap[
"scaleH"] = Options::astrometryImageScaleHigh();
1344 optionsMap[
"scaleUnits"] = units;
1348 if (Options::astrometryUsePosition() && m_Mount !=
nullptr)
1350 double ra = 0, dec = 0;
1351 m_Mount->getEqCoords(&ra, &dec);
1353 optionsMap[
"ra"] = ra * 15.0;
1354 optionsMap[
"de"] = dec;
1355 optionsMap[
"radius"] = Options::astrometryRadius();
1362 data->getRecordValue(
"NAXIS1",
width);
1363 if (
width.isValid())
1365 optionsMap[
"downsample"] = getSolverDownsample(
width.toInt());
1368 optionsMap[
"downsample"] = Options::astrometryDownsample();
1372 data->getRecordValue(
"SCALE", pixscale);
1375 optionsMap[
"scaleL"] = 0.8 * pixscale.
toDouble();
1376 optionsMap[
"scaleH"] = 1.2 * pixscale.
toDouble();
1377 optionsMap[
"scaleUnits"] =
"app";
1382 data->getRecordValue(
"RA", ra);
1383 data->getRecordValue(
"DEC", de);
1388 optionsMap[
"radius"] = Options::astrometryRadius();
1392 if (Options::astrometryCustomOptions().isEmpty() ==
false)
1393 optionsMap[
"custom"] = Options::astrometryCustomOptions();
1400 m_AlignTimer.
stop();
1401 m_CaptureTimer.
stop();
1403 if (m_Camera ==
nullptr)
1405 appendLogText(
i18n(
"Error: No camera detected."));
1409 if (m_Camera->isConnected() ==
false)
1411 appendLogText(
i18n(
"Error: lost connection to camera."));
1412 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"), KSNotification::Align,
1413 KSNotification::Alert);
1417 if (m_Camera->isBLOBEnabled() ==
false)
1419 m_Camera->setBLOBEnabled(
true);
1425 if (m_FocalLength == -1 || m_Aperture == -1)
1427 KSNotification::error(
1428 i18n(
"Telescope aperture and focal length are missing. Please check your optical train settings and try again."));
1432 if (m_CameraPixelWidth == -1 || m_CameraPixelHeight == -1)
1434 KSNotification::error(
i18n(
"CCD pixel size is missing. Please check your driver settings and try again."));
1438 if (m_FilterWheel !=
nullptr)
1440 if (m_FilterWheel->isConnected() ==
false)
1442 appendLogText(
i18n(
"Error: lost connection to filter wheel."));
1446 int targetPosition = alignFilter->currentIndex() + 1;
1448 if (targetPosition > 0 && targetPosition != currentFilterPosition)
1450 filterPositionPending =
true;
1452 m_FilterManager->setFilterPosition(targetPosition, FilterManager::NO_AUTOFOCUS_POLICY);
1458 if (m_Camera->getDriverInfo()->getClientManager()->getBLOBMode(m_Camera->getDeviceName().
toLatin1().
constData(),
1462 nullptr,
i18n(
"Image transfer is disabled for this camera. Would you like to enable it?")) ==
1465 m_Camera->getDriverInfo()->getClientManager()->setBLOBMode(B_ONLY, m_Camera->getDeviceName().
toLatin1().
constData(),
1467 m_Camera->getDriverInfo()->getClientManager()->setBLOBMode(B_ONLY, m_Camera->getDeviceName().
toLatin1().
constData(),
1476 double seqExpose = alignExposure->value();
1478 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1480 if (m_FocusState >= FOCUS_PROGRESS)
1482 appendLogText(
i18n(
"Cannot capture while focus module is busy. Retrying in %1 seconds...",
1483 CAPTURE_RETRY_DELAY / 1000));
1484 m_CaptureTimer.
start(CAPTURE_RETRY_DELAY);
1488 if (targetChip->isCapturing())
1490 appendLogText(
i18n(
"Cannot capture while CCD exposure is in progress. Retrying in %1 seconds...",
1491 CAPTURE_RETRY_DELAY / 1000));
1492 m_CaptureTimer.
start(CAPTURE_RETRY_DELAY);
1497 if (!m_SolveFromFile && m_Rotator && m_Rotator->absoluteAngleState() == IPS_BUSY)
1499 appendLogText(
i18n(
"Cannot capture while rotator is busy. Retrying in %1 seconds...",
1500 CAPTURE_RETRY_DELAY / 1000));
1501 m_CaptureTimer.
start(CAPTURE_RETRY_DELAY);
1505 m_AlignView->setBaseSize(alignWidget->size());
1506 m_AlignView->setProperty(
"suspended", (solverModeButtonGroup->checkedId() == SOLVER_LOCAL
1507 && alignDarkFrame->isChecked()));
1513 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser.get() !=
nullptr)
1515 if (m_RemoteParserDevice ==
nullptr)
1517 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
1523 auto activeDevices = m_RemoteParserDevice->getBaseDevice().getText(
"ACTIVE_DEVICES");
1526 auto activeCCD = activeDevices.findWidgetByName(
"ACTIVE_CCD");
1527 if (
QString(activeCCD->text) != m_Camera->getDeviceName())
1530 m_RemoteParserDevice->getClientManager()->sendNewProperty(activeDevices);
1537 solverTimer.
start();
1543 dir.setNameFilters(
QStringList() <<
"fits*" <<
"tmp.*");
1545 for (
auto &dirFile : dir.entryList())
1548 prepareCapture(targetChip);
1551 if (matchPAHStage(PAA::PAH_REFRESH))
1552 targetChip->capture(m_PolarAlignmentAssistant->getPAHExposureDuration());
1554 targetChip->capture(seqExpose);
1556 solveB->setEnabled(
false);
1557 loadSlewB->setEnabled(
false);
1558 stopB->setEnabled(
true);
1559 pi->startAnimation();
1561 differentialSlewingActivated =
false;
1564 emit newStatus(state);
1565 solverFOV->setProperty(
"visible",
true);
1568 if (matchPAHStage(PAA::PAH_REFRESH))
1571 appendLogText(
i18n(
"Capturing image..."));
1579 m_Mount->getEqCoords(&ra, &dec);
1580 if (!m_SolveFromFile)
1582 int currentRow = solutionTable->rowCount();
1583 solutionTable->insertRow(currentRow);
1584 for (
int i = 4; i < 6; i++)
1588 solutionTable->setItem(currentRow, i, disabledBox);
1592 RAReport->
setText(ScopeRAOut->text());
1595 solutionTable->setItem(currentRow, 0, RAReport);
1598 DECReport->
setText(ScopeDecOut->text());
1601 solutionTable->setItem(currentRow, 1, DECReport);
1603 double maxrad = 1.0;
1619 solutionTable->setItem(currentRow, 2, ObjNameReport);
1625 solutionTable->setCellWidget(currentRow, 3, alignIndicator);
1637 auto chip = data->property(
"chip");
1638 if (chip.isValid() && chip.toInt() == ISD::CameraChip::GUIDE_CCD)
1646 m_AlignView->loadData(data);
1650 m_ImageData.
reset();
1652 RUN_PAH(setImageData(m_ImageData));
1655 if (matchPAHStage(PAA::PAH_REFRESH))
1657 setCaptureComplete();
1661 appendLogText(
i18n(
"Image received."));
1664 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1667 if (alignDarkFrame->isChecked())
1669 int x,
y, w, h, binx = 1, biny = 1;
1670 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1671 targetChip->getFrame(&
x, &
y, &w, &h);
1672 targetChip->getBinning(&binx, &biny);
1674 uint16_t offsetX =
x / binx;
1675 uint16_t offsetY =
y / biny;
1677 m_DarkProcessor->denoise(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()),
1678 targetChip, m_ImageData, alignExposure->value(), offsetX, offsetY);
1682 setCaptureComplete();
1688 if (m_Camera->getUploadMode() == ISD::Camera::UPLOAD_LOCAL)
1690 rememberUploadMode = ISD::Camera::UPLOAD_LOCAL;
1691 m_Camera->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
1694 if (m_Camera->isFastExposureEnabled())
1696 m_RememberCameraFastExposure =
true;
1697 m_Camera->setFastExposureEnabled(
false);
1700 m_Camera->setEncodingFormat(
"FITS");
1701 targetChip->resetFrame();
1702 targetChip->setBatchMode(
false);
1703 targetChip->setCaptureMode(FITS_ALIGN);
1704 targetChip->setFrameType(FRAME_LIGHT);
1706 int bin = alignBinning->currentIndex() + 1;
1707 targetChip->setBinning(bin, bin);
1710 if (m_Camera->hasGain() && alignGain->isEnabled() && alignGain->value() > alignGainSpecialValue)
1711 m_Camera->setGain(alignGain->value());
1713 if (alignISO->currentIndex() >= 0)
1714 targetChip->setISOIndex(alignISO->currentIndex());
1718 void Align::setCaptureComplete()
1720 if (matchPAHStage(PAA::PAH_REFRESH))
1722 emit newFrame(m_AlignView);
1723 m_PolarAlignmentAssistant->processPAHRefresh();
1727 emit newImage(m_AlignView);
1729 solverFOV->setImage(m_AlignView->getDisplayImage());
1732 if (Options::saveAlignImages())
1744 m_ImageData->saveImage(filename);
1752 gotoModeButtonGroup->button(mode)->setChecked(
true);
1753 m_CurrentGotoMode =
static_cast<GotoMode
>(mode);
1762 QStringList astrometryDataDirs = KSUtils::getAstrometryDataDirs();
1765 m_UsedScale =
false;
1766 m_UsedPosition =
false;
1771 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1773 if(Options::solverType() != SSolver::SOLVER_ASTAP
1774 && Options::solverType() != SSolver::SOLVER_WATNEYASTROMETRY)
1776 bool foundAnIndex =
false;
1777 for(
auto &dataDir : astrometryDataDirs)
1784 if(indexList.
count() > 0)
1785 foundAnIndex =
true;
1791 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."));
1793 if(alignSettings && m_IndexFilesPage)
1796 alignSettings->
show();
1800 if (m_StellarSolver->isRunning())
1801 m_StellarSolver->abort();
1803 m_ImageData = m_AlignView->imageData();
1804 m_StellarSolver->loadNewImageBuffer(m_ImageData->getStatistics(), m_ImageData->getImageBuffer());
1805 m_StellarSolver->setProperty(
"ProcessType", SSolver::SOLVE);
1806 m_StellarSolver->setProperty(
"ExtractorType", Options::solveSextractorType());
1807 m_StellarSolver->setProperty(
"SolverType", Options::solverType());
1808 connect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
1809 m_StellarSolver->setIndexFolderPaths(Options::astrometryIndexFolderList());
1811 auto params = m_StellarSolverProfiles.
at(Options::solveOptionsProfile());
1812 params.partition = Options::stellarSolverPartition();
1813 m_StellarSolver->setParameters(params);
1815 const SSolver::SolverType type =
static_cast<SSolver::SolverType
>(m_StellarSolver->property(
"SolverType").toInt());
1816 if(type == SSolver::SOLVER_LOCALASTROMETRY || type == SSolver::SOLVER_ASTAP || type == SSolver::SOLVER_WATNEYASTROMETRY)
1820 m_AlignView->saveImage(filename);
1821 m_StellarSolver->setProperty(
"FileToProcess", filename);
1822 ExternalProgramPaths externalPaths;
1823 externalPaths.sextractorBinaryPath = Options::sextractorBinary();
1824 externalPaths.solverPath = Options::astrometrySolverBinary();
1825 externalPaths.astapBinaryPath = Options::aSTAPExecutable();
1826 externalPaths.watneyBinaryPath = Options::watneyBinary();
1827 externalPaths.wcsPath = Options::astrometryWCSInfo();
1828 m_StellarSolver->setExternalFilePaths(externalPaths);
1831 m_StellarSolver->setProperty(
"AutoGenerateAstroConfig",
true);
1834 if(type == SSolver::SOLVER_ONLINEASTROMETRY )
1838 m_AlignView->saveImage(filename);
1840 m_StellarSolver->setProperty(
"FileToProcess", filename);
1841 m_StellarSolver->setProperty(
"AstrometryAPIKey", Options::astrometryAPIKey());
1842 m_StellarSolver->setProperty(
"AstrometryAPIURL", Options::astrometryAPIURL());
1845 bool useImageScale = Options::astrometryUseImageScale();
1846 if (useBlindScale == BLIND_ENGAGNED)
1848 useImageScale =
false;
1849 useBlindScale = BLIND_USED;
1850 appendLogText(
i18n(
"Solving with blind image scale..."));
1853 bool useImagePosition = Options::astrometryUsePosition();
1854 if (useBlindPosition == BLIND_ENGAGNED)
1856 useImagePosition =
false;
1857 useBlindPosition = BLIND_USED;
1858 appendLogText(
i18n(
"Solving with blind image position..."));
1861 if (m_SolveFromFile)
1863 FITSImage::Solution solution;
1864 m_ImageData->parseSolution(solution);
1866 if (useImageScale && solution.pixscale > 0)
1869 m_ScaleUsed = solution.pixscale;
1870 m_StellarSolver->setSearchScale(solution.pixscale * 0.8,
1871 solution.pixscale * 1.2,
1872 SSolver::ARCSEC_PER_PIX);
1875 m_StellarSolver->setProperty(
"UseScale",
false);
1877 if (useImagePosition && solution.ra > 0)
1879 m_UsedPosition =
true;
1880 m_RAUsed = solution.ra;
1881 m_DECUsed = solution.dec;
1882 m_StellarSolver->setSearchPositionInDegrees(solution.ra, solution.dec);
1885 m_StellarSolver->setProperty(
"UsePosition",
false);
1888 if (!m_ImageData->getRecordValue(
"PIERSIDE", value))
1890 appendLogText(
i18n(
"Loaded image does not have pierside information"));
1891 m_TargetPierside = ISD::Mount::PIER_UNKNOWN;
1895 appendLogText(
i18n(
"Loaded image was taken on pierside %1", value.
toString()));
1896 (value ==
"WEST") ? m_TargetPierside = ISD::Mount::PIER_WEST : m_TargetPierside = ISD::Mount::PIER_EAST;
1898 RotatorUtils::Instance()->Instance()->setImagePierside(m_TargetPierside);
1906 m_ScaleUsed = Options::astrometryImageScaleLow();
1908 SSolver::ScaleUnits units =
static_cast<SSolver::ScaleUnits
>(Options::astrometryImageScaleUnits());
1910 m_StellarSolver->setSearchScale(Options::astrometryImageScaleLow() * 0.8,
1911 Options::astrometryImageScaleHigh() * 1.2,
1915 m_StellarSolver->setProperty(
"UseScale",
false);
1917 if(useImagePosition)
1919 m_StellarSolver->setSearchPositionInDegrees(m_TelescopeCoord.
ra().
Degrees(), m_TelescopeCoord.
dec().
Degrees());
1920 m_UsedPosition =
true;
1921 m_RAUsed = m_TelescopeCoord.
ra().
Degrees();
1922 m_DECUsed = m_TelescopeCoord.
dec().
Degrees();
1925 m_StellarSolver->setProperty(
"UsePosition",
false);
1928 if(Options::alignmentLogging())
1933 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
1934 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
1935 if(Options::astrometryLogToFile())
1937 m_StellarSolver->setProperty(
"LogToFile",
true);
1938 m_StellarSolver->setProperty(
"LogFileName", Options::astrometryLogFilepath());
1943 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
1944 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
1947 SolverUtils::patchMultiAlgorithm(m_StellarSolver.get());
1950 m_StellarSolver->start();
1954 if (m_ImageData.
isNull())
1955 m_ImageData = m_AlignView->imageData();
1958 remoteParser->startSolver(m_ImageData->filename(),
generateRemoteArgs(m_ImageData),
false);
1963 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
1964 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
1965 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
1966 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
1967 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
1968 matchPAHStage(PAA::PAH_THIRD_SOLVE) ||
1969 nothingR->isChecked() ||
1974 solverTimer.
start();
1977 emit newStatus(state);
1980 void Align::solverComplete()
1982 disconnect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
1983 if(!m_StellarSolver->solvingDone() || m_StellarSolver->failed())
1985 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
1986 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
1987 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
1988 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
1989 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
1990 matchPAHStage(PAA::PAH_THIRD_SOLVE))
1992 if (CHECK_PAH(processSolverFailure()))
2002 FITSImage::Solution solution = m_StellarSolver->getSolution();
2003 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false :
true;
2004 solverFinished(solution.orientation, solution.ra, solution.dec, solution.pixscale, eastToTheRight);
2010 pi->stopAnimation();
2011 stopB->setEnabled(
false);
2012 solveB->setEnabled(
true);
2014 sOrientation = orientation;
2018 double elapsed = solverTimer.
elapsed() / 1000.0;
2020 appendLogText(
i18n(
"Solver completed after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2022 m_AlignTimer.
stop();
2023 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && m_RemoteParserDevice && remoteParser.get())
2030 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2031 targetChip->getBinning(&binx, &biny);
2033 if (Options::alignmentLogging())
2035 QString parityString = eastToTheRight ?
"neg" :
"pos";
2036 appendLogText(
i18n(
"Solver RA (%1) DEC (%2) Orientation (%3) Pixel Scale (%4) Parity (%5)",
QString::number(ra,
'f', 5),
2042 if (!m_SolveFromFile &&
2043 (isEqual(m_FOVWidth, 0) || m_EffectiveFOVPending || std::fabs(pixscale - m_FOVPixelScale) > 0.005) &&
2046 double newFOVW = m_CameraWidth * pixscale / binx / 60.0;
2047 double newFOVH = m_CameraHeight * pixscale / biny / 60.0;
2049 calculateEffectiveFocalLength(newFOVW);
2050 saveNewEffectiveFOV(newFOVW, newFOVH);
2052 m_EffectiveFOVPending =
false;
2055 m_AlignCoord.
setRA0(ra / 15.0);
2061 m_AlignCoord.
EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
2064 if (!m_SolveFromFile)
2067 calculateAlignTargetDiff();
2073 double solverPA = KSUtils::rotationToPositionAngle(orientation);
2074 solverFOV->setCenter(m_AlignCoord);
2075 solverFOV->setPA(solverPA);
2076 solverFOV->setImageDisplay(Options::astrometrySolverOverlay());
2078 sensorFOV->setPA(solverPA);
2083 getFormattedCoords(m_AlignCoord.
ra().
Hours(), m_AlignCoord.
dec().
Degrees(), ra_dms, dec_dms);
2085 SolverRAOut->setText(ra_dms);
2086 SolverDecOut->setText(dec_dms);
2088 if (Options::astrometrySolverWCS())
2090 auto ccdRotation = m_Camera->getNumber(
"CCD_ROTATION");
2093 auto rotation = ccdRotation->findWidgetByName(
"CCD_ROTATION_VALUE");
2096 ClientManager *clientManager = m_Camera->getDriverInfo()->getClientManager();
2097 rotation->setValue(orientation);
2098 clientManager->sendNewProperty(ccdRotation);
2100 if (m_wcsSynced ==
false)
2103 i18n(
"WCS information updated. Images captured from this point forward shall have valid WCS."));
2106 auto telescopeInfo = m_Mount->getNumber(
"TELESCOPE_INFO");
2108 clientManager->sendNewProperty(telescopeInfo);
2116 m_CaptureErrorCounter = 0;
2117 m_SlewErrorCounter = 0;
2118 m_CaptureTimeoutCounter = 0;
2121 i18n(
"Solution coordinates: RA (%1) DEC (%2) Telescope Coordinates: RA (%3) DEC (%4) Target Coordinates: RA (%5) DEC (%6)",
2129 if (!m_SolveFromFile && m_CurrentGotoMode == GOTO_SLEW)
2131 dms diffDeg(m_TargetDiffTotal / 3600.0);
2132 appendLogText(
i18n(
"Target is within %1 degrees of solution coordinates.", diffDeg.
toDMSString()));
2135 if (rememberUploadMode != m_Camera->getUploadMode())
2136 m_Camera->setUploadMode(rememberUploadMode);
2139 if (m_RememberCameraFastExposure)
2141 m_RememberCameraFastExposure =
false;
2142 m_Camera->setFastExposureEnabled(
true);
2147 int currentRow = solutionTable->rowCount() - 1;
2148 if (!m_SolveFromFile)
2150 stopProgressAnimation();
2151 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
2154 if (m_Rotator !=
nullptr && m_Rotator->isConnected())
2156 if (
auto absAngle = m_Rotator->getNumber(
"ABS_ROTATOR_ANGLE"))
2159 sRawAngle = absAngle->at(0)->getValue();
2160 double OffsetAngle = RotatorUtils::Instance()->calcOffsetAngle(sRawAngle, solverPA);
2161 RotatorUtils::Instance()->updateOffset(OffsetAngle);
2163 auto reverseStatus =
"Unknown";
2164 auto reverseProperty = m_Rotator->
getSwitch(
"ROTATOR_REVERSE");
2165 if (reverseProperty)
2167 if (reverseProperty->at(0)->getState() == ISS_ON)
2168 reverseStatus =
"Reversed Direction";
2170 reverseStatus =
"Normal Direction";
2172 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << sRawAngle <<
"Rotator PA:" << solverPA
2173 <<
"Rotator Offset:" << OffsetAngle <<
"Direction:" << reverseStatus;
2175 emit newSolverResults(solverPA, ra, dec, pixscale);
2177 appendLogText(
i18n(
"Camera position angle is %1 degrees.", RotatorUtils::Instance()->calcCameraAngle(sRawAngle,
false)));
2184 {
"camera", m_Camera->getDeviceName()},
2185 {
"ra", SolverRAOut->text()},
2186 {
"de", SolverDecOut->text()},
2187 {
"dRA", m_TargetDiffRA},
2188 {
"dDE", m_TargetDiffDE},
2189 {
"targetDiff", m_TargetDiffTotal},
2192 {
"fov", FOVOut->text()},
2197 emit newStatus(state);
2198 solverIterations = 0;
2199 KSNotification::event(
QLatin1String(
"AlignSuccessful"),
i18n(
"Astrometry alignment completed successfully"),
2200 KSNotification::Align);
2202 switch (m_CurrentGotoMode)
2207 if (!m_SolveFromFile)
2209 stopProgressAnimation();
2210 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2211 solutionTable->setItem(currentRow, 3, statusReport.release());
2217 if (m_SolveFromFile || m_TargetDiffTotal >
static_cast<double>(alignAccuracyThreshold->value()))
2219 if (!m_SolveFromFile && ++solverIterations == MAXIMUM_SOLVER_ITERATIONS)
2221 appendLogText(
i18n(
"Maximum number of iterations reached. Solver failed."));
2223 if (!m_SolveFromFile)
2225 statusReport->setIcon(
QIcon(
":/icons/AlignFailure.svg"));
2226 solutionTable->setItem(currentRow, 3, statusReport.release());
2233 targetAccuracyNotMet =
true;
2235 if (!m_SolveFromFile)
2237 stopProgressAnimation();
2238 statusReport->setIcon(
QIcon(
":/icons/AlignWarning.svg"));
2239 solutionTable->setItem(currentRow, 3, statusReport.release());
2246 stopProgressAnimation();
2247 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2248 solutionTable->setItem(currentRow, 3, statusReport.release());
2250 appendLogText(
i18n(
"Target is within acceptable range."));
2254 if (!m_SolveFromFile)
2256 stopProgressAnimation();
2257 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2258 solutionTable->setItem(currentRow, 3, statusReport.release());
2263 solverFOV->setProperty(
"visible",
true);
2265 if (!matchPAHStage(PAA::PAH_IDLE))
2266 m_PolarAlignmentAssistant->processPAHStage(orientation, ra, dec, pixscale, eastToTheRight,
2267 m_StellarSolver->getSolutionHealpix(),
2268 m_StellarSolver->getSolutionIndexNumber());
2274 solveB->setEnabled(
false);
2275 loadSlewB->setEnabled(
false);
2281 emit newStatus(state);
2283 solveB->setEnabled(
true);
2284 loadSlewB->setEnabled(
true);
2292 if (Options::saveFailedAlignImages())
2300 extraFilenameInfo.
append(
QString(
"_s%1u%2").arg(m_ScaleUsed, 0,
'f', 3)
2301 .arg(Options::astrometryImageScaleUnits()));
2303 extraFilenameInfo.
append(
QString(
"_r%1_d%2").arg(m_RAUsed, 0,
'f', 5).arg(m_DECUsed, 0,
'f', 5));
2307 QString name =
"failed_align_frame_" + now.
toString(
"yyyy-MM-dd-HH-mm-ss") + extraFilenameInfo +
".fits";
2308 QString filename = path + QStringLiteral(
"/") + name;
2311 m_ImageData->saveImage(filename);
2312 appendLogText(
i18n(
"Saving failed solver image to %1", filename));
2319 if (Options::astrometryUseImageScale() && useBlindScale == BLIND_IDLE)
2321 appendLogText(
i18n(
"Solver failed. Retrying without scale constraint."));
2322 useBlindScale = BLIND_ENGAGNED;
2323 setAlignTableResult(ALIGN_RESULT_FAILED);
2329 if (Options::astrometryUsePosition() && useBlindPosition == BLIND_IDLE)
2331 appendLogText(
i18n(
"Solver failed. Retrying without position constraint."));
2332 useBlindPosition = BLIND_ENGAGNED;
2333 setAlignTableResult(ALIGN_RESULT_FAILED);
2339 appendLogText(
i18n(
"Solver Failed."));
2340 if(!Options::alignmentLogging())
2342 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."));
2344 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"),
2345 KSNotification::Align, KSNotification::Alert);
2348 pi->stopAnimation();
2349 stopB->setEnabled(
false);
2350 solveB->setEnabled(
true);
2351 loadSlewB->setEnabled(
true);
2353 m_AlignTimer.
stop();
2355 m_SolveFromFile =
false;
2356 solverIterations = 0;
2357 m_CaptureErrorCounter = 0;
2358 m_CaptureTimeoutCounter = 0;
2359 m_SlewErrorCounter = 0;
2362 emit newStatus(state);
2364 solverFOV->setProperty(
"visible",
false);
2366 setAlignTableResult(ALIGN_RESULT_FAILED);
2372 if (Options::astrometryUseRotator())
2374 if (m_SolveFromFile)
2376 m_TargetPositionAngle = solverFOV->PA();
2378 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file: Setting target PA to:" << m_TargetPositionAngle;
2382 currentRotatorPA = solverFOV->PA();
2383 if (std::isnan(m_TargetPositionAngle) ==
false)
2386 if (RotatorUtils::Instance()->Instance()->checkImageFlip() && (Options::astrometryFlipRotationAllowed()))
2389 sRawAngle = RotatorUtils::Instance()->calcRotatorAngle(m_TargetPositionAngle);
2390 m_TargetPositionAngle = RotatorUtils::Instance()->calcCameraAngle(sRawAngle,
true);
2391 RotatorUtils::Instance()->setImagePierside(ISD::Mount::PIER_UNKNOWN);
2394 if (m_Rotator !=
nullptr && m_Rotator->isConnected())
2396 if(fabs(KSUtils::rangePA(currentRotatorPA - m_TargetPositionAngle)) * 60 >
2397 Options::astrometryRotatorThreshold())
2400 emit newSolverResults(m_TargetPositionAngle, 0, 0, 0);
2401 appendLogText(
i18n(
"Setting camera position angle to %1 degrees ...", m_TargetPositionAngle));
2403 emit newStatus(state);
2408 appendLogText(
i18n(
"Camera position angle is within acceptable range."));
2410 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
2416 double current = currentRotatorPA;
2417 double target = m_TargetPositionAngle;
2419 double diff = KSUtils::rangePA(current - target);
2420 double threshold = Options::astrometryRotatorThreshold() / 60.0;
2422 appendLogText(
i18n(
"Current PA is %1; Target PA is %2; diff: %3", current, target, diff));
2424 emit manualRotatorChanged(current, target, threshold);
2426 m_ManualRotator->setRotatorDiff(current, target, diff);
2427 if (fabs(diff) > threshold)
2429 targetAccuracyNotMet =
true;
2430 m_ManualRotator->show();
2431 m_ManualRotator->raise();
2433 emit newStatus(state);
2438 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
2439 targetAccuracyNotMet =
false;
2450 m_CaptureTimer.
stop();
2451 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
2452 m_StellarSolver->abort();
2453 else if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser)
2454 remoteParser->stopSolver();
2456 pi->stopAnimation();
2457 stopB->setEnabled(
false);
2458 solveB->setEnabled(
true);
2459 loadSlewB->setEnabled(
true);
2461 m_SolveFromFile =
false;
2462 solverIterations = 0;
2463 m_CaptureErrorCounter = 0;
2464 m_CaptureTimeoutCounter = 0;
2465 m_SlewErrorCounter = 0;
2466 m_AlignTimer.
stop();
2471 if (rememberUploadMode != m_Camera->getUploadMode())
2472 m_Camera->setUploadMode(rememberUploadMode);
2475 if (m_RememberCameraFastExposure)
2477 m_RememberCameraFastExposure =
false;
2478 m_Camera->setFastExposureEnabled(
true);
2481 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2484 if (matchPAHStage(PAA::PAH_POST_REFRESH))
2486 if (targetChip->isCapturing())
2487 targetChip->abortExposure();
2489 appendLogText(
i18n(
"Refresh is complete."));
2493 if (targetChip->isCapturing())
2495 targetChip->abortExposure();
2496 appendLogText(
i18n(
"Capture aborted."));
2500 double elapsed = solverTimer.
elapsed() / 1000.0;
2502 appendLogText(
i18n(
"Solver aborted after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2507 emit newStatus(state);
2509 setAlignTableResult(ALIGN_RESULT_FAILED);
2514 int currentRow = solutionTable->rowCount() - 1;
2521 QWidget *indicator = solutionTable->cellWidget(currentRow, 3);
2522 if (indicator ==
nullptr)
2527 void Align::stopProgressAnimation()
2530 if (progress_indicator !=
nullptr)
2538 result << sOrientation << sRA << sDEC;
2543 void Align::appendLogText(
const QString &text)
2545 m_LogText.
insert(0,
i18nc(
"log entry; %1 is the date, %2 is the text",
"%1 %2",
2546 KStarsData::Instance()->lt().toString(
"yyyy-MM-ddThh:mm:ss"), text));
2548 qCInfo(KSTARS_EKOS_ALIGN) << text;
2553 void Align::clearLog()
2561 if (prop.isNameMatch(
"EQUATORIAL_EOD_COORD") || prop.isNameMatch(
"EQUATORIAL_COORD"))
2563 auto nvp = prop.getNumber();
2566 getFormattedCoords(m_TelescopeCoord.
ra().
Hours(), m_TelescopeCoord.
dec().
Degrees(), ra_dms, dec_dms);
2568 ScopeRAOut->setText(ra_dms);
2569 ScopeDecOut->setText(dec_dms);
2575 m_wasSlewStarted =
false;
2584 if (m_wasSlewStarted && Options::astrometryAutoUpdatePosition())
2587 opsAstrometry->estRA->setText(ra_dms);
2588 opsAstrometry->estDec->setText(dec_dms);
2590 Options::setAstrometryPositionRA(nvp->np[0].value * 15);
2591 Options::setAstrometryPositionDE(nvp->np[1].value);
2597 if (m_Dome && m_Dome->isMoving())
2604 if (m_wasSlewStarted && matchPAHStage(PAA::PAH_FIND_CP))
2607 m_wasSlewStarted =
false;
2608 appendLogText(
i18n(
"Mount completed slewing near celestial pole. Capture again to verify."));
2611 m_PolarAlignmentAssistant->setPAHStage(PAA::PAH_FIRST_CAPTURE);
2622 m_wasSlewStarted =
false;
2624 if (m_CurrentGotoMode == GOTO_SLEW)
2631 appendLogText(
i18n(
"Mount is synced to solution coordinates."));
2637 i18n(
"Astrometry alignment completed successfully"), KSNotification::Align);
2639 emit newStatus(state);
2640 solverIterations = 0;
2647 if (!didSlewStart())
2655 m_wasSlewStarted =
false;
2656 if (m_SolveFromFile)
2658 m_SolveFromFile =
false;
2659 m_TargetPositionAngle = solverFOV->PA();
2660 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file: Setting target PA to" << m_TargetPositionAngle;
2663 emit newStatus(state);
2665 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
2666 appendLogText(
i18n(
"Settling..."));
2667 m_CaptureTimer.
start(alignSettlingTime->value());
2670 else if (differentialSlewingActivated)
2672 appendLogText(
i18n(
"Differential slewing complete."));
2677 KSNotification::event(
QLatin1String(
"AlignSuccessful"),
i18n(
"Astrometry alignment completed successfully"),
2678 KSNotification::Align);
2680 emit newStatus(state);
2681 solverIterations = 0;
2683 else if (m_CurrentGotoMode == GOTO_SLEW || (m_MountModel && m_MountModel->isRunning()))
2685 if (targetAccuracyNotMet)
2686 appendLogText(
i18n(
"Slew complete. Target accuracy is not met, running solver again..."));
2688 appendLogText(
i18n(
"Slew complete. Solving Alignment Point. . ."));
2690 targetAccuracyNotMet =
false;
2693 emit newStatus(state);
2695 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
2696 appendLogText(
i18n(
"Settling..."));
2697 m_CaptureTimer.
start(alignSettlingTime->value());
2705 m_wasSlewStarted =
false;
2716 m_wasSlewStarted =
true;
2718 handleMountMotion();
2726 m_wasSlewStarted =
false;
2731 appendLogText(
i18n(
"Syncing failed."));
2733 appendLogText(
i18n(
"Slewing failed."));
2735 if (++m_SlewErrorCounter == 3)
2742 if (m_CurrentGotoMode == GOTO_SLEW)
2753 RUN_PAH(processMountRotation(m_TelescopeCoord.
ra(), alignSettlingTime->value()));
2755 else if (prop.isNameMatch(
"ABS_ROTATOR_ANGLE"))
2757 auto nvp = prop.getNumber();
2758 currentRotatorPA = RotatorUtils::Instance()->calcCameraAngle(nvp->np[0].value,
false);
2767 if (std::isnan(m_TargetPositionAngle) ==
false && state ==
ALIGN_ROTATING && nvp->s == IPS_OK)
2769 auto diff = fabs(RotatorUtils::Instance()->DiffPA(currentRotatorPA - m_TargetPositionAngle)) * 60;
2770 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << nvp->np[0].value <<
"Current PA:" << currentRotatorPA
2771 <<
"Target PA:" << m_TargetPositionAngle <<
"Diff (arcmin):" << diff <<
"Offset:"
2772 << Options::pAOffset();
2774 if (diff <= Options::astrometryRotatorThreshold())
2776 appendLogText(
i18n(
"Rotator reached camera position angle."));
2786 if (nvp->s == IPS_OK)
2788 appendLogText(i18n(
"Rotator failed to arrive at the requested position angle (Deviation %1 arcmin).", diff));
2789 setState(ALIGN_FAILED);
2790 emit newStatus(state);
2791 solveB->setEnabled(true);
2792 loadSlewB->setEnabled(true);
2798 else if (prop.isNameMatch(
"DOME_MOTION"))
2801 if (domeReady ==
false && prop.getState() == IPS_OK)
2806 handleMountStatus();
2809 else if (prop.isNameMatch(
"TELESCOPE_MOTION_NS") || prop.isNameMatch(
"TELESCOPE_MOTION_WE"))
2811 switch (prop.getState())
2815 handleMountMotion();
2816 m_wasSlewStarted =
true;
2819 qCDebug(KSTARS_EKOS_ALIGN) <<
"Mount motion finished.";
2820 handleMountStatus();
2826 void Align::handleMountMotion()
2830 if (matchPAHStage(PAA::PAH_IDLE))
2833 appendLogText(
i18n(
"Slew detected, suspend solving..."));
2841 void Align::handleMountStatus()
2843 auto nvp = m_Mount->getNumber(m_Mount->isJ2000() ?
"EQUATORIAL_COORD" :
2844 "EQUATORIAL_EOD_COORD");
2853 if (m_SolveFromFile)
2855 m_TargetCoord = m_AlignCoord;
2857 qCDebug(KSTARS_EKOS_ALIGN) <<
"Solving from file. Setting Target Coordinates align coords. RA:"
2863 else if (m_CurrentGotoMode == GOTO_SYNC)
2865 else if (m_CurrentGotoMode == GOTO_SLEW)
2873 if (m_Mount->Sync(&m_AlignCoord))
2875 emit newStatus(state);
2882 emit newStatus(state);
2883 appendLogText(
i18n(
"Syncing failed."));
2890 emit newStatus(state);
2898 if (m_Mount->Slew(&m_TargetCoord))
2900 slewStartTimer.
start();
2901 appendLogText(
i18n(
"Slewing to target coordinates: RA (%1) DEC (%2).", m_TargetCoord.
ra().
toHMSString(),
2906 appendLogText(
i18n(
"Slewing to target coordinates: RA (%1) DEC (%2) is rejected. (see notification)",
2910 emit newStatus(state);
2911 solveB->setEnabled(
true);
2912 loadSlewB->setEnabled(
true);
2918 if (canSync && !m_SolveFromFile)
2922 if (Ekos::Manager::Instance()->getCurrentJobName().isEmpty())
2924 KSNotification::event(
QLatin1String(
"EkosSchedulerTelescopeSynced"),
2925 i18n(
"Ekos job (%1) - Telescope synced",
2926 Ekos::Manager::Instance()->getCurrentJobName()));
2931 if (Options::astrometryDifferentialSlewing())
2936 m_TargetCoord.
setRA(m_TargetCoord.
ra() - m_TargetDiffRA);
2937 m_TargetCoord.
setDec(m_TargetCoord.
dec() - m_TargetDiffDE);
2939 differentialSlewingActivated =
true;
2941 qCDebug(KSTARS_EKOS_ALIGN) <<
"Using differential slewing. Setting Target Coordinates to RA:"
2956 void Align::getFormattedCoords(
double ra,
double dec,
QString &ra_str,
QString &dec_str)
2967 dec_str =
QString(
"-%1:%2:%3")
2982 "Images (*.fits *.fits.fz *.fit *.fts *.xisf "
2983 "*.jpg *.jpeg *.png *.gif *.bmp "
2984 "*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw *.orf)");
2990 if (fileInfo.
exists() ==
false)
2995 differentialSlewingActivated =
false;
2997 m_SolveFromFile =
true;
2999 if (m_PolarAlignmentAssistant)
3000 m_PolarAlignmentAssistant->stopPAHProcess();
3002 slewR->setChecked(
true);
3003 m_CurrentGotoMode = GOTO_SLEW;
3005 solveB->setEnabled(
false);
3006 loadSlewB->setEnabled(
false);
3007 stopB->setEnabled(
true);
3008 pi->startAnimation();
3010 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && m_RemoteParserDevice ==
nullptr)
3012 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
3016 m_ImageData.
clear();
3018 m_AlignView->loadFile(fileURL);
3027 differentialSlewingActivated =
false;
3028 m_SolveFromFile =
true;
3029 RUN_PAH(stopPAHProcess());
3030 slewR->setChecked(
true);
3031 m_CurrentGotoMode = GOTO_SLEW;
3032 solveB->setEnabled(
false);
3033 loadSlewB->setEnabled(
false);
3034 stopB->setEnabled(
true);
3035 pi->startAnimation();
3039 m_ImageData.
clear();
3042 data->loadFromBuffer(image, extension);
3043 m_AlignView->loadData(data);
3050 alignExposure->setValue(value);
3058 alignBinning->blockSignals(
true);
3059 alignBinning->setCurrentIndex(binIndex);
3060 alignBinning->blockSignals(
false);
3064 if (Options::astrometryImageScaleUnits() == SSolver::ARCSEC_PER_PIX)
3070 if (m_FilterWheel && m_FilterWheel == device)
3077 m_FilterWheel->disconnect(
this);
3079 m_FilterWheel = device;
3083 connect(m_FilterWheel, &ISD::ConcreteDevice::Connected,
this, [
this]()
3085 FilterPosLabel->setEnabled(
true);
3086 alignFilter->setEnabled(
true);
3088 connect(m_FilterWheel, &ISD::ConcreteDevice::Disconnected,
this, [
this]()
3090 FilterPosLabel->setEnabled(
false);
3091 alignFilter->setEnabled(
false);
3095 auto isConnected = m_FilterWheel && m_FilterWheel->isConnected();
3096 FilterPosLabel->setEnabled(isConnected);
3097 alignFilter->setEnabled(isConnected);
3106 return m_FilterWheel->getDeviceName();
3115 alignFilter->setCurrentText(filter);
3125 return alignFilter->currentText();
3130 alignFilter->clear();
3134 FilterPosLabel->setEnabled(
false);
3135 alignFilter->setEnabled(
false);
3139 auto isConnected = m_FilterWheel->isConnected();
3140 FilterPosLabel->setEnabled(isConnected);
3141 alignFilter->setEnabled(alignUseCurrentFilter->isChecked() ==
false);
3143 setupFilterManager();
3145 alignFilter->addItems(m_FilterManager->getFilterLabels());
3146 currentFilterPosition = m_FilterManager->getFilterPosition();
3148 if (alignUseCurrentFilter->isChecked())
3151 alignFilter->setCurrentIndex(currentFilterPosition - 1);
3156 auto filter = m_Settings[
"alignFilter"];
3157 if (filter.isValid())
3158 alignFilter->setCurrentText(filter.toString());
3164 if ((Manager::Instance()->existRotatorController()) && (!m_Rotator || !(Device == m_Rotator)))
3166 rotatorB->setEnabled(
false);
3170 m_RotatorControlPanel->close();
3175 if (Manager::Instance()->getRotatorController(m_Rotator->getDeviceName(), m_RotatorControlPanel))
3180 m_RotatorControlPanel->show();
3181 m_RotatorControlPanel->raise();
3183 rotatorB->setEnabled(
true);
3189 void Align::setWCSEnabled(
bool enable)
3194 auto wcsControl = m_Camera->
getSwitch(
"WCS_CONTROL");
3199 auto wcs_enable = wcsControl->findWidgetByName(
"WCS_ENABLE");
3200 auto wcs_disable = wcsControl->findWidgetByName(
"WCS_DISABLE");
3202 if (!wcs_enable || !wcs_disable)
3205 if ((wcs_enable->getState() == ISS_ON && enable) || (wcs_disable->getState() == ISS_ON && !enable))
3208 wcsControl->reset();
3211 appendLogText(
i18n(
"World Coordinate System (WCS) is enabled."));
3212 wcs_enable->setState(ISS_ON);
3216 appendLogText(
i18n(
"World Coordinate System (WCS) is disabled."));
3217 wcs_disable->setState(ISS_ON);
3218 m_wcsSynced =
false;
3221 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
3223 clientManager->sendNewProperty(wcsControl);
3228 INDI_UNUSED(targetChip);
3229 INDI_UNUSED(remaining);
3231 if (state == IPS_ALERT)
3233 if (++m_CaptureErrorCounter == 3 && !matchPAHStage(PolarAlignmentAssistant::PAH_REFRESH))
3235 appendLogText(
i18n(
"Capture error. Aborting..."));
3240 appendLogText(
i18n(
"Restarting capture attempt #%1", m_CaptureErrorCounter));
3241 setAlignTableResult(ALIGN_RESULT_FAILED);
3246 void Align::setAlignTableResult(AlignResult result)
3253 if (progress_indicator ==
nullptr || ! progress_indicator->
isAnimated())
3255 stopProgressAnimation();
3260 case ALIGN_RESULT_SUCCESS:
3261 icon =
QIcon(
":/icons/AlignSuccess.svg");
3264 case ALIGN_RESULT_WARNING:
3265 icon =
QIcon(
":/icons/AlignWarning.svg");
3268 case ALIGN_RESULT_FAILED:
3270 icon =
QIcon(
":/icons/AlignFailure.svg");
3273 int currentRow = solutionTable->rowCount() - 1;
3274 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
3278 solutionTable->setItem(currentRow, 3, statusReport);
3281 void Align::setFocusStatus(Ekos::FocusState state)
3283 m_FocusState = state;
3286 uint8_t Align::getSolverDownsample(uint16_t binnedW)
3288 uint8_t downsample = Options::astrometryDownsample();
3290 if (!Options::astrometryAutoDownsample())
3293 while (downsample < 8)
3295 if (binnedW / downsample <= 1024)
3309 if (m_Mount && m_Mount->hasAlignmentModel() && Options::resetMountModelAfterMeridian())
3311 qCDebug(KSTARS_EKOS_ALIGN) <<
"Post meridian flip mount model reset" << (m_Mount->clearAlignmentModel() ?
3312 "successful." :
"failed.");
3315 m_CaptureTimer.
start(alignSettlingTime->value());
3321 if (std::isnan(m_TargetPositionAngle) ==
false)
3322 m_TargetPositionAngle = KSUtils::rangePA(m_TargetPositionAngle + 180.0);
3328 m_CaptureState = newState;
3331 void Align::showFITSViewer()
3333 static int lastFVTabID = -1;
3340 fv->loadData(m_ImageData, url, &lastFVTabID);
3342 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) ==
false)
3343 fv->loadData(m_ImageData, url, &lastFVTabID);
3349 void Align::toggleAlignWidgetFullScreen()
3351 if (alignWidget->parent() ==
nullptr)
3353 alignWidget->setParent(
this);
3354 rightLayout->insertWidget(0, alignWidget);
3355 alignWidget->showNormal();
3359 alignWidget->setParent(
nullptr);
3360 alignWidget->setWindowTitle(
i18nc(
"@title:window",
"Align Frame"));
3362 alignWidget->showMaximized();
3363 alignWidget->show();
3367 void Align::setMountStatus(ISD::Mount::Status newState)
3371 case ISD::Mount::MOUNT_PARKED:
3372 solveB->setEnabled(
false);
3373 loadSlewB->setEnabled(
false);
3375 case ISD::Mount::MOUNT_IDLE:
3376 solveB->setEnabled(
true);
3377 loadSlewB->setEnabled(
true);
3379 case ISD::Mount::MOUNT_PARKING:
3380 solveB->setEnabled(
false);
3381 loadSlewB->setEnabled(
false);
3383 case ISD::Mount::MOUNT_SLEWING:
3384 case ISD::Mount::MOUNT_MOVING:
3385 solveB->setEnabled(
false);
3386 loadSlewB->setEnabled(
false);
3392 solveB->setEnabled(
true);
3393 if (matchPAHStage(PAA::PAH_IDLE))
3395 loadSlewB->setEnabled(
true);
3401 RUN_PAH(setMountStatus(newState));
3406 m_RemoteParserDevice = device;
3408 remoteSolverR->setEnabled(
true);
3409 if (remoteParser.get() !=
nullptr)
3411 remoteParser->setAstrometryDevice(m_RemoteParserDevice);
3419 solverFOV->setImageDisplay(Options::astrometrySolverWCS());
3420 m_AlignTimer.
setInterval(Options::astrometryTimeout() * 1000);
3422 m_RotatorControlPanel->updateFlipPolicy(Options::astrometryFlipRotationAllowed());
3425 void Align::setupOptions()
3433 opsAlign =
new OpsAlign(
this);
3439 opsPrograms =
new OpsPrograms(
this);
3440 page = dialog->
addPage(opsPrograms,
i18n(
"External & Online Programs"));
3443 opsAstrometry =
new OpsAstrometry(
this);
3444 page = dialog->
addPage(opsAstrometry,
i18n(
"Scale & Position"));
3445 page->
setIcon(
QIcon(
":/icons/center_telescope_red.svg"));
3447 optionsProfileEditor =
new StellarSolverProfileEditor(
this, Ekos::AlignProfiles, dialog);
3448 page = dialog->
addPage(optionsProfileEditor,
i18n(
"Align Options Profiles Editor"));
3449 connect(optionsProfileEditor, &StellarSolverProfileEditor::optionsProfilesUpdated,
this, [
this]()
3451 if(
QFile(savedOptionsProfiles).exists())
3452 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
3454 m_StellarSolverProfiles = getDefaultAlignOptionsProfiles();
3455 opsAlign->reloadOptionsProfiles();
3459 connect(opsAlign, &OpsAlign::needToLoadProfile,
this, [
this, dialog, page](
const QString & profile)
3461 optionsProfileEditor->loadProfile(profile);
3465 opsAstrometryIndexFiles =
new OpsAstrometryIndexFiles(
this);
3466 m_IndexFilesPage = dialog->
addPage(opsAstrometryIndexFiles,
i18n(
"Index Files"));
3470 void Align::setupSolutionTable()
3474 clearAllSolutionsB->setIcon(
3481 exportSolutionsCSV->setIcon(
3496 void Align::setupPlot()
3498 double accuracyRadius = alignAccuracyThreshold->value();
3501 alignPlot->setSelectionTolerance(10);
3512 alignPlot->xAxis->setTickLabelColor(
Qt::white);
3513 alignPlot->yAxis->setTickLabelColor(
Qt::white);
3515 alignPlot->xAxis->setLabelColor(
Qt::white);
3516 alignPlot->yAxis->setLabelColor(
Qt::white);
3518 alignPlot->xAxis->setLabelFont(
QFont(
font().family(), 10));
3519 alignPlot->yAxis->setLabelFont(
QFont(
font().family(), 10));
3521 alignPlot->xAxis->setLabelPadding(2);
3522 alignPlot->yAxis->setLabelPadding(2);
3531 alignPlot->xAxis->setLabel(
i18n(
"dRA (arcsec)"));
3532 alignPlot->yAxis->setLabel(
i18n(
"dDE (arcsec)"));
3534 alignPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
3535 alignPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
3540 alignPlot->addGraph();
3550 alignPlot->resize(190, 190);
3551 alignPlot->replot();
3554 void Align::setupFilterManager()
3557 if (m_FilterManager)
3558 m_FilterManager->disconnect(
this);
3561 Ekos::Manager::Instance()->createFilterManager(m_FilterWheel);
3564 Ekos::Manager::Instance()->getFilterManager(m_FilterWheel->getDeviceName(), m_FilterManager);
3566 connect(m_FilterManager.
get(), &FilterManager::ready,
this, [
this]()
3568 if (filterPositionPending)
3570 m_FocusState = FOCUS_IDLE;
3571 filterPositionPending = false;
3576 connect(m_FilterManager.get(), &FilterManager::failed,
this, [
this]()
3578 appendLogText(i18n(
"Filter operation failed."));
3583 connect(m_FilterManager.get(), &FilterManager::newStatus,
this, [
this](Ekos::FilterState filterState)
3585 if (filterPositionPending)
3587 switch (filterState)
3590 appendLogText(i18n(
"Changing focus offset by %1 steps...", m_FilterManager->getTargetFilterOffset()));
3595 const int filterComboIndex = m_FilterManager->getTargetFilterPosition() - 1;
3596 if (filterComboIndex >= 0 && filterComboIndex < alignFilter->count())
3597 appendLogText(i18n(
"Changing filter to %1...", alignFilter->itemText(filterComboIndex)));
3601 case FILTER_AUTOFOCUS:
3602 appendLogText(i18n(
"Auto focus on filter change..."));
3611 connect(m_FilterManager.get(), &FilterManager::labelsChanged,
this, &Align::checkFilter);
3612 connect(m_FilterManager.get(), &FilterManager::positionChanged,
this, &Align::checkFilter);
3615 QVariantMap Align::getEffectiveFOV()
3617 KStarsData::Instance()->
userdb()->GetAllEffectiveFOVs(effectiveFOVs);
3619 m_FOVWidth = m_FOVHeight = 0;
3621 for (
auto &map : effectiveFOVs)
3623 if (map[
"Profile"].
toString() == m_ActiveProfile->name && map[
"Train"].toString() == opticalTrain())
3625 if (isEqual(map[
"Width"].toInt(), m_CameraWidth) &&
3626 isEqual(map[
"Height"].toInt(), m_CameraHeight) &&
3627 isEqual(map[
"PixelW"].toDouble(), m_CameraPixelWidth) &&
3628 isEqual(map[
"PixelH"].toDouble(), m_CameraPixelHeight) &&
3629 isEqual(map[
"FocalLength"].toDouble(), m_FocalLength) &&
3630 isEqual(map[
"FocalRedcuer"].toDouble(), m_Reducer) &&
3631 isEqual(map[
"FocalRatio"].toDouble(), m_FocalRatio))
3633 m_FOVWidth =
map[
"FovW"].toDouble();
3634 m_FOVHeight =
map[
"FovH"].toDouble();
3640 return QVariantMap();
3643 void Align::saveNewEffectiveFOV(
double newFOVW,
double newFOVH)
3645 if (newFOVW < 0 || newFOVH < 0 || (isEqual(newFOVW, m_FOVWidth) && isEqual(newFOVH, m_FOVHeight)))
3648 QVariantMap effectiveMap = getEffectiveFOV();
3651 if (effectiveMap.isEmpty() ==
false)
3652 KStarsData::Instance()->
userdb()->DeleteEffectiveFOV(effectiveMap[
"id"].
toString());
3655 if (newFOVW == 0.0 && newFOVH == 0.0)
3661 effectiveMap[
"Profile"] = m_ActiveProfile->name;
3662 effectiveMap[
"Train"] = opticalTrainCombo->currentText();
3663 effectiveMap[
"Width"] = m_CameraWidth;
3664 effectiveMap[
"Height"] = m_CameraHeight;
3665 effectiveMap[
"PixelW"] = m_CameraPixelWidth;
3666 effectiveMap[
"PixelH"] = m_CameraPixelHeight;
3667 effectiveMap[
"FocalLength"] = m_FocalLength;
3668 effectiveMap[
"FocalReducer"] = m_Reducer;
3669 effectiveMap[
"FocalRatio"] = m_FocalRatio;
3670 effectiveMap[
"FovW"] = newFOVW;
3671 effectiveMap[
"FovH"] = newFOVH;
3673 KStarsData::Instance()->
userdb()->AddEffectiveFOV(effectiveMap);
3679 void Align::zoomAlignView()
3681 m_AlignView->ZoomDefault();
3687 emit newFrame(m_AlignView);
3691 void Align::setAlignZoom(
double scale)
3694 m_AlignView->ZoomIn();
3696 m_AlignView->ZoomOut();
3702 emit newFrame(m_AlignView);
3706 void Align::syncFOV()
3708 QString newFOV = FOVOut->text();
3711 if (
match.hasMatch())
3713 double newFOVW =
match.captured(1).toDouble();
3714 double newFOVH =
match.captured(2).toDouble();
3717 saveNewEffectiveFOV(newFOVW, newFOVH);
3719 FOVOut->setStyleSheet(
QString());
3723 KSNotification::error(
i18n(
"Invalid FOV."));
3724 FOVOut->setStyleSheet(
"background-color:red");
3729 bool Align::didSlewStart()
3731 if (m_wasSlewStarted)
3733 if (slewStartTimer.isValid() && slewStartTimer.elapsed() > MAX_WAIT_FOR_SLEW_START_MSEC)
3735 qCDebug(KSTARS_EKOS_ALIGN) <<
"Slew timed out...waited > 10s, it must have started already.";
3741 void Align::setTargetCoords(
double ra0,
double de0)
3752 m_TargetCoord = targetCoord;
3753 qCInfo(KSTARS_EKOS_ALIGN) <<
"Target coordinates updated to JNow RA:" << m_TargetCoord.
ra().
toHMSString()
3754 <<
"DE:" << m_TargetCoord.dec().toDMSString();
3759 return QList<double>() << m_TargetCoord.ra0().Hours() << m_TargetCoord.dec0().Degrees();
3762 void Align::setTargetPositionAngle(
double value)
3764 m_TargetPositionAngle = value;
3765 qCDebug(KSTARS_EKOS_ALIGN) <<
"Target PA updated to: " << m_TargetPositionAngle;
3768 void Align::calculateAlignTargetDiff()
3770 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
3771 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
3772 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
3773 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
3774 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
3775 matchPAHStage(PAA::PAH_THIRD_SOLVE) ||
3776 nothingR->isChecked() ||
3779 m_TargetDiffRA = (m_AlignCoord.ra().deltaAngle(m_TargetCoord.ra())).Degrees() * 3600;
3780 m_TargetDiffDE = (m_AlignCoord.dec().deltaAngle(m_TargetCoord.dec())).Degrees() * 3600;
3782 dms RADiff(fabs(m_TargetDiffRA) / 3600.0), DEDiff(m_TargetDiffDE / 3600.0);
3784 m_TargetDiffTotal = sqrt(m_TargetDiffRA * m_TargetDiffRA + m_TargetDiffDE * m_TargetDiffDE);
3786 errOut->setText(
QString(
"%1 arcsec. RA:%2 DE:%3").arg(
3790 if (m_TargetDiffTotal <=
static_cast<double>(alignAccuracyThreshold->value()))
3791 errOut->setStyleSheet(
"color:green");
3792 else if (m_TargetDiffTotal < 1.5 * alignAccuracyThreshold->value())
3793 errOut->setStyleSheet(
"color:yellow");
3795 errOut->setStyleSheet(
"color:red");
3798 int currentRow = solutionTable->rowCount() - 1;
3805 solutionTable->setItem(currentRow, 4, dRAReport);
3814 solutionTable->setItem(currentRow, 5, dDECReport);
3817 double raPlot = m_TargetDiffRA;
3818 double decPlot = m_TargetDiffDE;
3819 alignPlot->graph(0)->addData(raPlot, decPlot);
3825 textLabel->position->
setCoords(raPlot, decPlot);
3833 if (!alignPlot->xAxis->range().contains(m_TargetDiffRA))
3835 alignPlot->graph(0)->rescaleKeyAxis(
true);
3836 alignPlot->yAxis->setScaleRatio(alignPlot->xAxis, 1.0);
3838 if (!alignPlot->yAxis->range().contains(m_TargetDiffDE))
3840 alignPlot->graph(0)->rescaleValueAxis(
true);
3841 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
3843 alignPlot->replot();
3849 for (
auto ¶m : m_StellarSolverProfiles)
3850 profiles << param.listName;
3855 void Align::exportSolutionPoints()
3857 if (solutionTable->rowCount() == 0)
3862 "CSV File (*.csv)");
3873 i18n(
"A file named \"%1\" already exists. "
3877 if (r == KMessageBox::Cancel)
3884 KSNotification::sorry(
message,
i18n(
"Invalid URL"));
3893 KSNotification::sorry(
message,
i18n(
"Could Not Open File"));
3901 outstream <<
"RA (J" << epoch <<
"),DE (J" << epoch
3902 <<
"),RA (degrees),DE (degrees),Name,RA Error (arcsec),DE Error (arcsec)" <<
Qt::endl;
3904 for (
int i = 0; i < solutionTable->rowCount(); i++)
3912 if (!raCell || !deCell || !objNameCell || !raErrorCell || !deErrorCell)
3914 KSNotification::sorry(
i18n(
"Error in table structure."));
3923 emit newLog(
i18n(
"Solution Points Saved as: %1", path));
3927 void Align::setupPolarAlignmentAssistant()
3930 m_PolarAlignmentAssistant =
new PolarAlignmentAssistant(
this, m_AlignView);
3932 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newAlignTableResult,
this, &Ekos::Align::setAlignTableResult);
3933 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newFrame,
this, &Ekos::Align::newFrame);
3934 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newPAHStage,
this, &Ekos::Align::processPAHStage);
3935 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newLog,
this, &Ekos::Align::appendLogText);
3937 tabWidget->addTab(m_PolarAlignmentAssistant,
i18n(
"Polar Alignment"));
3940 void Align::setupManualRotator()
3942 if (m_ManualRotator)
3945 m_ManualRotator =
new ManualRotator(
this);
3949 connect(m_ManualRotator, &Ekos::ManualRotator::rejected,
this, [
this]()
3951 m_TargetPositionAngle = std::numeric_limits<double>::quiet_NaN();
3956 void Align::setuptDarkProcessor()
3958 if (m_DarkProcessor)
3961 m_DarkProcessor =
new DarkProcessor(
this);
3962 connect(m_DarkProcessor, &DarkProcessor::newLog,
this, &Ekos::Align::appendLogText);
3963 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted,
this, [
this](
bool completed)
3965 alignDarkFrame->setChecked(completed);
3966 m_AlignView->setProperty(
"suspended",
false);
3969 m_AlignView->rescale(ZOOM_KEEP_LEVEL);
3970 m_AlignView->updateFrame();
3972 setCaptureComplete();
3976 void Align::processPAHStage(
int stage)
3984 if (m_StellarSolver && m_StellarSolver->isRunning())
3985 m_StellarSolver->abort();
3987 case PAA::PAH_POST_REFRESH:
3989 Options::setAstrometrySolverWCS(rememberSolverWCS);
3990 Options::setAutoWCS(rememberAutoWCS);
3995 case PAA::PAH_FIRST_CAPTURE:
3996 nothingR->setChecked(
true);
3997 m_CurrentGotoMode = GOTO_NOTHING;
3998 loadSlewB->setEnabled(
false);
4000 rememberSolverWCS = Options::astrometrySolverWCS();
4001 rememberAutoWCS = Options::autoWCS();
4003 Options::setAutoWCS(
false);
4004 Options::setAstrometrySolverWCS(
true);
4006 case PAA::PAH_SECOND_CAPTURE:
4007 case PAA::PAH_THIRD_CAPTURE:
4008 if (alignSettlingTime->value() >= DELAY_THRESHOLD_NOTIFY)
4009 emit newLog(
i18n(
"Settling..."));
4010 m_CaptureTimer.start(alignSettlingTime->value());
4017 emit newPAAStage(stage);
4020 bool Align::matchPAHStage(uint32_t stage)
4022 return m_PolarAlignmentAssistant && m_PolarAlignmentAssistant->getPAHStage() == stage;
4025 void Align::toggleManualRotator(
bool toggled)
4029 m_ManualRotator->show();
4030 m_ManualRotator->raise();
4033 m_ManualRotator->close();
4036 void Align::setupOpticalTrainManager()
4038 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated,
this, &Align::refreshOpticalTrain);
4041 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
4045 ProfileSettings::Instance()->setOneSetting(ProfileSettings::AlignOpticalTrain,
4046 OpticalTrainManager::Instance()->
id(opticalTrainCombo->itemText(index)));
4047 refreshOpticalTrain();
4048 emit trainChanged();
4052 void Align::refreshOpticalTrain()
4054 opticalTrainCombo->blockSignals(
true);
4055 opticalTrainCombo->clear();
4056 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
4057 trainB->setEnabled(
true);
4059 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::AlignOpticalTrain);
4063 auto id = trainID.
toUInt();
4066 if (OpticalTrainManager::Instance()->exists(
id) ==
false)
4068 qCWarning(KSTARS_EKOS_ALIGN) <<
"Optical train doesn't exist for id" << id;
4069 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
4072 auto name = OpticalTrainManager::Instance()->name(
id);
4074 opticalTrainCombo->setCurrentText(name);
4076 auto scope = OpticalTrainManager::Instance()->getScope(name);
4077 m_FocalLength = scope[
"focal_length"].
toDouble(-1);
4078 m_Aperture = scope[
"aperture"].toDouble(-1);
4079 m_FocalRatio = scope[
"focal_ratio"].toDouble(-1);
4080 m_Reducer = OpticalTrainManager::Instance()->getReducer(name);
4083 if (m_Aperture < 0 && m_FocalRatio > 0)
4084 m_Aperture = m_FocalLength / m_FocalRatio;
4086 auto mount = OpticalTrainManager::Instance()->getMount(name);
4089 auto camera = OpticalTrainManager::Instance()->getCamera(name);
4092 camera->setScopeInfo(m_FocalLength * m_Reducer, m_Aperture);
4093 opticalTrainCombo->setToolTip(
QString(
"%1 @ %2").arg(camera->getDeviceName(), scope[
"name"].toString()));
4097 syncTelescopeInfo();
4099 auto filterWheel = OpticalTrainManager::Instance()->getFilterWheel(name);
4100 setFilterWheel(filterWheel);
4102 auto rotator = OpticalTrainManager::Instance()->getRotator(name);
4103 setRotator(rotator);
4106 OpticalTrainSettings::Instance()->setOpticalTrainID(
id);
4107 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Align);
4108 if (settings.isValid())
4109 setAllSettings(settings.toJsonObject().toVariantMap());
4111 m_Settings = m_GlobalSettings;
4114 Options::setTelescopeFocalLength(m_FocalLength);
4115 Options::setCameraPixelWidth(m_CameraPixelWidth);
4116 Options::setCameraPixelHeight(m_CameraPixelHeight);
4117 Options::setCameraWidth(m_CameraWidth);
4118 Options::setCameraHeight(m_CameraHeight);
4121 opticalTrainCombo->blockSignals(
false);
4124 void Align::syncSettings()
4135 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
4138 value = dsb->
value();
4141 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
4144 value = sb->
value();
4146 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
4151 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
4156 else if ( (cradio = qobject_cast<QRadioButton*>(sender())))
4165 if (m_Settings.contains(key))
4167 m_Settings.remove(key);
4168 emit settingsUpdated(getAllSettings());
4169 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()));
4170 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Align, m_Settings);
4179 Options::self()->setProperty(key.
toLatin1(), value);
4180 Options::self()->save();
4182 m_Settings[key] = value;
4183 m_GlobalSettings[key] = value;
4185 emit settingsUpdated(getAllSettings());
4188 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()));
4189 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Align, m_Settings);
4192 void Align::loadGlobalSettings()
4197 QVariantMap settings;
4199 for (
auto &oneWidget : findChildren<QComboBox*>())
4201 if (oneWidget->objectName() ==
"opticalTrainCombo")
4204 key = oneWidget->objectName();
4205 value = Options::self()->property(key.
toLatin1());
4208 oneWidget->setCurrentText(value.
toString());
4209 settings[key] = value;
4214 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4216 key = oneWidget->objectName();
4217 value = Options::self()->property(key.
toLatin1());
4221 settings[key] = value;
4226 for (
auto &oneWidget : findChildren<QSpinBox*>())
4228 key = oneWidget->objectName();
4229 value = Options::self()->property(key.
toLatin1());
4233 settings[key] = value;
4238 for (
auto &oneWidget : findChildren<QCheckBox*>())
4240 key = oneWidget->objectName();
4241 value = Options::self()->property(key.
toLatin1());
4244 oneWidget->setChecked(value.
toBool());
4245 settings[key] = value;
4249 m_GlobalSettings = m_Settings = settings;
4253 void Align::connectSettings()
4256 for (
auto &oneWidget : findChildren<QComboBox*>())
4257 connect(oneWidget, QOverload<int>::of(&
QComboBox::activated),
this, &Ekos::Align::syncSettings);
4260 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4264 for (
auto &oneWidget : findChildren<QSpinBox*>())
4268 for (
auto &oneWidget : findChildren<QCheckBox*>())
4272 for (
auto &oneWidget : findChildren<QRadioButton*>())
4276 disconnect(opticalTrainCombo, QOverload<int>::of(&
QComboBox::activated),
this, &Ekos::Align::syncSettings);
4279 void Align::disconnectSettings()
4282 for (
auto &oneWidget : findChildren<QComboBox*>())
4283 disconnect(oneWidget, QOverload<int>::of(&
QComboBox::activated),
this, &Ekos::Align::syncSettings);
4286 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4290 for (
auto &oneWidget : findChildren<QSpinBox*>())
4294 for (
auto &oneWidget : findChildren<QCheckBox*>())
4298 for (
auto &oneWidget : findChildren<QRadioButton*>())
4306 QVariantMap Align::getAllSettings()
const
4308 QVariantMap settings;
4311 for (
auto &oneWidget : findChildren<QComboBox*>())
4312 settings.insert(oneWidget->objectName(), oneWidget->currentText());
4315 for (
auto &oneWidget : findChildren<QDoubleSpinBox*>())
4316 settings.insert(oneWidget->objectName(), oneWidget->value());
4319 for (
auto &oneWidget : findChildren<QSpinBox*>())
4320 settings.insert(oneWidget->objectName(), oneWidget->value());
4323 for (
auto &oneWidget : findChildren<QCheckBox*>())
4324 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
4332 void Align::setAllSettings(
const QVariantMap &settings)
4336 disconnectSettings();
4338 for (
auto &name : settings.keys())
4341 auto comboBox = findChild<QComboBox*>(name);
4344 syncControl(settings, name, comboBox);
4349 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
4352 syncControl(settings, name, doubleSpinBox);
4357 auto spinBox = findChild<QSpinBox*>(name);
4360 syncControl(settings, name, spinBox);
4365 auto checkbox = findChild<QCheckBox*>(name);
4368 syncControl(settings, name, checkbox);
4373 auto radioButton = findChild<QRadioButton*>(name);
4376 syncControl(settings, name, radioButton);
4382 for (
auto &key : settings.keys())
4384 auto value = settings[key];
4386 Options::self()->setProperty(key.
toLatin1(), value);
4387 Options::self()->save();
4389 m_Settings[key] = value;
4390 m_GlobalSettings[key] = value;
4393 emit settingsUpdated(getAllSettings());
4396 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->
id(opticalTrainCombo->currentText()));
4397 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Align, m_Settings);
4406 bool Align::syncControl(
const QVariantMap &settings,
const QString &key,
QWidget * widget)
4415 if ((pSB = qobject_cast<QSpinBox *>(widget)))
4417 const int value = settings[key].
toInt(&ok);
4424 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
4426 const double value = settings[key].toDouble(&ok);
4433 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
4435 const bool value = settings[key].toBool();
4440 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
4442 const QString value = settings[key].toString();
4446 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
4448 const bool value = settings[key].toBool();
4456 void Align::setState(AlignState value)
4458 qCDebug(KSTARS_EKOS_ALIGN) <<
"Align state changed to" << getAlignStatusString(value);
4462 void Align::processCaptureTimeout()
4464 if (m_CaptureTimeoutCounter++ > 3)
4466 appendLogText(
i18n(
"Capture timed out."));
4467 m_CaptureTimer.stop();
4472 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
4473 if (targetChip->isCapturing())
4475 appendLogText(
i18n(
"Capturing still running, Retrying in %1 seconds...", m_CaptureTimer.interval() / 500));
4476 targetChip->abortExposure();
4477 m_CaptureTimer.start( m_CaptureTimer.interval() * 2);
4481 setAlignTableResult(ALIGN_RESULT_FAILED);