File indexing completed on 2024-05-05 15:55:10
0001 0002 /* 0003 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "adaptivefocus.h" 0009 #include "focusadaptor.h" 0010 #include "focusalgorithms.h" 0011 #include "focusfwhm.h" 0012 #include "aberrationinspector.h" 0013 #include "aberrationinspectorutils.h" 0014 #include "kstars.h" 0015 #include "kstarsdata.h" 0016 #include "Options.h" 0017 #include "stellarsolver.h" 0018 0019 // Modules 0020 #include "ekos/guide/guide.h" 0021 #include "ekos/manager.h" 0022 0023 // KStars Auxiliary 0024 #include "auxiliary/kspaths.h" 0025 #include "auxiliary/ksmessagebox.h" 0026 0027 // Ekos Auxiliary 0028 #include "ekos/auxiliary/darklibrary.h" 0029 #include "ekos/auxiliary/darkprocessor.h" 0030 #include "ekos/auxiliary/profilesettings.h" 0031 #include "ekos/auxiliary/opticaltrainmanager.h" 0032 #include "ekos/auxiliary/opticaltrainsettings.h" 0033 #include "ekos/auxiliary/filtermanager.h" 0034 #include "ekos/auxiliary/stellarsolverprofileeditor.h" 0035 0036 // FITS 0037 #include "fitsviewer/fitsdata.h" 0038 #include "fitsviewer/fitsview.h" 0039 #include "fitsviewer/fitsviewer.h" 0040 0041 // Devices 0042 #include "indi/indifilterwheel.h" 0043 #include "ksnotification.h" 0044 #include "kconfigdialog.h" 0045 0046 #include <basedevice.h> 0047 #include <gsl/gsl_fit.h> 0048 #include <gsl/gsl_vector.h> 0049 #include <gsl/gsl_min.h> 0050 0051 #include <ekos_focus_debug.h> 0052 0053 #include <cmath> 0054 0055 #define MAXIMUM_ABS_ITERATIONS 30 0056 #define MAXIMUM_RESET_ITERATIONS 3 0057 #define AUTO_STAR_TIMEOUT 45000 0058 #define MINIMUM_PULSE_TIMER 32 0059 #define MAX_RECAPTURE_RETRIES 3 0060 #define MINIMUM_POLY_SOLUTIONS 2 0061 0062 namespace Ekos 0063 { 0064 Focus::Focus() : QWidget() 0065 { 0066 // #1 Set the UI 0067 setupUi(this); 0068 0069 // #1a Prepare UI 0070 prepareGUI(); 0071 0072 // #2 Register DBus 0073 qRegisterMetaType<Ekos::FocusState>("Ekos::FocusState"); 0074 qDBusRegisterMetaType<Ekos::FocusState>(); 0075 new FocusAdaptor(this); 0076 QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Focus", this); 0077 0078 // #3 Init connections 0079 initConnections(); 0080 0081 // #4 Init Plots 0082 initPlots(); 0083 0084 // #5 Init View 0085 initView(); 0086 0087 // #6 Reset all buttons to default states 0088 resetButtons(); 0089 0090 // #7 Load All settings 0091 loadGlobalSettings(); 0092 0093 // #8 Init Setting Connection now 0094 connectSyncSettings(); 0095 0096 // #9 Init Adaptive Focus 0097 adaptFocus.reset(new AdaptiveFocus(this)); 0098 0099 connect(&m_StarFinderWatcher, &QFutureWatcher<bool>::finished, this, &Focus::starDetectionFinished); 0100 0101 //Note: This is to prevent a button from being called the default button 0102 //and then executing when the user hits the enter key such as when on a Text Box 0103 QList<QPushButton *> qButtons = findChildren<QPushButton *>(); 0104 for (auto &button : qButtons) 0105 button->setAutoDefault(false); 0106 0107 appendLogText(i18n("Idle.")); 0108 0109 // Focus motion timeout 0110 m_FocusMotionTimer.setInterval(m_OpsFocusMechanics->focusMotionTimeout->value() * 1000); 0111 m_FocusMotionTimer.setSingleShot(true); 0112 connect(&m_FocusMotionTimer, &QTimer::timeout, this, &Focus::handleFocusMotionTimeout); 0113 0114 // Create an autofocus CSV file, dated at startup time 0115 m_FocusLogFileName = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("focuslogs/autofocus-" + 0116 QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss") + ".txt"); 0117 m_FocusLogFile.setFileName(m_FocusLogFileName); 0118 0119 m_OpsFocusProcess->editFocusProfile->setIcon(QIcon::fromTheme("document-edit")); 0120 m_OpsFocusProcess->editFocusProfile->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0121 0122 connect(m_OpsFocusProcess->editFocusProfile, &QAbstractButton::clicked, this, [this]() 0123 { 0124 KConfigDialog *optionsEditor = new KConfigDialog(this, "OptionsProfileEditor", Options::self()); 0125 optionsProfileEditor = new StellarSolverProfileEditor(this, Ekos::FocusProfiles, optionsEditor); 0126 #ifdef Q_OS_OSX 0127 optionsEditor->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); 0128 #endif 0129 KPageWidgetItem *mainPage = optionsEditor->addPage(optionsProfileEditor, i18n("Focus Options Profile Editor")); 0130 mainPage->setIcon(QIcon::fromTheme("configure")); 0131 connect(optionsProfileEditor, &StellarSolverProfileEditor::optionsProfilesUpdated, this, &Focus::loadStellarSolverProfiles); 0132 optionsProfileEditor->loadProfile(m_OpsFocusProcess->focusSEPProfile->currentText()); 0133 optionsEditor->show(); 0134 }); 0135 0136 loadStellarSolverProfiles(); 0137 0138 // connect HFR plot widget 0139 connect(this, &Ekos::Focus::initHFRPlot, HFRPlot, &FocusHFRVPlot::init); 0140 connect(this, &Ekos::Focus::redrawHFRPlot, HFRPlot, &FocusHFRVPlot::redraw); 0141 connect(this, &Ekos::Focus::newHFRPlotPosition, HFRPlot, &FocusHFRVPlot::addPosition); 0142 connect(this, &Ekos::Focus::drawPolynomial, HFRPlot, &FocusHFRVPlot::drawPolynomial); 0143 // connect signal/slot for the curve plotting to the V-Curve widget 0144 connect(this, &Ekos::Focus::drawCurve, HFRPlot, &FocusHFRVPlot::drawCurve); 0145 connect(this, &Ekos::Focus::setTitle, HFRPlot, &FocusHFRVPlot::setTitle); 0146 connect(this, &Ekos::Focus::finalUpdates, HFRPlot, &FocusHFRVPlot::finalUpdates); 0147 connect(this, &Ekos::Focus::minimumFound, HFRPlot, &FocusHFRVPlot::drawMinimum); 0148 connect(this, &Ekos::Focus::drawCFZ, HFRPlot, &FocusHFRVPlot::drawCFZ); 0149 0150 m_DarkProcessor = new DarkProcessor(this); 0151 connect(m_DarkProcessor, &DarkProcessor::newLog, this, &Ekos::Focus::appendLogText); 0152 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted, this, [this](bool completed) 0153 { 0154 m_OpsFocusSettings->useFocusDarkFrame->setChecked(completed); 0155 m_FocusView->setProperty("suspended", false); 0156 if (completed) 0157 { 0158 m_FocusView->rescale(ZOOM_KEEP_LEVEL); 0159 m_FocusView->updateFrame(); 0160 } 0161 setCaptureComplete(); 0162 resetButtons(); 0163 }); 0164 0165 setupOpticalTrainManager(); 0166 // Needs to be done once 0167 connectFilterManager(); 0168 } 0169 0170 // Do once only preparation of GUI 0171 void Focus::prepareGUI() 0172 { 0173 // Parameters are handled by the KConfigDialog invoked by pressing the "Options..." button 0174 // on the Focus window. There are 3 pages of options. 0175 // Parameters are persisted per Optical Train, so when the user changes OT, the last persisted 0176 // parameters for the new OT are loaded. In addition the "current" parameter values are also 0177 // persisted locally using kstars.kcfg 0178 // KConfigDialog has the ability to persist parameters to kstars.kcfg but this functionality 0179 // is not used in Focus 0180 KConfigDialog *dialog = new KConfigDialog(this, "focussettings", Options::self()); 0181 m_OpsFocusSettings = new OpsFocusSettings(); 0182 #ifdef Q_OS_OSX 0183 dialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); 0184 #endif 0185 0186 KPageWidgetItem *page = dialog->addPage(m_OpsFocusSettings, i18n("Settings"), nullptr, i18n("Focus Settings"), false); 0187 page->setIcon(QIcon::fromTheme("configure")); 0188 0189 m_OpsFocusProcess = new OpsFocusProcess(); 0190 page = dialog->addPage(m_OpsFocusProcess, i18n("Process"), nullptr, i18n("Focus Process"), false); 0191 page->setIcon(QIcon::fromTheme("transform-move")); 0192 0193 m_OpsFocusMechanics = new OpsFocusMechanics(); 0194 page = dialog->addPage(m_OpsFocusMechanics, i18n("Mechanics"), nullptr, i18n("Focus Mechanics"), false); 0195 page->setIcon(QIcon::fromTheme("tool-measure")); 0196 0197 // The CFZ is a tool so has its own dialog box. 0198 m_CFZDialog = new QDialog(this); 0199 m_CFZUI.reset(new Ui::focusCFZDialog()); 0200 m_CFZUI->setupUi(m_CFZDialog); 0201 #ifdef Q_OS_OSX 0202 m_CFZDialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); 0203 #endif 0204 0205 // The Focus Advisor is a tool so has its own dialog box. 0206 m_AdvisorDialog = new QDialog(this); 0207 m_AdvisorUI.reset(new Ui::focusAdvisorDialog()); 0208 m_AdvisorUI->setupUi(m_AdvisorDialog); 0209 #ifdef Q_OS_OSX 0210 m_AdvisorDialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); 0211 #endif 0212 0213 // Remove all widgets from the temporary bucket. These will then be loaded as required 0214 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel); 0215 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusMultiRowAverage); 0216 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel); 0217 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusGaussianSigma); 0218 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusThresholdLabel); 0219 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusThreshold); 0220 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel); 0221 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize); 0222 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusToleranceLabel); 0223 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusTolerance); 0224 delete m_OpsFocusProcess->gridLayoutProcessBucket; 0225 0226 // Setup the Walk fields. OutSteps and NumSteps are either/or widgets so co-locate them 0227 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusOutStepsLabel, 0228 m_OpsFocusMechanics->focusNumStepsLabel); 0229 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusOutSteps, 0230 m_OpsFocusMechanics->focusNumSteps); 0231 0232 // Some combo-boxes have changeable values depending on other settings so store the full list of options from the .ui 0233 // This helps keep some synchronisation with the .ui 0234 for (int i = 0; i < m_OpsFocusProcess->focusStarMeasure->count(); i++) 0235 m_StarMeasureText.append(m_OpsFocusProcess->focusStarMeasure->itemText(i)); 0236 for (int i = 0; i < m_OpsFocusProcess->focusCurveFit->count(); i++) 0237 m_CurveFitText.append(m_OpsFocusProcess->focusCurveFit->itemText(i)); 0238 for (int i = 0; i < m_OpsFocusMechanics->focusWalk->count(); i++) 0239 m_FocusWalkText.append(m_OpsFocusMechanics->focusWalk->itemText(i)); 0240 } 0241 0242 void Focus::loadStellarSolverProfiles() 0243 { 0244 QString savedOptionsProfiles = QDir(KSPaths::writableLocation( 0245 QStandardPaths::AppLocalDataLocation)).filePath("SavedFocusProfiles.ini"); 0246 if(QFileInfo::exists(savedOptionsProfiles)) 0247 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles); 0248 else 0249 m_StellarSolverProfiles = getDefaultFocusOptionsProfiles(); 0250 m_OpsFocusProcess->focusSEPProfile->clear(); 0251 for(auto ¶m : m_StellarSolverProfiles) 0252 m_OpsFocusProcess->focusSEPProfile->addItem(param.listName); 0253 auto profile = m_Settings["focusSEPProfile"]; 0254 if (profile.isValid()) 0255 m_OpsFocusProcess->focusSEPProfile->setCurrentText(profile.toString()); 0256 } 0257 0258 QStringList Focus::getStellarSolverProfiles() 0259 { 0260 QStringList profiles; 0261 for (auto param : m_StellarSolverProfiles) 0262 profiles << param.listName; 0263 0264 return profiles; 0265 } 0266 0267 Focus::~Focus() 0268 { 0269 if (focusingWidget->parent() == nullptr) 0270 toggleFocusingWidgetFullScreen(); 0271 0272 m_FocusLogFile.close(); 0273 } 0274 0275 void Focus::resetFrame() 0276 { 0277 if (m_Camera && m_Camera->isConnected()) 0278 { 0279 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 0280 0281 if (targetChip) 0282 { 0283 //fx=fy=fw=fh=0; 0284 targetChip->resetFrame(); 0285 0286 int x, y, w, h; 0287 targetChip->getFrame(&x, &y, &w, &h); 0288 0289 qCDebug(KSTARS_EKOS_FOCUS) << "Frame is reset. X:" << x << "Y:" << y << "W:" << w << "H:" << h << "binX:" << 1 << "binY:" << 0290 1; 0291 0292 QVariantMap settings; 0293 settings["x"] = x; 0294 settings["y"] = y; 0295 settings["w"] = w; 0296 settings["h"] = h; 0297 settings["binx"] = 1; 0298 settings["biny"] = 1; 0299 frameSettings[targetChip] = settings; 0300 0301 starSelected = false; 0302 starCenter = QVector3D(); 0303 subFramed = false; 0304 0305 m_FocusView->setTrackingBox(QRect()); 0306 checkMosaicMaskLimits(); 0307 } 0308 } 0309 } 0310 0311 QString Focus::camera() 0312 { 0313 if (m_Camera) 0314 return m_Camera->getDeviceName(); 0315 0316 return QString(); 0317 } 0318 0319 void Focus::checkCamera() 0320 { 0321 if (!m_Camera) 0322 return; 0323 0324 // Do NOT perform checks when the camera is capturing or busy as this may result 0325 // in signals/slots getting disconnected. 0326 switch (state()) 0327 { 0328 // Idle, can change camera. 0329 case FOCUS_IDLE: 0330 case FOCUS_COMPLETE: 0331 case FOCUS_FAILED: 0332 case FOCUS_ABORTED: 0333 break; 0334 0335 // Busy, cannot change camera. 0336 case FOCUS_WAITING: 0337 case FOCUS_PROGRESS: 0338 case FOCUS_FRAMING: 0339 case FOCUS_CHANGING_FILTER: 0340 return; 0341 } 0342 0343 0344 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 0345 if (targetChip && targetChip->isCapturing()) 0346 return; 0347 0348 if (targetChip) 0349 { 0350 focusBinning->setEnabled(targetChip->canBin()); 0351 m_OpsFocusSettings->focusSubFrame->setEnabled(targetChip->canSubframe()); 0352 if (targetChip->canBin()) 0353 { 0354 int subBinX = 1, subBinY = 1; 0355 focusBinning->clear(); 0356 targetChip->getMaxBin(&subBinX, &subBinY); 0357 for (int i = 1; i <= subBinX; i++) 0358 focusBinning->addItem(QString("%1x%2").arg(i).arg(i)); 0359 0360 auto binning = m_Settings["focusBinning"]; 0361 if (binning.isValid()) 0362 focusBinning->setCurrentText(binning.toString()); 0363 } 0364 0365 connect(m_Camera, &ISD::Camera::videoStreamToggled, this, &Ekos::Focus::setVideoStreamEnabled, Qt::UniqueConnection); 0366 liveVideoB->setEnabled(m_Camera->hasVideoStream()); 0367 if (m_Camera->hasVideoStream()) 0368 setVideoStreamEnabled(m_Camera->isStreamingEnabled()); 0369 else 0370 liveVideoB->setIcon(QIcon::fromTheme("camera-off")); 0371 0372 } 0373 0374 syncCCDControls(); 0375 syncCameraInfo(); 0376 } 0377 0378 void Focus::syncCCDControls() 0379 { 0380 if (m_Camera == nullptr) 0381 return; 0382 0383 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 0384 if (targetChip == nullptr || (targetChip && targetChip->isCapturing())) 0385 return; 0386 0387 auto isoList = targetChip->getISOList(); 0388 focusISO->clear(); 0389 0390 if (isoList.isEmpty()) 0391 { 0392 focusISO->setEnabled(false); 0393 ISOLabel->setEnabled(false); 0394 } 0395 else 0396 { 0397 focusISO->setEnabled(true); 0398 ISOLabel->setEnabled(true); 0399 focusISO->addItems(isoList); 0400 focusISO->setCurrentIndex(targetChip->getISOIndex()); 0401 } 0402 0403 bool hasGain = m_Camera->hasGain(); 0404 gainLabel->setEnabled(hasGain); 0405 focusGain->setEnabled(hasGain && m_Camera->getGainPermission() != IP_RO); 0406 if (hasGain) 0407 { 0408 double gain = 0, min = 0, max = 0, step = 1; 0409 m_Camera->getGainMinMaxStep(&min, &max, &step); 0410 if (m_Camera->getGain(&gain)) 0411 { 0412 // Allow the possibility of no gain value at all. 0413 GainSpinSpecialValue = min - step; 0414 focusGain->setRange(GainSpinSpecialValue, max); 0415 focusGain->setSpecialValueText(i18n("--")); 0416 if (step > 0) 0417 focusGain->setSingleStep(step); 0418 0419 auto defaultGain = m_Settings["focusGain"]; 0420 if (defaultGain.isValid()) 0421 focusGain->setValue(defaultGain.toDouble()); 0422 else 0423 focusGain->setValue(GainSpinSpecialValue); 0424 } 0425 } 0426 else 0427 focusGain->clear(); 0428 } 0429 0430 void Focus::syncCameraInfo() 0431 { 0432 if (m_Camera == nullptr) 0433 return; 0434 0435 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 0436 if (targetChip == nullptr || (targetChip && targetChip->isCapturing())) 0437 return; 0438 0439 m_OpsFocusSettings->focusSubFrame->setEnabled(targetChip->canSubframe()); 0440 0441 if (frameSettings.contains(targetChip) == false) 0442 { 0443 int x, y, w, h; 0444 if (targetChip->getFrame(&x, &y, &w, &h)) 0445 { 0446 int binx = 1, biny = 1; 0447 targetChip->getBinning(&binx, &biny); 0448 if (w > 0 && h > 0) 0449 { 0450 int minX, maxX, minY, maxY, minW, maxW, minH, maxH; 0451 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); 0452 0453 QVariantMap settings; 0454 0455 settings["x"] = m_OpsFocusSettings->focusSubFrame->isChecked() ? x : minX; 0456 settings["y"] = m_OpsFocusSettings->focusSubFrame->isChecked() ? y : minY; 0457 settings["w"] = m_OpsFocusSettings->focusSubFrame->isChecked() ? w : maxW; 0458 settings["h"] = m_OpsFocusSettings->focusSubFrame->isChecked() ? h : maxH; 0459 settings["binx"] = binx; 0460 settings["biny"] = biny; 0461 0462 frameSettings[targetChip] = settings; 0463 } 0464 } 0465 } 0466 } 0467 0468 bool Focus::setFilterWheel(ISD::FilterWheel *device) 0469 { 0470 if (m_FilterWheel && m_FilterWheel == device) 0471 { 0472 checkFilter(); 0473 return false; 0474 } 0475 0476 if (m_FilterWheel) 0477 m_FilterWheel->disconnect(this); 0478 0479 m_FilterWheel = device; 0480 0481 if (m_FilterWheel) 0482 { 0483 connect(m_FilterWheel, &ISD::ConcreteDevice::Connected, this, [this]() 0484 { 0485 FilterPosLabel->setEnabled(true); 0486 focusFilter->setEnabled(true); 0487 filterManagerB->setEnabled(true); 0488 }); 0489 connect(m_FilterWheel, &ISD::ConcreteDevice::Disconnected, this, [this]() 0490 { 0491 FilterPosLabel->setEnabled(false); 0492 focusFilter->setEnabled(false); 0493 filterManagerB->setEnabled(false); 0494 }); 0495 } 0496 0497 auto isConnected = m_FilterWheel && m_FilterWheel->isConnected(); 0498 FilterPosLabel->setEnabled(isConnected); 0499 focusFilter->setEnabled(isConnected); 0500 filterManagerB->setEnabled(isConnected); 0501 0502 FilterPosLabel->setEnabled(true); 0503 focusFilter->setEnabled(true); 0504 0505 checkFilter(); 0506 return true; 0507 } 0508 0509 bool Focus::addTemperatureSource(const QSharedPointer<ISD::GenericDevice> &device) 0510 { 0511 if (device.isNull()) 0512 return false; 0513 0514 for (auto &oneSource : m_TemperatureSources) 0515 { 0516 if (oneSource->getDeviceName() == device->getDeviceName()) 0517 return false; 0518 } 0519 0520 m_TemperatureSources.append(device); 0521 defaultFocusTemperatureSource->addItem(device->getDeviceName()); 0522 0523 auto targetSource = m_Settings["defaultFocusTemperatureSource"]; 0524 if (targetSource.isValid()) 0525 checkTemperatureSource(targetSource.toString()); 0526 return true; 0527 } 0528 0529 void Focus::checkTemperatureSource(const QString &name ) 0530 { 0531 auto source = name; 0532 if (name.isEmpty()) 0533 { 0534 source = defaultFocusTemperatureSource->currentText(); 0535 if (source.isEmpty()) 0536 return; 0537 } 0538 0539 QSharedPointer<ISD::GenericDevice> currentSource; 0540 0541 for (auto &oneSource : m_TemperatureSources) 0542 { 0543 if (oneSource->getDeviceName() == source) 0544 { 0545 currentSource = oneSource; 0546 break; 0547 } 0548 } 0549 0550 m_LastSourceDeviceAutofocusTemperature = currentSource; 0551 0552 // No valid device found 0553 if (!currentSource) 0554 return; 0555 0556 // Disconnect all existing signals 0557 for (const auto &oneSource : m_TemperatureSources) 0558 disconnect(oneSource.get(), &ISD::GenericDevice::propertyUpdated, this, &Ekos::Focus::processTemperatureSource); 0559 0560 0561 if (findTemperatureElement(currentSource)) 0562 { 0563 m_LastSourceAutofocusTemperature = currentTemperatureSourceElement->value; 0564 absoluteTemperatureLabel->setText(QString("%1 °C").arg(currentTemperatureSourceElement->value, 0, 'f', 2)); 0565 deltaTemperatureLabel->setText(QString("%1 °C").arg(0.0, 0, 'f', 2)); 0566 } 0567 else 0568 { 0569 m_LastSourceAutofocusTemperature = INVALID_VALUE; 0570 } 0571 0572 connect(currentSource.get(), &ISD::GenericDevice::propertyUpdated, this, &Ekos::Focus::processTemperatureSource, 0573 Qt::UniqueConnection); 0574 } 0575 0576 bool Focus::findTemperatureElement(const QSharedPointer<ISD::GenericDevice> &device) 0577 { 0578 // protect for nullptr 0579 if (device.isNull()) 0580 return false; 0581 0582 auto temperatureProperty = device->getProperty("FOCUS_TEMPERATURE"); 0583 if (!temperatureProperty) 0584 temperatureProperty = device->getProperty("CCD_TEMPERATURE"); 0585 if (temperatureProperty) 0586 { 0587 currentTemperatureSourceElement = temperatureProperty.getNumber()->at(0); 0588 return true; 0589 } 0590 0591 temperatureProperty = device->getProperty("WEATHER_PARAMETERS"); 0592 if (temperatureProperty) 0593 { 0594 for (int i = 0; i < temperatureProperty.getNumber()->count(); i++) 0595 { 0596 if (strstr(temperatureProperty.getNumber()->at(i)->getName(), "_TEMPERATURE")) 0597 { 0598 currentTemperatureSourceElement = temperatureProperty.getNumber()->at(i); 0599 return true; 0600 } 0601 } 0602 } 0603 0604 return false; 0605 } 0606 0607 QString Focus::filterWheel() 0608 { 0609 if (m_FilterWheel) 0610 return m_FilterWheel->getDeviceName(); 0611 0612 return QString(); 0613 } 0614 0615 0616 bool Focus::setFilter(const QString &filter) 0617 { 0618 if (m_FilterWheel) 0619 { 0620 focusFilter->setCurrentText(filter); 0621 return true; 0622 } 0623 0624 return false; 0625 } 0626 0627 QString Focus::filter() 0628 { 0629 return focusFilter->currentText(); 0630 } 0631 0632 void Focus::checkFilter() 0633 { 0634 focusFilter->clear(); 0635 0636 if (!m_FilterWheel) 0637 { 0638 FilterPosLabel->setEnabled(false); 0639 focusFilter->setEnabled(false); 0640 filterManagerB->setEnabled(false); 0641 0642 if (m_FilterManager) 0643 { 0644 m_FilterManager->disconnect(this); 0645 disconnect(m_FilterManager.get()); 0646 m_FilterManager.reset(); 0647 } 0648 return; 0649 } 0650 0651 FilterPosLabel->setEnabled(true); 0652 focusFilter->setEnabled(true); 0653 filterManagerB->setEnabled(true); 0654 0655 setupFilterManager(); 0656 0657 focusFilter->addItems(m_FilterManager->getFilterLabels()); 0658 0659 currentFilterPosition = m_FilterManager->getFilterPosition(); 0660 0661 focusFilter->setCurrentIndex(currentFilterPosition - 1); 0662 0663 focusExposure->setValue(m_FilterManager->getFilterExposure()); 0664 } 0665 0666 bool Focus::setFocuser(ISD::Focuser *device) 0667 { 0668 if (m_Focuser && m_Focuser == device) 0669 { 0670 checkFocuser(); 0671 return false; 0672 } 0673 0674 if (m_Focuser) 0675 m_Focuser->disconnect(this); 0676 0677 m_Focuser = device; 0678 0679 if (m_Focuser) 0680 { 0681 connect(m_Focuser, &ISD::ConcreteDevice::Connected, this, [this]() 0682 { 0683 resetButtons(); 0684 }); 0685 connect(m_Focuser, &ISD::ConcreteDevice::Disconnected, this, [this]() 0686 { 0687 resetButtons(); 0688 }); 0689 } 0690 0691 checkFocuser(); 0692 return true; 0693 } 0694 0695 QString Focus::focuser() 0696 { 0697 if (m_Focuser) 0698 return m_Focuser->getDeviceName(); 0699 0700 return QString(); 0701 } 0702 0703 void Focus::checkFocuser() 0704 { 0705 if (!m_Focuser) 0706 { 0707 if (m_FilterManager) 0708 m_FilterManager->setFocusReady(false); 0709 canAbsMove = canRelMove = canTimerMove = false; 0710 resetButtons(); 0711 setFocusAlgorithm(static_cast<Algorithm> (m_OpsFocusProcess->focusAlgorithm->currentIndex())); 0712 return; 0713 } 0714 else 0715 focuserLabel->setText(m_Focuser->getDeviceName()); 0716 0717 if (m_FilterManager) 0718 m_FilterManager->setFocusReady(m_Focuser->isConnected()); 0719 0720 hasDeviation = m_Focuser->hasDeviation(); 0721 0722 canAbsMove = m_Focuser->canAbsMove(); 0723 0724 if (canAbsMove) 0725 { 0726 getAbsFocusPosition(); 0727 0728 absTicksSpin->setEnabled(true); 0729 absTicksLabel->setEnabled(true); 0730 startGotoB->setEnabled(true); 0731 0732 // Now that Optical Trains are managing settings, no need to set absTicksSpin, except if it has a bad value 0733 if (absTicksSpin->value() <= 0) 0734 absTicksSpin->setValue(currentPosition); 0735 } 0736 else 0737 { 0738 absTicksSpin->setEnabled(false); 0739 absTicksLabel->setEnabled(false); 0740 startGotoB->setEnabled(false); 0741 } 0742 0743 canRelMove = m_Focuser->canRelMove(); 0744 0745 // In case we have a purely relative focuser, we pretend 0746 // it is an absolute focuser with initial point set at 50,000. 0747 // This is done we can use the same algorithm used for absolute focuser. 0748 if (canAbsMove == false && canRelMove == true) 0749 { 0750 currentPosition = 50000; 0751 absMotionMax = 100000; 0752 absMotionMin = 0; 0753 } 0754 0755 canTimerMove = m_Focuser->canTimerMove(); 0756 0757 // In case we have a timer-based focuser and using the linear focus algorithm, 0758 // we pretend it is an absolute focuser with initial point set at 50,000. 0759 // These variables don't have in impact on timer-based focusers if the algorithm 0760 // is not the linear focus algorithm. 0761 if (!canAbsMove && !canRelMove && canTimerMove) 0762 { 0763 currentPosition = 50000; 0764 absMotionMax = 100000; 0765 absMotionMin = 0; 0766 } 0767 0768 m_FocusType = (canRelMove || canAbsMove || canTimerMove) ? FOCUS_AUTO : FOCUS_MANUAL; 0769 profilePlot->setFocusAuto(m_FocusType == FOCUS_AUTO); 0770 0771 bool hasBacklash = m_Focuser->hasBacklash(); 0772 m_OpsFocusMechanics->focusBacklash->setEnabled(hasBacklash); 0773 m_OpsFocusMechanics->focusBacklash->disconnect(this); 0774 if (hasBacklash) 0775 { 0776 double min = 0, max = 0, step = 0; 0777 m_Focuser->getMinMaxStep("FOCUS_BACKLASH_STEPS", "FOCUS_BACKLASH_VALUE", &min, &max, &step); 0778 m_OpsFocusMechanics->focusBacklash->setMinimum(min); 0779 m_OpsFocusMechanics->focusBacklash->setMaximum(max); 0780 m_OpsFocusMechanics->focusBacklash->setSingleStep(step); 0781 m_OpsFocusMechanics->focusBacklash->setValue(m_Focuser->getBacklash()); 0782 connect(m_OpsFocusMechanics->focusBacklash, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), 0783 this, [this](int value) 0784 { 0785 if (m_Focuser) 0786 { 0787 if (m_Focuser->getBacklash() == value) 0788 // Prevent an event storm where a fast update of the backlash field, e.g. changing 0789 // the value to "12" results in 2 events; "1", "12". As these events get 0790 // processed in the driver and persisted, they in turn update backlash and start 0791 // to conflict with this callback, getting stuck forever: "1", "12", "1', "12" 0792 return; 0793 m_Focuser->setBacklash(value); 0794 } 0795 }); 0796 // Re-esablish connection to sync settings. Only need to reconnect if the focuser 0797 // has backlash as the value can't be changed if the focuser doesn't have the backlash property. 0798 connect(m_OpsFocusMechanics->focusBacklash, QOverload<int>::of(&QSpinBox::valueChanged), this, &Ekos::Focus::syncSettings); 0799 } 0800 else 0801 { 0802 m_OpsFocusMechanics->focusBacklash->setValue(0); 0803 } 0804 0805 connect(m_Focuser, &ISD::Focuser::propertyUpdated, this, &Ekos::Focus::updateProperty, Qt::UniqueConnection); 0806 0807 resetButtons(); 0808 setFocusAlgorithm(static_cast<Algorithm> (m_OpsFocusProcess->focusAlgorithm->currentIndex())); 0809 } 0810 0811 bool Focus::setCamera(ISD::Camera *device) 0812 { 0813 if (m_Camera && m_Camera == device) 0814 { 0815 checkCamera(); 0816 return false; 0817 } 0818 0819 if (m_Camera) 0820 m_Camera->disconnect(this); 0821 0822 m_Camera = device; 0823 0824 if (m_Camera) 0825 { 0826 connect(m_Camera, &ISD::ConcreteDevice::Connected, this, [this]() 0827 { 0828 focuserGroup->setEnabled(true); 0829 ccdGroup->setEnabled(true); 0830 toolsGroup->setEnabled(true); 0831 }); 0832 connect(m_Camera, &ISD::ConcreteDevice::Disconnected, this, [this]() 0833 { 0834 focuserGroup->setEnabled(false); 0835 ccdGroup->setEnabled(false); 0836 toolsGroup->setEnabled(false); 0837 }); 0838 } 0839 0840 auto isConnected = m_Camera && m_Camera->isConnected(); 0841 focuserGroup->setEnabled(isConnected); 0842 ccdGroup->setEnabled(isConnected); 0843 toolsGroup->setEnabled(isConnected); 0844 0845 if (!m_Camera) 0846 return false; 0847 0848 resetFrame(); 0849 0850 checkCamera(); 0851 checkMosaicMaskLimits(); 0852 return true; 0853 } 0854 0855 void Focus::getAbsFocusPosition() 0856 { 0857 if (!canAbsMove) 0858 return; 0859 0860 auto absMove = m_Focuser->getNumber("ABS_FOCUS_POSITION"); 0861 0862 if (absMove) 0863 { 0864 const auto &it = absMove->at(0); 0865 currentPosition = static_cast<int>(it->getValue()); 0866 absMotionMax = it->getMax(); 0867 absMotionMin = it->getMin(); 0868 0869 absTicksSpin->setMinimum(it->getMin()); 0870 absTicksSpin->setMaximum(it->getMax()); 0871 absTicksSpin->setSingleStep(it->getStep()); 0872 0873 // Restrict the travel if needed 0874 double const travel = std::abs(it->getMax() - it->getMin()); 0875 m_OpsFocusMechanics->focusMaxTravel->setMaximum(travel);; 0876 0877 absTicksLabel->setText(QString::number(currentPosition)); 0878 0879 m_OpsFocusMechanics->focusTicks->setMaximum(it->getMax() / 2); 0880 } 0881 } 0882 0883 void Focus::processTemperatureSource(INDI::Property prop) 0884 { 0885 if (m_LastSourceAutofocusTemperature == INVALID_VALUE && m_LastSourceDeviceAutofocusTemperature 0886 && !currentTemperatureSourceElement ) 0887 { 0888 if( findTemperatureElement( m_LastSourceDeviceAutofocusTemperature ) ) 0889 { 0890 appendLogText(i18n("Finally found temperature source %1", QString(currentTemperatureSourceElement->nvp->name))); 0891 m_LastSourceAutofocusTemperature = currentTemperatureSourceElement->value; 0892 } 0893 } 0894 0895 double delta = 0; 0896 if (currentTemperatureSourceElement && currentTemperatureSourceElement->nvp->name == prop.getName()) 0897 { 0898 if (m_LastSourceAutofocusTemperature != INVALID_VALUE) 0899 { 0900 delta = currentTemperatureSourceElement->value - m_LastSourceAutofocusTemperature; 0901 emit newFocusTemperatureDelta(abs(delta), currentTemperatureSourceElement->value); 0902 } 0903 else 0904 { 0905 emit newFocusTemperatureDelta(0, currentTemperatureSourceElement->value); 0906 } 0907 0908 absoluteTemperatureLabel->setText(QString("%1 °C").arg(currentTemperatureSourceElement->value, 0, 'f', 2)); 0909 deltaTemperatureLabel->setText(QString("%1%2 °C").arg((delta > 0.0 ? "+" : "")).arg(delta, 0, 'f', 2)); 0910 if (delta == 0) 0911 deltaTemperatureLabel->setStyleSheet("color: lightgreen"); 0912 else if (delta > 0) 0913 deltaTemperatureLabel->setStyleSheet("color: lightcoral"); 0914 else 0915 deltaTemperatureLabel->setStyleSheet("color: lightblue"); 0916 } 0917 } 0918 0919 void Focus::setLastFocusTemperature() 0920 { 0921 m_LastSourceAutofocusTemperature = currentTemperatureSourceElement ? currentTemperatureSourceElement->value : INVALID_VALUE; 0922 0923 // Reset delta to zero now that we're just done with autofocus 0924 deltaTemperatureLabel->setText(QString("0 °C")); 0925 deltaTemperatureLabel->setStyleSheet("color: lightgreen"); 0926 0927 emit newFocusTemperatureDelta(0, -1e6); 0928 } 0929 0930 void Focus::setLastFocusAlt() 0931 { 0932 if (mountAlt < 0.0 || mountAlt > 90.0) 0933 m_LastSourceAutofocusAlt = INVALID_VALUE; 0934 else 0935 m_LastSourceAutofocusAlt = mountAlt; 0936 } 0937 0938 // Reset Adaptive Focus parameters. 0939 void Focus::resetAdaptiveFocus(bool enabled) 0940 { 0941 // Set the focusAdaptive switch so other modules such as Capture can access it 0942 Options::setFocusAdaptive(enabled); 0943 0944 if (enabled) 0945 adaptFocus.reset(new AdaptiveFocus(this)); 0946 adaptFocus->resetAdaptiveFocusCounters(); 0947 } 0948 0949 // Capture has signalled an Adaptive Focus run 0950 void Focus::adaptiveFocus() 0951 { 0952 // Invoke runAdaptiveFocus 0953 adaptFocus->runAdaptiveFocus(currentPosition, filter()); 0954 } 0955 0956 // Run Aberration Inspector 0957 void Focus::startAbIns() 0958 { 0959 m_abInsOn = canAbInsStart(); 0960 start(); 0961 } 0962 0963 void Focus::start() 0964 { 0965 if (m_Focuser == nullptr) 0966 { 0967 appendLogText(i18n("No Focuser connected.")); 0968 completeFocusProcedure(Ekos::FOCUS_ABORTED); 0969 return; 0970 } 0971 0972 if (m_Camera == nullptr) 0973 { 0974 appendLogText(i18n("No CCD connected.")); 0975 completeFocusProcedure(Ekos::FOCUS_ABORTED); 0976 return; 0977 } 0978 0979 if (!canAbsMove && !canRelMove && m_OpsFocusMechanics->focusTicks->value() <= MINIMUM_PULSE_TIMER) 0980 { 0981 appendLogText(i18n("Starting pulse step is too low. Increase the step size to %1 or higher...", 0982 MINIMUM_PULSE_TIMER * 5)); 0983 completeFocusProcedure(Ekos::FOCUS_ABORTED); 0984 return; 0985 } 0986 0987 if (inAutoFocus) 0988 { 0989 // If Autofocus is already running, just ignore this request. This condition should not happen 0990 // There is no point signalling Autofocus failure as that will trigger actions based on the 0991 // currently running Autofocus failing (whilst it is still running). 0992 appendLogText(i18n("Autofocus is already running, discarding start request.")); 0993 return; 0994 } 0995 else if (inAdjustFocus) 0996 { 0997 if (++AFStartRetries < MAXIMUM_RESET_ITERATIONS) 0998 { 0999 // Its possible that a start request can be triggered whilst an Adjust Focus is completing 1000 // This was reported by a user. The conditions require align resetting a filter and an offset 1001 // on the filter needing to be applied. So retry 3 times (10s interval) and fail if still a problem 1002 appendLogText(i18n("Autofocus start request - Waiting 10sec for AdjustFocus to complete.")); 1003 QTimer::singleShot(10 * 1000, this, [this]() 1004 { 1005 start(); 1006 }); 1007 return; 1008 } 1009 1010 appendLogText(i18n("Discarding Autofocus start request - AdjustFocus in progress.")); 1011 completeFocusProcedure(Ekos::FOCUS_ABORTED); 1012 return; 1013 } 1014 else if (adaptFocus->inAdaptiveFocus()) 1015 { 1016 // Protective code added as per the above else if. This scenario is unlikely 1017 if (++AFStartRetries < MAXIMUM_RESET_ITERATIONS) 1018 { 1019 appendLogText(i18n("Autofocus start request - Waiting 10sec for AdaptiveFocus to complete.")); 1020 QTimer::singleShot(10 * 1000, this, [this]() 1021 { 1022 start(); 1023 }); 1024 return; 1025 } 1026 1027 appendLogText(i18n("Discarding Autofocus start request - AdaptiveFocus in progress.")); 1028 completeFocusProcedure(Ekos::FOCUS_ABORTED); 1029 return; 1030 } 1031 1032 inAutoFocus = true; 1033 m_AFRun++; 1034 AFStartRetries = 0; 1035 m_LastFocusDirection = FOCUS_NONE; 1036 1037 waitStarSelectTimer.stop(); 1038 1039 // Reset the focus motion timer 1040 m_FocusMotionTimerCounter = 0; 1041 m_FocusMotionTimer.stop(); 1042 m_FocusMotionTimer.setInterval(m_OpsFocusMechanics->focusMotionTimeout->value() * 1000); 1043 1044 // Reset focuser reconnect counter 1045 m_FocuserReconnectCounter = 0; 1046 1047 // Reset focuser reconnect counter 1048 m_FocuserReconnectCounter = 0; 1049 m_DebugFocuserCounter = 0; 1050 1051 starsHFR.clear(); 1052 1053 lastHFR = 0; 1054 1055 // Reset state variable that deals with retrying and aborting aurofocus 1056 m_RestartState = RESTART_NONE; 1057 1058 // Keep the last focus temperature, it can still be useful in case the autofocus fails 1059 // lastFocusTemperature 1060 1061 if (canAbsMove) 1062 { 1063 absIterations = 0; 1064 getAbsFocusPosition(); 1065 pulseDuration = m_OpsFocusMechanics->focusTicks->value(); 1066 } 1067 else if (canRelMove) 1068 { 1069 //appendLogText(i18n("Setting dummy central position to 50000")); 1070 absIterations = 0; 1071 pulseDuration = m_OpsFocusMechanics->focusTicks->value(); 1072 //currentPosition = 50000; 1073 absMotionMax = 100000; 1074 absMotionMin = 0; 1075 } 1076 else 1077 { 1078 pulseDuration = m_OpsFocusMechanics->focusTicks->value(); 1079 absIterations = 0; 1080 absMotionMax = 100000; 1081 absMotionMin = 0; 1082 } 1083 1084 focuserAdditionalMovement = 0; 1085 starMeasureFrames.clear(); 1086 1087 resetButtons(); 1088 1089 reverseDir = false; 1090 1091 /*if (fw > 0 && fh > 0) 1092 starSelected= true; 1093 else 1094 starSelected= false;*/ 1095 1096 clearDataPoints(); 1097 profilePlot->clear(); 1098 FWHMOut->setText(""); 1099 1100 qCInfo(KSTARS_EKOS_FOCUS) << "Starting Autofocus " << m_AFRun 1101 << " on" << focuserLabel->text() 1102 << " CanAbsMove: " << (canAbsMove ? "yes" : "no" ) 1103 << " CanRelMove: " << (canRelMove ? "yes" : "no" ) 1104 << " CanTimerMove: " << (canTimerMove ? "yes" : "no" ) 1105 << " Position:" << currentPosition 1106 << " Filter:" << filter() 1107 << " Exp:" << focusExposure->value() 1108 << " Bin:" << focusBinning->currentText() 1109 << " Gain:" << focusGain->value() 1110 << " ISO:" << focusISO->currentText(); 1111 qCInfo(KSTARS_EKOS_FOCUS) << "Settings Tab." 1112 << " Auto Select Star:" << ( m_OpsFocusSettings->focusAutoStarEnabled->isChecked() ? "yes" : "no" ) 1113 << " Dark Frame:" << ( m_OpsFocusSettings->useFocusDarkFrame->isChecked() ? "yes" : "no" ) 1114 << " Sub Frame:" << ( m_OpsFocusSettings->focusSubFrame->isChecked() ? "yes" : "no" ) 1115 << " Box:" << m_OpsFocusSettings->focusBoxSize->value() 1116 << " Full frame:" << ( m_OpsFocusSettings->focusUseFullField->isChecked() ? "yes" : "no " ) 1117 << " Focus Mask: " << (m_OpsFocusSettings->focusNoMaskRB->isChecked() ? "Use all stars" : 1118 (m_OpsFocusSettings->focusRingMaskRB->isChecked() ? QString("Ring Mask: [%1%, %2%]"). 1119 arg(m_OpsFocusSettings->focusFullFieldInnerRadius->value()).arg(m_OpsFocusSettings->focusFullFieldOuterRadius->value()) : 1120 QString("Mosaic Mask: [%1%, space=%2px]"). 1121 arg(m_OpsFocusSettings->focusMosaicTileWidth->value()).arg(m_OpsFocusSettings->focusMosaicSpace->value()))) 1122 << " Suspend Guiding:" << ( m_OpsFocusSettings->focusSuspendGuiding->isChecked() ? "yes" : "no " ) 1123 << " Guide Settle:" << m_OpsFocusSettings->focusGuideSettleTime->value() 1124 << " Display Units:" << m_OpsFocusSettings->focusUnits->currentText() 1125 << " Adaptive Focus:" << ( m_OpsFocusSettings->focusAdaptive->isChecked() ? "yes" : "no" ) 1126 << " Min Move:" << m_OpsFocusSettings->focusAdaptiveMinMove->value() 1127 << " Adapt Start:" << ( m_OpsFocusSettings->focusAdaptStart->isChecked() ? "yes" : "no" ) 1128 << " Max Total Move:" << m_OpsFocusSettings->focusAdaptiveMaxMove->value(); 1129 qCInfo(KSTARS_EKOS_FOCUS) << "Process Tab." 1130 << " Detection:" << m_OpsFocusProcess->focusDetection->currentText() 1131 << " SEP Profile:" << m_OpsFocusProcess->focusSEPProfile->currentText() 1132 << " Algorithm:" << m_OpsFocusProcess->focusAlgorithm->currentText() 1133 << " Curve Fit:" << m_OpsFocusProcess->focusCurveFit->currentText() 1134 << " Measure:" << m_OpsFocusProcess->focusStarMeasure->currentText() 1135 << " PSF:" << m_OpsFocusProcess->focusStarPSF->currentText() 1136 << " Use Weights:" << ( m_OpsFocusProcess->focusUseWeights->isChecked() ? "yes" : "no" ) 1137 << " R2 Limit:" << m_OpsFocusProcess->focusR2Limit->value() 1138 << " Refine Curve Fit:" << ( m_OpsFocusProcess->focusRefineCurveFit->isChecked() ? "yes" : "no" ) 1139 << " Average Over:" << m_OpsFocusProcess->focusFramesCount->value() 1140 << " Num.of Rows:" << m_OpsFocusProcess->focusMultiRowAverage->value() 1141 << " Sigma:" << m_OpsFocusProcess->focusGaussianSigma->value() 1142 << " Threshold:" << m_OpsFocusProcess->focusThreshold->value() 1143 << " Kernel size:" << m_OpsFocusProcess->focusGaussianKernelSize->value() 1144 << " Tolerance:" << m_OpsFocusProcess->focusTolerance->value() 1145 << " Donut Buster:" << ( m_OpsFocusProcess->focusDonut->isChecked() ? "yes" : "no" ) 1146 << " Donut Time Dilation:" << m_OpsFocusProcess->focusTimeDilation->value(); 1147 qCInfo(KSTARS_EKOS_FOCUS) << "Mechanics Tab." 1148 << " Initial Step Size:" << m_OpsFocusMechanics->focusTicks->value() 1149 << " Out Step Multiple:" << m_OpsFocusMechanics->focusOutSteps->value() 1150 << " Number Steps:" << m_OpsFocusMechanics->focusNumSteps->value() 1151 << " Max Travel:" << m_OpsFocusMechanics->focusMaxTravel->value() 1152 << " Max Step Size:" << m_OpsFocusMechanics->focusMaxSingleStep->value() 1153 << " Driver Backlash:" << m_OpsFocusMechanics->focusBacklash->value() 1154 << " AF Overscan:" << m_OpsFocusMechanics->focusAFOverscan->value() 1155 << " Focuser Settle:" << m_OpsFocusMechanics->focusSettleTime->value() 1156 << " Walk:" << m_OpsFocusMechanics->focusWalk->currentText() 1157 << " Capture Timeout:" << m_OpsFocusMechanics->focusCaptureTimeout->value() 1158 << " Motion Timeout:" << m_OpsFocusMechanics->focusMotionTimeout->value(); 1159 qCInfo(KSTARS_EKOS_FOCUS) << "CFZ Tab." 1160 << " Algorithm:" << m_CFZUI->focusCFZAlgorithm->currentText() 1161 << " Tolerance:" << m_CFZUI->focusCFZTolerance->value() 1162 << " Tolerance (Ï„):" << m_CFZUI->focusCFZTau->value() 1163 << " Display:" << ( m_CFZUI->focusCFZDisplayVCurve->isChecked() ? "yes" : "no" ) 1164 << " Wavelength (λ):" << m_CFZUI->focusCFZWavelength->value() 1165 << " Aperture (A):" << m_CFZUI->focusCFZAperture->value() 1166 << " Focal Ratio (f):" << m_CFZUI->focusCFZFNumber->value() 1167 << " Step Size:" << m_CFZUI->focusCFZStepSize->value() 1168 << " FWHM (θ):" << m_CFZUI->focusCFZSeeing->value(); 1169 1170 if (currentTemperatureSourceElement) 1171 emit autofocusStarting(currentTemperatureSourceElement->value, filter()); 1172 else 1173 // dummy temperature will be ignored 1174 emit autofocusStarting(INVALID_VALUE, filter()); 1175 1176 if (m_OpsFocusSettings->focusAutoStarEnabled->isChecked()) 1177 appendLogText(i18n("Autofocus in progress...")); 1178 else if (!inAutoFocus) 1179 appendLogText(i18n("Please wait until image capture is complete...")); 1180 1181 // Only suspend when we have Off-Axis Guider 1182 // If the guide camera is operating on a different OTA 1183 // then no need to suspend. 1184 if (m_GuidingSuspended == false && m_OpsFocusSettings->focusSuspendGuiding->isChecked()) 1185 { 1186 m_GuidingSuspended = true; 1187 emit suspendGuiding(); 1188 } 1189 1190 //emit statusUpdated(true); 1191 setState(Ekos::FOCUS_PROGRESS); 1192 1193 KSNotification::event(QLatin1String("FocusStarted"), i18n("Autofocus operation started"), KSNotification::Focus); 1194 1195 // Used for all the focuser types. 1196 if (m_FocusAlgorithm == FOCUS_LINEAR || m_FocusAlgorithm == FOCUS_LINEAR1PASS) 1197 { 1198 QString AFfilter = filter(); 1199 const int position = adaptFocus->adaptStartPosition(currentPosition, AFfilter); 1200 1201 curveFitting.reset(new CurveFitting()); 1202 1203 FocusAlgorithmInterface::FocusParams params(curveFitting.get(), 1204 m_OpsFocusMechanics->focusMaxTravel->value(), m_OpsFocusMechanics->focusTicks->value(), position, absMotionMin, 1205 absMotionMax, 1206 MAXIMUM_ABS_ITERATIONS, m_OpsFocusProcess->focusTolerance->value() / 100.0, AFfilter, 1207 currentTemperatureSourceElement ? currentTemperatureSourceElement->value : INVALID_VALUE, 1208 m_OpsFocusMechanics->focusOutSteps->value(), m_OpsFocusMechanics->focusNumSteps->value(), 1209 m_FocusAlgorithm, m_OpsFocusMechanics->focusBacklash->value(), m_CurveFit, m_OpsFocusProcess->focusUseWeights->isChecked(), 1210 m_StarMeasure, m_StarPSF, m_OpsFocusProcess->focusRefineCurveFit->isChecked(), m_FocusWalk, 1211 m_OpsFocusProcess->focusDonut->isChecked(), m_OptDir, m_ScaleCalc); 1212 1213 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS) 1214 { 1215 // Curve fitting for stars and FWHM processing 1216 starFitting.reset(new CurveFitting()); 1217 focusFWHM.reset(new FocusFWHM(m_ScaleCalc)); 1218 focusFourierPower.reset(new FocusFourierPower(m_ScaleCalc)); 1219 // Donut Buster 1220 initDonutProcessing(); 1221 } 1222 1223 if (canAbsMove) 1224 initialFocuserAbsPosition = position; 1225 linearFocuser.reset(MakeLinearFocuser(params)); 1226 linearRequestedPosition = linearFocuser->initialPosition(); 1227 if (!changeFocus(linearRequestedPosition - currentPosition)) 1228 completeFocusProcedure(Ekos::FOCUS_ABORTED); 1229 1230 // Avoid the capture below. 1231 return; 1232 } 1233 capture(); 1234 } 1235 1236 // Initialise donut buster 1237 void Focus::initDonutProcessing() 1238 { 1239 if (m_OpsFocusProcess->focusDonut->isChecked()) 1240 m_donutOrigExposure = focusExposure->value(); 1241 } 1242 1243 // Reset donut buster 1244 void Focus::resetDonutProcessing() 1245 { 1246 // If donut busting variable focus exposures have been used, reset to starting value 1247 if (m_OpsFocusProcess->focusDonut->isChecked() && inAutoFocus) 1248 focusExposure->setValue(m_donutOrigExposure); 1249 } 1250 1251 int Focus::adjustLinearPosition(int position, int newPosition, int overscan, bool updateDir) 1252 { 1253 if (overscan > 0 && newPosition > position) 1254 { 1255 // If user has set an overscan value then use it 1256 int adjustment = overscan; 1257 qCDebug(KSTARS_EKOS_FOCUS) << QString("Linear: extending outward movement by overscan %1").arg(adjustment); 1258 1259 if (newPosition + adjustment > absMotionMax) 1260 adjustment = static_cast<int>(absMotionMax) - newPosition; 1261 1262 focuserAdditionalMovement = adjustment; 1263 focuserAdditionalMovementUpdateDir = updateDir; 1264 1265 return newPosition + adjustment; 1266 } 1267 return newPosition; 1268 } 1269 1270 void Focus::checkStopFocus(bool abort) 1271 { 1272 // if abort, avoid try to restart 1273 if (abort) 1274 resetFocusIteration = MAXIMUM_RESET_ITERATIONS + 1; 1275 1276 if (captureInProgress && inAutoFocus == false && inFocusLoop == false) 1277 { 1278 captureB->setEnabled(true); 1279 stopFocusB->setEnabled(false); 1280 1281 appendLogText(i18n("Capture aborted.")); 1282 } 1283 1284 if (hfrInProgress) 1285 { 1286 stopFocusB->setEnabled(false); 1287 appendLogText(i18n("Detection in progress, please wait.")); 1288 QTimer::singleShot(1000, this, [ &, abort]() 1289 { 1290 checkStopFocus(abort); 1291 }); 1292 } 1293 else 1294 { 1295 completeFocusProcedure(abort ? Ekos::FOCUS_ABORTED : Ekos::FOCUS_FAILED); 1296 } 1297 } 1298 1299 void Focus::meridianFlipStarted() 1300 { 1301 // if focusing is not running, do nothing 1302 if (state() == FOCUS_IDLE || state() == FOCUS_COMPLETE || state() == FOCUS_FAILED || state() == FOCUS_ABORTED) 1303 return; 1304 1305 // store current focus iteration counter since abort() sets it to the maximal value to avoid restarting 1306 int old = resetFocusIteration; 1307 // abort focusing 1308 abort(); 1309 // restore iteration counter 1310 resetFocusIteration = old; 1311 } 1312 1313 void Focus::abort() 1314 { 1315 // No need to "abort" if not already in progress. 1316 if (state() <= FOCUS_ABORTED) 1317 return; 1318 1319 bool focusLoop = inFocusLoop; 1320 checkStopFocus(true); 1321 appendLogText(i18n("Autofocus aborted.")); 1322 if (!focusLoop) 1323 // For looping leave focuser where it is 1324 // Otherwise try to shift the focuser back to its initial position 1325 resetFocuser(); 1326 } 1327 1328 void Focus::stop(Ekos::FocusState completionState) 1329 { 1330 qCDebug(KSTARS_EKOS_FOCUS) << "Stopping Focus"; 1331 1332 captureTimeout.stop(); 1333 m_FocusMotionTimer.stop(); 1334 m_FocusMotionTimerCounter = 0; 1335 m_FocuserReconnectCounter = 0; 1336 1337 opticalTrainCombo->setEnabled(true); 1338 resetDonutProcessing(); 1339 inAutoFocus = false; 1340 inAdjustFocus = false; 1341 adaptFocus->setInAdaptiveFocus(false); 1342 inBuildOffsets = false; 1343 focuserAdditionalMovement = 0; 1344 focuserAdditionalMovementUpdateDir = true; 1345 inFocusLoop = false; 1346 captureInProgress = false; 1347 isVShapeSolution = false; 1348 captureFailureCounter = 0; 1349 minimumRequiredHFR = INVALID_STAR_MEASURE; 1350 noStarCount = 0; 1351 starMeasureFrames.clear(); 1352 m_abInsOn = false; 1353 AFStartRetries = 0; 1354 1355 // Check if CCD was not removed due to crash or other reasons. 1356 if (m_Camera) 1357 { 1358 disconnect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Focus::processData); 1359 disconnect(m_Camera, &ISD::Camera::error, this, &Ekos::Focus::processCaptureError); 1360 1361 if (rememberUploadMode != m_Camera->getUploadMode()) 1362 m_Camera->setUploadMode(rememberUploadMode); 1363 1364 // Remember to reset fast exposure if it was enabled before. 1365 if (m_RememberCameraFastExposure) 1366 { 1367 m_RememberCameraFastExposure = false; 1368 m_Camera->setFastExposureEnabled(true); 1369 } 1370 1371 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 1372 targetChip->abortExposure(); 1373 } 1374 1375 resetButtons(); 1376 1377 absIterations = 0; 1378 HFRInc = 0; 1379 reverseDir = false; 1380 1381 if (m_GuidingSuspended) 1382 { 1383 emit resumeGuiding(); 1384 m_GuidingSuspended = false; 1385 } 1386 1387 if (completionState == Ekos::FOCUS_ABORTED || completionState == Ekos::FOCUS_FAILED) 1388 setState(completionState); 1389 } 1390 1391 void Focus::capture(double settleTime) 1392 { 1393 // If capturing should be delayed by a given settling time, we start the capture timer. 1394 // This is intentionally designed re-entrant, i.e. multiple calls with settle time > 0 takes the last delay 1395 if (settleTime > 0 && captureInProgress == false) 1396 { 1397 captureTimer.start(static_cast<int>(settleTime * 1000)); 1398 return; 1399 } 1400 1401 if (captureInProgress) 1402 { 1403 qCWarning(KSTARS_EKOS_FOCUS) << "Capture called while already in progress. Capture is ignored."; 1404 return; 1405 } 1406 1407 if (m_Camera == nullptr) 1408 { 1409 appendLogText(i18n("Error: No Camera detected.")); 1410 checkStopFocus(true); 1411 return; 1412 } 1413 1414 if (m_Camera->isConnected() == false) 1415 { 1416 appendLogText(i18n("Error: Lost connection to Camera.")); 1417 checkStopFocus(true); 1418 return; 1419 } 1420 1421 // reset timeout for receiving an image 1422 captureTimeout.stop(); 1423 // reset timeout for focus star selection 1424 waitStarSelectTimer.stop(); 1425 1426 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 1427 1428 if (m_Camera->isBLOBEnabled() == false) 1429 { 1430 m_Camera->setBLOBEnabled(true); 1431 } 1432 1433 if (focusFilter->currentIndex() != -1) 1434 { 1435 if (m_FilterWheel == nullptr) 1436 { 1437 appendLogText(i18n("Error: No Filter Wheel detected.")); 1438 checkStopFocus(true); 1439 return; 1440 } 1441 if (m_FilterWheel->isConnected() == false) 1442 { 1443 appendLogText(i18n("Error: Lost connection to Filter Wheel.")); 1444 checkStopFocus(true); 1445 return; 1446 } 1447 1448 // In "regular" autofocus mode if the chosen filter has an associated lock filter then we need 1449 // to swap to the lock filter before running autofocus. However, AF is also called from build 1450 // offsets to run AF. In this case we need to ignore the lock filter and use the chosen filter 1451 int targetPosition = focusFilter->currentIndex() + 1; 1452 if (!inBuildOffsets) 1453 { 1454 QString lockedFilter = m_FilterManager->getFilterLock(filter()); 1455 1456 // We change filter if: 1457 // 1. Target position is not equal to current position. 1458 // 2. Locked filter of CURRENT filter is a different filter. 1459 if (lockedFilter != "--" && lockedFilter != filter()) 1460 { 1461 int lockedFilterIndex = focusFilter->findText(lockedFilter); 1462 if (lockedFilterIndex >= 0) 1463 { 1464 // Go back to this filter once we are done 1465 fallbackFilterPending = true; 1466 fallbackFilterPosition = targetPosition; 1467 targetPosition = lockedFilterIndex + 1; 1468 } 1469 } 1470 } 1471 1472 filterPositionPending = (targetPosition != currentFilterPosition); 1473 if (filterPositionPending) 1474 { 1475 // Change the filter. When done this will signal to update the focusFilter combo 1476 // Apply filter change policy if in Autofocus; otherwise apply change and offsets 1477 // Note that Autofocus doesn't need the change policy as Adapt Start Pos takes care of this 1478 FilterManager::FilterPolicy policy = (inAutoFocus) ? 1479 static_cast<FilterManager::FilterPolicy>(FilterManager::CHANGE_POLICY) : 1480 static_cast<FilterManager::FilterPolicy>(FilterManager::CHANGE_POLICY | FilterManager::OFFSET_POLICY); 1481 m_FilterManager->setFilterPosition(targetPosition, policy); 1482 return; 1483 } 1484 else if (targetPosition != focusFilter->currentIndex() + 1) 1485 focusFilter->setCurrentIndex(targetPosition - 1); 1486 } 1487 1488 m_FocusView->setProperty("suspended", m_OpsFocusSettings->useFocusDarkFrame->isChecked()); 1489 prepareCapture(targetChip); 1490 1491 connect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Focus::processData); 1492 connect(m_Camera, &ISD::Camera::error, this, &Ekos::Focus::processCaptureError); 1493 1494 if (frameSettings.contains(targetChip)) 1495 { 1496 QVariantMap settings = frameSettings[targetChip]; 1497 targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(), 1498 settings["h"].toInt()); 1499 settings["binx"] = (focusBinning->currentIndex() + 1); 1500 settings["biny"] = (focusBinning->currentIndex() + 1); 1501 frameSettings[targetChip] = settings; 1502 } 1503 1504 captureInProgress = true; 1505 if (state() != FOCUS_PROGRESS) 1506 setState(FOCUS_PROGRESS); 1507 1508 m_FocusView->setBaseSize(focusingWidget->size()); 1509 1510 if (targetChip->capture(focusExposure->value())) 1511 { 1512 // Timeout is exposure duration + timeout threshold in seconds 1513 //long const timeout = lround(ceil(focusExposure->value() * 1000)) + FOCUS_TIMEOUT_THRESHOLD; 1514 captureTimeout.start( (focusExposure->value() + m_OpsFocusMechanics->focusCaptureTimeout->value()) * 1000); 1515 1516 if (inFocusLoop == false) 1517 appendLogText(i18n("Capturing image...")); 1518 1519 resetButtons(); 1520 } 1521 else if (inAutoFocus) 1522 { 1523 completeFocusProcedure(Ekos::FOCUS_ABORTED); 1524 } 1525 } 1526 1527 void Focus::prepareCapture(ISD::CameraChip *targetChip) 1528 { 1529 if (m_Camera->getUploadMode() == ISD::Camera::UPLOAD_LOCAL) 1530 { 1531 rememberUploadMode = ISD::Camera::UPLOAD_LOCAL; 1532 m_Camera->setUploadMode(ISD::Camera::UPLOAD_CLIENT); 1533 } 1534 1535 // We cannot use fast exposure in focus. 1536 if (m_Camera->isFastExposureEnabled()) 1537 { 1538 m_RememberCameraFastExposure = true; 1539 m_Camera->setFastExposureEnabled(false); 1540 } 1541 1542 m_Camera->setEncodingFormat("FITS"); 1543 targetChip->setBatchMode(false); 1544 targetChip->setBinning((focusBinning->currentIndex() + 1), (focusBinning->currentIndex() + 1)); 1545 targetChip->setCaptureMode(FITS_FOCUS); 1546 targetChip->setFrameType(FRAME_LIGHT); 1547 targetChip->setCaptureFilter(FITS_NONE); 1548 1549 if (isFocusISOEnabled() && focusISO->currentIndex() != -1 && 1550 targetChip->getISOIndex() != focusISO->currentIndex()) 1551 targetChip->setISOIndex(focusISO->currentIndex()); 1552 1553 if (isFocusGainEnabled() && focusGain->value() != GainSpinSpecialValue) 1554 m_Camera->setGain(focusGain->value()); 1555 } 1556 1557 bool Focus::focusIn(int ms) 1558 { 1559 if (currentPosition == absMotionMin) 1560 { 1561 appendLogText(i18n("At minimum focus position %1...", absMotionMin)); 1562 return false; 1563 } 1564 focusInB->setEnabled(false); 1565 focusOutB->setEnabled(false); 1566 startGotoB->setEnabled(false); 1567 if (ms <= 0) 1568 ms = m_OpsFocusMechanics->focusTicks->value(); 1569 if (currentPosition - ms <= absMotionMin) 1570 { 1571 ms = currentPosition - absMotionMin; 1572 appendLogText(i18n("Moving to minimum focus position %1...", absMotionMin)); 1573 } 1574 return changeFocus(-ms); 1575 } 1576 1577 bool Focus::focusOut(int ms) 1578 { 1579 if (currentPosition == absMotionMax) 1580 { 1581 appendLogText(i18n("At maximum focus position %1...", absMotionMax)); 1582 return false; 1583 } 1584 focusInB->setEnabled(false); 1585 focusOutB->setEnabled(false); 1586 startGotoB->setEnabled(false); 1587 if (ms <= 0) 1588 ms = m_OpsFocusMechanics->focusTicks->value(); 1589 if (currentPosition + ms >= absMotionMax) 1590 { 1591 ms = absMotionMax - currentPosition; 1592 appendLogText(i18n("Moving to maximum focus position %1...", absMotionMax)); 1593 } 1594 return changeFocus(ms); 1595 } 1596 1597 // Routine to manage focus movements. All moves are now subject to overscan 1598 // + amount indicates a movement out; - amount indictaes a movement in 1599 bool Focus::changeFocus(int amount, bool updateDir) 1600 { 1601 // Retry capture if we stay at the same position 1602 // Allow 1 step of tolerance--Have seen stalls with amount==1. 1603 if (inAutoFocus && abs(amount) <= 1) 1604 { 1605 capture(m_OpsFocusMechanics->focusSettleTime->value()); 1606 return true; 1607 } 1608 1609 if (m_Focuser == nullptr) 1610 { 1611 appendLogText(i18n("Error: No Focuser detected.")); 1612 checkStopFocus(true); 1613 return false; 1614 } 1615 1616 if (m_Focuser->isConnected() == false) 1617 { 1618 appendLogText(i18n("Error: Lost connection to Focuser.")); 1619 checkStopFocus(true); 1620 return false; 1621 } 1622 1623 const int newPosition = adjustLinearPosition(currentPosition, currentPosition + amount, 1624 m_OpsFocusMechanics->focusAFOverscan->value(), 1625 updateDir); 1626 if (newPosition == currentPosition) 1627 return true; 1628 1629 const int newAmount = newPosition - currentPosition; 1630 const int absNewAmount = abs(newAmount); 1631 const bool focusingOut = newAmount > 0; 1632 const QString dirStr = focusingOut ? i18n("outward") : i18n("inward"); 1633 // update the m_LastFocusDirection unless in Iterative / Polynomial which controls this variable itself. 1634 if (updateDir) 1635 m_LastFocusDirection = (focusingOut) ? FOCUS_OUT : FOCUS_IN; 1636 1637 if (focusingOut) 1638 m_Focuser->focusOut(); 1639 else 1640 m_Focuser->focusIn(); 1641 1642 // Keep track of motion in case it gets stuck. 1643 m_FocusMotionTimer.start(); 1644 1645 if (canAbsMove) 1646 { 1647 m_LastFocusSteps = newPosition; 1648 m_Focuser->moveAbs(newPosition); 1649 appendLogText(i18n("Focusing %2 by %1 steps...", abs(absNewAmount), dirStr)); 1650 } 1651 else if (canRelMove) 1652 { 1653 m_LastFocusSteps = absNewAmount; 1654 m_Focuser->moveRel(absNewAmount); 1655 appendLogText(i18np("Focusing %2 by %1 step...", "Focusing %2 by %1 steps...", absNewAmount, dirStr)); 1656 } 1657 else 1658 { 1659 m_LastFocusSteps = absNewAmount; 1660 m_Focuser->moveByTimer(absNewAmount); 1661 appendLogText(i18n("Focusing %2 by %1 ms...", absNewAmount, dirStr)); 1662 } 1663 1664 return true; 1665 } 1666 1667 void Focus::handleFocusMotionTimeout() 1668 { 1669 // handleFocusMotionTimeout is called when the focus motion timer times out. This is only 1670 // relevant to AutoFocus runs which could be unattended so make an attempt to recover. Other 1671 // types of focuser movement issues are logged and the user is expected to take action. 1672 // If set correctly, say 30 secs, this should only occur when there are comms issues 1673 // with the focuser. 1674 // Step 1: Just retry the last requested move. If the issue is a transient one-off issue 1675 // this should resolve it. Try this twice. 1676 // Step 2: Step 1 didn't resolve the issue so try to restart the focus driver. In this case 1677 // abort the inflight autofocus run and let it retry from the start. It will try to 1678 // return the focuser to the start (last successful autofocus) position. Try twice. 1679 // Step 3: Step 2 didn't work either because the driver restart wasn't successful or we are 1680 // still getting timeouts. In this case just stop the autoFocus process and return 1681 // control to either the Scheduer or GUI. Note that here we cannot reset the focuser 1682 // to the previous good position so if the focuser miraculously fixes itself the 1683 // next autofocus run won't start from the best place. 1684 1685 if (!inAutoFocus) 1686 { 1687 qCDebug(KSTARS_EKOS_FOCUS) << "handleFocusMotionTimeout() called while not in AutoFocus"; 1688 return; 1689 } 1690 1691 m_FocusMotionTimerCounter++; 1692 1693 if (m_FocusMotionTimerCounter > 4) 1694 { 1695 // We've tried everything but still get timeouts so abort... 1696 appendLogText(i18n("Focuser is still timing out. Aborting...")); 1697 stop(Ekos::FOCUS_ABORTED); 1698 return; 1699 } 1700 else if (m_FocusMotionTimerCounter > 2) 1701 { 1702 QString focuser = m_Focuser->getDeviceName(); 1703 appendLogText(i18n("Focus motion timed out (%1). Restarting focus driver %2", m_FocusMotionTimerCounter, focuser)); 1704 emit focuserTimedout(focuser); 1705 1706 QTimer::singleShot(5000, this, [ &, focuser]() 1707 { 1708 Focus::reconnectFocuser(focuser); 1709 }); 1710 return; 1711 } 1712 1713 if (!changeFocus(m_LastFocusSteps - currentPosition)) 1714 appendLogText(i18n("Focus motion timed out (%1). Focusing to %2 steps...", m_FocusMotionTimerCounter, m_LastFocusSteps)); 1715 } 1716 1717 void Focus::selectImageMask() 1718 { 1719 const bool useFullField = m_OpsFocusSettings->focusUseFullField->isChecked(); 1720 ImageMaskType masktype; 1721 if (m_OpsFocusSettings->focusRingMaskRB->isChecked()) 1722 masktype = FOCUS_MASK_RING; 1723 else if (m_OpsFocusSettings->focusMosaicMaskRB->isChecked()) 1724 masktype = FOCUS_MASK_MOSAIC; 1725 else 1726 masktype = FOCUS_MASK_NONE; 1727 1728 // mask selection only enabled if full field should be used for focusing 1729 m_OpsFocusSettings->focusRingMaskRB->setEnabled(useFullField); 1730 m_OpsFocusSettings->focusMosaicMaskRB->setEnabled(useFullField); 1731 // ring mask 1732 m_OpsFocusSettings->focusFullFieldInnerRadius->setEnabled(useFullField && masktype == FOCUS_MASK_RING); 1733 m_OpsFocusSettings->focusFullFieldOuterRadius->setEnabled(useFullField && masktype == FOCUS_MASK_RING); 1734 // aberration inspector mosaic 1735 m_OpsFocusSettings->focusMosaicTileWidth->setEnabled(useFullField && masktype == FOCUS_MASK_MOSAIC); 1736 m_OpsFocusSettings->focusSpacerLabel->setEnabled(useFullField && masktype == FOCUS_MASK_MOSAIC); 1737 m_OpsFocusSettings->focusMosaicSpace->setEnabled(useFullField && masktype == FOCUS_MASK_MOSAIC); 1738 1739 // create the type specific mask 1740 if (masktype == FOCUS_MASK_RING) 1741 m_FocusView->setImageMask(new ImageRingMask(m_OpsFocusSettings->focusFullFieldInnerRadius->value() / 100.0, 1742 m_OpsFocusSettings->focusFullFieldOuterRadius->value() / 100.0)); 1743 else if (masktype == FOCUS_MASK_MOSAIC) 1744 m_FocusView->setImageMask(new ImageMosaicMask(m_OpsFocusSettings->focusMosaicTileWidth->value(), 1745 m_OpsFocusSettings->focusMosaicSpace->value())); 1746 else 1747 m_FocusView->setImageMask(nullptr); 1748 1749 checkMosaicMaskLimits(); 1750 m_currentImageMask = masktype; 1751 startAbInsB->setEnabled(canAbInsStart()); 1752 } 1753 1754 void Focus::reconnectFocuser(const QString &focuser) 1755 { 1756 m_FocuserReconnectCounter++; 1757 1758 if (m_Focuser && m_Focuser->getDeviceName() == focuser) 1759 { 1760 appendLogText(i18n("Attempting to reconnect focuser: %1", focuser)); 1761 refreshOpticalTrain(); 1762 completeFocusProcedure(Ekos::FOCUS_ABORTED); 1763 return; 1764 } 1765 1766 if (m_FocuserReconnectCounter > 12) 1767 { 1768 // We've waited a minute and can't reconnect the focuser so abort... 1769 appendLogText(i18n("Cannot reconnect focuser: %1. Aborting...", focuser)); 1770 stop(Ekos::FOCUS_ABORTED); 1771 return; 1772 } 1773 1774 QTimer::singleShot(5000, this, [ &, focuser]() 1775 { 1776 reconnectFocuser(focuser); 1777 }); 1778 } 1779 1780 void Focus::processData(const QSharedPointer<FITSData> &data) 1781 { 1782 // Ignore guide head if there is any. 1783 if (data->property("chip").toInt() == ISD::CameraChip::GUIDE_CCD) 1784 return; 1785 1786 if (data) 1787 { 1788 m_FocusView->loadData(data); 1789 m_ImageData = data; 1790 } 1791 else 1792 m_ImageData.reset(); 1793 1794 captureTimeout.stop(); 1795 captureTimeoutCounter = 0; 1796 1797 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 1798 disconnect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Focus::processData); 1799 disconnect(m_Camera, &ISD::Camera::error, this, &Ekos::Focus::processCaptureError); 1800 1801 if (m_ImageData && m_OpsFocusSettings->useFocusDarkFrame->isChecked()) 1802 { 1803 QVariantMap settings = frameSettings[targetChip]; 1804 uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt(); 1805 uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt(); 1806 1807 m_DarkProcessor->denoise(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()), 1808 targetChip, m_ImageData, focusExposure->value(), offsetX, offsetY); 1809 return; 1810 } 1811 1812 setCaptureComplete(); 1813 resetButtons(); 1814 } 1815 1816 void Focus::starDetectionFinished() 1817 { 1818 appendLogText(i18n("Detection complete.")); 1819 1820 // Beware as this HFR value is then treated specifically by the graph renderer 1821 double hfr = INVALID_STAR_MEASURE; 1822 1823 if (m_StarFinderWatcher.result() == false) 1824 { 1825 qCWarning(KSTARS_EKOS_FOCUS) << "Failed to extract any stars."; 1826 } 1827 else 1828 { 1829 if (m_OpsFocusSettings->focusUseFullField->isChecked()) 1830 { 1831 m_FocusView->filterStars(); 1832 1833 // Get the average HFR of the whole frame 1834 hfr = m_ImageData->getHFR(m_StarMeasure == FOCUS_STAR_HFR_ADJ ? HFR_ADJ_AVERAGE : HFR_AVERAGE); 1835 } 1836 else 1837 { 1838 m_FocusView->setTrackingBoxEnabled(true); 1839 1840 // JM 2020-10-08: Try to get first the same HFR star already selected before 1841 // so that it doesn't keep jumping around 1842 1843 if (starCenter.isNull() == false) 1844 hfr = m_ImageData->getHFR(starCenter.x(), starCenter.y()); 1845 1846 // If not found, then get the MAX or MEDIAN depending on the selected algorithm. 1847 if (hfr < 0) 1848 hfr = m_ImageData->getHFR(m_FocusDetection == ALGORITHM_SEP ? HFR_HIGH : HFR_MAX); 1849 } 1850 } 1851 // JEE Frig 1852 if (0) 1853 { 1854 if (hfr > 3) 1855 hfr = 3 - (hfr - 3); 1856 if (hfr < 0.5) 1857 hfr = INVALID_STAR_MEASURE; 1858 } 1859 1860 hfrInProgress = false; 1861 currentHFR = hfr; 1862 currentNumStars = m_ImageData->getDetectedStars(); 1863 1864 // Setup with measure we are using (HFR, FWHM, etc) 1865 if (!inAutoFocus) 1866 currentMeasure = currentHFR; 1867 else 1868 { 1869 if (m_StarMeasure == FOCUS_STAR_NUM_STARS) 1870 { 1871 currentMeasure = currentNumStars; 1872 currentWeight = 1.0; 1873 } 1874 else if (m_StarMeasure == FOCUS_STAR_FWHM) 1875 { 1876 getFWHM(m_ImageData->getStarCenters(), ¤tFWHM, ¤tWeight); 1877 currentMeasure = currentFWHM; 1878 } 1879 else if (m_StarMeasure == FOCUS_STAR_FOURIER_POWER) 1880 { 1881 getFourierPower(¤tFourierPower, ¤tWeight); 1882 currentMeasure = currentFourierPower; 1883 } 1884 else 1885 { 1886 currentMeasure = currentHFR; 1887 QList<Edge*> stars = m_ImageData->getStarCenters(); 1888 std::vector<double> hfrs(stars.size()); 1889 std::transform(stars.constBegin(), stars.constEnd(), hfrs.begin(), [](Edge * edge) 1890 { 1891 return edge->HFR; 1892 }); 1893 currentWeight = calculateStarWeight(m_OpsFocusProcess->focusUseWeights->isChecked(), hfrs); 1894 } 1895 } 1896 setCurrentMeasure(); 1897 } 1898 1899 // The image has been processed for star centroids and HFRs so now process it for star FWHMs 1900 void Focus::getFWHM(const QList<Edge *> &stars, double *FWHM, double *weight) 1901 { 1902 *FWHM = INVALID_STAR_MEASURE; 1903 *weight = 0.0; 1904 1905 auto imageBuffer = m_ImageData->getImageBuffer(); 1906 1907 switch (m_ImageData->getStatistics().dataType) 1908 { 1909 case TBYTE: 1910 focusFWHM->processFWHM(reinterpret_cast<uint8_t const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight); 1911 break; 1912 1913 case TSHORT: // Don't think short is used as its recorded as unsigned short 1914 focusFWHM->processFWHM(reinterpret_cast<short const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight); 1915 break; 1916 1917 case TUSHORT: 1918 focusFWHM->processFWHM(reinterpret_cast<unsigned short const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, 1919 weight); 1920 break; 1921 1922 case TLONG: // Don't think long is used as its recorded as unsigned long 1923 focusFWHM->processFWHM(reinterpret_cast<long const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight); 1924 break; 1925 1926 case TULONG: 1927 focusFWHM->processFWHM(reinterpret_cast<unsigned long const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight); 1928 break; 1929 1930 case TFLOAT: 1931 focusFWHM->processFWHM(reinterpret_cast<float const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight); 1932 break; 1933 1934 case TLONGLONG: 1935 focusFWHM->processFWHM(reinterpret_cast<long long const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight); 1936 break; 1937 1938 case TDOUBLE: 1939 focusFWHM->processFWHM(reinterpret_cast<double const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight); 1940 break; 1941 1942 default: 1943 qCDebug(KSTARS_EKOS_FOCUS) << "Unknown image buffer datatype " << m_ImageData->getStatistics().dataType << 1944 " Cannot calc FWHM"; 1945 break; 1946 } 1947 } 1948 1949 // The image has been processed for star centroids and HFRs so now process it for star FWHMs 1950 void Focus::getFourierPower(double *fourierPower, double *weight, const int mosaicTile) 1951 { 1952 *fourierPower = INVALID_STAR_MEASURE; 1953 *weight = 1.0; 1954 1955 auto imageBuffer = m_ImageData->getImageBuffer(); 1956 1957 switch (m_ImageData->getStatistics().dataType) 1958 { 1959 case TBYTE: 1960 focusFourierPower->processFourierPower(reinterpret_cast<uint8_t const *>(imageBuffer), m_ImageData, 1961 m_FocusView->imageMask(), mosaicTile, fourierPower, weight); 1962 break; 1963 1964 case TSHORT: // Don't think short is used as its recorded as unsigned short 1965 focusFourierPower->processFourierPower(reinterpret_cast<short const *>(imageBuffer), m_ImageData, 1966 m_FocusView->imageMask(), mosaicTile, fourierPower, weight); 1967 break; 1968 1969 case TUSHORT: 1970 focusFourierPower->processFourierPower(reinterpret_cast<unsigned short const *>(imageBuffer), m_ImageData, 1971 m_FocusView->imageMask(), mosaicTile, fourierPower, weight); 1972 break; 1973 1974 case TLONG: // Don't think long is used as its recorded as unsigned long 1975 focusFourierPower->processFourierPower(reinterpret_cast<long const *>(imageBuffer), m_ImageData, 1976 m_FocusView->imageMask(), mosaicTile, fourierPower, weight); 1977 break; 1978 1979 case TULONG: 1980 focusFourierPower->processFourierPower(reinterpret_cast<unsigned long const *>(imageBuffer), m_ImageData, 1981 m_FocusView->imageMask(), mosaicTile, fourierPower, weight); 1982 break; 1983 1984 case TFLOAT: 1985 focusFourierPower->processFourierPower(reinterpret_cast<float const *>(imageBuffer), m_ImageData, 1986 m_FocusView->imageMask(), mosaicTile, fourierPower, weight); 1987 break; 1988 1989 case TLONGLONG: 1990 focusFourierPower->processFourierPower(reinterpret_cast<long long const *>(imageBuffer), m_ImageData, 1991 m_FocusView->imageMask(), mosaicTile, fourierPower, weight); 1992 break; 1993 1994 case TDOUBLE: 1995 focusFourierPower->processFourierPower(reinterpret_cast<double const *>(imageBuffer), m_ImageData, 1996 m_FocusView->imageMask(), mosaicTile, fourierPower, weight); 1997 break; 1998 1999 default: 2000 qCDebug(KSTARS_EKOS_FOCUS) << "Unknown image buffer datatype " << m_ImageData->getStatistics().dataType << 2001 " Cannot calc Fourier Power"; 2002 break; 2003 } 2004 } 2005 2006 double Focus::calculateStarWeight(const bool useWeights, const std::vector<double> values) 2007 { 2008 if (!useWeights || values.size() <= 0) 2009 // If we can't calculate weight set to 1 = equal weights 2010 // Also if we are using numStars as the measure - don't use weights 2011 return 1.0f; 2012 2013 return Mathematics::RobustStatistics::ComputeWeight(m_ScaleCalc, values); 2014 } 2015 2016 void Focus::analyzeSources() 2017 { 2018 appendLogText(i18n("Detecting sources...")); 2019 hfrInProgress = true; 2020 2021 QVariantMap extractionSettings; 2022 extractionSettings["optionsProfileIndex"] = m_OpsFocusProcess->focusSEPProfile->currentIndex(); 2023 extractionSettings["optionsProfileGroup"] = static_cast<int>(Ekos::FocusProfiles); 2024 m_ImageData->setSourceExtractorSettings(extractionSettings); 2025 // When we're using FULL field view, we always use either CENTROID algorithm which is the default 2026 // standard algorithm in KStars, or SEP. The other algorithms are too inefficient to run on full frames and require 2027 // a bounding box for them to be effective in near real-time application. 2028 if (m_OpsFocusSettings->focusUseFullField->isChecked()) 2029 { 2030 m_FocusView->setTrackingBoxEnabled(false); 2031 2032 if (m_FocusDetection != ALGORITHM_CENTROID && m_FocusDetection != ALGORITHM_SEP) 2033 m_StarFinderWatcher.setFuture(m_ImageData->findStars(ALGORITHM_CENTROID)); 2034 else 2035 m_StarFinderWatcher.setFuture(m_ImageData->findStars(m_FocusDetection)); 2036 } 2037 else 2038 { 2039 QRect searchBox = m_FocusView->isTrackingBoxEnabled() ? m_FocusView->getTrackingBox() : QRect(); 2040 // If star is already selected then use whatever algorithm currently selected. 2041 if (starSelected) 2042 { 2043 m_StarFinderWatcher.setFuture(m_ImageData->findStars(m_FocusDetection, searchBox)); 2044 } 2045 else 2046 { 2047 // Disable tracking box 2048 m_FocusView->setTrackingBoxEnabled(false); 2049 2050 // If algorithm is set something other than Centeroid or SEP, then force Centroid 2051 // Since it is the most reliable detector when nothing was selected before. 2052 if (m_FocusDetection != ALGORITHM_CENTROID && m_FocusDetection != ALGORITHM_SEP) 2053 m_StarFinderWatcher.setFuture(m_ImageData->findStars(ALGORITHM_CENTROID)); 2054 else 2055 // Otherwise, continue to find use using the selected algorithm 2056 m_StarFinderWatcher.setFuture(m_ImageData->findStars(m_FocusDetection, searchBox)); 2057 } 2058 } 2059 } 2060 2061 bool Focus::appendMeasure(double newMeasure) 2062 { 2063 // Add new star measure (e.g. HFR, FWHM, etc) to existing values, even if invalid 2064 starMeasureFrames.append(newMeasure); 2065 2066 // Prepare a work vector with valid HFR values 2067 QVector <double> samples(starMeasureFrames); 2068 samples.erase(std::remove_if(samples.begin(), samples.end(), [](const double measure) 2069 { 2070 return measure == INVALID_STAR_MEASURE; 2071 }), samples.end()); 2072 2073 // Consolidate the average star measure. Sigma clips outliers and averages remainder. 2074 if (!samples.isEmpty()) 2075 { 2076 currentMeasure = Mathematics::RobustStatistics::ComputeLocation( 2077 Mathematics::RobustStatistics::LOCATION_SIGMACLIPPING, std::vector<double>(samples.begin(), samples.end())); 2078 2079 switch(m_StarMeasure) 2080 { 2081 case FOCUS_STAR_HFR: 2082 case FOCUS_STAR_HFR_ADJ: 2083 currentHFR = currentMeasure; 2084 break; 2085 case FOCUS_STAR_FWHM: 2086 currentFWHM = currentMeasure; 2087 break; 2088 case FOCUS_STAR_NUM_STARS: 2089 currentNumStars = currentMeasure; 2090 break; 2091 case FOCUS_STAR_FOURIER_POWER: 2092 currentFourierPower = currentMeasure; 2093 break; 2094 default: 2095 break; 2096 } 2097 } 2098 2099 // Save the focus frame 2100 saveFocusFrame(); 2101 // Return whether we need more frame based on user requirement 2102 return starMeasureFrames.count() < m_OpsFocusProcess->focusFramesCount->value(); 2103 } 2104 2105 void Focus::settle(const FocusState completionState, const bool autoFocusUsed, const bool buildOffsetsUsed) 2106 { 2107 // TODO: check if the completion state can be emitted in all cases (sterne-jaeger 2023-09-12) 2108 m_state = completionState; 2109 if (completionState == Ekos::FOCUS_COMPLETE) 2110 { 2111 if (autoFocusUsed && fallbackFilterPending) 2112 { 2113 // Save the solution details for the filter used for the AF run before changing 2114 // filers to the fallback filter. Details for the fallback filter will be saved once that 2115 // filter has been processed and the offset applied 2116 m_FilterManager->setFilterAbsoluteFocusDetails(focusFilter->currentIndex(), currentPosition, 2117 m_LastSourceAutofocusTemperature, m_LastSourceAutofocusAlt); 2118 } 2119 2120 if (autoFocusUsed) 2121 { 2122 // Prepare the message for Analyze 2123 const int size = plot_position.size(); 2124 QString analysis_results = ""; 2125 2126 for (int i = 0; i < size; ++i) 2127 { 2128 analysis_results.append(QString("%1%2|%3") 2129 .arg(i == 0 ? "" : "|" ) 2130 .arg(QString::number(plot_position[i], 'f', 0)) 2131 .arg(QString::number(plot_value[i], 'f', 3))); 2132 } 2133 2134 KSNotification::event(QLatin1String("FocusSuccessful"), i18n("Autofocus operation completed successfully"), 2135 KSNotification::Focus); 2136 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS && curveFitting != nullptr) 2137 emit autofocusComplete(filter(), analysis_results, curveFitting->serialize(), linearFocuser->getTextStatus(R2)); 2138 else 2139 emit autofocusComplete(filter(), analysis_results); 2140 } 2141 } 2142 else 2143 { 2144 if (autoFocusUsed) 2145 { 2146 KSNotification::event(QLatin1String("FocusFailed"), i18n("Autofocus operation failed"), 2147 KSNotification::Focus, KSNotification::Alert); 2148 emit autofocusAborted(filter(), ""); 2149 } 2150 } 2151 2152 qCDebug(KSTARS_EKOS_FOCUS) << "Settled. State:" << Ekos::getFocusStatusString(state()); 2153 2154 // Delay state notification if we have a locked filter pending return to original filter 2155 if (fallbackFilterPending) 2156 { 2157 FilterManager::FilterPolicy policy = (autoFocusUsed) ? 2158 static_cast<FilterManager::FilterPolicy>(FilterManager::CHANGE_POLICY) : 2159 static_cast<FilterManager::FilterPolicy>(FilterManager::CHANGE_POLICY | FilterManager::OFFSET_POLICY); 2160 m_FilterManager->setFilterPosition(fallbackFilterPosition, policy); 2161 } 2162 else 2163 emit newStatus(state()); 2164 2165 if (autoFocusUsed && buildOffsetsUsed) 2166 // If we are building filter offsets signal AF run is complete 2167 m_FilterManager->autoFocusComplete(state(), currentPosition, m_LastSourceAutofocusTemperature, m_LastSourceAutofocusAlt); 2168 2169 resetButtons(); 2170 } 2171 2172 void Focus::completeFocusProcedure(FocusState completionState, bool plot) 2173 { 2174 if (inAutoFocus) 2175 { 2176 if (completionState == Ekos::FOCUS_COMPLETE) 2177 { 2178 if (plot) 2179 emit redrawHFRPlot(polynomialFit.get(), currentPosition, currentHFR); 2180 2181 // Update the plot_position and plot_value vectors (used by Analyze) 2182 updatePlotPosition(); 2183 2184 appendLogText(i18np("Focus procedure completed after %1 iteration.", 2185 "Focus procedure completed after %1 iterations.", plot_position.count())); 2186 2187 setLastFocusTemperature(); 2188 setLastFocusAlt(); 2189 resetAdaptiveFocus(m_OpsFocusSettings->focusAdaptive->isChecked()); 2190 2191 // CR add auto focus position, temperature and filter to log in CSV format 2192 // this will help with setting up focus offsets and temperature compensation 2193 qCInfo(KSTARS_EKOS_FOCUS) << "Autofocus values: position," << currentPosition << ", temperature," 2194 << m_LastSourceAutofocusTemperature << ", filter," << filter() 2195 << ", HFR," << currentHFR << ", altitude," << m_LastSourceAutofocusAlt; 2196 2197 if (m_FocusAlgorithm == FOCUS_POLYNOMIAL) 2198 { 2199 // Add the final polynomial values to the signal sent to Analyze. 2200 plot_position.append(currentPosition); 2201 plot_value.append(currentHFR); 2202 } 2203 2204 appendFocusLogText(QString("%1, %2, %3, %4, %5\n") 2205 .arg(QString::number(currentPosition)) 2206 .arg(QString::number(m_LastSourceAutofocusTemperature, 'f', 1)) 2207 .arg(filter()) 2208 .arg(QString::number(currentHFR, 'f', 3)) 2209 .arg(QString::number(m_LastSourceAutofocusAlt, 'f', 1))); 2210 2211 // Replace user position with optimal position 2212 absTicksSpin->setValue(currentPosition); 2213 } 2214 // In case of failure, go back to last position if the focuser is absolute 2215 else if (canAbsMove && initialFocuserAbsPosition >= 0 && resetFocusIteration <= MAXIMUM_RESET_ITERATIONS 2216 && m_RestartState != RESTART_ABORT) 2217 { 2218 // If we're doing in-sequence focusing using an absolute focuser, retry focusing once, starting from last known good position 2219 bool const retry_focusing = m_RestartState == RESTART_NONE && ++resetFocusIteration < MAXIMUM_RESET_ITERATIONS; 2220 2221 // If retrying, before moving, reset focus frame in case the star in subframe was lost 2222 if (retry_focusing) 2223 { 2224 m_RestartState = RESTART_NOW; 2225 resetFrame(); 2226 } 2227 2228 resetFocuser(); 2229 2230 // Bypass the rest of the function if we retry - we will fail if we could not move the focuser 2231 if (retry_focusing) 2232 { 2233 emit autofocusAborted(filter(), ""); 2234 return; 2235 } 2236 else 2237 { 2238 // We're in Autofocus and we've hit our max retry limit, so... 2239 // resetFocuser will have initiated a focuser reset back to its starting position 2240 // so we need to wait for that move to complete before returning control. 2241 // This is important if the scheduler is running autofocus as it will likely 2242 // immediately retry. The startup process will take the current focuser position 2243 // as the start position and so the focuser needs to have arrived at its starting 2244 // position before this. So set m_RestartState to log this. 2245 m_RestartState = RESTART_ABORT; 2246 return; 2247 } 2248 } 2249 2250 // Reset the retry count on success or maximum count 2251 resetFocusIteration = 0; 2252 } 2253 2254 const bool autoFocusUsed = inAutoFocus; 2255 const bool inBuildOffsetsUsed = inBuildOffsets; 2256 2257 // Refresh display if needed 2258 if (m_FocusAlgorithm == FOCUS_POLYNOMIAL && plot) 2259 emit drawPolynomial(polynomialFit.get(), isVShapeSolution, true); 2260 2261 // Enforce settling duration. Note stop resets m_GuidingSuspended 2262 int const settleTime = m_GuidingSuspended ? m_OpsFocusSettings->focusGuideSettleTime->value() : 0; 2263 2264 // Reset the autofocus flags 2265 stop(completionState); 2266 2267 if (settleTime > 0) 2268 appendLogText(i18n("Settling for %1s...", settleTime)); 2269 2270 QTimer::singleShot(settleTime * 1000, this, [ &, settleTime, completionState, autoFocusUsed, inBuildOffsetsUsed]() 2271 { 2272 settle(completionState, autoFocusUsed, inBuildOffsetsUsed); 2273 2274 if (settleTime > 0) 2275 appendLogText(i18n("Settling complete.")); 2276 }); 2277 } 2278 2279 void Focus::resetFocuser() 2280 { 2281 // If we are able to and need to, move the focuser back to the initial position and let the procedure restart from its termination 2282 if (m_Focuser && m_Focuser->isConnected() && initialFocuserAbsPosition >= 0) 2283 { 2284 // HACK: If the focuser will not move, cheat a little to get the notification - see processNumber 2285 if (currentPosition == initialFocuserAbsPosition) 2286 currentPosition--; 2287 2288 appendLogText(i18n("Autofocus failed, moving back to initial focus position %1.", initialFocuserAbsPosition)); 2289 changeFocus(initialFocuserAbsPosition - currentPosition); 2290 /* Restart will be executed by the end-of-move notification from the device if needed by resetFocus */ 2291 } 2292 } 2293 2294 void Focus::setCurrentMeasure() 2295 { 2296 // Let's now report the current HFR 2297 qCDebug(KSTARS_EKOS_FOCUS) << "Focus newFITS #" << starMeasureFrames.count() + 1 << ": Current HFR " << currentHFR << 2298 " Num stars " 2299 << (starSelected ? 1 : currentNumStars); 2300 2301 // Take the new HFR into account, eventually continue to stack samples 2302 if (appendMeasure(currentMeasure)) 2303 { 2304 capture(); 2305 return; 2306 } 2307 else starMeasureFrames.clear(); 2308 2309 // Let signal the current HFR now depending on whether the focuser is absolute or relative 2310 // Outside of Focus we continue to rely on HFR and independent of which measure the user selected we always calculate HFR 2311 if (canAbsMove) 2312 emit newHFR(currentHFR, currentPosition, inAutoFocus); 2313 else 2314 emit newHFR(currentHFR, -1, inAutoFocus); 2315 2316 // Format the labels under the V-curve 2317 HFROut->setText(QString("%1").arg(currentHFR * getStarUnits(m_StarMeasure, m_StarUnits), 0, 'f', 2)); 2318 if (m_StarMeasure == FOCUS_STAR_FWHM) 2319 FWHMOut->setText(QString("%1").arg(currentFWHM * getStarUnits(m_StarMeasure, m_StarUnits), 0, 'f', 2)); 2320 starsOut->setText(QString("%1").arg(m_ImageData->getDetectedStars())); 2321 iterOut->setText(QString("%1").arg(absIterations + 1)); 2322 2323 // Display message in case _last_ HFR was invalid 2324 if (lastHFR == INVALID_STAR_MEASURE) 2325 appendLogText(i18n("FITS received. No stars detected.")); 2326 2327 // If we have a valid HFR value 2328 if (currentHFR > 0) 2329 { 2330 // Check if we're done from polynomial fitting algorithm 2331 if (m_FocusAlgorithm == FOCUS_POLYNOMIAL && isVShapeSolution) 2332 { 2333 completeFocusProcedure(Ekos::FOCUS_COMPLETE); 2334 return; 2335 } 2336 2337 Edge selectedHFRStarHFR = m_ImageData->getSelectedHFRStar(); 2338 2339 // Center tracking box around selected star (if it valid) either in: 2340 // 1. Autofocus 2341 // 2. CheckFocus (minimumHFRCheck) 2342 // The starCenter _must_ already be defined, otherwise, we proceed until 2343 // the latter half of the function searches for a star and define it. 2344 if (starCenter.isNull() == false && (inAutoFocus || minimumRequiredHFR >= 0)) 2345 { 2346 // Now we have star selected in the frame 2347 starSelected = true; 2348 starCenter.setX(qMax(0, static_cast<int>(selectedHFRStarHFR.x))); 2349 starCenter.setY(qMax(0, static_cast<int>(selectedHFRStarHFR.y))); 2350 2351 syncTrackingBoxPosition(); 2352 2353 // Record the star information (X, Y, currentHFR) 2354 QVector3D oneStar = starCenter; 2355 oneStar.setZ(currentHFR); 2356 starsHFR.append(oneStar); 2357 } 2358 else 2359 { 2360 // Record the star information (X, Y, currentHFR) 2361 QVector3D oneStar(starCenter.x(), starCenter.y(), currentHFR); 2362 starsHFR.append(oneStar); 2363 } 2364 2365 if (currentHFR > maxHFR) 2366 maxHFR = currentHFR; 2367 2368 // Append point to the #Iterations vs #HFR chart in case of looping or in case in autofocus with a focus 2369 // that does not support position feedback. 2370 2371 // If inAutoFocus is true without canAbsMove and without canRelMove, canTimerMove must be true. 2372 // We'd only want to execute this if the focus linear algorithm is not being used, as that 2373 // algorithm simulates a position-based system even for timer-based focusers. 2374 if (inFocusLoop || (inAutoFocus && ! isPositionBased())) 2375 { 2376 int pos = plot_position.empty() ? 1 : plot_position.last() + 1; 2377 addPlotPosition(pos, currentHFR); 2378 } 2379 } 2380 else 2381 { 2382 // Let's record an invalid star result 2383 QVector3D oneStar(starCenter.x(), starCenter.y(), INVALID_STAR_MEASURE); 2384 starsHFR.append(oneStar); 2385 } 2386 2387 // First check that we haven't already search for stars 2388 // Since star-searching algorithm are time-consuming, we should only search when necessary 2389 m_FocusView->updateFrame(); 2390 2391 setHFRComplete(); 2392 2393 if (m_abInsOn) 2394 calculateAbInsData(); 2395 } 2396 2397 // Save off focus frame during Autofocus for later debugging 2398 void Focus::saveFocusFrame() 2399 { 2400 if (inAutoFocus && Options::focusLogging() && Options::saveFocusImages()) 2401 { 2402 QDir dir; 2403 QDateTime now = KStarsData::Instance()->lt(); 2404 QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("autofocus/" + 2405 now.toString("yyyy-MM-dd")); 2406 dir.mkpath(path); 2407 2408 // To help identify focus frames add run number, step and frame (for multiple frames at each step) 2409 QString detail; 2410 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS) 2411 { 2412 const int currentStep = linearFocuser->currentStep() + 1; 2413 detail = QString("_%1_%2_%3").arg(m_AFRun).arg(currentStep).arg(starMeasureFrames.count()); 2414 } 2415 2416 // IS8601 contains colons but they are illegal under Windows OS, so replacing them with '-' 2417 // The timestamp is no longer ISO8601 but it should solve interoperality issues between different OS hosts 2418 QString name = "autofocus_frame_" + now.toString("HH-mm-ss") + detail + ".fits"; 2419 QString filename = path + QStringLiteral("/") + name; 2420 m_ImageData->saveImage(filename); 2421 } 2422 } 2423 2424 void Focus::calculateAbInsData() 2425 { 2426 ImageMosaicMask *mosaicmask = dynamic_cast<ImageMosaicMask *>(m_FocusView->imageMask().get()); 2427 const QVector<QRect> tiles = mosaicmask->tiles(); 2428 auto stars = m_ImageData->getStarCenters(); 2429 QVector<QList<Edge *>> tileStars(NUM_TILES); 2430 2431 for (int star = 0; star < stars.count(); star++) 2432 { 2433 const int x = stars[star]->x; 2434 const int y = stars[star]->y; 2435 for (int tile = 0; tile < NUM_TILES; tile++) 2436 { 2437 QRect thisTile = tiles[tile]; 2438 if (thisTile.contains(x, y)) 2439 { 2440 tileStars[tile].append(stars[star]); 2441 break; 2442 } 2443 } 2444 } 2445 2446 // Get the measure for each tile 2447 for (int tile = 0; tile < tileStars.count(); tile++) 2448 { 2449 double measure, weight; 2450 2451 if (m_StarMeasure == FOCUS_STAR_NUM_STARS) 2452 { 2453 measure = tileStars[tile].count(); 2454 weight = 1.0; 2455 } 2456 else if (m_StarMeasure == FOCUS_STAR_FWHM) 2457 { 2458 getFWHM(tileStars[tile], &measure, &weight); 2459 } 2460 else if (m_StarMeasure == FOCUS_STAR_FOURIER_POWER) 2461 { 2462 getFourierPower(&measure, &weight, tile); 2463 } 2464 else 2465 { 2466 // HFR or HFR_adj 2467 std::vector<double> HFRs; 2468 2469 for (int star = 0; star < tileStars[tile].count(); star++) 2470 { 2471 HFRs.push_back(tileStars[tile][star]->HFR); 2472 } 2473 measure = Mathematics::RobustStatistics::ComputeLocation(Mathematics::RobustStatistics::LOCATION_SIGMACLIPPING, HFRs, 2); 2474 weight = calculateStarWeight(m_OpsFocusProcess->focusUseWeights->isChecked(), HFRs); 2475 } 2476 2477 m_abInsMeasure[tile].append(measure); 2478 m_abInsWeight[tile].append(weight); 2479 m_abInsNumStars[tile].append(tileStars[tile].count()); 2480 2481 if (!linearFocuser->isInFirstPass()) 2482 { 2483 // This is the last datapoint so calculate average star position in the tile from the tile center 2484 // FOCUS_STAR_FOURIER_POWER doesn't use stars directly so no need to calculate offset. The other 2485 // measures all use stars: FOCUS_STAR_NUM_STARS, FOCUS_STAR_FWHM, FOCUS_STAR_HFR, FOCUS_STAR_HFR_ADJ 2486 int xAv = 0, yAv = 0; 2487 if (m_StarMeasure != FOCUS_STAR_FOURIER_POWER) 2488 { 2489 QPoint tileCenter = tiles[tile].center(); 2490 int xSum = 0.0, ySum = 0.0; 2491 for (int star = 0; star < tileStars[tile].count(); star++) 2492 { 2493 xSum += tileStars[tile][star]->x - tileCenter.x(); 2494 ySum += tileStars[tile][star]->y - tileCenter.y(); 2495 } 2496 2497 xAv = (tileStars[tile].count() <= 0) ? 0 : xSum / tileStars[tile].count(); 2498 yAv = (tileStars[tile].count() <= 0) ? 0 : ySum / tileStars[tile].count(); 2499 } 2500 m_abInsTileCenterOffset.append(QPoint(xAv, yAv)); 2501 } 2502 } 2503 m_abInsPosition.append(currentPosition); 2504 } 2505 2506 void Focus::setCaptureComplete() 2507 { 2508 DarkLibrary::Instance()->disconnect(this); 2509 2510 // If we have a box, sync the bounding box to its position. 2511 syncTrackingBoxPosition(); 2512 2513 // Notify user if we're not looping 2514 if (inFocusLoop == false) 2515 appendLogText(i18n("Image received.")); 2516 2517 if (captureInProgress && inFocusLoop == false && inAutoFocus == false) 2518 m_Camera->setUploadMode(rememberUploadMode); 2519 2520 if (m_RememberCameraFastExposure && inFocusLoop == false && inAutoFocus == false) 2521 { 2522 m_RememberCameraFastExposure = false; 2523 m_Camera->setFastExposureEnabled(true); 2524 } 2525 2526 captureInProgress = false; 2527 // update the limits from the real values 2528 checkMosaicMaskLimits(); 2529 2530 // Emit the whole image 2531 emit newImage(m_FocusView); 2532 // Emit the tracking (bounding) box view. Used in Summary View 2533 emit newStarPixmap(m_FocusView->getTrackingBoxPixmap(10)); 2534 2535 // If we are not looping; OR 2536 // If we are looping but we already have tracking box enabled; OR 2537 // If we are asked to analyze _all_ the stars within the field 2538 // THEN let's find stars in the image and get current HFR 2539 if (inFocusLoop == false || (inFocusLoop && (m_FocusView->isTrackingBoxEnabled() 2540 || m_OpsFocusSettings->focusUseFullField->isChecked()))) 2541 analyzeSources(); 2542 else 2543 setHFRComplete(); 2544 } 2545 2546 void Focus::setHFRComplete() 2547 { 2548 // If we are just framing, let's capture again 2549 if (inFocusLoop) 2550 { 2551 capture(); 2552 return; 2553 } 2554 2555 // Get target chip 2556 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 2557 2558 // Get target chip binning 2559 int subBinX = 1, subBinY = 1; 2560 if (!targetChip->getBinning(&subBinX, &subBinY)) 2561 qCDebug(KSTARS_EKOS_FOCUS) << "Warning: target chip is reporting no binning property, using 1x1."; 2562 2563 // If star is NOT yet selected in a non-full-frame situation 2564 // then let's now try to find the star. This step is skipped for full frames 2565 // since there isn't a single star to select as we are only interested in the overall average HFR. 2566 // We need to check if we can find the star right away, or if we need to _subframe_ around the 2567 // selected star. 2568 if (m_OpsFocusSettings->focusUseFullField->isChecked() == false && starCenter.isNull()) 2569 { 2570 int x = 0, y = 0, w = 0, h = 0; 2571 2572 // Let's get the stored frame settings for this particular chip 2573 if (frameSettings.contains(targetChip)) 2574 { 2575 QVariantMap settings = frameSettings[targetChip]; 2576 x = settings["x"].toInt(); 2577 y = settings["y"].toInt(); 2578 w = settings["w"].toInt(); 2579 h = settings["h"].toInt(); 2580 } 2581 else 2582 // Otherwise let's get the target chip frame coordinates. 2583 targetChip->getFrame(&x, &y, &w, &h); 2584 2585 // In case auto star is selected. 2586 if (m_OpsFocusSettings->focusAutoStarEnabled->isChecked()) 2587 { 2588 // Do we have a valid star detected? 2589 const Edge selectedHFRStar = m_ImageData->getSelectedHFRStar(); 2590 2591 if (selectedHFRStar.x == -1) 2592 { 2593 appendLogText(i18n("Failed to automatically select a star. Please select a star manually.")); 2594 2595 // Center the tracking box in the frame and display it 2596 m_FocusView->setTrackingBox(QRect(w - m_OpsFocusSettings->focusBoxSize->value() / (subBinX * 2), 2597 h - m_OpsFocusSettings->focusBoxSize->value() / (subBinY * 2), 2598 m_OpsFocusSettings->focusBoxSize->value() / subBinX, m_OpsFocusSettings->focusBoxSize->value() / subBinY)); 2599 m_FocusView->setTrackingBoxEnabled(true); 2600 2601 // Use can now move it to select the desired star 2602 setState(Ekos::FOCUS_WAITING); 2603 2604 // Start the wait timer so we abort after a timeout if the user does not make a choice 2605 waitStarSelectTimer.start(); 2606 2607 return; 2608 } 2609 2610 // set the tracking box on selectedHFRStar 2611 starCenter.setX(selectedHFRStar.x); 2612 starCenter.setY(selectedHFRStar.y); 2613 starCenter.setZ(subBinX); 2614 starSelected = true; 2615 syncTrackingBoxPosition(); 2616 2617 // Do we need to subframe? 2618 if (subFramed == false && isFocusSubFrameEnabled() && m_OpsFocusSettings->focusSubFrame->isChecked()) 2619 { 2620 int offset = (static_cast<double>(m_OpsFocusSettings->focusBoxSize->value()) / subBinX) * 1.5; 2621 int subX = (selectedHFRStar.x - offset) * subBinX; 2622 int subY = (selectedHFRStar.y - offset) * subBinY; 2623 int subW = offset * 2 * subBinX; 2624 int subH = offset * 2 * subBinY; 2625 2626 int minX, maxX, minY, maxY, minW, maxW, minH, maxH; 2627 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); 2628 2629 // Try to limit the subframed selection 2630 if (subX < minX) 2631 subX = minX; 2632 if (subY < minY) 2633 subY = minY; 2634 if ((subW + subX) > maxW) 2635 subW = maxW - subX; 2636 if ((subH + subY) > maxH) 2637 subH = maxH - subY; 2638 2639 // Now we store the subframe coordinates in the target chip frame settings so we 2640 // reuse it later when we capture again. 2641 QVariantMap settings = frameSettings[targetChip]; 2642 settings["x"] = subX; 2643 settings["y"] = subY; 2644 settings["w"] = subW; 2645 settings["h"] = subH; 2646 settings["binx"] = subBinX; 2647 settings["biny"] = subBinY; 2648 2649 qCDebug(KSTARS_EKOS_FOCUS) << "Frame is subframed. X:" << subX << "Y:" << subY << "W:" << subW << "H:" << subH << "binX:" << 2650 subBinX << "binY:" << subBinY; 2651 2652 starsHFR.clear(); 2653 2654 frameSettings[targetChip] = settings; 2655 2656 // Set the star center in the center of the subframed coordinates 2657 starCenter.setX(subW / (2 * subBinX)); 2658 starCenter.setY(subH / (2 * subBinY)); 2659 starCenter.setZ(subBinX); 2660 2661 subFramed = true; 2662 2663 m_FocusView->setFirstLoad(true); 2664 2665 // Now let's capture again for the actual requested subframed image. 2666 capture(); 2667 return; 2668 } 2669 // If we're subframed or don't need subframe, let's record the max star coordinates 2670 else 2671 { 2672 starCenter.setX(selectedHFRStar.x); 2673 starCenter.setY(selectedHFRStar.y); 2674 starCenter.setZ(subBinX); 2675 2676 // Let's now capture again if we're autofocusing 2677 if (inAutoFocus) 2678 { 2679 capture(); 2680 return; 2681 } 2682 } 2683 } 2684 // If manual selection is enabled then let's ask the user to select the focus star 2685 else 2686 { 2687 appendLogText(i18n("Capture complete. Select a star to focus.")); 2688 2689 starSelected = false; 2690 2691 // Let's now display and set the tracking box in the center of the frame 2692 // so that the user moves it around to select the desired star. 2693 int subBinX = 1, subBinY = 1; 2694 targetChip->getBinning(&subBinX, &subBinY); 2695 2696 m_FocusView->setTrackingBox(QRect((w - m_OpsFocusSettings->focusBoxSize->value()) / (subBinX * 2), 2697 (h - m_OpsFocusSettings->focusBoxSize->value()) / (2 * subBinY), 2698 m_OpsFocusSettings->focusBoxSize->value() / subBinX, m_OpsFocusSettings->focusBoxSize->value() / subBinY)); 2699 m_FocusView->setTrackingBoxEnabled(true); 2700 2701 // Now we wait 2702 setState(Ekos::FOCUS_WAITING); 2703 2704 // If the user does not select for a timeout period, we abort. 2705 waitStarSelectTimer.start(); 2706 return; 2707 } 2708 } 2709 2710 // Check if the focus module is requested to verify if the minimum HFR value is met. 2711 if (minimumRequiredHFR >= 0) 2712 { 2713 // In case we failed to detected, we capture again. 2714 if (currentHFR == INVALID_STAR_MEASURE) 2715 { 2716 if (noStarCount++ < MAX_RECAPTURE_RETRIES) 2717 { 2718 appendLogText(i18n("No stars detected while testing HFR, capturing again...")); 2719 // On Last Attempt reset focus frame to capture full frame and recapture star if possible 2720 if (noStarCount == MAX_RECAPTURE_RETRIES) 2721 resetFrame(); 2722 capture(); 2723 return; 2724 } 2725 // If we exceeded maximum tries we abort 2726 else 2727 { 2728 noStarCount = 0; 2729 completeFocusProcedure(Ekos::FOCUS_ABORTED); 2730 } 2731 } 2732 // If the detect current HFR is more than the minimum required HFR 2733 // then we should start the autofocus process now to bring it down. 2734 else if (currentHFR > minimumRequiredHFR) 2735 { 2736 qCDebug(KSTARS_EKOS_FOCUS) << "Current HFR:" << currentHFR << "is above required minimum HFR:" << minimumRequiredHFR << 2737 ". Starting AutoFocus..."; 2738 minimumRequiredHFR = INVALID_STAR_MEASURE; 2739 start(); 2740 } 2741 // Otherwise, the current HFR is fine and lower than the required minimum HFR so we announce success. 2742 else 2743 { 2744 qCDebug(KSTARS_EKOS_FOCUS) << "Current HFR:" << currentHFR << "is below required minimum HFR:" << minimumRequiredHFR << 2745 ". Autofocus successful."; 2746 completeFocusProcedure(Ekos::FOCUS_COMPLETE, false); 2747 } 2748 2749 // Nothing more for now 2750 return; 2751 } 2752 2753 // If we are not in autofocus process, we're done. 2754 if (inAutoFocus == false) 2755 { 2756 // If we are done and there is no further autofocus, 2757 // we reset state to IDLE 2758 if (state() != Ekos::FOCUS_IDLE) 2759 setState(Ekos::FOCUS_IDLE); 2760 2761 resetButtons(); 2762 return; 2763 } 2764 2765 // Set state to progress 2766 if (state() != Ekos::FOCUS_PROGRESS) 2767 setState(Ekos::FOCUS_PROGRESS); 2768 2769 // Now let's kick in the algorithms 2770 2771 if (m_FocusAlgorithm == FOCUS_LINEAR || m_FocusAlgorithm == FOCUS_LINEAR1PASS) 2772 autoFocusLinear(); 2773 else if (canAbsMove || canRelMove) 2774 // Position-based algorithms 2775 autoFocusAbs(); 2776 else 2777 // Time open-looped algorithms 2778 autoFocusRel(); 2779 } 2780 2781 QString Focus::getyAxisLabel(StarMeasure starMeasure) 2782 { 2783 QString str = "HFR"; 2784 m_StarUnits == FOCUS_UNITS_ARCSEC ? str += " (\")" : str += " (pix)"; 2785 2786 if (inAutoFocus) 2787 { 2788 switch (starMeasure) 2789 { 2790 case FOCUS_STAR_HFR: 2791 break; 2792 case FOCUS_STAR_HFR_ADJ: 2793 str = "HFR Adj"; 2794 m_StarUnits == FOCUS_UNITS_ARCSEC ? str += " (\")" : str += " (pix)"; 2795 break; 2796 case FOCUS_STAR_FWHM: 2797 str = "FWHM"; 2798 m_StarUnits == FOCUS_UNITS_ARCSEC ? str += " (\")" : str += " (pix)"; 2799 break; 2800 case FOCUS_STAR_NUM_STARS: 2801 str = "# Stars"; 2802 break; 2803 case FOCUS_STAR_FOURIER_POWER: 2804 str = "Fourier Power"; 2805 break; 2806 default: 2807 break; 2808 } 2809 } 2810 return str; 2811 } 2812 2813 void Focus::clearDataPoints() 2814 { 2815 maxHFR = 1; 2816 polynomialFit.reset(); 2817 plot_position.clear(); 2818 plot_value.clear(); 2819 isVShapeSolution = false; 2820 m_abInsPosition.clear(); 2821 m_abInsTileCenterOffset.clear(); 2822 if (m_abInsMeasure.count() != NUM_TILES) 2823 { 2824 m_abInsMeasure.resize(NUM_TILES); 2825 m_abInsWeight.resize(NUM_TILES); 2826 m_abInsNumStars.resize(NUM_TILES); 2827 } 2828 else 2829 { 2830 for (int i = 0; i < m_abInsMeasure.count(); i++) 2831 { 2832 m_abInsMeasure[i].clear(); 2833 m_abInsWeight[i].clear(); 2834 m_abInsNumStars[i].clear(); 2835 } 2836 } 2837 2838 emit initHFRPlot(getyAxisLabel(m_StarMeasure), getStarUnits(m_StarMeasure, m_StarUnits), 2839 m_OptDir == CurveFitting::OPTIMISATION_MINIMISE, m_OpsFocusProcess->focusUseWeights->isChecked(), 2840 inFocusLoop == false && isPositionBased()); 2841 } 2842 2843 bool Focus::autoFocusChecks() 2844 { 2845 if (++absIterations > MAXIMUM_ABS_ITERATIONS) 2846 { 2847 appendLogText(i18n("Autofocus failed to reach proper focus. Try increasing tolerance value.")); 2848 completeFocusProcedure(Ekos::FOCUS_ABORTED); 2849 return false; 2850 } 2851 2852 // No stars detected, try to capture again 2853 if (currentHFR == INVALID_STAR_MEASURE) 2854 { 2855 if (noStarCount < MAX_RECAPTURE_RETRIES) 2856 { 2857 noStarCount++; 2858 appendLogText(i18n("No stars detected, capturing again...")); 2859 capture(); 2860 return false; 2861 } 2862 else if (m_FocusAlgorithm == FOCUS_LINEAR) 2863 { 2864 appendLogText(i18n("Failed to detect any stars at position %1. Continuing...", currentPosition)); 2865 noStarCount = 0; 2866 } 2867 else if(!m_OpsFocusProcess->focusDonut->isChecked()) 2868 { 2869 // Carry on for donut detection 2870 appendLogText(i18n("Failed to detect any stars. Reset frame and try again.")); 2871 completeFocusProcedure(Ekos::FOCUS_ABORTED); 2872 return false; 2873 } 2874 } 2875 else 2876 noStarCount = 0; 2877 2878 return true; 2879 } 2880 2881 void Focus::plotLinearFocus() 2882 { 2883 // I was hoping to avoid intermediate plotting, just set everything up then plot, 2884 // but this isn't working. For now, with plt=true, plot on every intermediate update. 2885 bool plt = true; 2886 2887 // Get the data to plot. 2888 QVector<double> values, weights; 2889 QVector<int> positions; 2890 linearFocuser->getMeasurements(&positions, &values, &weights); 2891 const FocusAlgorithmInterface::FocusParams ¶ms = linearFocuser->getParams(); 2892 2893 // As an optimization for slower machines, e.g. RPi4s, if the points are the same except for 2894 // the last point, just emit the last point instead of redrawing everything. 2895 static QVector<double> lastValues; 2896 static QVector<int> lastPositions; 2897 2898 bool incrementalChange = false; 2899 if (positions.size() > 1 && positions.size() == lastPositions.size() + 1) 2900 { 2901 bool ok = true; 2902 for (int i = 0; i < positions.size() - 1; ++i) 2903 if (positions[i] != lastPositions[i] || values[i] != lastValues[i]) 2904 { 2905 ok = false; 2906 break; 2907 } 2908 incrementalChange = ok; 2909 } 2910 lastPositions = positions; 2911 lastValues = values; 2912 2913 const bool outlier = false; 2914 if (incrementalChange) 2915 emit newHFRPlotPosition(static_cast<double>(positions.last()), values.last(), (pow(weights.last(), -0.5)), 2916 outlier, params.initialStepSize, plt); 2917 else 2918 { 2919 emit initHFRPlot(getyAxisLabel(m_StarMeasure), getStarUnits(m_StarMeasure, m_StarUnits), 2920 m_OptDir == CurveFitting::OPTIMISATION_MINIMISE, params.useWeights, plt); 2921 for (int i = 0; i < positions.size(); ++i) 2922 emit newHFRPlotPosition(static_cast<double>(positions[i]), values[i], (pow(weights.last(), -0.5)), 2923 outlier, params.initialStepSize, plt); 2924 } 2925 2926 // Plot the polynomial, if there are enough points. 2927 if (values.size() > 3) 2928 { 2929 // The polynomial should only reflect 1st-pass samples. 2930 QVector<double> pass1Values, pass1Weights; 2931 QVector<int> pass1Positions; 2932 QVector<bool> pass1Outliers; 2933 double minPosition, minValue; 2934 double searchMin = std::max(params.minPositionAllowed, params.startPosition - params.maxTravel); 2935 double searchMax = std::min(params.maxPositionAllowed, params.startPosition + params.maxTravel); 2936 2937 linearFocuser->getPass1Measurements(&pass1Positions, &pass1Values, &pass1Weights, &pass1Outliers); 2938 if (m_FocusAlgorithm == FOCUS_LINEAR || m_CurveFit == CurveFitting::FOCUS_QUADRATIC) 2939 { 2940 // TODO: Need to determine whether to change LINEAR over to the LM solver in CurveFitting 2941 // This will be determined after L1P's first release has been deemed successful. 2942 polynomialFit.reset(new PolynomialFit(2, pass1Positions, pass1Values)); 2943 2944 if (polynomialFit->findMinimum(params.startPosition, searchMin, searchMax, &minPosition, &minValue)) 2945 { 2946 emit drawPolynomial(polynomialFit.get(), true, true, plt); 2947 2948 // Only plot the first pass' min position if we're not done. 2949 // Once we have a result, we don't want to display an intermediate minimum. 2950 if (linearFocuser->isDone()) 2951 emit minimumFound(-1, -1, plt); 2952 else 2953 emit minimumFound(minPosition, minValue, plt); 2954 } 2955 else 2956 { 2957 // Didn't get a good polynomial fit. 2958 emit drawPolynomial(polynomialFit.get(), false, false, plt); 2959 emit minimumFound(-1, -1, plt); 2960 } 2961 2962 } 2963 else // Linear 1 Pass 2964 { 2965 if (curveFitting->findMinMax(params.startPosition, searchMin, searchMax, &minPosition, &minValue, params.curveFit, 2966 params.optimisationDirection)) 2967 { 2968 R2 = curveFitting->calculateR2(static_cast<CurveFitting::CurveFit>(params.curveFit)); 2969 emit drawCurve(curveFitting.get(), true, true, plt); 2970 2971 // For Linear 1 Pass always display the minimum on the graph 2972 emit minimumFound(minPosition, minValue, plt); 2973 } 2974 else 2975 { 2976 // Didn't get a good fit. 2977 emit drawCurve(curveFitting.get(), false, false, plt); 2978 emit minimumFound(-1, -1, plt); 2979 } 2980 } 2981 } 2982 2983 // Linear focuser might change the latest hfr with its relativeHFR scheme. 2984 HFROut->setText(QString("%1").arg(currentHFR * getStarUnits(m_StarMeasure, m_StarUnits), 0, 'f', 2)); 2985 2986 emit setTitle(linearFocuser->getTextStatus(R2)); 2987 2988 if (!plt) HFRPlot->replot(); 2989 } 2990 2991 // Get the curve fitting goal 2992 CurveFitting::FittingGoal Focus::getGoal(int numSteps) 2993 { 2994 // The classic walk needs the STANDARD curve fitting 2995 if (m_FocusWalk == FOCUS_WALK_CLASSIC) 2996 return CurveFitting::FittingGoal::STANDARD; 2997 2998 // Fixed step walks will use C, except for the last step which should be BEST 2999 return (numSteps >= m_OpsFocusMechanics->focusNumSteps->value()) ? CurveFitting::FittingGoal::BEST : 3000 CurveFitting::FittingGoal::STANDARD; 3001 } 3002 3003 // Called after the first pass is complete and we're moving to the final focus position 3004 // Calculate R2 for the curve and update the graph. 3005 // Add the CFZ to the graph 3006 void Focus::plotLinearFinalUpdates() 3007 { 3008 bool plt = true; 3009 if (!m_OpsFocusProcess->focusRefineCurveFit->isChecked()) 3010 { 3011 // Display the CFZ on the graph 3012 emit drawCFZ(linearFocuser->solution(), linearFocuser->solutionValue(), m_cfzSteps, 3013 m_CFZUI->focusCFZDisplayVCurve->isChecked()); 3014 // Final updates to the graph title 3015 emit finalUpdates(linearFocuser->getTextStatus(R2), plt); 3016 } 3017 else 3018 { 3019 // v-curve needs to be redrawn to reflect the data from the refining process 3020 // Get the data to plot. 3021 QVector<double> pass1Values, pass1Weights; 3022 QVector<int> pass1Positions; 3023 QVector<bool> pass1Outliers; 3024 3025 linearFocuser->getPass1Measurements(&pass1Positions, &pass1Values, &pass1Weights, &pass1Outliers); 3026 const FocusAlgorithmInterface::FocusParams ¶ms = linearFocuser->getParams(); 3027 3028 emit initHFRPlot(getyAxisLabel(m_StarMeasure), getStarUnits(m_StarMeasure, m_StarUnits), 3029 m_OptDir == CurveFitting::OPTIMISATION_MINIMISE, m_OpsFocusProcess->focusUseWeights->isChecked(), plt); 3030 3031 for (int i = 0; i < pass1Positions.size(); ++i) 3032 emit newHFRPlotPosition(static_cast<double>(pass1Positions[i]), pass1Values[i], (pow(pass1Weights[i], -0.5)), 3033 pass1Outliers[i], params.initialStepSize, plt); 3034 3035 R2 = curveFitting->calculateR2(static_cast<CurveFitting::CurveFit>(params.curveFit)); 3036 emit drawCurve(curveFitting.get(), true, true, false); 3037 3038 // For Linear 1 Pass always display the minimum on the graph 3039 emit minimumFound(linearFocuser->solution(), linearFocuser->solutionValue(), plt); 3040 // Display the CFZ on the graph 3041 emit drawCFZ(linearFocuser->solution(), linearFocuser->solutionValue(), m_cfzSteps, 3042 m_CFZUI->focusCFZDisplayVCurve->isChecked()); 3043 // Update the graph title 3044 emit setTitle(linearFocuser->getTextStatus(R2), plt); 3045 } 3046 } 3047 3048 void Focus::startAberrationInspector() 3049 { 3050 // Fill in the data structure to be passed to the Aberration Inspector 3051 AberrationInspector::abInsData data; 3052 3053 ImageMosaicMask *mosaicmask = dynamic_cast<ImageMosaicMask *>(m_FocusView->imageMask().get()); 3054 if (!mosaicmask) 3055 { 3056 appendLogText(i18n("Unable to launch Aberration Inspector run %1...", m_abInsRun)); 3057 return; 3058 } 3059 3060 3061 data.run = ++m_abInsRun; 3062 data.curveFit = m_CurveFit; 3063 data.useWeights = m_OpsFocusProcess->focusUseWeights->isChecked(); 3064 data.optDir = m_OptDir; 3065 data.sensorWidth = m_CcdWidth; 3066 data.sensorHeight = m_CcdHeight; 3067 data.pixelSize = m_CcdPixelSizeX; 3068 data.tileWidth = mosaicmask->tiles()[0].width(); 3069 data.focuserStepMicrons = m_CFZUI->focusCFZStepSize->value(); 3070 data.yAxisLabel = getyAxisLabel(m_StarMeasure); 3071 data.starUnits = getStarUnits(m_StarMeasure, m_StarUnits); 3072 data.cfzSteps = m_cfzSteps; 3073 data.isPositionBased = isPositionBased(); 3074 3075 // Launch the Aberration Inspector. 3076 appendLogText(i18n("Launching Aberration Inspector run %1...", m_abInsRun)); 3077 QPointer<AberrationInspector> abIns(new AberrationInspector(data, m_abInsPosition, m_abInsMeasure, m_abInsWeight, 3078 m_abInsNumStars, m_abInsTileCenterOffset)); 3079 abIns->setAttribute(Qt::WA_DeleteOnClose); 3080 abIns->show(); 3081 } 3082 3083 void Focus::autoFocusLinear() 3084 { 3085 if (!autoFocusChecks()) 3086 return; 3087 3088 if (!canAbsMove && !canRelMove && canTimerMove) 3089 { 3090 //const bool kFixPosition = true; 3091 if (linearRequestedPosition != currentPosition) 3092 //if (kFixPosition && (linearRequestedPosition != currentPosition)) 3093 { 3094 qCDebug(KSTARS_EKOS_FOCUS) << "Linear: warning, changing position " << currentPosition << " to " 3095 << linearRequestedPosition; 3096 3097 currentPosition = linearRequestedPosition; 3098 } 3099 } 3100 3101 // Only use the relativeHFR algorithm if full field is enabled with one capture/measurement. 3102 bool useFocusStarsHFR = m_OpsFocusSettings->focusUseFullField->isChecked() 3103 && m_OpsFocusProcess->focusFramesCount->value() == 1; 3104 auto focusStars = useFocusStarsHFR || (m_FocusAlgorithm == FOCUS_LINEAR1PASS) ? &(m_ImageData->getStarCenters()) : nullptr; 3105 3106 linearRequestedPosition = linearFocuser->newMeasurement(currentPosition, currentMeasure, currentWeight, focusStars); 3107 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS && linearFocuser->isDone() && linearFocuser->solution() != -1) 3108 { 3109 // Linear 1 Pass is done, graph is drawn, so just move to the focus position, and update the graph. 3110 plotLinearFinalUpdates(); 3111 if (m_abInsOn) 3112 startAberrationInspector(); 3113 } 3114 else 3115 // Update the graph with the next datapoint, draw the curve, etc. 3116 plotLinearFocus(); 3117 3118 if (linearFocuser->isDone()) 3119 { 3120 if (linearFocuser->solution() != -1) 3121 { 3122 // Now test that the curve fit was acceptable. If not retry the focus process using standard retry process 3123 // R2 check is only available for Linear 1 Pass for Hyperbola and Parabola 3124 if (m_CurveFit == CurveFitting::FOCUS_QUADRATIC) 3125 // Linear only uses Quadratic so no need to do the R2 check, just complete 3126 completeFocusProcedure(Ekos::FOCUS_COMPLETE, false); 3127 else if (R2 >= m_OpsFocusProcess->focusR2Limit->value()) 3128 { 3129 qCDebug(KSTARS_EKOS_FOCUS) << QString("Linear Curve Fit check passed R2=%1 focusR2Limit=%2").arg(R2).arg( 3130 m_OpsFocusProcess->focusR2Limit->value()); 3131 completeFocusProcedure(Ekos::FOCUS_COMPLETE, false); 3132 R2Retries = 0; 3133 } 3134 else if (R2Retries == 0) 3135 { 3136 // Failed the R2 check for the first time so retry... 3137 appendLogText(i18n("Curve Fit check failed R2=%1 focusR2Limit=%2 retrying...", R2, 3138 m_OpsFocusProcess->focusR2Limit->value())); 3139 completeFocusProcedure(Ekos::FOCUS_ABORTED, false); 3140 R2Retries++; 3141 } 3142 else 3143 { 3144 // Retried after an R2 check fail but failed again so... log msg and continue 3145 appendLogText(i18n("Curve Fit check failed again R2=%1 focusR2Limit=%2 but continuing...", R2, 3146 m_OpsFocusProcess->focusR2Limit->value())); 3147 completeFocusProcedure(Ekos::FOCUS_COMPLETE, false); 3148 R2Retries = 0; 3149 } 3150 } 3151 else 3152 { 3153 qCDebug(KSTARS_EKOS_FOCUS) << linearFocuser->doneReason(); 3154 appendLogText("Linear autofocus algorithm aborted."); 3155 completeFocusProcedure(Ekos::FOCUS_ABORTED, false); 3156 } 3157 return; 3158 } 3159 else 3160 { 3161 const int delta = linearRequestedPosition - currentPosition; 3162 3163 if (!changeFocus(delta)) 3164 completeFocusProcedure(Ekos::FOCUS_ABORTED, false); 3165 3166 return; 3167 } 3168 } 3169 3170 void Focus::autoFocusAbs() 3171 { 3172 // Q_ASSERT_X(canAbsMove || canRelMove, __FUNCTION__, "Prerequisite: only absolute and relative focusers"); 3173 3174 static int minHFRPos = 0, focusOutLimit = 0, focusInLimit = 0, lastHFRPos = 0, fluctuations = 0; 3175 static double minHFR = 0, lastDelta = 0; 3176 double targetPosition = 0; 3177 bool ignoreLimitedDelta = false; 3178 3179 QString deltaTxt = QString("%1").arg(fabs(currentHFR - minHFR) * 100.0, 0, 'g', 3); 3180 QString HFRText = QString("%1").arg(currentHFR, 0, 'g', 3); 3181 3182 qCDebug(KSTARS_EKOS_FOCUS) << "==============================="; 3183 qCDebug(KSTARS_EKOS_FOCUS) << "Current HFR: " << currentHFR << " Current Position: " << currentPosition; 3184 qCDebug(KSTARS_EKOS_FOCUS) << "Last minHFR: " << minHFR << " Last MinHFR Pos: " << minHFRPos; 3185 qCDebug(KSTARS_EKOS_FOCUS) << "Delta: " << deltaTxt << "%"; 3186 qCDebug(KSTARS_EKOS_FOCUS) << "========================================"; 3187 3188 if (minHFR) 3189 appendLogText(i18n("FITS received. HFR %1 @ %2. Delta (%3%)", HFRText, currentPosition, deltaTxt)); 3190 else 3191 appendLogText(i18n("FITS received. HFR %1 @ %2.", HFRText, currentPosition)); 3192 3193 if (!autoFocusChecks()) 3194 return; 3195 3196 addPlotPosition(currentPosition, currentHFR); 3197 3198 switch (m_LastFocusDirection) 3199 { 3200 case FOCUS_NONE: 3201 lastHFR = currentHFR; 3202 initialFocuserAbsPosition = currentPosition; 3203 minHFR = currentHFR; 3204 minHFRPos = currentPosition; 3205 HFRDec = 0; 3206 HFRInc = 0; 3207 focusOutLimit = 0; 3208 focusInLimit = 0; 3209 lastDelta = 0; 3210 fluctuations = 0; 3211 3212 // This is the first step, so clamp the initial target position to the device limits 3213 // If the focuser cannot move because it is at one end of the interval, try the opposite direction next 3214 if (absMotionMax < currentPosition + pulseDuration) 3215 { 3216 if (currentPosition < absMotionMax) 3217 { 3218 pulseDuration = absMotionMax - currentPosition; 3219 } 3220 else 3221 { 3222 pulseDuration = 0; 3223 m_LastFocusDirection = FOCUS_IN; 3224 } 3225 } 3226 else if (currentPosition + pulseDuration < absMotionMin) 3227 { 3228 if (absMotionMin < currentPosition) 3229 { 3230 pulseDuration = currentPosition - absMotionMin; 3231 } 3232 else 3233 { 3234 pulseDuration = 0; 3235 m_LastFocusDirection = FOCUS_OUT; 3236 } 3237 } 3238 3239 m_LastFocusDirection = (pulseDuration > 0) ? FOCUS_OUT : FOCUS_IN; 3240 if (!changeFocus(pulseDuration, false)) 3241 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3242 3243 break; 3244 3245 case FOCUS_IN: 3246 case FOCUS_OUT: 3247 if (reverseDir && focusInLimit && focusOutLimit && 3248 fabs(currentHFR - minHFR) < (m_OpsFocusProcess->focusTolerance->value() / 100.0) && HFRInc == 0) 3249 { 3250 if (absIterations <= 2) 3251 { 3252 QString message = i18n("Change in HFR is too small. Try increasing the step size or decreasing the tolerance."); 3253 appendLogText(message); 3254 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert); 3255 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3256 } 3257 else if (noStarCount > 0) 3258 { 3259 QString message = i18n("Failed to detect focus star in frame. Capture and select a focus star."); 3260 appendLogText(message); 3261 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert); 3262 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3263 } 3264 else 3265 { 3266 completeFocusProcedure(Ekos::FOCUS_COMPLETE); 3267 } 3268 break; 3269 } 3270 else if (currentHFR < lastHFR) 3271 { 3272 // Let's now limit the travel distance of the focuser 3273 if (HFRInc >= 1 && m_LastFocusDirection == FOCUS_OUT && lastHFRPos < focusInLimit && fabs(currentHFR - lastHFR) > 0.1) 3274 { 3275 focusInLimit = lastHFRPos; 3276 qCDebug(KSTARS_EKOS_FOCUS) << "New FocusInLimit " << focusInLimit; 3277 } 3278 else if (HFRInc >= 1 && m_LastFocusDirection == FOCUS_IN && lastHFRPos > focusOutLimit && 3279 fabs(currentHFR - lastHFR) > 0.1) 3280 { 3281 focusOutLimit = lastHFRPos; 3282 qCDebug(KSTARS_EKOS_FOCUS) << "New FocusOutLimit " << focusOutLimit; 3283 } 3284 3285 double factor = std::max(1.0, HFRDec / 2.0); 3286 if (m_LastFocusDirection == FOCUS_IN) 3287 targetPosition = currentPosition - (pulseDuration * factor); 3288 else 3289 targetPosition = currentPosition + (pulseDuration * factor); 3290 3291 qCDebug(KSTARS_EKOS_FOCUS) << "current Position" << currentPosition << " targetPosition " << targetPosition; 3292 3293 lastHFR = currentHFR; 3294 3295 // Let's keep track of the minimum HFR 3296 if (lastHFR < minHFR) 3297 { 3298 minHFR = lastHFR; 3299 minHFRPos = currentPosition; 3300 qCDebug(KSTARS_EKOS_FOCUS) << "new minHFR " << minHFR << " @ position " << minHFRPos; 3301 } 3302 3303 lastHFRPos = currentPosition; 3304 3305 // HFR is decreasing, we are on the right direction 3306 HFRDec++; 3307 if (HFRInc > 0) 3308 { 3309 // Remove bad data point and mark fluctuation 3310 if (plot_position.count() >= 2) 3311 { 3312 plot_position.remove(plot_position.count() - 2); 3313 plot_value.remove(plot_value.count() - 2); 3314 } 3315 fluctuations++; 3316 } 3317 HFRInc = 0; 3318 } 3319 else 3320 { 3321 // HFR increased, let's deal with it. 3322 HFRInc++; 3323 if (HFRDec > 0) 3324 fluctuations++; 3325 HFRDec = 0; 3326 3327 lastHFR = currentHFR; 3328 lastHFRPos = currentPosition; 3329 3330 // Keep moving in same direction (even if bad) for one more iteration to gather data points. 3331 if (HFRInc > 1) 3332 { 3333 // Looks like we're going away from optimal HFR 3334 reverseDir = true; 3335 HFRInc = 0; 3336 3337 qCDebug(KSTARS_EKOS_FOCUS) << "Focus is moving away from optimal HFR."; 3338 3339 // Let's set new limits 3340 if (m_LastFocusDirection == FOCUS_IN) 3341 { 3342 focusInLimit = currentPosition; 3343 qCDebug(KSTARS_EKOS_FOCUS) << "Setting focus IN limit to " << focusInLimit; 3344 } 3345 else 3346 { 3347 focusOutLimit = currentPosition; 3348 qCDebug(KSTARS_EKOS_FOCUS) << "Setting focus OUT limit to " << focusOutLimit; 3349 } 3350 3351 if (m_FocusAlgorithm == FOCUS_POLYNOMIAL && plot_position.count() > 4) 3352 { 3353 polynomialFit.reset(new PolynomialFit(2, 5, plot_position, plot_value)); 3354 double a = *std::min_element(plot_position.constBegin(), plot_position.constEnd()); 3355 double b = *std::max_element(plot_position.constBegin(), plot_position.constEnd()); 3356 double min_position = 0, min_hfr = 0; 3357 isVShapeSolution = polynomialFit->findMinimum(minHFRPos, a, b, &min_position, &min_hfr); 3358 qCDebug(KSTARS_EKOS_FOCUS) << "Found Minimum?" << (isVShapeSolution ? "Yes" : "No"); 3359 if (isVShapeSolution) 3360 { 3361 ignoreLimitedDelta = true; 3362 qCDebug(KSTARS_EKOS_FOCUS) << "Minimum Solution:" << min_hfr << "@" << min_position; 3363 targetPosition = round(min_position); 3364 appendLogText(i18n("Found polynomial solution @ %1", QString::number(min_position, 'f', 0))); 3365 3366 emit drawPolynomial(polynomialFit.get(), isVShapeSolution, true); 3367 emit minimumFound(min_position, min_hfr); 3368 } 3369 else 3370 { 3371 emit drawPolynomial(polynomialFit.get(), isVShapeSolution, false); 3372 } 3373 } 3374 } 3375 3376 if (HFRInc == 1) 3377 { 3378 // Keep going at same stride even if we are going away from CFZ 3379 // This is done to gather data points are the trough. 3380 if (std::abs(lastDelta) > 0) 3381 targetPosition = currentPosition + lastDelta; 3382 else 3383 targetPosition = currentPosition + pulseDuration; 3384 } 3385 else if (isVShapeSolution == false) 3386 { 3387 ignoreLimitedDelta = true; 3388 // Let's get close to the minimum HFR position so far detected 3389 if (m_LastFocusDirection == FOCUS_OUT) 3390 targetPosition = minHFRPos - pulseDuration / 2; 3391 else 3392 targetPosition = minHFRPos + pulseDuration / 2; 3393 } 3394 3395 qCDebug(KSTARS_EKOS_FOCUS) << "new targetPosition " << targetPosition; 3396 } 3397 3398 // Limit target Pulse to algorithm limits 3399 if (focusInLimit != 0 && m_LastFocusDirection == FOCUS_IN && targetPosition < focusInLimit) 3400 { 3401 targetPosition = focusInLimit; 3402 qCDebug(KSTARS_EKOS_FOCUS) << "Limiting target pulse to focus in limit " << targetPosition; 3403 } 3404 else if (focusOutLimit != 0 && m_LastFocusDirection == FOCUS_OUT && targetPosition > focusOutLimit) 3405 { 3406 targetPosition = focusOutLimit; 3407 qCDebug(KSTARS_EKOS_FOCUS) << "Limiting target pulse to focus out limit " << targetPosition; 3408 } 3409 3410 // Limit target pulse to focuser limits 3411 if (targetPosition < absMotionMin) 3412 targetPosition = absMotionMin; 3413 else if (targetPosition > absMotionMax) 3414 targetPosition = absMotionMax; 3415 3416 // We cannot go any further because of the device limits, this is a failure 3417 if (targetPosition == currentPosition) 3418 { 3419 // If case target position happens to be the minimal historical 3420 // HFR position, accept this value instead of bailing out. 3421 if (targetPosition == minHFRPos || isVShapeSolution) 3422 { 3423 appendLogText("Stopping at minimum recorded HFR position."); 3424 completeFocusProcedure(Ekos::FOCUS_COMPLETE); 3425 } 3426 else 3427 { 3428 QString message = i18n("Focuser cannot move further, device limits reached. Autofocus aborted."); 3429 appendLogText(message); 3430 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert); 3431 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3432 } 3433 return; 3434 } 3435 3436 // Too many fluctuatoins 3437 if (fluctuations >= MAXIMUM_FLUCTUATIONS) 3438 { 3439 QString message = i18n("Unstable fluctuations. Try increasing initial step size or exposure time."); 3440 appendLogText(message); 3441 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert); 3442 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3443 return; 3444 } 3445 3446 // Ops, deadlock 3447 if (focusOutLimit && focusOutLimit == focusInLimit) 3448 { 3449 QString message = i18n("Deadlock reached. Please try again with different settings."); 3450 appendLogText(message); 3451 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert); 3452 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3453 return; 3454 } 3455 3456 // Restrict the target position even more with the maximum travel option 3457 if (fabs(targetPosition - initialFocuserAbsPosition) > m_OpsFocusMechanics->focusMaxTravel->value()) 3458 { 3459 int minTravelLimit = qMax(0.0, initialFocuserAbsPosition - m_OpsFocusMechanics->focusMaxTravel->value()); 3460 int maxTravelLimit = qMin(absMotionMax, initialFocuserAbsPosition + m_OpsFocusMechanics->focusMaxTravel->value()); 3461 3462 // In case we are asked to go below travel limit, but we are not there yet 3463 // let us go there and see the result before aborting 3464 if (fabs(currentPosition - minTravelLimit) > 10 && targetPosition < minTravelLimit) 3465 { 3466 targetPosition = minTravelLimit; 3467 } 3468 // Same for max travel 3469 else if (fabs(currentPosition - maxTravelLimit) > 10 && targetPosition > maxTravelLimit) 3470 { 3471 targetPosition = maxTravelLimit; 3472 } 3473 else 3474 { 3475 qCDebug(KSTARS_EKOS_FOCUS) << "targetPosition (" << targetPosition << ") - initHFRAbsPos (" 3476 << initialFocuserAbsPosition << ") exceeds maxTravel distance of " << m_OpsFocusMechanics->focusMaxTravel->value(); 3477 3478 QString message = i18n("Maximum travel limit reached. Autofocus aborted."); 3479 appendLogText(message); 3480 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert); 3481 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3482 break; 3483 } 3484 } 3485 3486 // Get delta for next move 3487 lastDelta = (targetPosition - currentPosition); 3488 3489 qCDebug(KSTARS_EKOS_FOCUS) << "delta (targetPosition - currentPosition) " << lastDelta; 3490 3491 // Limit to Maximum permitted delta (Max Single Step Size) 3492 if (ignoreLimitedDelta == false) 3493 { 3494 double limitedDelta = qMax(-1.0 * m_OpsFocusMechanics->focusMaxSingleStep->value(), 3495 qMin(1.0 * m_OpsFocusMechanics->focusMaxSingleStep->value(), lastDelta)); 3496 if (std::fabs(limitedDelta - lastDelta) > 0) 3497 { 3498 qCDebug(KSTARS_EKOS_FOCUS) << "Limited delta to maximum permitted single step " << 3499 m_OpsFocusMechanics->focusMaxSingleStep->value(); 3500 lastDelta = limitedDelta; 3501 } 3502 } 3503 3504 m_LastFocusDirection = (lastDelta > 0) ? FOCUS_OUT : FOCUS_IN; 3505 if (!changeFocus(lastDelta, false)) 3506 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3507 3508 break; 3509 } 3510 } 3511 3512 void Focus::addPlotPosition(int pos, double value, bool plot) 3513 { 3514 plot_position.append(pos); 3515 plot_value.append(value); 3516 if (plot) 3517 emit newHFRPlotPosition(static_cast<double>(pos), value, 1.0, false, pulseDuration); 3518 } 3519 3520 // Synchronises the plot_position and plot_value vectors with the data used by linearFocuser 3521 // This keeps the 2 sets of data in sync for the Linear and Linear1Pass algorithms. 3522 // For Iterative and Polynomial these vectors are built during the focusing cycle so nothing to do here 3523 void Focus::updatePlotPosition() 3524 { 3525 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS || m_FocusAlgorithm == FOCUS_LINEAR) 3526 { 3527 QVector<double> weights; 3528 QVector<int> positions; 3529 linearFocuser->getMeasurements(&positions, &plot_value, &weights); 3530 plot_position.clear(); 3531 for (int i = 0; i < positions.count(); i++) 3532 plot_position.append(positions[i]); 3533 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS) 3534 { 3535 // For L1P add in the solution datapoint. Linear already has this included. 3536 plot_position.append(linearFocuser->solution()); 3537 plot_value.append(linearFocuser->solutionValue()); 3538 } 3539 } 3540 } 3541 3542 void Focus::autoFocusRel() 3543 { 3544 static int noStarCount = 0; 3545 static double minHFR = 1e6; 3546 QString deltaTxt = QString("%1").arg(fabs(currentHFR - minHFR) * 100.0, 0, 'g', 2); 3547 QString minHFRText = QString("%1").arg(minHFR, 0, 'g', 3); 3548 QString HFRText = QString("%1").arg(currentHFR, 0, 'g', 3); 3549 3550 appendLogText(i18n("FITS received. HFR %1. Delta (%2%) Min HFR (%3)", HFRText, deltaTxt, minHFRText)); 3551 3552 if (pulseDuration <= MINIMUM_PULSE_TIMER) 3553 { 3554 appendLogText(i18n("Autofocus failed to reach proper focus. Try adjusting the tolerance value.")); 3555 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3556 return; 3557 } 3558 3559 // No stars detected, try to capture again 3560 if (currentHFR == INVALID_STAR_MEASURE) 3561 { 3562 if (noStarCount < MAX_RECAPTURE_RETRIES) 3563 { 3564 noStarCount++; 3565 appendLogText(i18n("No stars detected, capturing again...")); 3566 capture(); 3567 return; 3568 } 3569 else if (m_FocusAlgorithm == FOCUS_LINEAR || m_FocusAlgorithm == FOCUS_LINEAR1PASS) 3570 { 3571 appendLogText(i18n("Failed to detect any stars at position %1. Continuing...", currentPosition)); 3572 noStarCount = 0; 3573 } 3574 else if(!m_OpsFocusProcess->focusDonut->isChecked()) 3575 { 3576 // Carry on for donut detection 3577 appendLogText(i18n("Failed to detect any stars. Reset frame and try again.")); 3578 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3579 return; 3580 } 3581 } 3582 else 3583 noStarCount = 0; 3584 3585 switch (m_LastFocusDirection) 3586 { 3587 case FOCUS_NONE: 3588 lastHFR = currentHFR; 3589 minHFR = 1e6; 3590 m_LastFocusDirection = FOCUS_IN; 3591 changeFocus(-pulseDuration, false); 3592 break; 3593 3594 case FOCUS_IN: 3595 case FOCUS_OUT: 3596 if (fabs(currentHFR - minHFR) < (m_OpsFocusProcess->focusTolerance->value() / 100.0) && HFRInc == 0) 3597 { 3598 completeFocusProcedure(Ekos::FOCUS_COMPLETE); 3599 } 3600 else if (currentHFR < lastHFR) 3601 { 3602 if (currentHFR < minHFR) 3603 minHFR = currentHFR; 3604 3605 lastHFR = currentHFR; 3606 changeFocus(m_LastFocusDirection == FOCUS_IN ? -pulseDuration : pulseDuration, false); 3607 HFRInc = 0; 3608 } 3609 else 3610 { 3611 //HFRInc++; 3612 3613 lastHFR = currentHFR; 3614 3615 HFRInc = 0; 3616 3617 pulseDuration *= 0.75; 3618 3619 if (!changeFocus(m_LastFocusDirection == FOCUS_IN ? pulseDuration : -pulseDuration, false)) 3620 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3621 3622 // HFR getting worse so reverse direction 3623 m_LastFocusDirection = (m_LastFocusDirection == FOCUS_IN) ? FOCUS_OUT : FOCUS_IN; 3624 } 3625 break; 3626 } 3627 } 3628 3629 void Focus::autoFocusProcessPositionChange(IPState state) 3630 { 3631 if (state == IPS_OK) 3632 { 3633 // Normally, if we are auto-focusing, after we move the focuser we capture an image. 3634 // However, the Linear algorithm, at the start of its passes, requires two 3635 // consecutive focuser moves--the first out further than we want, and a second 3636 // move back in, so that we eliminate backlash and are always moving in before a capture. 3637 if (focuserAdditionalMovement > 0) 3638 { 3639 int temp = focuserAdditionalMovement; 3640 focuserAdditionalMovement = 0; 3641 qCDebug(KSTARS_EKOS_FOCUS) << QString("Undoing overscan extension. Moving back in by %1").arg(temp); 3642 3643 if (!changeFocus(-temp, focuserAdditionalMovementUpdateDir)) 3644 { 3645 appendLogText(i18n("Focuser error, check INDI panel.")); 3646 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3647 } 3648 } 3649 else if (inAutoFocus) 3650 { 3651 qCDebug(KSTARS_EKOS_FOCUS) << QString("Focus position reached at %1, starting capture in %2 seconds.").arg( 3652 currentPosition).arg(m_OpsFocusMechanics->focusSettleTime->value()); 3653 // Adjust exposure if Donut Buster activated 3654 if (m_OpsFocusProcess->focusDonut->isChecked()) 3655 donutTimeDilation(); 3656 capture(m_OpsFocusMechanics->focusSettleTime->value()); 3657 } 3658 } 3659 else if (state == IPS_ALERT) 3660 { 3661 appendLogText(i18n("Focuser error, check INDI panel.")); 3662 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3663 } 3664 else 3665 qCDebug(KSTARS_EKOS_FOCUS) << 3666 QString("autoFocusProcessPositionChange called with state %1 (%2), focuserAdditionalMovement=%3, inAutoFocus=%4, captureInProgress=%5, currentPosition=%6") 3667 .arg(state).arg(pstateStr(state)).arg(focuserAdditionalMovement).arg(inAutoFocus).arg(captureInProgress) 3668 .arg(currentPosition); 3669 } 3670 3671 // This routine adjusts capture exposure during Autofocus depending on donut parameter settings 3672 void Focus::donutTimeDilation() 3673 { 3674 if (m_OpsFocusProcess->focusTimeDilation->value() == 1.0) 3675 return; 3676 3677 // Get the max distance from focus to outer points 3678 const double centre = (m_OpsFocusMechanics->focusNumSteps->value() + 1.0) / 2.0; 3679 // Get the current step - treat the final 3680 const int currentStep = linearFocuser->currentStep() + 1; 3681 double distance; 3682 if (currentStep <= m_OpsFocusMechanics->focusNumSteps->value()) 3683 distance = std::abs(centre - currentStep); 3684 else 3685 // Last step is always back to focus 3686 distance = 0.0; 3687 double multiplier = distance / (centre - 1.0) * m_OpsFocusProcess->focusTimeDilation->value(); 3688 multiplier = std::max(multiplier, 1.0); 3689 const double exposure = multiplier * m_donutOrigExposure; 3690 focusExposure->setValue(exposure); 3691 qCDebug(KSTARS_EKOS_FOCUS) << "Donut time dilation for point " << currentStep << " from " << m_donutOrigExposure << " to " 3692 << exposure; 3693 3694 } 3695 3696 void Focus::updateProperty(INDI::Property prop) 3697 { 3698 if (m_Focuser == nullptr || prop.getType() != INDI_NUMBER || prop.getDeviceName() != m_Focuser->getDeviceName()) 3699 return; 3700 3701 auto nvp = prop.getNumber(); 3702 3703 // Only process focus properties 3704 if (QString(nvp->getName()).contains("focus", Qt::CaseInsensitive) == false) 3705 return; 3706 3707 if (nvp->isNameMatch("FOCUS_BACKLASH_STEPS")) 3708 { 3709 m_OpsFocusMechanics->focusBacklash->setValue(nvp->np[0].value); 3710 return; 3711 } 3712 3713 if (nvp->isNameMatch("ABS_FOCUS_POSITION")) 3714 { 3715 if (m_DebugFocuser) 3716 { 3717 // Simulate focuser comms issues every 5 moves 3718 if (m_DebugFocuserCounter++ >= 10 && m_DebugFocuserCounter <= 14) 3719 { 3720 if (m_DebugFocuserCounter == 14) 3721 m_DebugFocuserCounter = 0; 3722 appendLogText(i18n("Simulate focuser comms failure...")); 3723 return; 3724 } 3725 } 3726 3727 m_FocusMotionTimer.stop(); 3728 INumber *pos = IUFindNumber(nvp, "FOCUS_ABSOLUTE_POSITION"); 3729 3730 // FIXME: We should check state validity, but some focusers do not care - make ignore an option! 3731 if (pos) 3732 { 3733 int newPosition = static_cast<int>(pos->value); 3734 3735 // Some absolute focuser constantly report the position without a state change. 3736 // Therefore we ignore it if both value and state are the same as last time. 3737 // HACK: This would shortcut the autofocus procedure reset, see completeFocusProcedure for the small hack 3738 if (currentPosition == newPosition && currentPositionState == nvp->s) 3739 { 3740 qCDebug(KSTARS_EKOS_FOCUS) << "Focuser position " << currentPosition << " and state:" 3741 << pstateStr(currentPositionState) << " unchanged"; 3742 return; 3743 } 3744 3745 currentPositionState = nvp->s; 3746 3747 if (currentPosition != newPosition) 3748 { 3749 currentPosition = newPosition; 3750 qCDebug(KSTARS_EKOS_FOCUS) << "Abs Focuser position changed to " << currentPosition << "State:" << pstateStr( 3751 currentPositionState); 3752 absTicksLabel->setText(QString::number(currentPosition)); 3753 emit absolutePositionChanged(currentPosition); 3754 } 3755 } 3756 3757 if (nvp->s != IPS_OK) 3758 { 3759 if (inAutoFocus || inAdjustFocus || adaptFocus->inAdaptiveFocus()) 3760 { 3761 // We had something back from the focuser but we're not done yet, so 3762 // restart motion timer in case focuser gets stuck. 3763 qCDebug(KSTARS_EKOS_FOCUS) << "Restarting focus motion timer, state " << pstateStr(nvp->s); 3764 m_FocusMotionTimer.start(); 3765 } 3766 } 3767 else 3768 { 3769 // Systematically reset UI when focuser finishes moving 3770 if (focuserAdditionalMovement == 0) 3771 resetButtons(); 3772 3773 if (inAdjustFocus) 3774 { 3775 if (focuserAdditionalMovement == 0) 3776 { 3777 inAdjustFocus = false; 3778 emit focusPositionAdjusted(); 3779 return; 3780 } 3781 } 3782 3783 if (adaptFocus->inAdaptiveFocus()) 3784 { 3785 if (focuserAdditionalMovement == 0) 3786 { 3787 adaptFocus->adaptiveFocusAdmin(currentPosition, true, true); 3788 return; 3789 } 3790 } 3791 3792 if (m_RestartState == RESTART_NOW && status() != Ekos::FOCUS_ABORTED) 3793 { 3794 if (focuserAdditionalMovement == 0) 3795 { 3796 m_RestartState = RESTART_NONE; 3797 inAutoFocus = inAdjustFocus = false; 3798 adaptFocus->setInAdaptiveFocus(false); 3799 appendLogText(i18n("Restarting autofocus process...")); 3800 start(); 3801 } 3802 } 3803 else if (m_RestartState == RESTART_ABORT) 3804 { 3805 // We are trying to abort an autofocus run 3806 // This event means that the focuser has been reset and arrived at its starting point 3807 // so we can finish processing the abort. Set inAutoFocus to avoid repeating 3808 // processing already done in completeFocusProcedure 3809 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3810 m_RestartState = RESTART_NONE; 3811 inAutoFocus = inAdjustFocus = false; 3812 adaptFocus->setInAdaptiveFocus(false); 3813 } 3814 } 3815 3816 if (canAbsMove) 3817 autoFocusProcessPositionChange(nvp->s); 3818 else if (nvp->s == IPS_ALERT) 3819 appendLogText(i18n("Focuser error, check INDI panel.")); 3820 return; 3821 } 3822 3823 if (canAbsMove) 3824 return; 3825 3826 if (nvp->isNameMatch("manualfocusdrive")) 3827 { 3828 if (m_DebugFocuser) 3829 { 3830 // Simulate focuser comms issues every 5 moves 3831 if (m_DebugFocuserCounter++ >= 10 && m_DebugFocuserCounter <= 14) 3832 { 3833 if (m_DebugFocuserCounter == 14) 3834 m_DebugFocuserCounter = 0; 3835 appendLogText(i18n("Simulate focuser comms failure...")); 3836 return; 3837 } 3838 } 3839 3840 m_FocusMotionTimer.stop(); 3841 3842 INumber *pos = IUFindNumber(nvp, "manualfocusdrive"); 3843 if (pos && nvp->s == IPS_OK) 3844 { 3845 currentPosition += pos->value; 3846 absTicksLabel->setText(QString::number(static_cast<int>(currentPosition))); 3847 emit absolutePositionChanged(currentPosition); 3848 } 3849 3850 if (inAdjustFocus && nvp->s == IPS_OK) 3851 { 3852 if (focuserAdditionalMovement == 0) 3853 { 3854 inAdjustFocus = false; 3855 emit focusPositionAdjusted(); 3856 return; 3857 } 3858 } 3859 3860 if (adaptFocus->inAdaptiveFocus() && nvp->s == IPS_OK) 3861 { 3862 if (focuserAdditionalMovement == 0) 3863 { 3864 adaptFocus->adaptiveFocusAdmin(currentPosition, true, true); 3865 return; 3866 } 3867 } 3868 3869 // restart if focus movement has finished 3870 if (m_RestartState == RESTART_NOW && nvp->s == IPS_OK && status() != Ekos::FOCUS_ABORTED) 3871 { 3872 if (focuserAdditionalMovement == 0) 3873 { 3874 m_RestartState = RESTART_NONE; 3875 inAutoFocus = inAdjustFocus = false; 3876 adaptFocus->setInAdaptiveFocus(false); 3877 appendLogText(i18n("Restarting autofocus process...")); 3878 start(); 3879 } 3880 } 3881 else if (m_RestartState == RESTART_ABORT && nvp->s == IPS_OK) 3882 { 3883 // Abort the autofocus run now the focuser has finished moving to its start position 3884 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3885 m_RestartState = RESTART_NONE; 3886 inAutoFocus = inAdjustFocus = false; 3887 adaptFocus->setInAdaptiveFocus(false); 3888 } 3889 3890 if (canRelMove) 3891 autoFocusProcessPositionChange(nvp->s); 3892 else if (nvp->s == IPS_ALERT) 3893 appendLogText(i18n("Focuser error, check INDI panel.")); 3894 3895 return; 3896 } 3897 3898 if (nvp->isNameMatch("REL_FOCUS_POSITION")) 3899 { 3900 m_FocusMotionTimer.stop(); 3901 3902 INumber *pos = IUFindNumber(nvp, "FOCUS_RELATIVE_POSITION"); 3903 if (pos && nvp->s == IPS_OK) 3904 { 3905 currentPosition += pos->value * (m_LastFocusDirection == FOCUS_IN ? -1 : 1); 3906 qCDebug(KSTARS_EKOS_FOCUS) 3907 << QString("Rel Focuser position moved %1 by %2 to %3") 3908 .arg((m_LastFocusDirection == FOCUS_IN) ? "in" : "out").arg(pos->value).arg(currentPosition); 3909 absTicksLabel->setText(QString::number(static_cast<int>(currentPosition))); 3910 emit absolutePositionChanged(currentPosition); 3911 } 3912 3913 if (inAdjustFocus && nvp->s == IPS_OK) 3914 { 3915 if (focuserAdditionalMovement == 0) 3916 { 3917 inAdjustFocus = false; 3918 emit focusPositionAdjusted(); 3919 return; 3920 } 3921 } 3922 3923 if (adaptFocus->inAdaptiveFocus() && nvp->s == IPS_OK) 3924 { 3925 if (focuserAdditionalMovement == 0) 3926 { 3927 adaptFocus->adaptiveFocusAdmin(currentPosition, true, true); 3928 return; 3929 } 3930 } 3931 3932 // restart if focus movement has finished 3933 if (m_RestartState == RESTART_NOW && nvp->s == IPS_OK && status() != Ekos::FOCUS_ABORTED) 3934 { 3935 if (focuserAdditionalMovement == 0) 3936 { 3937 m_RestartState = RESTART_NONE; 3938 inAutoFocus = inAdjustFocus = false; 3939 adaptFocus->setInAdaptiveFocus(false); 3940 appendLogText(i18n("Restarting autofocus process...")); 3941 start(); 3942 } 3943 } 3944 else if (m_RestartState == RESTART_ABORT && nvp->s == IPS_OK) 3945 { 3946 // Abort the autofocus run now the focuser has finished moving to its start position 3947 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3948 m_RestartState = RESTART_NONE; 3949 inAutoFocus = inAdjustFocus = false; 3950 adaptFocus->setInAdaptiveFocus(false); 3951 } 3952 3953 if (canRelMove) 3954 autoFocusProcessPositionChange(nvp->s); 3955 else if (nvp->s == IPS_ALERT) 3956 appendLogText(i18n("Focuser error, check INDI panel.")); 3957 3958 return; 3959 } 3960 3961 if (canRelMove) 3962 return; 3963 3964 if (nvp->isNameMatch("FOCUS_TIMER")) 3965 { 3966 m_FocusMotionTimer.stop(); 3967 // restart if focus movement has finished 3968 if (m_RestartState == RESTART_NOW && nvp->s == IPS_OK && status() != Ekos::FOCUS_ABORTED) 3969 { 3970 if (focuserAdditionalMovement == 0) 3971 { 3972 m_RestartState = RESTART_NONE; 3973 inAutoFocus = inAdjustFocus = false; 3974 adaptFocus->setInAdaptiveFocus(false); 3975 appendLogText(i18n("Restarting autofocus process...")); 3976 start(); 3977 } 3978 } 3979 else if (m_RestartState == RESTART_ABORT && nvp->s == IPS_OK) 3980 { 3981 // Abort the autofocus run now the focuser has finished moving to its start position 3982 completeFocusProcedure(Ekos::FOCUS_ABORTED); 3983 m_RestartState = RESTART_NONE; 3984 inAutoFocus = inAdjustFocus = false; 3985 adaptFocus->setInAdaptiveFocus(false); 3986 } 3987 3988 if (canAbsMove == false && canRelMove == false) 3989 { 3990 // Used by the linear focus algorithm. Ignored if that's not in use for the timer-focuser. 3991 INumber *pos = IUFindNumber(nvp, "FOCUS_TIMER_VALUE"); 3992 if (pos) 3993 { 3994 currentPosition += pos->value * (m_LastFocusDirection == FOCUS_IN ? -1 : 1); 3995 qCDebug(KSTARS_EKOS_FOCUS) 3996 << QString("Timer Focuser position moved %1 by %2 to %3") 3997 .arg((m_LastFocusDirection == FOCUS_IN) ? "in" : "out").arg(pos->value).arg(currentPosition); 3998 } 3999 4000 if (inAdjustFocus && nvp->s == IPS_OK) 4001 { 4002 if (focuserAdditionalMovement == 0) 4003 { 4004 inAdjustFocus = false; 4005 emit focusPositionAdjusted(); 4006 return; 4007 } 4008 } 4009 4010 if (adaptFocus->inAdaptiveFocus() && nvp->s == IPS_OK) 4011 { 4012 if (focuserAdditionalMovement == 0) 4013 { 4014 adaptFocus->adaptiveFocusAdmin(true, true, true); 4015 return; 4016 } 4017 } 4018 4019 autoFocusProcessPositionChange(nvp->s); 4020 } 4021 else if (nvp->s == IPS_ALERT) 4022 appendLogText(i18n("Focuser error, check INDI panel.")); 4023 4024 return; 4025 } 4026 } 4027 4028 void Focus::appendLogText(const QString &text) 4029 { 4030 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", 4031 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text)); 4032 4033 qCInfo(KSTARS_EKOS_FOCUS) << text; 4034 4035 emit newLog(text); 4036 } 4037 4038 void Focus::clearLog() 4039 { 4040 m_LogText.clear(); 4041 emit newLog(QString()); 4042 } 4043 4044 void Focus::appendFocusLogText(const QString &lines) 4045 { 4046 if (Options::focusLogging()) 4047 { 4048 4049 if (!m_FocusLogFile.exists()) 4050 { 4051 // Create focus-specific log file and write the header record 4052 QDir dir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); 4053 dir.mkpath("focuslogs"); 4054 m_FocusLogEnabled = m_FocusLogFile.open(QIODevice::WriteOnly | QIODevice::Text); 4055 if (m_FocusLogEnabled) 4056 { 4057 QTextStream header(&m_FocusLogFile); 4058 header << "date, time, position, temperature, filter, HFR, altitude\n"; 4059 header.flush(); 4060 } 4061 else 4062 qCWarning(KSTARS_EKOS_FOCUS) << "Failed to open focus log file: " << m_FocusLogFileName; 4063 } 4064 4065 if (m_FocusLogEnabled) 4066 { 4067 QTextStream out(&m_FocusLogFile); 4068 out << QDateTime::currentDateTime().toString("yyyy-MM-dd, hh:mm:ss, ") << lines; 4069 out.flush(); 4070 } 4071 } 4072 } 4073 4074 void Focus::startFraming() 4075 { 4076 if (m_Camera == nullptr) 4077 { 4078 appendLogText(i18n("No CCD connected.")); 4079 return; 4080 } 4081 4082 waitStarSelectTimer.stop(); 4083 4084 inFocusLoop = true; 4085 starMeasureFrames.clear(); 4086 4087 clearDataPoints(); 4088 4089 //emit statusUpdated(true); 4090 setState(Ekos::FOCUS_FRAMING); 4091 4092 resetButtons(); 4093 4094 appendLogText(i18n("Starting continuous exposure...")); 4095 4096 capture(); 4097 } 4098 4099 void Focus::resetButtons() 4100 { 4101 if (inFocusLoop) 4102 { 4103 startFocusB->setEnabled(false); 4104 startAbInsB->setEnabled(false); 4105 startLoopB->setEnabled(false); 4106 focusOutB->setEnabled(true); 4107 focusInB->setEnabled(true); 4108 startGotoB->setEnabled(canAbsMove); 4109 stopFocusB->setEnabled(true); 4110 captureB->setEnabled(false); 4111 opticalTrainCombo->setEnabled(false); 4112 trainB->setEnabled(false); 4113 return; 4114 } 4115 4116 if (inAutoFocus) 4117 { 4118 // During an Autofocus run we need to disable input widgets to stop the user changing them 4119 // We need to disable the widgets currently enabled and save a QVector of these to be 4120 // reinstated once the AF run completes. 4121 // Certain widgets (highlighted below) have the isEnabled() property used in the code to 4122 // determine functionality. So the isEnabled state for these is saved off before the 4123 // interface is disabled and these saved states used to control the code path and preserve 4124 // functionality. 4125 // Since this function can be called several times only load up widgets once 4126 if (disabledWidgets.empty()) 4127 { 4128 AFDisable(trainLabel, false); 4129 AFDisable(opticalTrainCombo, false); 4130 AFDisable(trainB, false); 4131 AFDisable(focuserGroup, true); 4132 AFDisable(clearDataB, false); 4133 4134 // In the ccdGroup save the enabled state of Gain and ISO 4135 m_FocusGainAFEnabled = focusGain->isEnabled(); 4136 m_FocusISOAFEnabled = focusISO->isEnabled(); 4137 AFDisable(ccdGroup, false); 4138 4139 AFDisable(toolsGroup, false); 4140 4141 // Save the enabled state of SubFrame 4142 m_FocusSubFrameAFEnabled = m_OpsFocusSettings->focusSubFrame->isEnabled(); 4143 4144 // Disable parameters and tools to prevent changes while Autofocus is running 4145 AFDisable(m_OpsFocusSettings, false); 4146 AFDisable(m_OpsFocusProcess, false); 4147 AFDisable(m_OpsFocusMechanics, false); 4148 AFDisable(m_AdvisorDialog, false); 4149 AFDisable(m_CFZDialog, false); 4150 4151 // Enable the "stop" button so the user can abort an AF run 4152 stopFocusB->setEnabled(true); 4153 } 4154 return; 4155 } 4156 4157 // Restore widgets that were disabled when Autofocus started 4158 for(int i = 0 ; i < disabledWidgets.size() ; i++) 4159 disabledWidgets[i]->setEnabled(true); 4160 disabledWidgets.clear(); 4161 4162 auto enableCaptureButtons = (captureInProgress == false && hfrInProgress == false); 4163 4164 captureB->setEnabled(enableCaptureButtons); 4165 resetFrameB->setEnabled(enableCaptureButtons); 4166 startLoopB->setEnabled(enableCaptureButtons); 4167 m_OpsFocusSettings->focusAutoStarEnabled->setEnabled(enableCaptureButtons 4168 && m_OpsFocusSettings->focusUseFullField->isChecked() == false); 4169 4170 if (m_Focuser && m_Focuser->isConnected()) 4171 { 4172 focusOutB->setEnabled(true); 4173 focusInB->setEnabled(true); 4174 4175 startFocusB->setEnabled(m_FocusType == FOCUS_AUTO); 4176 startAbInsB->setEnabled(canAbInsStart()); 4177 stopFocusB->setEnabled(!enableCaptureButtons); 4178 startGotoB->setEnabled(canAbsMove); 4179 stopGotoB->setEnabled(true); 4180 } 4181 else 4182 { 4183 focusOutB->setEnabled(false); 4184 focusInB->setEnabled(false); 4185 4186 startFocusB->setEnabled(false); 4187 startAbInsB->setEnabled(false); 4188 stopFocusB->setEnabled(false); 4189 startGotoB->setEnabled(false); 4190 stopGotoB->setEnabled(false); 4191 } 4192 } 4193 4194 // Return whether the Aberration Inspector Start button should be enabled. The pre-requisties are: 4195 // - Absolute position focuser 4196 // - Mosaic Mask is on 4197 // - Algorithm is LINEAR 1 PASS 4198 bool Focus::canAbInsStart() 4199 { 4200 return canAbsMove && m_FocusAlgorithm == FOCUS_LINEAR1PASS && m_currentImageMask == FOCUS_MASK_MOSAIC; 4201 } 4202 4203 // Disable input widgets during an Autofocus run. Keep a record so after the AF run, widgets can be re-enabled 4204 void Focus::AFDisable(QWidget * widget, const bool children) 4205 { 4206 if (children) 4207 { 4208 // The parent widget has been passed in so disable any enabled child widgets 4209 for(auto *wid : widget->findChildren<QWidget *>()) 4210 { 4211 if (wid->isEnabled()) 4212 { 4213 wid->setEnabled(false); 4214 disabledWidgets.push_back(wid); 4215 } 4216 } 4217 4218 } 4219 else if (widget->isEnabled()) 4220 { 4221 // Base level widget or group of widgets, so just disable the passed what was passed in 4222 widget->setEnabled(false); 4223 disabledWidgets.push_back(widget); 4224 } 4225 } 4226 4227 bool Focus::isFocusGainEnabled() 4228 { 4229 return (inAutoFocus) ? m_FocusGainAFEnabled : focusGain->isEnabled(); 4230 } 4231 4232 bool Focus::isFocusISOEnabled() 4233 { 4234 return (inAutoFocus) ? m_FocusISOAFEnabled : focusISO->isEnabled(); 4235 } 4236 4237 bool Focus::isFocusSubFrameEnabled() 4238 { 4239 return (inAutoFocus) ? m_FocusSubFrameAFEnabled : m_OpsFocusSettings->focusSubFrame->isEnabled(); 4240 } 4241 4242 void Focus::updateBoxSize(int value) 4243 { 4244 if (m_Camera == nullptr) 4245 return; 4246 4247 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 4248 4249 if (targetChip == nullptr) 4250 return; 4251 4252 int subBinX, subBinY; 4253 targetChip->getBinning(&subBinX, &subBinY); 4254 4255 QRect trackBox = m_FocusView->getTrackingBox(); 4256 QPoint center(trackBox.x() + (trackBox.width() / 2), trackBox.y() + (trackBox.height() / 2)); 4257 4258 trackBox = 4259 QRect(center.x() - value / (2 * subBinX), center.y() - value / (2 * subBinY), value / subBinX, value / subBinY); 4260 4261 m_FocusView->setTrackingBox(trackBox); 4262 } 4263 4264 void Focus::selectFocusStarFraction(double x, double y) 4265 { 4266 if (m_ImageData.isNull()) 4267 return; 4268 4269 focusStarSelected(x * m_ImageData->width(), y * m_ImageData->height()); 4270 // Focus view timer takes 50ms second to update, so let's emit afterwards. 4271 QTimer::singleShot(250, this, [this]() 4272 { 4273 emit newImage(m_FocusView); 4274 }); 4275 } 4276 4277 void Focus::focusStarSelected(int x, int y) 4278 { 4279 if (state() == Ekos::FOCUS_PROGRESS) 4280 return; 4281 4282 if (subFramed == false) 4283 { 4284 rememberStarCenter.setX(x); 4285 rememberStarCenter.setY(y); 4286 } 4287 4288 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 4289 4290 int subBinX, subBinY; 4291 targetChip->getBinning(&subBinX, &subBinY); 4292 4293 // If binning was changed outside of the focus module, recapture 4294 if (subBinX != (focusBinning->currentIndex() + 1)) 4295 { 4296 capture(); 4297 return; 4298 } 4299 4300 int offset = (static_cast<double>(m_OpsFocusSettings->focusBoxSize->value()) / subBinX) * 1.5; 4301 4302 QRect starRect; 4303 4304 bool squareMovedOutside = false; 4305 4306 if (subFramed == false && m_OpsFocusSettings->focusSubFrame->isChecked() && targetChip->canSubframe()) 4307 { 4308 int minX, maxX, minY, maxY, minW, maxW, minH, maxH; //, fx,fy,fw,fh; 4309 4310 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH); 4311 //targetChip->getFrame(&fx, &fy, &fw, &fy); 4312 4313 x = (x - offset) * subBinX; 4314 y = (y - offset) * subBinY; 4315 int w = offset * 2 * subBinX; 4316 int h = offset * 2 * subBinY; 4317 4318 if (x < minX) 4319 x = minX; 4320 if (y < minY) 4321 y = minY; 4322 if ((x + w) > maxW) 4323 w = maxW - x; 4324 if ((y + h) > maxH) 4325 h = maxH - y; 4326 4327 //fx += x; 4328 //fy += y; 4329 //fw = w; 4330 //fh = h; 4331 4332 //targetChip->setFocusFrame(fx, fy, fw, fh); 4333 //frameModified=true; 4334 4335 QVariantMap settings = frameSettings[targetChip]; 4336 settings["x"] = x; 4337 settings["y"] = y; 4338 settings["w"] = w; 4339 settings["h"] = h; 4340 settings["binx"] = subBinX; 4341 settings["biny"] = subBinY; 4342 4343 frameSettings[targetChip] = settings; 4344 4345 subFramed = true; 4346 4347 qCDebug(KSTARS_EKOS_FOCUS) << "Frame is subframed. X:" << x << "Y:" << y << "W:" << w << "H:" << h << "binX:" << subBinX << 4348 "binY:" << subBinY; 4349 4350 m_FocusView->setFirstLoad(true); 4351 4352 capture(); 4353 4354 //starRect = QRect((w-focusBoxSize->value())/(subBinX*2), (h-focusBoxSize->value())/(subBinY*2), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY); 4355 starCenter.setX(w / (2 * subBinX)); 4356 starCenter.setY(h / (2 * subBinY)); 4357 } 4358 else 4359 { 4360 //starRect = QRect(x-focusBoxSize->value()/(subBinX*2), y-focusBoxSize->value()/(subBinY*2), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY); 4361 double dist = sqrt((starCenter.x() - x) * (starCenter.x() - x) + (starCenter.y() - y) * (starCenter.y() - y)); 4362 4363 squareMovedOutside = (dist > (static_cast<double>(m_OpsFocusSettings->focusBoxSize->value()) / subBinX)); 4364 starCenter.setX(x); 4365 starCenter.setY(y); 4366 //starRect = QRect( starCenter.x()-focusBoxSize->value()/(2*subBinX), starCenter.y()-focusBoxSize->value()/(2*subBinY), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY); 4367 starRect = QRect(starCenter.x() - m_OpsFocusSettings->focusBoxSize->value() / (2 * subBinX), 4368 starCenter.y() - m_OpsFocusSettings->focusBoxSize->value() / (2 * subBinY), 4369 m_OpsFocusSettings->focusBoxSize->value() / subBinX, 4370 m_OpsFocusSettings->focusBoxSize->value() / subBinY); 4371 m_FocusView->setTrackingBox(starRect); 4372 } 4373 4374 starsHFR.clear(); 4375 4376 starCenter.setZ(subBinX); 4377 4378 if (squareMovedOutside && inAutoFocus == false && m_OpsFocusSettings->focusAutoStarEnabled->isChecked()) 4379 { 4380 m_OpsFocusSettings->focusAutoStarEnabled->blockSignals(true); 4381 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(false); 4382 m_OpsFocusSettings->focusAutoStarEnabled->blockSignals(false); 4383 appendLogText(i18n("Disabling Auto Star Selection as star selection box was moved manually.")); 4384 starSelected = false; 4385 } 4386 else if (starSelected == false) 4387 { 4388 appendLogText(i18n("Focus star is selected.")); 4389 starSelected = true; 4390 capture(); 4391 } 4392 4393 waitStarSelectTimer.stop(); 4394 FocusState nextState = inAutoFocus ? FOCUS_PROGRESS : FOCUS_IDLE; 4395 if (nextState != state()) 4396 { 4397 setState(nextState); 4398 } 4399 } 4400 4401 void Focus::checkFocus(double requiredHFR) 4402 { 4403 if (inAutoFocus || inFocusLoop || inAdjustFocus || adaptFocus->inAdaptiveFocus() || inBuildOffsets) 4404 { 4405 qCDebug(KSTARS_EKOS_FOCUS) << "Check Focus rejected, focus procedure is already running."; 4406 } 4407 else 4408 { 4409 qCDebug(KSTARS_EKOS_FOCUS) << "Check Focus requested with minimum required HFR" << requiredHFR; 4410 minimumRequiredHFR = requiredHFR; 4411 4412 appendLogText("Capturing to check HFR..."); 4413 capture(); 4414 } 4415 } 4416 4417 // Start an AF run. This is called from Build Offsets but could be extended in the future 4418 void Focus::runAutoFocus(bool buildOffsets) 4419 { 4420 if (inAutoFocus || inFocusLoop || inAdjustFocus || adaptFocus->inAdaptiveFocus() || inBuildOffsets) 4421 qCDebug(KSTARS_EKOS_FOCUS) << "runAutoFocus rejected, focus procedure is already running."; 4422 else 4423 { 4424 // Set the inBuildOffsets flag and start the AF run 4425 inBuildOffsets = buildOffsets; 4426 start(); 4427 } 4428 } 4429 4430 void Focus::toggleSubframe(bool enable) 4431 { 4432 if (enable == false) 4433 resetFrame(); 4434 4435 starSelected = false; 4436 starCenter = QVector3D(); 4437 4438 if (enable) 4439 { 4440 // sub frame focusing 4441 m_OpsFocusSettings->focusAutoStarEnabled->setEnabled(true); 4442 // disable focus image mask 4443 m_OpsFocusSettings->focusNoMaskRB->setChecked(true); 4444 } 4445 else 4446 { 4447 // full frame focusing 4448 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(false); 4449 m_OpsFocusSettings->focusAutoStarEnabled->setEnabled(false); 4450 } 4451 // update image mask controls 4452 selectImageMask(); 4453 // enable focus mask selection if full field is selected 4454 m_OpsFocusSettings->focusRingMaskRB->setEnabled(!enable); 4455 m_OpsFocusSettings->focusMosaicMaskRB->setEnabled(!enable); 4456 4457 setUseWeights(); 4458 } 4459 4460 // Set the useWeights widget based on various other user selected parameters 4461 // 1. weights are only available with the LM solver used by Hyperbola and Parabola 4462 // 2. weights are only used for multiple stars so only if full frame is selected 4463 // 3. weights are only used for star measures involving multiple star measures: HFR, HFR_ADJ and FWHM 4464 void Focus::setUseWeights() 4465 { 4466 if (m_CurveFit == CurveFitting::FOCUS_QUADRATIC || !m_OpsFocusSettings->focusUseFullField->isChecked() 4467 || m_StarMeasure == FOCUS_STAR_NUM_STARS 4468 || m_StarMeasure == FOCUS_STAR_FOURIER_POWER) 4469 { 4470 m_OpsFocusProcess->focusUseWeights->setEnabled(false); 4471 m_OpsFocusProcess->focusUseWeights->setChecked(false); 4472 } 4473 else 4474 m_OpsFocusProcess->focusUseWeights->setEnabled(true); 4475 4476 } 4477 4478 // Set the setDonutBuster widget based on various other user selected parameters 4479 // 1. Donut Buster is only available for algorithm: Linear 1 Pass 4480 // 2. Donut Buster is available for measures: HFR, HFR Adj and FWHM 4481 // 3. Donut Buster is available for walks: Fixed and CFZ Shuffle 4482 void Focus::setDonutBuster() 4483 { 4484 if (m_FocusAlgorithm != FOCUS_LINEAR1PASS) 4485 { 4486 m_OpsFocusProcess->focusDonut->hide(); 4487 m_OpsFocusProcess->focusDonut->setEnabled(false); 4488 m_OpsFocusProcess->focusDonut->setChecked(false); 4489 } 4490 else 4491 { 4492 m_OpsFocusProcess->focusDonut->show(); 4493 if ((m_StarMeasure == FOCUS_STAR_HFR || m_StarMeasure == FOCUS_STAR_HFR_ADJ || m_StarMeasure == FOCUS_STAR_FWHM) && 4494 (m_FocusWalk == FOCUS_WALK_FIXED_STEPS || m_FocusWalk == FOCUS_WALK_CFZ_SHUFFLE)) 4495 m_OpsFocusProcess->focusDonut->setEnabled(true); 4496 else 4497 { 4498 m_OpsFocusProcess->focusDonut->setEnabled(false); 4499 m_OpsFocusProcess->focusDonut->setChecked(false); 4500 } 4501 } 4502 } 4503 4504 void Focus::setExposure(double value) 4505 { 4506 focusExposure->setValue(value); 4507 } 4508 4509 void Focus::setBinning(int subBinX, int subBinY) 4510 { 4511 INDI_UNUSED(subBinY); 4512 focusBinning->setCurrentIndex(subBinX - 1); 4513 } 4514 4515 void Focus::setAutoStarEnabled(bool enable) 4516 { 4517 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(enable); 4518 } 4519 4520 void Focus::setAutoSubFrameEnabled(bool enable) 4521 { 4522 m_OpsFocusSettings->focusSubFrame->setChecked(enable); 4523 } 4524 4525 void Focus::setAutoFocusParameters(int boxSize, int stepSize, int maxTravel, double tolerance) 4526 { 4527 m_OpsFocusSettings->focusBoxSize->setValue(boxSize); 4528 m_OpsFocusMechanics->focusTicks->setValue(stepSize); 4529 m_OpsFocusMechanics->focusMaxTravel->setValue(maxTravel); 4530 m_OpsFocusProcess->focusTolerance->setValue(tolerance); 4531 } 4532 4533 void Focus::checkAutoStarTimeout() 4534 { 4535 //if (starSelected == false && inAutoFocus) 4536 if (starCenter.isNull() && (inAutoFocus || minimumRequiredHFR > 0)) 4537 { 4538 if (inAutoFocus) 4539 { 4540 if (rememberStarCenter.isNull() == false) 4541 { 4542 focusStarSelected(rememberStarCenter.x(), rememberStarCenter.y()); 4543 appendLogText(i18n("No star was selected. Using last known position...")); 4544 return; 4545 } 4546 } 4547 4548 initialFocuserAbsPosition = -1; 4549 appendLogText(i18n("No star was selected. Aborting...")); 4550 completeFocusProcedure(Ekos::FOCUS_ABORTED); 4551 } 4552 else if (state() == FOCUS_WAITING) 4553 setState(FOCUS_IDLE); 4554 } 4555 4556 void Focus::setAbsoluteFocusTicks() 4557 { 4558 if (absTicksSpin->value() == currentPosition) 4559 { 4560 appendLogText(i18n("Focuser already at %1...", currentPosition)); 4561 return; 4562 } 4563 focusInB->setEnabled(false); 4564 focusOutB->setEnabled(false); 4565 startGotoB->setEnabled(false); 4566 if (!changeFocus(absTicksSpin->value() - currentPosition)) 4567 qCDebug(KSTARS_EKOS_FOCUS) << "setAbsoluteFocusTicks unable to move focuser."; 4568 } 4569 4570 void Focus::syncTrackingBoxPosition() 4571 { 4572 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 4573 Q_ASSERT(targetChip); 4574 4575 int subBinX = 1, subBinY = 1; 4576 targetChip->getBinning(&subBinX, &subBinY); 4577 4578 if (starCenter.isNull() == false) 4579 { 4580 double boxSize = m_OpsFocusSettings->focusBoxSize->value(); 4581 int x, y, w, h; 4582 targetChip->getFrame(&x, &y, &w, &h); 4583 // If box size is larger than image size, set it to lower index 4584 if (boxSize / subBinX >= w || boxSize / subBinY >= h) 4585 { 4586 m_OpsFocusSettings->focusBoxSize->setValue((boxSize / subBinX >= w) ? w : h); 4587 return; 4588 } 4589 4590 // If binning changed, update coords accordingly 4591 if (subBinX != starCenter.z()) 4592 { 4593 if (starCenter.z() > 0) 4594 { 4595 starCenter.setX(starCenter.x() * (starCenter.z() / subBinX)); 4596 starCenter.setY(starCenter.y() * (starCenter.z() / subBinY)); 4597 } 4598 4599 starCenter.setZ(subBinX); 4600 } 4601 4602 QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY), 4603 boxSize / subBinX, boxSize / subBinY); 4604 m_FocusView->setTrackingBoxEnabled(true); 4605 m_FocusView->setTrackingBox(starRect); 4606 } 4607 } 4608 4609 void Focus::showFITSViewer() 4610 { 4611 static int lastFVTabID = -1; 4612 if (m_ImageData) 4613 { 4614 QUrl url = QUrl::fromLocalFile("focus.fits"); 4615 if (fv.isNull()) 4616 { 4617 fv = KStars::Instance()->createFITSViewer(); 4618 fv->loadData(m_ImageData, url, &lastFVTabID); 4619 connect(fv.get(), &FITSViewer::terminated, this, [this]() 4620 { 4621 fv.clear(); 4622 }); 4623 } 4624 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) == false) 4625 fv->loadData(m_ImageData, url, &lastFVTabID); 4626 4627 fv->show(); 4628 } 4629 } 4630 4631 void Focus::adjustFocusOffset(int value, bool useAbsoluteOffset) 4632 { 4633 if (inAdjustFocus) 4634 { 4635 qCDebug(KSTARS_EKOS_FOCUS) << "adjustFocusOffset called whilst inAdjustFocus in progress. Ignoring..."; 4636 return; 4637 } 4638 4639 if (inFocusLoop) 4640 { 4641 qCDebug(KSTARS_EKOS_FOCUS) << "adjustFocusOffset called whilst inFocusLoop. Ignoring..."; 4642 return; 4643 4644 } 4645 4646 if (adaptFocus->inAdaptiveFocus()) 4647 { 4648 qCDebug(KSTARS_EKOS_FOCUS) << "adjustFocusOffset called whilst inAdaptiveFocus. Ignoring..."; 4649 return; 4650 } 4651 4652 inAdjustFocus = true; 4653 4654 // Get the new position 4655 int newPosition = (useAbsoluteOffset) ? value : value + currentPosition; 4656 4657 if (!changeFocus(newPosition - currentPosition)) 4658 qCDebug(KSTARS_EKOS_FOCUS) << "adjustFocusOffset unable to move focuser"; 4659 } 4660 4661 void Focus::toggleFocusingWidgetFullScreen() 4662 { 4663 if (focusingWidget->parent() == nullptr) 4664 { 4665 focusingWidget->setParent(this); 4666 rightLayout->insertWidget(0, focusingWidget); 4667 focusingWidget->showNormal(); 4668 } 4669 else 4670 { 4671 focusingWidget->setParent(nullptr); 4672 focusingWidget->setWindowTitle(i18nc("@title:window", "Focus Frame")); 4673 focusingWidget->setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint); 4674 focusingWidget->showMaximized(); 4675 focusingWidget->show(); 4676 } 4677 } 4678 4679 void Focus::setMountStatus(ISD::Mount::Status newState) 4680 { 4681 switch (newState) 4682 { 4683 case ISD::Mount::MOUNT_PARKING: 4684 case ISD::Mount::MOUNT_SLEWING: 4685 case ISD::Mount::MOUNT_MOVING: 4686 captureB->setEnabled(false); 4687 startFocusB->setEnabled(false); 4688 startAbInsB->setEnabled(false); 4689 startLoopB->setEnabled(false); 4690 4691 // If mount is moved while we have a star selected and subframed 4692 // let us reset the frame. 4693 if (subFramed) 4694 resetFrame(); 4695 4696 break; 4697 4698 default: 4699 resetButtons(); 4700 break; 4701 } 4702 } 4703 4704 void Focus::setMountCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha) 4705 { 4706 Q_UNUSED(pierSide) 4707 Q_UNUSED(ha) 4708 mountAlt = position.alt().Degrees(); 4709 } 4710 4711 void Focus::removeDevice(const QSharedPointer<ISD::GenericDevice> &deviceRemoved) 4712 { 4713 auto name = deviceRemoved->getDeviceName(); 4714 4715 // Check in Focusers 4716 4717 if (m_Focuser && m_Focuser->getDeviceName() == name) 4718 { 4719 m_Focuser->disconnect(this); 4720 m_Focuser = nullptr; 4721 QTimer::singleShot(1000, this, [this]() 4722 { 4723 checkFocuser(); 4724 resetButtons(); 4725 }); 4726 } 4727 4728 // Check in Temperature Sources. 4729 for (auto &oneSource : m_TemperatureSources) 4730 { 4731 if (oneSource->getDeviceName() == name) 4732 { 4733 // clear reference to avoid runtime exception in checkTemperatureSource() 4734 if (m_LastSourceDeviceAutofocusTemperature && m_LastSourceDeviceAutofocusTemperature->getDeviceName() == name) 4735 m_LastSourceDeviceAutofocusTemperature.reset(nullptr); 4736 4737 m_TemperatureSources.removeAll(oneSource); 4738 QTimer::singleShot(1000, this, [this, name]() 4739 { 4740 defaultFocusTemperatureSource->removeItem(defaultFocusTemperatureSource->findText(name)); 4741 }); 4742 break; 4743 } 4744 } 4745 4746 // Check camera 4747 if (m_Camera && m_Camera->getDeviceName() == name) 4748 { 4749 m_Camera->disconnect(this); 4750 m_Camera = nullptr; 4751 4752 QTimer::singleShot(1000, this, [this]() 4753 { 4754 checkCamera(); 4755 resetButtons(); 4756 }); 4757 } 4758 4759 // Check Filter 4760 if (m_FilterWheel && m_FilterWheel->getDeviceName() == name) 4761 { 4762 m_FilterWheel->disconnect(this); 4763 m_FilterWheel = nullptr; 4764 4765 QTimer::singleShot(1000, this, [this]() 4766 { 4767 checkFilter(); 4768 resetButtons(); 4769 }); 4770 } 4771 } 4772 4773 void Focus::setupFilterManager() 4774 { 4775 // Do we have an existing filter manager? 4776 if (m_FilterManager) 4777 m_FilterManager->disconnect(this); 4778 4779 // Create new or refresh device 4780 Ekos::Manager::Instance()->createFilterManager(m_FilterWheel); 4781 4782 // Return global filter manager for this filter wheel. 4783 Ekos::Manager::Instance()->getFilterManager(m_FilterWheel->getDeviceName(), m_FilterManager); 4784 4785 // Focus Module ----> Filter Manager connections 4786 4787 // Update focuser absolute position. 4788 connect(this, &Focus::absolutePositionChanged, m_FilterManager.get(), &FilterManager::setFocusAbsolutePosition); 4789 4790 // Update Filter Manager state 4791 connect(this, &Focus::newStatus, this, [this](Ekos::FocusState state) 4792 { 4793 if (m_FilterManager) 4794 { 4795 m_FilterManager->setFocusStatus(state); 4796 if (focusFilter->currentIndex() != -1 && canAbsMove && state == Ekos::FOCUS_COMPLETE) 4797 { 4798 m_FilterManager->setFilterAbsoluteFocusDetails(focusFilter->currentIndex(), currentPosition, 4799 m_LastSourceAutofocusTemperature, m_LastSourceAutofocusAlt); 4800 } 4801 } 4802 }); 4803 4804 // Filter Manager ----> Focus Module connections 4805 4806 // Suspend guiding if filter offset is change with OAG 4807 connect(m_FilterManager.get(), &FilterManager::newStatus, this, [this](Ekos::FilterState filterState) 4808 { 4809 // If we are changing filter offset while idle, then check if we need to suspend guiding. 4810 if (filterState == FILTER_OFFSET && state() != Ekos::FOCUS_PROGRESS) 4811 { 4812 if (m_GuidingSuspended == false && m_OpsFocusSettings->focusSuspendGuiding->isChecked()) 4813 { 4814 m_GuidingSuspended = true; 4815 emit suspendGuiding(); 4816 } 4817 } 4818 }); 4819 4820 // Take action once filter manager completes filter position 4821 connect(m_FilterManager.get(), &FilterManager::ready, this, [this]() 4822 { 4823 // Keep the focusFilter widget consistent with the filter wheel 4824 if (focusFilter->currentIndex() != currentFilterPosition - 1) 4825 focusFilter->setCurrentIndex(currentFilterPosition - 1); 4826 4827 if (filterPositionPending) 4828 { 4829 filterPositionPending = false; 4830 capture(); 4831 } 4832 else if (fallbackFilterPending) 4833 { 4834 fallbackFilterPending = false; 4835 emit newStatus(state()); 4836 } 4837 }); 4838 4839 // Take action when filter operation fails 4840 connect(m_FilterManager.get(), &FilterManager::failed, this, [this]() 4841 { 4842 appendLogText(i18n("Filter operation failed.")); 4843 completeFocusProcedure(Ekos::FOCUS_ABORTED); 4844 }); 4845 4846 // Run Autofocus if required by filter manager 4847 connect(m_FilterManager.get(), &FilterManager::runAutoFocus, this, &Focus::runAutoFocus); 4848 4849 // Abort Autofocus if required by filter manager 4850 connect(m_FilterManager.get(), &FilterManager::abortAutoFocus, this, &Focus::abort); 4851 4852 // Adjust focus offset 4853 connect(m_FilterManager.get(), &FilterManager::newFocusOffset, this, &Focus::adjustFocusOffset); 4854 4855 // Update labels 4856 connect(m_FilterManager.get(), &FilterManager::labelsChanged, this, [this]() 4857 { 4858 focusFilter->clear(); 4859 focusFilter->addItems(m_FilterManager->getFilterLabels()); 4860 currentFilterPosition = m_FilterManager->getFilterPosition(); 4861 focusFilter->setCurrentIndex(currentFilterPosition - 1); 4862 }); 4863 4864 // Position changed 4865 connect(m_FilterManager.get(), &FilterManager::positionChanged, this, [this]() 4866 { 4867 currentFilterPosition = m_FilterManager->getFilterPosition(); 4868 focusFilter->setCurrentIndex(currentFilterPosition - 1); 4869 }); 4870 4871 // Exposure Changed 4872 connect(m_FilterManager.get(), &FilterManager::exposureChanged, this, [this]() 4873 { 4874 focusExposure->setValue(m_FilterManager->getFilterExposure()); 4875 }); 4876 4877 // Wavelength Changed 4878 connect(m_FilterManager.get(), &FilterManager::wavelengthChanged, this, [this]() 4879 { 4880 wavelengthChanged(); 4881 }); 4882 } 4883 4884 void Focus::connectFilterManager() 4885 { 4886 // Show filter manager if toggled. 4887 connect(filterManagerB, &QPushButton::clicked, this, [this]() 4888 { 4889 if (m_FilterManager) 4890 { 4891 m_FilterManager->refreshFilterModel(); 4892 m_FilterManager->show(); 4893 m_FilterManager->raise(); 4894 } 4895 }); 4896 4897 // Resume guiding if suspended after focus position is adjusted. 4898 connect(this, &Focus::focusPositionAdjusted, this, [this]() 4899 { 4900 if (m_FilterManager) 4901 m_FilterManager->setFocusOffsetComplete(); 4902 if (m_GuidingSuspended && state() != Ekos::FOCUS_PROGRESS) 4903 { 4904 QTimer::singleShot(m_OpsFocusMechanics->focusSettleTime->value() * 1000, this, [this]() 4905 { 4906 m_GuidingSuspended = false; 4907 emit resumeGuiding(); 4908 }); 4909 } 4910 }); 4911 4912 // Save focus exposure for a particular filter 4913 connect(focusExposure, &QDoubleSpinBox::editingFinished, this, [this]() 4914 { 4915 // Don't save if donut processing is changing this field 4916 if (inAutoFocus && m_OpsFocusProcess->focusDonut->isEnabled()) 4917 return; 4918 4919 if (m_FilterManager) 4920 m_FilterManager->setFilterExposure(focusFilter->currentIndex(), focusExposure->value()); 4921 }); 4922 4923 // Load exposure if filter is changed. 4924 connect(focusFilter, &QComboBox::currentTextChanged, this, [this](const QString & text) 4925 { 4926 if (m_FilterManager) 4927 { 4928 focusExposure->setValue(m_FilterManager->getFilterExposure(text)); 4929 // Update the CFZ for the new filter - force all data back to OT & filter values 4930 resetCFZToOT(); 4931 } 4932 }); 4933 4934 } 4935 4936 void Focus::toggleVideo(bool enabled) 4937 { 4938 if (m_Camera == nullptr) 4939 return; 4940 4941 if (m_Camera->isBLOBEnabled() == false) 4942 { 4943 4944 if (Options::guiderType() != Ekos::Guide::GUIDE_INTERNAL) 4945 m_Camera->setBLOBEnabled(true); 4946 else 4947 { 4948 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, enabled]() 4949 { 4950 //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr); 4951 KSMessageBox::Instance()->disconnect(this); 4952 m_Camera->setVideoStreamEnabled(enabled); 4953 }); 4954 KSMessageBox::Instance()->questionYesNo(i18n("Image transfer is disabled for this camera. Would you like to enable it?")); 4955 } 4956 } 4957 else 4958 m_Camera->setVideoStreamEnabled(enabled); 4959 } 4960 4961 //void Focus::setWeatherData(const std::vector<ISD::Weather::WeatherData> &data) 4962 //{ 4963 // auto pos = std::find_if(data.begin(), data.end(), [](ISD::Weather::WeatherData oneEntry) 4964 // { 4965 // return (oneEntry.name == "WEATHER_TEMPERATURE"); 4966 // }); 4967 4968 // if (pos != data.end()) 4969 // { 4970 // updateTemperature(OBSERVATORY_TEMPERATURE, pos->value); 4971 // } 4972 //} 4973 4974 void Focus::setVideoStreamEnabled(bool enabled) 4975 { 4976 if (enabled) 4977 { 4978 liveVideoB->setChecked(true); 4979 liveVideoB->setIcon(QIcon::fromTheme("camera-on")); 4980 } 4981 else 4982 { 4983 liveVideoB->setChecked(false); 4984 liveVideoB->setIcon(QIcon::fromTheme("camera-ready")); 4985 } 4986 } 4987 4988 void Focus::processCaptureTimeout() 4989 { 4990 captureTimeoutCounter++; 4991 4992 if (captureTimeoutCounter >= 3) 4993 { 4994 captureTimeoutCounter = 0; 4995 captureTimeout.stop(); 4996 appendLogText(i18n("Exposure timeout. Aborting...")); 4997 completeFocusProcedure(Ekos::FOCUS_ABORTED); 4998 } 4999 else 5000 { 5001 appendLogText(i18n("Exposure timeout. Restarting exposure...")); 5002 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 5003 targetChip->abortExposure(); 5004 5005 prepareCapture(targetChip); 5006 5007 if (targetChip->capture(focusExposure->value())) 5008 { 5009 // Timeout is exposure duration + timeout threshold in seconds 5010 //long const timeout = lround(ceil(focusExposure->value() * 1000)) + FOCUS_TIMEOUT_THRESHOLD; 5011 captureTimeout.start( (focusExposure->value() + m_OpsFocusMechanics->focusCaptureTimeout->value()) * 1000); 5012 5013 if (inFocusLoop == false) 5014 appendLogText(i18n("Capturing image again...")); 5015 5016 resetButtons(); 5017 } 5018 else if (inAutoFocus) 5019 { 5020 completeFocusProcedure(Ekos::FOCUS_ABORTED); 5021 } 5022 } 5023 } 5024 5025 void Focus::processCaptureError(ISD::Camera::ErrorType type) 5026 { 5027 if (type == ISD::Camera::ERROR_SAVE) 5028 { 5029 appendLogText(i18n("Failed to save image. Aborting...")); 5030 completeFocusProcedure(Ekos::FOCUS_ABORTED); 5031 return; 5032 } 5033 5034 captureFailureCounter++; 5035 5036 if (captureFailureCounter >= 3) 5037 { 5038 captureFailureCounter = 0; 5039 appendLogText(i18n("Exposure failure. Aborting...")); 5040 completeFocusProcedure(Ekos::FOCUS_ABORTED); 5041 return; 5042 } 5043 5044 appendLogText(i18n("Exposure failure. Restarting exposure...")); 5045 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 5046 targetChip->abortExposure(); 5047 targetChip->capture(focusExposure->value()); 5048 } 5049 5050 void Focus::syncSettings() 5051 { 5052 QDoubleSpinBox *dsb = nullptr; 5053 QSpinBox *sb = nullptr; 5054 QCheckBox *cb = nullptr; 5055 QRadioButton *rb = nullptr; 5056 QComboBox *cbox = nullptr; 5057 QSplitter *s = nullptr; 5058 5059 QString key; 5060 QVariant value; 5061 5062 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender()))) 5063 { 5064 key = dsb->objectName(); 5065 value = dsb->value(); 5066 5067 } 5068 else if ( (sb = qobject_cast<QSpinBox*>(sender()))) 5069 { 5070 key = sb->objectName(); 5071 value = sb->value(); 5072 } 5073 else if ( (cb = qobject_cast<QCheckBox*>(sender()))) 5074 { 5075 key = cb->objectName(); 5076 value = cb->isChecked(); 5077 } 5078 else if ( (rb = qobject_cast<QRadioButton*>(sender()))) 5079 { 5080 key = rb->objectName(); 5081 value = rb->isChecked(); 5082 } 5083 else if ( (cbox = qobject_cast<QComboBox*>(sender()))) 5084 { 5085 key = cbox->objectName(); 5086 value = cbox->currentText(); 5087 } 5088 else if ( (s = qobject_cast<QSplitter*>(sender()))) 5089 { 5090 key = s->objectName(); 5091 // Convert from the QByteArray to QString using Base64 5092 value = QString::fromUtf8(s->saveState().toBase64()); 5093 } 5094 5095 // Save immediately 5096 Options::self()->setProperty(key.toLatin1(), value); 5097 5098 m_Settings[key] = value; 5099 m_GlobalSettings[key] = value; 5100 5101 emit settingsUpdated(getAllSettings()); 5102 5103 // Save to optical train specific settings as well 5104 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText())); 5105 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Focus, m_Settings); 5106 5107 // propagate image mask attributes 5108 selectImageMask(); 5109 } 5110 5111 void Focus::loadGlobalSettings() 5112 { 5113 QString key; 5114 QVariant value; 5115 5116 QVariantMap settings; 5117 // All Combo Boxes 5118 for (auto &oneWidget : findChildren<QComboBox*>()) 5119 { 5120 if (oneWidget->objectName() == "opticalTrainCombo") 5121 continue; 5122 5123 key = oneWidget->objectName(); 5124 value = Options::self()->property(key.toLatin1()); 5125 if (value.isValid()) 5126 { 5127 oneWidget->setCurrentText(value.toString()); 5128 settings[key] = value; 5129 } 5130 else 5131 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!"; 5132 } 5133 5134 // All Double Spin Boxes 5135 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 5136 { 5137 key = oneWidget->objectName(); 5138 value = Options::self()->property(key.toLatin1()); 5139 if (value.isValid()) 5140 { 5141 oneWidget->setValue(value.toDouble()); 5142 settings[key] = value; 5143 } 5144 else 5145 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!"; 5146 } 5147 5148 // All Spin Boxes 5149 for (auto &oneWidget : findChildren<QSpinBox*>()) 5150 { 5151 key = oneWidget->objectName(); 5152 value = Options::self()->property(key.toLatin1()); 5153 if (value.isValid()) 5154 { 5155 oneWidget->setValue(value.toInt()); 5156 settings[key] = value; 5157 } 5158 else 5159 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!"; 5160 } 5161 5162 // All Checkboxes 5163 for (auto &oneWidget : findChildren<QCheckBox*>()) 5164 { 5165 key = oneWidget->objectName(); 5166 value = Options::self()->property(key.toLatin1()); 5167 if (value.isValid()) 5168 { 5169 oneWidget->setChecked(value.toBool()); 5170 settings[key] = value; 5171 } 5172 else 5173 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!"; 5174 } 5175 5176 // All Checkable Groupboxes 5177 for (auto &oneWidget : findChildren<QGroupBox*>()) 5178 { 5179 if (oneWidget->isCheckable()) 5180 { 5181 key = oneWidget->objectName(); 5182 value = Options::self()->property(key.toLatin1()); 5183 if (value.isValid()) 5184 { 5185 oneWidget->setChecked(value.toBool()); 5186 settings[key] = value; 5187 } 5188 else 5189 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!"; 5190 } 5191 } 5192 5193 // All Splitters 5194 for (auto &oneWidget : findChildren<QSplitter*>()) 5195 { 5196 key = oneWidget->objectName(); 5197 value = Options::self()->property(key.toLatin1()); 5198 if (value.isValid()) 5199 { 5200 // Convert the saved QString to a QByteArray using Base64 5201 auto valueBA = QByteArray::fromBase64(value.toString().toUtf8()); 5202 oneWidget->restoreState(valueBA); 5203 settings[key] = valueBA; 5204 } 5205 else 5206 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!"; 5207 } 5208 5209 // All Radio buttons 5210 for (auto &oneWidget : findChildren<QRadioButton*>()) 5211 { 5212 key = oneWidget->objectName(); 5213 value = Options::self()->property(key.toLatin1()); 5214 if (value.isValid()) 5215 { 5216 oneWidget->setChecked(value.toBool()); 5217 settings[key] = value; 5218 } 5219 } 5220 // propagate image mask attributes 5221 selectImageMask(); 5222 m_GlobalSettings = m_Settings = settings; 5223 } 5224 5225 void Focus::checkMosaicMaskLimits() 5226 { 5227 if (m_Camera == nullptr || m_Camera->isConnected() == false) 5228 return; 5229 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 5230 if (targetChip == nullptr || frameSettings.contains(targetChip) == false) 5231 return; 5232 auto settings = frameSettings[targetChip]; 5233 5234 // Watch out for invalid values. 5235 auto width = settings["w"].toInt(); 5236 auto height = settings["h"].toInt(); 5237 if (width == 0 || height == 0) 5238 return; 5239 5240 // determine maximal square size 5241 auto min = std::min(width, height); 5242 // now check if the tile size is below this limit 5243 m_OpsFocusSettings->focusMosaicTileWidth->setMaximum(100 * min / (3 * width)); 5244 } 5245 5246 void Focus::connectSyncSettings() 5247 { 5248 // All Combo Boxes 5249 for (auto &oneWidget : findChildren<QComboBox*>()) 5250 // Don't sync Optical Train combo 5251 if (oneWidget != opticalTrainCombo) 5252 connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Focus::syncSettings); 5253 5254 // All Double Spin Boxes 5255 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 5256 connect(oneWidget, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &Ekos::Focus::syncSettings); 5257 5258 // All Spin Boxes 5259 for (auto &oneWidget : findChildren<QSpinBox*>()) 5260 connect(oneWidget, QOverload<int>::of(&QSpinBox::valueChanged), this, &Ekos::Focus::syncSettings); 5261 5262 // All Checkboxes 5263 for (auto &oneWidget : findChildren<QCheckBox*>()) 5264 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings); 5265 5266 // All Checkable Groupboxes 5267 for (auto &oneWidget : findChildren<QGroupBox*>()) 5268 if (oneWidget->isCheckable()) 5269 connect(oneWidget, &QGroupBox::toggled, this, &Ekos::Focus::syncSettings); 5270 5271 // All Splitters 5272 for (auto &oneWidget : findChildren<QSplitter*>()) 5273 connect(oneWidget, &QSplitter::splitterMoved, this, &Ekos::Focus::syncSettings); 5274 5275 // All Radio Buttons 5276 for (auto &oneWidget : findChildren<QRadioButton*>()) 5277 connect(oneWidget, &QRadioButton::toggled, this, &Ekos::Focus::syncSettings); 5278 } 5279 5280 void Focus::disconnectSyncSettings() 5281 { 5282 // All Combo Boxes 5283 for (auto &oneWidget : findChildren<QComboBox*>()) 5284 disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Focus::syncSettings); 5285 5286 // All Double Spin Boxes 5287 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 5288 disconnect(oneWidget, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &Ekos::Focus::syncSettings); 5289 5290 // All Spin Boxes 5291 for (auto &oneWidget : findChildren<QSpinBox*>()) 5292 disconnect(oneWidget, QOverload<int>::of(&QSpinBox::valueChanged), this, &Ekos::Focus::syncSettings); 5293 5294 // All Checkboxes 5295 for (auto &oneWidget : findChildren<QCheckBox*>()) 5296 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings); 5297 5298 // All Checkable Groupboxes 5299 for (auto &oneWidget : findChildren<QGroupBox*>()) 5300 if (oneWidget->isCheckable()) 5301 disconnect(oneWidget, &QGroupBox::toggled, this, &Ekos::Focus::syncSettings); 5302 5303 // All Splitters 5304 for (auto &oneWidget : findChildren<QSplitter*>()) 5305 disconnect(oneWidget, &QSplitter::splitterMoved, this, &Ekos::Focus::syncSettings); 5306 5307 // All Radio Buttons 5308 for (auto &oneWidget : findChildren<QRadioButton*>()) 5309 disconnect(oneWidget, &QRadioButton::toggled, this, &Ekos::Focus::syncSettings); 5310 } 5311 5312 void Focus::initPlots() 5313 { 5314 connect(clearDataB, &QPushButton::clicked, this, &Ekos::Focus::clearDataPoints); 5315 5316 profileDialog = new QDialog(this); 5317 profileDialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); 5318 QVBoxLayout *profileLayout = new QVBoxLayout(profileDialog); 5319 profileDialog->setWindowTitle(i18nc("@title:window", "Relative Profile")); 5320 profilePlot = new FocusProfilePlot(profileDialog); 5321 5322 profileLayout->addWidget(profilePlot); 5323 profileDialog->setLayout(profileLayout); 5324 profileDialog->resize(400, 300); 5325 5326 connect(relativeProfileB, &QPushButton::clicked, profileDialog, &QDialog::show); 5327 connect(this, &Ekos::Focus::newHFR, [this](double currentHFR, int pos) 5328 { 5329 Q_UNUSED(pos) profilePlot->drawProfilePlot(currentHFR); 5330 }); 5331 } 5332 5333 void Focus::initConnections() 5334 { 5335 // How long do we wait until the user select a star? 5336 waitStarSelectTimer.setInterval(AUTO_STAR_TIMEOUT); 5337 connect(&waitStarSelectTimer, &QTimer::timeout, this, &Ekos::Focus::checkAutoStarTimeout); 5338 connect(liveVideoB, &QPushButton::clicked, this, &Ekos::Focus::toggleVideo); 5339 5340 // Show FITS Image in a new window 5341 showFITSViewerB->setIcon(QIcon::fromTheme("kstars_fitsviewer")); 5342 showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 5343 connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Focus::showFITSViewer); 5344 5345 // Toggle FITS View to full screen 5346 toggleFullScreenB->setIcon(QIcon::fromTheme("view-fullscreen")); 5347 toggleFullScreenB->setShortcut(Qt::Key_F4); 5348 toggleFullScreenB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 5349 connect(toggleFullScreenB, &QPushButton::clicked, this, &Ekos::Focus::toggleFocusingWidgetFullScreen); 5350 5351 // delayed capturing for waiting the scope to settle 5352 captureTimer.setSingleShot(true); 5353 connect(&captureTimer, &QTimer::timeout, this, [this]() 5354 { 5355 capture(); 5356 }); 5357 5358 // How long do we wait until an exposure times out and needs a retry? 5359 captureTimeout.setSingleShot(true); 5360 connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Focus::processCaptureTimeout); 5361 5362 // Start/Stop focus 5363 connect(startFocusB, &QPushButton::clicked, this, &Ekos::Focus::start); 5364 connect(stopFocusB, &QPushButton::clicked, this, &Ekos::Focus::abort); 5365 5366 // Focus IN/OUT 5367 connect(focusOutB, &QPushButton::clicked, this, &Ekos::Focus::focusOut); 5368 connect(focusInB, &QPushButton::clicked, this, &Ekos::Focus::focusIn); 5369 5370 // Capture a single frame 5371 connect(captureB, &QPushButton::clicked, this, &Ekos::Focus::capture); 5372 // Start continuous capture 5373 connect(startLoopB, &QPushButton::clicked, this, &Ekos::Focus::startFraming); 5374 // Use a subframe when capturing 5375 connect(m_OpsFocusSettings->focusSubFrame, &QRadioButton::toggled, this, &Ekos::Focus::toggleSubframe); 5376 // Reset frame dimensions to default 5377 connect(resetFrameB, &QPushButton::clicked, this, &Ekos::Focus::resetFrame); 5378 5379 // handle frame size changes 5380 connect(focusBinning, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Focus::checkMosaicMaskLimits); 5381 5382 // Sync settings if the temperature source selection is updated. 5383 connect(defaultFocusTemperatureSource, &QComboBox::currentTextChanged, this, &Ekos::Focus::checkTemperatureSource); 5384 5385 // Set focuser absolute position 5386 connect(startGotoB, &QPushButton::clicked, this, &Ekos::Focus::setAbsoluteFocusTicks); 5387 connect(stopGotoB, &QPushButton::clicked, this, [this]() 5388 { 5389 if (m_Focuser) 5390 m_Focuser->stop(); 5391 }); 5392 // Update the focuser box size used to enclose a star 5393 connect(m_OpsFocusSettings->focusBoxSize, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, 5394 &Ekos::Focus::updateBoxSize); 5395 5396 // Setup the tools buttons 5397 connect(startAbInsB, &QPushButton::clicked, this, &Ekos::Focus::startAbIns); 5398 connect(cfzB, &QPushButton::clicked, this, [this]() 5399 { 5400 m_CFZDialog->show(); 5401 m_CFZDialog->raise(); 5402 }); 5403 connect(advisorB, &QPushButton::clicked, this, [this]() 5404 { 5405 m_AdvisorDialog->show(); 5406 m_AdvisorDialog->raise(); 5407 }); 5408 5409 // Update the focuser star detection if the detection algorithm selection changes. 5410 connect(m_OpsFocusProcess->focusDetection, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int index) 5411 { 5412 setFocusDetection(static_cast<StarAlgorithm>(index)); 5413 }); 5414 5415 // Update the focuser solution algorithm if the selection changes. 5416 connect(m_OpsFocusProcess->focusAlgorithm, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int index) 5417 { 5418 setFocusAlgorithm(static_cast<Algorithm>(index)); 5419 }); 5420 5421 // Update the curve fit if the selection changes. Use the currentIndexChanged method rather than 5422 // activated as the former fires when the index is changed by the user AND if changed programmatically 5423 connect(m_OpsFocusProcess->focusCurveFit, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), 5424 this, [&](int index) 5425 { 5426 setCurveFit(static_cast<CurveFitting::CurveFit>(index)); 5427 }); 5428 5429 // Update the star measure if the selection changes 5430 connect(m_OpsFocusProcess->focusStarMeasure, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), 5431 this, [&](int index) 5432 { 5433 setStarMeasure(static_cast<StarMeasure>(index)); 5434 }); 5435 5436 // Update the star PSF if the selection changes 5437 connect(m_OpsFocusProcess->focusStarPSF, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), 5438 this, [&](int index) 5439 { 5440 setStarPSF(static_cast<StarPSF>(index)); 5441 }); 5442 5443 // Update the units (pixels or arcsecs) if the selection changes 5444 connect(m_OpsFocusSettings->focusUnits, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), 5445 this, [&](int index) 5446 { 5447 setStarUnits(static_cast<StarUnits>(index)); 5448 }); 5449 5450 // Update the walk if the selection changes 5451 connect(m_OpsFocusMechanics->focusWalk, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), 5452 this, [&](int index) 5453 { 5454 setWalk(static_cast<FocusWalk>(index)); 5455 }); 5456 5457 // Adaptive Focus on/off switch toggled 5458 connect(m_OpsFocusSettings->focusAdaptive, &QCheckBox::toggled, this, &Ekos::Focus::resetAdaptiveFocus); 5459 5460 // Reset star center on auto star check toggle 5461 connect(m_OpsFocusSettings->focusAutoStarEnabled, &QCheckBox::toggled, this, [&](bool enabled) 5462 { 5463 if (enabled) 5464 { 5465 starCenter = QVector3D(); 5466 starSelected = false; 5467 m_FocusView->setTrackingBox(QRect()); 5468 } 5469 }); 5470 5471 // CFZ Panel 5472 connect(m_CFZUI->resetToOTB, &QPushButton::clicked, this, &Ekos::Focus::resetCFZToOT); 5473 5474 connect(m_CFZUI->focusCFZDisplayVCurve, static_cast<void (QCheckBox::*)(int)>(&QCheckBox::stateChanged), this, 5475 &Ekos::Focus::calcCFZ); 5476 5477 connect(m_CFZUI->focusCFZAlgorithm, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, 5478 &Ekos::Focus::calcCFZ); 5479 5480 connect(m_CFZUI->focusCFZTolerance, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &Ekos::Focus::calcCFZ); 5481 5482 connect(m_CFZUI->focusCFZTau, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](double d) 5483 { 5484 Q_UNUSED(d); 5485 calcCFZ(); 5486 }); 5487 5488 connect(m_CFZUI->focusCFZWavelength, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](int i) 5489 { 5490 Q_UNUSED(i); 5491 calcCFZ(); 5492 }); 5493 5494 connect(m_CFZUI->focusCFZFNumber, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](double d) 5495 { 5496 Q_UNUSED(d); 5497 calcCFZ(); 5498 }); 5499 5500 connect(m_CFZUI->focusCFZStepSize, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](double d) 5501 { 5502 Q_UNUSED(d); 5503 calcCFZ(); 5504 }); 5505 5506 connect(m_CFZUI->focusCFZAperture, QOverload<int>::of(&QSpinBox::valueChanged), [ = ](int i) 5507 { 5508 Q_UNUSED(i); 5509 calcCFZ(); 5510 }); 5511 5512 connect(m_CFZUI->focusCFZSeeing, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](double d) 5513 { 5514 Q_UNUSED(d); 5515 calcCFZ(); 5516 }); 5517 5518 // Focus Advisor Panel 5519 connect(m_AdvisorUI->focusAdvReset, &QPushButton::clicked, this, &Ekos::Focus::focusAdvisorAction); 5520 connect(m_AdvisorUI->focusAdvHelp, &QPushButton::clicked, this, &Ekos::Focus::focusAdvisorHelp); 5521 // Update the defaulted step size on the FA panel if the CFZ changes 5522 connect(m_CFZUI->focusCFZFinal, &QLineEdit::textChanged, this, [this]() 5523 { 5524 m_AdvisorUI->focusAdvSteps->setValue(m_cfzSteps); 5525 }); 5526 } 5527 5528 void Focus::setFocusDetection(StarAlgorithm starAlgorithm) 5529 { 5530 static bool first = true; 5531 if (!first && m_FocusDetection == starAlgorithm) 5532 return; 5533 5534 first = false; 5535 5536 m_FocusDetection = starAlgorithm; 5537 5538 // setFocusAlgorithm displays the appropriate widgets for the selection 5539 setFocusAlgorithm(m_FocusAlgorithm); 5540 5541 if (m_FocusDetection == ALGORITHM_BAHTINOV) 5542 { 5543 // In case of Bahtinov mask uncheck auto select star 5544 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(false); 5545 m_OpsFocusSettings->focusBoxSize->setMaximum(512); 5546 } 5547 else 5548 { 5549 // When not using Bathinov mask, limit box size to 256 and make sure value stays within range. 5550 if (m_OpsFocusSettings->focusBoxSize->value() > 256) 5551 { 5552 // Focus box size changed, update control 5553 m_OpsFocusSettings->focusBoxSize->setValue(m_OpsFocusSettings->focusBoxSize->value()); 5554 } 5555 m_OpsFocusSettings->focusBoxSize->setMaximum(256); 5556 } 5557 m_OpsFocusSettings->focusAutoStarEnabled->setEnabled(m_FocusDetection != ALGORITHM_BAHTINOV); 5558 setDonutBuster(); 5559 } 5560 5561 void Focus::setFocusAlgorithm(Algorithm algorithm) 5562 { 5563 m_FocusAlgorithm = algorithm; 5564 switch(algorithm) 5565 { 5566 case FOCUS_ITERATIVE: 5567 // Remove unused widgets from the grid and hide them 5568 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel); 5569 m_OpsFocusProcess->focusMultiRowAverageLabel->hide(); 5570 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverage); 5571 m_OpsFocusProcess->focusMultiRowAverage->hide(); 5572 5573 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel); 5574 m_OpsFocusProcess->focusGaussianSigmaLabel->hide(); 5575 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigma); 5576 m_OpsFocusProcess->focusGaussianSigma->hide(); 5577 5578 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel); 5579 m_OpsFocusProcess->focusGaussianKernelSizeLabel->hide(); 5580 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize); 5581 m_OpsFocusProcess->focusGaussianKernelSize->hide(); 5582 5583 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarMeasureLabel); 5584 m_OpsFocusProcess->focusStarMeasureLabel->hide(); 5585 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarMeasure); 5586 m_OpsFocusProcess->focusStarMeasure->hide(); 5587 5588 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel); 5589 m_OpsFocusProcess->focusStarPSFLabel->hide(); 5590 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF); 5591 m_OpsFocusProcess->focusStarPSF->hide(); 5592 5593 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusUseWeights); 5594 m_OpsFocusProcess->focusUseWeights->hide(); 5595 5596 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2LimitLabel); 5597 m_OpsFocusProcess->focusR2LimitLabel->hide(); 5598 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2Limit); 5599 m_OpsFocusProcess->focusR2Limit->hide(); 5600 5601 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusRefineCurveFit); 5602 m_OpsFocusProcess->focusRefineCurveFit->hide(); 5603 m_OpsFocusProcess->focusRefineCurveFit->setChecked(false); 5604 5605 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusToleranceLabel); 5606 m_OpsFocusProcess->focusToleranceLabel->hide(); 5607 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusTolerance); 5608 m_OpsFocusProcess->focusTolerance->hide(); 5609 5610 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThresholdLabel); 5611 m_OpsFocusProcess->focusThresholdLabel->hide(); 5612 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThreshold); 5613 m_OpsFocusProcess->focusThreshold->hide(); 5614 5615 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusCurveFitLabel); 5616 m_OpsFocusProcess->focusCurveFitLabel->hide(); 5617 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusCurveFit); 5618 m_OpsFocusProcess->focusCurveFit->hide(); 5619 5620 m_OpsFocusProcess->focusDonut->hide(); 5621 m_OpsFocusProcess->focusDonut->setChecked(false); 5622 5623 // Although CurveFit is not used by Iterative setting to Quadratic will configure other widgets 5624 m_OpsFocusProcess->focusCurveFit->setCurrentIndex(CurveFitting::FOCUS_QUADRATIC); 5625 5626 // Set Measure to just HFR 5627 if (m_OpsFocusProcess->focusStarMeasure->count() != 1) 5628 { 5629 m_OpsFocusProcess->focusStarMeasure->clear(); 5630 m_OpsFocusProcess->focusStarMeasure->addItem(m_StarMeasureText.at(FOCUS_STAR_HFR)); 5631 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR); 5632 } 5633 5634 // Add necessary widgets to the grid 5635 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusToleranceLabel, 3, 0); 5636 m_OpsFocusProcess->focusToleranceLabel->show(); 5637 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusTolerance, 3, 1); 5638 m_OpsFocusProcess->focusTolerance->show(); 5639 5640 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCountLabel, 3, 2); 5641 m_OpsFocusProcess->focusFramesCountLabel->show(); 5642 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCount, 3, 3); 5643 m_OpsFocusProcess->focusFramesCount->show(); 5644 5645 if (m_FocusDetection == ALGORITHM_THRESHOLD) 5646 { 5647 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThresholdLabel, 4, 0); 5648 m_OpsFocusProcess->focusThresholdLabel->show(); 5649 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThreshold, 4, 1); 5650 m_OpsFocusProcess->focusThreshold->show(); 5651 } 5652 else if (m_FocusDetection == ALGORITHM_BAHTINOV) 5653 { 5654 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverageLabel, 4, 0); 5655 m_OpsFocusProcess->focusMultiRowAverageLabel->show(); 5656 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverage, 4, 1); 5657 m_OpsFocusProcess->focusMultiRowAverage->show(); 5658 5659 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigmaLabel, 4, 2); 5660 m_OpsFocusProcess->focusGaussianSigmaLabel->show(); 5661 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigma, 4, 3); 5662 m_OpsFocusProcess->focusGaussianSigma->show(); 5663 5664 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel, 5, 0); 5665 m_OpsFocusProcess->focusGaussianKernelSizeLabel->show(); 5666 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSize, 5, 1); 5667 m_OpsFocusProcess->focusGaussianKernelSize->show(); 5668 } 5669 5670 // Aberration Inspector button 5671 startAbInsB->setEnabled(canAbInsStart()); 5672 5673 // Settings changes 5674 // Disable adaptive focus 5675 m_OpsFocusSettings->focusAdaptive->setChecked(false); 5676 m_OpsFocusSettings->focusAdaptStart->setChecked(false); 5677 m_OpsFocusSettings->adaptiveFocusGroup->setEnabled(false); 5678 5679 // Mechanics changes 5680 m_OpsFocusMechanics->focusMaxSingleStep->setEnabled(true); 5681 m_OpsFocusMechanics->focusOutSteps->setEnabled(false); 5682 5683 // Set Walk to just Classic on 1st time through 5684 if (m_OpsFocusMechanics->focusWalk->count() != 1) 5685 { 5686 m_OpsFocusMechanics->focusWalk->clear(); 5687 m_OpsFocusMechanics->focusWalk->addItem(m_FocusWalkText.at(FOCUS_WALK_CLASSIC)); 5688 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC); 5689 } 5690 break; 5691 5692 case FOCUS_POLYNOMIAL: 5693 // Remove unused widgets from the grid and hide them 5694 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel); 5695 m_OpsFocusProcess->focusMultiRowAverageLabel->hide(); 5696 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverage); 5697 m_OpsFocusProcess->focusMultiRowAverage->hide(); 5698 5699 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel); 5700 m_OpsFocusProcess->focusGaussianSigmaLabel->hide(); 5701 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigma); 5702 m_OpsFocusProcess->focusGaussianSigma->hide(); 5703 5704 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel); 5705 m_OpsFocusProcess->focusGaussianKernelSizeLabel->hide(); 5706 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize); 5707 m_OpsFocusProcess->focusGaussianKernelSize->hide(); 5708 5709 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel); 5710 m_OpsFocusProcess->focusStarPSFLabel->hide(); 5711 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF); 5712 m_OpsFocusProcess->focusStarPSF->hide(); 5713 5714 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusUseWeights); 5715 m_OpsFocusProcess->focusUseWeights->hide(); 5716 5717 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2LimitLabel); 5718 m_OpsFocusProcess->focusR2LimitLabel->hide(); 5719 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2Limit); 5720 m_OpsFocusProcess->focusR2Limit->hide(); 5721 5722 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusRefineCurveFit); 5723 m_OpsFocusProcess->focusRefineCurveFit->hide(); 5724 m_OpsFocusProcess->focusRefineCurveFit->setChecked(false); 5725 5726 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusToleranceLabel); 5727 m_OpsFocusProcess->focusToleranceLabel->hide(); 5728 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusTolerance); 5729 m_OpsFocusProcess->focusTolerance->hide(); 5730 5731 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThresholdLabel); 5732 m_OpsFocusProcess->focusThresholdLabel->hide(); 5733 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThreshold); 5734 m_OpsFocusProcess->focusThreshold->hide(); 5735 5736 // Donut buster not available 5737 m_OpsFocusProcess->focusDonut->hide(); 5738 m_OpsFocusProcess->focusDonut->setChecked(false); 5739 5740 // Set Measure to just HFR 5741 if (m_OpsFocusProcess->focusStarMeasure->count() != 1) 5742 { 5743 m_OpsFocusProcess->focusStarMeasure->clear(); 5744 m_OpsFocusProcess->focusStarMeasure->addItem(m_StarMeasureText.at(FOCUS_STAR_HFR)); 5745 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR); 5746 } 5747 5748 // Add necessary widgets to the grid 5749 // Curve fit can only be QUADRATIC so only allow this value 5750 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFitLabel, 1, 2); 5751 m_OpsFocusProcess->focusCurveFitLabel->show(); 5752 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFit, 1, 3); 5753 m_OpsFocusProcess->focusCurveFit->show(); 5754 if (m_OpsFocusProcess->focusCurveFit->count() != 1) 5755 { 5756 m_OpsFocusProcess->focusCurveFit->clear(); 5757 m_OpsFocusProcess->focusCurveFit->addItem(m_CurveFitText.at(CurveFitting::FOCUS_QUADRATIC)); 5758 m_OpsFocusProcess->focusCurveFit->setCurrentIndex(CurveFitting::FOCUS_QUADRATIC); 5759 } 5760 5761 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusToleranceLabel, 3, 0); 5762 m_OpsFocusProcess->focusToleranceLabel->show(); 5763 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusTolerance, 3, 1); 5764 m_OpsFocusProcess->focusTolerance->show(); 5765 5766 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCountLabel, 3, 2); 5767 m_OpsFocusProcess->focusFramesCountLabel->show(); 5768 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCount, 3, 3); 5769 m_OpsFocusProcess->focusFramesCount->show(); 5770 5771 if (m_FocusDetection == ALGORITHM_THRESHOLD) 5772 { 5773 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThresholdLabel, 4, 0); 5774 m_OpsFocusProcess->focusThresholdLabel->show(); 5775 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThreshold, 4, 1); 5776 m_OpsFocusProcess->focusThreshold->show(); 5777 } 5778 else if (m_FocusDetection == ALGORITHM_BAHTINOV) 5779 { 5780 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverageLabel, 4, 0); 5781 m_OpsFocusProcess->focusMultiRowAverageLabel->show(); 5782 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverage, 4, 1); 5783 m_OpsFocusProcess->focusMultiRowAverage->show(); 5784 5785 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigmaLabel, 4, 2); 5786 m_OpsFocusProcess->focusGaussianSigmaLabel->show(); 5787 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigma, 4, 3); 5788 m_OpsFocusProcess->focusGaussianSigma->show(); 5789 5790 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel, 5, 0); 5791 m_OpsFocusProcess->focusGaussianKernelSizeLabel->show(); 5792 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSize, 5, 1); 5793 m_OpsFocusProcess->focusGaussianKernelSize->show(); 5794 } 5795 5796 // Aberration Inspector button 5797 startAbInsB->setEnabled(canAbInsStart()); 5798 5799 // Settings changes 5800 // Disable adaptive focus 5801 m_OpsFocusSettings->focusAdaptive->setChecked(false); 5802 m_OpsFocusSettings->focusAdaptStart->setChecked(false); 5803 m_OpsFocusSettings->adaptiveFocusGroup->setEnabled(false); 5804 5805 // Mechanics changes 5806 m_OpsFocusMechanics->focusMaxSingleStep->setEnabled(true); 5807 m_OpsFocusMechanics->focusOutSteps->setEnabled(false); 5808 5809 // Set Walk to just Classic on 1st time through 5810 if (m_OpsFocusMechanics->focusWalk->count() != 1) 5811 { 5812 m_OpsFocusMechanics->focusWalk->clear(); 5813 m_OpsFocusMechanics->focusWalk->addItem(m_FocusWalkText.at(FOCUS_WALK_CLASSIC)); 5814 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC); 5815 } 5816 break; 5817 5818 case FOCUS_LINEAR: 5819 // Remove unused widgets from the grid and hide them 5820 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel); 5821 m_OpsFocusProcess->focusMultiRowAverageLabel->hide(); 5822 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverage); 5823 m_OpsFocusProcess->focusMultiRowAverage->hide(); 5824 5825 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel); 5826 m_OpsFocusProcess->focusGaussianSigmaLabel->hide(); 5827 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigma); 5828 m_OpsFocusProcess->focusGaussianSigma->hide(); 5829 5830 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel); 5831 m_OpsFocusProcess->focusGaussianKernelSizeLabel->hide(); 5832 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize); 5833 m_OpsFocusProcess->focusGaussianKernelSize->hide(); 5834 5835 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThresholdLabel); 5836 m_OpsFocusProcess->focusThresholdLabel->hide(); 5837 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThreshold); 5838 m_OpsFocusProcess->focusThreshold->hide(); 5839 5840 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel); 5841 m_OpsFocusProcess->focusStarPSFLabel->hide(); 5842 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF); 5843 m_OpsFocusProcess->focusStarPSF->hide(); 5844 5845 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusUseWeights); 5846 m_OpsFocusProcess->focusUseWeights->hide(); 5847 5848 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2LimitLabel); 5849 m_OpsFocusProcess->focusR2LimitLabel->hide(); 5850 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2Limit); 5851 m_OpsFocusProcess->focusR2Limit->hide(); 5852 5853 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusRefineCurveFit); 5854 m_OpsFocusProcess->focusRefineCurveFit->hide(); 5855 m_OpsFocusProcess->focusRefineCurveFit->setChecked(false); 5856 5857 // Donut buster not available 5858 m_OpsFocusProcess->focusDonut->hide(); 5859 m_OpsFocusProcess->focusDonut->setChecked(false); 5860 5861 // Set Measure to just HFR 5862 if (m_OpsFocusProcess->focusStarMeasure->count() != 1) 5863 { 5864 m_OpsFocusProcess->focusStarMeasure->clear(); 5865 m_OpsFocusProcess->focusStarMeasure->addItem(m_StarMeasureText.at(FOCUS_STAR_HFR)); 5866 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR); 5867 } 5868 5869 // Add necessary widgets to the grid 5870 // For Linear the only allowable CurveFit is Quadratic 5871 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFitLabel, 1, 2); 5872 m_OpsFocusProcess->focusCurveFitLabel->show(); 5873 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFit, 1, 3); 5874 m_OpsFocusProcess->focusCurveFit->show(); 5875 if (m_OpsFocusProcess->focusCurveFit->count() != 1) 5876 { 5877 m_OpsFocusProcess->focusCurveFit->clear(); 5878 m_OpsFocusProcess->focusCurveFit->addItem(m_CurveFitText.at(CurveFitting::FOCUS_QUADRATIC)); 5879 m_OpsFocusProcess->focusCurveFit->setCurrentIndex(CurveFitting::FOCUS_QUADRATIC); 5880 } 5881 5882 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusToleranceLabel, 3, 0); 5883 m_OpsFocusProcess->focusToleranceLabel->show(); 5884 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusTolerance, 3, 1); 5885 m_OpsFocusProcess->focusTolerance->show(); 5886 5887 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCountLabel, 3, 2); 5888 m_OpsFocusProcess->focusFramesCountLabel->show(); 5889 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCount, 3, 3); 5890 m_OpsFocusProcess->focusFramesCount->show(); 5891 5892 if (m_FocusDetection == ALGORITHM_THRESHOLD) 5893 { 5894 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThresholdLabel, 4, 0); 5895 m_OpsFocusProcess->focusThresholdLabel->show(); 5896 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThreshold, 4, 1); 5897 m_OpsFocusProcess->focusThreshold->show(); 5898 } 5899 else if (m_FocusDetection == ALGORITHM_BAHTINOV) 5900 { 5901 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverageLabel, 4, 0); 5902 m_OpsFocusProcess->focusMultiRowAverageLabel->show(); 5903 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverage, 4, 1); 5904 m_OpsFocusProcess->focusMultiRowAverage->show(); 5905 5906 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigmaLabel, 4, 2); 5907 m_OpsFocusProcess->focusGaussianSigmaLabel->show(); 5908 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigma, 4, 3); 5909 m_OpsFocusProcess->focusGaussianSigma->show(); 5910 5911 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel, 5, 0); 5912 m_OpsFocusProcess->focusGaussianKernelSizeLabel->show(); 5913 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSize, 5, 1); 5914 m_OpsFocusProcess->focusGaussianKernelSize->show(); 5915 } 5916 5917 // Aberration Inspector button 5918 startAbInsB->setEnabled(canAbInsStart()); 5919 5920 // Settings changes 5921 // Disable adaptive focus 5922 m_OpsFocusSettings->focusAdaptive->setChecked(false); 5923 m_OpsFocusSettings->focusAdaptStart->setChecked(false); 5924 m_OpsFocusSettings->adaptiveFocusGroup->setEnabled(false); 5925 5926 // Mechanics changes 5927 m_OpsFocusMechanics->focusMaxSingleStep->setEnabled(false); 5928 m_OpsFocusMechanics->focusOutSteps->setEnabled(true); 5929 5930 // Set Walk to just Classic on 1st time through 5931 if (m_OpsFocusMechanics->focusWalk->count() != 1) 5932 { 5933 m_OpsFocusMechanics->focusWalk->clear(); 5934 m_OpsFocusMechanics->focusWalk->addItem(m_FocusWalkText.at(FOCUS_WALK_CLASSIC)); 5935 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC); 5936 } 5937 break; 5938 5939 case FOCUS_LINEAR1PASS: 5940 // Remove unused widgets from the grid and hide them 5941 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel); 5942 m_OpsFocusProcess->focusMultiRowAverageLabel->hide(); 5943 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverage); 5944 m_OpsFocusProcess->focusMultiRowAverage->hide(); 5945 5946 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel); 5947 m_OpsFocusProcess->focusGaussianSigmaLabel->hide(); 5948 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigma); 5949 m_OpsFocusProcess->focusGaussianSigma->hide(); 5950 5951 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel); 5952 m_OpsFocusProcess->focusGaussianKernelSizeLabel->hide(); 5953 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize); 5954 m_OpsFocusProcess->focusGaussianKernelSize->hide(); 5955 5956 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThresholdLabel); 5957 m_OpsFocusProcess->focusThresholdLabel->hide(); 5958 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThreshold); 5959 m_OpsFocusProcess->focusThreshold->hide(); 5960 5961 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusToleranceLabel); 5962 m_OpsFocusProcess->focusToleranceLabel->hide(); 5963 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusTolerance); 5964 m_OpsFocusProcess->focusTolerance->hide(); 5965 5966 // Setup Measure with all options for detection=SEP and curveFit=HYPERBOLA or PARABOLA 5967 // or just HDR otherwise. Reset on 1st time through only 5968 if (m_FocusDetection == ALGORITHM_SEP && m_CurveFit != CurveFitting::FOCUS_QUADRATIC) 5969 { 5970 if (m_OpsFocusProcess->focusStarMeasure->count() != m_StarMeasureText.count()) 5971 { 5972 m_OpsFocusProcess->focusStarMeasure->clear(); 5973 m_OpsFocusProcess->focusStarMeasure->addItems(m_StarMeasureText); 5974 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR); 5975 } 5976 } 5977 else if (m_FocusDetection != ALGORITHM_SEP || m_CurveFit == CurveFitting::FOCUS_QUADRATIC) 5978 { 5979 if (m_OpsFocusProcess->focusStarMeasure->count() != 1) 5980 { 5981 m_OpsFocusProcess->focusStarMeasure->clear(); 5982 m_OpsFocusProcess->focusStarMeasure->addItem(m_StarMeasureText.at(FOCUS_STAR_HFR)); 5983 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR); 5984 } 5985 } 5986 5987 // Add necessary widgets to the grid 5988 // All Curve Fits are available - default to Hyperbola which is the best option 5989 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFitLabel, 1, 2); 5990 m_OpsFocusProcess->focusCurveFitLabel->show(); 5991 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFit, 1, 3); 5992 m_OpsFocusProcess->focusCurveFit->show(); 5993 if (m_OpsFocusProcess->focusCurveFit->count() != m_CurveFitText.count()) 5994 { 5995 m_OpsFocusProcess->focusCurveFit->clear(); 5996 m_OpsFocusProcess->focusCurveFit->addItems(m_CurveFitText); 5997 m_OpsFocusProcess->focusCurveFit->setCurrentIndex(CurveFitting::FOCUS_HYPERBOLA); 5998 } 5999 6000 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusUseWeights, 3, 0, 1, 2); // Spans 2 columns 6001 m_OpsFocusProcess->focusUseWeights->show(); 6002 6003 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusR2LimitLabel, 3, 2); 6004 m_OpsFocusProcess->focusR2LimitLabel->show(); 6005 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusR2Limit, 3, 3); 6006 m_OpsFocusProcess->focusR2Limit->show(); 6007 6008 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusRefineCurveFit, 4, 0, 1, 2); // Spans 2 columns 6009 m_OpsFocusProcess->focusRefineCurveFit->show(); 6010 6011 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCountLabel, 4, 2); 6012 m_OpsFocusProcess->focusFramesCountLabel->show(); 6013 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCount, 4, 3); 6014 m_OpsFocusProcess->focusFramesCount->show(); 6015 6016 if (m_FocusDetection == ALGORITHM_THRESHOLD) 6017 { 6018 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThresholdLabel, 5, 0); 6019 m_OpsFocusProcess->focusThresholdLabel->show(); 6020 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThreshold, 5, 1); 6021 m_OpsFocusProcess->focusThreshold->show(); 6022 } 6023 else if (m_FocusDetection == ALGORITHM_BAHTINOV) 6024 { 6025 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverageLabel, 5, 0); 6026 m_OpsFocusProcess->focusMultiRowAverageLabel->show(); 6027 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverage, 5, 1); 6028 m_OpsFocusProcess->focusMultiRowAverage->show(); 6029 6030 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigmaLabel, 5, 2); 6031 m_OpsFocusProcess->focusGaussianSigmaLabel->show(); 6032 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigma, 5, 3); 6033 m_OpsFocusProcess->focusGaussianSigma->show(); 6034 6035 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel, 6, 0); 6036 m_OpsFocusProcess->focusGaussianKernelSizeLabel->show(); 6037 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSize, 6, 1); 6038 m_OpsFocusProcess->focusGaussianKernelSize->show(); 6039 } 6040 6041 // Donut Buster 6042 m_OpsFocusProcess->focusDonut->show(); 6043 m_OpsFocusProcess->focusDonut->setEnabled(true); 6044 6045 // Aberration Inspector button 6046 startAbInsB->setEnabled(canAbInsStart()); 6047 6048 // Settings changes 6049 // Enable adaptive focus for Absolute focusers 6050 m_OpsFocusSettings->adaptiveFocusGroup->setEnabled(canAbsMove); 6051 6052 // Mechanics changes 6053 // Firstly max Single Step is not used by Linear 1 Pass 6054 m_OpsFocusMechanics->focusMaxSingleStep->setEnabled(false); 6055 m_OpsFocusMechanics->focusOutSteps->setEnabled(true); 6056 6057 // Set Walk to just Classic for Quadratic, all options otherwise 6058 if (m_CurveFit == CurveFitting::FOCUS_QUADRATIC) 6059 { 6060 if (m_OpsFocusMechanics->focusWalk->count() != 1) 6061 { 6062 m_OpsFocusMechanics->focusWalk->clear(); 6063 m_OpsFocusMechanics->focusWalk->addItem(m_FocusWalkText.at(FOCUS_WALK_CLASSIC)); 6064 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC); 6065 } 6066 } 6067 else 6068 { 6069 if (m_OpsFocusMechanics->focusWalk->count() != m_FocusWalkText.count()) 6070 { 6071 m_OpsFocusMechanics->focusWalk->clear(); 6072 m_OpsFocusMechanics->focusWalk->addItems(m_FocusWalkText); 6073 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC); 6074 } 6075 } 6076 break; 6077 } 6078 } 6079 6080 void Focus::setCurveFit(CurveFitting::CurveFit curve) 6081 { 6082 if (m_OpsFocusProcess->focusCurveFit->currentIndex() == -1) 6083 return; 6084 6085 static bool first = true; 6086 if (!first && m_CurveFit == curve) 6087 return; 6088 6089 first = false; 6090 6091 m_CurveFit = curve; 6092 setFocusAlgorithm(static_cast<Algorithm> (m_OpsFocusProcess->focusAlgorithm->currentIndex())); 6093 setUseWeights(); 6094 setDonutBuster(); 6095 6096 switch(m_CurveFit) 6097 { 6098 case CurveFitting::FOCUS_QUADRATIC: 6099 m_OpsFocusProcess->focusR2Limit->setEnabled(false); 6100 m_OpsFocusProcess->focusRefineCurveFit->setChecked(false); 6101 m_OpsFocusProcess->focusRefineCurveFit->setEnabled(false); 6102 break; 6103 6104 case CurveFitting::FOCUS_HYPERBOLA: 6105 m_OpsFocusProcess->focusR2Limit->setEnabled(true); // focusR2Limit allowed 6106 m_OpsFocusProcess->focusRefineCurveFit->setEnabled(true); 6107 break; 6108 6109 case CurveFitting::FOCUS_PARABOLA: 6110 m_OpsFocusProcess->focusR2Limit->setEnabled(true); // focusR2Limit allowed 6111 m_OpsFocusProcess->focusRefineCurveFit->setEnabled(true); 6112 break; 6113 6114 default: 6115 break; 6116 } 6117 } 6118 6119 void Focus::setStarMeasure(StarMeasure starMeasure) 6120 { 6121 if (m_OpsFocusProcess->focusStarMeasure->currentIndex() == -1) 6122 return; 6123 6124 static bool first = true; 6125 if (!first && m_StarMeasure == starMeasure) 6126 return; 6127 6128 first = false; 6129 6130 m_StarMeasure = starMeasure; 6131 setFocusAlgorithm(static_cast<Algorithm> (m_OpsFocusProcess->focusAlgorithm->currentIndex())); 6132 setUseWeights(); 6133 setDonutBuster(); 6134 6135 // So what is the best estimator of scale to use? Not much to choose from analysis on the sim. 6136 // Variance is the simplest but isn't robust in the presence of outliers. 6137 // MAD is probably better but Sn and Qn are theoretically a little better as they are both efficient and 6138 // able to deal with skew. Have picked Sn. 6139 switch(m_StarMeasure) 6140 { 6141 case FOCUS_STAR_HFR: 6142 m_OptDir = CurveFitting::OPTIMISATION_MINIMISE; 6143 m_ScaleCalc = Mathematics::RobustStatistics::ScaleCalculation::SCALE_SESTIMATOR; 6144 m_FocusView->setStarsHFREnabled(true); 6145 6146 // Don't display the PSF widgets 6147 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel); 6148 m_OpsFocusProcess->focusStarPSFLabel->hide(); 6149 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF); 6150 m_OpsFocusProcess->focusStarPSF->hide(); 6151 break; 6152 6153 case FOCUS_STAR_HFR_ADJ: 6154 m_OptDir = CurveFitting::OPTIMISATION_MINIMISE; 6155 m_ScaleCalc = Mathematics::RobustStatistics::ScaleCalculation::SCALE_SESTIMATOR; 6156 m_FocusView->setStarsHFREnabled(false); 6157 6158 // Don't display the PSF widgets 6159 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel); 6160 m_OpsFocusProcess->focusStarPSFLabel->hide(); 6161 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF); 6162 m_OpsFocusProcess->focusStarPSF->hide(); 6163 break; 6164 6165 case FOCUS_STAR_FWHM: 6166 m_OptDir = CurveFitting::OPTIMISATION_MINIMISE; 6167 m_ScaleCalc = Mathematics::RobustStatistics::ScaleCalculation::SCALE_SESTIMATOR; 6168 // Ideally the FITSViewer would display FWHM. Until then disable HFRs to avoid confusion 6169 m_FocusView->setStarsHFREnabled(false); 6170 6171 // Display the PSF widgets 6172 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusStarPSFLabel, 2, 2); 6173 m_OpsFocusProcess->focusStarPSFLabel->show(); 6174 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusStarPSF, 2, 3); 6175 m_OpsFocusProcess->focusStarPSF->show(); 6176 break; 6177 6178 case FOCUS_STAR_NUM_STARS: 6179 m_OptDir = CurveFitting::OPTIMISATION_MAXIMISE; 6180 m_ScaleCalc = Mathematics::RobustStatistics::ScaleCalculation::SCALE_SESTIMATOR; 6181 m_FocusView->setStarsHFREnabled(true); 6182 6183 // Don't display the PSF widgets 6184 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel); 6185 m_OpsFocusProcess->focusStarPSFLabel->hide(); 6186 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF); 6187 m_OpsFocusProcess->focusStarPSF->hide(); 6188 break; 6189 6190 case FOCUS_STAR_FOURIER_POWER: 6191 m_OptDir = CurveFitting::OPTIMISATION_MAXIMISE; 6192 m_ScaleCalc = Mathematics::RobustStatistics::ScaleCalculation::SCALE_SESTIMATOR; 6193 m_FocusView->setStarsHFREnabled(true); 6194 6195 // Don't display the PSF widgets 6196 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel); 6197 m_OpsFocusProcess->focusStarPSFLabel->hide(); 6198 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF); 6199 m_OpsFocusProcess->focusStarPSF->hide(); 6200 break; 6201 6202 default: 6203 break; 6204 } 6205 } 6206 6207 void Focus::setStarPSF(StarPSF starPSF) 6208 { 6209 m_StarPSF = starPSF; 6210 } 6211 6212 void Focus::setStarUnits(StarUnits starUnits) 6213 { 6214 m_StarUnits = starUnits; 6215 } 6216 6217 void Focus::setWalk(FocusWalk walk) 6218 { 6219 if (m_OpsFocusMechanics->focusWalk->currentIndex() == -1) 6220 return; 6221 6222 static bool first = true; 6223 if (!first && m_FocusWalk == walk) 6224 return; 6225 6226 first = false; 6227 6228 m_FocusWalk = walk; 6229 6230 switch(m_FocusWalk) 6231 { 6232 case FOCUS_WALK_CLASSIC: 6233 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusNumStepsLabel, 6234 m_OpsFocusMechanics->focusOutStepsLabel); 6235 m_OpsFocusMechanics->focusNumStepsLabel->hide(); 6236 m_OpsFocusMechanics->focusOutStepsLabel->show(); 6237 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusNumSteps, 6238 m_OpsFocusMechanics->focusOutSteps); 6239 m_OpsFocusMechanics->focusNumSteps->hide(); 6240 m_OpsFocusMechanics->focusOutSteps->show(); 6241 break; 6242 6243 case FOCUS_WALK_FIXED_STEPS: 6244 case FOCUS_WALK_CFZ_SHUFFLE: 6245 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusOutStepsLabel, 6246 m_OpsFocusMechanics->focusNumStepsLabel); 6247 m_OpsFocusMechanics->focusOutStepsLabel->hide(); 6248 m_OpsFocusMechanics->focusNumStepsLabel->show(); 6249 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusOutSteps, 6250 m_OpsFocusMechanics->focusNumSteps); 6251 m_OpsFocusMechanics->focusOutSteps->hide(); 6252 m_OpsFocusMechanics->focusNumSteps->show(); 6253 break; 6254 6255 default: 6256 break; 6257 } 6258 setDonutBuster(); 6259 } 6260 6261 double Focus::getStarUnits(const StarMeasure starMeasure, const StarUnits starUnits) 6262 { 6263 if (starUnits == FOCUS_UNITS_PIXEL || starMeasure == FOCUS_STAR_NUM_STARS || starMeasure == FOCUS_STAR_FOURIER_POWER) 6264 return 1.0; 6265 if (m_CcdPixelSizeX <= 0.0 || m_FocalLength <= 0.0) 6266 return 1.0; 6267 6268 // Convert to arcsecs from pixels. PixelSize / FocalLength * 206265 6269 return m_CcdPixelSizeX / m_FocalLength * 206.265; 6270 } 6271 6272 void Focus::calcCFZ() 6273 { 6274 double cfzMicrons, cfzSteps; 6275 double cfzCameraSteps = calcCameraCFZ() / m_CFZUI->focusCFZStepSize->value(); 6276 6277 switch(static_cast<Focus::CFZAlgorithm> (m_CFZUI->focusCFZAlgorithm->currentIndex())) 6278 { 6279 case Focus::FOCUS_CFZ_CLASSIC: 6280 // CFZ = 4.88 t λ f2 6281 cfzMicrons = 4.88f * m_CFZUI->focusCFZTolerance->value() * m_CFZUI->focusCFZWavelength->value() / 1000.0f * 6282 pow(m_CFZUI->focusCFZFNumber->value(), 2.0f); 6283 cfzSteps = cfzMicrons / m_CFZUI->focusCFZStepSize->value(); 6284 m_cfzSteps = std::round(std::max(cfzSteps, cfzCameraSteps)); 6285 m_CFZUI->focusCFZFormula->setText("CFZ = 4.88 t λ f²"); 6286 m_CFZUI->focusCFZ->setText(QString("%1 μm").arg(cfzMicrons, 0, 'f', 0)); 6287 m_CFZUI->focusCFZSteps->setText(QString("%1 steps").arg(cfzSteps, 0, 'f', 0)); 6288 m_CFZUI->focusCFZCameraSteps->setText(QString("%1 steps").arg(cfzCameraSteps, 0, 'f', 0)); 6289 m_CFZUI->focusCFZFinal->setText(QString("%1 steps").arg(m_cfzSteps, 0, 'f', 0)); 6290 6291 // Remove widgets not relevant to this algo 6292 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTauLabel); 6293 m_CFZUI->focusCFZTauLabel->hide(); 6294 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTau); 6295 m_CFZUI->focusCFZTau->hide(); 6296 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZSeeingLabel); 6297 m_CFZUI->focusCFZSeeingLabel->hide(); 6298 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZSeeing); 6299 m_CFZUI->focusCFZSeeing->hide(); 6300 6301 // Add necessary widgets to the grid 6302 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZToleranceLabel, 1, 0); 6303 m_CFZUI->focusCFZToleranceLabel->show(); 6304 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZTolerance, 1, 1); 6305 m_CFZUI->focusCFZTolerance->show(); 6306 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZWavelengthLabel, 2, 0); 6307 m_CFZUI->focusCFZWavelengthLabel->show(); 6308 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZWavelength, 2, 1); 6309 m_CFZUI->focusCFZWavelength->show(); 6310 break; 6311 6312 case Focus::FOCUS_CFZ_WAVEFRONT: 6313 // CFZ = 4 t λ f2 6314 cfzMicrons = 4.0f * m_CFZUI->focusCFZTolerance->value() * m_CFZUI->focusCFZWavelength->value() / 1000.0f * pow( 6315 m_CFZUI->focusCFZFNumber->value(), 6316 2.0f); 6317 cfzSteps = cfzMicrons / m_CFZUI->focusCFZStepSize->value(); 6318 m_cfzSteps = std::round(std::max(cfzSteps, cfzCameraSteps)); 6319 m_CFZUI->focusCFZFormula->setText("CFZ = 4 t λ f²"); 6320 m_CFZUI->focusCFZ->setText(QString("%1 μm").arg(cfzMicrons, 0, 'f', 0)); 6321 m_CFZUI->focusCFZSteps->setText(QString("%1 steps").arg(cfzSteps, 0, 'f', 0)); 6322 m_CFZUI->focusCFZCameraSteps->setText(QString("%1 steps").arg(cfzCameraSteps, 0, 'f', 0)); 6323 m_CFZUI->focusCFZFinal->setText(QString("%1 steps").arg(m_cfzSteps, 0, 'f', 0)); 6324 6325 // Remove widgets not relevant to this algo 6326 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTauLabel); 6327 m_CFZUI->focusCFZTauLabel->hide(); 6328 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTau); 6329 m_CFZUI->focusCFZTau->hide(); 6330 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZSeeingLabel); 6331 m_CFZUI->focusCFZSeeingLabel->hide(); 6332 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZSeeing); 6333 m_CFZUI->focusCFZSeeing->hide(); 6334 6335 // Add necessary widgets to the grid 6336 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZToleranceLabel, 1, 0); 6337 m_CFZUI->focusCFZToleranceLabel->show(); 6338 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZTolerance, 1, 1); 6339 m_CFZUI->focusCFZTolerance->show(); 6340 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZWavelengthLabel, 2, 0); 6341 m_CFZUI->focusCFZWavelengthLabel->show(); 6342 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZWavelength, 2, 1); 6343 m_CFZUI->focusCFZWavelength->show(); 6344 break; 6345 6346 case Focus::FOCUS_CFZ_GOLD: 6347 // CFZ = 0.00225 √τ θ f² A 6348 cfzMicrons = 0.00225f * pow(m_CFZUI->focusCFZTau->value(), 0.5f) * m_CFZUI->focusCFZSeeing->value() 6349 * pow(m_CFZUI->focusCFZFNumber->value(), 2.0f) * m_CFZUI->focusCFZAperture->value(); 6350 cfzSteps = cfzMicrons / m_CFZUI->focusCFZStepSize->value(); 6351 m_cfzSteps = std::round(std::max(cfzSteps, cfzCameraSteps)); 6352 m_CFZUI->focusCFZFormula->setText("CFZ = 0.00225 √τ θ f² A"); 6353 m_CFZUI->focusCFZ->setText(QString("%1 μm").arg(cfzMicrons, 0, 'f', 0)); 6354 m_CFZUI->focusCFZSteps->setText(QString("%1 steps").arg(cfzSteps, 0, 'f', 0)); 6355 m_CFZUI->focusCFZCameraSteps->setText(QString("%1 steps").arg(cfzCameraSteps, 0, 'f', 0)); 6356 m_CFZUI->focusCFZFinal->setText(QString("%1 steps").arg(m_cfzSteps, 0, 'f', 0)); 6357 6358 // Remove widgets not relevant to this algo 6359 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZToleranceLabel); 6360 m_CFZUI->focusCFZToleranceLabel->hide(); 6361 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTolerance); 6362 m_CFZUI->focusCFZTolerance->hide(); 6363 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZWavelengthLabel); 6364 m_CFZUI->focusCFZWavelengthLabel->hide(); 6365 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZWavelength); 6366 m_CFZUI->focusCFZWavelength->hide(); 6367 6368 // Add necessary widgets to the grid 6369 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZTauLabel, 1, 0); 6370 m_CFZUI->focusCFZTauLabel->show(); 6371 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZTau, 1, 1); 6372 m_CFZUI->focusCFZTau->show(); 6373 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZSeeingLabel, 2, 0); 6374 m_CFZUI->focusCFZSeeingLabel->show(); 6375 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZSeeing, 2, 1); 6376 m_CFZUI->focusCFZSeeing->show(); 6377 break; 6378 } 6379 if (linearFocuser != nullptr && linearFocuser->isDone()) 6380 emit drawCFZ(linearFocuser->solution(), linearFocuser->solutionValue(), m_cfzSteps, 6381 m_CFZUI->focusCFZDisplayVCurve->isChecked()); 6382 } 6383 6384 // Calculate the CFZ of the camera in microns using 6385 // CFZcamera = pixel_size * f^2 * A 6386 // pixel_size in microns and A in mm so divide A by 1000. 6387 double Focus::calcCameraCFZ() 6388 { 6389 return m_CcdPixelSizeX * pow(m_CFZUI->focusCFZFNumber->value(), 2.0) * m_CFZUI->focusCFZAperture->value() / 1000.0; 6390 } 6391 6392 void Focus::wavelengthChanged() 6393 { 6394 // The static data for the wavelength held against the filter has been updated so use the new value 6395 if (m_FilterManager) 6396 { 6397 m_CFZUI->focusCFZWavelength->setValue(m_FilterManager->getFilterWavelength(filter())); 6398 calcCFZ(); 6399 } 6400 } 6401 6402 void Focus::resetCFZToOT() 6403 { 6404 // Set the aperture and focal ratio for the scope on the connected optical train 6405 m_CFZUI->focusCFZFNumber->setValue(m_FocalRatio); 6406 m_CFZUI->focusCFZAperture->setValue(m_Aperture); 6407 6408 // Set the wavelength from the active filter 6409 if (m_FilterManager) 6410 { 6411 if (m_CFZUI->focusCFZWavelength->value() != m_FilterManager->getFilterWavelength(filter())) 6412 m_CFZUI->focusCFZWavelength->setValue(m_FilterManager->getFilterWavelength(filter())); 6413 } 6414 calcCFZ(); 6415 } 6416 6417 // Load up the Focus Advisor recommendations 6418 void Focus::focusAdvisorSetup() 6419 { 6420 bool longFL = m_FocalLength > 1500; 6421 double imageScale = getStarUnits(FOCUS_STAR_HFR, FOCUS_UNITS_ARCSEC); 6422 QString str; 6423 bool centralObstruction = scopeHasObstruction(m_ScopeType); 6424 6425 // Set the FA label based on the optical train 6426 m_AdvisorUI->focusAdvLabel->setText(QString("Recommendations: %1 FL=%2 ImageScale=%3") 6427 .arg(m_ScopeType).arg(m_FocalLength).arg(imageScale, 0, 'f', 2)); 6428 6429 // Step Size - Recommend CFZ 6430 m_AdvisorUI->focusAdvSteps->setValue(m_cfzSteps); 6431 6432 // Number steps - start with 5 6433 m_AdvisorUI->focusAdvOutStepMult->setValue(5); 6434 if (centralObstruction) 6435 str = "A good figure to start with is 5. You have a scope with a central obstruction that turns stars to donuts when\n" 6436 "they are out of focus. When this happens the system will struggle to identify stars correctly. To avoid this reduce\n" 6437 "either the step size or the number of steps.\n\n" 6438 "To check this situation, start at focus and move away by 'step size' * 'number of steps' steps. Take a focus frame\n" 6439 "and zoom in on the fitsviewer to see whether stars appear as stars or donuts."; 6440 else 6441 str = "A good figure to start with is 5."; 6442 m_AdvisorUI->focusAdvOutStepMult->setToolTip(str); 6443 m_AdvisorUI->focusAdvOutStepMultLabel->setToolTip(str); 6444 6445 // Camera options: exposure and bining 6446 str = "Camera & Filter Wheel Parameters:\n"; 6447 if (longFL) 6448 { 6449 FAExposure = 4.0; 6450 str.append("Exp=4.0\n"); 6451 } 6452 else 6453 { 6454 FAExposure = 2.0; 6455 str.append("Exp=2.0\n"); 6456 } 6457 6458 FABinning = ""; 6459 if (focusBinning->isEnabled()) 6460 { 6461 // Only try and update the binning field if camera supports it (binning field enabled) 6462 QString binTarget = (imageScale < 1.0) ? "2x2" : "1x1"; 6463 6464 for (int i = 0; i < focusBinning->count(); i++) 6465 { 6466 if (focusBinning->itemText(i) == binTarget) 6467 { 6468 FABinning = binTarget; 6469 str.append(QString("Bin=%1\n").arg(binTarget)); 6470 break; 6471 } 6472 } 6473 } 6474 6475 str.append("Gain ***Set Manually to Unity Gain***\n"); 6476 str.append("Filter ***Set Manually***"); 6477 m_AdvisorUI->focusAdvCameraLabel->setToolTip(str); 6478 6479 // Settings 6480 str = "Settings Parameters:\n"; 6481 FAAutoSelectStar = false; 6482 str.append("Auto Select Star=off\n"); 6483 6484 FADarkFrame = false; 6485 str.append("Dark Frame=off\n"); 6486 6487 FAFullFieldInnerRadius = 0.0; 6488 FAFullFieldOuterRadius = 80.0; 6489 str.append("Ring Mask 0%-80%\n"); 6490 6491 // Suspend Guilding, Guide Settle and Display Units won't affect Autofocus so don't set 6492 6493 FAAdaptiveFocus = false; 6494 str.append("Adaptive Focus=off\n"); 6495 6496 FAAdaptStartPos = false; 6497 str.append("Adapt Start Pos=off"); 6498 6499 m_AdvisorUI->focusAdvSettingsLabel->setToolTip(str); 6500 6501 // Process 6502 str = "Process Parameters:\n"; 6503 FAFocusDetection = ALGORITHM_SEP; 6504 str.append("Detection=SEP\n"); 6505 6506 FAFocusSEPProfile = ""; 6507 for (int i = 0; i < m_OpsFocusProcess->focusSEPProfile->count(); i++) 6508 { 6509 if (m_OpsFocusProcess->focusSEPProfile->itemText(i) == "1-Focus-Default") 6510 { 6511 FAFocusSEPProfile = "1-Focus-Default"; 6512 str.append(QString("SEP Profile=%1\n").arg(FAFocusSEPProfile)); 6513 break; 6514 } 6515 } 6516 6517 FAFocusAlgorithm = FOCUS_LINEAR1PASS; 6518 str.append("Algorithm=Linear 1 Pass\n"); 6519 6520 FACurveFit = CurveFitting::FOCUS_HYPERBOLA; 6521 str.append("Curve Fit=Hyperbola\n"); 6522 6523 FAStarMeasure = FOCUS_STAR_HFR; 6524 str.append("Measure=HFR\n"); 6525 6526 FAUseWeights = true; 6527 str.append("Use Weights=on\n"); 6528 6529 FAFocusR2Limit = 0.8; 6530 str.append("R² Limit=0.8\n"); 6531 6532 FAFocusRefineCurveFit = true; 6533 str.append("Refine Curve Fit=on\n"); 6534 6535 FAFocusFramesCount = 1; 6536 str.append("Average Over=1"); 6537 6538 m_AdvisorUI->focusAdvProcessLabel->setToolTip(str); 6539 6540 // Mechanics 6541 str = "Mechanics Parameters:\n"; 6542 FAFocusWalk = FOCUS_WALK_CLASSIC; 6543 str.append("Walk=Classic\n"); 6544 6545 FAFocusSettleTime = 1.0; 6546 str.append("Focuser Settle=1\n"); 6547 6548 // Set Max travel to max value - no need to limit it 6549 FAFocusMaxTravel = m_OpsFocusMechanics->focusMaxTravel->maximum(); 6550 str.append(QString("Max Travel=%1\n").arg(FAFocusMaxTravel)); 6551 6552 // Driver Backlash and AF Overscan are dealt with separately so inform user to do this 6553 str.append("Backlash ***Set Manually***\n"); 6554 str.append("AF Overscan ***Set Manually***\n"); 6555 6556 FAFocusCaptureTimeout = 30; 6557 str.append(QString("Capture Timeout=%1\n").arg(FAFocusCaptureTimeout)); 6558 6559 FAFocusMotionTimeout = 30; 6560 str.append(QString("Motion Timeout=%1").arg(FAFocusMotionTimeout)); 6561 6562 m_AdvisorUI->focusAdvMechanicsLabel->setToolTip(str); 6563 } 6564 6565 // Focus Advisor help popup 6566 void Focus::focusAdvisorHelp() 6567 { 6568 QString str = i18n("Focus Advisor (FA) is designed to help you with focus parameters.\n" 6569 "It will not necessarily give you the perfect combination of parameters, you will " 6570 "need to experiment yourself, but it will give you a basic set of parameters to " 6571 "achieve focus.\n\n" 6572 "FA will recommend values for the majority of parameters. A few, however, will need " 6573 "extra work from you to setup. These are identified below along with a basic explanation " 6574 "of how to set them.\n\n" 6575 "The first step is to set backlash. Your focuser manual will likely explain how to do " 6576 "this. Once you have a value for backlash for your system, set either the Backlash field " 6577 "to have the driver perform backlash compensation or the AF Overscan field to have Autofocus " 6578 "perform backlash compensation. Set only one field and set the other to 0.\n\n" 6579 "The second step is to set Step Size. This can be defaulted from the Critical Focus Zone (CFZ) " 6580 "for your equipment - so configure this now in the CFZ tab.\n\n" 6581 "The third step is to set the Out Step Multiple. Start with the suggested default."); 6582 6583 if (scopeHasObstruction(m_ScopeType)) 6584 str.append(i18n(" You have a scope with a central obstruction so be careful not to move too far away from " 6585 "focus as stars will appear as donuts and will not be detected properly. Experiment by " 6586 "finding focus and moving Step Size * Out Step Multiple ticks away from focus and take a " 6587 "focus frame. Zoom in to observe star detection. If it is poor then move the focuser back " 6588 "towards focus until star detection is acceptable. Adjust Out Step Multiple to correspond to " 6589 "this range of focuser motion.")); 6590 6591 str.append(i18n("\n\nThe fourth step is to set the remaining focus parameters to sensible values. Focus Advisor " 6592 "will suggest values for 4 categories of parameters. Check the associated Update box to " 6593 "accept these recommendations when you press Update Params.\n" 6594 "1. Camera Properties - Note you need to ensure Gain is set appropriately, e.g. unity gain.\n" 6595 "2. Focus Settings (Options Popup): These all have recommendations.\n" 6596 "3. Focus Process (Options Popup): These all have recommendations.\n" 6597 "4. Focus Mechanics (Options Popup): Note Step Size and Out Step Multiple are dealt with above.\n\n" 6598 "Now move the focuser to approximate focus and select a broadband filter, e.g. Luminance\n" 6599 "You are now ready to start an Autofocus run.")); 6600 6601 KMessageBox::information(nullptr, str, i18n("Focus Advisor")); 6602 } 6603 6604 // Action the focus params recommendations 6605 void Focus::focusAdvisorAction() 6606 { 6607 if (m_AdvisorUI->focusAdvStepSize->isChecked()) 6608 m_OpsFocusMechanics->focusTicks->setValue(m_AdvisorUI->focusAdvSteps->value()); 6609 6610 if (m_AdvisorUI->focusAdvOutStepMultiple->isChecked()) 6611 m_OpsFocusMechanics->focusOutSteps->setValue(m_AdvisorUI->focusAdvOutStepMult->value()); 6612 6613 if (m_AdvisorUI->focusAdvCamera->isChecked()) 6614 { 6615 focusExposure->setValue(FAExposure); 6616 if (focusBinning->isEnabled() && FABinning != "") 6617 // Only try and update the binning field if camera supports it (binning field enabled) 6618 focusBinning->setCurrentText(FABinning); 6619 } 6620 6621 if (m_AdvisorUI->focusAdvSettingsTab->isChecked()) 6622 { 6623 // Settings 6624 m_OpsFocusSettings->useFocusDarkFrame->setChecked(FADarkFrame); 6625 m_OpsFocusSettings->focusUseFullField->setChecked(true); 6626 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(FAAutoSelectStar); 6627 m_OpsFocusSettings->focusFullFieldInnerRadius->setValue(FAFullFieldInnerRadius); 6628 m_OpsFocusSettings->focusFullFieldOuterRadius->setValue(FAFullFieldOuterRadius); 6629 m_OpsFocusSettings->focusRingMaskRB->setChecked(true); 6630 m_OpsFocusSettings->focusAdaptive->setChecked(FAAdaptiveFocus); 6631 m_OpsFocusSettings->focusAdaptStart->setChecked(FAAdaptStartPos); 6632 } 6633 6634 if (m_AdvisorUI->focusAdvProcessTab->isChecked()) 6635 { 6636 // Process 6637 m_OpsFocusProcess->focusDetection->setCurrentIndex(FAFocusDetection); 6638 if (FAFocusSEPProfile != "") 6639 m_OpsFocusProcess->focusSEPProfile->setCurrentText(FAFocusSEPProfile); 6640 m_OpsFocusProcess->focusAlgorithm->setCurrentIndex(FAFocusAlgorithm); 6641 m_OpsFocusProcess->focusCurveFit->setCurrentIndex(FACurveFit); 6642 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FAStarMeasure); 6643 m_OpsFocusProcess->focusUseWeights->setChecked(FAUseWeights); 6644 m_OpsFocusProcess->focusR2Limit->setValue(FAFocusR2Limit); 6645 m_OpsFocusProcess->focusRefineCurveFit->setChecked(FAFocusRefineCurveFit); 6646 m_OpsFocusProcess->focusFramesCount->setValue(FAFocusFramesCount); 6647 } 6648 6649 if (m_AdvisorUI->focusAdvMechanicsTab->isChecked()) 6650 { 6651 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FAFocusWalk); 6652 m_OpsFocusMechanics->focusSettleTime->setValue(FAFocusSettleTime); 6653 m_OpsFocusMechanics->focusMaxTravel->setValue(FAFocusMaxTravel); 6654 m_OpsFocusMechanics->focusCaptureTimeout->setValue(FAFocusCaptureTimeout); 6655 m_OpsFocusMechanics->focusMotionTimeout->setValue(FAFocusMotionTimeout); 6656 } 6657 } 6658 6659 // Returns whether or not the passed in scopeType has a central obstruction or not. The scopeTypes 6660 // are defined in the equipmentWriter code. It would be better, probably, if that code included 6661 // a flag for central obstruction, rather than hard coding strings for the scopeType that are compared 6662 // in this routine. 6663 bool Focus::scopeHasObstruction(QString scopeType) 6664 { 6665 if (scopeType == "Refractor" || scopeType == "Kutter (Schiefspiegler)") 6666 return false; 6667 else 6668 return true; 6669 } 6670 6671 void Focus::setState(FocusState newState) 6672 { 6673 qCDebug(KSTARS_EKOS_FOCUS) << "Focus State changes from" << getFocusStatusString(m_state) << "to" << getFocusStatusString( 6674 newState); 6675 m_state = newState; 6676 emit newStatus(m_state); 6677 } 6678 6679 void Focus::initView() 6680 { 6681 m_FocusView.reset(new FITSView(focusingWidget, FITS_FOCUS)); 6682 m_FocusView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 6683 m_FocusView->setBaseSize(focusingWidget->size()); 6684 m_FocusView->createFloatingToolBar(); 6685 QVBoxLayout *vlayout = new QVBoxLayout(); 6686 vlayout->addWidget(m_FocusView.get()); 6687 focusingWidget->setLayout(vlayout); 6688 connect(m_FocusView.get(), &FITSView::trackingStarSelected, this, &Ekos::Focus::focusStarSelected, Qt::UniqueConnection); 6689 m_FocusView->setStarsEnabled(true); 6690 m_FocusView->setStarsHFREnabled(true); 6691 } 6692 6693 QVariantMap Focus::getAllSettings() const 6694 { 6695 QVariantMap settings; 6696 6697 // All Combo Boxes 6698 for (auto &oneWidget : findChildren<QComboBox*>()) 6699 settings.insert(oneWidget->objectName(), oneWidget->currentText()); 6700 6701 // All Double Spin Boxes 6702 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 6703 settings.insert(oneWidget->objectName(), oneWidget->value()); 6704 6705 // All Spin Boxes 6706 for (auto &oneWidget : findChildren<QSpinBox*>()) 6707 settings.insert(oneWidget->objectName(), oneWidget->value()); 6708 6709 // All Checkboxes 6710 for (auto &oneWidget : findChildren<QCheckBox*>()) 6711 settings.insert(oneWidget->objectName(), oneWidget->isChecked()); 6712 6713 // All Checkable Groupboxes 6714 for (auto &oneWidget : findChildren<QGroupBox*>()) 6715 if (oneWidget->isCheckable()) 6716 settings.insert(oneWidget->objectName(), oneWidget->isChecked()); 6717 6718 // All Checkable Groupboxes 6719 for (auto &oneWidget : findChildren<QGroupBox*>()) 6720 if (oneWidget->isCheckable()) 6721 settings.insert(oneWidget->objectName(), oneWidget->isChecked()); 6722 6723 // All Splitters 6724 for (auto &oneWidget : findChildren<QSplitter*>()) 6725 settings.insert(oneWidget->objectName(), QString::fromUtf8(oneWidget->saveState().toBase64())); 6726 6727 // All Radio Buttons 6728 for (auto &oneWidget : findChildren<QRadioButton*>()) 6729 settings.insert(oneWidget->objectName(), oneWidget->isChecked()); 6730 6731 return settings; 6732 } 6733 6734 void Focus::setAllSettings(const QVariantMap &settings) 6735 { 6736 // Disconnect settings that we don't end up calling syncSettings while 6737 // performing the changes. 6738 disconnectSyncSettings(); 6739 6740 for (auto &name : settings.keys()) 6741 { 6742 // Combo 6743 auto comboBox = findChild<QComboBox*>(name); 6744 if (comboBox) 6745 { 6746 syncControl(settings, name, comboBox); 6747 continue; 6748 } 6749 6750 // Double spinbox 6751 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name); 6752 if (doubleSpinBox) 6753 { 6754 syncControl(settings, name, doubleSpinBox); 6755 continue; 6756 } 6757 6758 // spinbox 6759 auto spinBox = findChild<QSpinBox*>(name); 6760 if (spinBox) 6761 { 6762 syncControl(settings, name, spinBox); 6763 continue; 6764 } 6765 6766 // checkbox 6767 auto checkbox = findChild<QCheckBox*>(name); 6768 if (checkbox) 6769 { 6770 syncControl(settings, name, checkbox); 6771 continue; 6772 } 6773 6774 // Splitters 6775 auto splitter = findChild<QSplitter*>(name); 6776 if (splitter) 6777 { 6778 syncControl(settings, name, splitter); 6779 continue; 6780 } 6781 6782 // Radio button 6783 auto radioButton = findChild<QRadioButton*>(name); 6784 if (radioButton) 6785 { 6786 syncControl(settings, name, radioButton); 6787 continue; 6788 } 6789 } 6790 6791 // Sync to options 6792 for (auto &key : settings.keys()) 6793 { 6794 auto value = settings[key]; 6795 // Save immediately 6796 Options::self()->setProperty(key.toLatin1(), value); 6797 6798 m_Settings[key] = value; 6799 m_GlobalSettings[key] = value; 6800 } 6801 6802 emit settingsUpdated(getAllSettings()); 6803 6804 // Save to optical train specific settings as well 6805 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText())); 6806 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Focus, m_Settings); 6807 6808 // Restablish connections 6809 connectSyncSettings(); 6810 6811 // Once settings have been loaded run through routines to set state variables 6812 m_CurveFit = static_cast<CurveFitting::CurveFit> (m_OpsFocusProcess->focusCurveFit->currentIndex()); 6813 setFocusDetection(static_cast<StarAlgorithm> (m_OpsFocusProcess->focusDetection->currentIndex())); 6814 setCurveFit(static_cast<CurveFitting::CurveFit>(m_OpsFocusProcess->focusCurveFit->currentIndex())); 6815 setStarMeasure(static_cast<StarMeasure>(m_OpsFocusProcess->focusStarMeasure->currentIndex())); 6816 setWalk(static_cast<FocusWalk>(m_OpsFocusMechanics->focusWalk->currentIndex())); 6817 } 6818 6819 bool Focus::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget) 6820 { 6821 QSpinBox *pSB = nullptr; 6822 QDoubleSpinBox *pDSB = nullptr; 6823 QCheckBox *pCB = nullptr; 6824 QComboBox *pComboBox = nullptr; 6825 QSplitter *pSplitter = nullptr; 6826 QRadioButton *pRadioButton = nullptr; 6827 bool ok = true; 6828 6829 if ((pSB = qobject_cast<QSpinBox *>(widget))) 6830 { 6831 const int value = settings[key].toInt(&ok); 6832 if (ok) 6833 { 6834 pSB->setValue(value); 6835 return true; 6836 } 6837 } 6838 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget))) 6839 { 6840 const double value = settings[key].toDouble(&ok); 6841 if (ok) 6842 { 6843 pDSB->setValue(value); 6844 return true; 6845 } 6846 } 6847 else if ((pCB = qobject_cast<QCheckBox *>(widget))) 6848 { 6849 const bool value = settings[key].toBool(); 6850 pCB->setChecked(value); 6851 return true; 6852 } 6853 // ONLY FOR STRINGS, not INDEX 6854 else if ((pComboBox = qobject_cast<QComboBox *>(widget))) 6855 { 6856 const QString value = settings[key].toString(); 6857 pComboBox->setCurrentText(value); 6858 return true; 6859 } 6860 else if ((pSplitter = qobject_cast<QSplitter *>(widget))) 6861 { 6862 const auto value = QByteArray::fromBase64(settings[key].toString().toUtf8()); 6863 pSplitter->restoreState(value); 6864 return true; 6865 } 6866 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget))) 6867 { 6868 const bool value = settings[key].toBool(); 6869 pRadioButton->setChecked(value); 6870 return true; 6871 } 6872 6873 return false; 6874 }; 6875 6876 void Focus::setupOpticalTrainManager() 6877 { 6878 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &Focus::refreshOpticalTrain); 6879 connect(trainB, &QPushButton::clicked, this, [this]() 6880 { 6881 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText()); 6882 }); 6883 connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) 6884 { 6885 ProfileSettings::Instance()->setOneSetting(ProfileSettings::FocusOpticalTrain, 6886 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index))); 6887 refreshOpticalTrain(); 6888 emit trainChanged(); 6889 }); 6890 } 6891 6892 void Focus::refreshOpticalTrain() 6893 { 6894 opticalTrainCombo->blockSignals(true); 6895 opticalTrainCombo->clear(); 6896 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames()); 6897 trainB->setEnabled(true); 6898 6899 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::FocusOpticalTrain); 6900 6901 if (trainID.isValid()) 6902 { 6903 auto id = trainID.toUInt(); 6904 6905 // If train not found, select the first one available. 6906 if (OpticalTrainManager::Instance()->exists(id) == false) 6907 { 6908 qCWarning(KSTARS_EKOS_FOCUS) << "Optical train doesn't exist for id" << id; 6909 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0)); 6910 } 6911 6912 auto name = OpticalTrainManager::Instance()->name(id); 6913 6914 opticalTrainCombo->setCurrentText(name); 6915 6916 // Load train settings 6917 // This needs to be done near the start of this function as methods further down 6918 // cause settings to be updated, which in turn interferes with the persistence and 6919 // setup of settings in OpticalTrainSettings 6920 OpticalTrainSettings::Instance()->setOpticalTrainID(id); 6921 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Focus); 6922 if (settings.isValid()) 6923 setAllSettings(settings.toJsonObject().toVariantMap()); 6924 else 6925 m_Settings = m_GlobalSettings; 6926 6927 auto focuser = OpticalTrainManager::Instance()->getFocuser(name); 6928 setFocuser(focuser); 6929 6930 auto scope = OpticalTrainManager::Instance()->getScope(name); 6931 6932 // CFZ and FA use scope parameters in their calcs - so update... 6933 m_Aperture = scope["aperture"].toDouble(-1); 6934 m_FocalLength = scope["focal_length"].toDouble(-1); 6935 m_FocalRatio = scope["focal_ratio"].toDouble(-1); 6936 m_ScopeType = scope["type"].toString(); 6937 m_Reducer = OpticalTrainManager::Instance()->getReducer(name); 6938 6939 // Adjust telescope FL and F# for any reducer 6940 if (m_Reducer > 0.0) 6941 m_FocalLength *= m_Reducer; 6942 6943 // Use the adjusted focal length to calculate an adjusted focal ratio 6944 if (m_FocalRatio <= 0.0) 6945 // For a scope, FL and aperture are specified so calc the F# 6946 m_FocalRatio = (m_Aperture > 0.001) ? m_FocalLength / m_Aperture : 0.0f; 6947 else if (m_Aperture < 0.0) 6948 // DSLR Lens. FL and F# are specified so calc the aperture 6949 m_Aperture = m_FocalLength / m_FocalRatio; 6950 6951 auto camera = OpticalTrainManager::Instance()->getCamera(name); 6952 if (camera) 6953 { 6954 opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(camera->getDeviceName(), scope["name"].toString())); 6955 6956 // Get the pixel size of the active camera for later calculations 6957 auto nvp = camera->getNumber("CCD_INFO"); 6958 if (!nvp) 6959 { 6960 m_CcdPixelSizeX = 0.0; 6961 m_CcdWidth = m_CcdHeight = 0; 6962 } 6963 else 6964 { 6965 auto np = nvp->findWidgetByName("CCD_PIXEL_SIZE_X"); 6966 if (np) 6967 m_CcdPixelSizeX = np->getValue(); 6968 np = nvp->findWidgetByName("CCD_MAX_X"); 6969 if (np) 6970 m_CcdWidth = np->getValue(); 6971 np = nvp->findWidgetByName("CCD_MAX_Y"); 6972 if (np) 6973 m_CcdHeight = np->getValue(); 6974 } 6975 } 6976 setCamera(camera); 6977 6978 auto filterWheel = OpticalTrainManager::Instance()->getFilterWheel(name); 6979 setFilterWheel(filterWheel); 6980 6981 // Update calcs for the CFZ and Focus Advisor based on the new OT 6982 resetCFZToOT(); 6983 focusAdvisorSetup(); 6984 } 6985 6986 opticalTrainCombo->blockSignals(false); 6987 } 6988 6989 }