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 "polaralign.h"
27 #include "manualrotator.h"
30 #include "fitsviewer/fitsdata.h"
31 #include "fitsviewer/fitstab.h"
34 #include "auxiliary/QProgressIndicator.h"
35 #include "auxiliary/ksmessagebox.h"
36 #include "ekos/auxiliary/stellarsolverprofileeditor.h"
37 #include "dialogs/finddialog.h"
38 #include "ksnotification.h"
42 #include "kstarsdata.h"
43 #include "skymapcomposite.h"
46 #include "ekos/manager.h"
47 #include "indi/clientmanager.h"
48 #include "indi/driverinfo.h"
49 #include "indi/indifilterwheel.h"
50 #include "indi/indiauxiliary.h"
51 #include "profileinfo.h"
54 #include <KActionCollection>
55 #include <basedevice.h>
62 #define MAXIMUM_SOLVER_ITERATIONS 10
63 #define CAPTURE_RETRY_DELAY 10000
64 #define PAH_CUTOFF_FOV 10 // Minimum FOV width in arcminutes for PAH to work
65 #define CHECK_PAH(x) \
66 m_PolarAlignmentAssistant && m_PolarAlignmentAssistant->x
68 if (m_PolarAlignmentAssistant) m_PolarAlignmentAssistant->x
75 Align::Align(ProfileInfo *activeProfile) : m_ActiveProfile(activeProfile)
79 qRegisterMetaType<Ekos::AlignState>(
"Ekos::AlignState");
80 qDBusRegisterMetaType<Ekos::AlignState>();
82 new AlignAdaptor(
this);
87 KStarsData::Instance()->clearTransientFOVs();
90 solverFOV.reset(
new FOV());
91 solverFOV->setName(
i18n(
"Solver FOV"));
92 solverFOV->setObjectName(
"solver_fov");
93 solverFOV->setLockCelestialPole(
true);
94 solverFOV->setColor(
KStars::Instance()->data()->colorScheme()->colorNamed(
"SolverFOVColor").name());
95 solverFOV->setProperty(
"visible",
false);
98 sensorFOV.reset(
new FOV());
99 sensorFOV->setObjectName(
"sensor_fov");
100 sensorFOV->setLockCelestialPole(
true);
101 sensorFOV->setProperty(
"visible", Options::showSensorFOV());
108 showFITSViewerB->setIcon(
113 toggleFullScreenB->setIcon(
117 connect(toggleFullScreenB, &
QPushButton::clicked,
this, &Ekos::Align::toggleAlignWidgetFullScreen);
119 m_AlignView.reset(
new AlignView(alignWidget, FITS_ALIGN));
121 m_AlignView->setBaseSize(alignWidget->size());
122 m_AlignView->createFloatingToolBar();
126 alignWidget->setLayout(vlayout);
130 updateTargetCoords();
138 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
140 &Ekos::Align::setDefaultCCD);
143 &Ekos::Align::setDefaultCCD);
152 FilterDevicesCombo->addItem(
"--");
153 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
162 Options::setDefaultAlignFilterWheel(text);
171 Options::setLockAlignFilterIndex(index);
175 gotoModeButtonGroup->setId(syncR, GOTO_SYNC);
176 gotoModeButtonGroup->setId(slewR, GOTO_SLEW);
177 gotoModeButtonGroup->setId(nothingR, GOTO_NOTHING);
179 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
187 this->m_CurrentGotoMode =
static_cast<GotoMode
>(id);
190 m_CaptureTimer.setSingleShot(
true);
191 m_CaptureTimer.setInterval(CAPTURE_RETRY_DELAY);
194 if (m_CaptureTimeoutCounter++ > 3)
196 appendLogText(
i18n(
"Capture timed out."));
197 m_CaptureTimer.stop();
202 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
203 if (targetChip->isCapturing())
205 appendLogText(
i18n(
"Capturing still running, Retrying in %1 seconds...", m_CaptureTimer.interval() / 500));
206 targetChip->abortExposure();
207 m_CaptureTimer.start( m_CaptureTimer.interval() * 2);
211 setAlignTableResult(ALIGN_RESULT_FAILED);
217 m_AlignTimer.setSingleShot(
true);
218 m_AlignTimer.setInterval(Options::astrometryTimeout() * 1000);
219 connect(&m_AlignTimer, &
QTimer::timeout,
this, &Ekos::Align::checkAlignmentTimeout);
221 m_CurrentGotoMode =
static_cast<GotoMode
>(Options::solverGotoOption());
222 gotoModeButtonGroup->button(m_CurrentGotoMode)->setChecked(
true);
230 opsAlign =
new OpsAlign(
this);
236 opsPrograms =
new OpsPrograms(
this);
237 page = dialog->
addPage(opsPrograms,
i18n(
"External & Online Programs"));
240 opsAstrometry =
new OpsAstrometry(
this);
241 page = dialog->
addPage(opsAstrometry,
i18n(
"Scale & Position"));
242 page->
setIcon(
QIcon(
":/icons/center_telescope_red.svg"));
244 optionsProfileEditor =
new StellarSolverProfileEditor(
this, Ekos::AlignProfiles, dialog);
245 page = dialog->
addPage(optionsProfileEditor,
i18n(
"Align Options Profiles Editor"));
246 connect(optionsProfileEditor, &StellarSolverProfileEditor::optionsProfilesUpdated,
this, [
this]()
248 if(
QFile(savedOptionsProfiles).exists())
249 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
251 m_StellarSolverProfiles = getDefaultAlignOptionsProfiles();
252 opsAlign->reloadOptionsProfiles();
256 connect(opsAlign, &OpsAlign::needToLoadProfile,
this, [
this, dialog, page](
int profile)
258 optionsProfileEditor->loadProfile(profile);
262 opsAstrometryIndexFiles =
new OpsAstrometryIndexFiles(
this);
263 m_IndexFilesPage = dialog->
addPage(opsAstrometryIndexFiles,
i18n(
"Index Files"));
266 appendLogText(
i18n(
"Idle."));
270 stopLayout->addWidget(pi.get());
272 exposureIN->setValue(Options::alignExposure());
278 rememberSolverWCS = Options::astrometrySolverWCS();
279 rememberAutoWCS = Options::autoWCS();
281 solverModeButtonGroup->setId(localSolverR, SOLVER_LOCAL);
282 solverModeButtonGroup->setId(remoteSolverR, SOLVER_REMOTE);
284 localSolverR->setChecked(Options::solverMode() == SOLVER_LOCAL);
285 remoteSolverR->setChecked(Options::solverMode() == SOLVER_REMOTE);
287 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
294 setSolverMode(solverModeButtonGroup->checkedId());
297 FOVScopeCombo->setCurrentIndex(Options::solverScopeType());
299 &Ekos::Align::updateTelescopeType);
301 accuracySpin->setValue(Options::solverAccuracyThreshold());
304 Options::setSolverAccuracyThreshold(accuracySpin->value());
310 Options::setAlignDarkFrame(alignDarkFrameCheck->isChecked());
312 alignDarkFrameCheck->setChecked(Options::alignDarkFrame());
314 delaySpin->setValue(Options::settlingTime());
320 double accuracyRadius = accuracySpin->value();
323 alignPlot->setSelectionTolerance(10);
334 alignPlot->xAxis->setTickLabelColor(
Qt::white);
335 alignPlot->yAxis->setTickLabelColor(
Qt::white);
337 alignPlot->xAxis->setLabelColor(
Qt::white);
338 alignPlot->yAxis->setLabelColor(
Qt::white);
340 alignPlot->xAxis->setLabelFont(
QFont(font().family(), 10));
341 alignPlot->yAxis->setLabelFont(
QFont(font().family(), 10));
343 alignPlot->xAxis->setLabelPadding(2);
344 alignPlot->yAxis->setLabelPadding(2);
353 alignPlot->xAxis->setLabel(
i18n(
"dRA (arcsec)"));
354 alignPlot->yAxis->setLabel(
i18n(
"dDE (arcsec)"));
356 alignPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
357 alignPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
362 alignPlot->addGraph();
372 alignPlot->resize(190, 190);
377 clearAllSolutionsB->setIcon(
384 exportSolutionsCSV->setIcon(
391 connect(clearAllSolutionsB, &
QPushButton::clicked,
this, &Ekos::Align::slotClearAllSolutionPoints);
401 for (
auto &button : qButtons)
402 button->setAutoDefault(
false);
404 savedOptionsProfiles =
QDir(KSPaths::writableLocation(
406 if(
QFile(savedOptionsProfiles).exists())
407 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
409 m_StellarSolverProfiles = getDefaultAlignOptionsProfiles();
411 m_StellarSolver.reset(
new StellarSolver());
412 connect(m_StellarSolver.get(), &StellarSolver::logOutput,
this, &Align::appendLogText);
414 initPolarAlignmentAssistant();
421 if (m_StellarSolver.get() !=
nullptr)
422 disconnect(m_StellarSolver.get(), &StellarSolver::logOutput,
this, &Align::appendLogText);
424 if (alignWidget->parent() ==
nullptr)
425 toggleAlignWidgetFullScreen();
432 for (
auto &dirFile : dir.entryList())
435 void Align::selectSolutionTableRow(
int row,
int column)
439 solutionTable->selectRow(row);
440 for (
int i = 0; i < alignPlot->itemCount(); i++)
445 QCPItemText *item = qobject_cast<QCPItemText *>(abstractItem);
464 void Align::handleHorizontalPlotSizeChange()
466 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
470 void Align::handleVerticalPlotSizeChange()
472 alignPlot->yAxis->setScaleRatio(alignPlot->xAxis, 1.0);
478 if (
event->oldSize().width() != -1)
481 handleHorizontalPlotSizeChange();
483 handleVerticalPlotSizeChange();
496 QCPItemText *label = qobject_cast<QCPItemText *>(item);
499 QString labelText = label->text();
500 int point = labelText.
toInt() - 1;
507 "<th colspan=\"2\">Object %1: %2</th>"
510 "<td>RA:</td><td>%3</td>"
513 "<td>DE:</td><td>%4</td>"
516 "<td>dRA:</td><td>%5</td>"
519 "<td>dDE:</td><td>%6</td>"
523 solutionTable->item(point, 2)->text(),
524 solutionTable->item(point, 0)->text(),
525 solutionTable->item(point, 1)->text(),
526 solutionTable->item(point, 4)->text(),
527 solutionTable->item(point, 5)->text()),
528 alignPlot, alignPlot->rect());
533 void Align::buildTarget()
535 double accuracyRadius = accuracySpin->value();
538 concentricRings->
data()->clear();
539 redTarget->
data()->clear();
540 yellowTarget->
data()->clear();
541 centralTarget->
data()->clear();
545 concentricRings =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
546 redTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
547 yellowTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
548 centralTarget =
new QCPCurve(alignPlot->xAxis, alignPlot->yAxis);
550 const int pointCount = 200;
557 int circleRingPt = 0;
558 for (
int i = 0; i < pointCount; i++)
560 double theta = i /
static_cast<double>(pointCount) * 2 * M_PI;
562 for (
double ring = 1; ring < 8; ring++)
564 if (ring != 4 && ring != 6)
566 if (i % (9 -
static_cast<int>(ring)) == 0)
568 circleRings[circleRingPt] =
QCPCurveData(circleRingPt, accuracyRadius * ring * 0.25 * qCos(theta),
569 accuracyRadius * ring * 0.25 * qSin(theta));
575 circleCentral[i] =
QCPCurveData(i, accuracyRadius * qCos(theta), accuracyRadius * qSin(theta));
576 circleYellow[i] =
QCPCurveData(i, accuracyRadius * 1.5 * qCos(theta), accuracyRadius * 1.5 * qSin(theta));
577 circleRed[i] =
QCPCurveData(i, accuracyRadius * 2 * qCos(theta), accuracyRadius * 2 * qSin(theta));
584 concentricRings->
data()->set(circleRings,
true);
585 redTarget->
data()->set(circleRed,
true);
586 yellowTarget->
data()->set(circleYellow,
true);
587 centralTarget->
data()->set(circleCentral,
true);
600 if (alignPlot->size().width() > 0)
604 void Align::slotAutoScaleGraph()
606 double accuracyRadius = accuracySpin->value();
607 alignPlot->xAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
608 alignPlot->yAxis->setRange(-accuracyRadius * 3, accuracyRadius * 3);
610 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
616 void Align::slotClearAllSolutionPoints()
618 if (solutionTable->rowCount() == 0)
626 solutionTable->setRowCount(0);
627 alignPlot->graph(0)->data()->clear();
628 alignPlot->clearItems();
631 slotAutoScaleGraph();
635 KSMessageBox::Instance()->questionYesNo(
i18n(
"Are you sure you want to clear all of the solution points?"),
636 i18n(
"Clear Solution Points"), 60);
639 void Align::slotRemoveSolutionPoint()
641 QCPAbstractItem *abstractItem = alignPlot->item(solutionTable->currentRow());
644 QCPItemText *item = qobject_cast<QCPItemText *>(abstractItem);
647 double point = item->position->key();
648 alignPlot->graph(0)->data()->remove(point);
651 alignPlot->removeItem(solutionTable->currentRow());
652 for (
int i = 0; i < alignPlot->itemCount(); i++)
657 QCPItemText *item = qobject_cast<QCPItemText *>(abstractItem);
662 solutionTable->removeRow(solutionTable->currentRow());
666 void Align::slotMountModel()
670 m_MountModel =
new MountModel(
this);
672 connect(m_MountModel, &Ekos::MountModel::aborted,
this, [
this]()
674 if (m_Mount && m_Mount->isSlewing())
681 m_MountModel->show();
688 Q_ASSERT_X(parser, __FUNCTION__,
"Astrometry parser is not valid.");
690 bool rc = parser->init();
701 void Align::checkAlignmentTimeout()
703 if (m_SolveFromFile || ++solverIterations == MAXIMUM_SOLVER_ITERATIONS)
705 else if (!m_SolveFromFile)
707 appendLogText(
i18n(
"Solver timed out."));
708 parser->stopSolver();
710 setAlignTableResult(ALIGN_RESULT_FAILED);
718 if (
sender() ==
nullptr && mode >= 0 && mode <= 1)
720 solverModeButtonGroup->button(mode)->setChecked(
true);
723 Options::setSolverMode(mode);
725 if (mode == SOLVER_REMOTE)
727 if (remoteParser.get() !=
nullptr && remoteParserDevice !=
nullptr)
729 parser = remoteParser.get();
735 parser = remoteParser.get();
740 parser->setAlign(
this);
754 for (
int i = 0; i < CCDCaptureCombo->count(); i++)
755 if (device == CCDCaptureCombo->itemText(i))
757 CCDCaptureCombo->setCurrentIndex(i);
768 return m_Camera->getDeviceName();
773 void Align::setDefaultCCD(
QString ccd)
776 Options::setDefaultAlignCCD(ccd);
796 case ALIGN_SUSPENDED:
801 if (ccdNum == -1 || ccdNum >= m_Cameras.
count())
803 ccdNum = CCDCaptureCombo->currentIndex();
809 m_Camera = m_Cameras.
at(ccdNum);
811 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
812 if (targetChip ==
nullptr || (targetChip && targetChip->isCapturing()))
815 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser.get() !=
nullptr)
826 for (
auto &oneCamera : m_Cameras)
828 if (oneCamera->getDeviceName() == device->getDeviceName())
832 for (
auto &oneCamera : m_Cameras)
833 oneCamera->disconnect(
this);
836 m_Cameras.append(device);
838 CCDCaptureCombo->addItem(device->getDeviceName());
850 for (
auto &oneMount : m_Mounts)
852 if (oneMount->getDeviceName() == device->getDeviceName())
856 for (
auto &oneMount : m_Mounts)
857 oneMount->disconnect(
this);
860 m_Mounts.append(device);
862 RUN_PAH(setCurrentTelescope(m_Mount));
866 connect(m_Mount, &ISD::Mount::Disconnected,
this, [
this]()
868 m_isRateSynced =
false;
872 if (m_isRateSynced ==
false)
874 RUN_PAH(syncMountSpeed());
875 m_isRateSynced = !m_Mount->slewRates().
empty();
885 for (
auto &oneDome : m_Domes)
887 if (oneDome->getDeviceName() == device->getDeviceName())
891 for (
auto &oneDome : m_Domes)
892 oneDome->disconnect(
this);
895 m_Domes.append(device);
902 auto name = device->getDeviceName();
906 for (
auto &oneMount : m_Mounts)
908 if (oneMount->getDeviceName() == name)
910 m_Mounts.removeOne(oneMount);
911 if (m_Mount && m_Mount->getDeviceName() == name)
914 m_isRateSynced =
false;
921 for (
auto &oneDome : m_Domes)
923 if (oneDome->getDeviceName() == name)
925 m_Domes.removeOne(oneDome);
926 if (m_Dome && m_Dome->getDeviceName() == name)
933 for (
auto &oneRotator : m_Rotators)
935 if (oneRotator->getDeviceName() == name)
937 m_Rotators.removeOne(oneRotator);
938 if (m_Rotator && m_Rotator->getDeviceName() == name)
945 for (
auto &oneCCD : m_Cameras)
947 if (oneCCD->getDeviceName() == name)
949 m_Cameras.removeAll(oneCCD);
950 CCDCaptureCombo->removeItem(CCDCaptureCombo->findText(name));
951 CCDCaptureCombo->removeItem(CCDCaptureCombo->findText(name +
" Guider"));
952 if (m_Cameras.empty())
955 CCDCaptureCombo->setCurrentIndex(-1);
959 m_Camera = m_Cameras[0];
960 CCDCaptureCombo->setCurrentIndex(0);
973 for (
auto &oneFilter : m_FilterWheels)
975 if (oneFilter->getDeviceName() ==
name)
977 m_FilterWheels.removeAll(oneFilter);
978 filterManager->removeDevice(device);
979 FilterDevicesCombo->removeItem(FilterDevicesCombo->findText(name));
980 if (m_FilterWheels.empty())
982 m_FilterWheel =
nullptr;
983 FilterDevicesCombo->setCurrentIndex(-1);
986 FilterDevicesCombo->setCurrentIndex(0);
1000 if (m_Mount ==
nullptr || m_Mount->isConnected() ==
false)
1003 canSync = m_Mount->canSync();
1005 if (canSync ==
false && syncR->isEnabled())
1007 slewR->setChecked(
true);
1008 appendLogText(
i18n(
"Mount does not support syncing."));
1011 syncR->setEnabled(canSync);
1013 auto nvp = m_Mount->getNumber(
"TELESCOPE_INFO");
1017 auto np = nvp->findWidgetByName(
"TELESCOPE_APERTURE");
1019 if (np && np->getValue() > 0)
1020 primaryAperture = np->getValue();
1022 np = nvp->findWidgetByName(
"GUIDER_APERTURE");
1023 if (np && np->getValue() > 0)
1024 guideAperture = np->getValue();
1026 m_TelescopeAperture = primaryAperture;
1029 if (FOVScopeCombo->currentIndex() == ISD::Camera::TELESCOPE_GUIDE)
1030 m_TelescopeAperture = guideAperture;
1032 np = nvp->findWidgetByName(
"TELESCOPE_FOCAL_LENGTH");
1033 if (np && np->getValue() > 0)
1034 primaryFL = np->getValue();
1036 np = nvp->findWidgetByName(
"GUIDER_FOCAL_LENGTH");
1037 if (np && np->getValue() > 0)
1038 guideFL = np->getValue();
1040 m_TelescopeFocalLength = primaryFL;
1043 if (FOVScopeCombo->currentIndex() == ISD::Camera::TELESCOPE_GUIDE)
1044 m_TelescopeFocalLength = guideFL;
1047 if (m_TelescopeFocalLength == -1 || m_TelescopeAperture == -1)
1050 if (m_CameraPixelWidth != -1 && m_CameraPixelHeight != -1 && m_TelescopeFocalLength != -1 && m_TelescopeAperture != -1)
1052 FOVScopeCombo->setItemData(
1053 ISD::Camera::TELESCOPE_PRIMARY,
1054 i18nc(
"F-Number, Focal length, Aperture",
1055 "<nobr>F<b>%1</b> Focal length: <b>%2</b> mm Aperture: <b>%3</b> mm<sup>2</sup></nobr>",
1059 FOVScopeCombo->setItemData(
1060 ISD::Camera::TELESCOPE_GUIDE,
1061 i18nc(
"F-Number, Focal length, Aperture",
1062 "<nobr>F<b>%1</b> Focal length: <b>%2</b> mm Aperture: <b>%3</b> mm<sup>2</sup></nobr>",
1078 double guideAperture)
1080 if (primaryFocalLength > 0)
1081 primaryFL = primaryFocalLength;
1082 if (guideFocalLength > 0)
1083 guideFL = guideFocalLength;
1085 if (primaryAperture > 0)
1086 this->primaryAperture = primaryAperture;
1087 if (guideAperture > 0)
1088 this->guideAperture = guideAperture;
1090 m_TelescopeFocalLength = primaryFL;
1092 if (m_Camera && m_Camera->getTelescopeType() == ISD::Camera::TELESCOPE_GUIDE)
1093 m_TelescopeFocalLength = guideFL;
1095 m_TelescopeAperture = primaryAperture;
1097 if (m_Camera && m_Camera->getTelescopeType() == ISD::Camera::TELESCOPE_GUIDE)
1098 m_TelescopeAperture = guideAperture;
1108 auto targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1109 Q_ASSERT(targetChip);
1112 uint8_t bit_depth = 8;
1113 targetChip->getImageInfo(m_CameraWidth, m_CameraHeight, m_CameraPixelWidth, m_CameraPixelHeight, bit_depth);
1115 setWCSEnabled(Options::astrometrySolverWCS());
1117 int binx = 1, biny = 1;
1118 binningCombo->setEnabled(targetChip->canBin());
1119 if (targetChip->canBin())
1121 binningCombo->blockSignals(
true);
1123 targetChip->getMaxBin(&binx, &biny);
1124 binningCombo->clear();
1126 for (
int i = 0; i < binx; i++)
1127 binningCombo->addItem(
QString(
"%1x%2").arg(i + 1).arg(i + 1));
1129 binningCombo->setCurrentIndex(Options::solverBinningIndex());
1130 binningCombo->blockSignals(
false);
1135 int roiW = 0, roiH = 0;
1136 targetChip->getFrameMinMax(
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &roiW,
nullptr, &roiH);
1139 if ( (roiW > 0 && roiW < m_CameraWidth) || (roiH > 0 && roiH < m_CameraHeight))
1141 m_CameraWidth = roiW;
1142 m_CameraHeight = roiH;
1145 if (m_CameraPixelWidth > 0 && m_CameraPixelHeight > 0 && m_TelescopeFocalLength > 0 && m_TelescopeAperture > 0)
1153 if (m_Camera ==
nullptr)
1156 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
1157 if (targetChip ==
nullptr || (targetChip && targetChip->isCapturing()))
1160 auto isoList = targetChip->getISOList();
1163 if (isoList.isEmpty())
1165 ISOCombo->setEnabled(
false);
1169 ISOCombo->setEnabled(
true);
1170 ISOCombo->addItems(isoList);
1171 ISOCombo->setCurrentIndex(targetChip->getISOIndex());
1175 if (m_Camera->hasGain())
1177 double min, max, step, value;
1178 m_Camera->getGainMinMaxStep(&min, &max, &step);
1181 GainSpinSpecialValue = min - step;
1182 GainSpin->setRange(GainSpinSpecialValue, max);
1183 GainSpin->setSpecialValueText(
i18n(
"--"));
1184 GainSpin->setEnabled(
true);
1185 GainSpin->setSingleStep(step);
1186 m_Camera->getGain(&value);
1190 TargetCustomGainValue = Options::solverCameraGain();
1191 if (TargetCustomGainValue > 0)
1192 GainSpin->setValue(TargetCustomGainValue);
1194 GainSpin->setValue(GainSpinSpecialValue);
1196 GainSpin->setReadOnly(m_Camera->getGainPermission() == IP_RO);
1200 if (GainSpin->value() > GainSpinSpecialValue)
1202 TargetCustomGainValue = GainSpin->value();
1204 Options::setSolverCameraGain(TargetCustomGainValue);
1209 GainSpin->setEnabled(
false);
1215 fov_h = m_FOVHeight;
1216 fov_scale = m_FOVPixelScale;
1223 result << m_FOVWidth << m_FOVHeight << m_FOVPixelScale;
1232 result << m_CameraWidth << m_CameraHeight << m_CameraPixelWidth << m_CameraPixelHeight;
1241 result << m_TelescopeFocalLength << m_TelescopeAperture;
1249 fov_w = 206264.8062470963552 * m_CameraWidth * m_CameraPixelWidth / 1000.0 / m_TelescopeFocalLength;
1250 fov_h = 206264.8062470963552 * m_CameraHeight * m_CameraPixelHeight / 1000.0 / m_TelescopeFocalLength;
1253 fov_scale = (fov_w * (Options::solverBinningIndex() + 1)) / m_CameraWidth;
1260 void Align::calculateEffectiveFocalLength(
double newFOVW)
1262 if (newFOVW < 0 || newFOVW == m_FOVWidth)
1265 double new_focal_length = ((m_CameraWidth * m_CameraPixelWidth / 1000.0) * 206264.8062470963552) / (newFOVW * 60.0);
1266 double focal_diff = std::fabs(new_focal_length - m_TelescopeFocalLength);
1270 if (FOVScopeCombo->currentIndex() == ISD::Camera::TELESCOPE_PRIMARY)
1271 primaryEffectiveFL = new_focal_length;
1273 guideEffectiveFL = new_focal_length;
1274 appendLogText(
i18n(
"Effective telescope focal length is updated to %1 mm.", new_focal_length));
1278 void Align::calculateFOV()
1283 m_FOVWidth = 206264.8062470963552 * m_CameraWidth * m_CameraPixelWidth / 1000.0 / m_TelescopeFocalLength;
1284 m_FOVHeight = 206264.8062470963552 * m_CameraHeight * m_CameraPixelHeight / 1000.0 / m_TelescopeFocalLength;
1287 m_FOVPixelScale = (m_FOVWidth * (Options::solverBinningIndex() + 1)) / m_CameraWidth;
1291 m_FOVHeight /= 60.0;
1293 double calculated_fov_x = m_FOVWidth;
1294 double calculated_fov_y = m_FOVHeight;
1296 QString calculatedFOV = (
QString(
"%1' x %2'").
arg(m_FOVWidth, 0,
'f', 1).
arg(m_FOVHeight, 0,
'f', 1));
1299 if (m_FOVWidth < 1 || m_FOVWidth > 60 * 180 || m_FOVHeight < 1 || m_FOVHeight > 60 * 180)
1302 i18n(
"Warning! The calculated field of view (%1) is out of bounds. Ensure the telescope focal length and camera pixel size are correct.",
1307 double effectiveFocalLength = FOVScopeCombo->currentIndex() == ISD::Camera::TELESCOPE_PRIMARY ? primaryEffectiveFL :
1310 FocalLengthOut->setText(
QString(
"%1 (%2)").arg(m_TelescopeFocalLength, 0,
'f', 1).
1311 arg(effectiveFocalLength > 0 ? effectiveFocalLength : m_TelescopeFocalLength, 0,
'f', 1));
1312 FocalRatioOut->setText(
QString(
"%1 (%2)").arg(m_TelescopeFocalLength / m_TelescopeAperture, 0,
'f', 1).
1313 arg(effectiveFocalLength > 0 ? effectiveFocalLength / m_TelescopeAperture : m_TelescopeFocalLength / m_TelescopeAperture, 0,
1316 if (effectiveFocalLength > 0)
1318 double focal_diff = std::fabs(effectiveFocalLength - m_TelescopeFocalLength);
1320 FocalLengthOut->setStyleSheet(
"color:green");
1321 else if (focal_diff < 15)
1322 FocalLengthOut->setStyleSheet(
"color:yellow");
1324 FocalLengthOut->setStyleSheet(
"color:red");
1332 if (m_FOVWidth == 0)
1336 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>",
1338 m_FOVWidth = calculated_fov_x;
1339 m_FOVHeight = calculated_fov_y;
1340 m_EffectiveFOVPending =
true;
1344 m_EffectiveFOVPending =
false;
1345 FOVOut->setToolTip(
i18n(
"<p>Effective field of view size in arcminutes.</p>"));
1348 solverFOV->setSize(m_FOVWidth, m_FOVHeight);
1349 sensorFOV->setSize(m_FOVWidth, m_FOVHeight);
1351 sensorFOV->setName(m_Camera->getDeviceName());
1353 FOVOut->setText(
QString(
"%1' x %2'").arg(m_FOVWidth, 0,
'f', 1).arg(m_FOVHeight, 0,
'f', 1));
1356 const bool fovOK = ((m_FOVWidth + m_FOVHeight) / 2.0) > PAH_CUTOFF_FOV;
1357 if (m_PolarAlignmentAssistant !=
nullptr)
1358 m_PolarAlignmentAssistant->setEnabled(fovOK);
1360 if (opsAstrometry->kcfg_AstrometryUseImageScale->isChecked())
1362 int unitType = opsAstrometry->kcfg_AstrometryImageScaleUnits->currentIndex();
1367 double fov_low = qMin(m_FOVWidth / 60, m_FOVHeight / 60);
1368 double fov_high = qMax(m_FOVWidth / 60, m_FOVHeight / 60);
1369 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(fov_low);
1370 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(fov_high);
1372 Options::setAstrometryImageScaleLow(fov_low);
1373 Options::setAstrometryImageScaleHigh(fov_high);
1376 else if (unitType == 1)
1378 double fov_low = qMin(m_FOVWidth, m_FOVHeight);
1379 double fov_high = qMax(m_FOVWidth, m_FOVHeight);
1380 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(fov_low);
1381 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(fov_high);
1383 Options::setAstrometryImageScaleLow(fov_low);
1384 Options::setAstrometryImageScaleHigh(fov_high);
1389 opsAstrometry->kcfg_AstrometryImageScaleLow->setValue(m_FOVPixelScale * 0.9);
1390 opsAstrometry->kcfg_AstrometryImageScaleHigh->setValue(m_FOVPixelScale * 1.1);
1393 Options::setAstrometryImageScaleLow(m_FOVPixelScale * 0.9);
1394 Options::setAstrometryImageScaleHigh(m_FOVPixelScale * 1.1);
1421 if (optionsMap.contains(
"noverify"))
1422 solver_args <<
"--no-verify";
1425 if (optionsMap.contains(
"resort"))
1426 solver_args <<
"--resort";
1429 if (optionsMap.contains(
"nofits2fits"))
1430 solver_args <<
"--no-fits2fits";
1433 if (optionsMap.contains(
"downsample"))
1434 solver_args <<
"--downsample" <<
QString::number(optionsMap.value(
"downsample", 2).toInt());
1437 if (optionsMap.contains(
"scaleL"))
1438 solver_args <<
"-L" <<
QString::number(optionsMap.value(
"scaleL").toDouble());
1441 if (optionsMap.contains(
"scaleH"))
1442 solver_args <<
"-H" <<
QString::number(optionsMap.value(
"scaleH").toDouble());
1445 if (optionsMap.contains(
"scaleUnits"))
1446 solver_args <<
"-u" << optionsMap.
value(
"scaleUnits").toString();
1449 if (optionsMap.contains(
"ra"))
1450 solver_args <<
"-3" <<
QString::number(optionsMap.value(
"ra").toDouble());
1453 if (optionsMap.contains(
"de"))
1454 solver_args <<
"-4" <<
QString::number(optionsMap.value(
"de").toDouble());
1457 if (optionsMap.contains(
"radius"))
1458 solver_args <<
"-5" <<
QString::number(optionsMap.value(
"radius").toDouble());
1461 if (optionsMap.contains(
"custom"))
1462 solver_args << optionsMap.
value(
"custom").toString();
1468 void Align::generateFOVBounds(
double fov_w,
QString &fov_low,
QString &fov_high,
double tolerance)
1472 double lower_boundary = 1.0 - tolerance;
1473 double upper_boundary = 1.0 + tolerance;
1480 double fov_lower = fov_w * lower_boundary;
1481 double fov_upper = fov_w * upper_boundary;
1491 QVariantMap optionsMap;
1504 if (Options::astrometryUseNoVerify())
1505 optionsMap[
"noverify"] =
true;
1507 if (Options::astrometryUseResort())
1508 optionsMap[
"resort"] =
true;
1510 if (Options::astrometryUseNoFITS2FITS())
1511 optionsMap[
"nofits2fits"] =
true;
1513 if (data ==
nullptr)
1515 if (Options::astrometryUseDownsample())
1517 if (Options::astrometryAutoDownsample() && m_CameraWidth && m_CameraHeight)
1519 uint8_t bin = qMax(Options::solverBinningIndex() + 1, 1u);
1520 uint16_t w = m_CameraWidth / bin;
1521 optionsMap[
"downsample"] = getSolverDownsample(w);
1524 optionsMap[
"downsample"] = Options::astrometryDownsample();
1528 int bin = Options::solverBinningIndex() + 1;
1529 optionsMap[
"image_width"] = m_CameraWidth / bin;
1530 optionsMap[
"image_height"] = m_CameraHeight / bin;
1532 if (Options::astrometryUseImageScale() && m_FOVWidth > 0 && m_FOVHeight > 0)
1535 if (Options::astrometryImageScaleUnits() == 1)
1537 else if (Options::astrometryImageScaleUnits() == 2)
1539 if (Options::astrometryAutoUpdateImageScale())
1542 double fov_w = m_FOVWidth;
1543 double fov_h = m_FOVHeight;
1545 if (Options::astrometryImageScaleUnits() == SSolver::DEG_WIDTH)
1550 else if (Options::astrometryImageScaleUnits() == SSolver::ARCSEC_PER_PIX)
1552 fov_w = m_FOVPixelScale;
1553 fov_h = m_FOVPixelScale;
1557 generateFOVBounds(fov_w, fov_low, fov_high, m_EffectiveFOVPending ? 0.3 : 0.05);
1559 optionsMap[
"scaleL"] = fov_low;
1560 optionsMap[
"scaleH"] = fov_high;
1561 optionsMap[
"scaleUnits"] = units;
1565 optionsMap[
"scaleL"] = Options::astrometryImageScaleLow();
1566 optionsMap[
"scaleH"] = Options::astrometryImageScaleHigh();
1567 optionsMap[
"scaleUnits"] = units;
1571 if (Options::astrometryUsePosition() && m_Mount !=
nullptr)
1573 double ra = 0, dec = 0;
1574 m_Mount->getEqCoords(&ra, &dec);
1576 optionsMap[
"ra"] = ra * 15.0;
1577 optionsMap[
"de"] = dec;
1578 optionsMap[
"radius"] = Options::astrometryRadius();
1585 data->getRecordValue(
"NAXIS1",
width);
1586 if (
width.isValid())
1588 optionsMap[
"downsample"] = getSolverDownsample(
width.toInt());
1591 optionsMap[
"downsample"] = Options::astrometryDownsample();
1595 data->getRecordValue(
"SCALE", pixscale);
1598 optionsMap[
"scaleL"] = 0.8 * pixscale.
toDouble();
1599 optionsMap[
"scaleH"] = 1.2 * pixscale.
toDouble();
1600 optionsMap[
"scaleUnits"] =
"app";
1605 data->getRecordValue(
"RA", ra);
1606 data->getRecordValue(
"DEC", de);
1611 optionsMap[
"radius"] = Options::astrometryRadius();
1615 if (Options::astrometryCustomOptions().isEmpty() ==
false)
1616 optionsMap[
"custom"] = Options::astrometryCustomOptions();
1623 m_AlignTimer.
stop();
1624 m_CaptureTimer.
stop();
1626 if (m_Camera ==
nullptr)
1628 appendLogText(
i18n(
"Error: No camera detected."));
1632 if (m_Camera->isConnected() ==
false)
1634 appendLogText(
i18n(
"Error: lost connection to camera."));
1635 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"), KSNotification::EVENT_ALERT);
1639 if (m_Camera->isBLOBEnabled() ==
false)
1641 m_Camera->setBLOBEnabled(
true);
1646 if (m_Camera->getTelescopeType() != FOVScopeCombo->currentIndex())
1648 rememberTelescopeType = m_Camera->getTelescopeType();
1649 m_Camera->setTelescopeType(
static_cast<ISD::Camera::TelescopeType
>(FOVScopeCombo->currentIndex()));
1655 if (m_TelescopeFocalLength == -1 || m_TelescopeAperture == -1)
1657 KSNotification::error(
1658 i18n(
"Telescope aperture and focal length are missing. Please check your driver settings and try again."));
1662 if (m_CameraPixelWidth == -1 || m_CameraPixelHeight == -1)
1664 KSNotification::error(
i18n(
"CCD pixel size is missing. Please check your driver settings and try again."));
1668 if (m_FilterWheel !=
nullptr)
1670 if (m_FilterWheel->isConnected() ==
false)
1672 appendLogText(
i18n(
"Error: lost connection to filter wheel."));
1676 int targetPosition = FilterPosCombo->currentIndex() + 1;
1678 if (targetPosition > 0 && targetPosition != currentFilterPosition)
1680 filterPositionPending =
true;
1682 filterManager->setFilterPosition(
1683 targetPosition, FilterManager::NO_AUTOFOCUS_POLICY);
1684 state = ALIGN_PROGRESS;
1689 if (m_Camera->getDriverInfo()->getClientManager()->getBLOBMode(m_Camera->getDeviceName().
toLatin1().
constData(),
1693 nullptr,
i18n(
"Image transfer is disabled for this camera. Would you like to enable it?")) ==
1696 m_Camera->getDriverInfo()->getClientManager()->setBLOBMode(B_ONLY, m_Camera->getDeviceName().
toLatin1().
constData(),
1698 m_Camera->getDriverInfo()->getClientManager()->setBLOBMode(B_ONLY, m_Camera->getDeviceName().
toLatin1().
constData(),
1707 double seqExpose = exposureIN->value();
1709 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1711 if (m_FocusState >= FOCUS_PROGRESS)
1713 appendLogText(
i18n(
"Cannot capture while focus module is busy. Retrying in %1 seconds...", CAPTURE_RETRY_DELAY / 1000));
1714 m_CaptureTimer.
start(CAPTURE_RETRY_DELAY);
1718 if (targetChip->isCapturing())
1720 appendLogText(
i18n(
"Cannot capture while CCD exposure is in progress. Retrying in %1 seconds...",
1721 CAPTURE_RETRY_DELAY / 1000));
1722 m_CaptureTimer.
start(CAPTURE_RETRY_DELAY);
1726 m_AlignView->setBaseSize(alignWidget->size());
1727 m_AlignView->setProperty(
"suspended", (solverModeButtonGroup->checkedId() == SOLVER_LOCAL
1728 && alignDarkFrameCheck->isChecked()));
1734 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser.get() !=
nullptr)
1736 if (remoteParserDevice ==
nullptr)
1738 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
1744 auto activeDevices = remoteParserDevice->getBaseDevice()->getText(
"ACTIVE_DEVICES");
1747 auto activeCCD = activeDevices->findWidgetByName(
"ACTIVE_CCD");
1748 if (
QString(activeCCD->text) != CCDCaptureCombo->currentText())
1750 activeCCD->setText(CCDCaptureCombo->currentText().toLatin1().data());
1752 remoteParserDevice->getClientManager()->sendNewText(activeDevices);
1759 solverTimer.
start();
1765 dir.setNameFilters(
QStringList() <<
"fits*" <<
"tmp.*");
1767 for (
auto &dirFile : dir.entryList())
1770 prepareCapture(targetChip);
1773 if (matchPAHStage(PAA::PAH_REFRESH))
1774 targetChip->capture(m_PolarAlignmentAssistant->getPAHExposureDuration());
1776 targetChip->capture(seqExpose);
1778 Options::setAlignExposure(seqExpose);
1780 solveB->setEnabled(
false);
1781 stopB->setEnabled(
true);
1782 pi->startAnimation();
1784 differentialSlewingActivated =
false;
1786 state = ALIGN_PROGRESS;
1787 emit newStatus(state);
1788 solverFOV->setProperty(
"visible",
true);
1791 if (matchPAHStage(PAA::PAH_REFRESH))
1794 appendLogText(
i18n(
"Capturing image..."));
1799 m_Mount->getEqCoords(&ra, &dec);
1800 if (!m_SolveFromFile)
1802 int currentRow = solutionTable->rowCount();
1803 solutionTable->insertRow(currentRow);
1804 for (
int i = 4; i < 6; i++)
1808 solutionTable->setItem(currentRow, i, disabledBox);
1812 RAReport->
setText(ScopeRAOut->text());
1815 solutionTable->setItem(currentRow, 0, RAReport);
1818 DECReport->
setText(ScopeDecOut->text());
1821 solutionTable->setItem(currentRow, 1, DECReport);
1823 double maxrad = 1.0;
1839 solutionTable->setItem(currentRow, 2, ObjNameReport);
1845 solutionTable->setCellWidget(currentRow, 3, alignIndicator);
1857 if (data->property(
"chip").toInt() == ISD::CameraChip::GUIDE_CCD)
1865 m_AlignView->loadData(data);
1869 m_ImageData.
reset();
1871 RUN_PAH(setImageData(m_ImageData));
1874 if (matchPAHStage(PAA::PAH_REFRESH))
1876 setCaptureComplete();
1880 appendLogText(
i18n(
"Image received."));
1883 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1886 if (alignDarkFrameCheck->isChecked())
1888 int x,
y, w, h, binx = 1, biny = 1;
1889 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
1890 targetChip->getFrame(&
x, &
y, &w, &h);
1891 targetChip->getBinning(&binx, &biny);
1893 uint16_t offsetX =
x / binx;
1894 uint16_t offsetY =
y / biny;
1896 m_DarkProcessor->denoise(targetChip, m_ImageData, exposureIN->value(), offsetX, offsetY);
1900 setCaptureComplete();
1906 if (m_Camera->getUploadMode() == ISD::Camera::UPLOAD_LOCAL)
1908 rememberUploadMode = ISD::Camera::UPLOAD_LOCAL;
1909 m_Camera->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
1912 if (m_Camera->isFastExposureEnabled())
1914 m_RememberCameraFastExposure =
true;
1915 m_Camera->setFastExposureEnabled(
false);
1918 m_Camera->setEncodingFormat(
"FITS");
1919 targetChip->resetFrame();
1920 targetChip->setBatchMode(
false);
1921 targetChip->setCaptureMode(FITS_ALIGN);
1922 targetChip->setFrameType(FRAME_LIGHT);
1924 int bin = Options::solverBinningIndex() + 1;
1925 targetChip->setBinning(bin, bin);
1928 if (m_Camera->hasGain() && GainSpin->isEnabled() && GainSpin->value() > GainSpinSpecialValue)
1929 m_Camera->setGain(GainSpin->value());
1931 if (ISOCombo->currentIndex() >= 0)
1932 targetChip->setISOIndex(ISOCombo->currentIndex());
1936 void Align::setCaptureComplete()
1938 if (matchPAHStage(PAA::PAH_REFRESH))
1940 emit newFrame(m_AlignView);
1941 m_PolarAlignmentAssistant->processPAHRefresh();
1945 emit newImage(m_AlignView);
1947 solverFOV->setImage(m_AlignView->getDisplayImage());
1954 gotoModeButtonGroup->button(mode)->setChecked(
true);
1955 m_CurrentGotoMode =
static_cast<GotoMode
>(mode);
1964 QStringList astrometryDataDirs = KSUtils::getAstrometryDataDirs();
1967 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
1969 if(Options::solverType() != SSolver::SOLVER_ASTAP
1970 && Options::solverType() != SSolver::SOLVER_WATNEYASTROMETRY)
1972 bool foundAnIndex =
false;
1973 for(
QString dataDir : astrometryDataDirs)
1980 if(indexList.
count() > 0)
1981 foundAnIndex =
true;
1987 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."));
1989 if(alignSettings && m_IndexFilesPage)
1992 alignSettings->
show();
1996 if (m_StellarSolver->isRunning())
1997 m_StellarSolver->abort();
1999 m_ImageData = m_AlignView->imageData();
2000 m_StellarSolver->loadNewImageBuffer(m_ImageData->getStatistics(), m_ImageData->getImageBuffer());
2001 m_StellarSolver->setProperty(
"ProcessType", SSolver::SOLVE);
2002 m_StellarSolver->setProperty(
"ExtractorType", Options::solveSextractorType());
2003 m_StellarSolver->setProperty(
"SolverType", Options::solverType());
2004 connect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
2005 m_StellarSolver->setIndexFolderPaths(Options::astrometryIndexFolderList());
2007 auto params = m_StellarSolverProfiles.
at(Options::solveOptionsProfile());
2008 params.partition = Options::stellarSolverPartition();
2009 m_StellarSolver->setParameters(params);
2011 const SSolver::SolverType type =
static_cast<SSolver::SolverType
>(m_StellarSolver->property(
"SolverType").toInt());
2012 if(type == SSolver::SOLVER_LOCALASTROMETRY || type == SSolver::SOLVER_ASTAP || type == SSolver::SOLVER_WATNEYASTROMETRY)
2016 m_AlignView->saveImage(filename);
2017 m_StellarSolver->setProperty(
"FileToProcess", filename);
2018 ExternalProgramPaths externalPaths;
2019 externalPaths.sextractorBinaryPath = Options::sextractorBinary();
2020 externalPaths.solverPath = Options::astrometrySolverBinary();
2021 externalPaths.astapBinaryPath = Options::aSTAPExecutable();
2022 externalPaths.watneyBinaryPath = Options::watneyBinary();
2023 externalPaths.wcsPath = Options::astrometryWCSInfo();
2024 m_StellarSolver->setExternalFilePaths(externalPaths);
2027 m_StellarSolver->setProperty(
"AutoGenerateAstroConfig",
true);
2030 if(type == SSolver::SOLVER_ONLINEASTROMETRY )
2034 m_AlignView->saveImage(filename);
2036 m_StellarSolver->setProperty(
"FileToProcess", filename);
2037 m_StellarSolver->setProperty(
"AstrometryAPIKey", Options::astrometryAPIKey());
2038 m_StellarSolver->setProperty(
"AstrometryAPIURL", Options::astrometryAPIURL());
2041 bool useImageScale = Options::astrometryUseImageScale();
2042 if (useBlindScale == BLIND_ENGAGNED)
2044 useImageScale =
false;
2045 useBlindScale = BLIND_USED;
2046 appendLogText(
i18n(
"Solving with blind image scale..."));
2049 bool useImagePosition = Options::astrometryUsePosition();
2050 if (useBlindPosition == BLIND_ENGAGNED)
2052 useImagePosition =
false;
2053 useBlindPosition = BLIND_USED;
2054 appendLogText(
i18n(
"Solving with blind image position..."));
2057 if (m_SolveFromFile)
2059 FITSImage::Solution solution;
2060 m_ImageData->parseSolution(solution);
2062 if (useImageScale && solution.pixscale > 0)
2063 m_StellarSolver->setSearchScale(solution.pixscale * 0.8,
2064 solution.pixscale * 1.2,
2065 SSolver::ARCSEC_PER_PIX);
2067 m_StellarSolver->setProperty(
"UseScale",
false);
2069 if (useImagePosition && solution.ra > 0)
2070 m_StellarSolver->setSearchPositionInDegrees(solution.ra, solution.dec);
2072 m_StellarSolver->setProperty(
"UsePosition",
false);
2079 SSolver::ScaleUnits units =
static_cast<SSolver::ScaleUnits
>(Options::astrometryImageScaleUnits());
2081 m_StellarSolver->setSearchScale(Options::astrometryImageScaleLow() * 0.8,
2082 Options::astrometryImageScaleHigh() * 1.2,
2086 m_StellarSolver->setProperty(
"UseScale",
false);
2088 if(useImagePosition)
2089 m_StellarSolver->setSearchPositionInDegrees(telescopeCoord.
ra().
Degrees(), telescopeCoord.
dec().
Degrees());
2091 m_StellarSolver->setProperty(
"UsePosition",
false);
2094 if(Options::alignmentLogging())
2099 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
2100 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
2101 if(Options::astrometryLogToFile())
2103 m_StellarSolver->setProperty(
"LogToFile",
true);
2104 m_StellarSolver->setProperty(
"LogFileName", Options::astrometryLogFilepath());
2109 m_StellarSolver->setLogLevel(SSolver::LOG_NONE);
2110 m_StellarSolver->setSSLogLevel(SSolver::LOG_OFF);
2114 m_StellarSolver->start();
2118 if (m_ImageData.
isNull())
2119 m_ImageData = m_AlignView->imageData();
2122 remoteParser->startSolver(m_ImageData->filename(),
generateRemoteArgs(m_ImageData),
false);
2126 solverTimer.
start();
2128 state = ALIGN_PROGRESS;
2129 emit newStatus(state);
2132 void Align::solverComplete()
2134 disconnect(m_StellarSolver.get(), &StellarSolver::ready,
this, &Align::solverComplete);
2135 if(!m_StellarSolver->solvingDone() || m_StellarSolver->failed())
2138 if (CHECK_PAH(processSolverFailure()))
2145 FITSImage::Solution solution = m_StellarSolver->getSolution();
2146 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false :
true;
2147 solverFinished(solution.orientation, solution.ra, solution.dec, solution.pixscale, eastToTheRight);
2153 pi->stopAnimation();
2154 stopB->setEnabled(
false);
2155 solveB->setEnabled(
true);
2157 sOrientation = orientation;
2161 double elapsed = solverTimer.
elapsed() / 1000.0;
2162 appendLogText(
i18n(
"Solver completed after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2165 if (rememberTelescopeType != ISD::Camera::TELESCOPE_UNKNOWN)
2167 m_Camera->setTelescopeType(rememberTelescopeType);
2168 rememberTelescopeType = ISD::Camera::TELESCOPE_UNKNOWN;
2171 m_AlignTimer.
stop();
2172 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParserDevice && remoteParser.get())
2179 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2180 targetChip->getBinning(&binx, &biny);
2182 if (Options::alignmentLogging())
2184 QString parityString = eastToTheRight ?
"neg" :
"pos";
2185 appendLogText(
i18n(
"Solver RA (%1) DEC (%2) Orientation (%3) Pixel Scale (%4) Parity (%5)",
QString::number(ra,
'f', 5),
2191 if (!m_SolveFromFile &&
2192 (m_FOVWidth == 0 || m_EffectiveFOVPending || std::fabs(pixscale - m_FOVPixelScale) > 0.005) &&
2195 double newFOVW = m_CameraWidth * pixscale / binx / 60.0;
2196 double newFOVH = m_CameraHeight * pixscale / biny / 60.0;
2198 calculateEffectiveFocalLength(newFOVW);
2199 saveNewEffectiveFOV(newFOVW, newFOVH);
2201 m_EffectiveFOVPending =
false;
2204 alignCoord.
setRA0(ra / 15.0);
2213 if (!m_SolveFromFile)
2216 calculateAlignTargetDiff();
2222 double solverPA = SolverUtils::rotationToPositionAngle(orientation);
2223 solverFOV->setCenter(alignCoord);
2224 solverFOV->setPA(solverPA);
2225 solverFOV->setImageDisplay(Options::astrometrySolverOverlay());
2227 sensorFOV->setPA(solverPA);
2232 getFormattedCoords(alignCoord.
ra().
Hours(), alignCoord.
dec().
Degrees(), ra_dms, dec_dms);
2234 SolverRAOut->setText(ra_dms);
2235 SolverDecOut->setText(dec_dms);
2238 if (Options::astrometrySolverWCS())
2240 auto ccdRotation = m_Camera->getNumber(
"CCD_ROTATION");
2243 auto rotation = ccdRotation->findWidgetByName(
"CCD_ROTATION_VALUE");
2246 ClientManager *clientManager = m_Camera->getDriverInfo()->getClientManager();
2247 rotation->setValue(orientation);
2248 clientManager->sendNewNumber(ccdRotation);
2250 if (m_wcsSynced ==
false)
2253 i18n(
"WCS information updated. Images captured from this point forward shall have valid WCS."));
2256 auto telescopeInfo = m_Mount->getNumber(
"TELESCOPE_INFO");
2258 clientManager->sendNewNumber(telescopeInfo);
2266 m_CaptureErrorCounter = 0;
2267 m_SlewErrorCounter = 0;
2268 m_CaptureTimeoutCounter = 0;
2270 appendLogText(
i18n(
"Solution coordinates: RA (%1) DEC (%2) Telescope Coordinates: RA (%3) DEC (%4)",
2273 if (!m_SolveFromFile && m_CurrentGotoMode == GOTO_SLEW)
2275 dms diffDeg(m_TargetDiffTotal / 3600.0);
2276 appendLogText(
i18n(
"Target is within %1 degrees of solution coordinates.", diffDeg.
toDMSString()));
2279 if (rememberUploadMode != m_Camera->getUploadMode())
2280 m_Camera->setUploadMode(rememberUploadMode);
2283 if (m_RememberCameraFastExposure)
2285 m_RememberCameraFastExposure =
false;
2286 m_Camera->setFastExposureEnabled(
true);
2291 int currentRow = solutionTable->rowCount() - 1;
2292 if (!m_SolveFromFile)
2294 stopProgressAnimation();
2295 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
2299 if (m_SolveFromFile && Options::astrometryUseRotator())
2301 loadSlewTargetPA = solverPA;
2302 qCDebug(KSTARS_EKOS_ALIGN) <<
"loaSlewTargetPA:" << loadSlewTargetPA;
2304 else if (Options::astrometryUseRotator())
2306 currentRotatorPA = solverPA;
2309 if (m_Rotator !=
nullptr && m_Rotator->isConnected())
2312 auto absAngle = m_Rotator->getNumber(
"ABS_ROTATOR_ANGLE");
2318 double rawAngle = absAngle->at(0)->getValue();
2319 double offset = range360((rawAngle * Options::pAMultiplier()) - currentRotatorPA);
2321 auto reverseStatus =
"Unknown";
2322 auto reverseProperty = m_Rotator->
getSwitch(
"REVERSE_ROTATOR");
2323 if (reverseProperty)
2325 if (reverseProperty->at(0)->getState() == ISS_ON)
2326 reverseStatus =
"Reversed Direction";
2328 reverseStatus =
"Normal Direction";
2331 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << rawAngle <<
"Rotator PA:" << currentRotatorPA
2332 <<
"Rotator Offset:" << offset <<
"Direction:" << reverseStatus;
2333 Options::setPAOffset(offset);
2336 if (absAngle && std::isnan(loadSlewTargetPA) ==
false
2337 && fabs(currentRotatorPA - loadSlewTargetPA) * 60 > Options::astrometryRotatorThreshold())
2340 double rawAngle = range360((Options::pAOffset() + loadSlewTargetPA) / Options::pAMultiplier());
2341 absAngle->at(0)->setValue(rawAngle);
2342 ClientManager *clientManager = m_Rotator->getDriverInfo()->getClientManager();
2343 clientManager->sendNewNumber(absAngle);
2344 appendLogText(
i18n(
"Setting position angle to %1 degrees E of N...", loadSlewTargetPA));
2348 else if (std::isnan(loadSlewTargetPA) ==
false)
2350 double current = currentRotatorPA;
2351 double target = loadSlewTargetPA;
2353 double diff = SolverUtils::rangePA(current - target);
2354 double threshold = Options::astrometryRotatorThreshold() / 60.0;
2356 appendLogText(
i18n(
"Current PA is %1; Target PA is %2; diff: %3", current, target, diff));
2358 emit manualRotatorChanged(current, target, threshold);
2360 m_ManualRotator->setRotatorDiff(current, target, diff);
2361 if (fabs(diff) > threshold)
2363 targetAccuracyNotMet =
true;
2364 m_ManualRotator->show();
2365 m_ManualRotator->raise();
2369 loadSlewTargetPA = std::numeric_limits<double>::quiet_NaN();
2370 targetAccuracyNotMet =
false;
2375 emit newSolverResults(orientation, ra, dec, pixscale);
2378 {
"camera", m_Camera->getDeviceName()},
2379 {
"ra", SolverRAOut->text()},
2380 {
"de", SolverDecOut->text()},
2381 {
"dRA", m_TargetDiffRA},
2382 {
"dDE", m_TargetDiffDE},
2383 {
"targetDiff", m_TargetDiffTotal},
2386 {
"fov", FOVOut->text()},
2390 switch (m_CurrentGotoMode)
2395 if (!m_SolveFromFile)
2397 stopProgressAnimation();
2398 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2399 solutionTable->setItem(currentRow, 3, statusReport.release());
2405 if (m_SolveFromFile || m_TargetDiffTotal >
static_cast<double>(accuracySpin->value()))
2407 if (!m_SolveFromFile && ++solverIterations == MAXIMUM_SOLVER_ITERATIONS)
2409 appendLogText(
i18n(
"Maximum number of iterations reached. Solver failed."));
2411 if (!m_SolveFromFile)
2413 statusReport->setIcon(
QIcon(
":/icons/AlignFailure.svg"));
2414 solutionTable->setItem(currentRow, 3, statusReport.release());
2421 targetAccuracyNotMet =
true;
2423 if (!m_SolveFromFile)
2425 stopProgressAnimation();
2426 statusReport->setIcon(
QIcon(
":/icons/AlignWarning.svg"));
2427 solutionTable->setItem(currentRow, 3, statusReport.release());
2434 if (!m_SolveFromFile)
2436 stopProgressAnimation();
2437 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2438 solutionTable->setItem(currentRow, 3, statusReport.release());
2441 appendLogText(
i18n(
"Target is within acceptable range. Astrometric solver is successful."));
2452 if (!m_SolveFromFile)
2454 stopProgressAnimation();
2455 statusReport->setIcon(
QIcon(
":/icons/AlignSuccess.svg"));
2456 solutionTable->setItem(currentRow, 3, statusReport.release());
2461 KSNotification::event(
QLatin1String(
"AlignSuccessful"),
i18n(
"Astrometry alignment completed successfully"));
2462 state = ALIGN_COMPLETE;
2463 emit newStatus(state);
2464 solverIterations = 0;
2466 solverFOV->setProperty(
"visible",
true);
2468 if (!matchPAHStage(PAA::PAH_IDLE))
2469 m_PolarAlignmentAssistant->processPAHStage(orientation, ra, dec, pixscale, eastToTheRight);
2472 solveB->setEnabled(
true);
2473 loadSlewB->setEnabled(
true);
2479 if (state != ALIGN_ABORTED)
2482 if (Options::astrometryUseImageScale() && useBlindScale == BLIND_IDLE)
2484 useBlindScale = BLIND_ENGAGNED;
2485 setAlignTableResult(ALIGN_RESULT_FAILED);
2491 if (Options::astrometryUsePosition() && useBlindPosition == BLIND_IDLE)
2493 useBlindPosition = BLIND_ENGAGNED;
2494 setAlignTableResult(ALIGN_RESULT_FAILED);
2500 appendLogText(
i18n(
"Solver Failed."));
2501 if(!Options::alignmentLogging())
2503 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."));
2505 KSNotification::event(
QLatin1String(
"AlignFailed"),
i18n(
"Astrometry alignment failed"),
2506 KSNotification::EVENT_ALERT);
2509 pi->stopAnimation();
2510 stopB->setEnabled(
false);
2511 solveB->setEnabled(
true);
2512 loadSlewB->setEnabled(
true);
2514 m_AlignTimer.
stop();
2516 m_SolveFromFile =
false;
2517 solverIterations = 0;
2518 m_CaptureErrorCounter = 0;
2519 m_CaptureTimeoutCounter = 0;
2520 m_SlewErrorCounter = 0;
2522 state = ALIGN_FAILED;
2523 emit newStatus(state);
2525 solverFOV->setProperty(
"visible",
false);
2527 setAlignTableResult(ALIGN_RESULT_FAILED);
2532 m_CaptureTimer.
stop();
2533 if (solverModeButtonGroup->checkedId() == SOLVER_LOCAL)
2534 m_StellarSolver->abort();
2535 else if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParser)
2536 remoteParser->stopSolver();
2538 pi->stopAnimation();
2539 stopB->setEnabled(
false);
2540 solveB->setEnabled(
true);
2541 loadSlewB->setEnabled(
true);
2544 if (rememberTelescopeType != ISD::Camera::TELESCOPE_UNKNOWN)
2546 m_Camera->setTelescopeType(rememberTelescopeType);
2547 rememberTelescopeType = ISD::Camera::TELESCOPE_UNKNOWN;
2550 m_SolveFromFile =
false;
2551 solverIterations = 0;
2552 m_CaptureErrorCounter = 0;
2553 m_CaptureTimeoutCounter = 0;
2554 m_SlewErrorCounter = 0;
2555 m_AlignTimer.
stop();
2560 if (rememberUploadMode != m_Camera->getUploadMode())
2561 m_Camera->setUploadMode(rememberUploadMode);
2564 if (m_RememberCameraFastExposure)
2566 m_RememberCameraFastExposure =
false;
2567 m_Camera->setFastExposureEnabled(
true);
2570 ISD::CameraChip *targetChip = m_Camera->getChip(useGuideHead ? ISD::CameraChip::GUIDE_CCD : ISD::CameraChip::PRIMARY_CCD);
2573 if (matchPAHStage(PAA::PAH_POST_REFRESH))
2575 if (targetChip->isCapturing())
2576 targetChip->abortExposure();
2578 appendLogText(
i18n(
"Refresh is complete."));
2582 if (targetChip->isCapturing())
2584 targetChip->abortExposure();
2585 appendLogText(
i18n(
"Capture aborted."));
2589 double elapsed = solverTimer.
elapsed() / 1000.0;
2591 appendLogText(
i18n(
"Solver aborted after %1 seconds.",
QString::number(elapsed,
'f', 2)));
2596 emit newStatus(state);
2598 setAlignTableResult(ALIGN_RESULT_FAILED);
2603 int currentRow = solutionTable->rowCount() - 1;
2610 QWidget *indicator = solutionTable->cellWidget(currentRow, 3);
2611 if (indicator ==
nullptr)
2616 void Align::stopProgressAnimation()
2619 if (progress_indicator !=
nullptr)
2627 result << sOrientation << sRA << sDEC;
2632 void Align::appendLogText(
const QString &text)
2634 m_LogText.
insert(0,
i18nc(
"log entry; %1 is the date, %2 is the text",
"%1 %2",
2635 KStarsData::Instance()->lt().toString(
"yyyy-MM-ddThh:mm:ss"), text));
2637 qCInfo(KSTARS_EKOS_ALIGN) << text;
2642 void Align::clearLog()
2650 if (!strcmp(svp->name,
"DOME_MOTION"))
2653 if (domeReady ==
false && svp->s == IPS_OK)
2658 handleMountStatus();
2661 else if ((!strcmp(svp->name,
"TELESCOPE_MOTION_NS") || !strcmp(svp->name,
"TELESCOPE_MOTION_WE")))
2666 handleMountMotion();
2667 m_wasSlewStarted =
true;
2670 qCDebug(KSTARS_EKOS_ALIGN) <<
"Mount motion finished.";
2671 handleMountStatus();
2679 if (!strcmp(nvp->name,
"EQUATORIAL_EOD_COORD") || !strcmp(nvp->name,
"EQUATORIAL_COORD"))
2683 getFormattedCoords(telescopeCoord.
ra().
Hours(), telescopeCoord.
dec().
Degrees(), ra_dms, dec_dms);
2685 ScopeRAOut->setText(ra_dms);
2686 ScopeDecOut->setText(dec_dms);
2695 m_wasSlewStarted =
false;
2704 if (m_wasSlewStarted && Options::astrometryAutoUpdatePosition())
2707 opsAstrometry->estRA->setText(ra_dms);
2708 opsAstrometry->estDec->setText(dec_dms);
2710 Options::setAstrometryPositionRA(nvp->np[0].value * 15);
2711 Options::setAstrometryPositionDE(nvp->np[1].value);
2717 if (m_Dome && m_Dome->isMoving())
2724 if (m_wasSlewStarted && matchPAHStage(PAA::PAH_FIND_CP))
2727 m_wasSlewStarted =
false;
2728 appendLogText(
i18n(
"Mount completed slewing near celestial pole. Capture again to verify."));
2731 m_PolarAlignmentAssistant->setPAHStage(PAA::PAH_FIRST_CAPTURE);
2737 case ALIGN_PROGRESS:
2742 m_wasSlewStarted =
false;
2744 if (m_CurrentGotoMode == GOTO_SLEW)
2751 appendLogText(
i18n(
"Mount is synced to solution coordinates. Astrometric solver is successful."));
2753 i18n(
"Astrometry alignment completed successfully"));
2754 state = ALIGN_COMPLETE;
2755 emit newStatus(state);
2756 solverIterations = 0;
2763 if (!didSlewStart())
2771 m_wasSlewStarted =
false;
2772 if (m_SolveFromFile)
2774 m_SolveFromFile =
false;
2776 state = ALIGN_PROGRESS;
2777 emit newStatus(state);
2779 if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY)
2780 appendLogText(
i18n(
"Settling..."));
2781 m_CaptureTimer.
start(delaySpin->value());
2784 else if (differentialSlewingActivated)
2786 appendLogText(
i18n(
"Differential slewing complete. Astrometric solver is successful."));
2787 KSNotification::event(
QLatin1String(
"AlignSuccessful"),
i18n(
"Astrometry alignment completed successfully"));
2788 state = ALIGN_COMPLETE;
2789 emit newStatus(state);
2790 solverIterations = 0;
2792 else if (m_CurrentGotoMode == GOTO_SLEW || (m_MountModel && m_MountModel->isRunning()))
2794 if (targetAccuracyNotMet)
2795 appendLogText(
i18n(
"Slew complete. Target accuracy is not met, running solver again..."));
2797 appendLogText(
i18n(
"Slew complete. Solving Alignment Point. . ."));
2799 targetAccuracyNotMet =
false;
2801 state = ALIGN_PROGRESS;
2802 emit newStatus(state);
2804 if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY)
2805 appendLogText(
i18n(
"Settling..."));
2806 m_CaptureTimer.
start(delaySpin->value());
2814 m_wasSlewStarted =
false;
2825 m_wasSlewStarted =
true;
2827 handleMountMotion();
2835 m_wasSlewStarted =
false;
2837 if (state == ALIGN_SYNCING || state == ALIGN_SLEWING)
2839 if (state == ALIGN_SYNCING)
2840 appendLogText(
i18n(
"Syncing failed."));
2842 appendLogText(
i18n(
"Slewing failed."));
2844 if (++m_SlewErrorCounter == 3)
2851 if (m_CurrentGotoMode == GOTO_SLEW)
2862 RUN_PAH(processMountRotation(telescopeCoord.
ra(), delaySpin->value()));
2864 else if (!strcmp(nvp->name,
"ABS_ROTATOR_ANGLE"))
2867 currentRotatorPA = SolverUtils::rangePA( (nvp->np[0].value * Options::pAMultiplier()) - Options::pAOffset());
2868 if (std::isnan(loadSlewTargetPA) ==
false && nvp->s == IPS_OK)
2870 auto diff = fabs(currentRotatorPA - loadSlewTargetPA) * 60;
2871 qCDebug(KSTARS_EKOS_ALIGN) <<
"Raw Rotator Angle:" << nvp->np[0].value <<
"Current PA:" << currentRotatorPA
2872 <<
"Target PA:" << loadSlewTargetPA <<
"Diff (arcmin):" << diff <<
"Offset:" << Options::pAOffset();
2874 if (diff <= Options::astrometryRotatorThreshold())
2876 appendLogText(
i18n(
"Rotator reached target position angle."));
2877 targetAccuracyNotMet =
true;
2878 loadSlewTargetPA = std::numeric_limits<double>::quiet_NaN();
2882 else if (diff <= Options::astrometryRotatorThreshold() * 2)
2884 appendLogText(
i18n(
"Rotator failed to arrive at the requested position angle. Check power, backlash, or obstructions."));
2890 i18n(
"Rotator failed to arrive at the requested position angle. Try to reverse rotation direction in Rotator Settings."));
2896 void Align::handleMountMotion()
2898 if (state == ALIGN_PROGRESS)
2900 if (matchPAHStage(PAA::PAH_IDLE))
2903 appendLogText(
i18n(
"Slew detected, suspend solving..."));
2906 m_SolveFromFile =
true;
2912 state = ALIGN_SLEWING;
2916 void Align::handleMountStatus()
2918 auto nvp = m_Mount->getNumber(m_Mount->isJ2000() ?
"EQUATORIAL_COORD" :
2919 "EQUATORIAL_EOD_COORD");
2928 if (m_SolveFromFile)
2930 m_targetCoord = alignCoord;
2933 else if (m_CurrentGotoMode == GOTO_SYNC)
2935 else if (m_CurrentGotoMode == GOTO_SLEW)
2941 state = ALIGN_SYNCING;
2943 if (m_Mount->Sync(&alignCoord))
2945 emit newStatus(state);
2952 emit newStatus(state);
2953 appendLogText(
i18n(
"Syncing failed."));
2959 state = ALIGN_SLEWING;
2960 emit newStatus(state);
2968 m_Mount->Slew(&m_targetCoord);
2969 slewStartTimer.
start();
2970 appendLogText(
i18n(
"Slewing to target coordinates: RA (%1) DEC (%2).", m_targetCoord.
ra().
toHMSString(),
2976 if (canSync && !m_SolveFromFile)
2980 if (Ekos::Manager::Instance()->getCurrentJobName().isEmpty())
2982 KSNotification::event(
QLatin1String(
"EkosSchedulerTelescopeSynced"),
2983 i18n(
"Ekos job (%1) - Telescope synced",
2984 Ekos::Manager::Instance()->getCurrentJobName()));
2989 if (Options::astrometryDifferentialSlewing())
2994 m_targetCoord.
setRA(m_targetCoord.
ra() - m_TargetDiffRA);
2995 m_targetCoord.
setDec(m_targetCoord.
dec() - m_TargetDiffDE);
2997 differentialSlewingActivated =
true;
2999 qCDebug(KSTARS_EKOS_ALIGN) <<
"Using differential slewing...";
3013 void Align::getFormattedCoords(
double ra,
double dec,
QString &ra_str,
QString &dec_str)
3024 dec_str =
QString(
"-%1:%2:%3")
3039 "Images (*.fits *.fits.fz *.fit *.fts "
3040 "*.jpg *.jpeg *.png *.gif *.bmp "
3041 "*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw *.orf)");
3050 differentialSlewingActivated =
false;
3052 m_SolveFromFile =
true;
3054 if (m_PolarAlignmentAssistant)
3055 m_PolarAlignmentAssistant->stopPAHProcess();
3057 slewR->setChecked(
true);
3058 m_CurrentGotoMode = GOTO_SLEW;
3060 solveB->setEnabled(
false);
3061 stopB->setEnabled(
true);
3062 pi->startAnimation();
3064 if (solverModeButtonGroup->checkedId() == SOLVER_REMOTE && remoteParserDevice ==
nullptr)
3066 appendLogText(
i18n(
"No remote astrometry driver detected, switching to StellarSolver."));
3070 m_ImageData.
clear();
3072 m_AlignView->loadFile(fileURL);
3081 differentialSlewingActivated =
false;
3082 m_SolveFromFile =
true;
3083 RUN_PAH(stopPAHProcess());
3084 slewR->setChecked(
true);
3085 m_CurrentGotoMode = GOTO_SLEW;
3086 solveB->setEnabled(
false);
3087 stopB->setEnabled(
true);
3088 pi->startAnimation();
3092 m_ImageData.
clear();
3095 data->loadFromBuffer(image, extension);
3096 m_AlignView->loadData(data);
3103 exposureIN->setValue(value);
3109 Options::setSolverBinningIndex(binIndex);
3114 binningCombo->blockSignals(
true);
3115 binningCombo->setCurrentIndex(binIndex);
3116 binningCombo->blockSignals(
false);
3120 if (Options::astrometryImageScaleUnits() == SSolver::ARCSEC_PER_PIX)
3129 FOVScopeCombo->setCurrentIndex(index);
3134 for (
auto filter : m_FilterWheels)
3136 if (filter->getDeviceName() == device->getDeviceName())
3140 FilterCaptureLabel->setEnabled(
true);
3141 FilterDevicesCombo->setEnabled(
true);
3142 FilterPosLabel->setEnabled(
true);
3143 FilterPosCombo->setEnabled(
true);
3145 FilterDevicesCombo->addItem(device->getDeviceName());
3147 m_FilterWheels.append(device);
3149 int filterWheelIndex = 1;
3150 if (Options::defaultAlignFilterWheel().isEmpty() ==
false)
3151 filterWheelIndex = FilterDevicesCombo->findText(Options::defaultAlignFilterWheel());
3153 if (filterWheelIndex < 1)
3154 filterWheelIndex = 1;
3157 FilterDevicesCombo->setCurrentIndex(filterWheelIndex);
3159 emit settingsUpdated(getSettings());
3165 bool deviceFound =
false;
3167 for (
int i = 1; i < FilterDevicesCombo->count(); i++)
3168 if (device == FilterDevicesCombo->itemText(i))
3175 if (deviceFound ==
false)
3183 if (FilterDevicesCombo->currentIndex() >= 1)
3184 return FilterDevicesCombo->currentText();
3191 if (FilterDevicesCombo->currentIndex() >= 1)
3193 FilterPosCombo->setCurrentText(filter);
3202 return FilterPosCombo->currentText();
3208 if (filterNum == -1)
3210 filterNum = FilterDevicesCombo->currentIndex();
3211 if (filterNum == -1)
3218 m_FilterWheel =
nullptr;
3219 currentFilterPosition = -1;
3220 FilterPosCombo->clear();
3224 if (filterNum <= m_FilterWheels.count())
3225 m_FilterWheel = m_FilterWheels.at(filterNum - 1);
3227 FilterPosCombo->clear();
3229 FilterPosCombo->addItems(filterManager->getFilterLabels());
3231 currentFilterPosition = filterManager->getFilterPosition();
3233 FilterPosCombo->setCurrentIndex(Options::lockAlignFilterIndex());
3238 void Align::setWCSEnabled(
bool enable)
3243 auto wcsControl = m_Camera->
getSwitch(
"WCS_CONTROL");
3248 auto wcs_enable = wcsControl->findWidgetByName(
"WCS_ENABLE");
3249 auto wcs_disable = wcsControl->findWidgetByName(
"WCS_DISABLE");
3251 if (!wcs_enable || !wcs_disable)
3254 if ((wcs_enable->getState() == ISS_ON && enable) || (wcs_disable->getState() == ISS_ON && !enable))
3257 wcsControl->reset();
3260 appendLogText(
i18n(
"World Coordinate System (WCS) is enabled."));
3261 wcs_enable->setState(ISS_ON);
3265 appendLogText(
i18n(
"World Coordinate System (WCS) is disabled."));
3266 wcs_disable->setState(ISS_ON);
3267 m_wcsSynced =
false;
3270 auto clientManager = m_Camera->getDriverInfo()->getClientManager();
3272 clientManager->sendNewSwitch(wcsControl);
3277 INDI_UNUSED(targetChip);
3278 INDI_UNUSED(remaining);
3280 if (state == IPS_ALERT)
3282 if (++m_CaptureErrorCounter == 3 && !matchPAHStage(PolarAlignmentAssistant::PAH_REFRESH))
3284 appendLogText(
i18n(
"Capture error. Aborting..."));
3289 appendLogText(
i18n(
"Restarting capture attempt #%1", m_CaptureErrorCounter));
3290 setAlignTableResult(ALIGN_RESULT_FAILED);
3295 void Align::setAlignTableResult(AlignResult result)
3302 if (progress_indicator ==
nullptr || ! progress_indicator->
isAnimated())
3304 stopProgressAnimation();
3309 case ALIGN_RESULT_SUCCESS:
3310 icon =
QIcon(
":/icons/AlignSuccess.svg");
3313 case ALIGN_RESULT_WARNING:
3314 icon =
QIcon(
":/icons/AlignWarning.svg");
3317 case ALIGN_RESULT_FAILED:
3319 icon =
QIcon(
":/icons/AlignFailure.svg");
3322 int currentRow = solutionTable->rowCount() - 1;
3323 solutionTable->setCellWidget(currentRow, 3,
new QWidget());
3327 solutionTable->setItem(currentRow, 3, statusReport);
3330 void Align::setFocusStatus(Ekos::FocusState state)
3332 m_FocusState = state;
3335 uint8_t Align::getSolverDownsample(uint16_t binnedW)
3337 uint8_t downsample = Options::astrometryDownsample();
3339 if (!Options::astrometryAutoDownsample())
3342 while (downsample < 8)
3344 if (binnedW / downsample <= 1024)
3353 void Align::saveSettleTime()
3355 Options::setSettlingTime(delaySpin->value());
3374 if (m_Mount && m_Mount->hasAlignmentModel() && Options::resetMountModelAfterMeridian())
3376 qCDebug(KSTARS_EKOS_ALIGN) <<
"Post meridian flip mount model reset" << (m_Mount->clearAlignmentModel() ?
3377 "successful." :
"failed.");
3380 m_CaptureTimer.
start(Options::settlingTime());
3387 m_CaptureState = newState;
3390 void Align::showFITSViewer()
3392 static int lastFVTabID = -1;
3399 fv->loadData(m_ImageData, url, &lastFVTabID);
3401 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) ==
false)
3402 fv->loadData(m_ImageData, url, &lastFVTabID);
3408 void Align::toggleAlignWidgetFullScreen()
3410 if (alignWidget->parent() ==
nullptr)
3412 alignWidget->setParent(
this);
3413 rightLayout->insertWidget(0, alignWidget);
3414 alignWidget->showNormal();
3418 alignWidget->setParent(
nullptr);
3419 alignWidget->setWindowTitle(
i18nc(
"@title:window",
"Align Frame"));
3421 alignWidget->showMaximized();
3422 alignWidget->show();
3426 void Align::updateTelescopeType(
int index)
3428 if (m_Camera ==
nullptr)
3432 FocalLengthOut->setStyleSheet(
QString());
3436 m_TelescopeFocalLength = (index == ISD::Camera::TELESCOPE_PRIMARY) ? primaryFL : guideFL;
3437 m_TelescopeAperture = (index == ISD::Camera::TELESCOPE_PRIMARY) ? primaryAperture : guideAperture;
3439 Options::setSolverScopeType(index);
3455 void Align::setMountStatus(ISD::Mount::Status newState)
3459 case ISD::Mount::MOUNT_PARKING:
3460 case ISD::Mount::MOUNT_SLEWING:
3461 case ISD::Mount::MOUNT_MOVING:
3462 solveB->setEnabled(
false);
3463 loadSlewB->setEnabled(
false);
3467 if (state != ALIGN_PROGRESS)
3469 solveB->setEnabled(
true);
3470 if (matchPAHStage(PAA::PAH_IDLE))
3472 loadSlewB->setEnabled(
true);
3478 RUN_PAH(setMountStatus(newState));
3483 remoteParserDevice = device;
3485 remoteSolverR->setEnabled(
true);
3486 if (remoteParser.get() !=
nullptr)
3488 remoteParser->setAstrometryDevice(remoteParserDevice);
3496 for (
auto &oneRotator : m_Rotators)
3498 if (oneRotator->getDeviceName() == device->getDeviceName())
3502 m_Rotators.append(device);
3510 solverFOV->setImageDisplay(Options::astrometrySolverWCS());
3511 m_AlignTimer.
setInterval(Options::astrometryTimeout() * 1000);
3516 filterManager = manager;
3518 connect(filterManager.
data(), &FilterManager::ready, [
this]()
3520 if (filterPositionPending)
3522 m_FocusState = FOCUS_IDLE;
3523 filterPositionPending = false;
3529 connect(filterManager.data(), &FilterManager::failed, [
this]()
3531 appendLogText(i18n(
"Filter operation failed."));
3536 connect(filterManager.data(), &FilterManager::newStatus, [
this](Ekos::FilterState filterState)
3538 if (filterPositionPending)
3540 switch (filterState)
3543 appendLogText(i18n(
"Changing focus offset by %1 steps...", filterManager->getTargetFilterOffset()));
3548 const int filterComboIndex = filterManager->getTargetFilterPosition() - 1;
3549 if (filterComboIndex >= 0 && filterComboIndex < FilterPosCombo->count())
3550 appendLogText(i18n(
"Changing filter to %1...", FilterPosCombo->itemText(filterComboIndex)));
3554 case FILTER_AUTOFOCUS:
3555 appendLogText(i18n(
"Auto focus on filter change..."));
3564 connect(filterManager.data(), &FilterManager::labelsChanged,
this, [
this]()
3568 connect(filterManager.data(), &FilterManager::positionChanged,
this, [
this]()
3574 QVariantMap Align::getEffectiveFOV()
3576 KStarsData::Instance()->
userdb()->GetAllEffectiveFOVs(effectiveFOVs);
3578 m_FOVWidth = m_FOVHeight = 0;
3580 for (
auto &map : effectiveFOVs)
3582 if (map[
"Profile"].
toString() == m_ActiveProfile->name)
3584 if (map[
"Width"].toInt() == m_CameraWidth &&
3585 map[
"Height"].toInt() == m_CameraHeight &&
3586 map[
"PixelW"].toDouble() == m_CameraPixelWidth &&
3587 map[
"PixelH"].toDouble() == m_CameraPixelHeight &&
3588 map[
"FocalLength"].toDouble() == m_TelescopeFocalLength)
3590 m_FOVWidth =
map[
"FovW"].toDouble();
3591 m_FOVHeight =
map[
"FovH"].toDouble();
3597 return QVariantMap();
3600 void Align::saveNewEffectiveFOV(
double newFOVW,
double newFOVH)
3602 if (newFOVW < 0 || newFOVH < 0 || (newFOVW == m_FOVWidth && newFOVH == m_FOVHeight))
3605 QVariantMap effectiveMap = getEffectiveFOV();
3608 if (effectiveMap.isEmpty() ==
false)
3609 KStarsData::Instance()->
userdb()->DeleteEffectiveFOV(effectiveMap[
"id"].
toString());
3612 if (newFOVW == 0.0 && newFOVH == 0.0)
3618 effectiveMap[
"Profile"] = m_ActiveProfile->name;
3619 effectiveMap[
"Width"] = m_CameraWidth;
3620 effectiveMap[
"Height"] = m_CameraHeight;
3621 effectiveMap[
"PixelW"] = m_CameraPixelWidth;
3622 effectiveMap[
"PixelH"] = m_CameraPixelHeight;
3623 effectiveMap[
"FocalLength"] = m_TelescopeFocalLength;
3624 effectiveMap[
"FovW"] = newFOVW;
3625 effectiveMap[
"FovH"] = newFOVH;
3627 KStarsData::Instance()->
userdb()->AddEffectiveFOV(effectiveMap);
3633 void Align::zoomAlignView()
3635 m_AlignView->ZoomDefault();
3637 emit newFrame(m_AlignView);
3640 void Align::setAlignZoom(
double scale)
3643 m_AlignView->ZoomIn();
3645 m_AlignView->ZoomOut();
3647 emit newFrame(m_AlignView);
3655 if (GainSpinSpecialValue > INVALID_VALUE && GainSpin->value() > GainSpinSpecialValue)
3656 gain = GainSpin->
value();
3657 else if (m_Camera && m_Camera->hasGain())
3658 m_Camera->getGain(&gain);
3660 settings.
insert(
"camera", CCDCaptureCombo->currentText());
3661 settings.
insert(
"fw", FilterDevicesCombo->currentText());
3662 settings.
insert(
"filter", FilterPosCombo->currentText());
3663 settings.
insert(
"exp", exposureIN->value());
3664 settings.
insert(
"bin", qMax(1, binningCombo->currentIndex() + 1));
3665 settings.
insert(
"solverAction", gotoModeButtonGroup->checkedId());
3666 settings.
insert(
"scopeType", FOVScopeCombo->currentIndex());
3667 settings.
insert(
"gain", gain);
3668 settings.
insert(
"iso", ISOCombo->currentIndex());
3669 settings.
insert(
"accuracy", accuracySpin->value());
3670 settings.
insert(
"settle", delaySpin->value());
3671 settings.
insert(
"dark", alignDarkFrameCheck->isChecked());
3676 void Align::setSettings(
const QJsonObject &settings)
3678 static bool init =
false;
3680 auto syncControl = [settings](
const QString & key,
QWidget * widget)
3682 if (settings.
contains(key) ==
false)
3690 if ((pSB = qobject_cast<QSpinBox *>(widget)))
3692 const int value = settings[key].toInt(pSB->
value());
3693 if (value != pSB->
value())
3699 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
3701 const double value = settings[key].toDouble(pDSB->
value());
3702 if (value != pDSB->
value())
3708 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
3710 const bool value = settings[key].toBool(pCB->
isChecked());
3718 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
3732 if (syncControl(
"camera", CCDCaptureCombo) ||
init ==
false)
3735 if (syncControl(
"fw", FilterDevicesCombo) ||
init ==
false)
3738 syncControl(
"filter", FilterPosCombo);
3739 Options::setLockAlignFilterIndex(FilterPosCombo->currentIndex());
3741 syncControl(
"exp", exposureIN);
3743 const int bin = settings[
"bin"].toInt(binningCombo->currentIndex() + 1) - 1;
3744 if (bin != binningCombo->currentIndex())
3745 binningCombo->setCurrentIndex(bin);
3747 const QString profileName = settings[
"sep"].toString();
3748 QStringList profiles = getStellarSolverProfiles();
3749 int profileIndex = getStellarSolverProfiles().indexOf(profileName);
3750 if (profileIndex >= 0 && profileIndex !=
static_cast<int>(Options::solveOptionsProfile()))
3751 Options::setSolveOptionsProfile(profileIndex);
3753 int solverAction = settings[
"solverAction"].toInt(gotoModeButtonGroup->checkedId());
3754 if (solverAction != gotoModeButtonGroup->checkedId())
3756 gotoModeButtonGroup->button(solverAction)->setChecked(
true);
3757 m_CurrentGotoMode =
static_cast<GotoMode
>(solverAction);
3760 FOVScopeCombo->setCurrentIndex(settings[
"scopeType"].toInt(0));
3763 if (m_Camera->hasGain())
3765 syncControl(
"gain", GainSpin);
3768 if (ISOCombo->count() > 1)
3770 const int iso = settings[
"iso"].toInt(ISOCombo->currentIndex());
3771 if (iso != ISOCombo->currentIndex())
3772 ISOCombo->setCurrentIndex(iso);
3775 syncControl(
"dark", alignDarkFrameCheck);
3777 syncControl(
"accuracy", accuracySpin);
3779 syncControl(
"settle", delaySpin);
3784 void Align::syncSettings()
3786 emit settingsUpdated(getSettings());
3790 void Align::syncFOV()
3792 QString newFOV = FOVOut->text();
3795 if (
match.hasMatch())
3797 double newFOVW =
match.captured(1).toDouble();
3798 double newFOVH =
match.captured(2).toDouble();
3801 saveNewEffectiveFOV(newFOVW, newFOVH);
3803 FOVOut->setStyleSheet(
QString());
3807 KSNotification::error(
i18n(
"Invalid FOV."));
3808 FOVOut->setStyleSheet(
"background-color:red");
3813 bool Align::didSlewStart()
3815 if (m_wasSlewStarted)
3817 if (slewStartTimer.isValid() && slewStartTimer.elapsed() > MAX_WAIT_FOR_SLEW_START_MSEC)
3819 qCDebug(KSTARS_EKOS_ALIGN) <<
"Slew timed out...waited > 10s, it must have started already.";
3825 void Align::setTargetCoords(
double ra0,
double de0)
3836 m_targetCoord = targetCoord;
3837 m_targetCoordValid =
true;
3838 qCInfo(KSTARS_EKOS_ALIGN) <<
"Target updated to JNow RA:" << m_targetCoord.
ra().
toHMSString()
3839 <<
"DE:" << m_targetCoord.dec().toDMSString();
3846 if (m_targetCoordValid)
3847 coord << m_targetCoord.ra0().Hours() << m_targetCoord.dec0().Degrees();
3849 coord << telescopeCoord.ra0().Hours() << telescopeCoord.dec0().Degrees();
3854 void Align::setTargetPositionAngle(
double value)
3856 loadSlewTargetPA = value;
3857 qCDebug(KSTARS_EKOS_ALIGN) <<
"Target Rotation updated to: " << loadSlewTargetPA;
3860 void Align::calculateAlignTargetDiff()
3862 if (matchPAHStage(PAA::PAH_FIRST_CAPTURE) ||
3863 matchPAHStage(PAA::PAH_SECOND_CAPTURE) ||
3864 matchPAHStage(PAA::PAH_THIRD_CAPTURE) ||
3865 matchPAHStage(PAA::PAH_FIRST_SOLVE) ||
3866 matchPAHStage(PAA::PAH_SECOND_SOLVE) ||
3867 matchPAHStage(PAA::PAH_THIRD_SOLVE))
3869 m_TargetDiffRA = (alignCoord.ra().deltaAngle(m_targetCoord.ra())).Degrees() * 3600;
3870 m_TargetDiffDE = (alignCoord.dec().deltaAngle(m_targetCoord.dec())).Degrees() * 3600;
3872 dms RADiff(fabs(m_TargetDiffRA) / 3600.0), DEDiff(m_TargetDiffDE / 3600.0);
3873 QString dRAText =
QString(
"%1%2").
arg((m_TargetDiffRA > 0 ?
"+" :
"-"), RADiff.toHMSString());
3874 QString dDEText = DEDiff.toDMSString(
true);
3876 m_TargetDiffTotal = sqrt(m_TargetDiffRA * m_TargetDiffRA + m_TargetDiffDE * m_TargetDiffDE);
3878 errOut->setText(
QString(
"%1 arcsec. RA:%2 DE:%3").arg(
3882 if (m_TargetDiffTotal <=
static_cast<double>(accuracySpin->value()))
3883 errOut->setStyleSheet(
"color:green");
3884 else if (m_TargetDiffTotal < 1.5 * accuracySpin->value())
3885 errOut->setStyleSheet(
"color:yellow");
3887 errOut->setStyleSheet(
"color:red");
3890 int currentRow = solutionTable->rowCount() - 1;
3897 solutionTable->setItem(currentRow, 4, dRAReport);
3906 solutionTable->setItem(currentRow, 5, dDECReport);
3909 double raPlot = m_TargetDiffRA;
3910 double decPlot = m_TargetDiffDE;
3911 alignPlot->graph(0)->addData(raPlot, decPlot);
3917 textLabel->position->
setCoords(raPlot, decPlot);
3925 if (!alignPlot->xAxis->range().contains(m_TargetDiffRA))
3927 alignPlot->graph(0)->rescaleKeyAxis(
true);
3928 alignPlot->yAxis->setScaleRatio(alignPlot->xAxis, 1.0);
3930 if (!alignPlot->yAxis->range().contains(m_TargetDiffDE))
3932 alignPlot->graph(0)->rescaleValueAxis(
true);
3933 alignPlot->xAxis->setScaleRatio(alignPlot->yAxis, 1.0);
3935 alignPlot->replot();
3938 void Align::updateTargetCoords()
3941 if (m_targetCoordValid)
3946 m_targetCoord = telescopeCoord;
3947 m_targetCoordValid =
true;
3948 qCDebug(KSTARS_EKOS_ALIGN) <<
"No target set, using mount Coordinates JNow RA:" << telescopeCoord.ra().toHMSString()
3949 <<
"DE:" << telescopeCoord.dec().toDMSString();
3956 for (
auto param : m_StellarSolverProfiles)
3957 profiles << param.listName;
3962 void Align::exportSolutionPoints()
3964 if (solutionTable->rowCount() == 0)
3969 "CSV File (*.csv)");
3980 i18n(
"A file named \"%1\" already exists. "
3984 if (r == KMessageBox::Cancel)
3991 KSNotification::sorry(
message,
i18n(
"Invalid URL"));
4000 KSNotification::sorry(
message,
i18n(
"Could Not Open File"));
4008 outstream <<
"RA (J" << epoch <<
"),DE (J" << epoch
4009 <<
"),RA (degrees),DE (degrees),Name,RA Error (arcsec),DE Error (arcsec)" <<
Qt::endl;
4011 for (
int i = 0; i < solutionTable->rowCount(); i++)
4019 if (!raCell || !deCell || !objNameCell || !raErrorCell || !deErrorCell)
4021 KSNotification::sorry(
i18n(
"Error in table structure."));
4030 emit newLog(
i18n(
"Solution Points Saved as: %1", path));
4034 void Align::initPolarAlignmentAssistant()
4037 m_PolarAlignmentAssistant =
new PolarAlignmentAssistant(
this, m_AlignView);
4039 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newAlignTableResult,
this, &Ekos::Align::setAlignTableResult);
4040 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newFrame,
this, &Ekos::Align::newFrame);
4041 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newPAHStage,
this, &Ekos::Align::processPAHStage);
4042 connect(m_PolarAlignmentAssistant, &Ekos::PAA::newLog,
this, &Ekos::Align::appendLogText);
4044 tabWidget->addTab(m_PolarAlignmentAssistant,
i18n(
"Polar Alignment"));
4047 void Align::initManualRotator()
4049 if (m_ManualRotator)
4052 m_ManualRotator =
new ManualRotator(
this);
4056 connect(m_ManualRotator, &Ekos::ManualRotator::rejected,
this, [
this]()
4058 loadSlewTargetPA = std::numeric_limits<double>::quiet_NaN();
4062 void Align::initDarkProcessor()
4064 if (m_DarkProcessor)
4067 m_DarkProcessor =
new DarkProcessor(
this);
4068 connect(m_DarkProcessor, &DarkProcessor::newLog,
this, &Ekos::Align::appendLogText);
4069 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted,
this, [
this](
bool completed)
4071 alignDarkFrameCheck->setChecked(completed);
4072 m_AlignView->setProperty(
"suspended",
false);
4075 m_AlignView->rescale(ZOOM_KEEP_LEVEL);
4076 m_AlignView->updateFrame();
4078 setCaptureComplete();
4082 void Align::processPAHStage(
int stage)
4090 if (m_StellarSolver && m_StellarSolver->isRunning())
4091 m_StellarSolver->abort();
4093 case PAA::PAH_POST_REFRESH:
4095 Options::setAstrometrySolverWCS(rememberSolverWCS);
4096 Options::setAutoWCS(rememberAutoWCS);
4101 case PAA::PAH_FIRST_CAPTURE:
4102 nothingR->setChecked(
true);
4103 m_CurrentGotoMode = GOTO_NOTHING;
4104 loadSlewB->setEnabled(
false);
4106 rememberSolverWCS = Options::astrometrySolverWCS();
4107 rememberAutoWCS = Options::autoWCS();
4109 Options::setAutoWCS(
false);
4110 Options::setAstrometrySolverWCS(
true);
4112 case PAA::PAH_SECOND_CAPTURE:
4113 case PAA::PAH_THIRD_CAPTURE:
4114 if (delaySpin->value() >= DELAY_THRESHOLD_NOTIFY)
4115 emit newLog(
i18n(
"Settling..."));
4116 m_CaptureTimer.start(delaySpin->value());
4123 emit newPAAStage(stage);
4126 bool Align::matchPAHStage(uint32_t stage)
4128 return m_PolarAlignmentAssistant && m_PolarAlignmentAssistant->getPAHStage() == stage;
4131 void Align::toggleManualRotator(
bool toggled)
4135 m_ManualRotator->show();
4136 m_ManualRotator->raise();
4139 m_ManualRotator->close();