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

KDE's Doxygen guidelines are available online.