Kstars

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

KDE's Doxygen guidelines are available online.