Kstars

focus.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "adaptivefocus.h"
8#include "focusadvisor.h"
9#include "focusadaptor.h"
10#include "focusalgorithms.h"
11#include "focusfwhm.h"
12#if defined(HAVE_OPENCV)
13#include "focusblurriness.h"
14#endif
15#include "aberrationinspector.h"
16#include "aberrationinspectorutils.h"
17#include "kstars.h"
18#include "kstarsdata.h"
19#include "Options.h"
20#include "stellarsolver.h"
21
22// Modules
23#include "ekos/guide/guide.h"
24#include "ekos/manager.h"
25
26// KStars Auxiliary
27#include "auxiliary/kspaths.h"
28#include "auxiliary/ksmessagebox.h"
29
30// Ekos Auxiliary
31#include "ekos/auxiliary/darklibrary.h"
32#include "ekos/auxiliary/darkprocessor.h"
33#include "ekos/auxiliary/profilesettings.h"
34#include "ekos/auxiliary/opticaltrainmanager.h"
35#include "ekos/auxiliary/opticaltrainsettings.h"
36#include "ekos/auxiliary/filtermanager.h"
37#include "ekos/auxiliary/stellarsolverprofileeditor.h"
38
39// FITS
40#include "fitsviewer/fitsdata.h"
41#include "fitsviewer/fitsview.h"
42#include "fitsviewer/fitsviewer.h"
43
44// Devices
45#include "indi/indifilterwheel.h"
46#include "ksnotification.h"
47#include "kconfigdialog.h"
48
49#include <basedevice.h>
50#include <gsl/gsl_fit.h>
51#include <gsl/gsl_vector.h>
52#include <gsl/gsl_min.h>
53
54#include <ekos_focus_debug.h>
55#include <config-kstars.h>
56
57#include <cmath>
58
59#ifdef HAVE_DATAVISUALIZATION
60#include "aberrationinspector.h"
61#include "aberrationinspectorutils.h"
62#endif
63
64#define MAXIMUM_ABS_ITERATIONS 51
65#define MAXIMUM_RESET_ITERATIONS 3
66#define MAXIMUM_RESTART_ITERATIONS 3
67#define AUTO_STAR_TIMEOUT 45000
68#define MINIMUM_PULSE_TIMER 32
69#define MAX_RECAPTURE_RETRIES 3
70#define MINIMUM_POLY_SOLUTIONS 2
71
72namespace Ekos
73{
74Focus::Focus(int id) : QWidget()
75{
76 // #1 Set the UI
77 setupUi(this);
78 m_focuserId = id;
79 // create a dedicated dialog name for each focuser
80 m_opsDialogName = QString("focussettings %1").arg(id);
81
82 // #1a Prepare UI
83 prepareGUI();
84
85 // #2 Register DBus
86 qRegisterMetaType<Ekos::FocusState>("Ekos::FocusState");
87 qDBusRegisterMetaType<Ekos::FocusState>();
88 new FocusAdaptor(this);
89 QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Focus", this);
90
91 // #3 Init connections
92 initConnections();
93
94 // #4 Init Plots
95 initPlots();
96
97 // #5 Init View
98 initView();
99
100 // #6 Init helper objects
101 initHelperObjects();
102
103 // #7 Reset all buttons to default states
104 resetButtons();
105
106 // #8 Load All settings
107 loadGlobalSettings();
108
109 // #9 Init Setting Connection now
110 connectSyncSettings();
111
112 connect(focusAdvisor.get(), &FocusAdvisor::newStage, this, &Focus::newFocusAdvisorStage);
113 connect(focusAdvisor.get(), &FocusAdvisor::newMessage, this, &Focus::newFocusAdvisorMessage);
114
115 connect(&m_StarFinderWatcher, &QFutureWatcher<bool>::finished, this, &Focus::starDetectionFinished);
116
117 //Note: This is to prevent a button from being called the default button
118 //and then executing when the user hits the enter key such as when on a Text Box
119 QList<QPushButton *> qButtons = findChildren<QPushButton *>();
120 for (auto &button : qButtons)
121 button->setAutoDefault(false);
122
123 appendLogText(i18n("Idle."));
124
125 // Focus motion timeout
126 m_FocusMotionTimer.setInterval(m_OpsFocusMechanics->focusMotionTimeout->value() * 1000);
127 m_FocusMotionTimer.setSingleShot(true);
128 connect(&m_FocusMotionTimer, &QTimer::timeout, this, &Focus::handleFocusMotionTimeout);
129
130 m_OpsFocusProcess->editFocusProfile->setIcon(QIcon::fromTheme("document-edit"));
131 m_OpsFocusProcess->editFocusProfile->setAttribute(Qt::WA_LayoutUsesWidgetRect);
132
133 connect(m_OpsFocusProcess->editFocusProfile, &QAbstractButton::clicked, this, [this]()
134 {
135 KConfigDialog *optionsEditor = new KConfigDialog(this, "OptionsProfileEditor", Options::self());
136 optionsProfileEditor = new StellarSolverProfileEditor(this, Ekos::FocusProfiles, optionsEditor);
137#ifdef Q_OS_MACOS
138 optionsEditor->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
139#endif
140 KPageWidgetItem *mainPage = optionsEditor->addPage(optionsProfileEditor, i18n("Focus Options Profile Editor"));
141 mainPage->setIcon(QIcon::fromTheme("configure"));
142 connect(optionsProfileEditor, &StellarSolverProfileEditor::optionsProfilesUpdated, this, &Focus::loadStellarSolverProfiles);
143 optionsProfileEditor->loadProfile(m_OpsFocusProcess->focusSEPProfile->currentText());
144 optionsEditor->show();
145 });
146
147 loadStellarSolverProfiles();
148
149 // connect HFR plot widget
150 connect(this, &Ekos::Focus::initHFRPlot, HFRPlot, &FocusHFRVPlot::init);
151 connect(this, &Ekos::Focus::redrawHFRPlot, HFRPlot, &FocusHFRVPlot::redraw);
152 connect(this, &Ekos::Focus::newHFRPlotPosition, HFRPlot, &FocusHFRVPlot::addPosition);
153 connect(this, &Ekos::Focus::drawPolynomial, HFRPlot, &FocusHFRVPlot::drawPolynomial);
154 // connect signal/slot for the curve plotting to the V-Curve widget
155 connect(this, &Ekos::Focus::drawCurve, HFRPlot, &FocusHFRVPlot::drawCurve);
156 connect(this, &Ekos::Focus::setTitle, HFRPlot, &FocusHFRVPlot::setTitle);
157 connect(this, &Ekos::Focus::finalUpdates, HFRPlot, &FocusHFRVPlot::finalUpdates);
158 connect(this, &Ekos::Focus::minimumFound, HFRPlot, &FocusHFRVPlot::drawMinimum);
159 connect(this, &Ekos::Focus::drawCFZ, HFRPlot, &FocusHFRVPlot::drawCFZ);
160
161 m_DarkProcessor = new DarkProcessor(this);
162 connect(m_DarkProcessor, &DarkProcessor::newLog, this, &Ekos::Focus::appendLogText);
163 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted, this, [this](bool completed)
164 {
165 m_OpsFocusSettings->useFocusDarkFrame->setChecked(completed);
166 m_FocusView->setProperty("suspended", false);
167 if (completed)
168 {
169 m_FocusView->rescale(ZOOM_KEEP_LEVEL);
170 m_FocusView->updateFrame();
171 }
172 setCaptureComplete();
173 resetButtons();
174 });
175
176 setupOpticalTrainManager();
177 refreshOpticalTrain();
178
179 // Needs to be done once
180 connectFilterManager();
181}
182
183// Do once only preparation of GUI
184void Focus::prepareGUI()
185{
186 // Parameters are handled by a dedicated KConfigDialog per focuser invoked by pressing the "Options..." button
187 // on the Focus window. There are 3 pages of options.
188 // Parameters are persisted per Optical Train, so when the user changes OT, the last persisted
189 // parameters for the new OT are loaded. In addition the "current" parameter values are also
190 // persisted locally using kstars.kcfg
191 // KConfigDialog has the ability to persist parameters to kstars.kcfg but this functionality
192 // is not used in Focus
193 KConfigDialog *dialog = new KConfigDialog(this, opsDialogName(), Options::self());
194 m_OpsFocusSettings = new OpsFocusSettings(opsDialogName());
195#ifdef Q_OS_MACOS
197#endif
198
199 KPageWidgetItem *page = dialog->addPage(m_OpsFocusSettings, i18n("Settings"), nullptr, i18n("Focus Settings"), false);
200 page->setIcon(QIcon::fromTheme("configure"));
201
202 m_OpsFocusProcess = new OpsFocusProcess(opsDialogName());
203 page = dialog->addPage(m_OpsFocusProcess, i18n("Process"), nullptr, i18n("Focus Process"), false);
204 page->setIcon(QIcon::fromTheme("transform-move"));
205
206 m_OpsFocusMechanics = new OpsFocusMechanics(opsDialogName());
207 page = dialog->addPage(m_OpsFocusMechanics, i18n("Mechanics"), nullptr, i18n("Focus Mechanics"), false);
208 page->setIcon(QIcon::fromTheme("tool-measure"));
209
210 // The CFZ is a tool so has its own dialog box.
211 m_CFZDialog = new QDialog(this);
212 m_CFZUI.reset(new Ui::focusCFZDialog());
213 m_CFZUI->setupUi(m_CFZDialog);
214#ifdef Q_OS_MACOS
215 m_CFZDialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
216#endif
217
218 // Remove all widgets from the temporary bucket. These will then be loaded as required
219 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel);
220 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusMultiRowAverage);
221 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel);
222 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusGaussianSigma);
223 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusThresholdLabel);
224 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusThreshold);
225 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel);
226 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize);
227 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusToleranceLabel);
228 m_OpsFocusProcess->gridLayoutProcessBucket->removeWidget(m_OpsFocusProcess->focusTolerance);
229 delete m_OpsFocusProcess->gridLayoutProcessBucket;
230
231 // Setup the Walk fields. OutSteps and NumSteps are either/or widgets so co-locate them
232 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusOutStepsLabel,
233 m_OpsFocusMechanics->focusNumStepsLabel);
234 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusOutSteps,
235 m_OpsFocusMechanics->focusNumSteps);
236
237 // Some combo-boxes have changeable values depending on other settings so store the full list of options from the .ui
238 // This helps keep some synchronisation with the .ui
239 for (int i = 0; i < m_OpsFocusProcess->focusStarMeasure->count(); i++)
240 m_StarMeasureText.append(m_OpsFocusProcess->focusStarMeasure->itemText(i));
241 for (int i = 0; i < m_OpsFocusProcess->focusCurveFit->count(); i++)
242 m_CurveFitText.append(m_OpsFocusProcess->focusCurveFit->itemText(i));
243 for (int i = 0; i < m_OpsFocusMechanics->focusWalk->count(); i++)
244 m_FocusWalkText.append(m_OpsFocusMechanics->focusWalk->itemText(i));
245}
246
247void Focus::loadStellarSolverProfiles()
248{
249 QString savedOptionsProfiles = QDir(KSPaths::writableLocation(
250 QStandardPaths::AppLocalDataLocation)).filePath("SavedFocusProfiles.ini");
251 if(QFileInfo::exists(savedOptionsProfiles))
252 {
253 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
254 addMissingStellarSolverProfile(savedOptionsProfiles, FOCUS_DEFAULT_NAME);
255 addMissingStellarSolverProfile(savedOptionsProfiles, FOCUS_DEFAULT_DONUT_NAME);
256 }
257 else
258 m_StellarSolverProfiles = getDefaultFocusOptionsProfiles();
259 m_OpsFocusProcess->focusSEPProfile->clear();
260 for(auto &param : m_StellarSolverProfiles)
261 m_OpsFocusProcess->focusSEPProfile->addItem(param.listName);
262 auto profile = m_Settings["focusSEPProfile"];
263 if (profile.isValid())
264 m_OpsFocusProcess->focusSEPProfile->setCurrentText(profile.toString());
265}
266
267void Focus::addMissingStellarSolverProfile(const QString profilePath, const QString profile)
268{
269 for(auto params : m_StellarSolverProfiles)
270 {
271 if (params.listName == profile)
272 // Profile already exists so nothing more to do
273 return;
274 }
275
276 QSettings settings(profilePath, QSettings::IniFormat);
277 SSolver::Parameters params;
278 if (profile == FOCUS_DEFAULT_DONUT_NAME)
279 params = getFocusOptionsProfileDefaultDonut();
280 else if (profile == FOCUS_DEFAULT_NAME)
281 params = getFocusOptionsProfileDefault();
282 else
283 return;
284
285 settings.beginGroup(params.listName);
286 QMap<QString, QVariant> map = SSolver::Parameters::convertToMap(params);
288 while(it.hasNext())
289 {
290 it.next();
291 settings.setValue(it.key(), it.value());
292 }
293 settings.endGroup();
294
295 m_StellarSolverProfiles.append(params);
296}
297
298QStringList Focus::getStellarSolverProfiles()
299{
300 QStringList profiles;
301 for (auto param : m_StellarSolverProfiles)
302 profiles << param.listName;
303
304 return profiles;
305}
306
307
308Focus::~Focus()
309{
310 if (focusingWidget->parent() == nullptr)
311 toggleFocusingWidgetFullScreen();
312}
313
314void Focus::resetFrame()
315{
316 if (m_Camera && m_Camera->isConnected())
317 {
318 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
319
320 if (targetChip)
321 {
322 //fx=fy=fw=fh=0;
323 targetChip->resetFrame();
324
325 int x, y, w, h;
326 targetChip->getFrame(&x, &y, &w, &h);
327
328 qCDebug(KSTARS_EKOS_FOCUS) << "Frame is reset. X:" << x << "Y:" << y << "W:" << w << "H:" << h << "binX:" << 1 << "binY:" <<
329 1;
330
331 QVariantMap settings;
332 settings["x"] = x;
333 settings["y"] = y;
334 settings["w"] = w;
335 settings["h"] = h;
336 settings["binx"] = 1;
337 settings["biny"] = 1;
338 frameSettings[targetChip] = settings;
339
340 starSelected = false;
341 starCenter = QVector3D();
342 subFramed = false;
343
344 m_FocusView->setTrackingBox(QRect());
345 checkMosaicMaskLimits();
346 }
347 }
348}
349
350QString Focus::camera()
351{
352 if (m_Camera)
353 return m_Camera->getDeviceName();
354
355 return QString();
356}
357
358void Focus::checkCamera()
359{
360 if (!m_Camera)
361 return;
362
363 // Do NOT perform checks when the camera is capturing or busy as this may result
364 // in signals/slots getting disconnected.
365 switch (state())
366 {
367 // Idle, can change camera.
368 case FOCUS_IDLE:
369 case FOCUS_COMPLETE:
370 case FOCUS_FAILED:
371 case FOCUS_ABORTED:
372 break;
373
374 // Busy, cannot change camera.
375 case FOCUS_WAITING:
376 case FOCUS_PROGRESS:
377 case FOCUS_FRAMING:
378 case FOCUS_CHANGING_FILTER:
379 return;
380 }
381
382
383 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
384 if (targetChip && targetChip->isCapturing())
385 return;
386
387 if (targetChip)
388 {
389 focusBinning->setEnabled(targetChip->canBin());
390 m_OpsFocusSettings->focusSubFrame->setEnabled(targetChip->canSubframe());
391 if (targetChip->canBin())
392 {
393 int subBinX = 1, subBinY = 1;
394 focusBinning->clear();
395 targetChip->getMaxBin(&subBinX, &subBinY);
396 for (int i = 1; i <= subBinX; i++)
397 focusBinning->addItem(QString("%1x%2").arg(i).arg(i));
398
399 auto binning = m_Settings["focusBinning"];
400 if (binning.isValid())
401 focusBinning->setCurrentText(binning.toString());
402 }
403
404 connect(m_Camera, &ISD::Camera::videoStreamToggled, this, &Ekos::Focus::setVideoStreamEnabled, Qt::UniqueConnection);
405 liveVideoB->setEnabled(m_Camera->hasVideoStream());
406 if (m_Camera->hasVideoStream())
407 setVideoStreamEnabled(m_Camera->isStreamingEnabled());
408 else
409 liveVideoB->setIcon(QIcon::fromTheme("camera-off"));
410
411 }
412
413 syncCCDControls();
414 syncCameraInfo();
415}
416
417void Focus::syncCCDControls()
418{
419 if (m_Camera == nullptr)
420 return;
421
422 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
423 if (targetChip == nullptr || (targetChip && targetChip->isCapturing()))
424 return;
425
426 auto isoList = targetChip->getISOList();
427 focusISO->clear();
428
429 if (isoList.isEmpty())
430 {
431 focusISO->setEnabled(false);
432 ISOLabel->setEnabled(false);
433 }
434 else
435 {
436 focusISO->setEnabled(true);
437 ISOLabel->setEnabled(true);
438 focusISO->addItems(isoList);
439 focusISO->setCurrentIndex(targetChip->getISOIndex());
440 }
441
442 bool hasGain = m_Camera->hasGain();
443 gainLabel->setEnabled(hasGain);
444 focusGain->setEnabled(hasGain && m_Camera->getGainPermission() != IP_RO);
445 if (hasGain)
446 {
447 double gain = 0, min = 0, max = 0, step = 1;
448 m_Camera->getGainMinMaxStep(&min, &max, &step);
449 if (m_Camera->getGain(&gain))
450 {
451 // Allow the possibility of no gain value at all.
452 GainSpinSpecialValue = min - step;
453 focusGain->setRange(GainSpinSpecialValue, max);
454 focusGain->setSpecialValueText(i18n("--"));
455 if (step > 0)
456 focusGain->setSingleStep(step);
457
458 auto defaultGain = m_Settings["focusGain"];
459 if (defaultGain.isValid())
460 focusGain->setValue(defaultGain.toDouble());
461 else
462 focusGain->setValue(GainSpinSpecialValue);
463 }
464 }
465 else
466 focusGain->clear();
467}
468
469void Focus::syncCameraInfo()
470{
471 if (m_Camera == nullptr)
472 return;
473
474 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
475 if (targetChip == nullptr || (targetChip && targetChip->isCapturing()))
476 return;
477
478 m_OpsFocusSettings->focusSubFrame->setEnabled(targetChip->canSubframe());
479
480 if (frameSettings.contains(targetChip) == false)
481 {
482 int x, y, w, h;
483 if (targetChip->getFrame(&x, &y, &w, &h))
484 {
485 int binx = 1, biny = 1;
486 targetChip->getBinning(&binx, &biny);
487 if (w > 0 && h > 0)
488 {
489 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
490 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
491
492 QVariantMap settings;
493
494 settings["x"] = m_OpsFocusSettings->focusSubFrame->isChecked() ? x : minX;
495 settings["y"] = m_OpsFocusSettings->focusSubFrame->isChecked() ? y : minY;
496 settings["w"] = m_OpsFocusSettings->focusSubFrame->isChecked() ? w : maxW;
497 settings["h"] = m_OpsFocusSettings->focusSubFrame->isChecked() ? h : maxH;
498 settings["binx"] = binx;
499 settings["biny"] = biny;
500
501 frameSettings[targetChip] = settings;
502 }
503 }
504 }
505}
506
507bool Focus::setFilterWheel(ISD::FilterWheel *device)
508{
509 if (m_FilterWheel && m_FilterWheel == device)
510 {
511 checkFilter();
512 return false;
513 }
514
515 if (m_FilterWheel)
516 m_FilterWheel->disconnect(this);
517
518 m_FilterWheel = device;
519
520 if (m_FilterWheel)
521 {
522 connect(m_FilterWheel, &ISD::ConcreteDevice::Connected, this, [this]()
523 {
524 FilterPosLabel->setEnabled(true);
525 focusFilter->setEnabled(true);
526 filterManagerB->setEnabled(true);
527 });
528 connect(m_FilterWheel, &ISD::ConcreteDevice::Disconnected, this, [this]()
529 {
530 FilterPosLabel->setEnabled(false);
531 focusFilter->setEnabled(false);
532 filterManagerB->setEnabled(false);
533 });
534 }
535
536 auto isConnected = m_FilterWheel && m_FilterWheel->isConnected();
537 FilterPosLabel->setEnabled(isConnected);
538 focusFilter->setEnabled(isConnected);
539 filterManagerB->setEnabled(isConnected);
540
541 FilterPosLabel->setEnabled(true);
542 focusFilter->setEnabled(true);
543
544 checkFilter();
545 return true;
546}
547
548void Focus::updateTemperatureSources(const QList<QSharedPointer<ISD::GenericDevice>> &temperatureSources)
549{
550 defaultFocusTemperatureSource->blockSignals(true);
551 m_TemperatureSources = temperatureSources;
552
553 defaultFocusTemperatureSource->clear();
554 for (auto device : temperatureSources)
555 {
556 if (device.isNull())
557 break;
558 defaultFocusTemperatureSource->addItem(device->getDeviceName());
559 }
560 defaultFocusTemperatureSource->blockSignals(false);
561
562 auto targetSource = m_Settings["defaultFocusTemperatureSource"];
563 if (targetSource.isValid())
564 checkTemperatureSource(targetSource.toString());
565}
566
567void Focus::checkTemperatureSource(const QString &name )
568{
569 auto source = name;
570 if (name.isEmpty())
571 {
572 source = defaultFocusTemperatureSource->currentText();
573 if (source.isEmpty())
574 return;
575 }
576
578
579 for (auto &oneSource : m_TemperatureSources)
580 {
581 if (oneSource->getDeviceName() == source)
582 {
583 currentSource = oneSource;
584 break;
585 }
586 }
587
588 m_LastSourceDeviceAutofocusTemperature = currentSource;
589
590 // No valid device found
591 if (!currentSource)
592 return;
593
594 // Disconnect all existing signals
595 for (const auto &oneSource : m_TemperatureSources)
596 disconnect(oneSource.get(), &ISD::GenericDevice::propertyUpdated, this, &Ekos::Focus::processTemperatureSource);
597
598
599 if (findTemperatureElement(currentSource))
600 {
601 defaultFocusTemperatureSource->setCurrentText(name);
602 m_LastSourceAutofocusTemperature = currentTemperatureSourceElement->value;
603 absoluteTemperatureLabel->setText(QString("%1 °C").arg(currentTemperatureSourceElement->value, 0, 'f', 2));
604 deltaTemperatureLabel->setText(QString("%1 °C").arg(0.0, 0, 'f', 2));
605 }
606 else
607 {
608 m_LastSourceAutofocusTemperature = INVALID_VALUE;
609 }
610
611 connect(currentSource.get(), &ISD::GenericDevice::propertyUpdated, this, &Ekos::Focus::processTemperatureSource,
613}
614
615bool Focus::findTemperatureElement(const QSharedPointer<ISD::GenericDevice> &device)
616{
617 // protect for nullptr
618 if (device.isNull())
619 return false;
620
621 auto temperatureProperty = device->getProperty("FOCUS_TEMPERATURE");
622 if (!temperatureProperty)
623 temperatureProperty = device->getProperty("CCD_TEMPERATURE");
624 if (temperatureProperty)
625 {
626 currentTemperatureSourceElement = temperatureProperty.getNumber()->at(0);
627 return true;
628 }
629
630 temperatureProperty = device->getProperty("WEATHER_PARAMETERS");
631 if (temperatureProperty)
632 {
633 for (int i = 0; i < temperatureProperty.getNumber()->count(); i++)
634 {
635 if (strstr(temperatureProperty.getNumber()->at(i)->getName(), "_TEMPERATURE"))
636 {
637 currentTemperatureSourceElement = temperatureProperty.getNumber()->at(i);
638 return true;
639 }
640 }
641 }
642
643 return false;
644}
645
646QString Focus::filterWheel()
647{
648 if (m_FilterWheel)
649 return m_FilterWheel->getDeviceName();
650
651 return QString();
652}
653
654
655bool Focus::setFilter(const QString &filter)
656{
657 if (m_FilterWheel)
658 {
659 focusFilter->setCurrentText(filter);
660 return true;
661 }
662
663 return false;
664}
665
666QString Focus::filter()
667{
668 return focusFilter->currentText();
669}
670
671void Focus::checkFilter()
672{
673 focusFilter->clear();
674
675 if (!m_FilterWheel)
676 {
677 FilterPosLabel->setEnabled(false);
678 focusFilter->setEnabled(false);
679 filterManagerB->setEnabled(false);
680
681 if (m_FilterManager)
682 {
683 m_FilterManager->disconnect(this);
684 disconnect(m_FilterManager.get());
685 m_FilterManager.reset();
686 }
687 return;
688 }
689
690 FilterPosLabel->setEnabled(true);
691 focusFilter->setEnabled(true);
692 filterManagerB->setEnabled(true);
693
694 setupFilterManager();
695
696 focusFilter->addItems(m_FilterManager->getFilterLabels());
697
698 currentFilterPosition = m_FilterManager->getFilterPosition();
699
700 focusFilter->setCurrentIndex(currentFilterPosition - 1);
701
702 focusExposure->setValue(m_FilterManager->getFilterExposure());
703}
704
705bool Focus::setFocuser(ISD::Focuser *device)
706{
707 if (m_Focuser && m_Focuser == device)
708 {
709 checkFocuser();
710 return false;
711 }
712
713 if (m_Focuser)
714 m_Focuser->disconnect(this);
715
716 m_Focuser = device;
717
718 if (m_Focuser)
719 {
720 connect(m_Focuser, &ISD::ConcreteDevice::Connected, this, [this]()
721 {
722 resetButtons();
723 });
724 connect(m_Focuser, &ISD::ConcreteDevice::Disconnected, this, [this]()
725 {
726 resetButtons();
727 });
728 }
729
730 checkFocuser();
731 emit focuserChanged(m_focuserId, device != nullptr);
732 return true;
733}
734
735QString Focus::focuser()
736{
737 if (m_Focuser)
738 return m_Focuser->getDeviceName();
739
740 return QString();
741}
742
743void Focus::checkFocuser()
744{
745 if (!m_Focuser)
746 {
747 if (m_FilterManager)
748 m_FilterManager->setFocusReady(false);
749 canAbsMove = canRelMove = canTimerMove = false;
750 resetButtons();
751 setFocusAlgorithm(static_cast<Algorithm> (m_OpsFocusProcess->focusAlgorithm->currentIndex()));
752 return;
753 }
754
755 if (m_FilterManager)
756 m_FilterManager->setFocusReady(m_Focuser->isConnected());
757
758 hasDeviation = m_Focuser->hasDeviation();
759
760 canAbsMove = m_Focuser->canAbsMove();
761
762 if (canAbsMove)
763 {
764 getAbsFocusPosition();
765
766 absTicksSpin->setEnabled(true);
767 absTicksLabel->setEnabled(true);
768 startGotoB->setEnabled(true);
769
770 // Now that Optical Trains are managing settings, no need to set absTicksSpin, except if it has a bad value
771 if (absTicksSpin->value() <= 0)
772 absTicksSpin->setValue(currentPosition);
773 }
774 else
775 {
776 absTicksSpin->setEnabled(false);
777 absTicksLabel->setEnabled(false);
778 startGotoB->setEnabled(false);
779 }
780
781 canRelMove = m_Focuser->canRelMove();
782
783 // In case we have a purely relative focuser, we pretend
784 // it is an absolute focuser with initial point set at 50,000.
785 // This is done we can use the same algorithm used for absolute focuser.
786 if (canAbsMove == false && canRelMove == true)
787 {
788 currentPosition = 50000;
789 absMotionMax = 100000;
790 absMotionMin = 0;
791 }
792
793 canTimerMove = m_Focuser->canTimerMove();
794
795 // In case we have a timer-based focuser and using the linear focus algorithm,
796 // we pretend it is an absolute focuser with initial point set at 50,000.
797 // These variables don't have in impact on timer-based focusers if the algorithm
798 // is not the linear focus algorithm.
799 if (!canAbsMove && !canRelMove && canTimerMove)
800 {
801 currentPosition = 50000;
802 absMotionMax = 100000;
803 absMotionMin = 0;
804 }
805
806 m_FocusType = (canRelMove || canAbsMove || canTimerMove) ? FOCUS_AUTO : FOCUS_MANUAL;
807 profilePlot->setFocusAuto(m_FocusType == FOCUS_AUTO);
808
809 bool hasBacklash = m_Focuser->hasBacklash();
810 m_OpsFocusMechanics->focusBacklash->setEnabled(hasBacklash);
811 m_OpsFocusMechanics->focusBacklash->disconnect(this);
812 if (hasBacklash)
813 {
814 double min = 0, max = 0, step = 0;
815 m_Focuser->getMinMaxStep("FOCUS_BACKLASH_STEPS", "FOCUS_BACKLASH_VALUE", &min, &max, &step);
816 m_OpsFocusMechanics->focusBacklash->setMinimum(min);
817 m_OpsFocusMechanics->focusBacklash->setMaximum(max);
818 m_OpsFocusMechanics->focusBacklash->setSingleStep(step);
819 m_OpsFocusMechanics->focusBacklash->setValue(m_Focuser->getBacklash());
820 connect(m_OpsFocusMechanics->focusBacklash, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
821 this, [this](int value)
822 {
823 if (m_Focuser)
824 {
825 if (m_Focuser->getBacklash() == value)
826 // Prevent an event storm where a fast update of the backlash field, e.g. changing
827 // the value to "12" results in 2 events; "1", "12". As these events get
828 // processed in the driver and persisted, they in turn update backlash and start
829 // to conflict with this callback, getting stuck forever: "1", "12", "1', "12"
830 return;
831 m_Focuser->setBacklash(value);
832 }
833 });
834 // Re-esablish connection to sync settings. Only need to reconnect if the focuser
835 // has backlash as the value can't be changed if the focuser doesn't have the backlash property.
836 connect(m_OpsFocusMechanics->focusBacklash, QOverload<int>::of(&QSpinBox::valueChanged), this, &Ekos::Focus::syncSettings);
837 }
838 else
839 {
840 m_OpsFocusMechanics->focusBacklash->setValue(0);
841 }
842
843 connect(m_Focuser, &ISD::Focuser::propertyUpdated, this, &Ekos::Focus::updateProperty, Qt::UniqueConnection);
844
845 resetButtons();
846 setFocusAlgorithm(static_cast<Algorithm> (m_OpsFocusProcess->focusAlgorithm->currentIndex()));
847}
848
849bool Focus::setCamera(ISD::Camera *device)
850{
851 if (m_Camera && m_Camera == device)
852 {
853 checkCamera();
854 return false;
855 }
856
857 m_captureInProgress = false;
858
859 if (m_Camera)
860 m_Camera->disconnect(this);
861
862 m_Camera = device;
863
864 if (m_Camera)
865 {
866 connect(m_Camera, &ISD::ConcreteDevice::Connected, this, [this]()
867 {
868 resetButtons();
869 });
870 connect(m_Camera, &ISD::ConcreteDevice::Disconnected, this, [this]()
871 {
872 resetButtons();
873 });
874 }
875
876 auto isConnected = m_Camera && m_Camera->isConnected();
877 focuserGroup->setEnabled(isConnected);
878 ccdGroup->setEnabled(isConnected);
879 toolsGroup->setEnabled(isConnected);
880
881 if (!m_Camera)
882 return false;
883
884 resetFrame();
885
886 checkCamera();
887 checkMosaicMaskLimits();
888 return true;
889}
890
891void Focus::getAbsFocusPosition()
892{
893 if (!canAbsMove)
894 return;
895
896 auto absMove = m_Focuser->getNumber("ABS_FOCUS_POSITION");
897
898 if (absMove)
899 {
900 const auto &it = absMove->at(0);
901 currentPosition = static_cast<int>(it->getValue());
902 absMotionMax = it->getMax();
903 absMotionMin = it->getMin();
904
905 absTicksSpin->setMinimum(it->getMin());
906 absTicksSpin->setMaximum(it->getMax());
907 absTicksSpin->setSingleStep(it->getStep());
908
909 // Restrict the travel if needed
910 double const travel = std::abs(it->getMax() - it->getMin());
911 m_OpsFocusMechanics->focusMaxTravel->setMaximum(travel);;
912
913 absTicksLabel->setText(QString::number(currentPosition));
914
915 m_OpsFocusMechanics->focusTicks->setMaximum(it->getMax() / 2);
916 }
917}
918
919void Focus::processTemperatureSource(INDI::Property prop)
920{
921 if (m_LastSourceAutofocusTemperature == INVALID_VALUE && m_LastSourceDeviceAutofocusTemperature
922 && !currentTemperatureSourceElement )
923 {
924 if( findTemperatureElement( m_LastSourceDeviceAutofocusTemperature ) )
925 {
926 appendLogText(i18n("Finally found temperature source %1", QString(currentTemperatureSourceElement->nvp->name)));
927 m_LastSourceAutofocusTemperature = currentTemperatureSourceElement->value;
928 }
929 }
930
931 double delta = 0;
932 if (currentTemperatureSourceElement && currentTemperatureSourceElement->nvp->name == prop.getName())
933 {
934 if (m_LastSourceAutofocusTemperature != INVALID_VALUE)
935 {
936 delta = currentTemperatureSourceElement->value - m_LastSourceAutofocusTemperature;
937 emit newFocusTemperatureDelta(abs(delta), currentTemperatureSourceElement->value, opticalTrain());
938 }
939 else
940 {
941 emit newFocusTemperatureDelta(0, currentTemperatureSourceElement->value, opticalTrain());
942 }
943
944 absoluteTemperatureLabel->setText(QString("%1 °C").arg(currentTemperatureSourceElement->value, 0, 'f', 2));
945 deltaTemperatureLabel->setText(QString("%1%2 °C").arg((delta > 0.0 ? "+" : "")).arg(delta, 0, 'f', 2));
946 if (delta == 0)
947 deltaTemperatureLabel->setStyleSheet("color: lightgreen");
948 else if (delta > 0)
949 deltaTemperatureLabel->setStyleSheet("color: lightcoral");
950 else
951 deltaTemperatureLabel->setStyleSheet("color: lightblue");
952 }
953}
954
955void Focus::setLastFocusTemperature()
956{
957 m_LastSourceAutofocusTemperature = currentTemperatureSourceElement ? currentTemperatureSourceElement->value : INVALID_VALUE;
958
959 // Reset delta to zero now that we're just done with autofocus
960 deltaTemperatureLabel->setText(QString("0 °C"));
961 deltaTemperatureLabel->setStyleSheet("color: lightgreen");
962
963 emit newFocusTemperatureDelta(0, -1e6, opticalTrain());
964}
965
966void Focus::setLastFocusAlt()
967{
968 if (mountAlt < 0.0 || mountAlt > 90.0)
969 m_LastSourceAutofocusAlt = INVALID_VALUE;
970 else
971 m_LastSourceAutofocusAlt = mountAlt;
972}
973
974// Reset Adaptive Focus parameters.
975void Focus::resetAdaptiveFocus(bool enabled)
976{
977 // Set the focusAdaptive switch so other modules such as Capture can access it
978 Options::setFocusAdaptive(enabled);
979
980 if (enabled)
981 adaptFocus.reset(new AdaptiveFocus(this));
982 adaptFocus->resetAdaptiveFocusCounters();
983}
984
985// Capture has signalled an Adaptive Focus run
986void Focus::adaptiveFocus()
987{
988 // Invoke runAdaptiveFocus
989 adaptFocus->runAdaptiveFocus(currentPosition, filter());
990}
991
992// Run Aberration Inspector
993void Focus::startAbIns()
994{
995 m_abInsOn = canAbInsStart();
996 runAutoFocus(AutofocusReason::FOCUS_ABERRATION_INSPECTOR, "");
997}
998
999// User pressed the Autofocus button
1000void Focus::manualStart()
1001{
1002 runAutoFocus(AutofocusReason::FOCUS_MANUAL, "");
1003}
1004
1005// An Autofocus start request over DBUS (most likely from the Scheduler)
1006void Focus::start()
1007{
1008 runAutoFocus(AutofocusReason::FOCUS_SCHEDULER, "");
1009}
1010
1011// Start an Autofocus run. This is called from Build Offsets, FilterManger and Capture
1012void Focus::runAutoFocus(AutofocusReason autofocusReason, const QString &reasonInfo)
1013{
1014 m_AutofocusReason = autofocusReason;
1015 m_AutofocusReasonInfo = reasonInfo;
1016 if (m_Focuser == nullptr)
1017 {
1018 appendLogText(i18n("No Focuser connected."));
1019 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_NO_FOCUSER);
1020 return;
1021 }
1022
1023 if (m_Camera == nullptr)
1024 {
1025 appendLogText(i18n("No CCD connected."));
1026 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_NO_CAMERA);
1027 return;
1028 }
1029
1030 if (!canAbsMove && !canRelMove && m_OpsFocusMechanics->focusTicks->value() <= MINIMUM_PULSE_TIMER)
1031 {
1032 appendLogText(i18n("Starting pulse step is too low. Increase the step size to %1 or higher...",
1033 MINIMUM_PULSE_TIMER * 5));
1034 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_LOW_PULSE);
1035 return;
1036 }
1037
1038 if (inAutoFocus)
1039 {
1040 // If Autofocus is already running, just ignore this request. This condition should not happen
1041 // There is no point signalling Autofocus failure as that will trigger actions based on the
1042 // currently running Autofocus failing (whilst it is still running).
1043 appendLogText(i18n("Autofocus is already running, discarding start request."));
1044 return;
1045 }
1046
1047 if (inBuildOffsets)
1048 {
1049 // If Build Offsets is running, reject the Autofocus request.
1050 appendLogText(i18n("Build Offset is already running, Autofocus rejected."));
1051 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_INTERNAL);
1052 return;
1053 }
1054
1055 if (inFocusLoop)
1056 {
1057 // If inFocusLoop is already running, reject this Autofocus request
1058 appendLogText(i18n("In Focus Loop, Autofocus rejected."));
1059 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_INTERNAL);
1060 return;
1061 }
1062
1063 if (inAdjustFocus || adaptFocus->inAdaptiveFocus())
1064 {
1065 QString str = inAdjustFocus ? i18n("Adjust Focus") : i18n("Adaptive Focus");
1066 if (++m_StartRetries < MAXIMUM_RESTART_ITERATIONS)
1067 {
1068 appendLogText(i18n("Autofocus start request - Waiting 10sec for %1 to complete.", str));
1069 QTimer::singleShot(10 * 1000, this, [this]()
1070 {
1071 runAutoFocus(m_AutofocusReason, m_AutofocusReasonInfo);
1072 });
1073 return;
1074 }
1075
1076 appendLogText(i18n("Discarding Autofocus start request - %1 in progress.", str));
1077 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_INTERNAL);
1078 return;
1079 }
1080
1081 inBuildOffsets = (autofocusReason == AutofocusReason::FOCUS_FILTER_OFFSETS);
1082 if (autofocusReason == AutofocusReason::FOCUS_USER_REQUEST)
1083 {
1084 forceInSeqAF->setChecked(false);
1085 emit inSequenceAF(false, opticalTrain());
1086 }
1087 inAutoFocus = true;
1088 m_AFRun++;
1089 m_StartRetries = 0;
1090 m_LastFocusDirection = FOCUS_NONE;
1091
1092 waitStarSelectTimer.stop();
1093
1094 // Reset the focus motion timer
1095 m_FocusMotionTimerCounter = 0;
1096 m_FocusMotionTimer.stop();
1097 m_FocusMotionTimer.setInterval(m_OpsFocusMechanics->focusMotionTimeout->value() * 1000);
1098
1099 // Reset focuser reconnect counter
1100 m_FocuserReconnectCounter = 0;
1101
1102 // Reset focuser reconnect counter
1103 m_FocuserReconnectCounter = 0;
1104 m_DebugFocuserCounter = 0;
1105
1106 starsHFR.clear();
1107
1108 lastHFR = 0;
1109
1110 // Reset state variable that deals with retrying and aborting aurofocus
1111 m_RestartState = RESTART_NONE;
1112
1113 // Keep the last focus temperature, it can still be useful in case the autofocus fails
1114 // lastFocusTemperature
1115
1116 if (canAbsMove)
1117 {
1118 absIterations = 0;
1119 getAbsFocusPosition();
1120 pulseDuration = m_OpsFocusMechanics->focusTicks->value();
1121 }
1122 else if (canRelMove)
1123 {
1124 //appendLogText(i18n("Setting dummy central position to 50000"));
1125 absIterations = 0;
1126 pulseDuration = m_OpsFocusMechanics->focusTicks->value();
1127 //currentPosition = 50000;
1128 absMotionMax = 100000;
1129 absMotionMin = 0;
1130 }
1131 else
1132 {
1133 pulseDuration = m_OpsFocusMechanics->focusTicks->value();
1134 absIterations = 0;
1135 absMotionMax = 100000;
1136 absMotionMin = 0;
1137 }
1138
1139 focuserAdditionalMovement = 0;
1140 starMeasureFrames.clear();
1141
1142 resetButtons();
1143
1144 reverseDir = false;
1145
1146 /*if (fw > 0 && fh > 0)
1147 starSelected= true;
1148 else
1149 starSelected= false;*/
1150
1151 clearDataPoints();
1152 profilePlot->clear();
1153 FWHMOut->setText("");
1154
1155 qCInfo(KSTARS_EKOS_FOCUS) << "Starting Autofocus " << m_AFRun
1156 << " on" << opticalTrain()
1157 << " Reason: " << AutofocusReasonStr[m_AutofocusReason]
1158 << " Reason Info: " << m_AutofocusReasonInfo
1159 << " CanAbsMove: " << (canAbsMove ? "yes" : "no" )
1160 << " CanRelMove: " << (canRelMove ? "yes" : "no" )
1161 << " CanTimerMove: " << (canTimerMove ? "yes" : "no" )
1162 << " Position:" << currentPosition
1163 << " Filter:" << filter()
1164 << " Exp:" << focusExposure->value()
1165 << " Bin:" << focusBinning->currentText()
1166 << " Gain:" << focusGain->value()
1167 << " ISO:" << focusISO->currentText();
1168 qCInfo(KSTARS_EKOS_FOCUS) << "Settings Tab."
1169 << " Auto Select Star:" << ( m_OpsFocusSettings->focusAutoStarEnabled->isChecked() ? "yes" : "no" )
1170 << " Dark Frame:" << ( m_OpsFocusSettings->useFocusDarkFrame->isChecked() ? "yes" : "no" )
1171 << " Sub Frame:" << ( m_OpsFocusSettings->focusSubFrame->isChecked() ? "yes" : "no" )
1172 << " Box:" << m_OpsFocusSettings->focusBoxSize->value()
1173 << " Full frame:" << ( m_OpsFocusSettings->focusUseFullField->isChecked() ? "yes" : "no " )
1174 << " Focus Mask: " << (m_OpsFocusSettings->focusNoMaskRB->isChecked() ? "Use all stars" :
1175 (m_OpsFocusSettings->focusRingMaskRB->isChecked() ? QString("Ring Mask: [%1%, %2%]").
1176 arg(m_OpsFocusSettings->focusFullFieldInnerRadius->value()).arg(m_OpsFocusSettings->focusFullFieldOuterRadius->value()) :
1177 QString("Mosaic Mask: [%1%, space=%2px]").
1178 arg(m_OpsFocusSettings->focusMosaicTileWidth->value()).arg(m_OpsFocusSettings->focusMosaicSpace->value())))
1179 << " Suspend Guiding:" << ( m_OpsFocusSettings->focusSuspendGuiding->isChecked() ? "yes" : "no " )
1180 << " Guide Settle:" << m_OpsFocusSettings->focusGuideSettleTime->value()
1181 << " Display Units:" << m_OpsFocusSettings->focusUnits->currentText()
1182 << " Adaptive Focus:" << ( m_OpsFocusSettings->focusAdaptive->isChecked() ? "yes" : "no" )
1183 << " Min Move:" << m_OpsFocusSettings->focusAdaptiveMinMove->value()
1184 << " Adapt Start:" << ( m_OpsFocusSettings->focusAdaptStart->isChecked() ? "yes" : "no" )
1185 << " Max Total Move:" << m_OpsFocusSettings->focusAdaptiveMaxMove->value();
1186 qCInfo(KSTARS_EKOS_FOCUS) << "Process Tab."
1187 << " Detection:" << m_OpsFocusProcess->focusDetection->currentText()
1188 << " SEP Profile:" << m_OpsFocusProcess->focusSEPProfile->currentText()
1189 << " Algorithm:" << m_OpsFocusProcess->focusAlgorithm->currentText()
1190 << " Curve Fit:" << m_OpsFocusProcess->focusCurveFit->currentText()
1191 << " Measure:" << m_OpsFocusProcess->focusStarMeasure->currentText()
1192 << " PSF:" << m_OpsFocusProcess->focusStarPSF->currentText()
1193 << " Use Weights:" << ( m_OpsFocusProcess->focusUseWeights->isChecked() ? "yes" : "no" )
1194 << " R2 Limit:" << m_OpsFocusProcess->focusR2Limit->value()
1195 << " Refine Curve Fit:" << ( m_OpsFocusProcess->focusRefineCurveFit->isChecked() ? "yes" : "no" )
1196 << " Average Over:" << m_OpsFocusProcess->focusFramesCount->value()
1197 << " Average HFR Check Over:" << m_OpsFocusProcess->focusHFRFramesCount->value()
1198 << " Num.of Rows:" << m_OpsFocusProcess->focusMultiRowAverage->value()
1199 << " Sigma:" << m_OpsFocusProcess->focusGaussianSigma->value()
1200 << " Threshold:" << m_OpsFocusProcess->focusThreshold->value()
1201 << " Kernel size:" << m_OpsFocusProcess->focusGaussianKernelSize->value()
1202 << " Tolerance:" << m_OpsFocusProcess->focusTolerance->value()
1203 << " Denoise:" << ( m_OpsFocusProcess->focusDenoise->isChecked() ? "yes" : "no" )
1204 << " Donut Buster:" << ( m_OpsFocusProcess->focusDonut->isChecked() ? "yes" : "no" )
1205 << " Donut Time Dilation:" << m_OpsFocusProcess->focusTimeDilation->value()
1206 << " Outlier Rejection:" << m_OpsFocusProcess->focusOutlierRejection->value()
1207 << " Scan Start Pos:" << ( m_OpsFocusProcess->focusScanStartPos->isChecked() ? "yes" : "no" )
1208 << " Scan Always On:" << ( m_OpsFocusProcess->focusScanAlwaysOn->isChecked() ? "yes" : "no" )
1209 << " Num Datapoints:" << m_OpsFocusProcess->focusScanDatapoints->value()
1210 << " Scan Step Factor:" << m_OpsFocusProcess->focusScanStepSizeFactor->value();
1211 qCInfo(KSTARS_EKOS_FOCUS) << "Mechanics Tab."
1212 << " Initial Step Size:" << m_OpsFocusMechanics->focusTicks->value()
1213 << " Out Step Multiple:" << m_OpsFocusMechanics->focusOutSteps->value()
1214 << " Number Steps:" << m_OpsFocusMechanics->focusNumSteps->value()
1215 << " Max Travel:" << m_OpsFocusMechanics->focusMaxTravel->value()
1216 << " Max Step Size:" << m_OpsFocusMechanics->focusMaxSingleStep->value()
1217 << " Driver Backlash:" << m_OpsFocusMechanics->focusBacklash->value()
1218 << " AF Overscan:" << m_OpsFocusMechanics->focusAFOverscan->value()
1219 << " Overscan Delay:" << m_OpsFocusMechanics->focusOverscanDelay->value()
1220 << " Focuser Settle:" << m_OpsFocusMechanics->focusSettleTime->value()
1221 << " Walk:" << m_OpsFocusMechanics->focusWalk->currentText()
1222 << " Capture Timeout:" << m_OpsFocusMechanics->focusCaptureTimeout->value()
1223 << " Motion Timeout:" << m_OpsFocusMechanics->focusMotionTimeout->value();
1224 qCInfo(KSTARS_EKOS_FOCUS) << "CFZ Tab."
1225 << " Algorithm:" << m_CFZUI->focusCFZAlgorithm->currentText()
1226 << " Tolerance:" << m_CFZUI->focusCFZTolerance->value()
1227 << " Tolerance (Ï„):" << m_CFZUI->focusCFZTau->value()
1228 << " Display:" << ( m_CFZUI->focusCFZDisplayVCurve->isChecked() ? "yes" : "no" )
1229 << " Wavelength (λ):" << m_CFZUI->focusCFZWavelength->value()
1230 << " Aperture (A):" << m_CFZUI->focusCFZAperture->value()
1231 << " Focal Ratio (f):" << m_CFZUI->focusCFZFNumber->value()
1232 << " Step Size:" << m_CFZUI->focusCFZStepSize->value()
1233 << " FWHM (θ):" << m_CFZUI->focusCFZSeeing->value();
1234
1235 const double temperature = (currentTemperatureSourceElement) ? currentTemperatureSourceElement->value : INVALID_VALUE;
1236 emit autofocusStarting(temperature, filter(), m_AutofocusReason, m_AutofocusReasonInfo);
1237
1238 appendLogText(i18n("Autofocus starting..."));
1239
1240 // Only suspend when we have Off-Axis Guider
1241 // If the guide camera is operating on a different OTA
1242 // then no need to suspend.
1243 if (m_GuidingSuspended == false && m_OpsFocusSettings->focusSuspendGuiding->isChecked())
1244 {
1245 m_GuidingSuspended = true;
1246 emit suspendGuiding();
1247 }
1248
1249 //emit statusUpdated(true);
1250 setState(Ekos::FOCUS_PROGRESS);
1251
1252 KSNotification::event(QLatin1String("FocusStarted"), i18n("Autofocus operation started"), KSNotification::Focus);
1253
1254 // Used for all the focuser types.
1255 if (m_FocusAlgorithm == FOCUS_LINEAR || m_FocusAlgorithm == FOCUS_LINEAR1PASS)
1256 {
1257 m_AFfilter = filter();
1258 int position = adaptFocus->adaptStartPosition(currentPosition, m_AFfilter);
1259
1260 curveFitting.reset(new CurveFitting());
1261
1262 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS)
1263 {
1264 // Curve fitting for stars and FWHM processing
1265 starFitting.reset(new CurveFitting());
1266 focusFWHM.reset(new FocusFWHM(m_ScaleCalc));
1267 focusFourierPower.reset(new FocusFourierPower(m_ScaleCalc));
1268#if defined(HAVE_OPENCV)
1269 focusBlurriness.reset(new FocusBlurriness());
1270#endif
1271 // Donut Buster
1272 initDonutProcessing();
1273 // Scan for Start Position
1274 if (initScanStartPos(false, currentPosition))
1275 return;
1276 // Focus Advisor
1277 if (m_AutofocusReason == FOCUS_FOCUS_ADVISOR)
1278 {
1279 focusAdvisor->initControl();
1280 return;
1281 }
1282 }
1283
1284 // Setup the focuser
1285 setupLinearFocuser(position);
1286 if (!changeFocus(linearRequestedPosition - currentPosition))
1287 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_NO_MOVE);
1288
1289 // Avoid the capture below.
1290 return;
1291 }
1292 capture();
1293}
1294
1295void Focus::setupLinearFocuser(int initialPosition)
1296{
1297 FocusAlgorithmInterface::FocusParams params(curveFitting.get(),
1298 m_OpsFocusMechanics->focusMaxTravel->value(), m_OpsFocusMechanics->focusTicks->value(), initialPosition, absMotionMin,
1299 absMotionMax, MAXIMUM_ABS_ITERATIONS, m_OpsFocusProcess->focusTolerance->value() / 100.0, m_AFfilter,
1300 currentTemperatureSourceElement ? currentTemperatureSourceElement->value : INVALID_VALUE,
1301 m_OpsFocusMechanics->focusOutSteps->value(), m_OpsFocusMechanics->focusNumSteps->value(),
1302 m_FocusAlgorithm, m_OpsFocusMechanics->focusBacklash->value(), m_CurveFit, m_OpsFocusProcess->focusUseWeights->isChecked(),
1303 m_StarMeasure, m_StarPSF, m_OpsFocusProcess->focusRefineCurveFit->isChecked(), m_FocusWalk,
1304 m_OpsFocusProcess->focusDonut->isChecked(), m_OpsFocusProcess->focusOutlierRejection->value(), m_OptDir, m_ScaleCalc);
1305
1306 if (canAbsMove)
1307 initialFocuserAbsPosition = initialPosition;
1308 linearFocuser.reset(MakeLinearFocuser(params));
1309 linearRequestedPosition = linearFocuser->initialPosition();
1310}
1311
1312// Initialise donut buster
1313void Focus::initDonutProcessing()
1314{
1315 if (!m_OpsFocusProcess->focusDonut->isChecked())
1316 return;
1317
1318 m_donutOrigExposure = focusExposure->value();
1319}
1320
1321// Reset donut buster
1322void Focus::resetDonutProcessing()
1323{
1324 // If donut busting variable focus exposures have been used, reset to starting value
1325 if (m_OpsFocusProcess->focusDonut->isChecked() && inAutoFocus)
1326 focusExposure->setValue(m_donutOrigExposure);
1327}
1328
1329int Focus::adjustLinearPosition(int position, int newPosition, int overscan, bool updateDir)
1330{
1331 if (overscan > 0 && newPosition > position)
1332 {
1333 // If user has set an overscan value then use it
1334 int adjustment = overscan;
1335 qCDebug(KSTARS_EKOS_FOCUS) << QString("Linear: extending outward movement by overscan %1").arg(adjustment);
1336
1337 if (newPosition + adjustment > absMotionMax)
1338 adjustment = static_cast<int>(absMotionMax) - newPosition;
1339
1340 focuserAdditionalMovement = adjustment;
1341 focuserAdditionalMovementUpdateDir = updateDir;
1342
1343 return newPosition + adjustment;
1344 }
1345 return newPosition;
1346}
1347
1348// This function checks whether capture is in progress or HFR processing is in progress. If so, it waits until complete before
1349// stopping processing and signalling to Capture and Scheduler. This prevents Capture / Scheduler from starting the next action
1350// before Focus is done.
1351void Focus::checkStopFocus(bool abort)
1352{
1353 m_abortInProgress = true;
1354 // if abort, avoid try to restart
1355 if (abort)
1356 resetFocusIteration = MAXIMUM_RESET_ITERATIONS + 1;
1357
1358 if (captureTimer.isActive())
1359 // Capture is waiting for the settling period before starting, so just kill the timer which will prevent capture starting
1360 captureTimer.stop();
1361
1362 if (m_captureInProgress)
1363 {
1364 if (inAutoFocus == false && inFocusLoop == false)
1365 {
1366 captureB->setEnabled(true);
1367 stopFocusB->setEnabled(false);
1368
1369 appendLogText(i18n("Capture aborted."));
1370 }
1371 else if (inAutoFocus)
1372 {
1373 stopFocusB->setEnabled(false);
1374 appendLogText(i18n("Capture in progress, retrying in 1s..."));
1375 QTimer::singleShot(1000, this, [ &, abort]()
1376 {
1377 checkStopFocus(abort);
1378 });
1379 return;
1380 }
1381 }
1382
1383 if (m_starDetectInProgress)
1384 {
1385 stopFocusB->setEnabled(false);
1386 appendLogText(i18n("Star detection in progress, retrying in 1s..."));
1387 QTimer::singleShot(1000, this, [ &, abort]()
1388 {
1389 checkStopFocus(abort);
1390 });
1391 return;
1392 }
1393
1394 completeFocusProcedure(abort ? Ekos::FOCUS_ABORTED : Ekos::FOCUS_FAILED, Ekos::FOCUS_FAIL_ABORT);
1395}
1396
1397void Focus::meridianFlipStarted()
1398{
1399 // if focusing is not running, do nothing
1400 if (state() == FOCUS_IDLE || state() == FOCUS_COMPLETE || state() == FOCUS_FAILED || state() == FOCUS_ABORTED)
1401 return;
1402
1403 // store current focus iteration counter since abort() sets it to the maximal value to avoid restarting
1404 int old = resetFocusIteration;
1405 // abort focusing
1406 abort();
1407 // restore iteration counter
1408 resetFocusIteration = old;
1409}
1410
1411void Focus::abort()
1412{
1413 // No need to "abort" if not already in progress.
1414 if (state() <= FOCUS_ABORTED)
1415 return;
1416
1417 // If Focus Advisor is running let it handle the abort
1418 if (focusAdvisor->inFocusAdvisor())
1419 {
1420 focusAdvisor->stop();
1421 return;
1422 }
1423 processAbort();
1424}
1425
1426void Focus::processAbort()
1427{
1428 bool focusLoop = inFocusLoop;
1429 checkStopFocus(true);
1430 appendLogText(i18n("Autofocus aborted."));
1431 if (!focusLoop)
1432 // For looping leave focuser where it is
1433 // Otherwise try to shift the focuser back to its initial position
1434 resetFocuser();
1435}
1436
1437void Focus::stop(Ekos::FocusState completionState)
1438{
1439 Q_UNUSED(completionState);
1440 qCDebug(KSTARS_EKOS_FOCUS) << "Stopping Focus";
1441
1442 captureTimeout.stop();
1443 m_FocusMotionTimer.stop();
1444 m_FocusMotionTimerCounter = 0;
1445 m_FocuserReconnectCounter = 0;
1446
1447 opticalTrainCombo->setEnabled(true);
1448 resetDonutProcessing();
1449 inAutoFocus = false;
1450 inAdjustFocus = false;
1451 adaptFocus->setInAdaptiveFocus(false);
1452 inBuildOffsets = false;
1453 inScanStartPos = false;
1454 focusAdvisor->reset();
1455 m_AutofocusReason = AutofocusReason::FOCUS_NONE;
1456 m_AutofocusReasonInfo = "";
1457 focuserAdditionalMovement = 0;
1458 focuserAdditionalMovementUpdateDir = true;
1459 inFocusLoop = false;
1460 m_captureInProgress = false;
1461 m_abortInProgress = false;
1462 m_starDetectInProgress = false;
1463 isVShapeSolution = false;
1464 captureFailureCounter = 0;
1465 minimumRequiredHFR = INVALID_STAR_MEASURE;
1466 noStarCount = 0;
1467 starMeasureFrames.clear();
1468 m_abInsOn = false;
1469 m_StartRetries = 0;
1470 m_AFRerun = false;
1471
1472 // Check if CCD was not removed due to crash or other reasons.
1473 if (m_Camera)
1474 {
1475 disconnect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Focus::processData);
1476 disconnect(m_Camera, &ISD::Camera::error, this, &Ekos::Focus::processCaptureError);
1477
1478 if (rememberUploadMode != m_Camera->getUploadMode())
1479 m_Camera->setUploadMode(rememberUploadMode);
1480
1481 // Remember to reset fast exposure if it was enabled before.
1482 if (m_RememberCameraFastExposure)
1483 {
1484 m_RememberCameraFastExposure = false;
1485 m_Camera->setFastExposureEnabled(true);
1486 }
1487
1488 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
1489 targetChip->abortExposure();
1490 }
1491
1492 resetButtons();
1493
1494 absIterations = 0;
1495 HFRInc = 0;
1496 reverseDir = false;
1497
1498 if (m_GuidingSuspended)
1499 {
1500 emit resumeGuiding();
1501 m_GuidingSuspended = false;
1502 }
1503}
1504
1505void Focus::capture(double settleTime)
1506{
1507 if (m_abortInProgress)
1508 {
1509 // We are trying to abort Autofocus so do no further processing
1510 m_captureInProgress = false;
1511 qCDebug(KSTARS_EKOS_FOCUS) << "Abort AF: discarding capture request.";
1512 return;
1513 }
1514
1515 // If capturing should be delayed by a given settling time, we start the capture timer.
1516 // This is intentionally designed re-entrant, i.e. multiple calls with settle time > 0 takes the last delay
1517 if (settleTime > 0 && m_captureInProgress == false)
1518 {
1519 captureTimer.start(static_cast<int>(settleTime * 1000));
1520 return;
1521 }
1522
1523 if (m_captureInProgress)
1524 {
1525 qCWarning(KSTARS_EKOS_FOCUS) << "Capture called while already in progress. Capture is ignored.";
1526 return;
1527 }
1528
1529 if (m_Camera == nullptr)
1530 {
1531 appendLogText(i18n("Error: No Camera detected."));
1532 checkStopFocus(true);
1533 return;
1534 }
1535
1536 if (m_Camera->isConnected() == false)
1537 {
1538 appendLogText(i18n("Error: Lost connection to Camera."));
1539 checkStopFocus(true);
1540 return;
1541 }
1542
1543 // reset timeout for receiving an image
1544 captureTimeout.stop();
1545 // reset timeout for focus star selection
1546 waitStarSelectTimer.stop();
1547
1548 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
1549
1550 if (m_Camera->isBLOBEnabled() == false)
1551 {
1552 m_Camera->setBLOBEnabled(true);
1553 }
1554
1555 if (focusFilter->currentIndex() != -1)
1556 {
1557 if (m_FilterWheel == nullptr)
1558 {
1559 appendLogText(i18n("Error: No Filter Wheel detected."));
1560 checkStopFocus(true);
1561 return;
1562 }
1563 if (m_FilterWheel->isConnected() == false)
1564 {
1565 appendLogText(i18n("Error: Lost connection to Filter Wheel."));
1566 checkStopFocus(true);
1567 return;
1568 }
1569
1570 // In "regular" autofocus mode if the chosen filter has an associated lock filter then we need
1571 // to swap to the lock filter before running autofocus. However, AF is also called from build
1572 // offsets to run AF. In this case we need to ignore the lock filter and use the chosen filter
1573 int targetPosition = focusFilter->currentIndex() + 1;
1574 if (!inBuildOffsets)
1575 {
1576 QString lockedFilter = m_FilterManager->getFilterLock(filter());
1577
1578 // We change filter if:
1579 // 1. Target position is not equal to current position.
1580 // 2. Locked filter of CURRENT filter is a different filter.
1581 if (lockedFilter != "--" && lockedFilter != filter())
1582 {
1583 int lockedFilterIndex = focusFilter->findText(lockedFilter);
1584 if (lockedFilterIndex >= 0)
1585 {
1586 // Go back to this filter once we are done
1587 fallbackFilterPending = true;
1588 fallbackFilterPosition = targetPosition;
1589 targetPosition = lockedFilterIndex + 1;
1590 }
1591 }
1592 }
1593
1594 filterPositionPending = (targetPosition != currentFilterPosition);
1595 if (filterPositionPending)
1596 {
1597 // Change the filter. When done this will signal to update the focusFilter combo
1598 // Apply filter change policy if in Autofocus; otherwise apply change and offsets
1599 // Note that Autofocus doesn't need the change policy as Adapt Start Pos takes care of this
1600 FilterManager::FilterPolicy policy = (inAutoFocus) ?
1601 static_cast<FilterManager::FilterPolicy>(FilterManager::CHANGE_POLICY) :
1602 static_cast<FilterManager::FilterPolicy>(FilterManager::CHANGE_POLICY | FilterManager::OFFSET_POLICY);
1603 m_FilterManager->setFilterPosition(targetPosition, policy);
1604 return;
1605 }
1606 else if (targetPosition != focusFilter->currentIndex() + 1)
1607 focusFilter->setCurrentIndex(targetPosition - 1);
1608 }
1609
1610 m_FocusView->setProperty("suspended", m_OpsFocusSettings->useFocusDarkFrame->isChecked());
1611 prepareCapture(targetChip);
1612
1613 connect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Focus::processData, Qt::UniqueConnection);
1614 connect(m_Camera, &ISD::Camera::error, this, &Ekos::Focus::processCaptureError, Qt::UniqueConnection);
1615
1616 if (frameSettings.contains(targetChip))
1617 {
1618 QVariantMap settings = frameSettings[targetChip];
1619 targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(),
1620 settings["h"].toInt());
1621 settings["binx"] = (focusBinning->currentIndex() + 1);
1622 settings["biny"] = (focusBinning->currentIndex() + 1);
1623 frameSettings[targetChip] = settings;
1624 }
1625
1626 m_captureInProgress = true;
1627 if (state() != FOCUS_PROGRESS)
1628 setState(FOCUS_PROGRESS);
1629
1630 m_FocusView->setBaseSize(focusingWidget->size());
1631
1632 if (targetChip->capture(focusExposure->value()))
1633 {
1634 // Timeout is exposure duration + timeout threshold in seconds
1635 //long const timeout = lround(ceil(focusExposure->value() * 1000)) + FOCUS_TIMEOUT_THRESHOLD;
1636 captureTimeout.start( (focusExposure->value() + m_OpsFocusMechanics->focusCaptureTimeout->value()) * 1000);
1637
1638 if (inFocusLoop == false)
1639 appendLogText(i18n("Capturing image..."));
1640
1641 resetButtons();
1642 }
1643 else if (inAutoFocus)
1644 {
1645 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_CAPTURE_FAILED);
1646 }
1647}
1648
1649void Focus::prepareCapture(ISD::CameraChip *targetChip)
1650{
1651 if (m_Camera->getUploadMode() == ISD::Camera::UPLOAD_REMOTE)
1652 {
1653 rememberUploadMode = ISD::Camera::UPLOAD_REMOTE;
1654 m_Camera->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
1655 }
1656
1657 // We cannot use fast exposure in focus.
1658 if (m_Camera->isFastExposureEnabled())
1659 {
1660 m_RememberCameraFastExposure = true;
1661 m_Camera->setFastExposureEnabled(false);
1662 }
1663
1664 m_Camera->setEncodingFormat("FITS");
1665 targetChip->setBatchMode(false);
1666 targetChip->setBinning((focusBinning->currentIndex() + 1), (focusBinning->currentIndex() + 1));
1667 targetChip->setCaptureMode(FITS_FOCUS);
1668 targetChip->setFrameType(FRAME_LIGHT);
1669 targetChip->setCaptureFilter(FITS_NONE);
1670
1671 if (isFocusISOEnabled() && focusISO->currentIndex() != -1 &&
1672 targetChip->getISOIndex() != focusISO->currentIndex())
1673 targetChip->setISOIndex(focusISO->currentIndex());
1674
1675 if (isFocusGainEnabled() && focusGain->value() != GainSpinSpecialValue)
1676 m_Camera->setGain(focusGain->value());
1677}
1678
1679bool Focus::focusIn(int ms)
1680{
1681 if (currentPosition == absMotionMin)
1682 {
1683 appendLogText(i18n("At minimum focus position %1...", absMotionMin));
1684 return false;
1685 }
1686 focusInB->setEnabled(false);
1687 focusOutB->setEnabled(false);
1688 startGotoB->setEnabled(false);
1689 if (ms <= 0)
1690 ms = m_OpsFocusMechanics->focusTicks->value();
1691 if (currentPosition - ms <= absMotionMin)
1692 {
1693 ms = currentPosition - absMotionMin;
1694 appendLogText(i18n("Moving to minimum focus position %1...", absMotionMin));
1695 }
1696 return changeFocus(-ms);
1697}
1698
1699bool Focus::focusOut(int ms)
1700{
1701 if (currentPosition == absMotionMax)
1702 {
1703 appendLogText(i18n("At maximum focus position %1...", absMotionMax));
1704 return false;
1705 }
1706 focusInB->setEnabled(false);
1707 focusOutB->setEnabled(false);
1708 startGotoB->setEnabled(false);
1709 if (ms <= 0)
1710 ms = m_OpsFocusMechanics->focusTicks->value();
1711 if (currentPosition + ms >= absMotionMax)
1712 {
1713 ms = absMotionMax - currentPosition;
1714 appendLogText(i18n("Moving to maximum focus position %1...", absMotionMax));
1715 }
1716 return changeFocus(ms);
1717}
1718
1719// Routine to manage focus movements. All moves are now subject to overscan
1720// + amount indicates a movement out; - amount indictaes a movement in
1721bool Focus::changeFocus(int amount, bool updateDir)
1722{
1723 // Retry capture if we stay at the same position
1724 // Allow 1 step of tolerance--Have seen stalls with amount==1.
1725 if (inAutoFocus && abs(amount) <= 1)
1726 {
1727 if (m_RestartState == RESTART_NOW)
1728 // Restart in progress which is normally triggered by the completion of the focuser
1729 // reset to its starting position but in this case the focuser is already at its starting
1730 // position... so create a small movement. As above comment amount=1 can cause a stall, so use 2.
1731 amount = -2;
1732 else
1733 {
1734 capture(m_OpsFocusMechanics->focusSettleTime->value());
1735 return true;
1736 }
1737 }
1738
1739 if (m_Focuser == nullptr)
1740 {
1741 appendLogText(i18n("Error: No Focuser detected."));
1742 checkStopFocus(true);
1743 return false;
1744 }
1745
1746 if (m_Focuser->isConnected() == false)
1747 {
1748 appendLogText(i18n("Error: Lost connection to Focuser."));
1749 checkStopFocus(true);
1750 return false;
1751 }
1752
1753 const int newPosition = adjustLinearPosition(currentPosition, currentPosition + amount,
1754 m_OpsFocusMechanics->focusAFOverscan->value(),
1755 updateDir);
1756 if (newPosition == currentPosition)
1757 return true;
1758
1759 const int newAmount = newPosition - currentPosition;
1760 const int absNewAmount = abs(newAmount);
1761 const bool focusingOut = newAmount > 0;
1762 const QString dirStr = focusingOut ? i18n("outward") : i18n("inward");
1763 // update the m_LastFocusDirection unless in Iterative / Polynomial which controls this variable itself.
1764 if (updateDir)
1765 m_LastFocusDirection = (focusingOut) ? FOCUS_OUT : FOCUS_IN;
1766
1767 if (focusingOut)
1768 m_Focuser->focusOut();
1769 else
1770 m_Focuser->focusIn();
1771
1772 // Keep track of motion in case it gets stuck.
1773 m_FocusMotionTimer.start();
1774
1775 if (canAbsMove)
1776 {
1777 m_LastFocusSteps = newPosition;
1778 m_Focuser->moveAbs(newPosition);
1779 appendLogText(i18n("Focusing %2 by %1 steps...", abs(absNewAmount), dirStr));
1780 }
1781 else if (canRelMove)
1782 {
1783 m_LastFocusSteps = absNewAmount;
1784 m_Focuser->moveRel(absNewAmount);
1785 appendLogText(i18np("Focusing %2 by %1 step...", "Focusing %2 by %1 steps...", absNewAmount, dirStr));
1786 }
1787 else
1788 {
1789 m_LastFocusSteps = absNewAmount;
1790 m_Focuser->moveByTimer(absNewAmount);
1791 appendLogText(i18n("Focusing %2 by %1 ms...", absNewAmount, dirStr));
1792 }
1793
1794 return true;
1795}
1796
1797void Focus::handleFocusMotionTimeout()
1798{
1799 // handleFocusMotionTimeout is called when the focus motion timer times out. This is only
1800 // relevant to AutoFocus runs which could be unattended so make an attempt to recover. Other
1801 // types of focuser movement issues are logged and the user is expected to take action.
1802 // If set correctly, say 30 secs, this should only occur when there are comms issues
1803 // with the focuser.
1804 // Step 1: Just retry the last requested move. If the issue is a transient one-off issue
1805 // this should resolve it. Try this twice.
1806 // Step 2: Step 1 didn't resolve the issue so try to restart the focus driver. In this case
1807 // abort the inflight autofocus run and let it retry from the start. It will try to
1808 // return the focuser to the start (last successful autofocus) position. Try twice.
1809 // Step 3: Step 2 didn't work either because the driver restart wasn't successful or we are
1810 // still getting timeouts. In this case just stop the autoFocus process and return
1811 // control to either the Scheduer or GUI. Note that here we cannot reset the focuser
1812 // to the previous good position so if the focuser miraculously fixes itself the
1813 // next autofocus run won't start from the best place.
1814
1815 if (!inAutoFocus)
1816 {
1817 qCDebug(KSTARS_EKOS_FOCUS) << "handleFocusMotionTimeout() called while not in AutoFocus";
1818 return;
1819 }
1820
1821 m_FocusMotionTimerCounter++;
1822
1823 if (m_FocusMotionTimerCounter > 4)
1824 {
1825 // We've tried everything but still get timeouts so abort...
1826 appendLogText(i18n("Focuser is still timing out. Aborting..."));
1827 stop(Ekos::FOCUS_ABORTED);
1828 return;
1829 }
1830 else if (m_FocusMotionTimerCounter > 2)
1831 {
1832 QString focuser = m_Focuser->getDeviceName();
1833 appendLogText(i18n("Focus motion timed out (%1). Restarting focus driver %2", m_FocusMotionTimerCounter, focuser));
1834 emit focuserTimedout(focuser);
1835
1836 QTimer::singleShot(5000, this, [ &, focuser]()
1837 {
1838 Focus::reconnectFocuser(focuser);
1839 });
1840 return;
1841 }
1842
1843 if (!changeFocus(m_LastFocusSteps - currentPosition))
1844 appendLogText(i18n("Focus motion timed out (%1). Focusing to %2 steps...", m_FocusMotionTimerCounter, m_LastFocusSteps));
1845}
1846
1847void Focus::selectImageMask()
1848{
1849 const bool useFullField = m_OpsFocusSettings->focusUseFullField->isChecked();
1850 ImageMaskType masktype;
1851 if (m_OpsFocusSettings->focusRingMaskRB->isChecked())
1852 masktype = FOCUS_MASK_RING;
1853 else if (m_OpsFocusSettings->focusMosaicMaskRB->isChecked())
1854 masktype = FOCUS_MASK_MOSAIC;
1855 else
1856 masktype = FOCUS_MASK_NONE;
1857
1858 // mask selection only enabled if full field should be used for focusing
1859 m_OpsFocusSettings->focusRingMaskRB->setEnabled(useFullField);
1860 m_OpsFocusSettings->focusMosaicMaskRB->setEnabled(useFullField);
1861 // ring mask
1862 m_OpsFocusSettings->focusFullFieldInnerRadius->setEnabled(useFullField && masktype == FOCUS_MASK_RING);
1863 m_OpsFocusSettings->focusFullFieldOuterRadius->setEnabled(useFullField && masktype == FOCUS_MASK_RING);
1864 // aberration inspector mosaic
1865 m_OpsFocusSettings->focusMosaicTileWidth->setEnabled(useFullField && masktype == FOCUS_MASK_MOSAIC);
1866 m_OpsFocusSettings->focusSpacerLabel->setEnabled(useFullField && masktype == FOCUS_MASK_MOSAIC);
1867 m_OpsFocusSettings->focusMosaicSpace->setEnabled(useFullField && masktype == FOCUS_MASK_MOSAIC);
1868
1869 // create the type specific mask
1870 if (masktype == FOCUS_MASK_RING)
1871 m_FocusView->setImageMask(new ImageRingMask(m_OpsFocusSettings->focusFullFieldInnerRadius->value() / 100.0,
1872 m_OpsFocusSettings->focusFullFieldOuterRadius->value() / 100.0));
1873 else if (masktype == FOCUS_MASK_MOSAIC)
1874 m_FocusView->setImageMask(new ImageMosaicMask(m_OpsFocusSettings->focusMosaicTileWidth->value(),
1875 m_OpsFocusSettings->focusMosaicSpace->value()));
1876 else
1877 m_FocusView->setImageMask(nullptr);
1878
1879 checkMosaicMaskLimits();
1880 m_currentImageMask = masktype;
1881 startAbInsB->setEnabled(canAbInsStart());
1882}
1883
1884void Focus::reconnectFocuser(const QString &focuser)
1885{
1886 m_FocuserReconnectCounter++;
1887
1888 if (m_Focuser && m_Focuser->getDeviceName() == focuser)
1889 {
1890 appendLogText(i18n("Attempting to reconnect focuser: %1", focuser));
1891 refreshOpticalTrain();
1892 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_ERROR);
1893 return;
1894 }
1895
1896 if (m_FocuserReconnectCounter > 12)
1897 {
1898 // We've waited a minute and can't reconnect the focuser so abort...
1899 appendLogText(i18n("Cannot reconnect focuser: %1. Aborting...", focuser));
1900 stop(Ekos::FOCUS_ABORTED);
1901 return;
1902 }
1903
1904 QTimer::singleShot(5000, this, [ &, focuser]()
1905 {
1906 reconnectFocuser(focuser);
1907 });
1908}
1909
1910void Focus::processData(const QSharedPointer<FITSData> &data)
1911{
1912 // Ignore guide head if there is any.
1913 if (data->property("chip").toInt() == ISD::CameraChip::GUIDE_CCD)
1914 return;
1915
1916 if (data)
1917 {
1918 m_FocusView->loadData(data);
1919 m_ImageData = data;
1920 }
1921 else
1922 m_ImageData.reset();
1923
1924 captureTimeout.stop();
1925 captureTimeoutCounter = 0;
1926 m_MissingCameraCounter = 0;
1927
1928 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
1929 disconnect(m_Camera, &ISD::Camera::newImage, this, &Ekos::Focus::processData);
1930 disconnect(m_Camera, &ISD::Camera::error, this, &Ekos::Focus::processCaptureError);
1931
1932 if (m_ImageData && m_OpsFocusSettings->useFocusDarkFrame->isChecked())
1933 {
1934 QVariantMap settings = frameSettings[targetChip];
1935 uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt();
1936 uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt();
1937
1938 m_DarkProcessor->denoise(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()),
1939 targetChip, m_ImageData, focusExposure->value(), offsetX, offsetY);
1940 return;
1941 }
1942
1943 setCaptureComplete();
1944 resetButtons();
1945}
1946
1947void Focus::starDetectionFinished()
1948{
1949 m_starDetectInProgress = false;
1950 if (m_abortInProgress)
1951 {
1952 // We are trying to abort Autofocus so do no further processing
1953 qCDebug(KSTARS_EKOS_FOCUS) << "Abort AF: discarding results from " << __FUNCTION__;
1954 return;
1955 }
1956
1957 appendLogText(i18n("Detection complete."));
1958
1959 // Beware as this HFR value is then treated specifically by the graph renderer
1960 double hfr = INVALID_STAR_MEASURE;
1961
1962 if (isStarMeasureStarBased())
1963 {
1964 appendLogText(i18n("Detection complete."));
1965
1966 if (m_StarFinderWatcher.result() == false)
1967 {
1968 qCWarning(KSTARS_EKOS_FOCUS) << "Failed to extract any stars.";
1969 }
1970 else
1971 {
1972 if (m_OpsFocusSettings->focusUseFullField->isChecked())
1973 {
1974 m_FocusView->filterStars();
1975
1976 // Get the average HFR of the whole frame
1977 hfr = m_ImageData->getHFR(m_StarMeasure == FOCUS_STAR_HFR_ADJ ? HFR_ADJ_AVERAGE : HFR_AVERAGE);
1978 }
1979 else
1980 {
1981 m_FocusView->setTrackingBoxEnabled(true);
1982
1983 // JM 2020-10-08: Try to get first the same HFR star already selected before
1984 // so that it doesn't keep jumping around
1985
1986 if (starCenter.isNull() == false)
1987 hfr = m_ImageData->getHFR(starCenter.x(), starCenter.y());
1988
1989 // If not found, then get the MAX or MEDIAN depending on the selected algorithm.
1990 if (hfr < 0)
1991 hfr = m_ImageData->getHFR(m_FocusDetection == ALGORITHM_SEP ? HFR_HIGH : HFR_MAX);
1992 }
1993 }
1994 }
1995
1996 currentHFR = hfr;
1997 currentNumStars = m_ImageData->getDetectedStars();
1998
1999 // Setup with measure we are using (HFR, FWHM, etc)
2000 if (m_StarMeasure == FOCUS_STAR_NUM_STARS)
2001 {
2002 currentMeasure = currentNumStars;
2003 currentWeight = 1.0;
2004 }
2005 else if (m_StarMeasure == FOCUS_STAR_FWHM)
2006 {
2007 getFWHM(m_ImageData->getStarCenters(), &currentFWHM, &currentWeight);
2008 currentMeasure = currentFWHM;
2009 }
2010 else if (m_StarMeasure == FOCUS_STAR_FOURIER_POWER)
2011 {
2012 getFourierPower(&currentFourierPower, &currentWeight);
2013 currentMeasure = currentFourierPower;
2014 }
2015 else if (m_StarMeasure == FOCUS_STAR_STDDEV || m_StarMeasure == FOCUS_STAR_SOBEL ||
2016 m_StarMeasure == FOCUS_STAR_LAPLASSIAN || m_StarMeasure == FOCUS_STAR_CANNY)
2017 {
2018 QRect roi = QRect();
2019 if (m_OpsFocusSettings->focusSubFrame->isChecked())
2020 roi = m_FocusView->isTrackingBoxEnabled() ? m_FocusView->getTrackingBox() : QRect();
2021 getBlurriness(m_StarMeasure, m_OpsFocusProcess->focusDenoise->isChecked(), &currentBlurriness, &currentWeight, roi);
2022 currentMeasure = currentBlurriness;
2023 }
2024 else
2025 {
2026 currentMeasure = currentHFR;
2027 QList<Edge*> stars = m_ImageData->getStarCenters();
2028 std::vector<double> hfrs(stars.size());
2029 std::transform(stars.constBegin(), stars.constEnd(), hfrs.begin(), [](Edge * edge)
2030 {
2031 return edge->HFR;
2032 });
2033 currentWeight = calculateStarWeight(m_OpsFocusProcess->focusUseWeights->isChecked(), hfrs);
2034 }
2035 setCurrentMeasure();
2036}
2037
2038// Returns whether the star measure is star based.
2039// FOCUS_STAR_SOBEL, FOCUS_STAR_LAPLASSIAN & FOCUS_STAR_CANNY work on non-star fields such as lunar fields where there may be no stars
2040// FOCUS_STAR_STDDEV can be used on star fields and non-star fields and does not require star detection.
2041bool Focus::isStarMeasureStarBased()
2042{
2043 if (m_StarMeasure == FOCUS_STAR_STDDEV || m_StarMeasure == FOCUS_STAR_SOBEL || m_StarMeasure == FOCUS_STAR_LAPLASSIAN
2044 || m_StarMeasure == FOCUS_STAR_CANNY)
2045 return false;
2046 else
2047 return true;
2048}
2049// The image has been processed for star centroids and HFRs so now process it for star FWHMs
2050void Focus::getFWHM(const QList<Edge *> &stars, double *FWHM, double *weight)
2051{
2052 *FWHM = INVALID_STAR_MEASURE;
2053 *weight = 0.0;
2054
2055 auto imageBuffer = m_ImageData->getImageBuffer();
2056
2057 switch (m_ImageData->getStatistics().dataType)
2058 {
2059 case TBYTE:
2060 focusFWHM->processFWHM(reinterpret_cast<uint8_t const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight);
2061 break;
2062
2063 case TSHORT: // Don't think short is used as its recorded as unsigned short
2064 focusFWHM->processFWHM(reinterpret_cast<short const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight);
2065 break;
2066
2067 case TUSHORT:
2068 focusFWHM->processFWHM(reinterpret_cast<unsigned short const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM,
2069 weight);
2070 break;
2071
2072 case TLONG: // Don't think long is used as its recorded as unsigned long
2073 focusFWHM->processFWHM(reinterpret_cast<long const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight);
2074 break;
2075
2076 case TULONG:
2077 focusFWHM->processFWHM(reinterpret_cast<unsigned long const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight);
2078 break;
2079
2080 case TFLOAT:
2081 focusFWHM->processFWHM(reinterpret_cast<float const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight);
2082 break;
2083
2084 case TLONGLONG:
2085 focusFWHM->processFWHM(reinterpret_cast<long long const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight);
2086 break;
2087
2088 case TDOUBLE:
2089 focusFWHM->processFWHM(reinterpret_cast<double const *>(imageBuffer), stars, m_ImageData, starFitting, FWHM, weight);
2090 break;
2091
2092 default:
2093 qCDebug(KSTARS_EKOS_FOCUS) << "Unknown image buffer datatype " << m_ImageData->getStatistics().dataType <<
2094 " Cannot calc FWHM";
2095 break;
2096 }
2097}
2098
2099// The image has been processed for star centroids and HFRs so now process it for star FWHMs
2100void Focus::getFourierPower(double *fourierPower, double *weight, const int mosaicTile)
2101{
2102 *fourierPower = INVALID_STAR_MEASURE;
2103 *weight = 1.0;
2104
2105 auto imageBuffer = m_ImageData->getImageBuffer();
2106
2107 switch (m_ImageData->getStatistics().dataType)
2108 {
2109 case TBYTE:
2110 focusFourierPower->processFourierPower(reinterpret_cast<uint8_t const *>(imageBuffer), m_ImageData,
2111 m_FocusView->imageMask(), mosaicTile, fourierPower, weight);
2112 break;
2113
2114 case TSHORT: // Don't think short is used as its recorded as unsigned short
2115 focusFourierPower->processFourierPower(reinterpret_cast<short const *>(imageBuffer), m_ImageData,
2116 m_FocusView->imageMask(), mosaicTile, fourierPower, weight);
2117 break;
2118
2119 case TUSHORT:
2120 focusFourierPower->processFourierPower(reinterpret_cast<unsigned short const *>(imageBuffer), m_ImageData,
2121 m_FocusView->imageMask(), mosaicTile, fourierPower, weight);
2122 break;
2123
2124 case TLONG: // Don't think long is used as its recorded as unsigned long
2125 focusFourierPower->processFourierPower(reinterpret_cast<long const *>(imageBuffer), m_ImageData,
2126 m_FocusView->imageMask(), mosaicTile, fourierPower, weight);
2127 break;
2128
2129 case TULONG:
2130 focusFourierPower->processFourierPower(reinterpret_cast<unsigned long const *>(imageBuffer), m_ImageData,
2131 m_FocusView->imageMask(), mosaicTile, fourierPower, weight);
2132 break;
2133
2134 case TFLOAT:
2135 focusFourierPower->processFourierPower(reinterpret_cast<float const *>(imageBuffer), m_ImageData,
2136 m_FocusView->imageMask(), mosaicTile, fourierPower, weight);
2137 break;
2138
2139 case TLONGLONG:
2140 focusFourierPower->processFourierPower(reinterpret_cast<long long const *>(imageBuffer), m_ImageData,
2141 m_FocusView->imageMask(), mosaicTile, fourierPower, weight);
2142 break;
2143
2144 case TDOUBLE:
2145 focusFourierPower->processFourierPower(reinterpret_cast<double const *>(imageBuffer), m_ImageData,
2146 m_FocusView->imageMask(), mosaicTile, fourierPower, weight);
2147 break;
2148
2149 default:
2150 qCDebug(KSTARS_EKOS_FOCUS) << "Unknown image buffer datatype " << m_ImageData->getStatistics().dataType <<
2151 " Cannot calc Fourier Power";
2152 break;
2153 }
2154}
2155
2156// The image has been processed for star centroids and HFRs so now process it for the Laplassian factor
2157void Focus::getBlurriness(const StarMeasure starMeasure, const bool denoise, double *blurriness, double *weight,
2158 const QRect &roi, const int mosaicTile)
2159{
2160 *blurriness = INVALID_STAR_MEASURE;
2161 *weight = 1.0;
2162
2163#if defined(HAVE_OPENCV)
2164
2165 auto imageBuffer = m_ImageData->getImageBuffer();
2166
2167 switch (m_ImageData->getStatistics().dataType)
2168 {
2169 case TBYTE:
2170 focusBlurriness->processBlurriness(reinterpret_cast<uint8_t const *>(imageBuffer), m_ImageData, denoise,
2171 m_FocusView->imageMask(), mosaicTile, roi, starMeasure, blurriness, weight);
2172 break;
2173
2174 case TSHORT: // Don't think short is used as its recorded as unsigned short
2175 focusBlurriness->processBlurriness(reinterpret_cast<short const *>(imageBuffer), m_ImageData, denoise,
2176 m_FocusView->imageMask(), mosaicTile, roi, starMeasure, blurriness, weight);
2177 break;
2178
2179 case TUSHORT:
2180 focusBlurriness->processBlurriness(reinterpret_cast<unsigned short const *>(imageBuffer), m_ImageData, denoise,
2181 m_FocusView->imageMask(), mosaicTile, roi, starMeasure, blurriness, weight);
2182 break;
2183
2184 case TLONG: // Don't think long is used as its recorded as unsigned long
2185 focusBlurriness->processBlurriness(reinterpret_cast<long const *>(imageBuffer), m_ImageData, denoise,
2186 m_FocusView->imageMask(), mosaicTile, roi, starMeasure, blurriness, weight);
2187 break;
2188
2189 case TULONG:
2190 focusBlurriness->processBlurriness(reinterpret_cast<unsigned long const *>(imageBuffer), m_ImageData, denoise,
2191 m_FocusView->imageMask(), mosaicTile, roi, starMeasure, blurriness, weight);
2192 break;
2193
2194 case TFLOAT:
2195 focusBlurriness->processBlurriness(reinterpret_cast<float const *>(imageBuffer), m_ImageData, denoise,
2196 m_FocusView->imageMask(), mosaicTile, roi, starMeasure, blurriness, weight);
2197 break;
2198
2199 case TLONGLONG:
2200 focusBlurriness->processBlurriness(reinterpret_cast<long long const *>(imageBuffer), m_ImageData, denoise,
2201 m_FocusView->imageMask(), mosaicTile, roi, starMeasure, blurriness, weight);
2202 break;
2203
2204 case TDOUBLE:
2205 focusBlurriness->processBlurriness(reinterpret_cast<double const *>(imageBuffer), m_ImageData, denoise,
2206 m_FocusView->imageMask(), mosaicTile, roi, starMeasure, blurriness, weight);
2207 break;
2208
2209 default:
2210 qCDebug(KSTARS_EKOS_FOCUS) << "Unknown image buffer datatype " << m_ImageData->getStatistics().dataType <<
2211 " Cannot calc Blurriness";
2212 break;
2213 }
2214#else
2215 Q_UNUSED(starMeasure);
2216 Q_UNUSED(denoise);
2217 Q_UNUSED(roi);
2218 Q_UNUSED(mosaicTile);
2219 // This shouldn't occur in normal operation
2220 qCDebug(KSTARS_EKOS_FOCUS) << "getBlurriness called when openCV not installed";
2221#endif
2222}
2223
2224double Focus::calculateStarWeight(const bool useWeights, const std::vector<double> values)
2225{
2226 if (!useWeights || values.size() <= 0)
2227 // If we can't calculate weight set to 1 = equal weights
2228 // Also if we are using numStars as the measure - don't use weights
2229 return 1.0f;
2230
2231 return Mathematics::RobustStatistics::ComputeWeight(m_ScaleCalc, values);
2232}
2233
2234void Focus::analyzeSources()
2235{
2236 appendLogText(i18n("Detecting sources..."));
2237 m_starDetectInProgress = true;
2238
2239 QVariantMap extractionSettings;
2240 extractionSettings["optionsProfileIndex"] = m_OpsFocusProcess->focusSEPProfile->currentIndex();
2241 extractionSettings["optionsProfileGroup"] = static_cast<int>(Ekos::FocusProfiles);
2242 m_ImageData->setSourceExtractorSettings(extractionSettings);
2243 // When we're using FULL field view, we always use either CENTROID algorithm which is the default
2244 // standard algorithm in KStars, or SEP. The other algorithms are too inefficient to run on full frames and require
2245 // a bounding box for them to be effective in near real-time application.
2246 if (m_OpsFocusSettings->focusUseFullField->isChecked())
2247 {
2248 m_FocusView->setTrackingBoxEnabled(false);
2249
2250 if (m_FocusDetection != ALGORITHM_CENTROID && m_FocusDetection != ALGORITHM_SEP)
2251 m_StarFinderWatcher.setFuture(m_ImageData->findStars(ALGORITHM_CENTROID));
2252 else
2253 m_StarFinderWatcher.setFuture(m_ImageData->findStars(m_FocusDetection));
2254 }
2255 else
2256 {
2257 QRect searchBox = m_FocusView->isTrackingBoxEnabled() ? m_FocusView->getTrackingBox() : QRect();
2258 // If star is already selected then use whatever algorithm currently selected.
2259 if (starSelected)
2260 {
2261 m_StarFinderWatcher.setFuture(m_ImageData->findStars(m_FocusDetection, searchBox));
2262 }
2263 else
2264 {
2265 // Disable tracking box
2266 m_FocusView->setTrackingBoxEnabled(false);
2267
2268 // If algorithm is set something other than Centeroid or SEP, then force Centroid
2269 // Since it is the most reliable detector when nothing was selected before.
2270 if (m_FocusDetection != ALGORITHM_CENTROID && m_FocusDetection != ALGORITHM_SEP)
2271 m_StarFinderWatcher.setFuture(m_ImageData->findStars(ALGORITHM_CENTROID));
2272 else
2273 // Otherwise, continue to find use using the selected algorithm
2274 m_StarFinderWatcher.setFuture(m_ImageData->findStars(m_FocusDetection, searchBox));
2275 }
2276 }
2277}
2278
2279bool Focus::appendMeasure(double newMeasure)
2280{
2281 // Add new star measure (e.g. HFR, FWHM, etc) to existing values, even if invalid
2282 starMeasureFrames.append(newMeasure);
2283
2284 // Prepare a work vector with valid HFR values
2285 QVector <double> samples(starMeasureFrames);
2286 samples.erase(std::remove_if(samples.begin(), samples.end(), [](const double measure)
2287 {
2288 return measure == INVALID_STAR_MEASURE;
2289 }), samples.end());
2290
2291 // Consolidate the average star measure. Sigma clips outliers and averages remainder.
2292 if (!samples.isEmpty())
2293 {
2294 currentMeasure = Mathematics::RobustStatistics::ComputeLocation(
2295 Mathematics::RobustStatistics::LOCATION_SIGMACLIPPING, std::vector<double>(samples.begin(), samples.end()));
2296
2297 switch(m_StarMeasure)
2298 {
2299 case FOCUS_STAR_HFR:
2300 case FOCUS_STAR_HFR_ADJ:
2301 currentHFR = currentMeasure;
2302 break;
2303 case FOCUS_STAR_FWHM:
2304 currentFWHM = currentMeasure;
2305 break;
2306 case FOCUS_STAR_NUM_STARS:
2307 currentNumStars = currentMeasure;
2308 break;
2309 case FOCUS_STAR_FOURIER_POWER:
2310 currentFourierPower = currentMeasure;
2311 break;
2312 case FOCUS_STAR_STDDEV:
2313 case FOCUS_STAR_SOBEL:
2314 case FOCUS_STAR_LAPLASSIAN:
2315 case FOCUS_STAR_CANNY:
2316 currentBlurriness = currentMeasure;
2317 break;
2318 default:
2319 break;
2320 }
2321 }
2322
2323 // Save the focus frame
2324 saveFocusFrame();
2325
2326 // Return whether we need more frame based on user requirement
2327 int framesCount = m_OpsFocusProcess->focusFramesCount->value();
2328 if ((minimumRequiredHFR > 0) || (inAutoFocus && !inBuildOffsets && !inScanStartPos && !focusAdvisor->inFocusAdvisor()
2329 && m_FocusAlgorithm == FOCUS_LINEAR1PASS
2330 && linearFocuser && !linearFocuser->isInFirstPass()))
2331 // If in-sequence HFR Check or L1P autofocus and doing the last (in focus) datapoint use focusHFRFramesCount
2332 framesCount = m_OpsFocusProcess->focusHFRFramesCount->value();
2333
2334 return starMeasureFrames.count() < framesCount;
2335}
2336
2337void Focus::settle(const FocusState completionState, const bool autoFocusUsed, const bool buildOffsetsUsed,
2338 const AutofocusFailReason failCode, const QString failCodeInfo)
2339{
2340 if (autoFocusUsed)
2341 {
2342 if (completionState == Ekos::FOCUS_COMPLETE)
2343 {
2344 if (failCode != FOCUS_FAIL_ADVISOR_COMPLETE)
2345 KSNotification::event(QLatin1String("FocusSuccessful"), i18n("Autofocus operation completed successfully"),
2346 KSNotification::Focus);
2347 // Pass consistent Autofocus temperature to analyze
2348 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS && curveFitting != nullptr)
2349 emit autofocusComplete(m_LastSourceAutofocusTemperature, filter(), getAnalyzeData(),
2350 m_OpsFocusProcess->focusUseWeights->isChecked(),
2351 curveFitting->serialize(), linearFocuser->getTextStatus(R2));
2352 else
2353 emit autofocusComplete(m_LastSourceAutofocusTemperature, filter(), getAnalyzeData(),
2354 m_OpsFocusProcess->focusUseWeights->isChecked());
2355 }
2356 else
2357 {
2358 if (failCode != FOCUS_FAIL_ADVISOR_COMPLETE)
2359 KSNotification::event(QLatin1String("FocusFailed"), i18n("Autofocus operation failed"),
2360 KSNotification::Focus, KSNotification::Alert);
2361 emit autofocusAborted(filter(), getAnalyzeData(), m_OpsFocusProcess->focusUseWeights->isChecked(), failCode, failCodeInfo);
2362 }
2363 }
2364
2365 if (completionState == Ekos::FOCUS_COMPLETE)
2366 {
2367 if (autoFocusUsed && fallbackFilterPending)
2368 {
2369 // Save the solution details for the filter used for the AF run before changing
2370 // filers to the fallback filter. Details for the fallback filter will be saved once that
2371 // filter has been processed and the offset applied
2372 m_FilterManager->setFilterAbsoluteFocusDetails(focusFilter->currentIndex(), currentPosition,
2373 m_LastSourceAutofocusTemperature, m_LastSourceAutofocusAlt);
2374 }
2375 }
2376
2377 // Delay state notification if we have a locked filter pending return to original filter
2378 if (fallbackFilterPending)
2379 {
2380 m_pendingState = completionState;
2381 FilterManager::FilterPolicy policy = static_cast<FilterManager::FilterPolicy>(FilterManager::CHANGE_POLICY |
2382 FilterManager::OFFSET_POLICY);
2383 m_FilterManager->setFilterPosition(fallbackFilterPosition, policy);
2384 }
2385 else
2386 setState(completionState);
2387
2388 if (autoFocusUsed && buildOffsetsUsed)
2389 // If we are building filter offsets signal AF run is complete
2390 m_FilterManager->autoFocusComplete(completionState, currentPosition, m_LastSourceAutofocusTemperature,
2391 m_LastSourceAutofocusAlt);
2392
2393 resetButtons();
2394}
2395
2396QString Focus::getAnalyzeData()
2397{
2398 QString analysis_results = "";
2399
2400 for (int i = 0; i < plot_position.size(); ++i)
2401 {
2402 analysis_results.append(QString("%1%2|%3|%4|%5")
2403 .arg(i == 0 ? "" : "|" )
2404 .arg(QString::number(plot_position[i], 'f', 0))
2405 .arg(QString::number(plot_value[i], 'f', 3))
2406 .arg(QString::number(plot_weight[i], 'f', 3))
2407 .arg(QString::number(plot_outlier[i])));
2408 }
2409 return analysis_results;
2410}
2411
2412void Focus::completeFocusProcedure(FocusState completionState, AutofocusFailReason failCode, QString failCodeInfo,
2413 bool plot)
2414{
2415 // On an Advisor complete, Autofocus wasn't run so don't update values / modules as per normal AF success
2416 if (inAutoFocus && failCode != Ekos::FOCUS_FAIL_ADVISOR_COMPLETE)
2417 {
2418 // Update the plot vectors (used by Analyze)
2419 updatePlotPosition();
2420
2421 if (completionState == Ekos::FOCUS_COMPLETE)
2422 {
2423 // Usually we'll record the AF information except for certain FocusAdvisor activities that are not complete AF runs
2424 if (plot)
2425 emit redrawHFRPlot(polynomialFit.get(), currentPosition, currentHFR);
2426
2427 appendLogText(i18np("Focus procedure completed after %1 iteration.",
2428 "Focus procedure completed after %1 iterations.", plot_position.count()));
2429
2430 setLastFocusTemperature();
2431 setLastFocusAlt();
2432 resetAdaptiveFocus(m_OpsFocusSettings->focusAdaptive->isChecked());
2433
2434 // CR add auto focus position, temperature and filter to log in CSV format
2435 // this will help with setting up focus offsets and temperature compensation
2436 qCInfo(KSTARS_EKOS_FOCUS) << "Autofocus values: position," << currentPosition << ", temperature,"
2437 << m_LastSourceAutofocusTemperature << ", filter," << filter()
2438 << ", HFR," << currentHFR << ", altitude," << m_LastSourceAutofocusAlt;
2439
2440 if (m_FocusAlgorithm == FOCUS_POLYNOMIAL)
2441 {
2442 // Add the final polynomial values to the signal sent to Analyze.
2443 plot_position.append(currentPosition);
2444 plot_value.append(currentHFR);
2445 plot_weight.append(1.0);
2446 plot_outlier.append(false);
2447 }
2448
2449 appendFocusLogText(QString("%1, %2, %3, %4, %5\n")
2450 .arg(QString::number(currentPosition))
2451 .arg(QString::number(m_LastSourceAutofocusTemperature, 'f', 1))
2452 .arg(filter())
2453 .arg(QString::number(currentHFR, 'f', 3))
2454 .arg(QString::number(m_LastSourceAutofocusAlt, 'f', 1)));
2455 // Replace user position with optimal position
2456 absTicksSpin->setValue(currentPosition);
2457 }
2458 // In case of failure, go back to last position if the focuser is absolute
2459 else if (canAbsMove && initialFocuserAbsPosition >= 0 && resetFocusIteration <= MAXIMUM_RESET_ITERATIONS &&
2460 m_RestartState != RESTART_ABORT)
2461 {
2462 // If we're doing in-sequence focusing using an absolute focuser, retry focusing once, starting from last known good position
2463 bool const retry_focusing = m_RestartState == RESTART_NONE &&
2464 (failCode == Ekos::FOCUS_FAIL_ADVISOR_RERUN || ++resetFocusIteration < MAXIMUM_RESET_ITERATIONS);
2465
2466 // If retrying, before moving, reset focus frame in case the star in subframe was lost
2467 if (retry_focusing)
2468 {
2469 m_RestartState = RESTART_NOW;
2470 resetFrame();
2471 }
2472
2473 resetFocuser();
2474
2475 // Bypass the rest of the function if we retry - we will fail if we could not move the focuser
2476 if (retry_focusing)
2477 {
2478 emit autofocusAborted(filter(), getAnalyzeData(), m_OpsFocusProcess->focusUseWeights->isChecked(), failCode, failCodeInfo);
2479 return;
2480 }
2481 else
2482 {
2483 // We're in Autofocus and we've hit our max retry limit, so...
2484 // resetFocuser will have initiated a focuser reset back to its starting position
2485 // so we need to wait for that move to complete before returning control.
2486 // This is important if the scheduler is running autofocus as it will likely
2487 // immediately retry. The startup process will take the current focuser position
2488 // as the start position and so the focuser needs to have arrived at its starting
2489 // position before this. So set m_RestartState to log this.
2490 m_RestartState = RESTART_ABORT;
2491 return;
2492 }
2493 }
2494
2495 // Reset the retry count on success or maximum count
2496 resetFocusIteration = 0;
2497 }
2498
2499 const bool autoFocusUsed = inAutoFocus;
2500 const bool inBuildOffsetsUsed = inBuildOffsets;
2501
2502 // Refresh display if needed
2503 if (m_FocusAlgorithm == FOCUS_POLYNOMIAL && plot)
2504 emit drawPolynomial(polynomialFit.get(), isVShapeSolution, true);
2505
2506 // Enforce settling duration. Note stop resets m_GuidingSuspended
2507 int const settleTime = m_GuidingSuspended ? m_OpsFocusSettings->focusGuideSettleTime->value() : 0;
2508
2509 // Reset the autofocus flags
2510 stop(completionState);
2511
2512 if (settleTime > 0)
2513 appendLogText(i18n("Settling for %1s...", settleTime));
2514
2515 QTimer::singleShot(settleTime * 1000, this, [ &, settleTime, completionState, autoFocusUsed, inBuildOffsetsUsed, failCode,
2516 failCodeInfo]()
2517 {
2518 settle(completionState, autoFocusUsed, inBuildOffsetsUsed, failCode, failCodeInfo);
2519
2520 if (settleTime > 0)
2521 appendLogText(i18n("Settling complete."));
2522 });
2523}
2524
2525void Focus::resetFocuser()
2526{
2527 // If we are able to and need to, move the focuser back to the initial position and let the procedure restart from its termination
2528 if (m_Focuser && m_Focuser->isConnected() && initialFocuserAbsPosition >= 0)
2529 {
2530 // HACK: If the focuser will not move, cheat a little to get the notification - see processNumber
2531 if (currentPosition == initialFocuserAbsPosition)
2532 currentPosition--;
2533
2534 appendLogText(i18n("Autofocus failed, moving back to initial focus position %1.", initialFocuserAbsPosition));
2535 changeFocus(initialFocuserAbsPosition - currentPosition);
2536 /* Restart will be executed by the end-of-move notification from the device if needed by resetFocus */
2537 }
2538}
2539
2540void Focus::setCurrentMeasure()
2541{
2542 // Let's now report the current HFR
2543 qCDebug(KSTARS_EKOS_FOCUS) << "Focus newFITS #" << starMeasureFrames.count() + 1 << ": Current HFR " << currentHFR <<
2544 " Num stars "
2545 << (starSelected ? 1 : currentNumStars);
2546
2547 // Take the new HFR into account, eventually continue to stack samples
2548 if (appendMeasure(currentMeasure))
2549 {
2550 capture();
2551 return;
2552 }
2553 else starMeasureFrames.clear();
2554
2555 // Let signal the current HFR now depending on whether the focuser is absolute or relative
2556 // Outside of Focus we continue to rely on HFR and independent of which measure the user selected we always calculate HFR
2557 if (canAbsMove)
2558 emit newHFR(currentHFR, currentPosition, inAutoFocus, opticalTrain());
2559 else
2560 emit newHFR(currentHFR, -1, inAutoFocus, opticalTrain());
2561
2562 // Format the labels under the V-curve
2563 HFROut->setText(QString("%1").arg(currentHFR * getStarUnits(m_StarMeasure, m_StarUnits), 0, 'f', 2));
2564 if (m_StarMeasure == FOCUS_STAR_FWHM)
2565 FWHMOut->setText(QString("%1").arg(currentFWHM * getStarUnits(m_StarMeasure, m_StarUnits), 0, 'f', 2));
2566 starsOut->setText(QString("%1").arg(m_ImageData->getDetectedStars()));
2567 iterOut->setText(QString("%1").arg(absIterations + 1));
2568
2569 // Display message in case _last_ HFR was invalid
2570 if (lastHFR == INVALID_STAR_MEASURE)
2571 appendLogText(i18n("FITS received. No stars detected."));
2572
2573 // If we have a valid HFR value
2574 if (!isStarMeasureStarBased() || currentHFR > 0)
2575 {
2576 // Check if we're done from polynomial fitting algorithm
2577 if (m_FocusAlgorithm == FOCUS_POLYNOMIAL && isVShapeSolution)
2578 {
2579 completeFocusProcedure(Ekos::FOCUS_COMPLETE, Ekos::FOCUS_FAIL_NONE);
2580 return;
2581 }
2582
2583 Edge selectedHFRStarHFR = m_ImageData->getSelectedHFRStar();
2584
2585 // Center tracking box around selected star (if it valid) either in:
2586 // 1. Autofocus
2587 // 2. CheckFocus (minimumHFRCheck)
2588 // The starCenter _must_ already be defined, otherwise, we proceed until
2589 // the latter half of the function searches for a star and define it.
2590 if (starCenter.isNull() == false && (inAutoFocus || minimumRequiredHFR >= 0))
2591 {
2592 // Now we have star selected in the frame
2593 starSelected = true;
2594 starCenter.setX(qMax(0, static_cast<int>(selectedHFRStarHFR.x)));
2595 starCenter.setY(qMax(0, static_cast<int>(selectedHFRStarHFR.y)));
2596
2597 syncTrackingBoxPosition();
2598
2599 // Record the star information (X, Y, currentHFR)
2600 QVector3D oneStar = starCenter;
2601 oneStar.setZ(currentHFR);
2602 starsHFR.append(oneStar);
2603 }
2604 else
2605 {
2606 // Record the star information (X, Y, currentHFR)
2607 QVector3D oneStar(starCenter.x(), starCenter.y(), currentHFR);
2608 starsHFR.append(oneStar);
2609 }
2610
2611 if (currentHFR > maxHFR)
2612 maxHFR = currentHFR;
2613
2614 // Append point to the #Iterations vs #HFR chart in case of looping or in case in autofocus with a focus
2615 // that does not support position feedback.
2616
2617 // If inAutoFocus is true without canAbsMove and without canRelMove, canTimerMove must be true.
2618 // We'd only want to execute this if the focus linear algorithm is not being used, as that
2619 // algorithm simulates a position-based system even for timer-based focusers.
2620 if (inFocusLoop || (inAutoFocus && ! isPositionBased()))
2621 {
2622 int pos = plot_position.empty() ? 1 : plot_position.last() + 1;
2623 addPlotPosition(pos, currentMeasure);
2624 }
2625 }
2626 else
2627 {
2628 // Let's record an invalid star result
2629 QVector3D oneStar(starCenter.x(), starCenter.y(), INVALID_STAR_MEASURE);
2630 starsHFR.append(oneStar);
2631 }
2632
2633 // First check that we haven't already search for stars
2634 // Since star-searching algorithm are time-consuming, we should only search when necessary
2635 m_FocusView->updateFrame();
2636
2637 setHFRComplete();
2638
2639 if (m_abInsOn && !inScanStartPos && !focusAdvisor->inFocusAdvisor())
2640 calculateAbInsData();
2641}
2642
2643// Save off focus frame during Autofocus for later debugging
2644void Focus::saveFocusFrame()
2645{
2646 if (inAutoFocus && Options::focusLogging() && Options::saveFocusImages())
2647 {
2648 QDir dir;
2649 QDateTime now = KStarsData::Instance()->lt();
2650 QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("autofocus/" +
2651 now.toString("yyyy-MM-dd"));
2652 dir.mkpath(path);
2653
2654 // To help identify focus frames add run number, step and frame (for multiple frames at each step)
2655 // If FocusAdvisor is manipuling Autofocus then annote frame appropriately
2656 QString prefix;
2657 if (focusAdvisor->inFocusAdvisor())
2658 prefix = focusAdvisor->getFocusFramePrefix();
2659 else if (inScanStartPos)
2660 prefix = QString("_ssp_%1_%2").arg(m_AFRun).arg(absIterations + 1);
2661 else if (m_FocusAlgorithm == FOCUS_LINEAR1PASS && linearFocuser)
2662 {
2663 const int currentStep = linearFocuser->currentStep() + 1;
2664 prefix = QString("_%1_%2").arg(m_AFRun).arg(currentStep);
2665 }
2666
2667 if (!prefix.isEmpty())
2668 // Add the frame count
2669 prefix.append(QString("_%1").arg(starMeasureFrames.count()));
2670
2671 // IS8601 contains colons but they are illegal under Windows OS, so replacing them with '-'
2672 // The timestamp is no longer ISO8601 but it should solve interoperality issues between different OS hosts
2673 QString name = "autofocus_frame_" + now.toString("HH-mm-ss") + prefix + ".fits";
2674 QString filename = path + QStringLiteral("/") + name;
2675 m_ImageData->saveImage(filename);
2676 }
2677}
2678
2679void Focus::calculateAbInsData()
2680{
2681 ImageMosaicMask *mosaicmask = dynamic_cast<ImageMosaicMask *>(m_FocusView->imageMask().get());
2682 const QVector<QRect> tiles = mosaicmask->tiles();
2683 auto stars = m_ImageData->getStarCenters();
2684 QVector<QList<Edge *>> tileStars(NUM_TILES);
2685
2686 for (int star = 0; star < stars.count(); star++)
2687 {
2688 const int x = stars[star]->x;
2689 const int y = stars[star]->y;
2690 for (int tile = 0; tile < NUM_TILES; tile++)
2691 {
2692 QRect thisTile = tiles[tile];
2693 if (thisTile.contains(x, y))
2694 {
2695 tileStars[tile].append(stars[star]);
2696 break;
2697 }
2698 }
2699 }
2700
2701 // Get the measure for each tile
2702 for (int tile = 0; tile < tileStars.count(); tile++)
2703 {
2704 double measure, weight;
2705 std::vector<double> HFRs;
2706
2707 switch (m_StarMeasure)
2708 {
2709 case FOCUS_STAR_HFR:
2710 case FOCUS_STAR_HFR_ADJ:
2711 for (int star = 0; star < tileStars[tile].count(); star++)
2712 {
2713 HFRs.push_back(tileStars[tile][star]->HFR);
2714 }
2715 measure = Mathematics::RobustStatistics::ComputeLocation(Mathematics::RobustStatistics::LOCATION_SIGMACLIPPING, HFRs, 2);
2716 weight = calculateStarWeight(m_OpsFocusProcess->focusUseWeights->isChecked(), HFRs);
2717 break;
2718 case FOCUS_STAR_FWHM:
2719 getFWHM(tileStars[tile], &measure, &weight);
2720 break;
2721 case FOCUS_STAR_NUM_STARS:
2722 measure = tileStars[tile].count();
2723 weight = 1.0;
2724 break;
2725 case FOCUS_STAR_FOURIER_POWER:
2726 getFourierPower(&measure, &weight, tile);
2727 break;
2728 case FOCUS_STAR_STDDEV:
2729 case FOCUS_STAR_SOBEL:
2730 case FOCUS_STAR_LAPLASSIAN:
2731 case FOCUS_STAR_CANNY:
2732 getBlurriness(m_StarMeasure, m_OpsFocusProcess->focusDenoise->isChecked(), &measure, &weight, QRect(), tile);
2733 break;
2734 default:
2735 qCDebug(KSTARS_EKOS_FOCUS) << "Unable to calculate Aberration Inspector data";
2736 return;
2737 }
2738
2739 m_abInsMeasure[tile].append(measure);
2740 m_abInsWeight[tile].append(weight);
2741 m_abInsNumStars[tile].append(tileStars[tile].count());
2742
2743 if (!linearFocuser->isInFirstPass())
2744 {
2745 // This is the last datapoint so calculate average star position in the tile from the tile center
2746 // FOCUS_STAR_FOURIER_POWER doesn't use stars directly so no need to calculate offset. The other
2747 // measures all use stars: FOCUS_STAR_NUM_STARS, FOCUS_STAR_FWHM, FOCUS_STAR_HFR, FOCUS_STAR_HFR_ADJ
2748 int xAv = 0, yAv = 0;
2749 if (m_StarMeasure != FOCUS_STAR_FOURIER_POWER)
2750 {
2751 QPoint tileCenter = tiles[tile].center();
2752 int xSum = 0.0, ySum = 0.0;
2753 for (int star = 0; star < tileStars[tile].count(); star++)
2754 {
2755 xSum += tileStars[tile][star]->x - tileCenter.x();
2756 ySum += tileStars[tile][star]->y - tileCenter.y();
2757 }
2758
2759 xAv = (tileStars[tile].count() <= 0) ? 0 : xSum / tileStars[tile].count();
2760 yAv = (tileStars[tile].count() <= 0) ? 0 : ySum / tileStars[tile].count();
2761 }
2762 m_abInsTileCenterOffset.append(QPoint(xAv, yAv));
2763 }
2764 }
2765 m_abInsPosition.append(currentPosition);
2766}
2767
2768void Focus::setCaptureComplete()
2769{
2770 DarkLibrary::Instance()->disconnect(this);
2771
2772 // If we have a box, sync the bounding box to its position.
2773 syncTrackingBoxPosition();
2774
2775 // Notify user if we're not looping
2776 if (inFocusLoop == false)
2777 appendLogText(i18n("Image received."));
2778
2779 if (m_captureInProgress && inFocusLoop == false && inAutoFocus == false)
2780 m_Camera->setUploadMode(rememberUploadMode);
2781
2782 if (m_RememberCameraFastExposure && inFocusLoop == false && inAutoFocus == false)
2783 {
2784 m_RememberCameraFastExposure = false;
2785 m_Camera->setFastExposureEnabled(true);
2786 }
2787
2788 m_captureInProgress = false;
2789 if (m_abortInProgress)
2790 {
2791 // We are trying to abort autofocus so do no further processing.
2792 // Note this will cover both the original capture and any dark frame processing
2793 qCDebug(KSTARS_EKOS_FOCUS) << "Abort AF: discarding frame results in " << __FUNCTION__;
2794 return;
2795 }
2796
2797 // update the limits from the real values
2798 checkMosaicMaskLimits();
2799
2800 // Emit the whole image
2801 emit newImage(m_FocusView);
2802 // Emit the tracking (bounding) box view. Used in Summary View
2803 emit newStarPixmap(m_FocusView->getTrackingBoxPixmap(10));
2804
2805 // If we are not looping; OR
2806 // If we are looping but we already have tracking box enabled; OR
2807 // If we are asked to analyze _all_ the stars within the field
2808 // THEN let's find stars in the image and get current HFR
2809 // Unless the Star Measure assumes a non-star field, e.g. lunar
2810 // in which case circumvent star detection
2811 if (!isStarMeasureStarBased())
2812 starDetectionFinished();
2813 else if (inFocusLoop == false || (inFocusLoop && (m_FocusView->isTrackingBoxEnabled()
2814 || m_OpsFocusSettings->focusUseFullField->isChecked())))
2815 analyzeSources();
2816 else
2817 setHFRComplete();
2818}
2819
2820void Focus::setHFRComplete()
2821{
2822 // If we are just framing, let's capture again
2823 if (inFocusLoop)
2824 {
2825 capture();
2826 return;
2827 }
2828
2829 // Get target chip
2830 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
2831
2832 // Get target chip binning
2833 int subBinX = 1, subBinY = 1;
2834 if (!targetChip->getBinning(&subBinX, &subBinY))
2835 qCDebug(KSTARS_EKOS_FOCUS) << "Warning: target chip is reporting no binning property, using 1x1.";
2836
2837 if (m_OpsFocusSettings->focusSubFrame->isChecked() && !isStarMeasureStarBased() && !starSelected)
2838 {
2839 appendLogText(i18n("Capture complete. Select a region of interest."));
2840
2841 syncTrackingBoxPosition();
2842
2843 // Now we wait
2844 setState(Ekos::FOCUS_WAITING);
2845
2846 // If the user does not select for a timeout period, we abort.
2847 waitStarSelectTimer.start();
2848 return;
2849 }
2850 else if (m_OpsFocusSettings->focusSubFrame->isChecked() && isStarMeasureStarBased() && starCenter.isNull())
2851 {
2852 // If star is NOT yet selected in a non-full-frame situation
2853 // then let's now try to find the star. This step is skipped for full frames
2854 // since there isn't a single star to select as we are only interested in the overall average HFR.
2855 // We need to check if we can find the star right away, or if we need to _subframe_ around the
2856 // selected star.
2857 int x = 0, y = 0, w = 0, h = 0;
2858
2859 // Let's get the stored frame settings for this particular chip
2860 if (frameSettings.contains(targetChip))
2861 {
2862 QVariantMap settings = frameSettings[targetChip];
2863 x = settings["x"].toInt();
2864 y = settings["y"].toInt();
2865 w = settings["w"].toInt();
2866 h = settings["h"].toInt();
2867 }
2868 else
2869 // Otherwise let's get the target chip frame coordinates.
2870 targetChip->getFrame(&x, &y, &w, &h);
2871
2872 // In case auto star is selected.
2873 if (m_OpsFocusSettings->focusAutoStarEnabled->isChecked())
2874 {
2875 // Do we have a valid star detected?
2876 const Edge selectedHFRStar = m_ImageData->getSelectedHFRStar();
2877
2878 if (selectedHFRStar.x == -1)
2879 {
2880 appendLogText(i18n("Failed to automatically select a star. Please select a star manually."));
2881
2882 // Center the tracking box in the frame and display it
2883 m_FocusView->setTrackingBox(QRect(w - m_OpsFocusSettings->focusBoxSize->value() / (subBinX * 2),
2884 h - m_OpsFocusSettings->focusBoxSize->value() / (subBinY * 2),
2885 m_OpsFocusSettings->focusBoxSize->value() / subBinX, m_OpsFocusSettings->focusBoxSize->value() / subBinY));
2886 m_FocusView->setTrackingBoxEnabled(true);
2887
2888 // Use can now move it to select the desired star
2889 setState(Ekos::FOCUS_WAITING);
2890
2891 // Start the wait timer so we abort after a timeout if the user does not make a choice
2892 waitStarSelectTimer.start();
2893
2894 return;
2895 }
2896
2897 // set the tracking box on selectedHFRStar
2898 starCenter.setX(selectedHFRStar.x);
2899 starCenter.setY(selectedHFRStar.y);
2900 starCenter.setZ(subBinX);
2901 starSelected = true;
2902 syncTrackingBoxPosition();
2903
2904 // Do we need to subframe?
2905 if (subFramed == false && isFocusSubFrameEnabled() && m_OpsFocusSettings->focusSubFrame->isChecked())
2906 {
2907 int offset = (static_cast<double>(m_OpsFocusSettings->focusBoxSize->value()) / subBinX) * 1.5;
2908 int subX = (selectedHFRStar.x - offset) * subBinX;
2909 int subY = (selectedHFRStar.y - offset) * subBinY;
2910 int subW = offset * 2 * subBinX;
2911 int subH = offset * 2 * subBinY;
2912
2913 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
2914 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
2915
2916 // Try to limit the subframed selection
2917 if (subX < minX)
2918 subX = minX;
2919 if (subY < minY)
2920 subY = minY;
2921 if ((subW + subX) > maxW)
2922 subW = maxW - subX;
2923 if ((subH + subY) > maxH)
2924 subH = maxH - subY;
2925
2926 // Now we store the subframe coordinates in the target chip frame settings so we
2927 // reuse it later when we capture again.
2928 QVariantMap settings = frameSettings[targetChip];
2929 settings["x"] = subX;
2930 settings["y"] = subY;
2931 settings["w"] = subW;
2932 settings["h"] = subH;
2933 settings["binx"] = subBinX;
2934 settings["biny"] = subBinY;
2935
2936 qCDebug(KSTARS_EKOS_FOCUS) << "Frame is subframed. X:" << subX << "Y:" << subY << "W:" << subW << "H:" << subH << "binX:" <<
2937 subBinX << "binY:" << subBinY;
2938
2939 starsHFR.clear();
2940
2941 frameSettings[targetChip] = settings;
2942
2943 // Set the star center in the center of the subframed coordinates
2944 starCenter.setX(subW / (2 * subBinX));
2945 starCenter.setY(subH / (2 * subBinY));
2946 starCenter.setZ(subBinX);
2947
2948 subFramed = true;
2949
2950 m_FocusView->setFirstLoad(true);
2951
2952 // Now let's capture again for the actual requested subframed image.
2953 capture();
2954 return;
2955 }
2956 // If we're subframed or don't need subframe, let's record the max star coordinates
2957 else
2958 {
2959 starCenter.setX(selectedHFRStar.x);
2960 starCenter.setY(selectedHFRStar.y);
2961 starCenter.setZ(subBinX);
2962
2963 // Let's now capture again if we're autofocusing
2964 if (inAutoFocus)
2965 {
2966 capture();
2967 return;
2968 }
2969 }
2970 }
2971 // If manual selection is enabled then let's ask the user to select the focus star
2972 else
2973 {
2974 appendLogText(i18n("Capture complete. Select a star to focus."));
2975
2976 starSelected = false;
2977
2978 // Let's now display and set the tracking box in the center of the frame
2979 // so that the user moves it around to select the desired star.
2980 int subBinX = 1, subBinY = 1;
2981 targetChip->getBinning(&subBinX, &subBinY);
2982
2983 m_FocusView->setTrackingBox(QRect((w - m_OpsFocusSettings->focusBoxSize->value()) / (subBinX * 2),
2984 (h - m_OpsFocusSettings->focusBoxSize->value()) / (2 * subBinY),
2985 m_OpsFocusSettings->focusBoxSize->value() / subBinX, m_OpsFocusSettings->focusBoxSize->value() / subBinY));
2986 m_FocusView->setTrackingBoxEnabled(true);
2987
2988 // Now we wait
2989 setState(Ekos::FOCUS_WAITING);
2990
2991 // If the user does not select for a timeout period, we abort.
2992 waitStarSelectTimer.start();
2993 return;
2994 }
2995 }
2996
2997 // Check if the focus module is requested to verify if the minimum HFR value is met.
2998 if (minimumRequiredHFR >= 0)
2999 {
3000 // In case we failed to detected, we capture again.
3001 if (currentHFR == INVALID_STAR_MEASURE)
3002 {
3003 if (noStarCount++ < MAX_RECAPTURE_RETRIES)
3004 {
3005 appendLogText(i18n("No stars detected while testing HFR, capturing again..."));
3006 // On Last Attempt reset focus frame to capture full frame and recapture star if possible
3007 if (noStarCount == MAX_RECAPTURE_RETRIES)
3008 resetFrame();
3009 capture();
3010 return;
3011 }
3012 // If we exceeded maximum tries we abort
3013 else
3014 {
3015 noStarCount = 0;
3016 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_NO_STARS);
3017 }
3018 }
3019 // If the detect current HFR is more than the minimum required HFR
3020 // then we should start the autofocus process now to bring it down.
3021 else if (currentHFR > minimumRequiredHFR)
3022 {
3023 qCDebug(KSTARS_EKOS_FOCUS) << "Current HFR:" << currentHFR << "is above required minimum HFR:" << minimumRequiredHFR <<
3024 ". Starting AutoFocus...";
3025 QString reasonInfo = i18n("HFR %1 > Limit %2", QString::number(currentHFR, 'f', 2), QString::number(minimumRequiredHFR, 'f',
3026 2));
3027 minimumRequiredHFR = INVALID_STAR_MEASURE;
3028 runAutoFocus(AutofocusReason::FOCUS_HFR_CHECK, reasonInfo);
3029 }
3030 // Otherwise, the current HFR is fine and lower than the required minimum HFR so we announce success.
3031 else
3032 {
3033 qCDebug(KSTARS_EKOS_FOCUS) << "Current HFR:" << currentHFR << "is below required minimum HFR:" << minimumRequiredHFR <<
3034 ". Autofocus successful.";
3035 completeFocusProcedure(Ekos::FOCUS_COMPLETE, Ekos::FOCUS_FAIL_NONE);
3036 }
3037
3038 // Nothing more for now
3039 return;
3040 }
3041
3042 // If we are not in autofocus process, we're done.
3043 if (inAutoFocus == false)
3044 {
3045 // If we are done and there is no further autofocus,
3046 // we reset state to IDLE
3047 if (state() != Ekos::FOCUS_IDLE)
3048 setState(Ekos::FOCUS_IDLE);
3049
3050 resetButtons();
3051 return;
3052 }
3053
3054 // Set state to progress
3055 if (state() != Ekos::FOCUS_PROGRESS)
3056 setState(Ekos::FOCUS_PROGRESS);
3057
3058 // Now let's kick in the algorithms
3059 if (inScanStartPos)
3060 scanStartPos();
3061 else if(focusAdvisor->inFocusAdvisor())
3062 focusAdvisor->control();
3063 else if(m_FocusAlgorithm == FOCUS_LINEAR || m_FocusAlgorithm == FOCUS_LINEAR1PASS)
3064 autoFocusLinear();
3065 else if (canAbsMove || canRelMove)
3066 // Position-based algorithms
3067 autoFocusAbs();
3068 else
3069 // Time open-looped algorithms
3070 autoFocusRel();
3071}
3072
3073QString Focus::getyAxisLabel(StarMeasure starMeasure)
3074{
3075 QString str = "HFR";
3076 m_StarUnits == FOCUS_UNITS_ARCSEC ? str += " (\")" : str += " (pix)";
3077
3078 switch (starMeasure)
3079 {
3080 case FOCUS_STAR_HFR:
3081 break;
3082 case FOCUS_STAR_HFR_ADJ:
3083 str = "HFR Adj";
3084 m_StarUnits == FOCUS_UNITS_ARCSEC ? str += " (\")" : str += " (pix)";
3085 break;
3086 case FOCUS_STAR_FWHM:
3087 str = "FWHM";
3088 m_StarUnits == FOCUS_UNITS_ARCSEC ? str += " (\")" : str += " (pix)";
3089 break;
3090 case FOCUS_STAR_NUM_STARS:
3091 str = "# Stars";
3092 break;
3093 case FOCUS_STAR_FOURIER_POWER:
3094 str = "Fourier Power";
3095 break;
3096 case FOCUS_STAR_STDDEV:
3097 str = "Std Dev";
3098 break;
3099 case FOCUS_STAR_SOBEL:
3100 str = "Sobel Factor";
3101 break;
3102 case FOCUS_STAR_LAPLASSIAN:
3103 str = "Laplassian Factor";
3104 break;
3105 case FOCUS_STAR_CANNY:
3106 str = "Canny Factor";
3107 break;
3108 default:
3109 break;
3110 }
3111 return str;
3112}
3113
3114void Focus::clearDataPoints()
3115{
3116 maxHFR = 1;
3117 polynomialFit.reset();
3118 plot_position.clear();
3119 plot_value.clear();
3120 plot_weight.clear();
3121 plot_outlier.clear();
3122 isVShapeSolution = false;
3123 m_abInsPosition.clear();
3124 m_abInsTileCenterOffset.clear();
3125 if (m_abInsMeasure.count() != NUM_TILES)
3126 {
3127 m_abInsMeasure.resize(NUM_TILES);
3128 m_abInsWeight.resize(NUM_TILES);
3129 m_abInsNumStars.resize(NUM_TILES);
3130 }
3131 else
3132 {
3133 for (int i = 0; i < m_abInsMeasure.count(); i++)
3134 {
3135 m_abInsMeasure[i].clear();
3136 m_abInsWeight[i].clear();
3137 m_abInsNumStars[i].clear();
3138 }
3139 }
3140
3141 emit initHFRPlot(getyAxisLabel(m_StarMeasure), getStarUnits(m_StarMeasure, m_StarUnits),
3142 m_OptDir == CurveFitting::OPTIMISATION_MINIMISE, m_OpsFocusProcess->focusUseWeights->isChecked(),
3143 inFocusLoop == false && isPositionBased());
3144}
3145
3146bool Focus::autoFocusChecks()
3147{
3148 if (++absIterations > MAXIMUM_ABS_ITERATIONS)
3149 {
3150 appendLogText(i18n("Autofocus failed: exceeded max iterations %1", MAXIMUM_ABS_ITERATIONS));
3151 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_MAX_ITERS);
3152 return false;
3153 }
3154
3155 // No stars detected, try to capture again
3156 if (isStarMeasureStarBased() && currentHFR == INVALID_STAR_MEASURE)
3157 {
3158 if (noStarCount < MAX_RECAPTURE_RETRIES)
3159 {
3160 noStarCount++;
3161 appendLogText(i18n("No stars detected, capturing again..."));
3162 capture();
3163 return false;
3164 }
3165 else if (m_FocusAlgorithm == FOCUS_LINEAR)
3166 {
3167 appendLogText(i18n("Failed to detect any stars at position %1. Continuing...", currentPosition));
3168 noStarCount = 0;
3169 }
3170 else if(!m_OpsFocusProcess->focusDonut->isChecked())
3171 {
3172 // Carry on for donut detection
3173 appendLogText(i18n("Failed to detect any stars. Reset frame and try again."));
3174 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_NO_STARS);
3175 return false;
3176 }
3177 }
3178 else
3179 noStarCount = 0;
3180
3181 return true;
3182}
3183
3184void Focus::plotLinearFocus()
3185{
3186 // I was hoping to avoid intermediate plotting, just set everything up then plot,
3187 // but this isn't working. For now, with plt=true, plot on every intermediate update.
3188 bool plt = true;
3189
3190 // Get the data to plot.
3191 QVector<double> values, weights;
3192 QVector<int> positions;
3193 linearFocuser->getMeasurements(&positions, &values, &weights);
3194 const FocusAlgorithmInterface::FocusParams &params = linearFocuser->getParams();
3195
3196 // As an optimization for slower machines, e.g. RPi4s, if the points are the same except for
3197 // the last point, just emit the last point instead of redrawing everything.
3198 static QVector<double> lastValues;
3199 static QVector<int> lastPositions;
3200
3201 bool incrementalChange = false;
3202 if (positions.size() > 1 && positions.size() == lastPositions.size() + 1)
3203 {
3204 bool ok = true;
3205 for (int i = 0; i < positions.size() - 1; ++i)
3206 if (positions[i] != lastPositions[i] || values[i] != lastValues[i])
3207 {
3208 ok = false;
3209 break;
3210 }
3211 incrementalChange = ok;
3212 }
3213 lastPositions = positions;
3214 lastValues = values;
3215
3216 const bool outlier = false;
3217 if (incrementalChange)
3218 emit newHFRPlotPosition(static_cast<double>(positions.last()), values.last(), (pow(weights.last(), -0.5)),
3219 outlier, params.initialStepSize, plt);
3220 else
3221 {
3222 emit initHFRPlot(getyAxisLabel(m_StarMeasure), getStarUnits(m_StarMeasure, m_StarUnits),
3223 m_OptDir == CurveFitting::OPTIMISATION_MINIMISE, params.useWeights, plt);
3224 for (int i = 0; i < positions.size(); ++i)
3225 emit newHFRPlotPosition(static_cast<double>(positions[i]), values[i], (pow(weights.last(), -0.5)),
3226 outlier, params.initialStepSize, plt);
3227 }
3228
3229 // Plot the polynomial, if there are enough points.
3230 if (values.size() > 3)
3231 {
3232 // The polynomial should only reflect 1st-pass samples.
3233 QVector<double> pass1Values, pass1Weights;
3234 QVector<int> pass1Positions;
3235 QVector<bool> pass1Outliers;
3236 double minPosition, minValue;
3237 double searchMin = std::max(params.minPositionAllowed, params.startPosition - params.maxTravel);
3238 double searchMax = std::min(params.maxPositionAllowed, params.startPosition + params.maxTravel);
3239
3240 linearFocuser->getPass1Measurements(&pass1Positions, &pass1Values, &pass1Weights, &pass1Outliers);
3241 if (m_FocusAlgorithm == FOCUS_LINEAR || m_CurveFit == CurveFitting::FOCUS_QUADRATIC)
3242 {
3243 // TODO: Need to determine whether to change LINEAR over to the LM solver in CurveFitting
3244 // This will be determined after L1P's first release has been deemed successful.
3245 polynomialFit.reset(new PolynomialFit(2, pass1Positions, pass1Values));
3246
3247 if (polynomialFit->findMinimum(params.startPosition, searchMin, searchMax, &minPosition, &minValue))
3248 {
3249 emit drawPolynomial(polynomialFit.get(), true, true, plt);
3250
3251 // Only plot the first pass' min position if we're not done.
3252 // Once we have a result, we don't want to display an intermediate minimum.
3253 if (linearFocuser->isDone())
3254 emit minimumFound(-1, -1, plt);
3255 else
3256 emit minimumFound(minPosition, minValue, plt);
3257 }
3258 else
3259 {
3260 // Didn't get a good polynomial fit.
3261 emit drawPolynomial(polynomialFit.get(), false, false, plt);
3262 emit minimumFound(-1, -1, plt);
3263 }
3264
3265 }
3266 else // Linear 1 Pass
3267 {
3268 if (curveFitting->findMinMax(params.startPosition, searchMin, searchMax, &minPosition, &minValue, params.curveFit,
3269 params.optimisationDirection))
3270 {
3271 R2 = curveFitting->calculateR2(static_cast<CurveFitting::CurveFit>(params.curveFit));
3272 emit drawCurve(curveFitting.get(), true, true, plt);
3273
3274 // For Linear 1 Pass always display the minimum on the graph
3275 emit minimumFound(minPosition, minValue, plt);
3276 }
3277 else
3278 {
3279 // Didn't get a good fit.
3280 emit drawCurve(curveFitting.get(), false, false, plt);
3281 emit minimumFound(-1, -1, plt);
3282 }
3283 }
3284 }
3285
3286 // Linear focuser might change the latest hfr with its relativeHFR scheme.
3287 HFROut->setText(QString("%1").arg(currentHFR * getStarUnits(m_StarMeasure, m_StarUnits), 0, 'f', 2));
3288
3289 emit setTitle(linearFocuser->getTextStatus(R2));
3290
3291 if (!plt) HFRPlot->replot();
3292}
3293
3294// Get the curve fitting goal
3295CurveFitting::FittingGoal Focus::getGoal(int numSteps)
3296{
3297 // The classic walk needs the STANDARD curve fitting
3298 if (m_FocusWalk == FOCUS_WALK_CLASSIC)
3299 return CurveFitting::FittingGoal::STANDARD;
3300
3301 // Fixed step walks will use C, except for the last step which should be BEST
3302 return (numSteps >= m_OpsFocusMechanics->focusNumSteps->value()) ? CurveFitting::FittingGoal::BEST :
3303 CurveFitting::FittingGoal::STANDARD;
3304}
3305
3306// Called after the first pass is complete and we're moving to the final focus position
3307// Calculate R2 for the curve and update the graph.
3308// Add the CFZ to the graph
3309void Focus::plotLinearFinalUpdates()
3310{
3311 bool plt = true;
3312 if (!m_OpsFocusProcess->focusRefineCurveFit->isChecked())
3313 {
3314 // Display the CFZ on the graph
3315 emit drawCFZ(linearFocuser->solution(), linearFocuser->solutionValue(), m_cfzSteps,
3316 m_CFZUI->focusCFZDisplayVCurve->isChecked());
3317 // Final updates to the graph title
3318 emit finalUpdates(linearFocuser->getTextStatus(R2), plt);
3319 }
3320 else
3321 {
3322 // v-curve needs to be redrawn to reflect the data from the refining process
3323 // Get the data to plot.
3324 QVector<double> pass1Values, pass1Weights;
3325 QVector<int> pass1Positions;
3326 QVector<bool> pass1Outliers;
3327
3328 linearFocuser->getPass1Measurements(&pass1Positions, &pass1Values, &pass1Weights, &pass1Outliers);
3329 const FocusAlgorithmInterface::FocusParams &params = linearFocuser->getParams();
3330
3331 emit initHFRPlot(getyAxisLabel(m_StarMeasure), getStarUnits(m_StarMeasure, m_StarUnits),
3332 m_OptDir == CurveFitting::OPTIMISATION_MINIMISE, m_OpsFocusProcess->focusUseWeights->isChecked(), plt);
3333
3334 for (int i = 0; i < pass1Positions.size(); ++i)
3335 emit newHFRPlotPosition(static_cast<double>(pass1Positions[i]), pass1Values[i], (pow(pass1Weights[i], -0.5)),
3336 pass1Outliers[i], params.initialStepSize, plt);
3337
3338 R2 = curveFitting->calculateR2(static_cast<CurveFitting::CurveFit>(params.curveFit));
3339 emit drawCurve(curveFitting.get(), true, true, false);
3340
3341 // For Linear 1 Pass always display the minimum on the graph
3342 emit minimumFound(linearFocuser->solution(), linearFocuser->solutionValue(), plt);
3343 // Display the CFZ on the graph
3344 emit drawCFZ(linearFocuser->solution(), linearFocuser->solutionValue(), m_cfzSteps,
3345 m_CFZUI->focusCFZDisplayVCurve->isChecked());
3346 // Update the graph title
3347 emit setTitle(linearFocuser->getTextStatus(R2), plt);
3348 }
3349}
3350
3351void Focus::startAberrationInspector()
3352{
3353#ifdef HAVE_DATAVISUALIZATION
3354 // Fill in the data structure to be passed to the Aberration Inspector
3355 AberrationInspector::abInsData data;
3356
3357 ImageMosaicMask *mosaicmask = dynamic_cast<ImageMosaicMask *>(m_FocusView->imageMask().get());
3358 if (!mosaicmask)
3359 {
3360 appendLogText(i18n("Unable to launch Aberration Inspector run %1...", m_abInsRun));
3361 return;
3362 }
3363
3364
3365 data.run = ++m_abInsRun;
3366 data.curveFit = m_CurveFit;
3367 data.useWeights = m_OpsFocusProcess->focusUseWeights->isChecked();
3368 data.optDir = m_OptDir;
3369 data.sensorWidth = m_CcdWidth;
3370 data.sensorHeight = m_CcdHeight;
3371 data.pixelSize = m_CcdPixelSizeX;
3372 data.tileWidth = mosaicmask->tiles()[0].width();
3373 data.focuserStepMicrons = m_CFZUI->focusCFZStepSize->value();
3374 data.yAxisLabel = getyAxisLabel(m_StarMeasure);
3375 data.starUnits = getStarUnits(m_StarMeasure, m_StarUnits);
3376 data.cfzSteps = m_cfzSteps;
3377 data.isPositionBased = isPositionBased();
3378
3379 // Launch the Aberration Inspector.
3380 appendLogText(i18n("Launching Aberration Inspector run %1...", m_abInsRun));
3381 QPointer<AberrationInspector> abIns(new AberrationInspector(data, m_abInsPosition, m_abInsMeasure, m_abInsWeight,
3382 m_abInsNumStars, m_abInsTileCenterOffset));
3383 abIns->setAttribute(Qt::WA_DeleteOnClose);
3384 abIns->show();
3385#endif
3386}
3387
3388// Initialise the Scan Start Position algorithm
3389bool Focus::initScanStartPos(const bool force, const int initialPosition)
3390{
3391 if (!force)
3392 {
3393 if (!m_OpsFocusProcess->focusScanStartPos->isChecked())
3394 return false;
3395
3396 if (!m_OpsFocusProcess->focusScanAlwaysOn->isChecked() && !m_AFRerun)
3397 // Only activate Scan Start Pos if "Always On" is set or we are rerunning after an AF failure
3398 return false;
3399 }
3400
3401 inScanStartPos = true;
3402 initialFocuserAbsPosition = initialPosition;
3403 m_scanMeasure.clear();
3404 m_scanPosition.clear();
3405
3406 appendLogText(i18n("Starting scan for initial focuser position."));
3407 emit setTitle(QString(i18n("Scanning for starting position...")), true);
3408
3409 if (!changeFocus(initialPosition - currentPosition))
3410 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_NO_MOVE);
3411 return true;
3412}
3413
3414// Algorithm to scan for an optimum start position for Autofocus
3415void Focus::scanStartPos()
3416{
3417 // Firstly do we have any stars?
3418 if (currentHFR == INVALID_STAR_MEASURE)
3419 {
3420 if (noStarCount < MAX_RECAPTURE_RETRIES)
3421 {
3422 noStarCount++;
3423 appendLogText(i18n("No stars detected, capturing again..."));
3424 capture();
3425 return;
3426 }
3427 else
3428 {
3429 appendLogText(i18n("Failed to detect any stars. Aborting..."));
3430 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_NO_STARS);
3431 return;
3432 }
3433 }
3434
3435 // Cap the maximum number of iterations before failing
3436 if (++absIterations > MAXIMUM_ABS_ITERATIONS)
3437 {
3438 appendLogText(i18n("Scan Start Pos: exceeded max iterations %1", MAXIMUM_ABS_ITERATIONS));
3439 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_MAX_ITERS);
3440 return;
3441 }
3442
3443 noStarCount = 0;
3444 m_scanPosition.push_back(currentPosition);
3445 m_scanMeasure.push_back(currentMeasure);
3446
3447 int deltaPos;
3448 const int step = m_scanPosition.size();
3449 const int maxSteps = m_OpsFocusProcess->focusScanDatapoints->value();
3450 const int stepSize = m_OpsFocusMechanics->focusTicks->value() * m_OpsFocusProcess->focusScanStepSizeFactor->value();
3451 emit newHFRPlotPosition(static_cast<double>(currentPosition), currentMeasure, pow(currentWeight, -0.5), false, stepSize,
3452 true);
3453 if (step < maxSteps)
3454 {
3455 // Collect more data
3456 emit setTitle(QString(i18n("Scanning for starting position %1/%2", step, maxSteps)), true);
3457 deltaPos = step * stepSize * std::pow(-1, step + 1);
3458 }
3459 else
3460 {
3461 // We have enough data
3462 auto it = std::min_element(std::begin(m_scanMeasure), std::end(m_scanMeasure));
3463 auto min = std::distance(std::begin(m_scanMeasure), it);
3464 if (min >= m_OpsFocusProcess->focusScanDatapoints->value() - 2)
3465 {
3466 // No minimum so we need to move and rerun the scan
3467 deltaPos = m_scanPosition[min] - currentPosition;
3468 m_scanPosition.clear();
3469 m_scanMeasure.clear();
3470 emit setTitle(QString(i18n("No scan minimum - widening search...")), true);
3471 }
3472 else
3473 {
3474 // We have a minimum so kick in Autofocus
3475 if (m_AutofocusReason == FOCUS_FOCUS_ADVISOR)
3476 focusAdvisor->setInFocusAdvisor(false);
3477
3478 const int initialPosition = m_scanPosition[min];
3479 setupLinearFocuser(initialPosition);
3480 inScanStartPos = false;
3481 absIterations = 0;
3482 deltaPos = linearRequestedPosition - currentPosition;
3483 emit setTitle(QString(i18n("Scan Start Position %1 Found", initialPosition)), true);
3484 appendLogText(i18n("Scan Start Position %1 found", initialPosition));
3485 }
3486 }
3487 if (!changeFocus(deltaPos))
3488 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_NO_MOVE);
3489}
3490
3491void Focus::autoFocusLinear()
3492{
3493 if (!autoFocusChecks())
3494 return;
3495
3496 if (!canAbsMove && !canRelMove && canTimerMove)
3497 {
3498 //const bool kFixPosition = true;
3499 if (linearRequestedPosition != currentPosition)
3500 //if (kFixPosition && (linearRequestedPosition != currentPosition))
3501 {
3502 qCDebug(KSTARS_EKOS_FOCUS) << "Linear: warning, changing position " << currentPosition << " to "
3503 << linearRequestedPosition;
3504
3505 currentPosition = linearRequestedPosition;
3506 }
3507 }
3508
3509 // Only use the relativeHFR algorithm if full field is enabled with one capture/measurement.
3510 bool useFocusStarsHFR = m_OpsFocusSettings->focusUseFullField->isChecked()
3511 && m_OpsFocusProcess->focusFramesCount->value() == 1;
3512 auto focusStars = useFocusStarsHFR || (m_FocusAlgorithm == FOCUS_LINEAR1PASS) ? &(m_ImageData->getStarCenters()) : nullptr;
3513
3514 linearRequestedPosition = linearFocuser->newMeasurement(currentPosition, currentMeasure, currentWeight, focusStars);
3515 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS && linearFocuser->isDone() && linearFocuser->solution() != -1)
3516 {
3517 // Linear 1 Pass is done, graph is drawn, so just move to the focus position, and update the graph.
3518 plotLinearFinalUpdates();
3519 if (m_abInsOn)
3520 startAberrationInspector();
3521 }
3522 else
3523 // Update the graph with the next datapoint, draw the curve, etc.
3524 plotLinearFocus();
3525
3526 if (linearFocuser->isDone())
3527 {
3528 if (linearFocuser->solution() != -1)
3529 {
3530 // If we're running under Focus Advisor analyse results and, if necessary, rerun
3531 if (m_AutofocusReason == FOCUS_FOCUS_ADVISOR)
3532 {
3533 if (focusAdvisor->analyseAF())
3534 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_ADVISOR_RERUN, "", false);
3535 else
3536 completeFocusProcedure(Ekos::FOCUS_COMPLETE, Ekos::FOCUS_FAIL_NONE, "", false);
3537 }
3538 else if (m_CurveFit == CurveFitting::FOCUS_QUADRATIC)
3539 // Linear only uses Quadratic so no need to do the R2 check, just complete
3540 completeFocusProcedure(Ekos::FOCUS_COMPLETE, Ekos::FOCUS_FAIL_NONE, "", false);
3541 else if (R2 >= m_OpsFocusProcess->focusR2Limit->value())
3542 {
3543 // Now test that the curve fit was acceptable. If not retry the focus process using standard retry process
3544 // R2 check is only available for Linear 1 Pass for Hyperbola and Parabola
3545 qCDebug(KSTARS_EKOS_FOCUS) << QString("Linear Curve Fit check passed R2=%1 focusR2Limit=%2").arg(R2).arg(
3546 m_OpsFocusProcess->focusR2Limit->value());
3547 completeFocusProcedure(Ekos::FOCUS_COMPLETE, FOCUS_FAIL_NONE, "", false);
3548 R2Retries = 0;
3549 }
3550 else if (R2Retries == 0)
3551 {
3552 // Failed the R2 check for the first time so retry...
3553 appendLogText(i18n("Curve Fit check failed R2=%1 focusR2Limit=%2 retrying...", R2,
3554 m_OpsFocusProcess->focusR2Limit->value()));
3555 QString failCodeInfo = i18n("R2=%1 < Limit=%2", QString::number(R2, 'f', 2),
3556 QString::number(m_OpsFocusProcess->focusR2Limit->value(), 'f', 2));
3557 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_R2, failCodeInfo, false);
3558 R2Retries++;
3559 }
3560 else
3561 {
3562 // Retried after an R2 check fail but failed again so... log msg and continue
3563 appendLogText(i18n("Curve Fit check failed again R2=%1 focusR2Limit=%2 but continuing...", R2,
3564 m_OpsFocusProcess->focusR2Limit->value()));
3565 completeFocusProcedure(Ekos::FOCUS_COMPLETE, Ekos::FOCUS_FAIL_NONE, "", false);
3566 R2Retries = 0;
3567 }
3568 }
3569 else
3570 {
3571 qCDebug(KSTARS_EKOS_FOCUS) << linearFocuser->doneReason();
3572 AutofocusFailReason failCode = linearFocuser->getFailCode();
3573 appendLogText(i18n("Linear autofocus algorithm aborted: %1", AutofocusFailReasonStr[failCode]));
3574 completeFocusProcedure(Ekos::FOCUS_ABORTED, failCode, "", false);
3575 }
3576 return;
3577 }
3578 else
3579 {
3580 const int delta = linearRequestedPosition - currentPosition;
3581
3582 if (!changeFocus(delta))
3583 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_NO_MOVE, "", false);
3584
3585 return;
3586 }
3587}
3588
3589void Focus::autoFocusAbs()
3590{
3591 // Q_ASSERT_X(canAbsMove || canRelMove, __FUNCTION__, "Prerequisite: only absolute and relative focusers");
3592
3593 static int minHFRPos = 0, focusOutLimit = 0, focusInLimit = 0, lastHFRPos = 0, fluctuations = 0;
3594 static double minHFR = 0, lastDelta = 0;
3595 double targetPosition = 0;
3596 bool ignoreLimitedDelta = false;
3597
3598 QString deltaTxt = QString("%1").arg(fabs(currentHFR - minHFR) * 100.0, 0, 'g', 3);
3599 QString HFRText = QString("%1").arg(currentHFR, 0, 'g', 3);
3600
3601 qCDebug(KSTARS_EKOS_FOCUS) << "===============================";
3602 qCDebug(KSTARS_EKOS_FOCUS) << "Current HFR: " << currentHFR << " Current Position: " << currentPosition;
3603 qCDebug(KSTARS_EKOS_FOCUS) << "Last minHFR: " << minHFR << " Last MinHFR Pos: " << minHFRPos;
3604 qCDebug(KSTARS_EKOS_FOCUS) << "Delta: " << deltaTxt << "%";
3605 qCDebug(KSTARS_EKOS_FOCUS) << "========================================";
3606
3607 if (minHFR)
3608 appendLogText(i18n("FITS received. HFR %1 @ %2. Delta (%3%)", HFRText, currentPosition, deltaTxt));
3609 else
3610 appendLogText(i18n("FITS received. HFR %1 @ %2.", HFRText, currentPosition));
3611
3612 if (!autoFocusChecks())
3613 return;
3614
3615 addPlotPosition(currentPosition, currentHFR);
3616
3617 switch (m_LastFocusDirection)
3618 {
3619 case FOCUS_NONE:
3620 lastHFR = currentHFR;
3621 initialFocuserAbsPosition = currentPosition;
3622 minHFR = currentHFR;
3623 minHFRPos = currentPosition;
3624 HFRDec = 0;
3625 HFRInc = 0;
3626 focusOutLimit = 0;
3627 focusInLimit = 0;
3628 lastDelta = 0;
3629 fluctuations = 0;
3630
3631 // This is the first step, so clamp the initial target position to the device limits
3632 // If the focuser cannot move because it is at one end of the interval, try the opposite direction next
3633 if (absMotionMax < currentPosition + pulseDuration)
3634 {
3635 if (currentPosition < absMotionMax)
3636 {
3637 pulseDuration = absMotionMax - currentPosition;
3638 }
3639 else
3640 {
3641 pulseDuration = 0;
3642 m_LastFocusDirection = FOCUS_IN;
3643 }
3644 }
3645 else if (currentPosition + pulseDuration < absMotionMin)
3646 {
3647 if (absMotionMin < currentPosition)
3648 {
3649 pulseDuration = currentPosition - absMotionMin;
3650 }
3651 else
3652 {
3653 pulseDuration = 0;
3654 m_LastFocusDirection = FOCUS_OUT;
3655 }
3656 }
3657
3658 m_LastFocusDirection = (pulseDuration > 0) ? FOCUS_OUT : FOCUS_IN;
3659 if (!changeFocus(pulseDuration, false))
3660 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_NO_MOVE);
3661
3662 break;
3663
3664 case FOCUS_IN:
3665 case FOCUS_OUT:
3666 if (reverseDir && focusInLimit && focusOutLimit &&
3667 fabs(currentHFR - minHFR) < (m_OpsFocusProcess->focusTolerance->value() / 100.0) && HFRInc == 0)
3668 {
3669 if (absIterations <= 2)
3670 {
3671 QString message = i18n("Change in HFR is too small. Try increasing the step size or decreasing the tolerance.");
3672 appendLogText(message);
3673 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert);
3674 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_SMALL_HFR);
3675 }
3676 else if (noStarCount > 0)
3677 {
3678 QString message = i18n("Failed to detect focus star in frame. Capture and select a focus star.");
3679 appendLogText(message);
3680 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert);
3681 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_NO_STARS);
3682 }
3683 else
3684 {
3685 completeFocusProcedure(Ekos::FOCUS_COMPLETE, Ekos::FOCUS_FAIL_NONE);
3686 }
3687 break;
3688 }
3689 else if (currentHFR < lastHFR)
3690 {
3691 // Let's now limit the travel distance of the focuser
3692 if (HFRInc >= 1 && m_LastFocusDirection == FOCUS_OUT && lastHFRPos < focusInLimit && fabs(currentHFR - lastHFR) > 0.1)
3693 {
3694 focusInLimit = lastHFRPos;
3695 qCDebug(KSTARS_EKOS_FOCUS) << "New FocusInLimit " << focusInLimit;
3696 }
3697 else if (HFRInc >= 1 && m_LastFocusDirection == FOCUS_IN && lastHFRPos > focusOutLimit &&
3698 fabs(currentHFR - lastHFR) > 0.1)
3699 {
3700 focusOutLimit = lastHFRPos;
3701 qCDebug(KSTARS_EKOS_FOCUS) << "New FocusOutLimit " << focusOutLimit;
3702 }
3703
3704 double factor = std::max(1.0, HFRDec / 2.0);
3705 if (m_LastFocusDirection == FOCUS_IN)
3706 targetPosition = currentPosition - (pulseDuration * factor);
3707 else
3708 targetPosition = currentPosition + (pulseDuration * factor);
3709
3710 qCDebug(KSTARS_EKOS_FOCUS) << "current Position" << currentPosition << " targetPosition " << targetPosition;
3711
3712 lastHFR = currentHFR;
3713
3714 // Let's keep track of the minimum HFR
3715 if (lastHFR < minHFR)
3716 {
3717 minHFR = lastHFR;
3718 minHFRPos = currentPosition;
3719 qCDebug(KSTARS_EKOS_FOCUS) << "new minHFR " << minHFR << " @ position " << minHFRPos;
3720 }
3721
3722 lastHFRPos = currentPosition;
3723
3724 // HFR is decreasing, we are on the right direction
3725 HFRDec++;
3726 if (HFRInc > 0)
3727 {
3728 // Remove bad data point and mark fluctuation
3729 if (plot_position.count() >= 2)
3730 {
3731 plot_position.remove(plot_position.count() - 2);
3732 plot_value.remove(plot_value.count() - 2);
3733 }
3734 fluctuations++;
3735 }
3736 HFRInc = 0;
3737 }
3738 else
3739 {
3740 // HFR increased, let's deal with it.
3741 HFRInc++;
3742 if (HFRDec > 0)
3743 fluctuations++;
3744 HFRDec = 0;
3745
3746 lastHFR = currentHFR;
3747 lastHFRPos = currentPosition;
3748
3749 // Keep moving in same direction (even if bad) for one more iteration to gather data points.
3750 if (HFRInc > 1)
3751 {
3752 // Looks like we're going away from optimal HFR
3753 reverseDir = true;
3754 HFRInc = 0;
3755
3756 qCDebug(KSTARS_EKOS_FOCUS) << "Focus is moving away from optimal HFR.";
3757
3758 // Let's set new limits
3759 if (m_LastFocusDirection == FOCUS_IN)
3760 {
3761 focusInLimit = currentPosition;
3762 qCDebug(KSTARS_EKOS_FOCUS) << "Setting focus IN limit to " << focusInLimit;
3763 }
3764 else
3765 {
3766 focusOutLimit = currentPosition;
3767 qCDebug(KSTARS_EKOS_FOCUS) << "Setting focus OUT limit to " << focusOutLimit;
3768 }
3769
3770 if (m_FocusAlgorithm == FOCUS_POLYNOMIAL && plot_position.count() > 4)
3771 {
3772 polynomialFit.reset(new PolynomialFit(2, 5, plot_position, plot_value));
3773 double a = *std::min_element(plot_position.constBegin(), plot_position.constEnd());
3774 double b = *std::max_element(plot_position.constBegin(), plot_position.constEnd());
3775 double min_position = 0, min_hfr = 0;
3776 isVShapeSolution = polynomialFit->findMinimum(minHFRPos, a, b, &min_position, &min_hfr);
3777 qCDebug(KSTARS_EKOS_FOCUS) << "Found Minimum?" << (isVShapeSolution ? "Yes" : "No");
3778 if (isVShapeSolution)
3779 {
3780 ignoreLimitedDelta = true;
3781 qCDebug(KSTARS_EKOS_FOCUS) << "Minimum Solution:" << min_hfr << "@" << min_position;
3782 targetPosition = round(min_position);
3783 appendLogText(i18n("Found polynomial solution @ %1", QString::number(min_position, 'f', 0)));
3784
3785 emit drawPolynomial(polynomialFit.get(), isVShapeSolution, true);
3786 emit minimumFound(min_position, min_hfr);
3787 }
3788 else
3789 {
3790 emit drawPolynomial(polynomialFit.get(), isVShapeSolution, false);
3791 }
3792 }
3793 }
3794
3795 if (HFRInc == 1)
3796 {
3797 // Keep going at same stride even if we are going away from CFZ
3798 // This is done to gather data points are the trough.
3799 if (std::abs(lastDelta) > 0)
3800 targetPosition = currentPosition + lastDelta;
3801 else
3802 targetPosition = currentPosition + pulseDuration;
3803 }
3804 else if (isVShapeSolution == false)
3805 {
3806 ignoreLimitedDelta = true;
3807 // Let's get close to the minimum HFR position so far detected
3808 if (m_LastFocusDirection == FOCUS_OUT)
3809 targetPosition = minHFRPos - pulseDuration / 2;
3810 else
3811 targetPosition = minHFRPos + pulseDuration / 2;
3812 }
3813
3814 qCDebug(KSTARS_EKOS_FOCUS) << "new targetPosition " << targetPosition;
3815 }
3816
3817 // Limit target Pulse to algorithm limits
3818 if (focusInLimit != 0 && m_LastFocusDirection == FOCUS_IN && targetPosition < focusInLimit)
3819 {
3820 targetPosition = focusInLimit;
3821 qCDebug(KSTARS_EKOS_FOCUS) << "Limiting target pulse to focus in limit " << targetPosition;
3822 }
3823 else if (focusOutLimit != 0 && m_LastFocusDirection == FOCUS_OUT && targetPosition > focusOutLimit)
3824 {
3825 targetPosition = focusOutLimit;
3826 qCDebug(KSTARS_EKOS_FOCUS) << "Limiting target pulse to focus out limit " << targetPosition;
3827 }
3828
3829 // Limit target pulse to focuser limits
3830 if (targetPosition < absMotionMin)
3831 targetPosition = absMotionMin;
3832 else if (targetPosition > absMotionMax)
3833 targetPosition = absMotionMax;
3834
3835 // We cannot go any further because of the device limits, this is a failure
3836 if (targetPosition == currentPosition)
3837 {
3838 // If case target position happens to be the minimal historical
3839 // HFR position, accept this value instead of bailing out.
3840 if (targetPosition == minHFRPos || isVShapeSolution)
3841 {
3842 appendLogText("Stopping at minimum recorded HFR position.");
3843 completeFocusProcedure(Ekos::FOCUS_COMPLETE, Ekos::FOCUS_FAIL_NONE);
3844 }
3845 else
3846 {
3847 QString message = i18n("Focuser cannot move further, device limits reached. Autofocus aborted.");
3848 appendLogText(message);
3849 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert);
3850 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_OOB);
3851 }
3852 return;
3853 }
3854
3855 // Too many fluctuatoins
3856 if (fluctuations >= MAXIMUM_FLUCTUATIONS)
3857 {
3858 QString message = i18n("Unstable fluctuations. Try increasing initial step size or exposure time.");
3859 appendLogText(message);
3860 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert);
3861 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FLUCTUATIONS);
3862 return;
3863 }
3864
3865 // Ops, deadlock
3866 if (focusOutLimit && focusOutLimit == focusInLimit)
3867 {
3868 QString message = i18n("Deadlock reached. Please try again with different settings.");
3869 appendLogText(message);
3870 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert);
3871 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_DEADLOCK);
3872 return;
3873 }
3874
3875 // Restrict the target position even more with the maximum travel option
3876 if (fabs(targetPosition - initialFocuserAbsPosition) > m_OpsFocusMechanics->focusMaxTravel->value())
3877 {
3878 int minTravelLimit = qMax(0.0, initialFocuserAbsPosition - m_OpsFocusMechanics->focusMaxTravel->value());
3879 int maxTravelLimit = qMin(absMotionMax, initialFocuserAbsPosition + m_OpsFocusMechanics->focusMaxTravel->value());
3880
3881 // In case we are asked to go below travel limit, but we are not there yet
3882 // let us go there and see the result before aborting
3883 if (fabs(currentPosition - minTravelLimit) > 10 && targetPosition < minTravelLimit)
3884 {
3885 targetPosition = minTravelLimit;
3886 }
3887 // Same for max travel
3888 else if (fabs(currentPosition - maxTravelLimit) > 10 && targetPosition > maxTravelLimit)
3889 {
3890 targetPosition = maxTravelLimit;
3891 }
3892 else
3893 {
3894 qCDebug(KSTARS_EKOS_FOCUS) << "targetPosition (" << targetPosition << ") - initHFRAbsPos ("
3895 << initialFocuserAbsPosition << ") exceeds maxTravel distance of " << m_OpsFocusMechanics->focusMaxTravel->value();
3896
3897 QString message = i18n("Maximum travel limit reached. Autofocus aborted.");
3898 appendLogText(message);
3899 KSNotification::event(QLatin1String("FocusFailed"), message, KSNotification::Focus, KSNotification::Alert);
3900 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_OOB);
3901 break;
3902 }
3903 }
3904
3905 // Get delta for next move
3906 lastDelta = (targetPosition - currentPosition);
3907
3908 qCDebug(KSTARS_EKOS_FOCUS) << "delta (targetPosition - currentPosition) " << lastDelta;
3909
3910 // Limit to Maximum permitted delta (Max Single Step Size)
3911 if (ignoreLimitedDelta == false)
3912 {
3913 double limitedDelta = qMax(-1.0 * m_OpsFocusMechanics->focusMaxSingleStep->value(),
3914 qMin(1.0 * m_OpsFocusMechanics->focusMaxSingleStep->value(), lastDelta));
3915 if (std::fabs(limitedDelta - lastDelta) > 0)
3916 {
3917 qCDebug(KSTARS_EKOS_FOCUS) << "Limited delta to maximum permitted single step " <<
3918 m_OpsFocusMechanics->focusMaxSingleStep->value();
3919 lastDelta = limitedDelta;
3920 }
3921 }
3922
3923 m_LastFocusDirection = (lastDelta > 0) ? FOCUS_OUT : FOCUS_IN;
3924 if (!changeFocus(lastDelta, false))
3925 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_NO_MOVE);
3926
3927 break;
3928 }
3929}
3930
3931void Focus::addPlotPosition(int pos, double value, bool plot)
3932{
3933 plot_position.append(pos);
3934 plot_value.append(value);
3935 if (plot)
3936 emit newHFRPlotPosition(static_cast<double>(pos), value, 1.0, false, pulseDuration);
3937}
3938
3939// Synchronises the plot_position and plot_value vectors with the data used by linearFocuser
3940// This keeps the 2 sets of data in sync for the Linear and Linear1Pass algorithms.
3941// For Iterative and Polynomial these vectors are built during the focusing cycle so nothing to do here
3942void Focus::updatePlotPosition()
3943{
3944 if (m_FocusAlgorithm == FOCUS_LINEAR1PASS)
3945 {
3946 QVector<int> positions;
3947 if (inScanStartPos || focusAdvisor->inFocusAdvisor())
3948 {
3949 // If we are inScanStartPos, or inFocusAdvisor then there is no focus data to save off
3950 plot_position.clear();
3951 plot_value.clear();
3952 plot_weight.clear();
3953 plot_outlier.clear();
3954 }
3955 else
3956 {
3957 linearFocuser->getPass1Measurements(&positions, &plot_value, &plot_weight, &plot_outlier);
3958 plot_position.clear();
3959 for (int i = 0; i < positions.count(); i++)
3960 plot_position.append(positions[i]);
3961
3962 // For L1P add in the solution datapoint (if there is one). Linear already has this included.
3963 if (linearFocuser && linearFocuser->isDone() && linearFocuser->solution() != -1)
3964 {
3965 plot_position.append(linearFocuser->solution());
3966 plot_value.append(linearFocuser->solutionValue());
3967 plot_weight.append(linearFocuser->solutionWeight());
3968 plot_outlier.append(false);
3969 }
3970 }
3971 }
3972 else if (m_FocusAlgorithm == FOCUS_LINEAR)
3973 {
3974 QVector<int> positions;
3975 linearFocuser->getMeasurements(&positions, &plot_value, &plot_weight);
3976 plot_position.clear();
3977 plot_outlier.clear();
3978 for (int i = 0; i < positions.count(); i++)
3979 {
3980 plot_position.append(positions[i]);
3981 // Outlier functionality isn't supported for Linear so make sure no outliers in the data
3982 plot_outlier.append(false);
3983 }
3984 }
3985 else
3986 {
3987 // Polynomial and Iterative don't support weights or outliers so fill in this data.
3988 for (int i = 0; i < plot_position.count(); i++)
3989 {
3990 plot_weight.append(1.0);
3991 plot_outlier.append(false);
3992 }
3993 }
3994}
3995
3996void Focus::autoFocusRel()
3997{
3998 static int noStarCount = 0;
3999 static double minHFR = 1e6;
4000 QString deltaTxt = QString("%1").arg(fabs(currentHFR - minHFR) * 100.0, 0, 'g', 2);
4001 QString minHFRText = QString("%1").arg(minHFR, 0, 'g', 3);
4002 QString HFRText = QString("%1").arg(currentHFR, 0, 'g', 3);
4003
4004 appendLogText(i18n("FITS received. HFR %1. Delta (%2%) Min HFR (%3)", HFRText, deltaTxt, minHFRText));
4005
4006 if (pulseDuration <= MINIMUM_PULSE_TIMER)
4007 {
4008 appendLogText(i18n("Autofocus failed to reach proper focus. Try adjusting the tolerance value."));
4009 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_TOLERANCE);
4010 return;
4011 }
4012
4013 // No stars detected, try to capture again
4014 if (currentHFR == INVALID_STAR_MEASURE)
4015 {
4016 if (noStarCount < MAX_RECAPTURE_RETRIES)
4017 {
4018 noStarCount++;
4019 appendLogText(i18n("No stars detected, capturing again..."));
4020 capture();
4021 return;
4022 }
4023 else if (m_FocusAlgorithm == FOCUS_LINEAR || m_FocusAlgorithm == FOCUS_LINEAR1PASS)
4024 {
4025 appendLogText(i18n("Failed to detect any stars at position %1. Continuing...", currentPosition));
4026 noStarCount = 0;
4027 }
4028 else if(!m_OpsFocusProcess->focusDonut->isChecked())
4029 {
4030 // Carry on for donut detection
4031 appendLogText(i18n("Failed to detect any stars. Reset frame and try again."));
4032 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_NO_STARS);
4033 return;
4034 }
4035 }
4036 else
4037 noStarCount = 0;
4038
4039 switch (m_LastFocusDirection)
4040 {
4041 case FOCUS_NONE:
4042 lastHFR = currentHFR;
4043 minHFR = 1e6;
4044 m_LastFocusDirection = FOCUS_IN;
4045 changeFocus(-pulseDuration, false);
4046 break;
4047
4048 case FOCUS_IN:
4049 case FOCUS_OUT:
4050 if (fabs(currentHFR - minHFR) < (m_OpsFocusProcess->focusTolerance->value() / 100.0) && HFRInc == 0)
4051 {
4052 completeFocusProcedure(Ekos::FOCUS_COMPLETE, Ekos::FOCUS_FAIL_NONE);
4053 }
4054 else if (currentHFR < lastHFR)
4055 {
4056 if (currentHFR < minHFR)
4057 minHFR = currentHFR;
4058
4059 lastHFR = currentHFR;
4060 changeFocus(m_LastFocusDirection == FOCUS_IN ? -pulseDuration : pulseDuration, false);
4061 HFRInc = 0;
4062 }
4063 else
4064 {
4065 //HFRInc++;
4066
4067 lastHFR = currentHFR;
4068
4069 HFRInc = 0;
4070
4071 pulseDuration *= 0.75;
4072
4073 if (!changeFocus(m_LastFocusDirection == FOCUS_IN ? pulseDuration : -pulseDuration, false))
4074 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_NO_MOVE);
4075
4076 // HFR getting worse so reverse direction
4077 m_LastFocusDirection = (m_LastFocusDirection == FOCUS_IN) ? FOCUS_OUT : FOCUS_IN;
4078 }
4079 break;
4080 }
4081}
4082
4083void Focus::autoFocusProcessPositionChange(IPState state)
4084{
4085 if (state == IPS_OK)
4086 {
4087 // Normally, if we are auto-focusing, after we move the focuser we capture an image.
4088 // However, the Linear algorithm, at the start of its passes, requires two
4089 // consecutive focuser moves--the first out further than we want, and a second
4090 // move back in, so that we eliminate backlash and are always moving in before a capture.
4091 if (focuserAdditionalMovement > 0)
4092 {
4093 int temp = focuserAdditionalMovement;
4094 focuserAdditionalMovement = 0;
4095
4096 qCDebug(KSTARS_EKOS_FOCUS) << QString("Undoing overscan extension. Moving back in by %1 ticks in %2s")
4097 .arg(temp).arg(m_OpsFocusMechanics->focusOverscanDelay->value());
4098
4099 QTimer::singleShot(m_OpsFocusMechanics->focusOverscanDelay->value() * 1000, this, [this, temp]()
4100 {
4101 if (!changeFocus(-temp, focuserAdditionalMovementUpdateDir))
4102 {
4103 appendLogText(i18n("Focuser error, check INDI panel."));
4104 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_NO_MOVE);
4105 }
4106 });
4107 }
4108 else if (inAutoFocus)
4109 {
4110 // Add a check that the current position matches the requested position (within a tolerance)
4111 if (m_FocusAlgorithm == FOCUS_LINEAR || m_FocusAlgorithm == FOCUS_LINEAR1PASS)
4112 if (abs(linearRequestedPosition - currentPosition) > m_OpsFocusMechanics->focusTicks->value())
4113 qCDebug(KSTARS_EKOS_FOCUS) << QString("Focus positioning error: requested position %1, current position %2")
4114 .arg(linearRequestedPosition).arg(currentPosition);
4115
4116 qCDebug(KSTARS_EKOS_FOCUS) << QString("Focus position reached at %1, starting capture in %2 seconds.").arg(
4117 currentPosition).arg(m_OpsFocusMechanics->focusSettleTime->value());
4118 // Adjust exposure if Donut Buster activated
4119 if (m_OpsFocusProcess->focusDonut->isChecked())
4120 donutTimeDilation();
4121 capture(m_OpsFocusMechanics->focusSettleTime->value());
4122 }
4123 }
4124 else if (state == IPS_ALERT)
4125 {
4126 appendLogText(i18n("Focuser error, check INDI panel."));
4127 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FOCUSER_ERROR);
4128 }
4129 else
4130 qCDebug(KSTARS_EKOS_FOCUS) <<
4131 QString("autoFocusProcessPositionChange called with state %1 (%2), focuserAdditionalMovement=%3, inAutoFocus=%4, m_captureInProgress=%5, currentPosition=%6")
4132 .arg(state).arg(pstateStr(state)).arg(focuserAdditionalMovement).arg(inAutoFocus).arg(m_captureInProgress)
4133 .arg(currentPosition);
4134}
4135
4136// This routine adjusts capture exposure during Autofocus depending on donut parameter settings
4137void Focus::donutTimeDilation()
4138{
4139 if (m_OpsFocusProcess->focusTimeDilation->value() == 1.0 || inScanStartPos || focusAdvisor->inFocusAdvisor())
4140 return;
4141
4142 // Get the max distance from focus to outer points
4143 const double centre = (m_OpsFocusMechanics->focusNumSteps->value() + 1.0) / 2.0;
4144 // Get the current step - treat the final
4145 const int currentStep = linearFocuser->currentStep() + 1;
4146 double distance;
4147 if (currentStep <= m_OpsFocusMechanics->focusNumSteps->value())
4148 distance = std::abs(centre - currentStep);
4149 else
4150 // Last step is always back to focus
4151 distance = 0.0;
4152 double multiplier = distance / (centre - 1.0) * m_OpsFocusProcess->focusTimeDilation->value();
4153 multiplier = std::max(multiplier, 1.0);
4154 const double exposure = multiplier * m_donutOrigExposure;
4155 focusExposure->setValue(exposure);
4156 qCDebug(KSTARS_EKOS_FOCUS) << "Donut time dilation for point " << currentStep << " from " << m_donutOrigExposure << " to "
4157 << exposure;
4158
4159}
4160
4161void Focus::updateProperty(INDI::Property prop)
4162{
4163 if (m_Focuser == nullptr || prop.getType() != INDI_NUMBER || prop.getDeviceName() != m_Focuser->getDeviceName())
4164 return;
4165
4166 auto nvp = prop.getNumber();
4167
4168 // Only process focus properties
4169 if (QString(nvp->getName()).contains("focus", Qt::CaseInsensitive) == false)
4170 return;
4171
4172 if (nvp->isNameMatch("FOCUS_BACKLASH_STEPS"))
4173 {
4174 m_OpsFocusMechanics->focusBacklash->setValue(nvp->np[0].value);
4175 return;
4176 }
4177
4178 if (nvp->isNameMatch("ABS_FOCUS_POSITION"))
4179 {
4180 if (m_DebugFocuser)
4181 {
4182 // Simulate focuser comms issues every 5 moves
4183 if (m_DebugFocuserCounter++ >= 10 && m_DebugFocuserCounter <= 14)
4184 {
4185 if (m_DebugFocuserCounter == 14)
4186 m_DebugFocuserCounter = 0;
4187 appendLogText(i18n("Simulate focuser comms failure..."));
4188 return;
4189 }
4190 }
4191
4192 m_FocusMotionTimer.stop();
4193 INumber *pos = IUFindNumber(nvp, "FOCUS_ABSOLUTE_POSITION");
4194 IPState newState = nvp->s;
4195
4196 // FIXME: We should check state validity, but some focusers do not care - make ignore an option!
4197 if (pos)
4198 {
4199 int newPosition = static_cast<int>(pos->value);
4200
4201 // Some absolute focuser constantly report the position without a state change.
4202 // Therefore we ignore it if both value and state are the same as last time.
4203 // HACK: This would shortcut the autofocus procedure reset, see completeFocusProcedure for the small hack
4204 if (currentPosition == newPosition && currentPositionState == newState)
4205 {
4206 if (logPositionAndState)
4207 {
4208 qCDebug(KSTARS_EKOS_FOCUS) << "Focuser position " << currentPosition << " and state:"
4209 << pstateStr(currentPositionState) << " unchanged";
4210 logPositionAndState = false;
4211 }
4212 return;
4213 }
4214 else
4215 {
4216 logPositionAndState = true;
4217 }
4218
4219 currentPositionState = newState;
4220
4221 if (currentPosition != newPosition)
4222 {
4223 currentPosition = newPosition;
4224 qCDebug(KSTARS_EKOS_FOCUS) << "Abs Focuser position changed to " << currentPosition << "State:" << pstateStr(
4225 currentPositionState);
4226 absTicksLabel->setText(QString::number(currentPosition));
4227 emit absolutePositionChanged(currentPosition);
4228 }
4229 }
4230 else
4231 qCDebug(KSTARS_EKOS_FOCUS) << "Can't access FOCUS_ABSOLUTE_POSITION. Current state:"
4232 << pstateStr(currentPositionState) << " New state:" << pstateStr(newState);
4233
4234 if (newState != IPS_OK)
4235 {
4236 if (inAutoFocus || inAdjustFocus || adaptFocus->inAdaptiveFocus())
4237 {
4238 // We had something back from the focuser but we're not done yet, so
4239 // restart motion timer in case focuser gets stuck.
4240 qCDebug(KSTARS_EKOS_FOCUS) << "Restarting focus motion timer, state " << pstateStr(newState);
4241 m_FocusMotionTimer.start();
4242 }
4243 }
4244 else
4245 {
4246 // Systematically reset UI when focuser finishes moving
4247 if (focuserAdditionalMovement == 0)
4248 resetButtons();
4249
4250 if (inAdjustFocus)
4251 {
4252 if (focuserAdditionalMovement == 0)
4253 {
4254 inAdjustFocus = false;
4255 emit focusPositionAdjusted();
4256 return;
4257 }
4258 }
4259
4260 if (adaptFocus->inAdaptiveFocus())
4261 {
4262 if (focuserAdditionalMovement == 0)
4263 {
4264 adaptFocus->adaptiveFocusAdmin(currentPosition, true, true);
4265 return;
4266 }
4267 }
4268
4269 if (m_RestartState == RESTART_NOW && status() != Ekos::FOCUS_ABORTED)
4270 {
4271 if (focuserAdditionalMovement == 0)
4272 {
4273 m_RestartState = RESTART_NONE;
4274 m_AFRerun = true;
4275 inAutoFocus = inBuildOffsets = inAdjustFocus = inScanStartPos = false;
4276 adaptFocus->setInAdaptiveFocus(false);
4277 focusAdvisor->setInFocusAdvisor(false);
4278 appendLogText(i18n("Restarting autofocus process..."));
4279 runAutoFocus(m_AutofocusReason, m_AutofocusReasonInfo);
4280 return;
4281 }
4282 }
4283 else if (m_RestartState == RESTART_ABORT && focuserAdditionalMovement == 0)
4284 {
4285 // We are trying to abort an autofocus run
4286 // This event means that the focuser has been reset and arrived at its starting point
4287 // so we can finish processing the abort. Set inAutoFocus to avoid repeating
4288 // processing already done in completeFocusProcedure
4289 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FORCE_ABORT);
4290 m_RestartState = RESTART_NONE;
4291 inAutoFocus = inBuildOffsets = inAdjustFocus = inScanStartPos = false;
4292 adaptFocus->setInAdaptiveFocus(false);
4293 focusAdvisor->setInFocusAdvisor(false);
4294 return;
4295 }
4296 }
4297
4298 if (canAbsMove)
4299 autoFocusProcessPositionChange(newState);
4300 else if (newState == IPS_ALERT)
4301 appendLogText(i18n("Focuser error, check INDI panel."));
4302 return;
4303 }
4304
4305 if (canAbsMove)
4306 return;
4307
4308 if (nvp->isNameMatch("manualfocusdrive"))
4309 {
4310 if (m_DebugFocuser)
4311 {
4312 // Simulate focuser comms issues every 5 moves
4313 if (m_DebugFocuserCounter++ >= 10 && m_DebugFocuserCounter <= 14)
4314 {
4315 if (m_DebugFocuserCounter == 14)
4316 m_DebugFocuserCounter = 0;
4317 appendLogText(i18n("Simulate focuser comms failure..."));
4318 return;
4319 }
4320 }
4321
4322 m_FocusMotionTimer.stop();
4323
4324 INumber *pos = IUFindNumber(nvp, "manualfocusdrive");
4325 IPState newState = nvp->s;
4326 if (pos && newState == IPS_OK)
4327 {
4328 if (focuserAdditionalMovement == 0)
4329 resetButtons();
4330
4331 currentPosition += pos->value;
4332 absTicksLabel->setText(QString::number(static_cast<int>(currentPosition)));
4333 emit absolutePositionChanged(currentPosition);
4334 }
4335
4336 if (inAdjustFocus && newState == IPS_OK)
4337 {
4338 if (focuserAdditionalMovement == 0)
4339 {
4340 inAdjustFocus = false;
4341 emit focusPositionAdjusted();
4342 return;
4343 }
4344 }
4345
4346 if (adaptFocus->inAdaptiveFocus() && newState == IPS_OK)
4347 {
4348 if (focuserAdditionalMovement == 0)
4349 {
4350 adaptFocus->adaptiveFocusAdmin(currentPosition, true, true);
4351 return;
4352 }
4353 }
4354
4355 // restart if focus movement has finished
4356 if (m_RestartState == RESTART_NOW && newState == IPS_OK && status() != Ekos::FOCUS_ABORTED)
4357 {
4358 if (focuserAdditionalMovement == 0)
4359 {
4360 m_RestartState = RESTART_NONE;
4361 m_AFRerun = true;
4362 inAutoFocus = inBuildOffsets = inAdjustFocus = inScanStartPos = false;
4363 adaptFocus->setInAdaptiveFocus(false);
4364 focusAdvisor->setInFocusAdvisor(false);
4365 appendLogText(i18n("Restarting autofocus process..."));
4366 runAutoFocus(m_AutofocusReason, m_AutofocusReasonInfo);
4367 return;
4368 }
4369 }
4370 else if (m_RestartState == RESTART_ABORT && newState == IPS_OK)
4371 {
4372 // Abort the autofocus run now the focuser has finished moving to its start position
4373 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FORCE_ABORT);
4374 m_RestartState = RESTART_NONE;
4375 inAutoFocus = inBuildOffsets = inAdjustFocus = inScanStartPos = false;
4376 adaptFocus->setInAdaptiveFocus(false);
4377 focusAdvisor->setInFocusAdvisor(false);
4378 return;
4379 }
4380
4381 if (canRelMove)
4382 autoFocusProcessPositionChange(newState);
4383 else if (newState == IPS_ALERT)
4384 appendLogText(i18n("Focuser error, check INDI panel."));
4385
4386 return;
4387 }
4388
4389 if (nvp->isNameMatch("REL_FOCUS_POSITION"))
4390 {
4391 m_FocusMotionTimer.stop();
4392
4393 INumber *pos = IUFindNumber(nvp, "FOCUS_RELATIVE_POSITION");
4394 IPState newState = nvp->s;
4395 if (pos && newState == IPS_OK)
4396 {
4397 if (focuserAdditionalMovement == 0)
4398 resetButtons();
4399
4400 currentPosition += pos->value * (m_LastFocusDirection == FOCUS_IN ? -1 : 1);
4401 qCDebug(KSTARS_EKOS_FOCUS)
4402 << QString("Rel Focuser position moved %1 by %2 to %3")
4403 .arg((m_LastFocusDirection == FOCUS_IN) ? "in" : "out").arg(pos->value).arg(currentPosition);
4404 absTicksLabel->setText(QString::number(static_cast<int>(currentPosition)));
4405 emit absolutePositionChanged(currentPosition);
4406 }
4407
4408 if (inAdjustFocus && newState == IPS_OK)
4409 {
4410 if (focuserAdditionalMovement == 0)
4411 {
4412 inAdjustFocus = false;
4413 emit focusPositionAdjusted();
4414 return;
4415 }
4416 }
4417
4418 if (adaptFocus->inAdaptiveFocus() && newState == IPS_OK)
4419 {
4420 if (focuserAdditionalMovement == 0)
4421 {
4422 adaptFocus->adaptiveFocusAdmin(currentPosition, true, true);
4423 return;
4424 }
4425 }
4426
4427 // restart if focus movement has finished
4428 if (m_RestartState == RESTART_NOW && newState == IPS_OK && status() != Ekos::FOCUS_ABORTED)
4429 {
4430 if (focuserAdditionalMovement == 0)
4431 {
4432 m_RestartState = RESTART_NONE;
4433 m_AFRerun = true;
4434 inAutoFocus = inBuildOffsets = inAdjustFocus = inScanStartPos = false;
4435 adaptFocus->setInAdaptiveFocus(false);
4436 focusAdvisor->setInFocusAdvisor(false);
4437 appendLogText(i18n("Restarting autofocus process..."));
4438 runAutoFocus(m_AutofocusReason, m_AutofocusReasonInfo);
4439 return;
4440 }
4441 }
4442 else if (m_RestartState == RESTART_ABORT && newState == IPS_OK)
4443 {
4444 // Abort the autofocus run now the focuser has finished moving to its start position
4445 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FORCE_ABORT);
4446 m_RestartState = RESTART_NONE;
4447 inAutoFocus = inBuildOffsets = inAdjustFocus = inScanStartPos = false;
4448 adaptFocus->setInAdaptiveFocus(false);
4449 focusAdvisor->setInFocusAdvisor(false);
4450 return;
4451 }
4452
4453 if (canRelMove)
4454 autoFocusProcessPositionChange(newState);
4455 else if (newState == IPS_ALERT)
4456 appendLogText(i18n("Focuser error, check INDI panel."));
4457
4458 return;
4459 }
4460
4461 if (canRelMove)
4462 return;
4463
4464 if (nvp->isNameMatch("FOCUS_TIMER"))
4465 {
4466 IPState newState = nvp->s;
4467 m_FocusMotionTimer.stop();
4468 // restart if focus movement has finished
4469 if (m_RestartState == RESTART_NOW && newState == IPS_OK && status() != Ekos::FOCUS_ABORTED)
4470 {
4471 if (focuserAdditionalMovement == 0)
4472 {
4473 m_RestartState = RESTART_NONE;
4474 m_AFRerun = true;
4475 inAutoFocus = inBuildOffsets = inAdjustFocus = inScanStartPos = false;
4476 adaptFocus->setInAdaptiveFocus(false);
4477 focusAdvisor->setInFocusAdvisor(false);
4478 appendLogText(i18n("Restarting autofocus process..."));
4479 runAutoFocus(m_AutofocusReason, m_AutofocusReasonInfo);
4480 return;
4481 }
4482 }
4483 else if (m_RestartState == RESTART_ABORT && newState == IPS_OK)
4484 {
4485 // Abort the autofocus run now the focuser has finished moving to its start position
4486 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FORCE_ABORT);
4487 m_RestartState = RESTART_NONE;
4488 inAutoFocus = inBuildOffsets = inAdjustFocus = inScanStartPos = false;
4489 adaptFocus->setInAdaptiveFocus(false);
4490 focusAdvisor->setInFocusAdvisor(false);
4491 return;
4492 }
4493
4494 if (canAbsMove == false && canRelMove == false)
4495 {
4496 // Used by the linear focus algorithm. Ignored if that's not in use for the timer-focuser.
4497 INumber *pos = IUFindNumber(nvp, "FOCUS_TIMER_VALUE");
4498 if (pos)
4499 {
4500 currentPosition += pos->value * (m_LastFocusDirection == FOCUS_IN ? -1 : 1);
4501 qCDebug(KSTARS_EKOS_FOCUS)
4502 << QString("Timer Focuser position moved %1 by %2 to %3")
4503 .arg((m_LastFocusDirection == FOCUS_IN) ? "in" : "out").arg(pos->value).arg(currentPosition);
4504 }
4505
4506 if (pos && newState == IPS_OK && focuserAdditionalMovement == 0)
4507 resetButtons();
4508
4509 if (inAdjustFocus && newState == IPS_OK)
4510 {
4511 if (focuserAdditionalMovement == 0)
4512 {
4513 inAdjustFocus = false;
4514 emit focusPositionAdjusted();
4515 return;
4516 }
4517 }
4518
4519 if (adaptFocus->inAdaptiveFocus() && newState == IPS_OK)
4520 {
4521 if (focuserAdditionalMovement == 0)
4522 {
4523 adaptFocus->adaptiveFocusAdmin(true, true, true);
4524 return;
4525 }
4526 }
4527
4528 autoFocusProcessPositionChange(newState);
4529 }
4530 else if (newState == IPS_ALERT)
4531 appendLogText(i18n("Focuser error, check INDI panel."));
4532
4533 return;
4534 }
4535}
4536
4537void Focus::appendLogText(const QString &text)
4538{
4539 const QString logtext(m_Focuser == nullptr ? text : QString("[%1] %2").arg(m_Focuser->getDeviceName()).arg(text));
4540 emit newLog(logtext);
4541}
4542
4543void Focus::appendFocusLogText(const QString &text)
4544{
4545 if (Options::focusLogging())
4546 {
4547 const QString logtext(m_Focuser == nullptr ? text : QString("[%1] %2").arg(m_Focuser->getDeviceName()).arg(text));
4548 emit newFocusLog(logtext);
4549 }
4550}
4551
4552void Focus::startFraming()
4553{
4554 if (m_Camera == nullptr)
4555 {
4556 appendLogText(i18n("No CCD connected."));
4557 return;
4558 }
4559
4560 waitStarSelectTimer.stop();
4561
4562 inFocusLoop = true;
4563 starMeasureFrames.clear();
4564
4565 clearDataPoints();
4566
4567 //emit statusUpdated(true);
4568 setState(Ekos::FOCUS_FRAMING);
4569
4570 resetButtons();
4571
4572 appendLogText(i18n("Starting continuous exposure..."));
4573
4574 capture();
4575}
4576
4577void Focus::resetButtons()
4578{
4579 if (inFocusLoop)
4580 {
4581 startFocusB->setEnabled(false);
4582 startAbInsB->setEnabled(false);
4583 startLoopB->setEnabled(false);
4584 focusOutB->setEnabled(true);
4585 focusInB->setEnabled(true);
4586 startGotoB->setEnabled(canAbsMove);
4587 stopFocusB->setEnabled(true);
4588 captureB->setEnabled(false);
4589 opticalTrainCombo->setEnabled(false);
4590 trainB->setEnabled(false);
4591 return;
4592 }
4593
4594 if (inAutoFocus)
4595 {
4596 // During an Autofocus run we need to disable input widgets to stop the user changing them
4597 // We need to disable the widgets currently enabled and save a QVector of these to be
4598 // reinstated once the AF run completes.
4599 // Certain widgets (highlighted below) have the isEnabled() property used in the code to
4600 // determine functionality. So the isEnabled state for these is saved off before the
4601 // interface is disabled and these saved states used to control the code path and preserve
4602 // functionality.
4603 // Since this function can be called several times only load up widgets once
4604 if (disabledWidgets.empty())
4605 {
4606 AFDisable(trainLabel, false);
4607 AFDisable(opticalTrainCombo, false);
4608 AFDisable(trainB, false);
4609 AFDisable(focuserGroup, true);
4610 AFDisable(clearDataB, false);
4611
4612 // In the ccdGroup save the enabled state of Gain and ISO
4613 m_FocusGainAFEnabled = focusGain->isEnabled();
4614 m_FocusISOAFEnabled = focusISO->isEnabled();
4615 AFDisable(ccdGroup, false);
4616
4617 AFDisable(toolsGroup, false);
4618
4619 // Save the enabled state of SubFrame
4620 m_FocusSubFrameAFEnabled = m_OpsFocusSettings->focusSubFrame->isEnabled();
4621
4622 // Disable parameters and tools to prevent changes while Autofocus is running
4623 AFDisable(m_OpsFocusSettings, false);
4624 AFDisable(m_OpsFocusProcess, false);
4625 AFDisable(m_OpsFocusMechanics, false);
4626 AFDisable(focusAdvisor->focusAdvGroupBox, false);
4627 AFDisable(m_CFZDialog, false);
4628
4629 // Enable the "stop" button so the user can abort an AF run
4630 stopFocusB->setEnabled(true);
4631 }
4632 return;
4633 }
4634
4635 // Restore widgets that were disabled when Autofocus started
4636 for(int i = 0 ; i < disabledWidgets.size() ; i++)
4637 disabledWidgets[i]->setEnabled(true);
4638 disabledWidgets.clear();
4639
4640 bool cameraConnected = m_Camera && m_Camera->isConnected();
4641 bool enableCaptureButtons = cameraConnected && (!m_captureInProgress && !m_starDetectInProgress);
4642
4643 captureB->setEnabled(enableCaptureButtons);
4644 resetFrameB->setEnabled(enableCaptureButtons);
4645 startLoopB->setEnabled(enableCaptureButtons);
4646
4647 if (cameraConnected)
4648 {
4649 ccdGroup->setEnabled(true);
4650 toolsGroup->setEnabled(true);
4651 }
4652 else
4653 {
4654 ccdGroup->setEnabled(false);
4655 toolsGroup->setEnabled(false);
4656 }
4657
4658 if (m_Focuser && m_Focuser->isConnected())
4659 {
4660 focusOutB->setEnabled(true);
4661 focusInB->setEnabled(true);
4662 startFocusB->setEnabled(cameraConnected && m_FocusType == FOCUS_AUTO);
4663 startAbInsB->setEnabled(canAbInsStart());
4664 stopFocusB->setEnabled(!enableCaptureButtons);
4665 startGotoB->setEnabled(canAbsMove);
4666 stopGotoB->setEnabled(true);
4667 focuserGroup->setEnabled(true);
4668 }
4669 else
4670 {
4671 focusOutB->setEnabled(false);
4672 focusInB->setEnabled(false);
4673 startFocusB->setEnabled(false);
4674 startAbInsB->setEnabled(false);
4675 stopFocusB->setEnabled(false);
4676 startGotoB->setEnabled(false);
4677 stopGotoB->setEnabled(false);
4678 focuserGroup->setEnabled(false);
4679 }
4680}
4681
4682// Return whether the Aberration Inspector Start button should be enabled. The pre-requisties are:
4683// - Absolute position focuser
4684// - Mosaic Mask is on
4685// - Algorithm is LINEAR 1 PASS
4686// - Curve fit is not Quadratic (which uses an old curve fitting approach not enabled for Aberration Inspector)
4687bool Focus::canAbInsStart()
4688{
4689 return canAbsMove && m_FocusAlgorithm == FOCUS_LINEAR1PASS && m_currentImageMask == FOCUS_MASK_MOSAIC && m_CurveFit != CurveFitting::FOCUS_QUADRATIC;
4690}
4691
4692// Disable input widgets during an Autofocus run. Keep a record so after the AF run, widgets can be re-enabled
4693void Focus::AFDisable(QWidget * widget, const bool children)
4694{
4695 if (children)
4696 {
4697 // The parent widget has been passed in so disable any enabled child widgets
4698 for(auto *wid : widget->findChildren<QWidget *>())
4699 {
4700 if (wid->isEnabled())
4701 {
4702 wid->setEnabled(false);
4703 disabledWidgets.push_back(wid);
4704 }
4705 }
4706
4707 }
4708 else if (widget->isEnabled())
4709 {
4710 // Base level widget or group of widgets, so just disable the passed what was passed in
4711 widget->setEnabled(false);
4712 disabledWidgets.push_back(widget);
4713 }
4714}
4715
4716bool Focus::isFocusGainEnabled()
4717{
4718 return (inAutoFocus) ? m_FocusGainAFEnabled : focusGain->isEnabled();
4719}
4720
4721bool Focus::isFocusISOEnabled()
4722{
4723 return (inAutoFocus) ? m_FocusISOAFEnabled : focusISO->isEnabled();
4724}
4725
4726bool Focus::isFocusSubFrameEnabled()
4727{
4728 return (inAutoFocus) ? m_FocusSubFrameAFEnabled : m_OpsFocusSettings->focusSubFrame->isEnabled();
4729}
4730
4731void Focus::updateBoxSize(int value)
4732{
4733 if (m_Camera == nullptr)
4734 return;
4735
4736 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
4737
4738 if (targetChip == nullptr)
4739 return;
4740
4741 int subBinX, subBinY;
4742 targetChip->getBinning(&subBinX, &subBinY);
4743
4744 QRect trackBox = m_FocusView->getTrackingBox();
4745 QPoint center(trackBox.x() + (trackBox.width() / 2), trackBox.y() + (trackBox.height() / 2));
4746
4747 trackBox =
4748 QRect(center.x() - value / (2 * subBinX), center.y() - value / (2 * subBinY), value / subBinX, value / subBinY);
4749
4750 m_FocusView->setTrackingBox(trackBox);
4751}
4752
4753void Focus::selectFocusStarFraction(double x, double y)
4754{
4755 if (m_ImageData.isNull())
4756 return;
4757
4758 focusStarSelected(x * m_ImageData->width(), y * m_ImageData->height());
4759 // Focus view timer takes 50ms second to update, so let's emit afterwards.
4760 QTimer::singleShot(250, this, [this]()
4761 {
4762 emit newImage(m_FocusView);
4763 });
4764}
4765
4766void Focus::focusStarSelected(int x, int y)
4767{
4768 if (state() == Ekos::FOCUS_PROGRESS)
4769 return;
4770
4771 if (subFramed == false)
4772 {
4773 rememberStarCenter.setX(x);
4774 rememberStarCenter.setY(y);
4775 }
4776
4777 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
4778
4779 int subBinX, subBinY;
4780 targetChip->getBinning(&subBinX, &subBinY);
4781
4782 // If binning was changed outside of the focus module, recapture
4783 if (subBinX != (focusBinning->currentIndex() + 1))
4784 {
4785 capture();
4786 return;
4787 }
4788
4789 int offset = (static_cast<double>(m_OpsFocusSettings->focusBoxSize->value()) / subBinX) * 1.5;
4790
4791 QRect starRect;
4792
4793 bool squareMovedOutside = false;
4794
4795 if (subFramed == false && m_OpsFocusSettings->focusSubFrame->isChecked() && targetChip->canSubframe())
4796 {
4797 // JEE - for now just save the x, y of the mouse click. Should check that its within bounds of the chip.
4798 if (!isStarMeasureStarBased())
4799 {
4800 starCenter.setX(x);
4801 starCenter.setY(y);
4802 }
4803
4804 int minX, maxX, minY, maxY, minW, maxW, minH, maxH; //, fx,fy,fw,fh;
4805
4806 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
4807 //targetChip->getFrame(&fx, &fy, &fw, &fy);
4808
4809 x = (x - offset) * subBinX;
4810 y = (y - offset) * subBinY;
4811 int w = offset * 2 * subBinX;
4812 int h = offset * 2 * subBinY;
4813
4814 if (x < minX)
4815 x = minX;
4816 if (y < minY)
4817 y = minY;
4818 if ((x + w) > maxW)
4819 w = maxW - x;
4820 if ((y + h) > maxH)
4821 h = maxH - y;
4822
4823 //fx += x;
4824 //fy += y;
4825 //fw = w;
4826 //fh = h;
4827
4828 //targetChip->setFocusFrame(fx, fy, fw, fh);
4829 //frameModified=true;
4830
4831 QVariantMap settings = frameSettings[targetChip];
4832 settings["x"] = x;
4833 settings["y"] = y;
4834 settings["w"] = w;
4835 settings["h"] = h;
4836 settings["binx"] = subBinX;
4837 settings["biny"] = subBinY;
4838
4839 frameSettings[targetChip] = settings;
4840
4841 subFramed = true;
4842
4843 qCDebug(KSTARS_EKOS_FOCUS) << "Frame is subframed. X:" << x << "Y:" << y << "W:" << w << "H:" << h << "binX:" << subBinX <<
4844 "binY:" << subBinY;
4845
4846 m_FocusView->setFirstLoad(true);
4847
4848 capture();
4849
4850 if (isStarMeasureStarBased())
4851 {
4852 starCenter.setX(w / (2 * subBinX));
4853 starCenter.setY(h / (2 * subBinY));
4854 }
4855 }
4856 else
4857 {
4858 // JEE Probbaly need to call syncTrackingBoxPosition() for non-star based measures
4859 //starRect = QRect(x-focusBoxSize->value()/(subBinX*2), y-focusBoxSize->value()/(subBinY*2), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY);
4860 double dist = sqrt((starCenter.x() - x) * (starCenter.x() - x) + (starCenter.y() - y) * (starCenter.y() - y));
4861
4862 squareMovedOutside = (dist > (static_cast<double>(m_OpsFocusSettings->focusBoxSize->value()) / subBinX));
4863 starCenter.setX(x);
4864 starCenter.setY(y);
4865 //starRect = QRect( starCenter.x()-focusBoxSize->value()/(2*subBinX), starCenter.y()-focusBoxSize->value()/(2*subBinY), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY);
4866 starRect = QRect(starCenter.x() - m_OpsFocusSettings->focusBoxSize->value() / (2 * subBinX),
4867 starCenter.y() - m_OpsFocusSettings->focusBoxSize->value() / (2 * subBinY),
4868 m_OpsFocusSettings->focusBoxSize->value() / subBinX,
4869 m_OpsFocusSettings->focusBoxSize->value() / subBinY);
4870 m_FocusView->setTrackingBox(starRect);
4871 }
4872
4873 starsHFR.clear();
4874
4875 starCenter.setZ(subBinX);
4876
4877 if (squareMovedOutside && inAutoFocus == false && m_OpsFocusSettings->focusAutoStarEnabled->isChecked())
4878 {
4879 m_OpsFocusSettings->focusAutoStarEnabled->blockSignals(true);
4880 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(false);
4881 m_OpsFocusSettings->focusAutoStarEnabled->blockSignals(false);
4882 appendLogText(i18n("Disabling Auto Star Selection as star selection box was moved manually."));
4883 starSelected = false;
4884 }
4885 else if (starSelected == false)
4886 {
4887 if (isStarMeasureStarBased())
4888 appendLogText(i18n("Focus star is selected."));
4889 else
4890 appendLogText(i18n("Region of interest is selected."));
4891
4892 starSelected = true;
4893 capture();
4894 }
4895
4896 waitStarSelectTimer.stop();
4897 FocusState nextState = inAutoFocus ? FOCUS_PROGRESS : FOCUS_IDLE;
4898 if (nextState != state())
4899 {
4900 setState(nextState);
4901 }
4902}
4903
4904void Focus::checkFocus(double requiredHFR)
4905{
4906 if (inAutoFocus || inAdjustFocus || adaptFocus->inAdaptiveFocus())
4907 {
4908 QString str;
4909 if (inAutoFocus)
4910 str = i18n("Autofocus");
4911 else if (inAdjustFocus)
4912 str = i18n("Adjust Focus");
4913 else
4914 str = i18n("Adaptive Focus");
4915
4916 if (++m_StartRetries < MAXIMUM_RESTART_ITERATIONS)
4917 {
4918 appendLogText(i18n("Check focus request - Waiting 10sec for %1 to complete.", str));
4919 QTimer::singleShot(10 * 1000, this, [this, requiredHFR]()
4920 {
4921 checkFocus(requiredHFR);
4922 });
4923 return;
4924 }
4925
4926 appendLogText(i18n("Discarding Check Focus request - %1 in progress.", str));
4927 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_INTERNAL);
4928 return;
4929 }
4930
4931 m_StartRetries = 0;
4932
4933 if (inFocusLoop || inBuildOffsets)
4934 {
4935 QString str = inFocusLoop ? "Focus Looping" : " Build Offsets";
4936 qCDebug(KSTARS_EKOS_FOCUS) << QString("Check Focus rejected, %1 is already running.").arg(str);
4937 }
4938 else if (!isStarMeasureStarBased())
4939 appendLogText(i18n("Check Focus ignored - Star Measure must be star based"));
4940 else
4941 {
4942 qCDebug(KSTARS_EKOS_FOCUS) << "Check Focus requested with minimum required HFR" << requiredHFR;
4943 minimumRequiredHFR = requiredHFR;
4944 appendLogText(i18n("Capturing to check HFR..."));
4945 capture();
4946 }
4947}
4948
4949void Focus::toggleSubframe(bool enable)
4950{
4951 if (enable == false)
4952 resetFrame();
4953
4954 m_FocusView->setTrackingBoxEnabled(enable);
4955 starSelected = false;
4956 starCenter = QVector3D();
4957
4958 if (enable)
4959 // sub frame focusing - disable focus image mask
4960 m_OpsFocusSettings->focusNoMaskRB->setChecked(true);
4961
4962 // update image mask controls
4963 selectImageMask();
4964 // enable focus mask selection if full field is selected
4965 m_OpsFocusSettings->focusRingMaskRB->setEnabled(!enable);
4966 m_OpsFocusSettings->focusMosaicMaskRB->setEnabled(!enable);
4967
4968 setUseWeights();
4969 setDenoise();
4970 setAutoStarAndBox();
4971}
4972
4973// Set the useWeights widget based on various other user selected parameters
4974// 1. weights are only available with the LM solver used by Hyperbola, Parabola & Gaussian
4975// 2. weights are only used for multiple stars so only if full frame is selected
4976// 3. weights are only used for star measures involving multiple star measures: HFR, HFR_ADJ and FWHM
4977void Focus::setUseWeights()
4978{
4979 if (m_CurveFit == CurveFitting::FOCUS_QUADRATIC || !m_OpsFocusSettings->focusUseFullField->isChecked()
4980 || m_StarMeasure == FOCUS_STAR_NUM_STARS || m_StarMeasure == FOCUS_STAR_FOURIER_POWER ||
4981 m_StarMeasure == FOCUS_STAR_STDDEV || m_StarMeasure == FOCUS_STAR_SOBEL ||
4982 m_StarMeasure == FOCUS_STAR_LAPLASSIAN || m_StarMeasure == FOCUS_STAR_CANNY)
4983 {
4984 m_OpsFocusProcess->focusUseWeights->setEnabled(false);
4985 m_OpsFocusProcess->focusUseWeights->setChecked(false);
4986 }
4987 else
4988 m_OpsFocusProcess->focusUseWeights->setEnabled(true);
4989
4990}
4991
4992// Set the focusDenoise widget based on various other user selected parameters
4993// 1. denoise is only availabel for the openCV star measures
4994// 2. denoise and useWeights are mutually exclusive
4995void Focus::setDenoise()
4996{
4997 if (m_StarMeasure == FOCUS_STAR_STDDEV || m_StarMeasure == FOCUS_STAR_SOBEL ||
4998 m_StarMeasure == FOCUS_STAR_LAPLASSIAN || m_StarMeasure == FOCUS_STAR_CANNY)
4999 m_OpsFocusProcess->focusDenoise->setEnabled(true);
5000 else
5001 {
5002 m_OpsFocusProcess->focusDenoise->setEnabled(false);
5003 m_OpsFocusProcess->focusDenoise->setChecked(false);
5004 }
5005}
5006
5007// Set the setDonutBuster widget based on various other user selected parameters
5008// 1. Donut Buster is only available for algorithm: Linear 1 Pass
5009// 2. Donut Buster is available for measures: HFR, HFR Adj and FWHM
5010// 3. Donut Buster is available for walks: Fixed and CFZ Shuffle
5011// 4. Donut Buster is available for curves fits: Hyperbola and Parabola
5012void Focus::setDonutBuster()
5013{
5014 if (m_FocusAlgorithm != FOCUS_LINEAR1PASS)
5015 {
5016 m_OpsFocusProcess->focusDonut->hide();
5017 m_OpsFocusProcess->focusDonut->setEnabled(false);
5018 m_OpsFocusProcess->focusDonut->setChecked(false);
5019 }
5020 else
5021 {
5022 m_OpsFocusProcess->focusDonut->show();
5023 if ((m_StarMeasure == FOCUS_STAR_HFR || m_StarMeasure == FOCUS_STAR_HFR_ADJ || m_StarMeasure == FOCUS_STAR_FWHM) &&
5024 (m_FocusWalk == FOCUS_WALK_FIXED_STEPS || m_FocusWalk == FOCUS_WALK_CFZ_SHUFFLE) &&
5025 (m_CurveFit != CurveFitting::FOCUS_QUADRATIC))
5026 m_OpsFocusProcess->focusDonut->setEnabled(true);
5027 else
5028 {
5029 m_OpsFocusProcess->focusDonut->setEnabled(false);
5030 m_OpsFocusProcess->focusDonut->setChecked(false);
5031 }
5032 }
5033}
5034
5035void Focus::setExposure(double value)
5036{
5037 focusExposure->setValue(value);
5038}
5039
5040void Focus::setBinning(int subBinX, int subBinY)
5041{
5042 INDI_UNUSED(subBinY);
5043 focusBinning->setCurrentIndex(subBinX - 1);
5044}
5045
5046void Focus::setAutoStarEnabled(bool enable)
5047{
5048 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(enable);
5049}
5050
5051void Focus::setAutoSubFrameEnabled(bool enable)
5052{
5053 m_OpsFocusSettings->focusSubFrame->setChecked(enable);
5054}
5055
5056void Focus::setAutoFocusParameters(int boxSize, int stepSize, int maxTravel, double tolerance)
5057{
5058 m_OpsFocusSettings->focusBoxSize->setValue(boxSize);
5059 m_OpsFocusMechanics->focusTicks->setValue(stepSize);
5060 m_OpsFocusMechanics->focusMaxTravel->setValue(maxTravel);
5061 m_OpsFocusProcess->focusTolerance->setValue(tolerance);
5062}
5063
5064void Focus::checkAutoStarTimeout()
5065{
5066 //if (starSelected == false && inAutoFocus)
5067 if (starCenter.isNull() && (inAutoFocus || minimumRequiredHFR > 0))
5068 {
5069 if (inAutoFocus)
5070 {
5071 if (rememberStarCenter.isNull() == false)
5072 {
5073 focusStarSelected(rememberStarCenter.x(), rememberStarCenter.y());
5074 appendLogText(i18n("No star was selected. Using last known position..."));
5075 return;
5076 }
5077 }
5078
5079 initialFocuserAbsPosition = -1;
5080 appendLogText(i18n("No star was selected. Aborting..."));
5081 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_NO_STARS);
5082 }
5083 else if (state() == FOCUS_WAITING)
5084 setState(FOCUS_IDLE);
5085}
5086
5087void Focus::setAbsoluteFocusTicks()
5088{
5089 if (absTicksSpin->value() == currentPosition)
5090 {
5091 appendLogText(i18n("Focuser already at %1...", currentPosition));
5092 return;
5093 }
5094 focusInB->setEnabled(false);
5095 focusOutB->setEnabled(false);
5096 startGotoB->setEnabled(false);
5097 if (!changeFocus(absTicksSpin->value() - currentPosition))
5098 qCDebug(KSTARS_EKOS_FOCUS) << "setAbsoluteFocusTicks unable to move focuser.";
5099}
5100
5101void Focus::syncTrackingBoxPosition()
5102{
5103 if (m_OpsFocusSettings->focusUseFullField->isChecked() || starCenter.isNull())
5104 return;
5105
5106 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
5107 Q_ASSERT(targetChip);
5108
5109 int subBinX = 1, subBinY = 1;
5110 targetChip->getBinning(&subBinX, &subBinY);
5111
5112 double boxSize = m_OpsFocusSettings->focusBoxSize->value();
5113 int x, y, w, h;
5114 targetChip->getFrame(&x, &y, &w, &h);
5115 // If box size is larger than image size, set it to lower index
5116 if (boxSize / subBinX >= w || boxSize / subBinY >= h)
5117 {
5118 m_OpsFocusSettings->focusBoxSize->setValue((boxSize / subBinX >= w) ? w : h);
5119 return;
5120 }
5121
5122 // If binning changed, update coords accordingly
5123 if (subBinX != starCenter.z())
5124 {
5125 if (starCenter.z() > 0)
5126 {
5127 starCenter.setX(starCenter.x() * (starCenter.z() / subBinX));
5128 starCenter.setY(starCenter.y() * (starCenter.z() / subBinY));
5129 }
5130
5131 starCenter.setZ(subBinX);
5132 }
5133
5134 QRect starRect = QRect();
5135 if (isStarMeasureStarBased())
5136 starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY),
5137 boxSize / subBinX, boxSize / subBinY);
5138 else
5139 {
5140 // get frame parameters and ensure tracking box remains within limits
5141 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
5142 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
5143 int boxTLX = starCenter.x() - boxSize / (2 * subBinX);
5144 boxTLX = std::max(boxTLX, minX);
5145 int boxTLY = starCenter.y() - boxSize / (2 * subBinY);
5146 boxTLY = std::max(boxTLY, minY);
5147 const int boxBRX = boxTLX + (boxSize / subBinX);
5148 if (boxBRX > maxX)
5149 boxTLX -= boxBRX - maxX;
5150 const int boxBRY = boxTLY + (boxSize / subBinY);
5151 if (boxBRY > maxY)
5152 boxTLY -= boxBRY - maxY;
5153
5154 starRect = QRect(boxTLX, boxTLY, boxSize / subBinX, boxSize / subBinY);
5155 }
5156 m_FocusView->setTrackingBoxEnabled(true);
5157 m_FocusView->setTrackingBox(starRect);
5158}
5159
5160void Focus::showFITSViewer()
5161{
5162 static int lastFVTabID = -1;
5163 if (m_ImageData)
5164 {
5165 QUrl url = QUrl::fromLocalFile("focus.fits");
5166 if (fv.isNull())
5167 {
5168 fv = KStars::Instance()->createFITSViewer();
5169 fv->loadData(m_ImageData, url, &lastFVTabID);
5170 connect(fv.get(), &FITSViewer::terminated, this, [this]()
5171 {
5172 fv.clear();
5173 });
5174 }
5175 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) == false)
5176 fv->loadData(m_ImageData, url, &lastFVTabID);
5177
5178 fv->show();
5179 }
5180}
5181
5182void Focus::adjustFocusOffset(int value, bool useAbsoluteOffset)
5183{
5184 // Allow focuser adjustments during looping & autofocus to honour Filter Offsets
5185 if (inAdjustFocus || adaptFocus->inAdaptiveFocus())
5186 {
5187 QString str = inAdjustFocus ? i18n("Adjust Focus") : i18n("Adaptive Focus");
5188 if (++m_StartRetries < MAXIMUM_RESTART_ITERATIONS)
5189 {
5190 appendLogText(i18n("Adjust focus request - Waiting 10sec for %1 to complete.", str));
5191 QTimer::singleShot(10 * 1000, this, [this, value, useAbsoluteOffset] ()
5192 {
5193 adjustFocusOffset(value, useAbsoluteOffset);
5194 });
5195 return;
5196 }
5197
5198 qCDebug(KSTARS_EKOS_FOCUS) << "adjustFocusOffset called whilst" << str << "in progress. Ignoring...";
5199 emit focusPositionAdjusted();
5200 return;
5201 }
5202
5203 m_StartRetries = 0;
5204 inAdjustFocus = true;
5205
5206 // Get the new position
5207 int newPosition = (useAbsoluteOffset) ? value : value + currentPosition;
5208
5209 if (!changeFocus(newPosition - currentPosition))
5210 qCDebug(KSTARS_EKOS_FOCUS) << "adjustFocusOffset unable to move focuser";
5211}
5212
5213void Focus::toggleFocusingWidgetFullScreen()
5214{
5215 if (focusingWidget->parent() == nullptr)
5216 {
5217 focusingWidget->setParent(this);
5218 rightLayout->insertWidget(0, focusingWidget);
5219 focusingWidget->showNormal();
5220 }
5221 else
5222 {
5223 focusingWidget->setParent(nullptr);
5224 focusingWidget->setWindowTitle(i18nc("@title:window", "Focus Frame"));
5225 focusingWidget->setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
5226 focusingWidget->showMaximized();
5227 focusingWidget->show();
5228 }
5229}
5230
5231void Focus::setMountStatus(ISD::Mount::Status newState)
5232{
5233 switch (newState)
5234 {
5235 case ISD::Mount::MOUNT_PARKING:
5236 case ISD::Mount::MOUNT_SLEWING:
5237 case ISD::Mount::MOUNT_MOVING:
5238 captureB->setEnabled(false);
5239 startFocusB->setEnabled(false);
5240 startAbInsB->setEnabled(false);
5241 startLoopB->setEnabled(false);
5242
5243 // If mount is moved while we have a star selected and subframed
5244 // let us reset the frame.
5245 if (subFramed)
5246 resetFrame();
5247
5248 break;
5249
5250 default:
5251 resetButtons();
5252 break;
5253 }
5254}
5255
5256void Focus::setMountCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha)
5257{
5258 Q_UNUSED(pierSide)
5259 Q_UNUSED(ha)
5260 mountAlt = position.alt().Degrees();
5261}
5262
5263void Focus::removeDevice(const QSharedPointer<ISD::GenericDevice> &deviceRemoved)
5264{
5265 auto name = deviceRemoved->getDeviceName();
5266
5267 // Check in Focusers
5268
5269 if (m_Focuser && m_Focuser->getDeviceName() == name)
5270 {
5271 m_Focuser->disconnect(this);
5272 m_Focuser = nullptr;
5273 QTimer::singleShot(1000, this, [this]()
5274 {
5275 checkFocuser();
5276 resetButtons();
5277 });
5278 }
5279
5280 // Check in Temperature Sources.
5281 for (auto &oneSource : m_TemperatureSources)
5282 {
5283 if (oneSource->getDeviceName() == name)
5284 {
5285 // clear reference to avoid runtime exception in checkTemperatureSource()
5286 if (m_LastSourceDeviceAutofocusTemperature && m_LastSourceDeviceAutofocusTemperature->getDeviceName() == name)
5287 m_LastSourceDeviceAutofocusTemperature.reset(nullptr);
5288
5289 m_TemperatureSources.removeAll(oneSource);
5290 QTimer::singleShot(1000, this, [this, name]()
5291 {
5292 defaultFocusTemperatureSource->removeItem(defaultFocusTemperatureSource->findText(name));
5293 });
5294 break;
5295 }
5296 }
5297
5298 // Check camera
5299 if (m_Camera && m_Camera->getDeviceName() == name)
5300 {
5301 m_Camera->disconnect(this);
5302 m_Camera = nullptr;
5303
5304 QTimer::singleShot(1000, this, [this]()
5305 {
5306 checkCamera();
5307 resetButtons();
5308 });
5309 }
5310
5311 // Check Filter
5312 if (m_FilterWheel && m_FilterWheel->getDeviceName() == name)
5313 {
5314 m_FilterWheel->disconnect(this);
5315 m_FilterWheel = nullptr;
5316
5317 QTimer::singleShot(1000, this, [this]()
5318 {
5319 checkFilter();
5320 resetButtons();
5321 });
5322 }
5323}
5324
5325void Focus::setupFilterManager()
5326{
5327 // Do we have an existing filter manager?
5328 if (m_FilterManager)
5329 m_FilterManager->disconnect(this);
5330
5331 // Create new or refresh device
5332 Ekos::Manager::Instance()->createFilterManager(m_FilterWheel);
5333
5334 // Return global filter manager for this filter wheel.
5335 Ekos::Manager::Instance()->getFilterManager(m_FilterWheel->getDeviceName(), m_FilterManager);
5336
5337 // Focus Module ----> Filter Manager connections
5338
5339 // Update focuser absolute position.
5340 connect(this, &Focus::absolutePositionChanged, m_FilterManager.get(), &FilterManager::setFocusAbsolutePosition);
5341
5342 // Update Filter Manager state
5343 connect(this, &Focus::newStatus, this, [this](Ekos::FocusState state)
5344 {
5345 if (m_FilterManager)
5346 {
5347 m_FilterManager->setFocusStatus(state);
5348 if (focusFilter->currentIndex() != -1 && canAbsMove && state == Ekos::FOCUS_COMPLETE)
5349 {
5350 m_FilterManager->setFilterAbsoluteFocusDetails(focusFilter->currentIndex(), currentPosition,
5351 m_LastSourceAutofocusTemperature, m_LastSourceAutofocusAlt);
5352 }
5353 }
5354 });
5355
5356 // Filter Manager ----> Focus Module connections
5357
5358 // Suspend guiding if filter offset is change with OAG
5359 connect(m_FilterManager.get(), &FilterManager::newStatus, this, [this](Ekos::FilterState filterState)
5360 {
5361 // If we are changing filter offset while idle, then check if we need to suspend guiding.
5362 if (filterState == FILTER_OFFSET && state() != Ekos::FOCUS_PROGRESS)
5363 {
5364 if (m_GuidingSuspended == false && m_OpsFocusSettings->focusSuspendGuiding->isChecked())
5365 {
5366 m_GuidingSuspended = true;
5367 emit suspendGuiding();
5368 }
5369 }
5370 });
5371
5372 // Take action once filter manager completes filter position
5373 connect(m_FilterManager.get(), &FilterManager::ready, this, [this]()
5374 {
5375 // Keep the focusFilter widget consistent with the filter wheel
5376 if (focusFilter->currentIndex() != currentFilterPosition - 1)
5377 focusFilter->setCurrentIndex(currentFilterPosition - 1);
5378
5379 if (filterPositionPending)
5380 {
5381 filterPositionPending = false;
5382 capture();
5383 }
5384 else if (fallbackFilterPending)
5385 {
5386 fallbackFilterPending = false;
5387 setState(m_pendingState);
5388 m_pendingState = FOCUS_IDLE;
5389 }
5390 });
5391
5392 // Take action when filter operation fails
5393 connect(m_FilterManager.get(), &FilterManager::failed, this, [this]()
5394 {
5395 if (filterPositionPending)
5396 {
5397 appendLogText(i18n("Filter operation failed."));
5398 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_FILTER_MANAGER);
5399 }
5400 });
5401
5402 // Run Autofocus if required by filter manager
5403 connect(m_FilterManager.get(), &FilterManager::runAutoFocus, this, &Focus::runAutoFocus);
5404
5405 // Abort Autofocus if required by filter manager
5406 connect(m_FilterManager.get(), &FilterManager::abortAutoFocus, this, &Focus::abort);
5407
5408 // Adjust focus offset
5409 connect(m_FilterManager.get(), &FilterManager::newFocusOffset, this, &Focus::adjustFocusOffset);
5410
5411 // Update labels
5412 connect(m_FilterManager.get(), &FilterManager::labelsChanged, this, [this]()
5413 {
5414 focusFilter->clear();
5415 focusFilter->addItems(m_FilterManager->getFilterLabels());
5416 currentFilterPosition = m_FilterManager->getFilterPosition();
5417 focusFilter->setCurrentIndex(currentFilterPosition - 1);
5418 });
5419
5420 // Position changed
5421 connect(m_FilterManager.get(), &FilterManager::positionChanged, this, [this]()
5422 {
5423 currentFilterPosition = m_FilterManager->getFilterPosition();
5424 focusFilter->setCurrentIndex(currentFilterPosition - 1);
5425 });
5426
5427 // Exposure Changed
5428 connect(m_FilterManager.get(), &FilterManager::exposureChanged, this, [this]()
5429 {
5430 focusExposure->setValue(m_FilterManager->getFilterExposure());
5431 });
5432
5433 // Wavelength Changed
5434 connect(m_FilterManager.get(), &FilterManager::wavelengthChanged, this, [this]()
5435 {
5436 wavelengthChanged();
5437 });
5438}
5439
5440void Focus::connectFilterManager()
5441{
5442 // Show filter manager if toggled.
5443 connect(filterManagerB, &QPushButton::clicked, this, [this]()
5444 {
5445 if (m_FilterManager)
5446 {
5447 m_FilterManager->refreshFilterModel();
5448 m_FilterManager->show();
5449 m_FilterManager->raise();
5450 }
5451 });
5452
5453 // Resume guiding if suspended after focus position is adjusted.
5454 connect(this, &Focus::focusPositionAdjusted, this, [this]()
5455 {
5456 if (m_FilterManager)
5457 m_FilterManager->setFocusOffsetComplete();
5458 if (m_GuidingSuspended && state() != Ekos::FOCUS_PROGRESS)
5459 {
5460 QTimer::singleShot(m_OpsFocusMechanics->focusSettleTime->value() * 1000, this, [this]()
5461 {
5462 m_GuidingSuspended = false;
5463 emit resumeGuiding();
5464 });
5465 }
5466 });
5467
5468 // Save focus exposure for a particular filter
5469 connect(focusExposure, &QDoubleSpinBox::editingFinished, this, [this]()
5470 {
5471 // Don't save if donut processing is changing this field
5472 if (inAutoFocus && m_OpsFocusProcess->focusDonut->isEnabled())
5473 return;
5474
5475 if (m_FilterManager)
5476 m_FilterManager->setFilterExposure(focusFilter->currentIndex(), focusExposure->value());
5477 });
5478
5479 // Load exposure if filter is changed.
5480 connect(focusFilter, &QComboBox::currentTextChanged, this, [this](const QString & text)
5481 {
5482 if (m_FilterManager)
5483 {
5484 focusExposure->setValue(m_FilterManager->getFilterExposure(text));
5485 // Update the CFZ for the new filter - force all data back to OT & filter values
5486 resetCFZToOT();
5487 }
5488 });
5489
5490}
5491
5492void Focus::toggleVideo(bool enabled)
5493{
5494 if (m_Camera == nullptr)
5495 return;
5496
5497 if (m_Camera->isBLOBEnabled() == false)
5498 {
5499
5500 if (Options::guiderType() != Ekos::Guide::GUIDE_INTERNAL)
5501 m_Camera->setBLOBEnabled(true);
5502 else
5503 {
5504 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, enabled]()
5505 {
5506 //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr);
5507 KSMessageBox::Instance()->disconnect(this);
5508 m_Camera->setVideoStreamEnabled(enabled);
5509 });
5510 KSMessageBox::Instance()->questionYesNo(i18n("Image transfer is disabled for this camera. Would you like to enable it?"));
5511 }
5512 }
5513 else
5514 m_Camera->setVideoStreamEnabled(enabled);
5515}
5516
5517//void Focus::setWeatherData(const std::vector<ISD::Weather::WeatherData> &data)
5518//{
5519// auto pos = std::find_if(data.begin(), data.end(), [](ISD::Weather::WeatherData oneEntry)
5520// {
5521// return (oneEntry.name == "WEATHER_TEMPERATURE");
5522// });
5523
5524// if (pos != data.end())
5525// {
5526// updateTemperature(OBSERVATORY_TEMPERATURE, pos->value);
5527// }
5528//}
5529
5530void Focus::setVideoStreamEnabled(bool enabled)
5531{
5532 if (enabled)
5533 {
5534 liveVideoB->setChecked(true);
5535 liveVideoB->setIcon(QIcon::fromTheme("camera-on"));
5536 }
5537 else
5538 {
5539 liveVideoB->setChecked(false);
5540 liveVideoB->setIcon(QIcon::fromTheme("camera-ready"));
5541 }
5542}
5543
5544void Focus::processCaptureTimeout()
5545{
5546 m_captureInProgress = false;
5547
5548 if (m_abortInProgress)
5549 {
5550 // Timeout occured whilst trying to abort AF. So do no further processing - stop function will call abortExposure()
5551 qCDebug(KSTARS_EKOS_FOCUS) << "Abort AF: discarding frame in " << __FUNCTION__;
5552 return;
5553 }
5554
5555 captureTimeoutCounter++;
5556
5557 if (captureTimeoutCounter >= 3)
5558 {
5559 captureTimeoutCounter = 0;
5560 m_MissingCameraCounter = 0;
5561 captureTimeout.stop();
5562 appendLogText(i18n("Exposure timeout. Aborting..."));
5563 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_CAPTURE_TIMEOUT);
5564 }
5565 else if (m_Camera)
5566 {
5567 appendLogText(i18n("Exposure timeout. Restarting exposure..."));
5568 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
5569 targetChip->abortExposure();
5570
5571 capture(focusExposure->value());
5572 }
5573 // Don't allow this to happen all night. We will repeat checking (most likely for activeCamera()
5574 // another 200s = 40 * 5s, but after that abort capture.
5575 else if (m_MissingCameraCounter < 40)
5576 {
5577 m_MissingCameraCounter++;
5578 qCDebug(KSTARS_EKOS_FOCUS) << "Unable to restart focus exposure as camera is missing, trying again in 5 seconds...";
5579 QTimer::singleShot(5000, this, &Focus::processCaptureTimeout);
5580 }
5581 else
5582 {
5583 m_MissingCameraCounter = 0;
5584 captureTimeoutCounter = 0;
5585 captureTimeout.stop();
5586 appendLogText(i18n("Exposure timeout. Aborting..."));
5587 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_CAPTURE_TIMEOUT);
5588 }
5589
5590}
5591
5592void Focus::processCaptureErrorDefault()
5593{
5594 processCaptureError(ISD::Camera::ERROR_CAPTURE);
5595}
5596
5597void Focus::processCaptureError(ISD::Camera::ErrorType type)
5598{
5599 m_captureInProgress = false;
5600 captureTimeout.stop();
5601
5602 if (m_abortInProgress)
5603 {
5604 qCDebug(KSTARS_EKOS_FOCUS) << "Abort AF: discarding frame in " << __FUNCTION__;
5605 return;
5606 }
5607
5608 if (type == ISD::Camera::ERROR_SAVE)
5609 {
5610 appendLogText(i18n("Failed to save image. Aborting..."));
5611 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_CAPTURE_FAILED);
5612 return;
5613 }
5614
5615
5616 if (m_Camera)
5617 {
5618 captureFailureCounter++;
5619
5620 if (captureFailureCounter >= 3)
5621 {
5622 captureFailureCounter = 0;
5623 captureTimeoutCounter = 0;
5624 m_MissingCameraCounter = 0;
5625 appendLogText(i18n("Exposure failure. Aborting..."));
5626 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_CAPTURE_FAILED);
5627 return;
5628 }
5629
5630 ISD::CameraChip *targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
5631 targetChip->abortExposure();
5632 capture(focusExposure->value());
5633 }
5634 // Don't allow this to happen all night. We will repeat checking (most likely for activeCamera()
5635 // another 200s = 40 * 5s, but after that abort capture.
5636 else if (m_MissingCameraCounter < 40)
5637 {
5638 m_MissingCameraCounter++;
5639 qCDebug(KSTARS_EKOS_FOCUS) << "Unable to restart focus exposure as camera is missing, trying again in 5 seconds...";
5640 // We already know type is not ERROR_SAVE, so the value of type isn't used
5641 // and we can let it default to ERROR_CAPTURE.
5642 QTimer::singleShot(5000, this, &Focus::processCaptureErrorDefault);
5643 }
5644 else
5645 {
5646 m_MissingCameraCounter = 0;
5647 captureFailureCounter = 0;
5648 captureTimeoutCounter = 0;
5649 captureTimeout.stop();
5650 appendLogText(i18n("Exposure failure. Aborting..."));
5651 completeFocusProcedure(Ekos::FOCUS_ABORTED, Ekos::FOCUS_FAIL_CAPTURE_FAILED);
5652 }
5653}
5654
5655void Focus::syncSettings()
5656{
5657 QDoubleSpinBox *dsb = nullptr;
5658 QSpinBox *sb = nullptr;
5659 QCheckBox *cb = nullptr;
5660 QGroupBox *gb = nullptr;
5661 QRadioButton *rb = nullptr;
5662 QComboBox *cbox = nullptr;
5663 QSplitter *s = nullptr;
5664
5665 QString key;
5666 QVariant value;
5667
5668 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
5669 {
5670 key = dsb->objectName();
5671 value = dsb->value();
5672
5673 }
5674 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
5675 {
5676 key = sb->objectName();
5677 value = sb->value();
5678 }
5679 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
5680 {
5681 key = cb->objectName();
5682 value = cb->isChecked();
5683 }
5684 else if ( (gb = qobject_cast<QGroupBox*>(sender())))
5685 {
5686 key = gb->objectName();
5687 value = gb->isChecked();
5688 }
5689 else if ( (rb = qobject_cast<QRadioButton*>(sender())))
5690 {
5691 key = rb->objectName();
5692 // Discard false requests
5693 if (rb->isChecked() == false)
5694 {
5695 m_Settings.remove(key);
5696 return;
5697 }
5698 value = true;
5699 }
5700 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
5701 {
5702 key = cbox->objectName();
5703 value = cbox->currentText();
5704 }
5705 else if ( (s = qobject_cast<QSplitter*>(sender())))
5706 {
5707 key = s->objectName();
5708 // Convert from the QByteArray to QString using Base64
5709 value = QString::fromUtf8(s->saveState().toBase64());
5710 }
5711
5712 // Save immediately
5713 Options::self()->setProperty(key.toLatin1(), value);
5714
5715 m_Settings[key] = value;
5716 m_GlobalSettings[key] = value;
5717 // propagate image mask attributes
5718 selectImageMask();
5719
5720 m_DebounceTimer.start();
5721}
5722
5723///////////////////////////////////////////////////////////////////////////////////////////
5724///
5725///////////////////////////////////////////////////////////////////////////////////////////
5726void Focus::settleSettings()
5727{
5728 emit settingsUpdated(getAllSettings());
5729 // Save to optical train specific settings as well
5730 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
5731 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Focus, m_Settings);
5732}
5733
5734void Focus::loadGlobalSettings()
5735{
5736 QString key;
5737 QVariant value;
5738
5739 QVariantMap settings;
5740 // All Combo Boxes
5741 for (auto &oneWidget : findChildren<QComboBox*>())
5742 {
5743 if (oneWidget->objectName() == "opticalTrainCombo")
5744 continue;
5745
5746 key = oneWidget->objectName();
5747 value = Options::self()->property(key.toLatin1());
5748 if (value.isValid() && oneWidget->count() > 0)
5749 {
5750 oneWidget->setCurrentText(value.toString());
5751 settings[key] = value;
5752 }
5753 else
5754 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!";
5755 }
5756
5757 // All Double Spin Boxes
5758 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
5759 {
5760 key = oneWidget->objectName();
5761 value = Options::self()->property(key.toLatin1());
5762 if (value.isValid())
5763 {
5764 oneWidget->setValue(value.toDouble());
5765 settings[key] = value;
5766 }
5767 else
5768 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!";
5769 }
5770
5771 // All Spin Boxes
5772 for (auto &oneWidget : findChildren<QSpinBox*>())
5773 {
5774 key = oneWidget->objectName();
5775 value = Options::self()->property(key.toLatin1());
5776 if (value.isValid())
5777 {
5778 oneWidget->setValue(value.toInt());
5779 settings[key] = value;
5780 }
5781 else
5782 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!";
5783 }
5784
5785 // All Checkboxes
5786 for (auto &oneWidget : findChildren<QCheckBox*>())
5787 {
5788 key = oneWidget->objectName();
5789 value = Options::self()->property(key.toLatin1());
5790 if (value.isValid())
5791 {
5792 oneWidget->setChecked(value.toBool());
5793 settings[key] = value;
5794 }
5795 else if (key != forceInSeqAF->objectName())
5796 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!";
5797 }
5798
5799 // All Checkable Groupboxes
5800 for (auto &oneWidget : findChildren<QGroupBox*>())
5801 {
5802 if (oneWidget->isCheckable())
5803 {
5804 key = oneWidget->objectName();
5805 value = Options::self()->property(key.toLatin1());
5806 if (value.isValid())
5807 {
5808 oneWidget->setChecked(value.toBool());
5809 settings[key] = value;
5810 }
5811 else
5812 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!";
5813 }
5814 }
5815
5816 // All Splitters
5817 for (auto &oneWidget : findChildren<QSplitter*>())
5818 {
5819 key = oneWidget->objectName();
5820 value = Options::self()->property(key.toLatin1());
5821 if (value.isValid())
5822 {
5823 // Convert the saved QString to a QByteArray using Base64
5824 auto valueBA = QByteArray::fromBase64(value.toString().toUtf8());
5825 oneWidget->restoreState(valueBA);
5826 settings[key] = valueBA;
5827 }
5828 else
5829 qCDebug(KSTARS_EKOS_FOCUS) << "Option" << key << "not found!";
5830 }
5831
5832 // All Radio buttons
5833 for (auto &oneWidget : findChildren<QRadioButton*>())
5834 {
5835 key = oneWidget->objectName();
5836 value = Options::self()->property(key.toLatin1());
5837 if (value.isValid())
5838 {
5839 oneWidget->setChecked(value.toBool());
5840 settings[key] = value;
5841 }
5842 }
5843 // propagate image mask attributes
5844 selectImageMask();
5845 m_GlobalSettings = m_Settings = settings;
5846}
5847
5848void Focus::checkMosaicMaskLimits()
5849{
5850 if (m_Camera == nullptr || m_Camera->isConnected() == false)
5851 return;
5852 auto targetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
5853 if (targetChip == nullptr || frameSettings.contains(targetChip) == false)
5854 return;
5855 auto settings = frameSettings[targetChip];
5856
5857 // Watch out for invalid values.
5858 auto width = settings["w"].toInt();
5859 auto height = settings["h"].toInt();
5860 if (width == 0 || height == 0)
5861 return;
5862
5863 // determine maximal square size
5864 auto min = std::min(width, height);
5865 // now check if the tile size is below this limit
5866 m_OpsFocusSettings->focusMosaicTileWidth->setMaximum(100 * min / (3 * width));
5867}
5868
5869void Focus::connectSyncSettings()
5870{
5871 // All Combo Boxes
5872 for (auto &oneWidget : findChildren<QComboBox*>())
5873 // Don't sync Optical Train combo
5874 if (oneWidget != opticalTrainCombo)
5875 connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Focus::syncSettings);
5876
5877 // All Double Spin Boxes
5878 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
5879 connect(oneWidget, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &Ekos::Focus::syncSettings);
5880
5881 // All Spin Boxes
5882 for (auto &oneWidget : findChildren<QSpinBox*>())
5883 connect(oneWidget, QOverload<int>::of(&QSpinBox::valueChanged), this, &Ekos::Focus::syncSettings);
5884
5885 // All Checkboxes
5886 for (auto &oneWidget : findChildren<QCheckBox*>())
5887 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings);
5888
5889 // All Checkable Groupboxes
5890 for (auto &oneWidget : findChildren<QGroupBox*>())
5891 if (oneWidget->isCheckable())
5892 connect(oneWidget, &QGroupBox::toggled, this, &Ekos::Focus::syncSettings);
5893
5894 // All Splitters
5895 for (auto &oneWidget : findChildren<QSplitter*>())
5896 connect(oneWidget, &QSplitter::splitterMoved, this, &Ekos::Focus::syncSettings);
5897
5898 // All Radio Buttons
5899 for (auto &oneWidget : findChildren<QRadioButton*>())
5900 connect(oneWidget, &QRadioButton::toggled, this, &Ekos::Focus::syncSettings);
5901}
5902
5903void Focus::disconnectSyncSettings()
5904{
5905 // All Combo Boxes
5906 for (auto &oneWidget : findChildren<QComboBox*>())
5907 disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Focus::syncSettings);
5908
5909 // All Double Spin Boxes
5910 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
5911 disconnect(oneWidget, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &Ekos::Focus::syncSettings);
5912
5913 // All Spin Boxes
5914 for (auto &oneWidget : findChildren<QSpinBox*>())
5915 disconnect(oneWidget, QOverload<int>::of(&QSpinBox::valueChanged), this, &Ekos::Focus::syncSettings);
5916
5917 // All Checkboxes
5918 for (auto &oneWidget : findChildren<QCheckBox*>())
5919 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings);
5920
5921 // All Checkable Groupboxes
5922 for (auto &oneWidget : findChildren<QGroupBox*>())
5923 if (oneWidget->isCheckable())
5924 disconnect(oneWidget, &QGroupBox::toggled, this, &Ekos::Focus::syncSettings);
5925
5926 // All Splitters
5927 for (auto &oneWidget : findChildren<QSplitter*>())
5928 disconnect(oneWidget, &QSplitter::splitterMoved, this, &Ekos::Focus::syncSettings);
5929
5930 // All Radio Buttons
5931 for (auto &oneWidget : findChildren<QRadioButton*>())
5932 disconnect(oneWidget, &QRadioButton::toggled, this, &Ekos::Focus::syncSettings);
5933}
5934
5935void Focus::initPlots()
5936{
5938
5939 profileDialog = new QDialog(this);
5940 profileDialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
5941 QVBoxLayout *profileLayout = new QVBoxLayout(profileDialog);
5942 profileDialog->setWindowTitle(i18nc("@title:window", "Relative Profile"));
5943 profilePlot = new FocusProfilePlot(profileDialog);
5944
5945 profileLayout->addWidget(profilePlot);
5946 profileDialog->setLayout(profileLayout);
5947 profileDialog->resize(400, 300);
5948
5949 connect(relativeProfileB, &QPushButton::clicked, profileDialog, &QDialog::show);
5950 connect(this, &Ekos::Focus::newHFR, [this](double currentHFR, int pos)
5951 {
5952 Q_UNUSED(pos) profilePlot->drawProfilePlot(currentHFR);
5953 });
5954}
5955
5956void Focus::initConnections()
5957{
5958 // How long do we wait until the user select a star?
5959 waitStarSelectTimer.setInterval(AUTO_STAR_TIMEOUT);
5960 connect(&waitStarSelectTimer, &QTimer::timeout, this, &Ekos::Focus::checkAutoStarTimeout);
5962
5963 // Setup Debounce timer to limit over-activation of settings changes
5964 m_DebounceTimer.setInterval(500);
5965 m_DebounceTimer.setSingleShot(true);
5966 connect(&m_DebounceTimer, &QTimer::timeout, this, &Focus::settleSettings);
5967
5968 // Show FITS Image in a new window
5969 showFITSViewerB->setIcon(QIcon::fromTheme("kstars_fitsviewer"));
5970 showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
5971 connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Focus::showFITSViewer);
5972
5973 // Toggle FITS View to full screen
5974 toggleFullScreenB->setIcon(QIcon::fromTheme("view-fullscreen"));
5975 toggleFullScreenB->setShortcut(Qt::Key_F4);
5976 toggleFullScreenB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
5977 connect(toggleFullScreenB, &QPushButton::clicked, this, &Ekos::Focus::toggleFocusingWidgetFullScreen);
5978
5979 // delayed capturing for waiting the scope to settle
5980 captureTimer.setSingleShot(true);
5981 connect(&captureTimer, &QTimer::timeout, this, [this]()
5982 {
5983 capture();
5984 });
5985
5986 // How long do we wait until an exposure times out and needs a retry?
5987 captureTimeout.setSingleShot(true);
5988 connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Focus::processCaptureTimeout);
5989
5990 // Start/Stop focus
5991 connect(startFocusB, &QPushButton::clicked, this, &Ekos::Focus::manualStart);
5992 connect(stopFocusB, &QPushButton::clicked, this, &Ekos::Focus::abort);
5993
5994 // Focus IN/OUT
5997
5998 // Capture a single frame
6000 // Start continuous capture
6002 // Use a subframe when capturing
6003 connect(m_OpsFocusSettings->focusSubFrame, &QRadioButton::toggled, this, &Ekos::Focus::toggleSubframe);
6004 // Reset frame dimensions to default
6006
6007 // handle frame size changes
6008 connect(focusBinning, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Focus::checkMosaicMaskLimits);
6009
6010 // Sync settings if the temperature source selection is updated.
6011 connect(defaultFocusTemperatureSource, &QComboBox::currentTextChanged, this, &Ekos::Focus::checkTemperatureSource);
6012
6013 // Set focuser absolute position
6014 connect(startGotoB, &QPushButton::clicked, this, &Ekos::Focus::setAbsoluteFocusTicks);
6015 connect(stopGotoB, &QPushButton::clicked, this, [this]()
6016 {
6017 if (m_Focuser)
6018 m_Focuser->stop();
6019 });
6020 // Update the focuser box size used to enclose a star
6021 connect(m_OpsFocusSettings->focusBoxSize, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
6022 &Ekos::Focus::updateBoxSize);
6023
6024 // Setup the tools buttons
6025 connect(startAbInsB, &QPushButton::clicked, this, &Ekos::Focus::startAbIns);
6026 connect(cfzB, &QPushButton::clicked, this, [this]()
6027 {
6028 m_CFZDialog->show();
6029 m_CFZDialog->raise();
6030 });
6031 connect(advisorB, &QPushButton::clicked, this, [this]()
6032 {
6033 if (focusAdvisor)
6034 {
6035 focusAdvisor->setButtons(false);
6036 focusAdvisor->show();
6037 focusAdvisor->raise();
6038 }
6039 });
6040
6041 connect(forceInSeqAF, &QCheckBox::toggled, this, [&](bool enabled)
6042 {
6043 emit inSequenceAF(enabled, opticalTrain());
6044 });
6045
6046 // Update the focuser star detection if the detection algorithm selection changes.
6047 connect(m_OpsFocusProcess->focusDetection, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int index)
6048 {
6049 setFocusDetection(static_cast<StarAlgorithm>(index));
6050 });
6051
6052 // Update the focuser solution algorithm if the selection changes.
6053 connect(m_OpsFocusProcess->focusAlgorithm, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int index)
6054 {
6055 setFocusAlgorithm(static_cast<Algorithm>(index));
6056 });
6057
6058 // Update the curve fit if the selection changes. Use the currentIndexChanged method rather than
6059 // activated as the former fires when the index is changed by the user AND if changed programmatically
6060 connect(m_OpsFocusProcess->focusCurveFit, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
6061 this, [&](int index)
6062 {
6063 setCurveFit(static_cast<CurveFitting::CurveFit>(index));
6064 });
6065
6066 // Update the star measure if the selection changes
6067 connect(m_OpsFocusProcess->focusStarMeasure, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
6068 this, [&](int index)
6069 {
6070 setStarMeasure(static_cast<StarMeasure>(index));
6071 });
6072
6073 // Update the star PSF if the selection changes
6074 connect(m_OpsFocusProcess->focusStarPSF, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
6075 this, [&](int index)
6076 {
6077 setStarPSF(static_cast<StarPSF>(index));
6078 });
6079
6080 // Update the units (pixels or arcsecs) if the selection changes
6081 connect(m_OpsFocusSettings->focusUnits, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
6082 this, [&](int index)
6083 {
6084 setStarUnits(static_cast<StarUnits>(index));
6085 });
6086
6087 // Update the walk if the selection changes
6088 connect(m_OpsFocusMechanics->focusWalk, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
6089 this, [&](int index)
6090 {
6091 setWalk(static_cast<FocusWalk>(index));
6092 });
6093
6094 // Adaptive Focus on/off switch toggled
6095 connect(m_OpsFocusSettings->focusAdaptive, &QCheckBox::toggled, this, &Ekos::Focus::resetAdaptiveFocus);
6096
6097 // Reset star center on auto star check toggle
6098 connect(m_OpsFocusSettings->focusAutoStarEnabled, &QCheckBox::toggled, this, [&](bool enabled)
6099 {
6100 if (enabled)
6101 {
6102 starCenter = QVector3D();
6103 starSelected = false;
6104 m_FocusView->setTrackingBox(QRect());
6105 }
6106 });
6107
6108 // CFZ Panel
6109 connect(m_CFZUI->resetToOTB, &QPushButton::clicked, this, &Ekos::Focus::resetCFZToOT);
6110
6111 connect(m_CFZUI->focusCFZDisplayVCurve, static_cast<void (QCheckBox::*)(int)>(&QCheckBox::stateChanged), this,
6112 &Ekos::Focus::calcCFZ);
6113
6114 connect(m_CFZUI->focusCFZAlgorithm, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
6115 &Ekos::Focus::calcCFZ);
6116
6117 connect(m_CFZUI->focusCFZTolerance, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &Ekos::Focus::calcCFZ);
6118
6119 connect(m_CFZUI->focusCFZTau, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](double d)
6120 {
6121 Q_UNUSED(d);
6122 calcCFZ();
6123 });
6124
6125 connect(m_CFZUI->focusCFZWavelength, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](int i)
6126 {
6127 Q_UNUSED(i);
6128 calcCFZ();
6129 });
6130
6131 connect(m_CFZUI->focusCFZFNumber, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](double d)
6132 {
6133 Q_UNUSED(d);
6134 calcCFZ();
6135 });
6136
6137 connect(m_CFZUI->focusCFZStepSize, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](double d)
6138 {
6139 Q_UNUSED(d);
6140 calcCFZ();
6141 });
6142
6143 connect(m_CFZUI->focusCFZAperture, QOverload<int>::of(&QSpinBox::valueChanged), [ = ](int i)
6144 {
6145 Q_UNUSED(i);
6146 calcCFZ();
6147 });
6148
6149 connect(m_CFZUI->focusCFZSeeing, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [ = ](double d)
6150 {
6151 Q_UNUSED(d);
6152 calcCFZ();
6153 });
6154}
6155
6156void Focus::setFocusDetection(StarAlgorithm starAlgorithm)
6157{
6158 static bool first = true;
6159 if (!first && m_FocusDetection == starAlgorithm)
6160 return;
6161
6162 first = false;
6163
6164 m_FocusDetection = starAlgorithm;
6165
6166 // setFocusAlgorithm displays the appropriate widgets for the selection
6167 setFocusAlgorithm(m_FocusAlgorithm);
6168 setAutoStarAndBox();
6169 setDonutBuster();
6170}
6171
6172// Controls the setting of focusAutoStarEnabled and focusBoxSize widgets
6173// Sub frame is selected (rather than full field)
6174// All m_FocusDetection except ALGORITHM_BAHTINOV
6175// m_StarMeasure must be star based
6176void Focus::setAutoStarAndBox()
6177{
6178 if (m_FocusDetection == ALGORITHM_BAHTINOV)
6179 {
6180 m_OpsFocusSettings->focusAutoStarEnabled->setEnabled(false);
6181 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(false);
6182 m_OpsFocusSettings->focusBoxSize->setEnabled(m_OpsFocusSettings->focusSubFrame->isChecked());
6183 m_OpsFocusSettings->focusBoxSizeLabel->setEnabled(m_OpsFocusSettings->focusSubFrame->isChecked());
6184 m_OpsFocusSettings->focusBoxSize->setMaximum(512);
6185 if (m_OpsFocusSettings->focusBoxSize->value() > 512)
6186 m_OpsFocusSettings->focusBoxSize->setValue(m_OpsFocusSettings->focusBoxSize->value());
6187 }
6188 else if(m_OpsFocusSettings->focusSubFrame->isChecked())
6189 {
6190 if(isStarMeasureStarBased())
6191 {
6192 m_OpsFocusSettings->focusAutoStarEnabled->setEnabled(true);
6193
6194 m_OpsFocusSettings->focusBoxSize->setEnabled(true);
6195 m_OpsFocusSettings->focusBoxSizeLabel->setEnabled(true);
6196 if (m_OpsFocusSettings->focusBoxSize->value() > 256)
6197 m_OpsFocusSettings->focusBoxSize->setValue(m_OpsFocusSettings->focusBoxSize->value());
6198 m_OpsFocusSettings->focusBoxSize->setMaximum(256);
6199 }
6200 else
6201 {
6202 m_OpsFocusSettings->focusAutoStarEnabled->setEnabled(false);
6203 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(false);
6204
6205 // For non-star based measures we cam make the box much bigger as it could encapsulate the moon, etc
6206 m_OpsFocusSettings->focusBoxSize->setEnabled(true);
6207 m_OpsFocusSettings->focusBoxSizeLabel->setEnabled(true);
6208 int maxSize = std::min(m_CcdWidth, m_CcdHeight);
6209 int step = m_OpsFocusSettings->focusBoxSize->singleStep();
6210 maxSize = std::max(256, (maxSize / step) * step);
6211 m_OpsFocusSettings->focusBoxSize->setMaximum(maxSize);
6212 }
6213 }
6214 else
6215 {
6216 // Full field is set
6217 m_OpsFocusSettings->focusAutoStarEnabled->setEnabled(false);
6218 m_OpsFocusSettings->focusAutoStarEnabled->setChecked(false);
6219 m_OpsFocusSettings->focusBoxSize->setEnabled(false);
6220 m_OpsFocusSettings->focusBoxSizeLabel->setEnabled(false);
6221 }
6222
6223}
6224
6225void Focus::setFocusAlgorithm(Algorithm algorithm)
6226{
6227 m_FocusAlgorithm = algorithm;
6228 switch(algorithm)
6229 {
6230 case FOCUS_ITERATIVE:
6231 // Remove unused widgets from the grid and hide them
6232 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel);
6233 m_OpsFocusProcess->focusMultiRowAverageLabel->hide();
6234 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverage);
6235 m_OpsFocusProcess->focusMultiRowAverage->hide();
6236
6237 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel);
6238 m_OpsFocusProcess->focusGaussianSigmaLabel->hide();
6239 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigma);
6240 m_OpsFocusProcess->focusGaussianSigma->hide();
6241
6242 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel);
6243 m_OpsFocusProcess->focusGaussianKernelSizeLabel->hide();
6244 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize);
6245 m_OpsFocusProcess->focusGaussianKernelSize->hide();
6246
6247 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarMeasureLabel);
6248 m_OpsFocusProcess->focusStarMeasureLabel->hide();
6249 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarMeasure);
6250 m_OpsFocusProcess->focusStarMeasure->hide();
6251
6252 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel);
6253 m_OpsFocusProcess->focusStarPSFLabel->hide();
6254 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF);
6255 m_OpsFocusProcess->focusStarPSF->hide();
6256
6257 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusUseWeights);
6258 m_OpsFocusProcess->focusUseWeights->hide();
6259
6260 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2LimitLabel);
6261 m_OpsFocusProcess->focusR2LimitLabel->hide();
6262 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2Limit);
6263 m_OpsFocusProcess->focusR2Limit->hide();
6264
6265 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusRefineCurveFit);
6266 m_OpsFocusProcess->focusRefineCurveFit->hide();
6267 m_OpsFocusProcess->focusRefineCurveFit->setChecked(false);
6268
6269 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusToleranceLabel);
6270 m_OpsFocusProcess->focusToleranceLabel->hide();
6271 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusTolerance);
6272 m_OpsFocusProcess->focusTolerance->hide();
6273
6274 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThresholdLabel);
6275 m_OpsFocusProcess->focusThresholdLabel->hide();
6276 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThreshold);
6277 m_OpsFocusProcess->focusThreshold->hide();
6278
6279 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusCurveFitLabel);
6280 m_OpsFocusProcess->focusCurveFitLabel->hide();
6281 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusCurveFit);
6282 m_OpsFocusProcess->focusCurveFit->hide();
6283
6284 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusDenoise);
6285 m_OpsFocusProcess->focusDenoise->hide();
6286
6287 // Donut Buster not available
6288 m_OpsFocusProcess->focusDonut->hide();
6289 m_OpsFocusProcess->focusDonut->setChecked(false);
6290
6291 // Scan Start Pos not available
6292 m_OpsFocusProcess->focusScanStartPos->hide();
6293 m_OpsFocusProcess->focusScanStartPos->setChecked(false);
6294
6295 // Although CurveFit is not used by Iterative setting to Quadratic will configure other widgets
6296 m_OpsFocusProcess->focusCurveFit->setCurrentIndex(CurveFitting::FOCUS_QUADRATIC);
6297
6298 // Set Measure to just HFR
6299 if (m_OpsFocusProcess->focusStarMeasure->count() != 1)
6300 {
6301 m_OpsFocusProcess->focusStarMeasure->clear();
6302 m_OpsFocusProcess->focusStarMeasure->addItem(m_StarMeasureText.at(FOCUS_STAR_HFR));
6303 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR);
6304 }
6305
6306 // Add necessary widgets to the grid
6307 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusToleranceLabel, 3, 0);
6308 m_OpsFocusProcess->focusToleranceLabel->show();
6309 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusTolerance, 3, 1);
6310 m_OpsFocusProcess->focusTolerance->show();
6311
6312 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCountLabel, 3, 2);
6313 m_OpsFocusProcess->focusFramesCountLabel->show();
6314 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCount, 3, 3);
6315 m_OpsFocusProcess->focusFramesCount->show();
6316
6317 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusHFRFramesCountLabel, 4, 2);
6318 m_OpsFocusProcess->focusHFRFramesCountLabel->show();
6319 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusHFRFramesCount, 4, 3);
6320 m_OpsFocusProcess->focusHFRFramesCount->show();
6321
6322 if (m_FocusDetection == ALGORITHM_THRESHOLD)
6323 {
6324 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThresholdLabel, 5, 0);
6325 m_OpsFocusProcess->focusThresholdLabel->show();
6326 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThreshold, 5, 1);
6327 m_OpsFocusProcess->focusThreshold->show();
6328 }
6329 else if (m_FocusDetection == ALGORITHM_BAHTINOV)
6330 {
6331 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverageLabel, 5, 0);
6332 m_OpsFocusProcess->focusMultiRowAverageLabel->show();
6333 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverage, 5, 1);
6334 m_OpsFocusProcess->focusMultiRowAverage->show();
6335
6336 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigmaLabel, 5, 2);
6337 m_OpsFocusProcess->focusGaussianSigmaLabel->show();
6338 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigma, 5, 3);
6339 m_OpsFocusProcess->focusGaussianSigma->show();
6340
6341 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel, 6, 0);
6342 m_OpsFocusProcess->focusGaussianKernelSizeLabel->show();
6343 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSize, 6, 1);
6344 m_OpsFocusProcess->focusGaussianKernelSize->show();
6345 }
6346
6347 // Aberration Inspector button
6348 startAbInsB->setEnabled(canAbInsStart());
6349
6350 // Settings changes
6351 // Disable adaptive focus
6352 m_OpsFocusSettings->focusAdaptive->setChecked(false);
6353 m_OpsFocusSettings->focusAdaptStart->setChecked(false);
6354 m_OpsFocusSettings->adaptiveFocusGroup->setEnabled(false);
6355
6356 // Mechanics changes
6357 m_OpsFocusMechanics->focusMaxSingleStep->setEnabled(true);
6358 m_OpsFocusMechanics->focusOutSteps->setEnabled(false);
6359
6360 // Set Walk to just Classic on 1st time through
6361 if (m_OpsFocusMechanics->focusWalk->count() != 1)
6362 {
6363 m_OpsFocusMechanics->focusWalk->clear();
6364 m_OpsFocusMechanics->focusWalk->addItem(m_FocusWalkText.at(FOCUS_WALK_CLASSIC));
6365 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC);
6366 }
6367 break;
6368
6369 case FOCUS_POLYNOMIAL:
6370 // Remove unused widgets from the grid and hide them
6371 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel);
6372 m_OpsFocusProcess->focusMultiRowAverageLabel->hide();
6373 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverage);
6374 m_OpsFocusProcess->focusMultiRowAverage->hide();
6375
6376 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel);
6377 m_OpsFocusProcess->focusGaussianSigmaLabel->hide();
6378 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigma);
6379 m_OpsFocusProcess->focusGaussianSigma->hide();
6380
6381 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel);
6382 m_OpsFocusProcess->focusGaussianKernelSizeLabel->hide();
6383 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize);
6384 m_OpsFocusProcess->focusGaussianKernelSize->hide();
6385
6386 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel);
6387 m_OpsFocusProcess->focusStarPSFLabel->hide();
6388 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF);
6389 m_OpsFocusProcess->focusStarPSF->hide();
6390
6391 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusUseWeights);
6392 m_OpsFocusProcess->focusUseWeights->hide();
6393
6394 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2LimitLabel);
6395 m_OpsFocusProcess->focusR2LimitLabel->hide();
6396 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2Limit);
6397 m_OpsFocusProcess->focusR2Limit->hide();
6398
6399 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusRefineCurveFit);
6400 m_OpsFocusProcess->focusRefineCurveFit->hide();
6401 m_OpsFocusProcess->focusRefineCurveFit->setChecked(false);
6402
6403 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusToleranceLabel);
6404 m_OpsFocusProcess->focusToleranceLabel->hide();
6405 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusTolerance);
6406 m_OpsFocusProcess->focusTolerance->hide();
6407
6408 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThresholdLabel);
6409 m_OpsFocusProcess->focusThresholdLabel->hide();
6410 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThreshold);
6411 m_OpsFocusProcess->focusThreshold->hide();
6412
6413 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusDenoise);
6414 m_OpsFocusProcess->focusDenoise->hide();
6415
6416 // Donut buster not available
6417 m_OpsFocusProcess->focusDonut->hide();
6418 m_OpsFocusProcess->focusDonut->setChecked(false);
6419
6420 // Scan Start Pos not available
6421 m_OpsFocusProcess->focusScanStartPos->hide();
6422 m_OpsFocusProcess->focusScanStartPos->setChecked(false);
6423
6424 // Set Measure to just HFR
6425 if (m_OpsFocusProcess->focusStarMeasure->count() != 1)
6426 {
6427 m_OpsFocusProcess->focusStarMeasure->clear();
6428 m_OpsFocusProcess->focusStarMeasure->addItem(m_StarMeasureText.at(FOCUS_STAR_HFR));
6429 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR);
6430 }
6431
6432 // Add necessary widgets to the grid
6433 // Curve fit can only be QUADRATIC so only allow this value
6434 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFitLabel, 1, 2);
6435 m_OpsFocusProcess->focusCurveFitLabel->show();
6436 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFit, 1, 3);
6437 m_OpsFocusProcess->focusCurveFit->show();
6438 if (m_OpsFocusProcess->focusCurveFit->count() != 1)
6439 {
6440 m_OpsFocusProcess->focusCurveFit->clear();
6441 m_OpsFocusProcess->focusCurveFit->addItem(m_CurveFitText.at(CurveFitting::FOCUS_QUADRATIC));
6442 m_OpsFocusProcess->focusCurveFit->setCurrentIndex(CurveFitting::FOCUS_QUADRATIC);
6443 }
6444
6445 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusToleranceLabel, 3, 0);
6446 m_OpsFocusProcess->focusToleranceLabel->show();
6447 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusTolerance, 3, 1);
6448 m_OpsFocusProcess->focusTolerance->show();
6449
6450 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCountLabel, 3, 2);
6451 m_OpsFocusProcess->focusFramesCountLabel->show();
6452 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCount, 3, 3);
6453 m_OpsFocusProcess->focusFramesCount->show();
6454
6455 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusHFRFramesCountLabel, 4, 2);
6456 m_OpsFocusProcess->focusHFRFramesCountLabel->show();
6457 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusHFRFramesCount, 4, 3);
6458 m_OpsFocusProcess->focusHFRFramesCount->show();
6459
6460 if (m_FocusDetection == ALGORITHM_THRESHOLD)
6461 {
6462 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThresholdLabel, 5, 0);
6463 m_OpsFocusProcess->focusThresholdLabel->show();
6464 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThreshold, 5, 1);
6465 m_OpsFocusProcess->focusThreshold->show();
6466 }
6467 else if (m_FocusDetection == ALGORITHM_BAHTINOV)
6468 {
6469 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverageLabel, 5, 0);
6470 m_OpsFocusProcess->focusMultiRowAverageLabel->show();
6471 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverage, 5, 1);
6472 m_OpsFocusProcess->focusMultiRowAverage->show();
6473
6474 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigmaLabel, 5, 2);
6475 m_OpsFocusProcess->focusGaussianSigmaLabel->show();
6476 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigma, 5, 3);
6477 m_OpsFocusProcess->focusGaussianSigma->show();
6478
6479 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel, 6, 0);
6480 m_OpsFocusProcess->focusGaussianKernelSizeLabel->show();
6481 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSize, 6, 1);
6482 m_OpsFocusProcess->focusGaussianKernelSize->show();
6483 }
6484
6485 // Aberration Inspector button
6486 startAbInsB->setEnabled(canAbInsStart());
6487
6488 // Settings changes
6489 // Disable adaptive focus
6490 m_OpsFocusSettings->focusAdaptive->setChecked(false);
6491 m_OpsFocusSettings->focusAdaptStart->setChecked(false);
6492 m_OpsFocusSettings->adaptiveFocusGroup->setEnabled(false);
6493
6494 // Mechanics changes
6495 m_OpsFocusMechanics->focusMaxSingleStep->setEnabled(true);
6496 m_OpsFocusMechanics->focusOutSteps->setEnabled(false);
6497
6498 // Set Walk to just Classic on 1st time through
6499 if (m_OpsFocusMechanics->focusWalk->count() != 1)
6500 {
6501 m_OpsFocusMechanics->focusWalk->clear();
6502 m_OpsFocusMechanics->focusWalk->addItem(m_FocusWalkText.at(FOCUS_WALK_CLASSIC));
6503 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC);
6504 }
6505 break;
6506
6507 case FOCUS_LINEAR:
6508 // Remove unused widgets from the grid and hide them
6509 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel);
6510 m_OpsFocusProcess->focusMultiRowAverageLabel->hide();
6511 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverage);
6512 m_OpsFocusProcess->focusMultiRowAverage->hide();
6513
6514 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel);
6515 m_OpsFocusProcess->focusGaussianSigmaLabel->hide();
6516 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigma);
6517 m_OpsFocusProcess->focusGaussianSigma->hide();
6518
6519 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel);
6520 m_OpsFocusProcess->focusGaussianKernelSizeLabel->hide();
6521 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize);
6522 m_OpsFocusProcess->focusGaussianKernelSize->hide();
6523
6524 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThresholdLabel);
6525 m_OpsFocusProcess->focusThresholdLabel->hide();
6526 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThreshold);
6527 m_OpsFocusProcess->focusThreshold->hide();
6528
6529 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel);
6530 m_OpsFocusProcess->focusStarPSFLabel->hide();
6531 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF);
6532 m_OpsFocusProcess->focusStarPSF->hide();
6533
6534 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusUseWeights);
6535 m_OpsFocusProcess->focusUseWeights->hide();
6536
6537 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2LimitLabel);
6538 m_OpsFocusProcess->focusR2LimitLabel->hide();
6539 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusR2Limit);
6540 m_OpsFocusProcess->focusR2Limit->hide();
6541
6542 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusRefineCurveFit);
6543 m_OpsFocusProcess->focusRefineCurveFit->hide();
6544 m_OpsFocusProcess->focusRefineCurveFit->setChecked(false);
6545
6546 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusDenoise);
6547 m_OpsFocusProcess->focusDenoise->hide();
6548
6549 // Donut buster not available
6550 m_OpsFocusProcess->focusDonut->hide();
6551 m_OpsFocusProcess->focusDonut->setChecked(false);
6552
6553 // Scan Start Pos not available
6554 m_OpsFocusProcess->focusScanStartPos->hide();
6555 m_OpsFocusProcess->focusScanStartPos->setChecked(false);
6556
6557 // Set Measure to just HFR
6558 if (m_OpsFocusProcess->focusStarMeasure->count() != 1)
6559 {
6560 m_OpsFocusProcess->focusStarMeasure->clear();
6561 m_OpsFocusProcess->focusStarMeasure->addItem(m_StarMeasureText.at(FOCUS_STAR_HFR));
6562 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR);
6563 }
6564
6565 // Add necessary widgets to the grid
6566 // For Linear the only allowable CurveFit is Quadratic
6567 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFitLabel, 1, 2);
6568 m_OpsFocusProcess->focusCurveFitLabel->show();
6569 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFit, 1, 3);
6570 m_OpsFocusProcess->focusCurveFit->show();
6571 if (m_OpsFocusProcess->focusCurveFit->count() != 1)
6572 {
6573 m_OpsFocusProcess->focusCurveFit->clear();
6574 m_OpsFocusProcess->focusCurveFit->addItem(m_CurveFitText.at(CurveFitting::FOCUS_QUADRATIC));
6575 m_OpsFocusProcess->focusCurveFit->setCurrentIndex(CurveFitting::FOCUS_QUADRATIC);
6576 }
6577
6578 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusToleranceLabel, 3, 0);
6579 m_OpsFocusProcess->focusToleranceLabel->show();
6580 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusTolerance, 3, 1);
6581 m_OpsFocusProcess->focusTolerance->show();
6582
6583 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCountLabel, 3, 2);
6584 m_OpsFocusProcess->focusFramesCountLabel->show();
6585 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCount, 3, 3);
6586 m_OpsFocusProcess->focusFramesCount->show();
6587
6588 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusHFRFramesCountLabel, 4, 2);
6589 m_OpsFocusProcess->focusHFRFramesCountLabel->show();
6590 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusHFRFramesCount, 4, 3);
6591 m_OpsFocusProcess->focusHFRFramesCount->show();
6592
6593 if (m_FocusDetection == ALGORITHM_THRESHOLD)
6594 {
6595 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThresholdLabel, 5, 0);
6596 m_OpsFocusProcess->focusThresholdLabel->show();
6597 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThreshold, 5, 1);
6598 m_OpsFocusProcess->focusThreshold->show();
6599 }
6600 else if (m_FocusDetection == ALGORITHM_BAHTINOV)
6601 {
6602 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverageLabel, 5, 0);
6603 m_OpsFocusProcess->focusMultiRowAverageLabel->show();
6604 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverage, 5, 1);
6605 m_OpsFocusProcess->focusMultiRowAverage->show();
6606
6607 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigmaLabel, 5, 2);
6608 m_OpsFocusProcess->focusGaussianSigmaLabel->show();
6609 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigma, 5, 3);
6610 m_OpsFocusProcess->focusGaussianSigma->show();
6611
6612 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel, 6, 0);
6613 m_OpsFocusProcess->focusGaussianKernelSizeLabel->show();
6614 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSize, 6, 1);
6615 m_OpsFocusProcess->focusGaussianKernelSize->show();
6616 }
6617
6618 // Aberration Inspector button
6619 startAbInsB->setEnabled(canAbInsStart());
6620
6621 // Settings changes
6622 // Disable adaptive focus
6623 m_OpsFocusSettings->focusAdaptive->setChecked(false);
6624 m_OpsFocusSettings->focusAdaptStart->setChecked(false);
6625 m_OpsFocusSettings->adaptiveFocusGroup->setEnabled(false);
6626
6627 // Mechanics changes
6628 m_OpsFocusMechanics->focusMaxSingleStep->setEnabled(false);
6629 m_OpsFocusMechanics->focusOutSteps->setEnabled(true);
6630
6631 // Set Walk to just Classic on 1st time through
6632 if (m_OpsFocusMechanics->focusWalk->count() != 1)
6633 {
6634 m_OpsFocusMechanics->focusWalk->clear();
6635 m_OpsFocusMechanics->focusWalk->addItem(m_FocusWalkText.at(FOCUS_WALK_CLASSIC));
6636 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC);
6637 }
6638 break;
6639
6640 case FOCUS_LINEAR1PASS:
6641 // Remove unused widgets from the grid and hide them
6642 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverageLabel);
6643 m_OpsFocusProcess->focusMultiRowAverageLabel->hide();
6644 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusMultiRowAverage);
6645 m_OpsFocusProcess->focusMultiRowAverage->hide();
6646
6647 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigmaLabel);
6648 m_OpsFocusProcess->focusGaussianSigmaLabel->hide();
6649 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianSigma);
6650 m_OpsFocusProcess->focusGaussianSigma->hide();
6651
6652 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel);
6653 m_OpsFocusProcess->focusGaussianKernelSizeLabel->hide();
6654 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusGaussianKernelSize);
6655 m_OpsFocusProcess->focusGaussianKernelSize->hide();
6656
6657 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThresholdLabel);
6658 m_OpsFocusProcess->focusThresholdLabel->hide();
6659 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusThreshold);
6660 m_OpsFocusProcess->focusThreshold->hide();
6661
6662 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusToleranceLabel);
6663 m_OpsFocusProcess->focusToleranceLabel->hide();
6664 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusTolerance);
6665 m_OpsFocusProcess->focusTolerance->hide();
6666
6667 // Setup Measure with all options for detection=SEP and curveFit=HYPERBOLA or PARABOLA
6668 // or just HDR otherwise. Reset on 1st time through only
6669 if (m_FocusDetection == ALGORITHM_SEP && m_CurveFit != CurveFitting::FOCUS_QUADRATIC)
6670 {
6671 if (m_OpsFocusProcess->focusStarMeasure->count() != m_StarMeasureText.count())
6672 {
6673 m_OpsFocusProcess->focusStarMeasure->clear();
6674 m_OpsFocusProcess->focusStarMeasure->addItems(m_StarMeasureText);
6675 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR);
6676 }
6677 // If we don't have openCV installed grey out the unavailble star measures. As opposed to just
6678 // hiding these measures, this will remind folks that want to try these that they need to install openCV
6679#if !defined(HAVE_OPENCV)
6680 QStandardItemModel *model = dynamic_cast<QStandardItemModel *>(m_OpsFocusProcess->focusStarMeasure->model());
6681 model->item(FOCUS_STAR_STDDEV, 0)->setEnabled(false);
6682 model->item(FOCUS_STAR_SOBEL, 0)->setEnabled(false);
6683 model->item(FOCUS_STAR_LAPLASSIAN, 0)->setEnabled(false);
6684 model->item(FOCUS_STAR_CANNY, 0)->setEnabled(false);
6685#endif
6686 }
6687 else if (m_FocusDetection != ALGORITHM_SEP || m_CurveFit == CurveFitting::FOCUS_QUADRATIC)
6688 {
6689 if (m_OpsFocusProcess->focusStarMeasure->count() != 1)
6690 {
6691 m_OpsFocusProcess->focusStarMeasure->clear();
6692 m_OpsFocusProcess->focusStarMeasure->addItem(m_StarMeasureText.at(FOCUS_STAR_HFR));
6693 m_OpsFocusProcess->focusStarMeasure->setCurrentIndex(FOCUS_STAR_HFR);
6694 }
6695 }
6696
6697 // Add necessary widgets to the grid
6698 // All Curve Fits are available - default to Hyperbola which is the best option
6699 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFitLabel, 1, 2);
6700 m_OpsFocusProcess->focusCurveFitLabel->show();
6701 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusCurveFit, 1, 3);
6702 m_OpsFocusProcess->focusCurveFit->show();
6703 if (m_OpsFocusProcess->focusCurveFit->count() != m_CurveFitText.count())
6704 {
6705 m_OpsFocusProcess->focusCurveFit->clear();
6706 m_OpsFocusProcess->focusCurveFit->addItems(m_CurveFitText);
6707 m_OpsFocusProcess->focusCurveFit->setCurrentIndex(CurveFitting::FOCUS_HYPERBOLA);
6708 }
6709
6710 if (m_StarMeasure == FOCUS_STAR_STDDEV || m_StarMeasure == FOCUS_STAR_SOBEL ||
6711 m_StarMeasure == FOCUS_STAR_LAPLASSIAN || m_StarMeasure == FOCUS_STAR_CANNY)
6712 {
6713 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusUseWeights);
6714 m_OpsFocusProcess->focusUseWeights->hide();
6715 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusDenoise, 3, 0, 1, 2); // Spans 2 columns
6716 m_OpsFocusProcess->focusDenoise->show();
6717 }
6718 else
6719 {
6720 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusDenoise);
6721 m_OpsFocusProcess->focusDenoise->hide();
6722 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusUseWeights, 3, 0, 1, 2); // Spans 2 columns
6723 m_OpsFocusProcess->focusUseWeights->show();
6724 }
6725
6726 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusR2LimitLabel, 3, 2);
6727 m_OpsFocusProcess->focusR2LimitLabel->show();
6728 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusR2Limit, 3, 3);
6729 m_OpsFocusProcess->focusR2Limit->show();
6730
6731 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusRefineCurveFit, 4, 0, 1, 2); // Spans 2 columns
6732 m_OpsFocusProcess->focusRefineCurveFit->show();
6733
6734 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCountLabel, 4, 2);
6735 m_OpsFocusProcess->focusFramesCountLabel->show();
6736 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusFramesCount, 4, 3);
6737 m_OpsFocusProcess->focusFramesCount->show();
6738
6739 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusHFRFramesCountLabel, 5, 2);
6740 m_OpsFocusProcess->focusHFRFramesCountLabel->show();
6741 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusHFRFramesCount, 5, 3);
6742 m_OpsFocusProcess->focusHFRFramesCount->show();
6743
6744 if (m_FocusDetection == ALGORITHM_THRESHOLD)
6745 {
6746 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThresholdLabel, 6, 0);
6747 m_OpsFocusProcess->focusThresholdLabel->show();
6748 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusThreshold, 6, 1);
6749 m_OpsFocusProcess->focusThreshold->show();
6750 }
6751 else if (m_FocusDetection == ALGORITHM_BAHTINOV)
6752 {
6753 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverageLabel, 6, 0);
6754 m_OpsFocusProcess->focusMultiRowAverageLabel->show();
6755 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusMultiRowAverage, 6, 1);
6756 m_OpsFocusProcess->focusMultiRowAverage->show();
6757
6758 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigmaLabel, 6, 2);
6759 m_OpsFocusProcess->focusGaussianSigmaLabel->show();
6760 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianSigma, 6, 3);
6761 m_OpsFocusProcess->focusGaussianSigma->show();
6762
6763 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSizeLabel, 7, 0);
6764 m_OpsFocusProcess->focusGaussianKernelSizeLabel->show();
6765 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusGaussianKernelSize, 7, 1);
6766 m_OpsFocusProcess->focusGaussianKernelSize->show();
6767 }
6768
6769 // Donut Buster
6770 m_OpsFocusProcess->focusDonut->show();
6771 m_OpsFocusProcess->focusDonut->setEnabled(true);
6772
6773 // Scan Start Pos
6774 m_OpsFocusProcess->focusScanStartPos->show();
6775 m_OpsFocusProcess->focusScanStartPos->setEnabled(true);
6776
6777 // Aberration Inspector button
6778 startAbInsB->setEnabled(canAbInsStart());
6779
6780 // Settings changes
6781 // Enable adaptive focus for Absolute focusers
6782 m_OpsFocusSettings->adaptiveFocusGroup->setEnabled(canAbsMove);
6783
6784 // Mechanics changes
6785 // Firstly max Single Step is not used by Linear 1 Pass
6786 m_OpsFocusMechanics->focusMaxSingleStep->setEnabled(false);
6787 m_OpsFocusMechanics->focusOutSteps->setEnabled(true);
6788
6789 // Set Walk to just Classic for Quadratic, all options otherwise
6790 if (m_CurveFit == CurveFitting::FOCUS_QUADRATIC)
6791 {
6792 if (m_OpsFocusMechanics->focusWalk->count() != 1)
6793 {
6794 m_OpsFocusMechanics->focusWalk->clear();
6795 m_OpsFocusMechanics->focusWalk->addItem(m_FocusWalkText.at(FOCUS_WALK_CLASSIC));
6796 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC);
6797 }
6798 }
6799 else
6800 {
6801 if (m_OpsFocusMechanics->focusWalk->count() != m_FocusWalkText.count())
6802 {
6803 m_OpsFocusMechanics->focusWalk->clear();
6804 m_OpsFocusMechanics->focusWalk->addItems(m_FocusWalkText);
6805 m_OpsFocusMechanics->focusWalk->setCurrentIndex(FOCUS_WALK_CLASSIC);
6806 }
6807 }
6808 break;
6809 }
6810}
6811
6812void Focus::setCurveFit(CurveFitting::CurveFit curve)
6813{
6814 if (m_OpsFocusProcess->focusCurveFit->currentIndex() == -1)
6815 return;
6816
6817 static bool first = true;
6818 if (!first && m_CurveFit == curve)
6819 return;
6820
6821 first = false;
6822
6823 m_CurveFit = curve;
6824 setFocusAlgorithm(static_cast<Algorithm> (m_OpsFocusProcess->focusAlgorithm->currentIndex()));
6825 setUseWeights();
6826 setDenoise();
6827 setDonutBuster();
6828
6829 switch(m_CurveFit)
6830 {
6831 case CurveFitting::FOCUS_QUADRATIC:
6832 m_OpsFocusProcess->focusR2Limit->setEnabled(false);
6833 m_OpsFocusProcess->focusRefineCurveFit->setChecked(false);
6834 m_OpsFocusProcess->focusRefineCurveFit->setEnabled(false);
6835 break;
6836
6837 case CurveFitting::FOCUS_HYPERBOLA:
6838 m_OpsFocusProcess->focusR2Limit->setEnabled(true); // focusR2Limit allowed
6839 m_OpsFocusProcess->focusRefineCurveFit->setEnabled(true);
6840 break;
6841
6842 case CurveFitting::FOCUS_PARABOLA:
6843 m_OpsFocusProcess->focusR2Limit->setEnabled(true); // focusR2Limit allowed
6844 m_OpsFocusProcess->focusRefineCurveFit->setEnabled(true);
6845 break;
6846
6847 case CurveFitting::FOCUS_2DGAUSSIAN:
6848 m_OpsFocusProcess->focusR2Limit->setEnabled(true); // focusR2Limit allowed
6849 m_OpsFocusProcess->focusRefineCurveFit->setEnabled(true);
6850 break;
6851
6852 default:
6853 break;
6854 }
6855}
6856
6857void Focus::setStarMeasure(StarMeasure starMeasure)
6858{
6859 if (m_OpsFocusProcess->focusStarMeasure->currentIndex() == -1)
6860 return;
6861
6862 static bool first = true;
6863 if (!first && m_StarMeasure == starMeasure)
6864 return;
6865
6866 first = false;
6867
6868 m_StarMeasure = starMeasure;
6869 setFocusAlgorithm(static_cast<Algorithm> (m_OpsFocusProcess->focusAlgorithm->currentIndex()));
6870 setUseWeights();
6871 setDenoise();
6872 setDonutBuster();
6873 setAutoStarAndBox();
6874
6875 // So what is the best estimator of scale to use? Not much to choose from analysis on the sim.
6876 // Variance is the simplest but isn't robust in the presence of outliers.
6877 // MAD is probably better but Sn and Qn are theoretically a little better as they are both efficient and
6878 // able to deal with skew. Have picked Sn.
6879 switch(m_StarMeasure)
6880 {
6881 case FOCUS_STAR_HFR:
6882 m_OptDir = CurveFitting::OPTIMISATION_MINIMISE;
6883 m_FocusView->setStarsHFREnabled(true);
6884
6885 // Don't display the PSF widgets
6886 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel);
6887 m_OpsFocusProcess->focusStarPSFLabel->hide();
6888 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF);
6889 m_OpsFocusProcess->focusStarPSF->hide();
6890 break;
6891
6892 case FOCUS_STAR_HFR_ADJ:
6893 m_OptDir = CurveFitting::OPTIMISATION_MINIMISE;
6894 m_FocusView->setStarsHFREnabled(false);
6895
6896 // Don't display the PSF widgets
6897 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel);
6898 m_OpsFocusProcess->focusStarPSFLabel->hide();
6899 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF);
6900 m_OpsFocusProcess->focusStarPSF->hide();
6901 break;
6902
6903 case FOCUS_STAR_FWHM:
6904 m_OptDir = CurveFitting::OPTIMISATION_MINIMISE;
6905 // Ideally the FITSViewer would display FWHM. Until then disable HFRs to avoid confusion
6906 m_FocusView->setStarsHFREnabled(false);
6907
6908 // Display the PSF widgets
6909 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusStarPSFLabel, 2, 2);
6910 m_OpsFocusProcess->focusStarPSFLabel->show();
6911 m_OpsFocusProcess->gridLayoutProcess->addWidget(m_OpsFocusProcess->focusStarPSF, 2, 3);
6912 m_OpsFocusProcess->focusStarPSF->show();
6913 break;
6914
6915 case FOCUS_STAR_NUM_STARS:
6916 m_OptDir = CurveFitting::OPTIMISATION_MAXIMISE;
6917 m_FocusView->setStarsHFREnabled(true);
6918
6919 // Don't display the PSF widgets
6920 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel);
6921 m_OpsFocusProcess->focusStarPSFLabel->hide();
6922 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF);
6923 m_OpsFocusProcess->focusStarPSF->hide();
6924 break;
6925
6926 case FOCUS_STAR_FOURIER_POWER:
6927 m_OptDir = CurveFitting::OPTIMISATION_MAXIMISE;
6928 m_FocusView->setStarsHFREnabled(true);
6929
6930 // Don't display the PSF widgets
6931 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel);
6932 m_OpsFocusProcess->focusStarPSFLabel->hide();
6933 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF);
6934 m_OpsFocusProcess->focusStarPSF->hide();
6935 break;
6936
6937 case FOCUS_STAR_STDDEV:
6938 case FOCUS_STAR_SOBEL:
6939 case FOCUS_STAR_LAPLASSIAN:
6940 case FOCUS_STAR_CANNY:
6941 m_OptDir = CurveFitting::OPTIMISATION_MAXIMISE;
6942 m_FocusView->setStarsHFREnabled(true);
6943
6944 // Don't display the PSF widgets
6945 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSFLabel);
6946 m_OpsFocusProcess->focusStarPSFLabel->hide();
6947 m_OpsFocusProcess->gridLayoutProcess->removeWidget(m_OpsFocusProcess->focusStarPSF);
6948 m_OpsFocusProcess->focusStarPSF->hide();
6949 break;
6950
6951 default:
6952 break;
6953 }
6954}
6955
6956void Focus::setStarPSF(StarPSF starPSF)
6957{
6958 m_StarPSF = starPSF;
6959}
6960
6961void Focus::setStarUnits(StarUnits starUnits)
6962{
6963 m_StarUnits = starUnits;
6964}
6965
6966void Focus::setWalk(FocusWalk walk)
6967{
6968 if (m_OpsFocusMechanics->focusWalk->currentIndex() == -1)
6969 return;
6970
6971 static bool first = true;
6972 if (!first && m_FocusWalk == walk)
6973 return;
6974
6975 first = false;
6976
6977 m_FocusWalk = walk;
6978
6979 switch(m_FocusWalk)
6980 {
6981 case FOCUS_WALK_CLASSIC:
6982 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusNumStepsLabel,
6983 m_OpsFocusMechanics->focusOutStepsLabel);
6984 m_OpsFocusMechanics->focusNumStepsLabel->hide();
6985 m_OpsFocusMechanics->focusOutStepsLabel->show();
6986 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusNumSteps,
6987 m_OpsFocusMechanics->focusOutSteps);
6988 m_OpsFocusMechanics->focusNumSteps->hide();
6989 m_OpsFocusMechanics->focusOutSteps->show();
6990 break;
6991
6992 case FOCUS_WALK_FIXED_STEPS:
6993 case FOCUS_WALK_CFZ_SHUFFLE:
6994 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusOutStepsLabel,
6995 m_OpsFocusMechanics->focusNumStepsLabel);
6996 m_OpsFocusMechanics->focusOutStepsLabel->hide();
6997 m_OpsFocusMechanics->focusNumStepsLabel->show();
6998 m_OpsFocusMechanics->gridLayoutMechanics->replaceWidget(m_OpsFocusMechanics->focusOutSteps,
6999 m_OpsFocusMechanics->focusNumSteps);
7000 m_OpsFocusMechanics->focusOutSteps->hide();
7001 m_OpsFocusMechanics->focusNumSteps->show();
7002 break;
7003
7004 default:
7005 break;
7006 }
7007 setDonutBuster();
7008}
7009
7010double Focus::getStarUnits(const StarMeasure starMeasure, const StarUnits starUnits)
7011{
7012 if (starUnits == FOCUS_UNITS_PIXEL || starMeasure == FOCUS_STAR_NUM_STARS || starMeasure == FOCUS_STAR_FOURIER_POWER
7013 || starMeasure == FOCUS_STAR_STDDEV || starMeasure == FOCUS_STAR_SOBEL || starMeasure == FOCUS_STAR_LAPLASSIAN
7014 || starMeasure == FOCUS_STAR_CANNY)
7015 return 1.0;
7016 if (m_CcdPixelSizeX <= 0.0 || m_FocalLength <= 0.0)
7017 return 1.0;
7018
7019 // Convert to arcsecs from pixels. PixelSize / FocalLength * 206265
7020 return m_CcdPixelSizeX / m_FocalLength * 206.265;
7021}
7022
7023void Focus::calcCFZ()
7024{
7025 double cfzMicrons, cfzSteps;
7026 double cfzCameraSteps = calcCameraCFZ() / m_CFZUI->focusCFZStepSize->value();
7027
7028 switch(static_cast<Focus::CFZAlgorithm> (m_CFZUI->focusCFZAlgorithm->currentIndex()))
7029 {
7030 case Focus::FOCUS_CFZ_CLASSIC:
7031 // CFZ = 4.88 t λ f2
7032 cfzMicrons = 4.88f * m_CFZUI->focusCFZTolerance->value() * m_CFZUI->focusCFZWavelength->value() / 1000.0f *
7033 pow(m_CFZUI->focusCFZFNumber->value(), 2.0f);
7034 cfzSteps = cfzMicrons / m_CFZUI->focusCFZStepSize->value();
7035 m_cfzSteps = std::round(std::max(cfzSteps, cfzCameraSteps));
7036 m_CFZUI->focusCFZFormula->setText("CFZ = 4.88 t λ f²");
7037 m_CFZUI->focusCFZ->setText(QString("%1 μm").arg(cfzMicrons, 0, 'f', 0));
7038 m_CFZUI->focusCFZSteps->setText(QString("%1 steps").arg(cfzSteps, 0, 'f', 0));
7039 m_CFZUI->focusCFZCameraSteps->setText(QString("%1 steps").arg(cfzCameraSteps, 0, 'f', 0));
7040 m_CFZUI->focusCFZFinal->setText(QString("%1 steps").arg(m_cfzSteps, 0, 'f', 0));
7041
7042 // Remove widgets not relevant to this algo
7043 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTauLabel);
7044 m_CFZUI->focusCFZTauLabel->hide();
7045 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTau);
7046 m_CFZUI->focusCFZTau->hide();
7047 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZSeeingLabel);
7048 m_CFZUI->focusCFZSeeingLabel->hide();
7049 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZSeeing);
7050 m_CFZUI->focusCFZSeeing->hide();
7051
7052 // Add necessary widgets to the grid
7053 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZToleranceLabel, 1, 0);
7054 m_CFZUI->focusCFZToleranceLabel->show();
7055 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZTolerance, 1, 1);
7056 m_CFZUI->focusCFZTolerance->show();
7057 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZWavelengthLabel, 2, 0);
7058 m_CFZUI->focusCFZWavelengthLabel->show();
7059 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZWavelength, 2, 1);
7060 m_CFZUI->focusCFZWavelength->show();
7061 break;
7062
7063 case Focus::FOCUS_CFZ_WAVEFRONT:
7064 // CFZ = 4 t λ f2
7065 cfzMicrons = 4.0f * m_CFZUI->focusCFZTolerance->value() * m_CFZUI->focusCFZWavelength->value() / 1000.0f * pow(
7066 m_CFZUI->focusCFZFNumber->value(),
7067 2.0f);
7068 cfzSteps = cfzMicrons / m_CFZUI->focusCFZStepSize->value();
7069 m_cfzSteps = std::round(std::max(cfzSteps, cfzCameraSteps));
7070 m_CFZUI->focusCFZFormula->setText("CFZ = 4 t λ f²");
7071 m_CFZUI->focusCFZ->setText(QString("%1 μm").arg(cfzMicrons, 0, 'f', 0));
7072 m_CFZUI->focusCFZSteps->setText(QString("%1 steps").arg(cfzSteps, 0, 'f', 0));
7073 m_CFZUI->focusCFZCameraSteps->setText(QString("%1 steps").arg(cfzCameraSteps, 0, 'f', 0));
7074 m_CFZUI->focusCFZFinal->setText(QString("%1 steps").arg(m_cfzSteps, 0, 'f', 0));
7075
7076 // Remove widgets not relevant to this algo
7077 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTauLabel);
7078 m_CFZUI->focusCFZTauLabel->hide();
7079 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTau);
7080 m_CFZUI->focusCFZTau->hide();
7081 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZSeeingLabel);
7082 m_CFZUI->focusCFZSeeingLabel->hide();
7083 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZSeeing);
7084 m_CFZUI->focusCFZSeeing->hide();
7085
7086 // Add necessary widgets to the grid
7087 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZToleranceLabel, 1, 0);
7088 m_CFZUI->focusCFZToleranceLabel->show();
7089 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZTolerance, 1, 1);
7090 m_CFZUI->focusCFZTolerance->show();
7091 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZWavelengthLabel, 2, 0);
7092 m_CFZUI->focusCFZWavelengthLabel->show();
7093 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZWavelength, 2, 1);
7094 m_CFZUI->focusCFZWavelength->show();
7095 break;
7096
7097 case Focus::FOCUS_CFZ_GOLD:
7098 // CFZ = 0.00225 √τ θ f² A
7099 cfzMicrons = 0.00225f * pow(m_CFZUI->focusCFZTau->value(), 0.5f) * m_CFZUI->focusCFZSeeing->value()
7100 * pow(m_CFZUI->focusCFZFNumber->value(), 2.0f) * m_CFZUI->focusCFZAperture->value();
7101 cfzSteps = cfzMicrons / m_CFZUI->focusCFZStepSize->value();
7102 m_cfzSteps = std::round(std::max(cfzSteps, cfzCameraSteps));
7103 m_CFZUI->focusCFZFormula->setText("CFZ = 0.00225 √τ θ f² A");
7104 m_CFZUI->focusCFZ->setText(QString("%1 μm").arg(cfzMicrons, 0, 'f', 0));
7105 m_CFZUI->focusCFZSteps->setText(QString("%1 steps").arg(cfzSteps, 0, 'f', 0));
7106 m_CFZUI->focusCFZCameraSteps->setText(QString("%1 steps").arg(cfzCameraSteps, 0, 'f', 0));
7107 m_CFZUI->focusCFZFinal->setText(QString("%1 steps").arg(m_cfzSteps, 0, 'f', 0));
7108
7109 // Remove widgets not relevant to this algo
7110 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZToleranceLabel);
7111 m_CFZUI->focusCFZToleranceLabel->hide();
7112 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZTolerance);
7113 m_CFZUI->focusCFZTolerance->hide();
7114 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZWavelengthLabel);
7115 m_CFZUI->focusCFZWavelengthLabel->hide();
7116 m_CFZUI->gridLayoutCFZ->removeWidget(m_CFZUI->focusCFZWavelength);
7117 m_CFZUI->focusCFZWavelength->hide();
7118
7119 // Add necessary widgets to the grid
7120 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZTauLabel, 1, 0);
7121 m_CFZUI->focusCFZTauLabel->show();
7122 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZTau, 1, 1);
7123 m_CFZUI->focusCFZTau->show();
7124 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZSeeingLabel, 2, 0);
7125 m_CFZUI->focusCFZSeeingLabel->show();
7126 m_CFZUI->gridLayoutCFZ->addWidget(m_CFZUI->focusCFZSeeing, 2, 1);
7127 m_CFZUI->focusCFZSeeing->show();
7128 break;
7129 }
7130 if (linearFocuser != nullptr && linearFocuser->isDone())
7131 emit drawCFZ(linearFocuser->solution(), linearFocuser->solutionValue(), m_cfzSteps,
7132 m_CFZUI->focusCFZDisplayVCurve->isChecked());
7133}
7134
7135// Calculate the CFZ of the camera in microns using
7136// CFZcamera = pixel_size * f^2 * A
7137// pixel_size in microns and A in mm so divide A by 1000.
7138double Focus::calcCameraCFZ()
7139{
7140 return m_CcdPixelSizeX * pow(m_CFZUI->focusCFZFNumber->value(), 2.0) * m_CFZUI->focusCFZAperture->value() / 1000.0;
7141}
7142
7143void Focus::wavelengthChanged()
7144{
7145 // The static data for the wavelength held against the filter has been updated so use the new value
7146 if (m_FilterManager)
7147 {
7148 m_CFZUI->focusCFZWavelength->setValue(m_FilterManager->getFilterWavelength(filter()));
7149 calcCFZ();
7150 }
7151}
7152
7153void Focus::resetCFZToOT()
7154{
7155 // Set the aperture and focal ratio for the scope on the connected optical train
7156 m_CFZUI->focusCFZFNumber->setValue(m_FocalRatio);
7157 m_CFZUI->focusCFZAperture->setValue(m_Aperture);
7158
7159 // Set the wavelength from the active filter
7160 if (m_FilterManager)
7161 {
7162 if (m_CFZUI->focusCFZWavelength->value() != m_FilterManager->getFilterWavelength(filter()))
7163 m_CFZUI->focusCFZWavelength->setValue(m_FilterManager->getFilterWavelength(filter()));
7164 }
7165 calcCFZ();
7166}
7167
7168void Focus::setState(FocusState newState)
7169{
7170 qCDebug(KSTARS_EKOS_FOCUS) << "Focus State changes from" << getFocusStatusString(m_state) << "to" << getFocusStatusString(
7171 newState);
7172 m_state = newState;
7173 emit newStatus(m_state, opticalTrain());
7174}
7175
7176void Focus::initView()
7177{
7178 m_FocusView.reset(new FITSView(focusingWidget, FITS_FOCUS));
7179 m_FocusView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
7180 m_FocusView->setBaseSize(focusingWidget->size());
7181 m_FocusView->createFloatingToolBar();
7182 QVBoxLayout *vlayout = new QVBoxLayout();
7183 vlayout->addWidget(m_FocusView.get());
7184 focusingWidget->setLayout(vlayout);
7185 connect(m_FocusView.get(), &FITSView::trackingStarSelected, this, &Ekos::Focus::focusStarSelected, Qt::UniqueConnection);
7186 m_FocusView->setStarsEnabled(true);
7187 m_FocusView->setStarsHFREnabled(true);
7188}
7189
7190void Focus::initHelperObjects()
7191{
7192 // Objects to do with focus measures
7193 starFitting.reset(new CurveFitting());
7194 focusFWHM.reset(new FocusFWHM(m_ScaleCalc));
7195 focusFourierPower.reset(new FocusFourierPower(m_ScaleCalc));
7196#if defined(HAVE_OPENCV)
7197 focusBlurriness.reset(new FocusBlurriness());
7198#endif
7199
7200 // Adaptive Focus
7201 adaptFocus.reset(new AdaptiveFocus(this));
7202
7203 // Focus Advisor
7204 focusAdvisor.reset(new FocusAdvisor(this));
7205}
7206
7207QVariantMap Focus::getAllSettings() const
7208{
7209 QVariantMap settings;
7210
7211 // All Combo Boxes
7212 for (auto &oneWidget : findChildren<QComboBox*>())
7213 settings.insert(oneWidget->objectName(), oneWidget->currentText());
7214
7215 // All Double Spin Boxes
7216 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
7217 settings.insert(oneWidget->objectName(), oneWidget->value());
7218
7219 // All Spin Boxes
7220 for (auto &oneWidget : findChildren<QSpinBox*>())
7221 settings.insert(oneWidget->objectName(), oneWidget->value());
7222
7223 // All Checkboxes
7224 for (auto &oneWidget : findChildren<QCheckBox*>())
7225 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
7226
7227 // All Checkable Groupboxes
7228 for (auto &oneWidget : findChildren<QGroupBox*>())
7229 if (oneWidget->isCheckable())
7230 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
7231
7232 // All Splitters
7233 for (auto &oneWidget : findChildren<QSplitter*>())
7234 settings.insert(oneWidget->objectName(), QString::fromUtf8(oneWidget->saveState().toBase64()));
7235
7236 // All Radio Buttons
7237 for (auto &oneWidget : findChildren<QRadioButton*>())
7238 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
7239
7240 return settings;
7241}
7242
7243void Focus::setAllSettings(const QVariantMap &settings)
7244{
7245 // Disconnect settings that we don't end up calling syncSettings while
7246 // performing the changes.
7247 disconnectSyncSettings();
7248
7249 for (auto &name : settings.keys())
7250 {
7251 // Combo
7252 auto comboBox = findChild<QComboBox*>(name);
7253 if (comboBox)
7254 {
7255 syncControl(settings, name, comboBox);
7256 continue;
7257 }
7258
7259 // Double spinbox
7260 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
7261 if (doubleSpinBox)
7262 {
7263 syncControl(settings, name, doubleSpinBox);
7264 continue;
7265 }
7266
7267 // spinbox
7268 auto spinBox = findChild<QSpinBox*>(name);
7269 if (spinBox)
7270 {
7271 syncControl(settings, name, spinBox);
7272 continue;
7273 }
7274
7275 // checkbox
7276 auto checkbox = findChild<QCheckBox*>(name);
7277 if (checkbox)
7278 {
7279 syncControl(settings, name, checkbox);
7280 continue;
7281 }
7282
7283 // Checkable Groupboxes
7284 auto groupbox = findChild<QGroupBox*>(name);
7285 if (groupbox && groupbox->isCheckable())
7286 {
7287 syncControl(settings, name, groupbox);
7288 continue;
7289 }
7290
7291 // Splitters
7292 auto splitter = findChild<QSplitter*>(name);
7293 if (splitter)
7294 {
7295 syncControl(settings, name, splitter);
7296 continue;
7297 }
7298
7299 // Radio button
7300 auto radioButton = findChild<QRadioButton*>(name);
7301 if (radioButton)
7302 {
7303 syncControl(settings, name, radioButton);
7304 continue;
7305 }
7306 }
7307
7308 // Sync to options
7309 for (auto &key : settings.keys())
7310 {
7311 auto value = settings[key];
7312 // Save immediately
7313 Options::self()->setProperty(key.toLatin1(), value);
7314
7315 m_Settings[key] = value;
7316 m_GlobalSettings[key] = value;
7317 }
7318
7319 emit settingsUpdated(getAllSettings());
7320
7321 // Save to optical train specific settings as well
7322 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
7323 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Focus, m_Settings);
7324
7325 // Restablish connections
7326 connectSyncSettings();
7327
7328 // Once settings have been loaded run through routines to set state variables
7329 m_CurveFit = static_cast<CurveFitting::CurveFit> (m_OpsFocusProcess->focusCurveFit->currentIndex());
7330 setFocusDetection(static_cast<StarAlgorithm> (m_OpsFocusProcess->focusDetection->currentIndex()));
7331 setCurveFit(static_cast<CurveFitting::CurveFit>(m_OpsFocusProcess->focusCurveFit->currentIndex()));
7332 setStarMeasure(static_cast<StarMeasure>(m_OpsFocusProcess->focusStarMeasure->currentIndex()));
7333 setWalk(static_cast<FocusWalk>(m_OpsFocusMechanics->focusWalk->currentIndex()));
7334 selectImageMask();
7335}
7336
7337bool Focus::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget)
7338{
7339 QSpinBox *pSB = nullptr;
7340 QDoubleSpinBox *pDSB = nullptr;
7341 QCheckBox *pCB = nullptr;
7342 QGroupBox *pGB = nullptr;
7343 QComboBox *pComboBox = nullptr;
7344 QSplitter *pSplitter = nullptr;
7345 QRadioButton *pRadioButton = nullptr;
7346 bool ok = true;
7347
7348 if ((pSB = qobject_cast<QSpinBox *>(widget)))
7349 {
7350 const int value = settings[key].toInt(&ok);
7351 if (ok)
7352 {
7353 pSB->setValue(value);
7354 return true;
7355 }
7356 }
7357 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
7358 {
7359 const double value = settings[key].toDouble(&ok);
7360 if (ok)
7361 {
7362 pDSB->setValue(value);
7363 return true;
7364 }
7365 }
7366 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
7367 {
7368 const bool value = settings[key].toBool();
7369 if (value != pCB->isChecked())
7370 pCB->setChecked(value);
7371 return true;
7372 }
7373 else if ((pGB = qobject_cast<QGroupBox *>(widget)))
7374 {
7375 const bool value = settings[key].toBool();
7376 if (value != pGB->isChecked())
7377 pGB->setChecked(value);
7378 return true;
7379 }
7380 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
7381 {
7382 const bool value = settings[key].toBool();
7383 if (value)
7384 pRadioButton->setChecked(true);
7385 return true;
7386 }
7387 // ONLY FOR STRINGS, not INDEX
7388 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
7389 {
7390 const QString value = settings[key].toString();
7391 pComboBox->setCurrentText(value);
7392 return true;
7393 }
7394 else if ((pSplitter = qobject_cast<QSplitter *>(widget)))
7395 {
7396 const auto value = QByteArray::fromBase64(settings[key].toString().toUtf8());
7397 pSplitter->restoreState(value);
7398 return true;
7399 }
7400
7401 return false;
7402};
7403
7404void Focus::setupOpticalTrainManager()
7405{
7406 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &Focus::refreshOpticalTrain);
7407 connect(trainB, &QPushButton::clicked, this, [this]()
7408 {
7409 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
7410 });
7411 connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
7412 {
7413 ProfileSettings::Instance()->setOneSetting(ProfileSettings::FocusOpticalTrain,
7414 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index)));
7415 refreshOpticalTrain();
7416 emit trainChanged();
7417 });
7418}
7419
7420void Focus::refreshOpticalTrain()
7421{
7422 bool validSettings = false;
7423 opticalTrainCombo->blockSignals(true);
7424 opticalTrainCombo->clear();
7425 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
7426 trainB->setEnabled(true);
7427
7428 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::FocusOpticalTrain);
7429
7430 if (trainID.isValid())
7431 {
7432 auto id = trainID.toUInt();
7433
7434 // If train not found, select the first one available.
7435 if (OpticalTrainManager::Instance()->exists(id) == false)
7436 {
7437 qCWarning(KSTARS_EKOS_FOCUS) << "Optical train doesn't exist for id" << id;
7438 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
7439 }
7440
7441 auto name = OpticalTrainManager::Instance()->name(id);
7442
7443 opticalTrainCombo->setCurrentText(name);
7444
7445 // Load train settings
7446 // This needs to be done near the start of this function as methods further down
7447 // cause settings to be updated, which in turn interferes with the persistence and
7448 // setup of settings in OpticalTrainSettings
7449 OpticalTrainSettings::Instance()->setOpticalTrainID(id);
7450
7451 auto focuser = OpticalTrainManager::Instance()->getFocuser(name);
7452 setFocuser(focuser);
7453
7454 auto scope = OpticalTrainManager::Instance()->getScope(name);
7455 double reducer = OpticalTrainManager::Instance()->getReducer(name);
7456 setScopeDetails(scope, reducer);
7457
7458 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Focus);
7459 if (settings.isValid())
7460 {
7461 validSettings = true;
7462 auto map = settings.toJsonObject().toVariantMap();
7463 if (map != m_Settings)
7464 {
7465 m_Settings.clear();
7466 setAllSettings(map);
7467 }
7468 }
7469
7470 auto camera = OpticalTrainManager::Instance()->getCamera(name);
7471 if (camera)
7472 {
7473 opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(camera->getDeviceName(), scope["name"].toString()));
7474
7475 // Get the pixel size of the active camera for later calculations
7476 auto nvp = camera->getNumber("CCD_INFO");
7477 if (!nvp)
7478 {
7479 m_CcdPixelSizeX = 0.0;
7480 m_CcdWidth = m_CcdHeight = 0;
7481 }
7482 else
7483 {
7484 auto np = nvp->findWidgetByName("CCD_PIXEL_SIZE_X");
7485 if (np)
7486 m_CcdPixelSizeX = np->getValue();
7487 np = nvp->findWidgetByName("CCD_MAX_X");
7488 if (np)
7489 m_CcdWidth = np->getValue();
7490 np = nvp->findWidgetByName("CCD_MAX_Y");
7491 if (np)
7492 m_CcdHeight = np->getValue();
7493 }
7494 }
7495 setCamera(camera);
7496
7497 auto filterWheel = OpticalTrainManager::Instance()->getFilterWheel(name);
7498 setFilterWheel(filterWheel);
7499
7500 // Update calcs for the CFZ based on the new OT
7501 resetCFZToOT();
7502
7503 // JM 2024.03.16 Also use focus advisor on new profiles
7504 if (!validSettings)
7505 {
7506 focusAdvisor->setupParams(name);
7507 focusAdvisor->updateParams();
7508 }
7509 }
7510
7511 opticalTrainCombo->blockSignals(false);
7512}
7513
7514// Function to set member variables based on Optical Train's attached scope
7515void Focus::setScopeDetails(const QJsonObject &scope, const double reducer)
7516{
7517 m_Aperture = scope["aperture"].toDouble(-1);
7518 m_FocalLength = scope["focal_length"].toDouble(-1);
7519 m_FocalRatio = scope["focal_ratio"].toDouble(-1);
7520 m_ScopeType = scope["type"].toString();
7521 m_Reducer = reducer;
7522
7523 // Adjust telescope FL and F# for any reducer
7524 if (m_Reducer > 0.0)
7525 m_FocalLength *= m_Reducer;
7526
7527 // Use the adjusted focal length to calculate an adjusted focal ratio
7528 if (m_FocalRatio <= 0.0)
7529 // For a scope, FL and aperture are specified so calc the F#
7530 m_FocalRatio = (m_Aperture > 0.001) ? m_FocalLength / m_Aperture : 0.0f;
7531 else if (m_Aperture < 0.0)
7532 // DSLR Lens. FL and F# are specified so calc the aperture
7533 m_Aperture = m_FocalLength / m_FocalRatio;
7534}
7535
7536}
void drawPolynomial(PolynomialFit *poly, bool isVShape, bool activate, bool plot=true)
draw the approximating polynomial into the HFR V-graph
void toggleVideo(bool enabled)
toggleVideo Turn on and off video streaming if supported by the camera.
Definition focus.cpp:5492
void checkTemperatureSource(const QString &name=QString())
Check temperature source and make sure information is updated accordingly.
Definition focus.cpp:567
void startFraming()
startFraming Begins continuous capture of the CCD and calculates HFR every frame.
Definition focus.cpp:4552
void newHFRPlotPosition(double pos, double hfr, double sigma, bool outlier, int pulseDuration, bool plot=true)
new HFR plot position with sigma
void redrawHFRPlot(PolynomialFit *poly, double solutionPosition, double solutionValue)
redraw the entire HFR plot
void clearDataPoints()
clearDataPoints Remove all data points from HFR plots
Definition focus.cpp:3114
void updateProperty(INDI::Property prop)
updateProperty Read focus number properties of interest as they arrive from the focuser driver and pr...
Definition focus.cpp:4161
void focusStarSelected(int x, int y)
focusStarSelected The user selected a focus star, save its coordinates and subframe it if subframing ...
Definition focus.cpp:4766
void processData(const QSharedPointer< FITSData > &data)
newFITS A new FITS blob is received by the CCD driver.
Definition focus.cpp:1910
void initHFRPlot(QString str, double starUnits, bool minimum, bool useWeights, bool showPosition)
initialize the HFR V plot
void appendLogText(const QString &logtext)
setFocusStatus Upon completion of the focusing process, set its status (fail or pass) and reset focus...
Definition focus.cpp:4537
Q_SCRIPTABLE Q_NOREPLY void resetFrame()
DBUS interface function.
Definition focus.cpp:314
void processTemperatureSource(INDI::Property prop)
processTemperatureSource Updates focus temperature source.
Definition focus.cpp:919
void drawCFZ(double minPosition, double minValue, int m_cfzSteps, bool plt)
Draw Critical Focus Zone on graph.
void finalUpdates(const QString &title, bool plot=true)
final updates after focus run comopletes on the focus plot
void minimumFound(double solutionPosition, double solutionValue, bool plot=true)
Focus solution with minimal HFR found.
void setTitle(const QString &title, bool plot=true)
draw a title on the focus plot
void drawCurve(CurveFitting *curve, bool isVShape, bool activate, bool plot=true)
draw the curve into the HFR V-graph
CameraChip class controls a particular chip in camera.
Camera class controls an INDI Camera device.
Definition indicamera.h:45
@ ERROR_SAVE
INDI Camera error.
Definition indicamera.h:66
Focuser class handles control of INDI focuser devices.
Definition indifocuser.h:21
KPageWidgetItem * addPage(QWidget *page, const QString &itemName, const QString &pixmapName=QString(), const QString &header=QString(), bool manage=true)
void setIcon(const QIcon &icon)
const KStarsDateTime & lt() const
Definition kstarsdata.h:153
static KStars * Instance()
Definition kstars.h:121
The sky coordinates of a point in the sky.
Definition skypoint.h:45
const dms & alt() const
Definition skypoint.h:281
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
const double & Degrees() const
Definition dms.h:141
Q_SCRIPTABLE Q_NOREPLY void capture(double settleTime=0.0)
DBUS interface function.
Definition focus.cpp:1505
Q_SCRIPTABLE bool focusOut(int ms=-1)
DBUS interface function.
Definition focus.cpp:1699
Q_SCRIPTABLE Q_NOREPLY void abort()
DBUS interface function.
Definition focus.cpp:1411
Q_SCRIPTABLE bool focusIn(int ms=-1)
DBUS interface function.
Definition focus.cpp:1679
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:83
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
KGuiItem stop()
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
bool isChecked() const const
void clicked(bool checked)
void toggled(bool checked)
void editingFinished()
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
QByteArray toBase64(Base64Options options) const const
void stateChanged(int state)
void activated(int index)
void currentIndexChanged(int index)
void currentTextChanged(const QString &text)
QString toString(QStringView format, QCalendar cal) const const
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
void accepted()
QString filePath(const QString &fileName) const const
void valueChanged(double d)
bool exists() const const
bool isChecked() const const
void toggled(bool on)
QIcon fromTheme(const QString &name)
void clear()
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
T & last()
qsizetype size() const const
bool disconnect(const QMetaObject::Connection &connection)
QList< T > findChildren(Qt::FindChildOptions options) const const
int x() const const
int y() const const
bool contains(const QPoint &point, bool proper) const const
int height() const const
int width() const const
int x() const const
int y() const const
T * get() const const
bool isNull() const const
void valueChanged(int i)
bool restoreState(const QByteArray &state)
QByteArray saveState() const const
void splitterMoved(int pos, int index)
void setEnabled(bool enabled)
QStandardItem * item(int row, int column) const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QByteArray toLatin1() const const
QByteArray toUtf8() const const
CaseInsensitive
UniqueConnection
WA_LayoutUsesWidgetRect
QTextStream & center(QTextStream &stream)
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
QUrl fromLocalFile(const QString &localFile)
bool isValid() const const
bool toBool() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QString toString() const const
uint toUInt(bool *ok) const const
void setZ(float z)
bool isEnabled() const const
void show()
void setWindowFlags(Qt::WindowFlags type)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 3 2025 11:47:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.