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

KDE's Doxygen guidelines are available online.