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

KDE's Doxygen guidelines are available online.