1 /*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "focus.h"
8
9 #include "focusadaptor.h"
10 #include "focusalgorithms.h"
11 #include "polynomialfit.h"
12 #include "kstars.h"
13 #include "kstarsdata.h"
14 #include "Options.h"
15 #include "auxiliary/kspaths.h"
16 #include "auxiliary/ksmessagebox.h"
17 #include "ekos/manager.h"
18 #include "ekos/auxiliary/darklibrary.h"
19 #include "fitsviewer/fitsdata.h"
20 #include "fitsviewer/fitstab.h"
21 #include "fitsviewer/fitsview.h"
22 #include "indi/indifilter.h"
23 #include "ksnotification.h"
24 #include "kconfigdialog.h"
25
26 #include <basedevice.h>
27
28 #include <gsl/gsl_fit.h>
29 #include <gsl/gsl_vector.h>
30 #include <gsl/gsl_min.h>
31
32 #include <ekos_focus_debug.h>
33
34 #include <cmath>
35
36 #define MAXIMUM_ABS_ITERATIONS 30
37 #define MAXIMUM_RESET_ITERATIONS 3
38 #define AUTO_STAR_TIMEOUT 45000
39 #define MINIMUM_PULSE_TIMER 32
40 #define MAX_RECAPTURE_RETRIES 3
41 #define MINIMUM_POLY_SOLUTIONS 2
42
43 namespace Ekos
44 {
Focus()45 Focus::Focus()
46 {
47 // #1 Set the UI
48 setupUi(this);
49
50 // #2 Register DBus
51 qRegisterMetaType<Ekos::FocusState>("Ekos::FocusState");
52 qDBusRegisterMetaType<Ekos::FocusState>();
53 new FocusAdaptor(this);
54 QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Focus", this);
55
56 // #3 Init connections
57 initConnections();
58
59 // #4 Init Plots
60 initPlots();
61
62 // #5 Init View
63 initView();
64
65 // #6 Reset all buttons to default states
66 resetButtons();
67
68 // #7 Image Effects - note there is already a no-op "--" in the gadget
69 filterCombo->addItems(FITSViewer::filterTypes);
70 connect(filterCombo, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Focus::filterChangeWarning);
71
72 // Check that the filter denominated by the Ekos option exists before setting it (count the no-op filter)
73 if (Options::focusEffect() < (uint) FITSViewer::filterTypes.count() + 1)
74 filterCombo->setCurrentIndex(Options::focusEffect());
75 filterChangeWarning(filterCombo->currentIndex());
76 defaultScale = static_cast<FITSScale>(Options::focusEffect());
77
78 // #8 Load All settings
79 loadSettings();
80
81 // #9 Init Setting Connection now
82 initSettingsConnections();
83
84 connect(&m_StarFinderWatcher, &QFutureWatcher<bool>::finished, this, &Focus::calculateHFR);
85
86 //Note: This is to prevent a button from being called the default button
87 //and then executing when the user hits the enter key such as when on a Text Box
88 QList<QPushButton *> qButtons = findChildren<QPushButton *>();
89 for (auto &button : qButtons)
90 button->setAutoDefault(false);
91
92 appendLogText(i18n("Idle."));
93
94 // Focus motion timeout
95 m_FocusMotionTimer.setInterval(Options::focusMotionTimeout() * 1000);
96 connect(&m_FocusMotionTimer, &QTimer::timeout, this, &Focus::handleFocusMotionTimeout);
97
98 // Create an autofocus CSV file, dated at startup time
99 m_FocusLogFileName = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("focuslogs/autofocus-" +
100 QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss") + ".txt");
101 m_FocusLogFile.setFileName(m_FocusLogFileName);
102
103 editFocusProfile->setIcon(QIcon::fromTheme("document-edit"));
104 editFocusProfile->setAttribute(Qt::WA_LayoutUsesWidgetRect);
105
106 connect(editFocusProfile, &QAbstractButton::clicked, this, [this]()
107 {
108 KConfigDialog *optionsEditor = new KConfigDialog(this, "OptionsProfileEditor", Options::self());
109 optionsProfileEditor = new StellarSolverProfileEditor(this, Ekos::FocusProfiles, optionsEditor);
110 #ifdef Q_OS_OSX
111 optionsEditor->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
112 #endif
113 KPageWidgetItem *mainPage = optionsEditor->addPage(optionsProfileEditor, i18n("Focus Options Profile Editor"));
114 mainPage->setIcon(QIcon::fromTheme("configure"));
115 connect(optionsProfileEditor, &StellarSolverProfileEditor::optionsProfilesUpdated, this, &Focus::loadStellarSolverProfiles);
116 optionsProfileEditor->loadProfile(focusOptionsProfiles->currentIndex());
117 optionsEditor->show();
118 });
119
120 loadStellarSolverProfiles();
121
122 connect(focusOptionsProfiles, QOverload<int>::of(&QComboBox::activated), this, [](int index)
123 {
124 Options::setFocusOptionsProfile(index);
125 });
126
127 // connect HFR plot widget
128 connect(this, &Ekos::Focus::initHFRPlot, HFRPlot, &FocusHFRVPlot::init);
129 connect(this, &Ekos::Focus::redrawHFRPlot, HFRPlot, &FocusHFRVPlot::redraw);
130 connect(this, &Ekos::Focus::newHFRPlotPosition, HFRPlot, &FocusHFRVPlot::addPosition);
131 connect(this, &Ekos::Focus::drawPolynomial, HFRPlot, &FocusHFRVPlot::drawPolynomial);
132 connect(this, &Ekos::Focus::setTitle, HFRPlot, &FocusHFRVPlot::setTitle);
133 connect(this, &Ekos::Focus::minimumFound, HFRPlot, &FocusHFRVPlot::drawMinimum);
134
135 m_DarkProcessor = new DarkProcessor(this);
136 connect(m_DarkProcessor, &DarkProcessor::newLog, this, &Ekos::Focus::appendLogText);
137 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted, this, [this](bool completed)
138 {
139 darkFrameCheck->setChecked(completed);
140 if (completed)
141 {
142 focusView->rescale(ZOOM_KEEP_LEVEL);
143 focusView->updateFrame();
144 }
145 setCaptureComplete();
146 resetButtons();
147 });
148 }
149
loadStellarSolverProfiles()150 void Focus::loadStellarSolverProfiles()
151 {
152 QString savedOptionsProfiles = QDir(KSPaths::writableLocation(
153 QStandardPaths::AppDataLocation)).filePath("SavedFocusProfiles.ini");
154 if(QFile(savedOptionsProfiles).exists())
155 m_StellarSolverProfiles = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
156 else
157 m_StellarSolverProfiles = getDefaultFocusOptionsProfiles();
158 focusOptionsProfiles->clear();
159 for(auto param : m_StellarSolverProfiles)
160 focusOptionsProfiles->addItem(param.listName);
161 focusOptionsProfiles->setCurrentIndex(Options::focusOptionsProfile());
162 }
163
getStellarSolverProfiles()164 QStringList Focus::getStellarSolverProfiles()
165 {
166 QStringList profiles;
167 for (auto param : m_StellarSolverProfiles)
168 profiles << param.listName;
169
170 return profiles;
171 }
172
173
~Focus()174 Focus::~Focus()
175 {
176 if (focusingWidget->parent() == nullptr)
177 toggleFocusingWidgetFullScreen();
178
179 m_FocusLogFile.close();
180 }
181
resetFrame()182 void Focus::resetFrame()
183 {
184 if (currentCCD && currentCCD->isConnected())
185 {
186 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
187
188 if (targetChip)
189 {
190 //fx=fy=fw=fh=0;
191 targetChip->resetFrame();
192
193 int x, y, w, h;
194 targetChip->getFrame(&x, &y, &w, &h);
195
196 qCDebug(KSTARS_EKOS_FOCUS) << "Frame is reset. X:" << x << "Y:" << y << "W:" << w << "H:" << h << "binX:" << 1 << "binY:" <<
197 1;
198
199 QVariantMap settings;
200 settings["x"] = x;
201 settings["y"] = y;
202 settings["w"] = w;
203 settings["h"] = h;
204 settings["binx"] = 1;
205 settings["biny"] = 1;
206 frameSettings[targetChip] = settings;
207
208 starSelected = false;
209 starCenter = QVector3D();
210 subFramed = false;
211
212 focusView->setTrackingBox(QRect());
213 }
214 }
215 }
216
setCamera(const QString & device)217 bool Focus::setCamera(const QString &device)
218 {
219 for (int i = 0; i < CCDCaptureCombo->count(); i++)
220 if (device == CCDCaptureCombo->itemText(i))
221 {
222 CCDCaptureCombo->setCurrentIndex(i);
223 checkCCD(i);
224 return true;
225 }
226
227 return false;
228 }
229
camera()230 QString Focus::camera()
231 {
232 if (currentCCD)
233 return currentCCD->getDeviceName();
234
235 return QString();
236 }
237
checkCCD(int ccdNum)238 void Focus::checkCCD(int ccdNum)
239 {
240 // Do NOT perform checks when the camera is capturing or busy as this may result
241 // in signals/slots getting disconnected.
242 switch (state)
243 {
244 // Idle, can change camera.
245 case FOCUS_IDLE:
246 case FOCUS_COMPLETE:
247 case FOCUS_FAILED:
248 case FOCUS_ABORTED:
249 break;
250
251 // Busy, cannot change camera.
252 case FOCUS_WAITING:
253 case FOCUS_PROGRESS:
254 case FOCUS_FRAMING:
255 case FOCUS_CHANGING_FILTER:
256 return;
257 }
258
259 if (ccdNum == -1)
260 {
261 ccdNum = CCDCaptureCombo->currentIndex();
262
263 if (ccdNum == -1)
264 return;
265 }
266
267 if (ccdNum >= 0 && ccdNum < CCDs.count())
268 {
269 currentCCD = CCDs.at(ccdNum);
270
271 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
272 if (targetChip && targetChip->isCapturing())
273 return;
274
275 for (ISD::CCD *oneCCD : CCDs)
276 {
277 if (oneCCD == currentCCD)
278 continue;
279 if (captureInProgress == false)
280 oneCCD->disconnect(this);
281 }
282
283 if (targetChip)
284 {
285 targetChip->setImageView(focusView, FITS_FOCUS);
286
287 binningCombo->setEnabled(targetChip->canBin());
288 useSubFrame->setEnabled(targetChip->canSubframe());
289 if (targetChip->canBin())
290 {
291 int subBinX = 1, subBinY = 1;
292 binningCombo->clear();
293 targetChip->getMaxBin(&subBinX, &subBinY);
294 for (int i = 1; i <= subBinX; i++)
295 binningCombo->addItem(QString("%1x%2").arg(i).arg(i));
296
297 activeBin = Options::focusXBin();
298 binningCombo->setCurrentIndex(activeBin - 1);
299 }
300 else
301 activeBin = 1;
302
303 QStringList isoList = targetChip->getISOList();
304 ISOCombo->clear();
305
306 if (isoList.isEmpty())
307 {
308 ISOCombo->setEnabled(false);
309 ISOLabel->setEnabled(false);
310 }
311 else
312 {
313 ISOCombo->setEnabled(true);
314 ISOLabel->setEnabled(true);
315 ISOCombo->addItems(isoList);
316 ISOCombo->setCurrentIndex(targetChip->getISOIndex());
317 }
318
319 connect(currentCCD, &ISD::CCD::videoStreamToggled, this, &Ekos::Focus::setVideoStreamEnabled, Qt::UniqueConnection);
320
321 liveVideoB->setEnabled(currentCCD->hasVideoStream());
322 if (currentCCD->hasVideoStream())
323 setVideoStreamEnabled(currentCCD->isStreamingEnabled());
324 else
325 liveVideoB->setIcon(QIcon::fromTheme("camera-off"));
326
327
328 bool hasGain = currentCCD->hasGain();
329 gainLabel->setEnabled(hasGain);
330 gainIN->setEnabled(hasGain && currentCCD->getGainPermission() != IP_RO);
331 if (hasGain)
332 {
333 double gain = 0, min = 0, max = 0, step = 1;
334 currentCCD->getGainMinMaxStep(&min, &max, &step);
335 if (currentCCD->getGain(&gain))
336 {
337 gainIN->setMinimum(min);
338 gainIN->setMaximum(max);
339 if (step > 0)
340 gainIN->setSingleStep(step);
341
342 double defaultGain = Options::focusGain();
343 if (defaultGain > 0)
344 gainIN->setValue(defaultGain);
345 else
346 gainIN->setValue(gain);
347 }
348 }
349 else
350 gainIN->clear();
351 }
352 }
353
354 syncCCDInfo();
355 }
356
syncCCDInfo()357 void Focus::syncCCDInfo()
358 {
359 if (currentCCD == nullptr)
360 return;
361
362 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
363
364 useSubFrame->setEnabled(targetChip->canSubframe());
365
366 if (frameSettings.contains(targetChip) == false)
367 {
368 int x, y, w, h;
369 if (targetChip->getFrame(&x, &y, &w, &h))
370 {
371 int binx = 1, biny = 1;
372 targetChip->getBinning(&binx, &biny);
373 if (w > 0 && h > 0)
374 {
375 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
376 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
377
378 QVariantMap settings;
379
380 settings["x"] = useSubFrame->isChecked() ? x : minX;
381 settings["y"] = useSubFrame->isChecked() ? y : minY;
382 settings["w"] = useSubFrame->isChecked() ? w : maxW;
383 settings["h"] = useSubFrame->isChecked() ? h : maxH;
384 settings["binx"] = binx;
385 settings["biny"] = biny;
386
387 frameSettings[targetChip] = settings;
388 }
389 }
390 }
391 }
392
addFilter(ISD::GDInterface * newFilter)393 void Focus::addFilter(ISD::GDInterface *newFilter)
394 {
395 for (auto &oneFilter : Filters)
396 {
397 if (oneFilter->getDeviceName() == newFilter->getDeviceName())
398 return;
399 }
400
401 FilterCaptureLabel->setEnabled(true);
402 FilterDevicesCombo->setEnabled(true);
403 FilterPosLabel->setEnabled(true);
404 FilterPosCombo->setEnabled(true);
405 filterManagerB->setEnabled(true);
406
407 FilterDevicesCombo->addItem(newFilter->getDeviceName());
408
409 Filters.append(static_cast<ISD::Filter *>(newFilter));
410
411 int filterWheelIndex = 1;
412 if (Options::defaultFocusFilterWheel().isEmpty() == false)
413 filterWheelIndex = FilterDevicesCombo->findText(Options::defaultFocusFilterWheel());
414
415 if (filterWheelIndex < 1)
416 filterWheelIndex = 1;
417
418 checkFilter(filterWheelIndex);
419 FilterDevicesCombo->setCurrentIndex(filterWheelIndex);
420
421 emit settingsUpdated(getSettings());
422 }
423
addTemperatureSource(ISD::GDInterface * newSource)424 void Focus::addTemperatureSource(ISD::GDInterface *newSource)
425 {
426 if (!newSource)
427 return;
428
429 for (const auto &oneSource : TemperatureSources)
430 {
431 if (oneSource->getDeviceName() == newSource->getDeviceName())
432 return;
433 }
434
435 TemperatureSources.append(newSource);
436 temperatureSourceCombo->addItem(newSource->getDeviceName());
437
438 int temperatureSourceIndex = temperatureSourceCombo->currentIndex();
439 if (Options::defaultFocusTemperatureSource().isEmpty())
440 Options::setDefaultFocusTemperatureSource(newSource->getDeviceName());
441 else
442 temperatureSourceIndex = temperatureSourceCombo->findText(Options::defaultFocusTemperatureSource());
443 if (temperatureSourceIndex < 0)
444 temperatureSourceIndex = 0;
445
446 checkTemperatureSource(temperatureSourceIndex);
447 }
448
checkTemperatureSource(int index)449 void Focus::checkTemperatureSource(int index)
450 {
451 if (index == -1)
452 {
453 index = temperatureSourceCombo->currentIndex();
454 if (index == -1)
455 return;
456 }
457
458 QString deviceName;
459 if (index < TemperatureSources.count())
460 deviceName = temperatureSourceCombo->itemText(index);
461
462 ISD::GDInterface *currentSource = nullptr;
463
464 for (auto &oneSource : TemperatureSources)
465 {
466 if (oneSource->getDeviceName() == deviceName)
467 {
468 currentSource = oneSource;
469 break;
470 }
471 }
472
473 // No valid device found
474 if (!currentSource)
475 return;
476
477 QStringList deviceNames;
478 // Disconnect all existing signals
479 for (const auto &oneSource : TemperatureSources)
480 {
481 deviceNames << oneSource->getDeviceName();
482 disconnect(oneSource, &ISD::GDInterface::numberUpdated, this, &Ekos::Focus::processTemperatureSource);
483 }
484
485 if (findTemperatureElement(currentSource))
486 {
487 m_LastSourceAutofocusTemperature = currentTemperatureSourceElement->value;
488 absoluteTemperatureLabel->setText(QString("%1 °C").arg(currentTemperatureSourceElement->value, 0, 'f', 2));
489 deltaTemperatureLabel->setText(QString("%1 °C").arg(0.0, 0, 'f', 2));
490 }
491 else
492 m_LastSourceAutofocusTemperature = INVALID_VALUE;
493 connect(currentSource, &ISD::GDInterface::numberUpdated, this, &Ekos::Focus::processTemperatureSource);
494
495 temperatureSourceCombo->clear();
496 temperatureSourceCombo->addItems(deviceNames);
497 temperatureSourceCombo->setCurrentIndex(index);
498 }
499
findTemperatureElement(ISD::GDInterface * device)500 bool Focus::findTemperatureElement(ISD::GDInterface *device)
501 {
502 INDI::Property *temperatureProperty = device->getProperty("FOCUS_TEMPERATURE");
503 if (!temperatureProperty)
504 temperatureProperty = device->getProperty("CCD_TEMPERATURE");
505 if (temperatureProperty)
506 {
507 currentTemperatureSourceElement = temperatureProperty->getNumber()->at(0);
508 return true;
509 }
510
511 temperatureProperty = device->getProperty("WEATHER_PARAMETERS");
512 if (temperatureProperty)
513 {
514 for (int i = 0; i < temperatureProperty->getNumber()->count(); i++)
515 {
516 if (strstr(temperatureProperty->getNumber()->at(i)->getName(), "_TEMPERATURE"))
517 {
518 currentTemperatureSourceElement = temperatureProperty->getNumber()->at(i);
519 return true;
520 }
521 }
522 }
523
524 return false;
525 }
526
setFilterWheel(const QString & device)527 bool Focus::setFilterWheel(const QString &device)
528 {
529 bool deviceFound = false;
530
531 for (int i = 1; i < FilterDevicesCombo->count(); i++)
532 if (device == FilterDevicesCombo->itemText(i))
533 {
534 checkFilter(i);
535 deviceFound = true;
536 break;
537 }
538
539 if (deviceFound == false)
540 return false;
541
542 return true;
543 }
544
filterWheel()545 QString Focus::filterWheel()
546 {
547 if (FilterDevicesCombo->currentIndex() >= 1)
548 return FilterDevicesCombo->currentText();
549
550 return QString();
551 }
552
setFilter(const QString & filter)553 bool Focus::setFilter(const QString &filter)
554 {
555 if (FilterDevicesCombo->currentIndex() >= 1)
556 {
557 FilterPosCombo->setCurrentText(filter);
558 return true;
559 }
560
561 return false;
562 }
563
filter()564 QString Focus::filter()
565 {
566 return FilterPosCombo->currentText();
567 }
568
checkFilter(int filterNum)569 void Focus::checkFilter(int filterNum)
570 {
571 if (filterNum == -1)
572 {
573 filterNum = FilterDevicesCombo->currentIndex();
574 if (filterNum == -1)
575 return;
576 }
577
578 // "--" is no filter
579 if (filterNum == 0)
580 {
581 currentFilter = nullptr;
582 currentFilterPosition = -1;
583 FilterPosCombo->clear();
584 return;
585 }
586
587 if (filterNum <= Filters.count())
588 currentFilter = Filters.at(filterNum - 1);
589
590 //Options::setDefaultFocusFilterWheel(currentFilter->getDeviceName());
591
592 filterManager->setCurrentFilterWheel(currentFilter);
593
594 FilterPosCombo->clear();
595
596 FilterPosCombo->addItems(filterManager->getFilterLabels());
597
598 currentFilterPosition = filterManager->getFilterPosition();
599
600 FilterPosCombo->setCurrentIndex(currentFilterPosition - 1);
601
602 //Options::setDefaultFocusFilterWheelFilter(FilterPosCombo->currentText());
603
604 exposureIN->setValue(filterManager->getFilterExposure());
605 }
606
addFocuser(ISD::GDInterface * newFocuser)607 void Focus::addFocuser(ISD::GDInterface *newFocuser)
608 {
609 ISD::Focuser *oneFocuser = static_cast<ISD::Focuser *>(newFocuser);
610
611 if (Focusers.contains(oneFocuser))
612 return;
613
614 focuserCombo->addItem(oneFocuser->getDeviceName());
615
616 Focusers.append(oneFocuser);
617
618 currentFocuser = oneFocuser;
619
620 checkFocuser();
621 }
622
setFocuser(const QString & device)623 bool Focus::setFocuser(const QString &device)
624 {
625 for (int i = 0; i < focuserCombo->count(); i++)
626 if (device == focuserCombo->itemText(i))
627 {
628 focuserCombo->setCurrentIndex(i);
629 checkFocuser(i);
630 return true;
631 }
632
633 return false;
634 }
635
focuser()636 QString Focus::focuser()
637 {
638 if (currentFocuser)
639 return currentFocuser->getDeviceName();
640
641 return QString();
642 }
643
checkFocuser(int FocuserNum)644 void Focus::checkFocuser(int FocuserNum)
645 {
646 if (FocuserNum == -1)
647 FocuserNum = focuserCombo->currentIndex();
648
649 if (FocuserNum == -1)
650 {
651 currentFocuser = nullptr;
652 return;
653 }
654
655 if (FocuserNum < Focusers.count())
656 currentFocuser = Focusers.at(FocuserNum);
657
658 filterManager->setFocusReady(currentFocuser->isConnected());
659
660 // Disconnect all focusers
661 for (auto &oneFocuser : Focusers)
662 {
663 disconnect(oneFocuser, &ISD::GDInterface::numberUpdated, this, &Ekos::Focus::processFocusNumber);
664 }
665
666 hasDeviation = currentFocuser->hasDeviation();
667
668 canAbsMove = currentFocuser->canAbsMove();
669
670 if (canAbsMove)
671 {
672 getAbsFocusPosition();
673
674 absTicksSpin->setEnabled(true);
675 absTicksLabel->setEnabled(true);
676 startGotoB->setEnabled(true);
677
678 absTicksSpin->setValue(currentPosition);
679 }
680 else
681 {
682 absTicksSpin->setEnabled(false);
683 absTicksLabel->setEnabled(false);
684 startGotoB->setEnabled(false);
685 }
686
687 canRelMove = currentFocuser->canRelMove();
688
689 // In case we have a purely relative focuser, we pretend
690 // it is an absolute focuser with initial point set at 50,000.
691 // This is done we can use the same algorithm used for absolute focuser.
692 if (canAbsMove == false && canRelMove == true)
693 {
694 currentPosition = 50000;
695 absMotionMax = 100000;
696 absMotionMin = 0;
697 }
698
699 canTimerMove = currentFocuser->canTimerMove();
700
701 // In case we have a timer-based focuser and using the linear focus algorithm,
702 // we pretend it is an absolute focuser with initial point set at 50,000.
703 // These variables don't have in impact on timer-based focusers if the algorithm
704 // is not the linear focus algorithm.
705 if (!canAbsMove && !canRelMove && canTimerMove)
706 {
707 currentPosition = 50000;
708 absMotionMax = 100000;
709 absMotionMin = 0;
710 }
711
712 focusType = (canRelMove || canAbsMove || canTimerMove) ? FOCUS_AUTO : FOCUS_MANUAL;
713 profilePlot->setFocusAuto(focusType == FOCUS_AUTO);
714
715 bool hasBacklash = currentFocuser->hasBacklash();
716 focusBacklashSpin->setEnabled(hasBacklash);
717 focusBacklashSpin->disconnect(this);
718 if (hasBacklash)
719 {
720 double min = 0, max = 0, step = 0;
721 currentFocuser->getMinMaxStep("FOCUS_BACKLASH_STEPS", "FOCUS_BACKLASH_VALUE", &min, &max, &step);
722 focusBacklashSpin->setMinimum(min);
723 focusBacklashSpin->setMaximum(max);
724 focusBacklashSpin->setSingleStep(step);
725 focusBacklashSpin->setValue(currentFocuser->getBacklash());
726 connect(focusBacklashSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this](int value)
727 {
728 if (currentFocuser)
729 currentFocuser->setBacklash(value);
730 });
731 }
732 else
733 {
734 focusBacklashSpin->setValue(0);
735 }
736
737 //initializeFocuserTemperature();
738
739 connect(currentFocuser, &ISD::GDInterface::numberUpdated, this, &Ekos::Focus::processFocusNumber, Qt::UniqueConnection);
740 //connect(currentFocuser, SIGNAL(propertyDefined(INDI::Property*)), this, &Ekos::Focus::(registerFocusProperty(INDI::Property*)), Qt::UniqueConnection);
741
742 resetButtons();
743
744 //if (!inAutoFocus && !inFocusLoop && !captureInProgress && !inSequenceFocus)
745 // emit autoFocusFinished(true, -1);
746 }
747
addCCD(ISD::GDInterface * newCCD)748 void Focus::addCCD(ISD::GDInterface *newCCD)
749 {
750 if (CCDs.contains(static_cast<ISD::CCD *>(newCCD)))
751 return;
752
753 CCDs.append(static_cast<ISD::CCD *>(newCCD));
754
755 CCDCaptureCombo->addItem(newCCD->getDeviceName());
756
757 checkCCD();
758 }
759
getAbsFocusPosition()760 void Focus::getAbsFocusPosition()
761 {
762 if (!canAbsMove)
763 return;
764
765 auto absMove = currentFocuser->getBaseDevice()->getNumber("ABS_FOCUS_POSITION");
766
767 if (absMove)
768 {
769 const auto &it = absMove->at(0);
770 currentPosition = static_cast<int>(it->getValue());
771 absMotionMax = it->getMax();
772 absMotionMin = it->getMin();
773
774 absTicksSpin->setMinimum(it->getMin());
775 absTicksSpin->setMaximum(it->getMax());
776 absTicksSpin->setSingleStep(it->getStep());
777
778 // Restrict the travel if needed
779 double const travel = std::abs(it->getMax() - it->getMin());
780 if (travel < maxTravelIN->maximum())
781 maxTravelIN->setMaximum(travel);
782
783 absTicksLabel->setText(QString::number(currentPosition));
784
785 stepIN->setMaximum(it->getMax() / 2);
786 //absTicksSpin->setValue(currentPosition);
787 }
788 }
789
processTemperatureSource(INumberVectorProperty * nvp)790 void Focus::processTemperatureSource(INumberVectorProperty *nvp)
791 {
792 double delta = 0;
793 if (currentTemperatureSourceElement && currentTemperatureSourceElement->nvp == nvp)
794 {
795 if (m_LastSourceAutofocusTemperature != INVALID_VALUE)
796 {
797 delta = currentTemperatureSourceElement->value - m_LastSourceAutofocusTemperature;
798 emit newFocusTemperatureDelta(abs(delta), currentTemperatureSourceElement->value);
799 }
800 else
801 {
802 emit newFocusTemperatureDelta(0, currentTemperatureSourceElement->value);
803 }
804
805 absoluteTemperatureLabel->setText(QString("%1 °C").arg(currentTemperatureSourceElement->value, 0, 'f', 2));
806 deltaTemperatureLabel->setText(QString("%1%2 °C").arg((delta > 0.0 ? "+" : "")).arg(delta, 0, 'f', 2));
807 if (delta == 0)
808 deltaTemperatureLabel->setStyleSheet("color: lightgreen");
809 else if (delta > 0)
810 deltaTemperatureLabel->setStyleSheet("color: lightcoral");
811 else
812 deltaTemperatureLabel->setStyleSheet("color: lightblue");
813 }
814 }
815
setLastFocusTemperature()816 void Focus::setLastFocusTemperature()
817 {
818 m_LastSourceAutofocusTemperature = currentTemperatureSourceElement ? currentTemperatureSourceElement->value : INVALID_VALUE;
819
820 // Reset delta to zero now that we're just done with autofocus
821 deltaTemperatureLabel->setText(QString("0 °C"));
822 deltaTemperatureLabel->setStyleSheet("color: lightgreen");
823
824 emit newFocusTemperatureDelta(0, -1e6);
825 }
826
827 #if 0
828 void Focus::initializeFocuserTemperature()
829 {
830 auto temperatureProperty = currentFocuser->getBaseDevice()->getNumber("FOCUS_TEMPERATURE");
831
832 if (temperatureProperty && temperatureProperty->getState() != IPS_ALERT)
833 {
834 focuserTemperature = temperatureProperty->at(0)->getValue();
835 qCDebug(KSTARS_EKOS_FOCUS) << QString("Setting current focuser temperature: %1").arg(focuserTemperature, 0, 'f', 2);
836 }
837 else
838 {
839 focuserTemperature = INVALID_VALUE;
840 qCDebug(KSTARS_EKOS_FOCUS) << QString("Focuser temperature is not available");
841 }
842 }
843
844 void Focus::setLastFocusTemperature()
845 {
846 // The focus temperature is taken by default from the focuser.
847 // If unavailable, fallback to the observatory temperature.
848 if (focuserTemperature != INVALID_VALUE)
849 {
850 lastFocusTemperature = focuserTemperature;
851 lastFocusTemperatureSource = FOCUSER_TEMPERATURE;
852 }
853 else if (observatoryTemperature != INVALID_VALUE)
854 {
855 lastFocusTemperature = observatoryTemperature;
856 lastFocusTemperatureSource = OBSERVATORY_TEMPERATURE;
857 }
858 else
859 {
860 lastFocusTemperature = INVALID_VALUE;
861 lastFocusTemperatureSource = NO_TEMPERATURE;
862 }
863
864 emit newFocusTemperatureDelta(0, -1e6);
865 }
866
867
868 void Focus::updateTemperature(TemperatureSource source, double newTemperature)
869 {
870 if (source == FOCUSER_TEMPERATURE && focuserTemperature != newTemperature)
871 {
872 focuserTemperature = newTemperature;
873 emitTemperatureEvents(source, newTemperature);
874 }
875 else if (source == OBSERVATORY_TEMPERATURE && observatoryTemperature != newTemperature)
876 {
877 observatoryTemperature = newTemperature;
878 emitTemperatureEvents(source, newTemperature);
879 }
880 }
881
882 void Focus::emitTemperatureEvents(TemperatureSource source, double newTemperature)
883 {
884 if (source != lastFocusTemperatureSource)
885 {
886 return;
887 }
888
889 if (lastFocusTemperature != INVALID_VALUE && newTemperature != INVALID_VALUE)
890 {
891 emit newFocusTemperatureDelta(abs(newTemperature - lastFocusTemperature), newTemperature);
892 }
893 else
894 {
895 emit newFocusTemperatureDelta(0, newTemperature);
896 }
897 }
898 #endif
899
start()900 void Focus::start()
901 {
902 if (currentFocuser == nullptr)
903 {
904 appendLogText(i18n("No Focuser connected."));
905 completeFocusProcedure(Ekos::FOCUS_ABORTED);
906 return;
907 }
908
909 if (currentCCD == nullptr)
910 {
911 appendLogText(i18n("No CCD connected."));
912 completeFocusProcedure(Ekos::FOCUS_ABORTED);
913 return;
914 }
915
916 if (!canAbsMove && !canRelMove && stepIN->value() <= MINIMUM_PULSE_TIMER)
917 {
918 appendLogText(i18n("Starting pulse step is too low. Increase the step size to %1 or higher...",
919 MINIMUM_PULSE_TIMER * 5));
920 completeFocusProcedure(Ekos::FOCUS_ABORTED);
921 return;
922 }
923
924 if (inAutoFocus)
925 {
926 appendLogText(i18n("Autofocus is already running, discarding start request."));
927 return;
928 }
929 else inAutoFocus = true;
930
931 m_LastFocusDirection = FOCUS_NONE;
932
933 polySolutionFound = 0;
934
935 waitStarSelectTimer.stop();
936
937 starsHFR.clear();
938
939 lastHFR = 0;
940
941 // Keep the last focus temperature, it can still be useful in case the autofocus fails
942 // lastFocusTemperature
943
944 if (canAbsMove)
945 {
946 absIterations = 0;
947 getAbsFocusPosition();
948 pulseDuration = stepIN->value();
949 }
950 else if (canRelMove)
951 {
952 //appendLogText(i18n("Setting dummy central position to 50000"));
953 absIterations = 0;
954 pulseDuration = stepIN->value();
955 //currentPosition = 50000;
956 absMotionMax = 100000;
957 absMotionMin = 0;
958 }
959 else
960 {
961 pulseDuration = stepIN->value();
962 absIterations = 0;
963 absMotionMax = 100000;
964 absMotionMin = 0;
965 }
966
967 focuserAdditionalMovement = 0;
968 HFRFrames.clear();
969
970 resetButtons();
971
972 reverseDir = false;
973
974 /*if (fw > 0 && fh > 0)
975 starSelected= true;
976 else
977 starSelected= false;*/
978
979 clearDataPoints();
980 profilePlot->clear();
981
982 // Options::setFocusTicks(stepIN->value());
983 // Options::setFocusTolerance(toleranceIN->value());
984 // Options::setFocusExposure(exposureIN->value());
985 // Options::setFocusMaxTravel(maxTravelIN->value());
986 // Options::setFocusBoxSize(focusBoxSize->value());
987 // Options::setFocusSubFrame(useSubFrame->isChecked());
988 // Options::setFocusAutoStarEnabled(useAutoStar->isChecked());
989 // Options::setSuspendGuiding(suspendGuideCheck->isChecked());
990 // Options::setUseFocusDarkFrame(darkFrameCheck->isChecked());
991 // Options::setFocusFramesCount(focusFramesSpin->value());
992 // Options::setFocusUseFullField(useFullField->isChecked());
993
994 qCDebug(KSTARS_EKOS_FOCUS) << "Starting focus with Detection: " << focusDetectionCombo->currentText()
995 << " Algorithm: " << focusAlgorithmCombo->currentText()
996 << " Box size: " << focusBoxSize->value()
997 << " Subframe: " << ( useSubFrame->isChecked() ? "yes" : "no" )
998 << " Autostar: " << ( useAutoStar->isChecked() ? "yes" : "no" )
999 << " Full frame: " << ( useFullField->isChecked() ? "yes" : "no " )
1000 << " [" << fullFieldInnerRing->value() << "%," << fullFieldOuterRing->value() << "%]"
1001 << " Step Size: " << stepIN->value() << " Threshold: " << thresholdSpin->value()
1002 << " Gaussian Sigma: " << gaussianSigmaSpin->value()
1003 << " Gaussian Kernel size: " << gaussianKernelSizeSpin->value()
1004 << " Multi row average: " << multiRowAverageSpin->value()
1005 << " Tolerance: " << toleranceIN->value()
1006 << " Frames: " << 1 /*focusFramesSpin->value()*/ << " Maximum Travel: " << maxTravelIN->value();
1007
1008 if (currentTemperatureSourceElement)
1009 emit autofocusStarting(currentTemperatureSourceElement->value, filter());
1010
1011 if (useAutoStar->isChecked())
1012 appendLogText(i18n("Autofocus in progress..."));
1013 else if (!inAutoFocus)
1014 appendLogText(i18n("Please wait until image capture is complete..."));
1015
1016 // Only suspend when we have Off-Axis Guider
1017 // If the guide camera is operating on a different OTA
1018 // then no need to suspend.
1019 const bool isOAG = currentCCD->getTelescopeType() == Options::guideScopeType();
1020 if (isOAG && m_GuidingSuspended == false && suspendGuideCheck->isChecked())
1021 {
1022 m_GuidingSuspended = true;
1023 emit suspendGuiding();
1024 }
1025
1026 //emit statusUpdated(true);
1027 state = Ekos::FOCUS_PROGRESS;
1028 qCDebug(KSTARS_EKOS_FOCUS) << "State:" << Ekos::getFocusStatusString(state);
1029 emit newStatus(state);
1030
1031 // Denoise with median filter
1032 //defaultScale = FITS_MEDIAN;
1033
1034 KSNotification::event(QLatin1String("FocusStarted"), i18n("Autofocus operation started"));
1035
1036 // Used for all the focuser types.
1037 if (focusAlgorithm == FOCUS_LINEAR)
1038 {
1039 const int position = static_cast<int>(currentPosition);
1040 FocusAlgorithmInterface::FocusParams params(
1041 maxTravelIN->value(), stepIN->value(), position, absMotionMin, absMotionMax,
1042 MAXIMUM_ABS_ITERATIONS, toleranceIN->value() / 100.0, filter(),
1043 currentTemperatureSourceElement ? currentTemperatureSourceElement->value : INVALID_VALUE,
1044 Options::initialFocusOutSteps());
1045 if (canAbsMove)
1046 initialFocuserAbsPosition = position;
1047 linearFocuser.reset(MakeLinearFocuser(params));
1048 linearRequestedPosition = linearFocuser->initialPosition();
1049 const int newPosition = adjustLinearPosition(position, linearRequestedPosition);
1050 if (newPosition != position)
1051 {
1052 if (!changeFocus(newPosition - position))
1053 {
1054 completeFocusProcedure(Ekos::FOCUS_ABORTED);
1055 }
1056 // Avoid the capture below.
1057 return;
1058 }
1059 }
1060 capture();
1061 }
1062
adjustLinearPosition(int position,int newPosition)1063 int Focus::adjustLinearPosition(int position, int newPosition)
1064 {
1065 if (newPosition > position)
1066 {
1067 constexpr int extraMotionSteps = 5;
1068 int adjustment = extraMotionSteps * stepIN->value();
1069 if (newPosition + adjustment > absMotionMax)
1070 adjustment = static_cast<int>(absMotionMax) - newPosition;
1071
1072 focuserAdditionalMovement = adjustment;
1073 qCDebug(KSTARS_EKOS_FOCUS) << QString("LinearFocuser: extending outward movement by %1").arg(adjustment);
1074
1075 return newPosition + adjustment;
1076 }
1077 return newPosition;
1078 }
1079
checkStopFocus(bool abort)1080 void Focus::checkStopFocus(bool abort)
1081 {
1082 // if abort, avoid try to restart
1083 if (abort)
1084 resetFocusIteration = MAXIMUM_RESET_ITERATIONS + 1;
1085
1086 if (captureInProgress && inAutoFocus == false && inFocusLoop == false)
1087 {
1088 captureB->setEnabled(true);
1089 stopFocusB->setEnabled(false);
1090
1091 appendLogText(i18n("Capture aborted."));
1092 }
1093
1094 if (hfrInProgress)
1095 {
1096 stopFocusB->setEnabled(false);
1097 appendLogText(i18n("Detection in progress, please wait."));
1098 QTimer::singleShot(1000, this, [ &, abort]()
1099 {
1100 checkStopFocus(abort);
1101 });
1102 }
1103 else
1104 {
1105 completeFocusProcedure(abort ? Ekos::FOCUS_ABORTED : Ekos::FOCUS_FAILED);
1106 }
1107 }
1108
meridianFlipStarted()1109 void Focus::meridianFlipStarted()
1110 {
1111 // if focusing is not running, do nothing
1112 if (state == FOCUS_IDLE || state == FOCUS_COMPLETE || state == FOCUS_FAILED || state == FOCUS_ABORTED)
1113 return;
1114
1115 // store current focus iteration counter since abort() sets it to the maximal value to avoid restarting
1116 int old = resetFocusIteration;
1117 // abort focusing
1118 abort();
1119 // try to shift the focuser back to its initial position
1120 resetFocuser();
1121 // restore iteration counter
1122 resetFocusIteration = old;
1123 }
1124
abort()1125 void Focus::abort()
1126 {
1127 // No need to "abort" if not already in progress.
1128 if (state <= FOCUS_ABORTED)
1129 return;
1130
1131 checkStopFocus(true);
1132 appendLogText(i18n("Autofocus aborted."));
1133 }
1134
stop(Ekos::FocusState completionState)1135 void Focus::stop(Ekos::FocusState completionState)
1136 {
1137 qCDebug(KSTARS_EKOS_FOCUS) << "Stopping Focus";
1138
1139 captureTimeout.stop();
1140 m_FocusMotionTimer.stop();
1141 m_FocusMotionTimerCounter = 0;
1142
1143 inAutoFocus = false;
1144 focuserAdditionalMovement = 0;
1145 inFocusLoop = false;
1146 polySolutionFound = 0;
1147 captureInProgress = false;
1148 captureFailureCounter = 0;
1149 minimumRequiredHFR = -1;
1150 noStarCount = 0;
1151 HFRFrames.clear();
1152
1153 // Check if CCD was not removed due to crash or other reasons.
1154 if (currentCCD)
1155 {
1156 disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Focus::processData);
1157 disconnect(currentCCD, &ISD::CCD::error, this, &Ekos::Focus::processCaptureError);
1158
1159 if (rememberUploadMode != currentCCD->getUploadMode())
1160 currentCCD->setUploadMode(rememberUploadMode);
1161
1162 // Remember to reset fast exposure if it was enabled before.
1163 if (m_RememberCameraFastExposure)
1164 {
1165 m_RememberCameraFastExposure = false;
1166 currentCCD->setFastExposureEnabled(true);
1167 }
1168
1169 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
1170 targetChip->abortExposure();
1171 }
1172
1173 resetButtons();
1174
1175 absIterations = 0;
1176 HFRInc = 0;
1177 reverseDir = false;
1178
1179 if (m_GuidingSuspended)
1180 {
1181 emit resumeGuiding();
1182 m_GuidingSuspended = false;
1183 }
1184
1185 if (completionState == Ekos::FOCUS_ABORTED || completionState == Ekos::FOCUS_FAILED)
1186 {
1187 state = completionState;
1188 qCDebug(KSTARS_EKOS_FOCUS) << "State:" << Ekos::getFocusStatusString(state);
1189 emit newStatus(state);
1190 }
1191 }
1192
capture(double settleTime)1193 void Focus::capture(double settleTime)
1194 {
1195 // If capturing should be delayed by a given settling time, we start the capture timer.
1196 // This is intentionally designed re-entrant, i.e. multiple calls with settle time > 0 takes the last delay
1197 if (settleTime > 0 && captureInProgress == false)
1198 {
1199 captureTimer.start(static_cast<int>(settleTime * 1000));
1200 return;
1201 }
1202
1203 if (captureInProgress)
1204 {
1205 qCWarning(KSTARS_EKOS_FOCUS) << "Capture called while already in progress. Capture is ignored.";
1206 return;
1207 }
1208
1209 if (currentCCD == nullptr)
1210 {
1211 appendLogText(i18n("Error: No Camera detected."));
1212 checkStopFocus(true);
1213 return;
1214 }
1215
1216 if (currentCCD->isConnected() == false)
1217 {
1218 appendLogText(i18n("Error: Lost connection to Camera."));
1219 checkStopFocus(true);
1220 return;
1221 }
1222
1223 // reset timeout for receiving an image
1224 captureTimeout.stop();
1225 // reset timeout for focus star selection
1226 waitStarSelectTimer.stop();
1227
1228 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
1229
1230 if (currentCCD->isBLOBEnabled() == false)
1231 {
1232 currentCCD->setBLOBEnabled(true);
1233 }
1234
1235 if (FilterPosCombo->currentIndex() != -1)
1236 {
1237 if (currentFilter == nullptr)
1238 {
1239 appendLogText(i18n("Error: No Filter Wheel detected."));
1240 checkStopFocus(true);
1241 return;
1242 }
1243 if (currentFilter->isConnected() == false)
1244 {
1245 appendLogText(i18n("Error: Lost connection to Filter Wheel."));
1246 checkStopFocus(true);
1247 return;
1248 }
1249
1250 int targetPosition = FilterPosCombo->currentIndex() + 1;
1251 QString lockedFilter = filterManager->getFilterLock(FilterPosCombo->currentText());
1252
1253 // We change filter if:
1254 // 1. Target position is not equal to current position.
1255 // 2. Locked filter of CURRENT filter is a different filter.
1256 if (lockedFilter != "--" && lockedFilter != FilterPosCombo->currentText())
1257 {
1258 int lockedFilterIndex = FilterPosCombo->findText(lockedFilter);
1259 if (lockedFilterIndex >= 0)
1260 {
1261 // Go back to this filter one we are done
1262 fallbackFilterPending = true;
1263 fallbackFilterPosition = targetPosition;
1264 targetPosition = lockedFilterIndex + 1;
1265 }
1266 }
1267
1268 filterPositionPending = (targetPosition != currentFilterPosition);
1269 // If either the target position is not equal to the current position, OR
1270 if (filterPositionPending)
1271 {
1272 // Apply all policies except autofocus since we are already in autofocus module doh.
1273 filterManager->setFilterPosition(targetPosition,
1274 static_cast<FilterManager::FilterPolicy>(FilterManager::CHANGE_POLICY | FilterManager::OFFSET_POLICY));
1275 return;
1276 }
1277 }
1278
1279 prepareCapture(targetChip);
1280
1281 connect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Focus::processData);
1282 connect(currentCCD, &ISD::CCD::error, this, &Ekos::Focus::processCaptureError);
1283
1284 if (frameSettings.contains(targetChip))
1285 {
1286 QVariantMap settings = frameSettings[targetChip];
1287 targetChip->setFrame(settings["x"].toInt(), settings["y"].toInt(), settings["w"].toInt(),
1288 settings["h"].toInt());
1289 settings["binx"] = activeBin;
1290 settings["biny"] = activeBin;
1291 frameSettings[targetChip] = settings;
1292 }
1293
1294 captureInProgress = true;
1295 if (state != FOCUS_PROGRESS)
1296 {
1297 state = FOCUS_PROGRESS;
1298 emit newStatus(state);
1299 }
1300
1301 focusView->setBaseSize(focusingWidget->size());
1302
1303 if (targetChip->capture(exposureIN->value()))
1304 {
1305 // Timeout is exposure duration + timeout threshold in seconds
1306 //long const timeout = lround(ceil(exposureIN->value() * 1000)) + FOCUS_TIMEOUT_THRESHOLD;
1307 captureTimeout.start(Options::focusCaptureTimeout() * 1000);
1308
1309 if (inFocusLoop == false)
1310 appendLogText(i18n("Capturing image..."));
1311
1312 resetButtons();
1313 }
1314 else if (inAutoFocus)
1315 {
1316 completeFocusProcedure(Ekos::FOCUS_ABORTED);
1317 }
1318 }
1319
prepareCapture(ISD::CCDChip * targetChip)1320 void Focus::prepareCapture(ISD::CCDChip *targetChip)
1321 {
1322 if (currentCCD->getUploadMode() == ISD::CCD::UPLOAD_LOCAL)
1323 {
1324 rememberUploadMode = ISD::CCD::UPLOAD_LOCAL;
1325 currentCCD->setUploadMode(ISD::CCD::UPLOAD_CLIENT);
1326 }
1327
1328 // We cannot use fast exposure in focus.
1329 if (currentCCD->isFastExposureEnabled())
1330 {
1331 m_RememberCameraFastExposure = true;
1332 currentCCD->setFastExposureEnabled(false);
1333 }
1334
1335 currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS);
1336 targetChip->setBatchMode(false);
1337 targetChip->setBinning(activeBin, activeBin);
1338 targetChip->setCaptureMode(FITS_FOCUS);
1339 targetChip->setFrameType(FRAME_LIGHT);
1340
1341 // Always disable filtering if using a dark frame and then re-apply after subtraction. TODO: Implement this in capture and guide and align
1342 if (darkFrameCheck->isChecked())
1343 targetChip->setCaptureFilter(FITS_NONE);
1344 else
1345 targetChip->setCaptureFilter(defaultScale);
1346
1347 if (ISOCombo->isEnabled() && ISOCombo->currentIndex() != -1 &&
1348 targetChip->getISOIndex() != ISOCombo->currentIndex())
1349 targetChip->setISOIndex(ISOCombo->currentIndex());
1350
1351 if (gainIN->isEnabled())
1352 currentCCD->setGain(gainIN->value());
1353 }
1354
focusIn(int ms)1355 bool Focus::focusIn(int ms)
1356 {
1357 if (ms == -1)
1358 ms = stepIN->value();
1359 return changeFocus(-ms);
1360 }
1361
focusOut(int ms)1362 bool Focus::focusOut(int ms)
1363 {
1364 if (ms == -1)
1365 ms = stepIN->value();
1366 return changeFocus(ms);
1367 }
1368
1369 // If amount > 0 we focus out, otherwise in.
changeFocus(int amount)1370 bool Focus::changeFocus(int amount)
1371 {
1372 const int absAmount = abs(amount);
1373
1374 // Retry capture if we stay at the same position
1375 // Allow 1 step of tolerance--Have seen stalls with amount==1.
1376 if (inAutoFocus && absAmount <= 1)
1377 {
1378 capture(FocusSettleTime->value());
1379 return true;
1380 }
1381
1382 if (currentFocuser == nullptr)
1383 {
1384 appendLogText(i18n("Error: No Focuser detected."));
1385 checkStopFocus(true);
1386 return false;
1387 }
1388
1389 if (currentFocuser->isConnected() == false)
1390 {
1391 appendLogText(i18n("Error: Lost connection to Focuser."));
1392 checkStopFocus(true);
1393 return false;
1394 }
1395
1396 const bool focusingOut = amount > 0;
1397 const QString dirStr = focusingOut ? i18n("outward") : i18n("inward");
1398 m_LastFocusDirection = focusingOut ? FOCUS_OUT : FOCUS_IN;
1399
1400 if (focusingOut)
1401 currentFocuser->focusOut();
1402 else
1403 currentFocuser->focusIn();
1404
1405 // Keep track of motion in case it gets stuck.
1406 m_FocusMotionTimerCounter = 0;
1407 m_FocusMotionTimer.start();
1408
1409 if (canAbsMove)
1410 {
1411 m_LastFocusSteps = currentPosition + amount;
1412 currentFocuser->moveAbs(currentPosition + amount);
1413 appendLogText(i18n("Focusing %2 by %1 steps...", absAmount, dirStr));
1414 }
1415 else if (canRelMove)
1416 {
1417 m_LastFocusSteps = absAmount;
1418 currentFocuser->moveRel(absAmount);
1419 appendLogText(i18np("Focusing %2 by %1 step...", "Focusing %2 by %1 steps...", absAmount, dirStr));
1420 }
1421 else
1422 {
1423 m_LastFocusSteps = absAmount;
1424 currentFocuser->moveByTimer(absAmount);
1425 appendLogText(i18n("Focusing %2 by %1 ms...", absAmount, dirStr));
1426 }
1427
1428 return true;
1429 }
1430
1431 ///////////////////////////////////////////////////////////////////////////////////////////////////
1432 ///
1433 ///////////////////////////////////////////////////////////////////////////////////////////////////
handleFocusMotionTimeout()1434 void Focus::handleFocusMotionTimeout()
1435 {
1436 if (++m_FocusMotionTimerCounter > 3)
1437 {
1438 appendLogText(i18n("Focuser is not responding to commands. Aborting..."));
1439 completeFocusProcedure(Ekos::FOCUS_ABORTED);
1440 }
1441
1442 const QString dirStr = m_LastFocusDirection == FOCUS_OUT ? i18n("outward") : i18n("inward");
1443 if (canAbsMove)
1444 {
1445 currentFocuser->moveAbs(m_LastFocusSteps);
1446 appendLogText(i18n("Focus motion timed out. Focusing to %1 steps...", m_LastFocusSteps));
1447 }
1448 else if (canRelMove)
1449 {
1450 currentFocuser->moveRel(m_LastFocusSteps);
1451 appendLogText(i18n("Focus motion timed out. Focusing %2 by %1 steps...", m_LastFocusSteps,
1452 dirStr));
1453 }
1454 else
1455 {
1456 currentFocuser->moveByTimer(m_LastFocusSteps);
1457 appendLogText(i18n("Focus motion timed out. Focusing %2 by %1 ms...",
1458 m_LastFocusSteps, dirStr));
1459 }
1460 }
1461
processData(const QSharedPointer<FITSData> & data)1462 void Focus::processData(const QSharedPointer<FITSData> &data)
1463 {
1464 // Ignore guide head if there is any.
1465 if (data->property("chip").toInt() == ISD::CCDChip::GUIDE_CCD)
1466 return;
1467
1468 if (data)
1469 m_ImageData = data;
1470 else
1471 m_ImageData.reset();
1472
1473 captureTimeout.stop();
1474 captureTimeoutCounter = 0;
1475
1476 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
1477 disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Focus::processData);
1478 disconnect(currentCCD, &ISD::CCD::error, this, &Ekos::Focus::processCaptureError);
1479
1480 if (m_ImageData && darkFrameCheck->isChecked())
1481 {
1482 QVariantMap settings = frameSettings[targetChip];
1483 uint16_t offsetX = settings["x"].toInt() / settings["binx"].toInt();
1484 uint16_t offsetY = settings["y"].toInt() / settings["biny"].toInt();
1485
1486 //targetChip->setCaptureFilter(defaultScale);
1487 m_DarkProcessor->denoise(targetChip, m_ImageData, exposureIN->value(), offsetX, offsetY);
1488 return;
1489 }
1490
1491 setCaptureComplete();
1492 resetButtons();
1493 }
1494
calculateHFR()1495 void Focus::calculateHFR()
1496 {
1497 appendLogText(i18n("Detection complete."));
1498
1499 // Beware as this HFR value is then treated specifically by the graph renderer
1500 double hfr = FocusAlgorithmInterface::IGNORED_HFR;
1501
1502 if (m_StarFinderWatcher.result() == false)
1503 {
1504 qCWarning(KSTARS_EKOS_FOCUS) << "Failed to extract any stars.";
1505 }
1506 else
1507 {
1508 if (Options::focusUseFullField())
1509 {
1510 focusView->setStarFilterRange(static_cast <float> (fullFieldInnerRing->value() / 100.0),
1511 static_cast <float> (fullFieldOuterRing->value() / 100.0));
1512 focusView->filterStars();
1513
1514 // Get the average HFR of the whole frame
1515 hfr = m_ImageData->getHFR(HFR_AVERAGE);
1516 }
1517 else
1518 {
1519 focusView->setTrackingBoxEnabled(true);
1520
1521 // JM 2020-10-08: Try to get first the same HFR star already selected before
1522 // so that it doesn't keep jumping around
1523
1524 if (starCenter.isNull() == false)
1525 hfr = m_ImageData->getHFR(starCenter.x(), starCenter.y());
1526
1527 // If not found, then get the MAX or MEDIAN depending on the selected algorithm.
1528 if (hfr < 0)
1529 hfr = m_ImageData->getHFR(focusDetection == ALGORITHM_SEP ? HFR_HIGH : HFR_MAX);
1530 }
1531 }
1532
1533 hfrInProgress = false;
1534 resetButtons();
1535 setCurrentHFR(hfr);
1536 }
1537
analyzeSources()1538 void Focus::analyzeSources()
1539 {
1540 appendLogText(i18n("Detecting sources..."));
1541 hfrInProgress = true;
1542
1543 QVariantMap extractionSettings;
1544 extractionSettings["optionsProfileIndex"] = Options::focusOptionsProfile();
1545 extractionSettings["optionsProfileGroup"] = static_cast<int>(Ekos::FocusProfiles);
1546 m_ImageData->setSourceExtractorSettings(extractionSettings);
1547 // When we're using FULL field view, we always use either CENTROID algorithm which is the default
1548 // standard algorithm in KStars, or SEP. The other algorithms are too inefficient to run on full frames and require
1549 // a bounding box for them to be effective in near real-time application.
1550 if (Options::focusUseFullField())
1551 {
1552 focusView->setTrackingBoxEnabled(false);
1553
1554 if (focusDetection != ALGORITHM_CENTROID && focusDetection != ALGORITHM_SEP)
1555 m_StarFinderWatcher.setFuture(m_ImageData->findStars(ALGORITHM_CENTROID));
1556 else
1557 m_StarFinderWatcher.setFuture(m_ImageData->findStars(focusDetection));
1558 }
1559 else
1560 {
1561 QRect searchBox = focusView->isTrackingBoxEnabled() ? focusView->getTrackingBox() : QRect();
1562 // If star is already selected then use whatever algorithm currently selected.
1563 if (starSelected)
1564 {
1565 m_StarFinderWatcher.setFuture(m_ImageData->findStars(focusDetection, searchBox));
1566 }
1567 else
1568 {
1569 // Disable tracking box
1570 focusView->setTrackingBoxEnabled(false);
1571
1572 // If algorithm is set something other than Centeroid or SEP, then force Centroid
1573 // Since it is the most reliable detector when nothing was selected before.
1574 if (focusDetection != ALGORITHM_CENTROID && focusDetection != ALGORITHM_SEP)
1575 m_StarFinderWatcher.setFuture(m_ImageData->findStars(ALGORITHM_CENTROID));
1576 else
1577 // Otherwise, continue to find use using the selected algorithm
1578 m_StarFinderWatcher.setFuture(m_ImageData->findStars(focusDetection, searchBox));
1579 }
1580 }
1581 }
1582
appendHFR(double newHFR)1583 bool Focus::appendHFR(double newHFR)
1584 {
1585 // Add new HFR to existing values, even if invalid
1586 HFRFrames.append(newHFR);
1587
1588 // Prepare a work vector with valid HFR values
1589 QVector <double> samples(HFRFrames);
1590 samples.erase(std::remove_if(samples.begin(), samples.end(), [](const double HFR)
1591 {
1592 return HFR == FocusAlgorithmInterface::IGNORED_HFR;
1593 }), samples.end());
1594
1595 // Perform simple sigma clipping if more than a few samples
1596 if (samples.count() > 3)
1597 {
1598 // Sort all HFRs and extract the median
1599 std::sort(samples.begin(), samples.end());
1600 const auto median =
1601 ((samples.size() % 2) ?
1602 samples[samples.size() / 2] :
1603 (static_cast<double>(samples[samples.size() / 2 - 1]) + samples[samples.size() / 2]) * .5);
1604
1605 // Extract the mean
1606 const auto mean = std::accumulate(samples.begin(), samples.end(), .0) / samples.size();
1607
1608 // Extract the variance
1609 double variance = 0;
1610 foreach (auto val, samples)
1611 variance += (val - mean) * (val - mean);
1612
1613 // Deduce the standard deviation
1614 const double stddev = sqrt(variance / samples.size());
1615
1616 // Reject those 2 sigma away from median
1617 const double sigmaHigh = median + stddev * 2;
1618 const double sigmaLow = median - stddev * 2;
1619
1620 // FIXME: why is the first value not considered?
1621 // FIXME: what if there are less than 3 samples after clipping?
1622 QMutableVectorIterator<double> i(samples);
1623 while (i.hasNext())
1624 {
1625 auto val = i.next();
1626 if (val > sigmaHigh || val < sigmaLow)
1627 i.remove();
1628 }
1629 }
1630
1631 // Consolidate the average HFR
1632 currentHFR = samples.isEmpty() ? -1 : std::accumulate(samples.begin(), samples.end(), .0) / samples.size();
1633
1634 // Return whether we need more frame based on user requirement
1635 return HFRFrames.count() < focusFramesSpin->value();
1636 }
1637
settle(const FocusState completionState,const bool autoFocusUsed)1638 void Focus::settle(const FocusState completionState, const bool autoFocusUsed)
1639 {
1640 state = completionState;
1641 if (completionState == Ekos::FOCUS_COMPLETE)
1642 {
1643 if (autoFocusUsed)
1644 {
1645 // Prepare the message for Analyze
1646 const int size = hfr_position.size();
1647 QString analysis_results = "";
1648
1649 for (int i = 0; i < size; ++i)
1650 {
1651 analysis_results.append(QString("%1%2|%3")
1652 .arg(i == 0 ? "" : "|" )
1653 .arg(QString::number(hfr_position[i], 'f', 0))
1654 .arg(QString::number(hfr_value[i], 'f', 3)));
1655 }
1656
1657 KSNotification::event(QLatin1String("FocusSuccessful"), i18n("Autofocus operation completed successfully"));
1658 emit autofocusComplete(filter(), analysis_results);
1659 }
1660 }
1661 else
1662 {
1663 if (autoFocusUsed)
1664 {
1665 KSNotification::event(QLatin1String("FocusFailed"), i18n("Autofocus operation failed"),
1666 KSNotification::EVENT_ALERT);
1667 emit autofocusAborted(filter(), "");
1668 }
1669 }
1670
1671 qCDebug(KSTARS_EKOS_FOCUS) << "Settled. State:" << Ekos::getFocusStatusString(state);
1672
1673 // Delay state notification if we have a locked filter pending return to original filter
1674 if (fallbackFilterPending)
1675 {
1676 filterManager->setFilterPosition(fallbackFilterPosition,
1677 static_cast<FilterManager::FilterPolicy>(FilterManager::CHANGE_POLICY | FilterManager::OFFSET_POLICY));
1678 }
1679 else
1680 emit newStatus(state);
1681
1682 resetButtons();
1683 }
1684
completeFocusProcedure(FocusState completionState,bool plot)1685 void Focus::completeFocusProcedure(FocusState completionState, bool plot)
1686 {
1687 if (inAutoFocus)
1688 {
1689 if (completionState == Ekos::FOCUS_COMPLETE)
1690 {
1691 if (plot)
1692 emit redrawHFRPlot(polynomialFit.get(), currentPosition, currentHFR);
1693 appendLogText(i18np("Focus procedure completed after %1 iteration.",
1694 "Focus procedure completed after %1 iterations.", hfr_position.count()));
1695
1696 setLastFocusTemperature();
1697
1698 // CR add auto focus position, temperature and filter to log in CSV format
1699 // this will help with setting up focus offsets and temperature compensation
1700 qCInfo(KSTARS_EKOS_FOCUS) << "Autofocus values: position," << currentPosition << ", temperature,"
1701 << m_LastSourceAutofocusTemperature << ", filter," << filter()
1702 << ", HFR," << currentHFR << ", altitude," << mountAlt;
1703
1704 appendFocusLogText(QString("%1, %2, %3, %4, %5\n")
1705 .arg(QString::number(currentPosition))
1706 .arg(QString::number(m_LastSourceAutofocusTemperature, 'f', 1))
1707 .arg(filter())
1708 .arg(QString::number(currentHFR, 'f', 3))
1709 .arg(QString::number(mountAlt, 'f', 1)));
1710
1711 // Replace user position with optimal position
1712 absTicksSpin->setValue(currentPosition);
1713 }
1714 // In case of failure, go back to last position if the focuser is absolute
1715 else if (canAbsMove && initialFocuserAbsPosition >= 0 && resetFocusIteration <= MAXIMUM_RESET_ITERATIONS)
1716 {
1717 // If we're doing in-sequence focusing using an absolute focuser, retry focusing once, starting from last known good position
1718 bool const retry_focusing = !restartFocus && ++resetFocusIteration < MAXIMUM_RESET_ITERATIONS;
1719
1720 // If retrying, before moving, reset focus frame in case the star in subframe was lost
1721 if (retry_focusing)
1722 {
1723 restartFocus = true;
1724 resetFrame();
1725 }
1726
1727 resetFocuser();
1728
1729 // Bypass the rest of the function if we retry - we will fail if we could not move the focuser
1730 if (retry_focusing)
1731 return;
1732 }
1733
1734 // Reset the retry count on success or maximum count
1735 resetFocusIteration = 0;
1736 }
1737
1738 const bool autoFocusUsed = inAutoFocus;
1739
1740 // Reset the autofocus flags
1741 stop(completionState);
1742
1743 // Refresh display if needed
1744 if (focusAlgorithm == FOCUS_POLYNOMIAL && plot)
1745 emit drawPolynomial(polynomialFit.get(), isVShapeSolution, true);
1746
1747 // Enforce settling duration
1748 int const settleTime = m_GuidingSuspended ? GuideSettleTime->value() : 0;
1749
1750 if (settleTime > 0)
1751 appendLogText(i18n("Settling for %1s...", settleTime));
1752
1753 QTimer::singleShot(settleTime * 1000, this, [ &, settleTime, completionState, autoFocusUsed]()
1754 {
1755 settle(completionState, autoFocusUsed);
1756
1757 if (settleTime > 0)
1758 appendLogText(i18n("Settling complete."));
1759 });
1760 }
1761
resetFocuser()1762 void Focus::resetFocuser()
1763 {
1764 // If we are able to and need to, move the focuser back to the initial position and let the procedure restart from its termination
1765 if (currentFocuser && currentFocuser->isConnected() && initialFocuserAbsPosition >= 0)
1766 {
1767 // HACK: If the focuser will not move, cheat a little to get the notification - see processNumber
1768 if (currentPosition == initialFocuserAbsPosition)
1769 currentPosition--;
1770
1771 appendLogText(i18n("Autofocus failed, moving back to initial focus position %1.", initialFocuserAbsPosition));
1772 currentFocuser->moveAbs(initialFocuserAbsPosition);
1773 /* Restart will be executed by the end-of-move notification from the device if needed by resetFocus */
1774 }
1775 }
1776
setCurrentHFR(double value)1777 void Focus::setCurrentHFR(double value)
1778 {
1779 currentHFR = value;
1780
1781 // Let's now report the current HFR
1782 qCDebug(KSTARS_EKOS_FOCUS) << "Focus newFITS #" << HFRFrames.count() + 1 << ": Current HFR " << currentHFR << " Num stars "
1783 << (starSelected ? 1 : m_ImageData->getDetectedStars());
1784
1785 // Take the new HFR into account, eventually continue to stack samples
1786 if (appendHFR(currentHFR))
1787 {
1788 capture();
1789 return;
1790 }
1791 else HFRFrames.clear();
1792
1793 // Let signal the current HFR now depending on whether the focuser is absolute or relative
1794 if (canAbsMove)
1795 emit newHFR(currentHFR, currentPosition);
1796 else
1797 emit newHFR(currentHFR, -1);
1798
1799 // Format the HFR value into a string
1800 QString HFRText = QString("%1").arg(currentHFR, 0, 'f', 2);
1801 HFROut->setText(HFRText);
1802 starsOut->setText(QString("%1").arg(m_ImageData->getDetectedStars()));
1803 iterOut->setText(QString("%1").arg(absIterations + 1));
1804
1805 // Display message in case _last_ HFR was negative
1806 if (lastHFR == FocusAlgorithmInterface::IGNORED_HFR)
1807 appendLogText(i18n("FITS received. No stars detected."));
1808
1809 // If we have a valid HFR value
1810 if (currentHFR > 0)
1811 {
1812 // Check if we're done from polynomial fitting algorithm
1813 if (focusAlgorithm == FOCUS_POLYNOMIAL && polySolutionFound == MINIMUM_POLY_SOLUTIONS)
1814 {
1815 polySolutionFound = 0;
1816 completeFocusProcedure(Ekos::FOCUS_COMPLETE);
1817 return;
1818 }
1819
1820 Edge *selectedHFRStarHFR = nullptr;
1821
1822 // Center tracking box around selected star (if it valid) either in:
1823 // 1. Autofocus
1824 // 2. CheckFocus (minimumHFRCheck)
1825 // The starCenter _must_ already be defined, otherwise, we proceed until
1826 // the latter half of the function searches for a star and define it.
1827 if (starCenter.isNull() == false && (inAutoFocus || minimumRequiredHFR >= 0) &&
1828 (selectedHFRStarHFR = m_ImageData->getSelectedHFRStar()) != nullptr)
1829 {
1830 // Now we have star selected in the frame
1831 starSelected = true;
1832 starCenter.setX(qMax(0, static_cast<int>(selectedHFRStarHFR->x)));
1833 starCenter.setY(qMax(0, static_cast<int>(selectedHFRStarHFR->y)));
1834
1835 syncTrackingBoxPosition();
1836
1837 // Record the star information (X, Y, currentHFR)
1838 QVector3D oneStar = starCenter;
1839 oneStar.setZ(currentHFR);
1840 starsHFR.append(oneStar);
1841 }
1842 else
1843 {
1844 // Record the star information (X, Y, currentHFR)
1845 QVector3D oneStar(starCenter.x(), starCenter.y(), currentHFR);
1846 starsHFR.append(oneStar);
1847 }
1848
1849 if (currentHFR > maxHFR)
1850 maxHFR = currentHFR;
1851
1852 // Append point to the #Iterations vs #HFR chart in case of looping or in case in autofocus with a focus
1853 // that does not support position feedback.
1854
1855 // If inAutoFocus is true without canAbsMove and without canRelMove, canTimerMove must be true.
1856 // We'd only want to execute this if the focus linear algorithm is not being used, as that
1857 // algorithm simulates a position-based system even for timer-based focusers.
1858 if (inFocusLoop || (inAutoFocus && ! isPositionBased()))
1859 {
1860 int pos = hfr_position.empty() ? 1 : hfr_position.last() + 1;
1861 addPlotPosition(pos, currentHFR);
1862 }
1863 }
1864 else
1865 {
1866 // Let's record an invalid star result - IGNORED_HFR must be suitable as invalid star HFR!
1867 QVector3D oneStar(starCenter.x(), starCenter.y(), FocusAlgorithmInterface::IGNORED_HFR);
1868 starsHFR.append(oneStar);
1869 }
1870
1871 // First check that we haven't already search for stars
1872 // Since star-searching algorithm are time-consuming, we should only search when necessary
1873 focusView->updateFrame();
1874
1875 setHFRComplete();
1876 }
1877
setCaptureComplete()1878 void Focus::setCaptureComplete()
1879 {
1880 DarkLibrary::Instance()->disconnect(this);
1881
1882 // If we have a box, sync the bounding box to its position.
1883 syncTrackingBoxPosition();
1884
1885 // Notify user if we're not looping
1886 if (inFocusLoop == false)
1887 appendLogText(i18n("Image received."));
1888
1889 if (captureInProgress && inFocusLoop == false && inAutoFocus == false)
1890 currentCCD->setUploadMode(rememberUploadMode);
1891
1892 if (m_RememberCameraFastExposure && inFocusLoop == false && inAutoFocus == false)
1893 {
1894 m_RememberCameraFastExposure = false;
1895 currentCCD->setFastExposureEnabled(true);
1896 }
1897
1898 captureInProgress = false;
1899
1900
1901 // Emit the whole image
1902 emit newImage(focusView);
1903 // Emit the tracking (bounding) box view. Used in Summary View
1904 emit newStarPixmap(focusView->getTrackingBoxPixmap(10));
1905
1906 // If we are not looping; OR
1907 // If we are looping but we already have tracking box enabled; OR
1908 // If we are asked to analyze _all_ the stars within the field
1909 // THEN let's find stars in the image and get current HFR
1910 if (inFocusLoop == false || (inFocusLoop && (focusView->isTrackingBoxEnabled() || Options::focusUseFullField())))
1911 {
1912 if (m_ImageData->areStarsSearched() == false)
1913 {
1914 analyzeSources();
1915 }
1916 }
1917 else
1918 setHFRComplete();
1919 }
1920
setHFRComplete()1921 void Focus::setHFRComplete()
1922 {
1923 // If we are just framing, let's capture again
1924 if (inFocusLoop)
1925 {
1926 capture();
1927 return;
1928 }
1929
1930 // Get target chip
1931 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
1932
1933 // Get target chip binning
1934 int subBinX = 1, subBinY = 1;
1935 if (!targetChip->getBinning(&subBinX, &subBinY))
1936 qCDebug(KSTARS_EKOS_FOCUS) << "Warning: target chip is reporting no binning property, using 1x1.";
1937
1938 // If star is NOT yet selected in a non-full-frame situation
1939 // then let's now try to find the star. This step is skipped for full frames
1940 // since there isn't a single star to select as we are only interested in the overall average HFR.
1941 // We need to check if we can find the star right away, or if we need to _subframe_ around the
1942 // selected star.
1943 if (Options::focusUseFullField() == false && starCenter.isNull())
1944 {
1945 int x = 0, y = 0, w = 0, h = 0;
1946
1947 // Let's get the stored frame settings for this particular chip
1948 if (frameSettings.contains(targetChip))
1949 {
1950 QVariantMap settings = frameSettings[targetChip];
1951 x = settings["x"].toInt();
1952 y = settings["y"].toInt();
1953 w = settings["w"].toInt();
1954 h = settings["h"].toInt();
1955 }
1956 else
1957 // Otherwise let's get the target chip frame coordinates.
1958 targetChip->getFrame(&x, &y, &w, &h);
1959
1960 // In case auto star is selected.
1961 if (useAutoStar->isChecked())
1962 {
1963 // Do we have a valid star detected?
1964 Edge *selectedHFRStar = m_ImageData->getSelectedHFRStar();
1965
1966 if (selectedHFRStar == nullptr)
1967 {
1968 appendLogText(i18n("Failed to automatically select a star. Please select a star manually."));
1969
1970 // Center the tracking box in the frame and display it
1971 focusView->setTrackingBox(QRect(w - focusBoxSize->value() / (subBinX * 2),
1972 h - focusBoxSize->value() / (subBinY * 2),
1973 focusBoxSize->value() / subBinX, focusBoxSize->value() / subBinY));
1974 focusView->setTrackingBoxEnabled(true);
1975
1976 // Use can now move it to select the desired star
1977 state = Ekos::FOCUS_WAITING;
1978 qCDebug(KSTARS_EKOS_FOCUS) << "State:" << Ekos::getFocusStatusString(state);
1979 emit newStatus(state);
1980
1981 // Start the wait timer so we abort after a timeout if the user does not make a choice
1982 waitStarSelectTimer.start();
1983
1984 return;
1985 }
1986
1987 // set the tracking box on selectedHFRStar
1988 starCenter.setX(selectedHFRStar->x);
1989 starCenter.setY(selectedHFRStar->y);
1990 starCenter.setZ(subBinX);
1991 starSelected = true;
1992 syncTrackingBoxPosition();
1993
1994 defaultScale = static_cast<FITSScale>(filterCombo->currentIndex());
1995
1996 // Do we need to subframe?
1997 if (subFramed == false && useSubFrame->isEnabled() && useSubFrame->isChecked())
1998 {
1999 int offset = (static_cast<double>(focusBoxSize->value()) / subBinX) * 1.5;
2000 int subX = (selectedHFRStar->x - offset) * subBinX;
2001 int subY = (selectedHFRStar->y - offset) * subBinY;
2002 int subW = offset * 2 * subBinX;
2003 int subH = offset * 2 * subBinY;
2004
2005 int minX, maxX, minY, maxY, minW, maxW, minH, maxH;
2006 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
2007
2008 // Try to limit the subframed selection
2009 if (subX < minX)
2010 subX = minX;
2011 if (subY < minY)
2012 subY = minY;
2013 if ((subW + subX) > maxW)
2014 subW = maxW - subX;
2015 if ((subH + subY) > maxH)
2016 subH = maxH - subY;
2017
2018 // Now we store the subframe coordinates in the target chip frame settings so we
2019 // reuse it later when we capture again.
2020 QVariantMap settings = frameSettings[targetChip];
2021 settings["x"] = subX;
2022 settings["y"] = subY;
2023 settings["w"] = subW;
2024 settings["h"] = subH;
2025 settings["binx"] = subBinX;
2026 settings["biny"] = subBinY;
2027
2028 qCDebug(KSTARS_EKOS_FOCUS) << "Frame is subframed. X:" << subX << "Y:" << subY << "W:" << subW << "H:" << subH << "binX:" <<
2029 subBinX << "binY:" << subBinY;
2030
2031 starsHFR.clear();
2032
2033 frameSettings[targetChip] = settings;
2034
2035 // Set the star center in the center of the subframed coordinates
2036 starCenter.setX(subW / (2 * subBinX));
2037 starCenter.setY(subH / (2 * subBinY));
2038 starCenter.setZ(subBinX);
2039
2040 subFramed = true;
2041
2042 focusView->setFirstLoad(true);
2043
2044 // Now let's capture again for the actual requested subframed image.
2045 capture();
2046 return;
2047 }
2048 // If we're subframed or don't need subframe, let's record the max star coordinates
2049 else
2050 {
2051 starCenter.setX(selectedHFRStar->x);
2052 starCenter.setY(selectedHFRStar->y);
2053 starCenter.setZ(subBinX);
2054
2055 // Let's now capture again if we're autofocusing
2056 if (inAutoFocus)
2057 {
2058 capture();
2059 return;
2060 }
2061 }
2062 }
2063 // If manual selection is enabled then let's ask the user to select the focus star
2064 else
2065 {
2066 appendLogText(i18n("Capture complete. Select a star to focus."));
2067
2068 starSelected = false;
2069
2070 // Let's now display and set the tracking box in the center of the frame
2071 // so that the user moves it around to select the desired star.
2072 int subBinX = 1, subBinY = 1;
2073 targetChip->getBinning(&subBinX, &subBinY);
2074
2075 focusView->setTrackingBox(QRect((w - focusBoxSize->value()) / (subBinX * 2),
2076 (h - focusBoxSize->value()) / (2 * subBinY),
2077 focusBoxSize->value() / subBinX, focusBoxSize->value() / subBinY));
2078 focusView->setTrackingBoxEnabled(true);
2079
2080 // Now we wait
2081 state = Ekos::FOCUS_WAITING;
2082 qCDebug(KSTARS_EKOS_FOCUS) << "State:" << Ekos::getFocusStatusString(state);
2083 emit newStatus(state);
2084
2085 // If the user does not select for a timeout period, we abort.
2086 waitStarSelectTimer.start();
2087 return;
2088 }
2089 }
2090
2091 // Check if the focus module is requested to verify if the minimum HFR value is met.
2092 if (minimumRequiredHFR >= 0)
2093 {
2094 // In case we failed to detected, we capture again.
2095 if (currentHFR == -1)
2096 {
2097 if (noStarCount++ < MAX_RECAPTURE_RETRIES)
2098 {
2099 appendLogText(i18n("No stars detected while testing HFR, capturing again..."));
2100 // On Last Attempt reset focus frame to capture full frame and recapture star if possible
2101 if (noStarCount == MAX_RECAPTURE_RETRIES)
2102 resetFrame();
2103 capture();
2104 return;
2105 }
2106 // If we exceeded maximum tries we abort
2107 else
2108 {
2109 noStarCount = 0;
2110 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2111 }
2112 }
2113 // If the detect current HFR is more than the minimum required HFR
2114 // then we should start the autofocus process now to bring it down.
2115 else if (currentHFR > minimumRequiredHFR)
2116 {
2117 qCDebug(KSTARS_EKOS_FOCUS) << "Current HFR:" << currentHFR << "is above required minimum HFR:" << minimumRequiredHFR <<
2118 ". Starting AutoFocus...";
2119 minimumRequiredHFR = -1;
2120 start();
2121 }
2122 // Otherwise, the current HFR is fine and lower than the required minimum HFR so we announce success.
2123 else
2124 {
2125 qCDebug(KSTARS_EKOS_FOCUS) << "Current HFR:" << currentHFR << "is below required minimum HFR:" << minimumRequiredHFR <<
2126 ". Autofocus successful.";
2127 completeFocusProcedure(Ekos::FOCUS_COMPLETE);
2128 }
2129
2130 // Nothing more for now
2131 return;
2132 }
2133
2134 // If focus logging is enabled, let's save the frame.
2135 if (Options::focusLogging() && Options::saveFocusImages())
2136 {
2137 QDir dir;
2138 QDateTime now = KStarsData::Instance()->lt();
2139 QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("autofocus/" +
2140 now.toString("yyyy-MM-dd"));
2141 dir.mkpath(path);
2142 // IS8601 contains colons but they are illegal under Windows OS, so replacing them with '-'
2143 // The timestamp is no longer ISO8601 but it should solve interoperality issues between different OS hosts
2144 QString name = "autofocus_frame_" + now.toString("HH-mm-ss") + ".fits";
2145 QString filename = path + QStringLiteral("/") + name;
2146 m_ImageData->saveImage(filename);
2147 }
2148
2149 // If we are not in autofocus process, we're done.
2150 if (inAutoFocus == false)
2151 {
2152 // If we are done and there is no further autofocus,
2153 // we reset state to IDLE
2154 if (state != Ekos::FOCUS_IDLE)
2155 {
2156 state = Ekos::FOCUS_IDLE;
2157 qCDebug(KSTARS_EKOS_FOCUS) << "State:" << Ekos::getFocusStatusString(state);
2158 emit newStatus(state);
2159 }
2160
2161 resetButtons();
2162 return;
2163 }
2164
2165 // Set state to progress
2166 if (state != Ekos::FOCUS_PROGRESS)
2167 {
2168 state = Ekos::FOCUS_PROGRESS;
2169 qCDebug(KSTARS_EKOS_FOCUS) << "State:" << Ekos::getFocusStatusString(state);
2170 emit newStatus(state);
2171 }
2172
2173 // Now let's kick in the algorithms
2174
2175 if (focusAlgorithm == FOCUS_LINEAR)
2176 autoFocusLinear();
2177 else if (canAbsMove || canRelMove)
2178 // Position-based algorithms
2179 autoFocusAbs();
2180 else
2181 // Time open-looped algorithms
2182 autoFocusRel();
2183 }
2184
clearDataPoints()2185 void Focus::clearDataPoints()
2186 {
2187 maxHFR = 1;
2188 polynomialFit.reset();
2189 hfr_position.clear();
2190 hfr_value.clear();
2191 isVShapeSolution = false;
2192 emit initHFRPlot(inFocusLoop == false && isPositionBased());
2193 }
2194
autoFocusChecks()2195 bool Focus::autoFocusChecks()
2196 {
2197 if (++absIterations > MAXIMUM_ABS_ITERATIONS)
2198 {
2199 appendLogText(i18n("Autofocus failed to reach proper focus. Try increasing tolerance value."));
2200 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2201 return false;
2202 }
2203
2204 // No stars detected, try to capture again
2205 if (currentHFR == FocusAlgorithmInterface::IGNORED_HFR)
2206 {
2207 if (noStarCount < MAX_RECAPTURE_RETRIES)
2208 {
2209 noStarCount++;
2210 appendLogText(i18n("No stars detected, capturing again..."));
2211 capture();
2212 return false;
2213 }
2214 else if (focusAlgorithm == FOCUS_LINEAR)
2215 {
2216 appendLogText(i18n("Failed to detect any stars at position %1. Continuing...", currentPosition));
2217 noStarCount = 0;
2218 }
2219 else
2220 {
2221 appendLogText(i18n("Failed to detect any stars. Reset frame and try again."));
2222 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2223 return false;
2224 }
2225 }
2226 else
2227 noStarCount = 0;
2228
2229 return true;
2230 }
2231
plotLinearFocus()2232 void Focus::plotLinearFocus()
2233 {
2234 // I was hoping to avoid intermediate plotting, just set everything up then plot,
2235 // but this isn't working. For now, with plt=true, plot on every intermediate update.
2236 bool plt = true;
2237
2238 // Get the data to plot.
2239 QVector<double> HFRs;
2240 QVector<int> positions;
2241 linearFocuser->getMeasurements(&positions, &HFRs);
2242 const FocusAlgorithmInterface::FocusParams ¶ms = linearFocuser->getParams();
2243
2244 // As an optimization for slower machines, e.g. RPi4s, if the points are the same except for
2245 // the last point, just emit the last point instead of redrawing everything.
2246 static QVector<double> lastHFRs;
2247 static QVector<int> lastPositions;
2248 bool incrementalChange = false;
2249 if (positions.size() > 1 && positions.size() == lastPositions.size() + 1)
2250 {
2251 bool ok = true;
2252 for (int i = 0; i < positions.size() - 1; ++i)
2253 if (positions[i] != lastPositions[i] || HFRs[i] != lastHFRs[i])
2254 {
2255 ok = false;
2256 break;
2257 }
2258 incrementalChange = ok;
2259 }
2260 lastPositions = positions;
2261 lastHFRs = HFRs;
2262
2263 if (incrementalChange)
2264 emit newHFRPlotPosition(static_cast<double>(positions.last()), HFRs.last(), params.initialStepSize, plt);
2265 else
2266 {
2267 emit initHFRPlot(true);
2268 for (int i = 0; i < positions.size(); ++i)
2269 emit newHFRPlotPosition(static_cast<double>(positions[i]), HFRs[i], params.initialStepSize, plt);
2270 }
2271
2272 // Plot the polynomial, if there are enough points.
2273 if (HFRs.size() > 3)
2274 {
2275 // The polynomial should only reflect 1st-pass samples.
2276 QVector<double> pass1HFRs;
2277 QVector<int> pass1Positions;
2278 linearFocuser->getPass1Measurements(&pass1Positions, &pass1HFRs);
2279 polynomialFit.reset(new PolynomialFit(2, pass1Positions, pass1HFRs));
2280
2281 double minPosition, minValue;
2282 double searchMin = std::max(params.minPositionAllowed, params.startPosition - params.maxTravel);
2283 double searchMax = std::min(params.maxPositionAllowed, params.startPosition + params.maxTravel);
2284 if (polynomialFit->findMinimum(params.startPosition, searchMin, searchMax, &minPosition, &minValue))
2285 {
2286 emit drawPolynomial(polynomialFit.get(), true, true, plt);
2287
2288 // Only plot the first pass' min position if we're not done.
2289 // Once we have a result, we don't want to display an intermediate minimum.
2290 if (linearFocuser->isDone())
2291 emit minimumFound(-1, -1, plt);
2292 else
2293 emit minimumFound(minPosition, minValue, plt);
2294 }
2295 else
2296 {
2297 // Didn't get a good polynomial fit.
2298 emit drawPolynomial(polynomialFit.get(), false, false, plt);
2299 emit minimumFound(-1, -1, plt);
2300 }
2301 }
2302
2303 // Linear focuser might change the latest hfr with its relativeHFR scheme.
2304 HFROut->setText(QString("%1").arg(linearFocuser->latestHFR(), 0, 'f', 2));
2305
2306 emit setTitle(linearFocuser->getTextStatus());
2307
2308 if (!plt) HFRPlot->replot();
2309 }
2310
autoFocusLinear()2311 void Focus::autoFocusLinear()
2312 {
2313 if (!autoFocusChecks())
2314 return;
2315
2316 if (!canAbsMove && !canRelMove && canTimerMove)
2317 {
2318 //const bool kFixPosition = true;
2319 if (linearRequestedPosition != currentPosition)
2320 //if (kFixPosition && (linearRequestedPosition != currentPosition))
2321 {
2322 qCDebug(KSTARS_EKOS_FOCUS) << "Linear: warning, changing position " << currentPosition << " to "
2323 << linearRequestedPosition;
2324
2325 currentPosition = linearRequestedPosition;
2326 }
2327 }
2328
2329 addPlotPosition(currentPosition, currentHFR, false);
2330
2331 // Only use the relativeHFR algorithm if full field is enabled with one capture/measurement.
2332 bool useFocusStarsHFR = Options::focusUseFullField() && focusFramesSpin->value() == 1;
2333 auto focusStars = useFocusStarsHFR ? &(m_ImageData->getStarCenters()) : nullptr;
2334
2335 linearRequestedPosition = linearFocuser->newMeasurement(currentPosition, currentHFR, focusStars);
2336 plotLinearFocus();
2337
2338 const int nextPosition = adjustLinearPosition(currentPosition, linearRequestedPosition);
2339 if (linearRequestedPosition == -1)
2340 {
2341 if (linearFocuser->isDone() && linearFocuser->solution() != -1)
2342 {
2343 completeFocusProcedure(Ekos::FOCUS_COMPLETE, false);
2344 }
2345 else
2346 {
2347 qCDebug(KSTARS_EKOS_FOCUS) << linearFocuser->doneReason();
2348 appendLogText("Linear autofocus algorithm aborted.");
2349 completeFocusProcedure(Ekos::FOCUS_ABORTED, false);
2350 }
2351 return;
2352 }
2353 else
2354 {
2355 const int delta = nextPosition - currentPosition;
2356
2357 if (!changeFocus(delta))
2358 completeFocusProcedure(Ekos::FOCUS_ABORTED, false);
2359
2360 return;
2361 }
2362 }
2363
autoFocusAbs()2364 void Focus::autoFocusAbs()
2365 {
2366 // Q_ASSERT_X(canAbsMove || canRelMove, __FUNCTION__, "Prerequisite: only absolute and relative focusers");
2367
2368 static int minHFRPos = 0, focusOutLimit = 0, focusInLimit = 0;
2369 static double minHFR = 0;
2370 double targetPosition = 0, delta = 0;
2371
2372 QString deltaTxt = QString("%1").arg(fabs(currentHFR - minHFR) * 100.0, 0, 'g', 3);
2373 QString HFRText = QString("%1").arg(currentHFR, 0, 'g', 3);
2374
2375 qCDebug(KSTARS_EKOS_FOCUS) << "========================================";
2376 qCDebug(KSTARS_EKOS_FOCUS) << "Current HFR: " << currentHFR << " Current Position: " << currentPosition;
2377 qCDebug(KSTARS_EKOS_FOCUS) << "Last minHFR: " << minHFR << " Last MinHFR Pos: " << minHFRPos;
2378 qCDebug(KSTARS_EKOS_FOCUS) << "Delta: " << deltaTxt << "%";
2379 qCDebug(KSTARS_EKOS_FOCUS) << "========================================";
2380
2381 if (minHFR)
2382 appendLogText(i18n("FITS received. HFR %1 @ %2. Delta (%3%)", HFRText, currentPosition, deltaTxt));
2383 else
2384 appendLogText(i18n("FITS received. HFR %1 @ %2.", HFRText, currentPosition));
2385
2386 if (!autoFocusChecks())
2387 return;
2388
2389 addPlotPosition(currentPosition, currentHFR);
2390
2391 switch (m_LastFocusDirection)
2392 {
2393 case FOCUS_NONE:
2394 lastHFR = currentHFR;
2395 initialFocuserAbsPosition = currentPosition;
2396 minHFR = currentHFR;
2397 minHFRPos = currentPosition;
2398 HFRDec = 0;
2399 HFRInc = 0;
2400 focusOutLimit = 0;
2401 focusInLimit = 0;
2402
2403 // This is the first step, so clamp the initial target position to the device limits
2404 // If the focuser cannot move because it is at one end of the interval, try the opposite direction next
2405 if (absMotionMax < currentPosition + pulseDuration)
2406 {
2407 if (currentPosition < absMotionMax)
2408 {
2409 pulseDuration = absMotionMax - currentPosition;
2410 }
2411 else
2412 {
2413 pulseDuration = 0;
2414 m_LastFocusDirection = FOCUS_IN;
2415 }
2416 }
2417 else if (currentPosition + pulseDuration < absMotionMin)
2418 {
2419 if (absMotionMin < currentPosition)
2420 {
2421 pulseDuration = currentPosition - absMotionMin;
2422 }
2423 else
2424 {
2425 pulseDuration = 0;
2426 m_LastFocusDirection = FOCUS_OUT;
2427 }
2428 }
2429
2430 if (!changeFocus(pulseDuration))
2431 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2432
2433 break;
2434
2435 case FOCUS_IN:
2436 case FOCUS_OUT:
2437 static int lastHFRPos = 0, initSlopePos = 0;
2438 static double initSlopeHFR = 0;
2439
2440 if (reverseDir && focusInLimit && focusOutLimit &&
2441 fabs(currentHFR - minHFR) < (toleranceIN->value() / 100.0) && HFRInc == 0)
2442 {
2443 if (absIterations <= 2)
2444 {
2445 appendLogText(
2446 i18n("Change in HFR is too small. Try increasing the step size or decreasing the tolerance."));
2447 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2448 }
2449 else if (noStarCount > 0)
2450 {
2451 appendLogText(i18n("Failed to detect focus star in frame. Capture and select a focus star."));
2452 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2453 }
2454 else
2455 {
2456 completeFocusProcedure(Ekos::FOCUS_COMPLETE);
2457 }
2458 break;
2459 }
2460 else if (currentHFR < lastHFR)
2461 {
2462 double slope = 0;
2463
2464 // Let's try to calculate slope of the V curve.
2465 if (initSlopeHFR == 0 && HFRInc == 0 && HFRDec >= 1)
2466 {
2467 initSlopeHFR = lastHFR;
2468 initSlopePos = lastHFRPos;
2469
2470 qCDebug(KSTARS_EKOS_FOCUS) << "Setting initial slop to " << initSlopePos << " @ HFR " << initSlopeHFR;
2471 }
2472
2473 // Let's now limit the travel distance of the focuser
2474 if (m_LastFocusDirection == FOCUS_OUT && lastHFRPos < focusInLimit && fabs(currentHFR - lastHFR) > 0.1)
2475 {
2476 focusInLimit = lastHFRPos;
2477 qCDebug(KSTARS_EKOS_FOCUS) << "New FocusInLimit " << focusInLimit;
2478 }
2479 else if (m_LastFocusDirection == FOCUS_IN && lastHFRPos > focusOutLimit &&
2480 fabs(currentHFR - lastHFR) > 0.1)
2481 {
2482 focusOutLimit = lastHFRPos;
2483 qCDebug(KSTARS_EKOS_FOCUS) << "New FocusOutLimit " << focusOutLimit;
2484 }
2485
2486 // If we have slope, get next target position
2487 if (initSlopeHFR && absMotionMax > 50)
2488 {
2489 double factor = 0.5;
2490 slope = (currentHFR - initSlopeHFR) / (currentPosition - initSlopePos);
2491 if (fabs(currentHFR - minHFR) * 100.0 < 0.5)
2492 factor = 1 - fabs(currentHFR - minHFR) * 10;
2493 targetPosition = currentPosition + (currentHFR * factor - currentHFR) / slope;
2494 if (targetPosition < 0)
2495 {
2496 factor = 1;
2497 while (targetPosition < 0 && factor > 0)
2498 {
2499 factor -= 0.005;
2500 targetPosition = currentPosition + (currentHFR * factor - currentHFR) / slope;
2501 }
2502 }
2503 qCDebug(KSTARS_EKOS_FOCUS) << "Using slope to calculate target pulse...";
2504 }
2505 // Otherwise proceed iteratively
2506 else
2507 {
2508 if (m_LastFocusDirection == FOCUS_IN)
2509 targetPosition = currentPosition - pulseDuration;
2510 else
2511 targetPosition = currentPosition + pulseDuration;
2512
2513 qCDebug(KSTARS_EKOS_FOCUS) << "Proceeding iteratively to next target pulse ...";
2514 }
2515
2516 qCDebug(KSTARS_EKOS_FOCUS) << "V-Curve Slope " << slope << " current Position " << currentPosition
2517 << " targetPosition " << targetPosition;
2518
2519 lastHFR = currentHFR;
2520
2521 // Let's keep track of the minimum HFR
2522 if (lastHFR < minHFR)
2523 {
2524 minHFR = lastHFR;
2525 minHFRPos = currentPosition;
2526 qCDebug(KSTARS_EKOS_FOCUS) << "new minHFR " << minHFR << " @ position " << minHFRPos;
2527 }
2528
2529 lastHFRPos = currentPosition;
2530
2531 // HFR is decreasing, we are on the right direction
2532 HFRDec++;
2533 HFRInc = 0;
2534 }
2535 else
2536
2537 {
2538 // HFR increased, let's deal with it.
2539 //HFRInc++;
2540 HFRDec = 0;
2541
2542 // Reality Check: If it's first time, let's capture again and see if it changes.
2543 /*if (HFRInc <= 1 && reverseDir == false)
2544 {
2545 capture();
2546 return;
2547 }
2548 // Looks like we're going away from optimal HFR
2549 else
2550 {*/
2551 reverseDir = true;
2552 lastHFR = currentHFR;
2553 lastHFRPos = currentPosition;
2554 initSlopeHFR = 0;
2555 HFRInc = 0;
2556
2557 qCDebug(KSTARS_EKOS_FOCUS) << "Focus is moving away from optimal HFR.";
2558
2559 // Let's set new limits
2560 if (m_LastFocusDirection == FOCUS_IN)
2561 {
2562 focusInLimit = currentPosition;
2563 qCDebug(KSTARS_EKOS_FOCUS) << "Setting focus IN limit to " << focusInLimit;
2564
2565 if (hfr_position.count() > 3)
2566 {
2567 focusOutLimit = hfr_position[hfr_position.count() - 3];
2568 qCDebug(KSTARS_EKOS_FOCUS) << "Setting focus OUT limit to " << focusOutLimit;
2569 }
2570 }
2571 else
2572 {
2573 focusOutLimit = currentPosition;
2574 qCDebug(KSTARS_EKOS_FOCUS) << "Setting focus OUT limit to " << focusOutLimit;
2575
2576 if (hfr_position.count() > 3)
2577 {
2578 focusInLimit = hfr_position[hfr_position.count() - 3];
2579 qCDebug(KSTARS_EKOS_FOCUS) << "Setting focus IN limit to " << focusInLimit;
2580 }
2581 }
2582
2583 if (focusAlgorithm == FOCUS_POLYNOMIAL && hfr_position.count() > 5)
2584 {
2585 polynomialFit.reset(new PolynomialFit(3, hfr_position, hfr_value));
2586 double a = *std::min_element(hfr_position.constBegin(), hfr_position.constEnd());
2587 double b = *std::max_element(hfr_position.constBegin(), hfr_position.constEnd());
2588 double min_position = 0, min_hfr = 0;
2589 isVShapeSolution = polynomialFit->findMinimum(minHFRPos, a, b, &min_position, &min_hfr);
2590 qCDebug(KSTARS_EKOS_FOCUS) << "Found Minimum?" << (isVShapeSolution ? "Yes" : "No");
2591 if (isVShapeSolution)
2592 {
2593 qCDebug(KSTARS_EKOS_FOCUS) << "Minimum Solution:" << min_hfr << "@" << min_position;
2594 polySolutionFound++;
2595 targetPosition = round(min_position);
2596 appendLogText(i18n("Found polynomial solution @ %1", QString::number(min_position, 'f', 0)));
2597
2598 emit drawPolynomial(polynomialFit.get(), isVShapeSolution, true);
2599 emit minimumFound(min_position, min_hfr);
2600 }
2601 else
2602 {
2603 emit drawPolynomial(polynomialFit.get(), isVShapeSolution, false);
2604 }
2605 }
2606
2607 if (isVShapeSolution == false)
2608 {
2609 // Decrease pulse
2610 pulseDuration = pulseDuration * 0.75;
2611
2612 // Let's get close to the minimum HFR position so far detected
2613 if (m_LastFocusDirection == FOCUS_OUT)
2614 targetPosition = minHFRPos - pulseDuration / 2;
2615 else
2616 targetPosition = minHFRPos + pulseDuration / 2;
2617 }
2618
2619 qCDebug(KSTARS_EKOS_FOCUS) << "new targetPosition " << targetPosition;
2620 }
2621
2622 // Limit target Pulse to algorithm limits
2623 if (focusInLimit != 0 && m_LastFocusDirection == FOCUS_IN && targetPosition < focusInLimit)
2624 {
2625 targetPosition = focusInLimit;
2626 qCDebug(KSTARS_EKOS_FOCUS) << "Limiting target pulse to focus in limit " << targetPosition;
2627 }
2628 else if (focusOutLimit != 0 && m_LastFocusDirection == FOCUS_OUT && targetPosition > focusOutLimit)
2629 {
2630 targetPosition = focusOutLimit;
2631 qCDebug(KSTARS_EKOS_FOCUS) << "Limiting target pulse to focus out limit " << targetPosition;
2632 }
2633
2634 // Limit target pulse to focuser limits
2635 if (targetPosition < absMotionMin)
2636 targetPosition = absMotionMin;
2637 else if (targetPosition > absMotionMax)
2638 targetPosition = absMotionMax;
2639
2640 // We cannot go any further because of the device limits, this is a failure
2641 if (targetPosition == currentPosition)
2642 {
2643 // If case target position happens to be the minimal historical
2644 // HFR position, accept this value instead of bailing out.
2645 if (targetPosition == minHFRPos)
2646 {
2647 appendLogText("Stopping at minimum recorded HFR position.");
2648 completeFocusProcedure(Ekos::FOCUS_COMPLETE);
2649 }
2650 else
2651 {
2652 appendLogText("Focuser cannot move further, device limits reached. Autofocus aborted.");
2653 qCDebug(KSTARS_EKOS_FOCUS) << "Focuser cannot move further, restricted by device limits at " << targetPosition;
2654 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2655 }
2656 return;
2657 }
2658
2659 // Ops, deadlock
2660 if (focusOutLimit && focusOutLimit == focusInLimit)
2661 {
2662 appendLogText(i18n("Deadlock reached. Please try again with different settings."));
2663 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2664 return;
2665 }
2666
2667 // Restrict the target position even more with the maximum travel option
2668 if (fabs(targetPosition - initialFocuserAbsPosition) > maxTravelIN->value())
2669 {
2670 int minTravelLimit = qMax(0.0, initialFocuserAbsPosition - maxTravelIN->value());
2671 int maxTravelLimit = qMin(absMotionMax, initialFocuserAbsPosition + maxTravelIN->value());
2672
2673 // In case we are asked to go below travel limit, but we are not there yet
2674 // let us go there and see the result before aborting
2675 if (fabs(currentPosition - minTravelLimit) > 10 && targetPosition < minTravelLimit)
2676 {
2677 targetPosition = minTravelLimit;
2678 }
2679 // Same for max travel
2680 else if (fabs(currentPosition - maxTravelLimit) > 10 && targetPosition > maxTravelLimit)
2681 {
2682 targetPosition = maxTravelLimit;
2683 }
2684 else
2685 {
2686 qCDebug(KSTARS_EKOS_FOCUS) << "targetPosition (" << targetPosition << ") - initHFRAbsPos ("
2687 << initialFocuserAbsPosition << ") exceeds maxTravel distance of " << maxTravelIN->value();
2688
2689 appendLogText("Maximum travel limit reached. Autofocus aborted.");
2690 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2691 break;
2692 }
2693 }
2694
2695 // Get delta for next move
2696 delta = (targetPosition - currentPosition);
2697
2698 qCDebug(KSTARS_EKOS_FOCUS) << "delta (targetPosition - currentPosition) " << delta;
2699
2700 // Limit to Maximum permitted delta (Max Single Step Size)
2701 double limitedDelta = qMax(-1.0 * maxSingleStepIN->value(), qMin(1.0 * maxSingleStepIN->value(), delta));
2702 if (std::fabs(limitedDelta - delta) > 0)
2703 {
2704 qCDebug(KSTARS_EKOS_FOCUS) << "Limited delta to maximum permitted single step " << maxSingleStepIN->value();
2705 delta = limitedDelta;
2706 }
2707
2708 // Now cross your fingers and wait
2709 if (!changeFocus(delta))
2710 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2711
2712 break;
2713 }
2714 }
2715
addPlotPosition(int pos,double hfr,bool plot)2716 void Focus::addPlotPosition(int pos, double hfr, bool plot)
2717 {
2718 hfr_position.append(pos);
2719 hfr_value.append(hfr);
2720 if (plot)
2721 emit newHFRPlotPosition(pos, hfr, pulseDuration);
2722 }
2723
autoFocusRel()2724 void Focus::autoFocusRel()
2725 {
2726 static int noStarCount = 0;
2727 static double minHFR = 1e6;
2728 QString deltaTxt = QString("%1").arg(fabs(currentHFR - minHFR) * 100.0, 0, 'g', 2);
2729 QString minHFRText = QString("%1").arg(minHFR, 0, 'g', 3);
2730 QString HFRText = QString("%1").arg(currentHFR, 0, 'g', 3);
2731
2732 appendLogText(i18n("FITS received. HFR %1. Delta (%2%) Min HFR (%3)", HFRText, deltaTxt, minHFRText));
2733
2734 if (pulseDuration <= MINIMUM_PULSE_TIMER)
2735 {
2736 appendLogText(i18n("Autofocus failed to reach proper focus. Try adjusting the tolerance value."));
2737 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2738 return;
2739 }
2740
2741 // No stars detected, try to capture again
2742 if (currentHFR == FocusAlgorithmInterface::IGNORED_HFR)
2743 {
2744 if (noStarCount < MAX_RECAPTURE_RETRIES)
2745 {
2746 noStarCount++;
2747 appendLogText(i18n("No stars detected, capturing again..."));
2748 capture();
2749 return;
2750 }
2751 else if (focusAlgorithm == FOCUS_LINEAR)
2752 {
2753 appendLogText(i18n("Failed to detect any stars at position %1. Continuing...", currentPosition));
2754 noStarCount = 0;
2755 }
2756 else
2757 {
2758 appendLogText(i18n("Failed to detect any stars. Reset frame and try again."));
2759 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2760 return;
2761 }
2762 }
2763 else
2764 noStarCount = 0;
2765
2766 switch (m_LastFocusDirection)
2767 {
2768 case FOCUS_NONE:
2769 lastHFR = currentHFR;
2770 minHFR = 1e6;
2771 changeFocus(-pulseDuration);
2772 break;
2773
2774 case FOCUS_IN:
2775 case FOCUS_OUT:
2776 if (fabs(currentHFR - minHFR) < (toleranceIN->value() / 100.0) && HFRInc == 0)
2777 {
2778 completeFocusProcedure(Ekos::FOCUS_COMPLETE);
2779 }
2780 else if (currentHFR < lastHFR)
2781 {
2782 if (currentHFR < minHFR)
2783 minHFR = currentHFR;
2784
2785 lastHFR = currentHFR;
2786 changeFocus(m_LastFocusDirection == FOCUS_IN ? -pulseDuration : pulseDuration);
2787 HFRInc = 0;
2788 }
2789 else
2790 {
2791 //HFRInc++;
2792
2793 lastHFR = currentHFR;
2794
2795 HFRInc = 0;
2796
2797 pulseDuration *= 0.75;
2798
2799 if (!changeFocus(m_LastFocusDirection == FOCUS_IN ? pulseDuration : -pulseDuration))
2800 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2801 }
2802 break;
2803 }
2804 }
2805
2806 /*void Focus::registerFocusProperty(INDI::Property prop)
2807 {
2808 // Return if it is not our current focuser
2809 if (strcmp(prop->getDeviceName(), currentFocuser->getDeviceName()))
2810 return;
2811
2812 // Do not make unnecessary function call
2813 // Check if current focuser supports absolute mode
2814 if (canAbsMove == false && currentFocuser->canAbsMove())
2815 {
2816 canAbsMove = true;
2817 getAbsFocusPosition();
2818
2819 absTicksSpin->setEnabled(true);
2820 absTicksLabel->setEnabled(true);
2821 startGotoB->setEnabled(true);
2822 }
2823
2824 // Do not make unnecessary function call
2825 // Check if current focuser supports relative mode
2826 if (canRelMove == false && currentFocuser->canRelMove())
2827 canRelMove = true;
2828
2829 if (canTimerMove == false && currentFocuser->canTimerMove())
2830 {
2831 canTimerMove = true;
2832 resetButtons();
2833 }
2834 }*/
2835
autoFocusProcessPositionChange(IPState state)2836 void Focus::autoFocusProcessPositionChange(IPState state)
2837 {
2838 if (state == IPS_OK && captureInProgress == false)
2839 {
2840 // Normally, if we are auto-focusing, after we move the focuser we capture an image.
2841 // However, the Linear algorithm, at the start of its passes, requires two
2842 // consecutive focuser moves--the first out further than we want, and a second
2843 // move back in, so that we eliminate backlash and are always moving in before a capture.
2844 if (focuserAdditionalMovement > 0)
2845 {
2846 int temp = focuserAdditionalMovement;
2847 focuserAdditionalMovement = 0;
2848 qCDebug(KSTARS_EKOS_FOCUS) << QString("LinearFocuser: un-doing extension. Moving back in by %1").arg(temp);
2849
2850 if (!focusIn(temp))
2851 {
2852 appendLogText(i18n("Focuser error, check INDI panel."));
2853 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2854 }
2855 }
2856 else
2857 {
2858 qCDebug(KSTARS_EKOS_FOCUS) << QString("Focus position reached at %1, starting capture in %2 seconds.").arg(
2859 currentPosition).arg(FocusSettleTime->value());
2860 capture(FocusSettleTime->value());
2861 }
2862 }
2863 else if (state == IPS_ALERT)
2864 {
2865 appendLogText(i18n("Focuser error, check INDI panel."));
2866 completeFocusProcedure(Ekos::FOCUS_ABORTED);
2867 }
2868 }
2869
processFocusNumber(INumberVectorProperty * nvp)2870 void Focus::processFocusNumber(INumberVectorProperty *nvp)
2871 {
2872 if (currentFocuser == nullptr)
2873 return;
2874
2875 // Return if it is not our current focuser
2876 if (nvp->device != currentFocuser->getDeviceName())
2877 return;
2878
2879 // Only process focus properties
2880 if (QString(nvp->name).contains("focus", Qt::CaseInsensitive) == false)
2881 return;
2882
2883 if (!strcmp(nvp->name, "FOCUS_BACKLASH_STEPS"))
2884 {
2885 focusBacklashSpin->setValue(nvp->np[0].value);
2886 return;
2887 }
2888
2889 if (!strcmp(nvp->name, "ABS_FOCUS_POSITION"))
2890 {
2891 m_FocusMotionTimer.stop();
2892 INumber *pos = IUFindNumber(nvp, "FOCUS_ABSOLUTE_POSITION");
2893
2894 // FIXME: We should check state validity, but some focusers do not care - make ignore an option!
2895 if (pos)
2896 {
2897 int newPosition = static_cast<int>(pos->value);
2898
2899 // Some absolute focuser constantly report the position without a state change.
2900 // Therefore we ignore it if both value and state are the same as last time.
2901 // HACK: This would shortcut the autofocus procedure reset, see completeFocusProcedure for the small hack
2902 if (currentPosition == newPosition && currentPositionState == nvp->s)
2903 return;
2904
2905 currentPositionState = nvp->s;
2906
2907 if (currentPosition != newPosition)
2908 {
2909 currentPosition = newPosition;
2910 qCDebug(KSTARS_EKOS_FOCUS) << "Abs Focuser position changed to " << currentPosition << "State:" << pstateStr(
2911 currentPositionState);
2912 absTicksLabel->setText(QString::number(currentPosition));
2913 emit absolutePositionChanged(currentPosition);
2914 }
2915 }
2916
2917 if (nvp->s == IPS_OK)
2918 {
2919 // Systematically reset UI when focuser finishes moving
2920 resetButtons();
2921
2922 if (adjustFocus)
2923 {
2924 adjustFocus = false;
2925 m_LastFocusDirection = FOCUS_NONE;
2926 emit focusPositionAdjusted();
2927 return;
2928 }
2929
2930 if (restartFocus && status() != Ekos::FOCUS_ABORTED)
2931 {
2932 restartFocus = false;
2933 inAutoFocus = false;
2934 appendLogText(i18n("Restarting autofocus process..."));
2935 start();
2936 }
2937 }
2938
2939 if (canAbsMove && inAutoFocus)
2940 {
2941 autoFocusProcessPositionChange(nvp->s);
2942 }
2943 else if (nvp->s == IPS_ALERT)
2944 appendLogText(i18n("Focuser error, check INDI panel."));
2945 return;
2946 }
2947
2948 if (canAbsMove)
2949 return;
2950
2951 if (!strcmp(nvp->name, "manualfocusdrive"))
2952 {
2953 m_FocusMotionTimer.stop();
2954
2955 INumber *pos = IUFindNumber(nvp, "manualfocusdrive");
2956 if (pos && nvp->s == IPS_OK)
2957 {
2958 currentPosition += pos->value;
2959 absTicksLabel->setText(QString::number(static_cast<int>(currentPosition)));
2960 emit absolutePositionChanged(currentPosition);
2961 }
2962
2963 if (adjustFocus && nvp->s == IPS_OK)
2964 {
2965 adjustFocus = false;
2966 m_LastFocusDirection = FOCUS_NONE;
2967 emit focusPositionAdjusted();
2968 return;
2969 }
2970
2971 // restart if focus movement has finished
2972 if (restartFocus && nvp->s == IPS_OK && status() != Ekos::FOCUS_ABORTED)
2973 {
2974 restartFocus = false;
2975 inAutoFocus = false;
2976 appendLogText(i18n("Restarting autofocus process..."));
2977 start();
2978 }
2979
2980 if (canRelMove && inAutoFocus)
2981 {
2982 autoFocusProcessPositionChange(nvp->s);
2983 }
2984 else if (nvp->s == IPS_ALERT)
2985 appendLogText(i18n("Focuser error, check INDI panel."));
2986
2987 return;
2988 }
2989
2990 if (!strcmp(nvp->name, "REL_FOCUS_POSITION"))
2991 {
2992 m_FocusMotionTimer.stop();
2993
2994 INumber *pos = IUFindNumber(nvp, "FOCUS_RELATIVE_POSITION");
2995 if (pos && nvp->s == IPS_OK)
2996 {
2997 currentPosition += pos->value * (m_LastFocusDirection == FOCUS_IN ? -1 : 1);
2998 qCDebug(KSTARS_EKOS_FOCUS)
2999 << QString("Rel Focuser position changed by %1 to %2")
3000 .arg(pos->value).arg(currentPosition);
3001 absTicksLabel->setText(QString::number(static_cast<int>(currentPosition)));
3002 emit absolutePositionChanged(currentPosition);
3003 }
3004
3005 if (adjustFocus && nvp->s == IPS_OK)
3006 {
3007 adjustFocus = false;
3008 m_LastFocusDirection = FOCUS_NONE;
3009 emit focusPositionAdjusted();
3010 return;
3011 }
3012
3013 // restart if focus movement has finished
3014 if (restartFocus && nvp->s == IPS_OK && status() != Ekos::FOCUS_ABORTED)
3015 {
3016 restartFocus = false;
3017 inAutoFocus = false;
3018 appendLogText(i18n("Restarting autofocus process..."));
3019 start();
3020 }
3021
3022 if (canRelMove && inAutoFocus)
3023 {
3024 autoFocusProcessPositionChange(nvp->s);
3025 }
3026 else if (nvp->s == IPS_ALERT)
3027 appendLogText(i18n("Focuser error, check INDI panel."));
3028
3029 return;
3030 }
3031
3032 if (canRelMove)
3033 return;
3034
3035 if (!strcmp(nvp->name, "FOCUS_TIMER"))
3036 {
3037 m_FocusMotionTimer.stop();
3038 // restart if focus movement has finished
3039 if (restartFocus && nvp->s == IPS_OK && status() != Ekos::FOCUS_ABORTED)
3040 {
3041 restartFocus = false;
3042 inAutoFocus = false;
3043 appendLogText(i18n("Restarting autofocus process..."));
3044 start();
3045 }
3046
3047 if (canAbsMove == false && canRelMove == false && inAutoFocus)
3048 {
3049 // Used by the linear focus algorithm. Ignored if that's not in use for the timer-focuser.
3050 INumber *pos = IUFindNumber(nvp, "FOCUS_TIMER_VALUE");
3051 if (pos)
3052 {
3053 currentPosition += pos->value * (m_LastFocusDirection == FOCUS_IN ? -1 : 1);
3054 qCDebug(KSTARS_EKOS_FOCUS)
3055 << QString("Timer Focuser position changed by %1 to %2")
3056 .arg(pos->value).arg(currentPosition);
3057 }
3058 autoFocusProcessPositionChange(nvp->s);
3059 }
3060 else if (nvp->s == IPS_ALERT)
3061 appendLogText(i18n("Focuser error, check INDI panel."));
3062
3063 return;
3064 }
3065 }
3066
appendLogText(const QString & text)3067 void Focus::appendLogText(const QString &text)
3068 {
3069 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
3070 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
3071
3072 qCInfo(KSTARS_EKOS_FOCUS) << text;
3073
3074 emit newLog(text);
3075 }
3076
clearLog()3077 void Focus::clearLog()
3078 {
3079 m_LogText.clear();
3080 emit newLog(QString());
3081 }
3082
appendFocusLogText(const QString & lines)3083 void Focus::appendFocusLogText(const QString &lines)
3084 {
3085 if (Options::focusLogging())
3086 {
3087
3088 if (!m_FocusLogFile.exists())
3089 {
3090 // Create focus-specific log file and write the header record
3091 QDir dir(KSPaths::writableLocation(QStandardPaths::AppDataLocation));
3092 dir.mkpath("focuslogs");
3093 m_FocusLogEnabled = m_FocusLogFile.open(QIODevice::WriteOnly | QIODevice::Text);
3094 if (m_FocusLogEnabled)
3095 {
3096 QTextStream header(&m_FocusLogFile);
3097 header << "date, time, position, temperature, filter, HFR, altitude\n";
3098 header.flush();
3099 }
3100 else
3101 qCWarning(KSTARS_EKOS_FOCUS) << "Failed to open focus log file: " << m_FocusLogFileName;
3102 }
3103
3104 if (m_FocusLogEnabled)
3105 {
3106 QTextStream out(&m_FocusLogFile);
3107 out << QDateTime::currentDateTime().toString("yyyy-MM-dd, hh:mm:ss, ") << lines;
3108 out.flush();
3109 }
3110 }
3111 }
3112
startFraming()3113 void Focus::startFraming()
3114 {
3115 if (currentCCD == nullptr)
3116 {
3117 appendLogText(i18n("No CCD connected."));
3118 return;
3119 }
3120
3121 waitStarSelectTimer.stop();
3122
3123 inFocusLoop = true;
3124 HFRFrames.clear();
3125
3126 clearDataPoints();
3127
3128 //emit statusUpdated(true);
3129 state = Ekos::FOCUS_FRAMING;
3130 qCDebug(KSTARS_EKOS_FOCUS) << "State:" << Ekos::getFocusStatusString(state);
3131 emit newStatus(state);
3132
3133 resetButtons();
3134
3135 appendLogText(i18n("Starting continuous exposure..."));
3136
3137 capture();
3138 }
3139
resetButtons()3140 void Focus::resetButtons()
3141 {
3142 if (inFocusLoop)
3143 {
3144 startFocusB->setEnabled(false);
3145 startLoopB->setEnabled(false);
3146 stopFocusB->setEnabled(true);
3147
3148 captureB->setEnabled(false);
3149
3150 return;
3151 }
3152
3153 if (inAutoFocus)
3154 {
3155 stopFocusB->setEnabled(true);
3156
3157 startFocusB->setEnabled(false);
3158 startLoopB->setEnabled(false);
3159 captureB->setEnabled(false);
3160 focusOutB->setEnabled(false);
3161 focusInB->setEnabled(false);
3162 startGotoB->setEnabled(false);
3163 stopGotoB->setEnabled(false);
3164
3165 resetFrameB->setEnabled(false);
3166
3167 return;
3168 }
3169
3170 bool const enableCaptureButtons = captureInProgress == false && hfrInProgress == false;
3171
3172 captureB->setEnabled(enableCaptureButtons);
3173 resetFrameB->setEnabled(enableCaptureButtons);
3174 startLoopB->setEnabled(enableCaptureButtons);
3175
3176 if (currentFocuser)
3177 {
3178 focusOutB->setEnabled(true);
3179 focusInB->setEnabled(true);
3180
3181 startFocusB->setEnabled(focusType == FOCUS_AUTO);
3182 stopFocusB->setEnabled(!enableCaptureButtons);
3183 startGotoB->setEnabled(canAbsMove);
3184 stopGotoB->setEnabled(true);
3185 }
3186 else
3187 {
3188 focusOutB->setEnabled(false);
3189 focusInB->setEnabled(false);
3190
3191 startFocusB->setEnabled(false);
3192 stopFocusB->setEnabled(false);
3193 startGotoB->setEnabled(false);
3194 stopGotoB->setEnabled(false);
3195 }
3196 }
3197
updateBoxSize(int value)3198 void Focus::updateBoxSize(int value)
3199 {
3200 if (currentCCD == nullptr)
3201 return;
3202
3203 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
3204
3205 if (targetChip == nullptr)
3206 return;
3207
3208 int subBinX, subBinY;
3209 targetChip->getBinning(&subBinX, &subBinY);
3210
3211 QRect trackBox = focusView->getTrackingBox();
3212 QPoint center(trackBox.x() + (trackBox.width() / 2), trackBox.y() + (trackBox.height() / 2));
3213
3214 trackBox =
3215 QRect(center.x() - value / (2 * subBinX), center.y() - value / (2 * subBinY), value / subBinX, value / subBinY);
3216
3217 focusView->setTrackingBox(trackBox);
3218 }
3219
selectFocusStarFraction(double x,double y)3220 void Focus::selectFocusStarFraction(double x, double y)
3221 {
3222 if (m_ImageData.isNull())
3223 return;
3224
3225 focusStarSelected(x * m_ImageData->width(), y * m_ImageData->height());
3226 // Focus view timer takes 50ms second to update, so let's emit afterwards.
3227 QTimer::singleShot(250, this, [this]()
3228 {
3229 emit newImage(focusView);
3230 });
3231 }
3232
focusStarSelected(int x,int y)3233 void Focus::focusStarSelected(int x, int y)
3234 {
3235 if (state == Ekos::FOCUS_PROGRESS)
3236 return;
3237
3238 if (subFramed == false)
3239 {
3240 rememberStarCenter.setX(x);
3241 rememberStarCenter.setY(y);
3242 }
3243
3244 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
3245
3246 int subBinX, subBinY;
3247 targetChip->getBinning(&subBinX, &subBinY);
3248
3249 // If binning was changed outside of the focus module, recapture
3250 if (subBinX != activeBin)
3251 {
3252 capture();
3253 return;
3254 }
3255
3256 int offset = (static_cast<double>(focusBoxSize->value()) / subBinX) * 1.5;
3257
3258 QRect starRect;
3259
3260 bool squareMovedOutside = false;
3261
3262 if (subFramed == false && useSubFrame->isChecked() && targetChip->canSubframe())
3263 {
3264 int minX, maxX, minY, maxY, minW, maxW, minH, maxH; //, fx,fy,fw,fh;
3265
3266 targetChip->getFrameMinMax(&minX, &maxX, &minY, &maxY, &minW, &maxW, &minH, &maxH);
3267 //targetChip->getFrame(&fx, &fy, &fw, &fy);
3268
3269 x = (x - offset) * subBinX;
3270 y = (y - offset) * subBinY;
3271 int w = offset * 2 * subBinX;
3272 int h = offset * 2 * subBinY;
3273
3274 if (x < minX)
3275 x = minX;
3276 if (y < minY)
3277 y = minY;
3278 if ((x + w) > maxW)
3279 w = maxW - x;
3280 if ((y + h) > maxH)
3281 h = maxH - y;
3282
3283 //fx += x;
3284 //fy += y;
3285 //fw = w;
3286 //fh = h;
3287
3288 //targetChip->setFocusFrame(fx, fy, fw, fh);
3289 //frameModified=true;
3290
3291 QVariantMap settings = frameSettings[targetChip];
3292 settings["x"] = x;
3293 settings["y"] = y;
3294 settings["w"] = w;
3295 settings["h"] = h;
3296 settings["binx"] = subBinX;
3297 settings["biny"] = subBinY;
3298
3299 frameSettings[targetChip] = settings;
3300
3301 subFramed = true;
3302
3303 qCDebug(KSTARS_EKOS_FOCUS) << "Frame is subframed. X:" << x << "Y:" << y << "W:" << w << "H:" << h << "binX:" << subBinX <<
3304 "binY:" << subBinY;
3305
3306 focusView->setFirstLoad(true);
3307
3308 capture();
3309
3310 //starRect = QRect((w-focusBoxSize->value())/(subBinX*2), (h-focusBoxSize->value())/(subBinY*2), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY);
3311 starCenter.setX(w / (2 * subBinX));
3312 starCenter.setY(h / (2 * subBinY));
3313 }
3314 else
3315 {
3316 //starRect = QRect(x-focusBoxSize->value()/(subBinX*2), y-focusBoxSize->value()/(subBinY*2), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY);
3317 double dist = sqrt((starCenter.x() - x) * (starCenter.x() - x) + (starCenter.y() - y) * (starCenter.y() - y));
3318
3319 squareMovedOutside = (dist > (static_cast<double>(focusBoxSize->value()) / subBinX));
3320 starCenter.setX(x);
3321 starCenter.setY(y);
3322 //starRect = QRect( starCenter.x()-focusBoxSize->value()/(2*subBinX), starCenter.y()-focusBoxSize->value()/(2*subBinY), focusBoxSize->value()/subBinX, focusBoxSize->value()/subBinY);
3323 starRect = QRect(starCenter.x() - focusBoxSize->value() / (2 * subBinX),
3324 starCenter.y() - focusBoxSize->value() / (2 * subBinY), focusBoxSize->value() / subBinX,
3325 focusBoxSize->value() / subBinY);
3326 focusView->setTrackingBox(starRect);
3327 }
3328
3329 starsHFR.clear();
3330
3331 starCenter.setZ(subBinX);
3332
3333 //starSelected=true;
3334
3335 defaultScale = static_cast<FITSScale>(filterCombo->currentIndex());
3336
3337 if (squareMovedOutside && inAutoFocus == false && useAutoStar->isChecked())
3338 {
3339 useAutoStar->blockSignals(true);
3340 useAutoStar->setChecked(false);
3341 useAutoStar->blockSignals(false);
3342 appendLogText(i18n("Disabling Auto Star Selection as star selection box was moved manually."));
3343 starSelected = false;
3344 }
3345 else if (starSelected == false)
3346 {
3347 appendLogText(i18n("Focus star is selected."));
3348 starSelected = true;
3349 capture();
3350 }
3351
3352 waitStarSelectTimer.stop();
3353 FocusState nextState = inAutoFocus ? FOCUS_PROGRESS : FOCUS_IDLE;
3354 if (nextState != state)
3355 {
3356 state = nextState;
3357 qCDebug(KSTARS_EKOS_FOCUS) << "State:" << Ekos::getFocusStatusString(state);
3358 emit newStatus(state);
3359 }
3360 }
3361
checkFocus(double requiredHFR)3362 void Focus::checkFocus(double requiredHFR)
3363 {
3364 if (inAutoFocus || inFocusLoop)
3365 {
3366 qCDebug(KSTARS_EKOS_FOCUS) << "Check Focus rejected, focus procedure is already running.";
3367 }
3368 else
3369 {
3370 qCDebug(KSTARS_EKOS_FOCUS) << "Check Focus requested with minimum required HFR" << requiredHFR;
3371 minimumRequiredHFR = requiredHFR;
3372
3373 appendLogText("Capturing to check HFR...");
3374 capture();
3375 }
3376 }
3377
toggleSubframe(bool enable)3378 void Focus::toggleSubframe(bool enable)
3379 {
3380 if (enable == false)
3381 resetFrame();
3382
3383 starSelected = false;
3384 starCenter = QVector3D();
3385
3386 if (useFullField->isChecked())
3387 useFullField->setChecked(!enable);
3388 }
3389
filterChangeWarning(int index)3390 void Focus::filterChangeWarning(int index)
3391 {
3392 Options::setFocusEffect(index);
3393 defaultScale = static_cast<FITSScale>(index);
3394
3395 // Median filter helps reduce noise, rotation/flip have no dire effect on focus, others degrade procedure
3396 switch (defaultScale)
3397 {
3398 case FITS_NONE:
3399 case FITS_MEDIAN:
3400 case FITS_ROTATE_CW:
3401 case FITS_ROTATE_CCW:
3402 case FITS_FLIP_H:
3403 case FITS_FLIP_V:
3404 break;
3405
3406 default:
3407 // Warn the end-user, count the no-op filter
3408 appendLogText(i18n("Warning: Only use filter '%1' for preview as it may interfere with autofocus operation.",
3409 FITSViewer::filterTypes.value(index - 1, "???")));
3410 }
3411 }
3412
setExposure(double value)3413 void Focus::setExposure(double value)
3414 {
3415 exposureIN->setValue(value);
3416 }
3417
setBinning(int subBinX,int subBinY)3418 void Focus::setBinning(int subBinX, int subBinY)
3419 {
3420 INDI_UNUSED(subBinY);
3421 binningCombo->setCurrentIndex(subBinX - 1);
3422 }
3423
setImageFilter(const QString & value)3424 void Focus::setImageFilter(const QString &value)
3425 {
3426 for (int i = 0; i < filterCombo->count(); i++)
3427 if (filterCombo->itemText(i) == value)
3428 {
3429 filterCombo->setCurrentIndex(i);
3430 filterCombo->activated(i);
3431 break;
3432 }
3433 }
3434
setAutoStarEnabled(bool enable)3435 void Focus::setAutoStarEnabled(bool enable)
3436 {
3437 useAutoStar->setChecked(enable);
3438 Options::setFocusAutoStarEnabled(enable);
3439 }
3440
setAutoSubFrameEnabled(bool enable)3441 void Focus::setAutoSubFrameEnabled(bool enable)
3442 {
3443 useSubFrame->setChecked(enable);
3444 Options::setFocusSubFrame(enable);
3445 }
3446
setAutoFocusParameters(int boxSize,int stepSize,int maxTravel,double tolerance)3447 void Focus::setAutoFocusParameters(int boxSize, int stepSize, int maxTravel, double tolerance)
3448 {
3449 focusBoxSize->setValue(boxSize);
3450 stepIN->setValue(stepSize);
3451 maxTravelIN->setValue(maxTravel);
3452 toleranceIN->setValue(tolerance);
3453 }
3454
checkAutoStarTimeout()3455 void Focus::checkAutoStarTimeout()
3456 {
3457 //if (starSelected == false && inAutoFocus)
3458 if (starCenter.isNull() && (inAutoFocus || minimumRequiredHFR > 0))
3459 {
3460 if (inAutoFocus)
3461 {
3462 if (rememberStarCenter.isNull() == false)
3463 {
3464 focusStarSelected(rememberStarCenter.x(), rememberStarCenter.y());
3465 appendLogText(i18n("No star was selected. Using last known position..."));
3466 return;
3467 }
3468 }
3469
3470 initialFocuserAbsPosition = -1;
3471 appendLogText(i18n("No star was selected. Aborting..."));
3472 completeFocusProcedure(Ekos::FOCUS_ABORTED);
3473 }
3474 else if (state == FOCUS_WAITING)
3475 {
3476 state = FOCUS_IDLE;
3477 qCDebug(KSTARS_EKOS_FOCUS) << "State:" << Ekos::getFocusStatusString(state);
3478 emit newStatus(state);
3479 }
3480 }
3481
setAbsoluteFocusTicks()3482 void Focus::setAbsoluteFocusTicks()
3483 {
3484 if (currentFocuser == nullptr)
3485 {
3486 appendLogText(i18n("Error: No Focuser detected."));
3487 checkStopFocus(true);
3488 return;
3489 }
3490
3491 if (currentFocuser->isConnected() == false)
3492 {
3493 appendLogText(i18n("Error: Lost connection to Focuser."));
3494 checkStopFocus(true);
3495 return;
3496 }
3497
3498 qCDebug(KSTARS_EKOS_FOCUS) << "Setting focus ticks to " << absTicksSpin->value();
3499
3500 currentFocuser->moveAbs(absTicksSpin->value());
3501 }
3502
3503 //void Focus::setActiveBinning(int bin)
3504 //{
3505 // activeBin = bin + 1;
3506 // Options::setFocusXBin(activeBin);
3507 //}
3508
3509 // TODO remove from kstars.kcfg
3510 /*void Focus::setFrames(int value)
3511 {
3512 Options::setFocusFrames(value);
3513 }*/
3514
syncTrackingBoxPosition()3515 void Focus::syncTrackingBoxPosition()
3516 {
3517 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
3518 Q_ASSERT(targetChip);
3519
3520 int subBinX = 1, subBinY = 1;
3521 targetChip->getBinning(&subBinX, &subBinY);
3522
3523 if (starCenter.isNull() == false)
3524 {
3525 double boxSize = focusBoxSize->value();
3526 int x, y, w, h;
3527 targetChip->getFrame(&x, &y, &w, &h);
3528 // If box size is larger than image size, set it to lower index
3529 if (boxSize / subBinX >= w || boxSize / subBinY >= h)
3530 {
3531 focusBoxSize->setValue((boxSize / subBinX >= w) ? w : h);
3532 return;
3533 }
3534
3535 // If binning changed, update coords accordingly
3536 if (subBinX != starCenter.z())
3537 {
3538 if (starCenter.z() > 0)
3539 {
3540 starCenter.setX(starCenter.x() * (starCenter.z() / subBinX));
3541 starCenter.setY(starCenter.y() * (starCenter.z() / subBinY));
3542 }
3543
3544 starCenter.setZ(subBinX);
3545 }
3546
3547 QRect starRect = QRect(starCenter.x() - boxSize / (2 * subBinX), starCenter.y() - boxSize / (2 * subBinY),
3548 boxSize / subBinX, boxSize / subBinY);
3549 focusView->setTrackingBoxEnabled(true);
3550 focusView->setTrackingBox(starRect);
3551 }
3552 }
3553
showFITSViewer()3554 void Focus::showFITSViewer()
3555 {
3556 static int lastFVTabID = -1;
3557 if (m_ImageData)
3558 {
3559 QUrl url = QUrl::fromLocalFile("focus.fits");
3560 if (fv.isNull())
3561 {
3562 fv = KStars::Instance()->createFITSViewer();
3563 fv->loadData(m_ImageData, url, &lastFVTabID);
3564 }
3565 else if (fv->updateData(m_ImageData, url, lastFVTabID, &lastFVTabID) == false)
3566 fv->loadData(m_ImageData, url, &lastFVTabID);
3567
3568 fv->show();
3569 }
3570 }
3571
adjustFocusOffset(int value,bool useAbsoluteOffset)3572 void Focus::adjustFocusOffset(int value, bool useAbsoluteOffset)
3573 {
3574 adjustFocus = true;
3575
3576 int relativeOffset = 0;
3577
3578 if (useAbsoluteOffset == false)
3579 relativeOffset = value;
3580 else
3581 relativeOffset = value - currentPosition;
3582
3583 changeFocus(relativeOffset);
3584 }
3585
toggleFocusingWidgetFullScreen()3586 void Focus::toggleFocusingWidgetFullScreen()
3587 {
3588 if (focusingWidget->parent() == nullptr)
3589 {
3590 focusingWidget->setParent(this);
3591 rightLayout->insertWidget(0, focusingWidget);
3592 focusingWidget->showNormal();
3593 }
3594 else
3595 {
3596 focusingWidget->setParent(nullptr);
3597 focusingWidget->setWindowTitle(i18nc("@title:window", "Focus Frame"));
3598 focusingWidget->setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
3599 focusingWidget->showMaximized();
3600 focusingWidget->show();
3601 }
3602 }
3603
setMountStatus(ISD::Telescope::Status newState)3604 void Focus::setMountStatus(ISD::Telescope::Status newState)
3605 {
3606 switch (newState)
3607 {
3608 case ISD::Telescope::MOUNT_PARKING:
3609 case ISD::Telescope::MOUNT_SLEWING:
3610 case ISD::Telescope::MOUNT_MOVING:
3611 captureB->setEnabled(false);
3612 startFocusB->setEnabled(false);
3613 startLoopB->setEnabled(false);
3614
3615 // If mount is moved while we have a star selected and subframed
3616 // let us reset the frame.
3617 if (subFramed)
3618 resetFrame();
3619
3620 break;
3621
3622 default:
3623 resetButtons();
3624 break;
3625 }
3626 }
3627
setMountCoords(const SkyPoint & position,ISD::Telescope::PierSide pierSide,const dms & ha)3628 void Focus::setMountCoords(const SkyPoint &position, ISD::Telescope::PierSide pierSide, const dms &ha)
3629 {
3630 Q_UNUSED(pierSide);
3631 Q_UNUSED(ha);
3632 mountAlt = position.alt().Degrees();
3633 }
3634
removeDevice(ISD::GDInterface * deviceRemoved)3635 void Focus::removeDevice(ISD::GDInterface *deviceRemoved)
3636 {
3637 // Check in Focusers
3638 for (ISD::GDInterface *focuser : Focusers)
3639 {
3640 if (focuser->getDeviceName() == deviceRemoved->getDeviceName())
3641 {
3642 Focusers.removeAll(dynamic_cast<ISD::Focuser*>(focuser));
3643 focuserCombo->removeItem(focuserCombo->findText(focuser->getDeviceName()));
3644 QTimer::singleShot(1000, this, [this]()
3645 {
3646 checkFocuser();
3647 resetButtons();
3648 });
3649 }
3650 }
3651
3652 // Check in Temperature Sources.
3653 for (auto &oneSource : TemperatureSources)
3654 {
3655 if (oneSource->getDeviceName() == deviceRemoved->getDeviceName())
3656 {
3657 TemperatureSources.removeAll(oneSource);
3658 temperatureSourceCombo->removeItem(temperatureSourceCombo->findText(oneSource->getDeviceName()));
3659 QTimer::singleShot(1000, this, [this]()
3660 {
3661 checkTemperatureSource();
3662 });
3663 }
3664 }
3665
3666 // Check in CCDs
3667 for (ISD::GDInterface *ccd : CCDs)
3668 {
3669 if (ccd->getDeviceName() == deviceRemoved->getDeviceName())
3670 {
3671 CCDs.removeAll(dynamic_cast<ISD::CCD*>(ccd));
3672 CCDCaptureCombo->removeItem(CCDCaptureCombo->findText(ccd->getDeviceName()));
3673 CCDCaptureCombo->removeItem(CCDCaptureCombo->findText(ccd->getDeviceName() + QString(" Guider")));
3674
3675 if (CCDs.empty())
3676 {
3677 currentCCD = nullptr;
3678 CCDCaptureCombo->setCurrentIndex(-1);
3679 }
3680 else
3681 {
3682 currentCCD = CCDs[0];
3683 CCDCaptureCombo->setCurrentIndex(0);
3684 }
3685
3686 QTimer::singleShot(1000, this, [this]()
3687 {
3688 checkCCD();
3689 resetButtons();
3690 });
3691 }
3692 }
3693
3694 // Check in Filters
3695 for (ISD::GDInterface *filter : Filters)
3696 {
3697 if (filter->getDeviceName() == deviceRemoved->getDeviceName())
3698 {
3699 Filters.removeAll(filter);
3700 FilterDevicesCombo->removeItem(FilterDevicesCombo->findText(filter->getDeviceName()));
3701 if (Filters.empty())
3702 {
3703 currentFilter = nullptr;
3704 FilterDevicesCombo->setCurrentIndex(-1);
3705 }
3706 else
3707 FilterDevicesCombo->setCurrentIndex(0);
3708
3709 QTimer::singleShot(1000, this, [this]()
3710 {
3711 checkFilter();
3712 resetButtons();
3713 });
3714 }
3715 }
3716 }
3717
setFilterManager(const QSharedPointer<FilterManager> & manager)3718 void Focus::setFilterManager(const QSharedPointer<FilterManager> &manager)
3719 {
3720 filterManager = manager;
3721 connect(filterManagerB, &QPushButton::clicked, [this]()
3722 {
3723 filterManager->show();
3724 filterManager->raise();
3725 });
3726
3727 connect(filterManager.data(), &FilterManager::ready, [this]()
3728 {
3729 if (filterPositionPending)
3730 {
3731 filterPositionPending = false;
3732 capture();
3733 }
3734 else if (fallbackFilterPending)
3735 {
3736 fallbackFilterPending = false;
3737 emit newStatus(state);
3738 }
3739 }
3740 );
3741
3742 connect(filterManager.data(), &FilterManager::failed, [this]()
3743 {
3744 appendLogText(i18n("Filter operation failed."));
3745 completeFocusProcedure(Ekos::FOCUS_ABORTED);
3746 }
3747 );
3748
3749 connect(this, &Focus::newStatus, [this](Ekos::FocusState state)
3750 {
3751 if (FilterPosCombo->currentIndex() != -1 && canAbsMove && state == Ekos::FOCUS_COMPLETE)
3752 {
3753 filterManager->setFilterAbsoluteFocusPosition(FilterPosCombo->currentIndex(), currentPosition);
3754 }
3755 });
3756
3757 // Resume guiding if suspended after focus position is adjusted.
3758 connect(this, &Focus::focusPositionAdjusted, this, [this]()
3759 {
3760 if (m_GuidingSuspended && state != Ekos::FOCUS_PROGRESS)
3761 {
3762 QTimer::singleShot(FocusSettleTime->value() * 1000, this, [this]()
3763 {
3764 m_GuidingSuspended = false;
3765 emit resumeGuiding();
3766 });
3767 }
3768 });
3769
3770 // Suspend guiding if filter offset is change with OAG
3771 connect(filterManager.data(), &FilterManager::newStatus, this, [this](Ekos::FilterState filterState)
3772 {
3773 // If we are changing filter offset while idle, then check if we need to suspend guiding.
3774 const bool isOAG = currentCCD->getTelescopeType() == Options::guideScopeType();
3775 if (isOAG && filterState == FILTER_OFFSET && state != Ekos::FOCUS_PROGRESS)
3776 {
3777 if (m_GuidingSuspended == false && suspendGuideCheck->isChecked())
3778 {
3779 m_GuidingSuspended = true;
3780 emit suspendGuiding();
3781 }
3782 }
3783 });
3784
3785 connect(exposureIN, &QDoubleSpinBox::editingFinished, [this]()
3786 {
3787 if (currentFilter)
3788 filterManager->setFilterExposure(FilterPosCombo->currentIndex(), exposureIN->value());
3789 else
3790 Options::setFocusExposure(exposureIN->value());
3791 });
3792
3793 connect(filterManager.data(), &FilterManager::labelsChanged, this, [this]()
3794 {
3795 FilterPosCombo->clear();
3796 FilterPosCombo->addItems(filterManager->getFilterLabels());
3797 currentFilterPosition = filterManager->getFilterPosition();
3798 FilterPosCombo->setCurrentIndex(currentFilterPosition - 1);
3799 //Options::setDefaultFocusFilterWheelFilter(FilterPosCombo->currentText());
3800 });
3801 connect(filterManager.data(), &FilterManager::positionChanged, this, [this]()
3802 {
3803 currentFilterPosition = filterManager->getFilterPosition();
3804 FilterPosCombo->setCurrentIndex(currentFilterPosition - 1);
3805 //Options::setDefaultFocusFilterWheelFilter(FilterPosCombo->currentText());
3806 });
3807 connect(filterManager.data(), &FilterManager::exposureChanged, this, [this]()
3808 {
3809 exposureIN->setValue(filterManager->getFilterExposure());
3810 });
3811
3812 connect(FilterPosCombo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged),
3813 [ = ](const QString & text)
3814 {
3815 exposureIN->setValue(filterManager->getFilterExposure(text));
3816 //Options::setDefaultFocusFilterWheelFilter(text);
3817 });
3818 }
3819
toggleVideo(bool enabled)3820 void Focus::toggleVideo(bool enabled)
3821 {
3822 if (currentCCD == nullptr)
3823 return;
3824
3825 if (currentCCD->isBLOBEnabled() == false)
3826 {
3827
3828 if (Options::guiderType() != Ekos::Guide::GUIDE_INTERNAL)
3829 currentCCD->setBLOBEnabled(true);
3830 else
3831 {
3832 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, enabled]()
3833 {
3834 //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr);
3835 KSMessageBox::Instance()->disconnect(this);
3836 currentCCD->setVideoStreamEnabled(enabled);
3837 });
3838 KSMessageBox::Instance()->questionYesNo(i18n("Image transfer is disabled for this camera. Would you like to enable it?"));
3839 }
3840 }
3841 else
3842 currentCCD->setVideoStreamEnabled(enabled);
3843 }
3844
3845 //void Focus::setWeatherData(const std::vector<ISD::Weather::WeatherData> &data)
3846 //{
3847 // auto pos = std::find_if(data.begin(), data.end(), [](ISD::Weather::WeatherData oneEntry)
3848 // {
3849 // return (oneEntry.name == "WEATHER_TEMPERATURE");
3850 // });
3851
3852 // if (pos != data.end())
3853 // {
3854 // updateTemperature(OBSERVATORY_TEMPERATURE, pos->value);
3855 // }
3856 //}
3857
setVideoStreamEnabled(bool enabled)3858 void Focus::setVideoStreamEnabled(bool enabled)
3859 {
3860 if (enabled)
3861 {
3862 liveVideoB->setChecked(true);
3863 liveVideoB->setIcon(QIcon::fromTheme("camera-on"));
3864 }
3865 else
3866 {
3867 liveVideoB->setChecked(false);
3868 liveVideoB->setIcon(QIcon::fromTheme("camera-ready"));
3869 }
3870 }
3871
processCaptureTimeout()3872 void Focus::processCaptureTimeout()
3873 {
3874 captureTimeoutCounter++;
3875
3876 if (captureTimeoutCounter >= 3)
3877 {
3878 captureTimeoutCounter = 0;
3879 captureTimeout.stop();
3880 appendLogText(i18n("Exposure timeout. Aborting..."));
3881 completeFocusProcedure(Ekos::FOCUS_ABORTED);
3882 }
3883 else
3884 {
3885 appendLogText(i18n("Exposure timeout. Restarting exposure..."));
3886 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
3887 targetChip->abortExposure();
3888
3889 prepareCapture(targetChip);
3890
3891 if (targetChip->capture(exposureIN->value()))
3892 {
3893 // Timeout is exposure duration + timeout threshold in seconds
3894 //long const timeout = lround(ceil(exposureIN->value() * 1000)) + FOCUS_TIMEOUT_THRESHOLD;
3895 captureTimeout.start(Options::focusCaptureTimeout() * 1000);
3896
3897 if (inFocusLoop == false)
3898 appendLogText(i18n("Capturing image again..."));
3899
3900 resetButtons();
3901 }
3902 else if (inAutoFocus)
3903 {
3904 completeFocusProcedure(Ekos::FOCUS_ABORTED);
3905 }
3906 }
3907 }
3908
processCaptureError(ISD::CCD::ErrorType type)3909 void Focus::processCaptureError(ISD::CCD::ErrorType type)
3910 {
3911 if (type == ISD::CCD::ERROR_SAVE)
3912 {
3913 appendLogText(i18n("Failed to save image. Aborting..."));
3914 completeFocusProcedure(Ekos::FOCUS_ABORTED);
3915 return;
3916 }
3917
3918 captureFailureCounter++;
3919
3920 if (captureFailureCounter >= 3)
3921 {
3922 captureFailureCounter = 0;
3923 appendLogText(i18n("Exposure failure. Aborting..."));
3924 completeFocusProcedure(Ekos::FOCUS_ABORTED);
3925 return;
3926 }
3927
3928 appendLogText(i18n("Exposure failure. Restarting exposure..."));
3929 ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
3930 targetChip->abortExposure();
3931 targetChip->capture(exposureIN->value());
3932 }
3933
syncSettings()3934 void Focus::syncSettings()
3935 {
3936 QDoubleSpinBox *dsb = nullptr;
3937 QSpinBox *sb = nullptr;
3938 QCheckBox *cb = nullptr;
3939 QComboBox *cbox = nullptr;
3940
3941 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
3942 {
3943 ///////////////////////////////////////////////////////////////////////////
3944 /// Focuser Group
3945 ///////////////////////////////////////////////////////////////////////////
3946 if (dsb == FocusSettleTime)
3947 Options::setFocusSettleTime(dsb->value());
3948
3949 ///////////////////////////////////////////////////////////////////////////
3950 /// CCD & Filter Wheel Group
3951 ///////////////////////////////////////////////////////////////////////////
3952 else if (dsb == gainIN)
3953 Options::setFocusGain(dsb->value());
3954
3955 ///////////////////////////////////////////////////////////////////////////
3956 /// Settings Group
3957 ///////////////////////////////////////////////////////////////////////////
3958 else if (dsb == fullFieldInnerRing)
3959 Options::setFocusFullFieldInnerRadius(dsb->value());
3960 else if (dsb == fullFieldOuterRing)
3961 Options::setFocusFullFieldOuterRadius(dsb->value());
3962 else if (dsb == GuideSettleTime)
3963 Options::setGuideSettleTime(dsb->value());
3964 else if (dsb == maxTravelIN)
3965 Options::setFocusMaxTravel(dsb->value());
3966 else if (dsb == toleranceIN)
3967 Options::setFocusTolerance(dsb->value());
3968 else if (dsb == thresholdSpin)
3969 Options::setFocusThreshold(dsb->value());
3970 else if (dsb == gaussianSigmaSpin)
3971 Options::setFocusGaussianSigma(dsb->value());
3972 else if (dsb == initialFocusOutStepsIN)
3973 Options::setInitialFocusOutSteps(dsb->value());
3974 }
3975 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
3976 {
3977 ///////////////////////////////////////////////////////////////////////////
3978 /// Settings Group
3979 ///////////////////////////////////////////////////////////////////////////
3980 if (sb == focusBoxSize)
3981 Options::setFocusBoxSize(sb->value());
3982 else if (sb == stepIN)
3983 Options::setFocusTicks(sb->value());
3984 else if (sb == maxSingleStepIN)
3985 Options::setFocusMaxSingleStep(sb->value());
3986 else if (sb == focusFramesSpin)
3987 Options::setFocusFramesCount(sb->value());
3988 else if (sb == gaussianKernelSizeSpin)
3989 Options::setFocusGaussianKernelSize(sb->value());
3990 else if (sb == multiRowAverageSpin)
3991 Options::setFocusMultiRowAverage(sb->value());
3992 else if (sb == captureTimeoutSpin)
3993 Options::setFocusCaptureTimeout(sb->value());
3994 else if (sb == motionTimeoutSpin)
3995 Options::setFocusMotionTimeout(sb->value());
3996 }
3997 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
3998 {
3999 ///////////////////////////////////////////////////////////////////////////
4000 /// Settings Group
4001 ///////////////////////////////////////////////////////////////////////////
4002 if (cb == useAutoStar)
4003 Options::setFocusAutoStarEnabled(cb->isChecked());
4004 else if (cb == useSubFrame)
4005 Options::setFocusSubFrame(cb->isChecked());
4006 else if (cb == darkFrameCheck)
4007 Options::setUseFocusDarkFrame(cb->isChecked());
4008 else if (cb == useFullField)
4009 Options::setFocusUseFullField(cb->isChecked());
4010 else if (cb == suspendGuideCheck)
4011 Options::setSuspendGuiding(cb->isChecked());
4012 }
4013 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
4014 {
4015 ///////////////////////////////////////////////////////////////////////////
4016 /// CCD & Filter Wheel Group
4017 ///////////////////////////////////////////////////////////////////////////
4018 if (cbox == focuserCombo)
4019 Options::setDefaultFocusFocuser(cbox->currentText());
4020 else if (cbox == CCDCaptureCombo)
4021 Options::setDefaultFocusCCD(cbox->currentText());
4022 else if (cbox == binningCombo)
4023 {
4024 activeBin = cbox->currentIndex() + 1;
4025 Options::setFocusXBin(activeBin);
4026 }
4027 else if (cbox == FilterDevicesCombo)
4028 Options::setDefaultFocusFilterWheel(cbox->currentText());
4029 else if (cbox == temperatureSourceCombo)
4030 Options::setDefaultFocusTemperatureSource(cbox->currentText());
4031 // Filter Effects already taken care of in filterChangeWarning
4032
4033 ///////////////////////////////////////////////////////////////////////////
4034 /// Settings Group
4035 ///////////////////////////////////////////////////////////////////////////
4036 else if (cbox == focusAlgorithmCombo)
4037 Options::setFocusAlgorithm(cbox->currentIndex());
4038 else if (cbox == focusDetectionCombo)
4039 Options::setFocusDetection(cbox->currentIndex());
4040 }
4041
4042 emit settingsUpdated(getSettings());
4043 }
4044
loadSettings()4045 void Focus::loadSettings()
4046 {
4047 ///////////////////////////////////////////////////////////////////////////
4048 /// Focuser Group
4049 ///////////////////////////////////////////////////////////////////////////
4050 // Focus settle time
4051 FocusSettleTime->setValue(Options::focusSettleTime());
4052
4053 ///////////////////////////////////////////////////////////////////////////
4054 /// CCD & Filter Wheel Group
4055 ///////////////////////////////////////////////////////////////////////////
4056 // Default Exposure
4057 exposureIN->setValue(Options::focusExposure());
4058 // Binning
4059 activeBin = Options::focusXBin();
4060 binningCombo->setCurrentIndex(activeBin - 1);
4061 // Gain
4062 gainIN->setValue(Options::focusGain());
4063
4064 ///////////////////////////////////////////////////////////////////////////
4065 /// Settings Group
4066 ///////////////////////////////////////////////////////////////////////////
4067 // Subframe?
4068 useSubFrame->setChecked(Options::focusSubFrame());
4069 // Dark frame?
4070 darkFrameCheck->setChecked(Options::useFocusDarkFrame());
4071 // Use full field?
4072 useFullField->setChecked(Options::focusUseFullField());
4073 // full field inner ring
4074 fullFieldInnerRing->setValue(Options::focusFullFieldInnerRadius());
4075 // full field outer ring
4076 fullFieldOuterRing->setValue(Options::focusFullFieldOuterRadius());
4077 // Suspend guiding?
4078 suspendGuideCheck->setChecked(Options::suspendGuiding());
4079 // Guide Setting time
4080 GuideSettleTime->setValue(Options::guideSettleTime());
4081
4082 // Box Size
4083 focusBoxSize->setValue(Options::focusBoxSize());
4084 // Max Travel - this will be overriden by the device
4085 maxTravelIN->setMinimum(0.0);
4086 if (Options::focusMaxTravel() > maxTravelIN->maximum())
4087 maxTravelIN->setMaximum(Options::focusMaxTravel());
4088 maxTravelIN->setValue(Options::focusMaxTravel());
4089 // Step
4090 stepIN->setValue(Options::focusTicks());
4091 // Single Max Step
4092 maxSingleStepIN->setValue(Options::focusMaxSingleStep());
4093 // LinearFocus initial outward steps
4094 initialFocusOutStepsIN->setValue(Options::initialFocusOutSteps());
4095 // Tolerance
4096 toleranceIN->setValue(Options::focusTolerance());
4097 // Threshold spin
4098 thresholdSpin->setValue(Options::focusThreshold());
4099 // Focus Algorithm
4100 setFocusAlgorithm(static_cast<FocusAlgorithm>(Options::focusAlgorithm()));
4101 // This must go below the above line (which sets focusAlgorithm from options).
4102 focusAlgorithmCombo->setCurrentIndex(focusAlgorithm);
4103 // Frames Count
4104 focusFramesSpin->setValue(Options::focusFramesCount());
4105 // Focus Detection
4106 focusDetection = static_cast<StarAlgorithm>(Options::focusDetection());
4107 thresholdSpin->setEnabled(focusDetection == ALGORITHM_THRESHOLD);
4108 focusDetectionCombo->setCurrentIndex(focusDetection);
4109 // Gaussian blur
4110 gaussianSigmaSpin->setValue(Options::focusGaussianSigma());
4111 gaussianKernelSizeSpin->setValue(Options::focusGaussianKernelSize());
4112 // Hough algorithm multi row average
4113 multiRowAverageSpin->setValue(Options::focusMultiRowAverage());
4114 multiRowAverageSpin->setEnabled(focusDetection == ALGORITHM_BAHTINOV);
4115 // Timeouts
4116 captureTimeoutSpin->setValue(Options::focusCaptureTimeout());
4117 motionTimeoutSpin->setValue(Options::focusMotionTimeout());
4118
4119 // Increase focus box size in case of Bahtinov mask focus
4120 // Disable auto star in case of Bahtinov mask focus
4121 if (focusDetection == ALGORITHM_BAHTINOV)
4122 {
4123 Options::setFocusAutoStarEnabled(false);
4124 focusBoxSize->setMaximum(512);
4125 }
4126 else
4127 {
4128 // When not using Bathinov mask, limit box size to 256 and make sure value stays within range.
4129 if (Options::focusBoxSize() > 256)
4130 {
4131 Options::setFocusBoxSize(32);
4132 }
4133 focusBoxSize->setMaximum(256);
4134 }
4135 // Box Size
4136 focusBoxSize->setValue(Options::focusBoxSize());
4137 // Auto Star?
4138 useAutoStar->setChecked(Options::focusAutoStarEnabled());
4139 useAutoStar->setEnabled(focusDetection != ALGORITHM_BAHTINOV);
4140 }
4141
initSettingsConnections()4142 void Focus::initSettingsConnections()
4143 {
4144 ///////////////////////////////////////////////////////////////////////////
4145 /// Focuser Group
4146 ///////////////////////////////////////////////////////////////////////////
4147 connect(focuserCombo, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), this,
4148 &Ekos::Focus::syncSettings);
4149 connect(FocusSettleTime, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4150
4151 ///////////////////////////////////////////////////////////////////////////
4152 /// CCD & Filter Wheel Group
4153 ///////////////////////////////////////////////////////////////////////////
4154 connect(CCDCaptureCombo, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), this,
4155 &Ekos::Focus::syncSettings);
4156 connect(binningCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &Ekos::Focus::syncSettings);
4157 connect(gainIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4158 connect(FilterDevicesCombo, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), this,
4159 &Ekos::Focus::syncSettings);
4160 connect(FilterPosCombo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::activated), this,
4161 &Ekos::Focus::syncSettings);
4162 connect(temperatureSourceCombo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::activated), this,
4163 &Ekos::Focus::syncSettings);
4164
4165 ///////////////////////////////////////////////////////////////////////////
4166 /// Settings Group
4167 ///////////////////////////////////////////////////////////////////////////
4168 connect(useAutoStar, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings);
4169 connect(useSubFrame, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings);
4170 connect(darkFrameCheck, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings);
4171 connect(useFullField, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings);
4172 connect(fullFieldInnerRing, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4173 connect(fullFieldOuterRing, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4174 connect(suspendGuideCheck, &QCheckBox::toggled, this, &Ekos::Focus::syncSettings);
4175 connect(GuideSettleTime, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4176
4177 connect(focusBoxSize, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &Focus::syncSettings);
4178 connect(maxTravelIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4179 connect(stepIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4180 connect(maxSingleStepIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4181 connect(initialFocusOutStepsIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4182 connect(toleranceIN, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4183 connect(thresholdSpin, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4184 connect(gaussianSigmaSpin, &QDoubleSpinBox::editingFinished, this, &Focus::syncSettings);
4185 connect(gaussianKernelSizeSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &Focus::syncSettings);
4186 connect(multiRowAverageSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &Focus::syncSettings);
4187 connect(captureTimeoutSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &Focus::syncSettings);
4188 connect(motionTimeoutSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &Focus::syncSettings);
4189
4190 connect(focusAlgorithmCombo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::activated), this,
4191 &Ekos::Focus::syncSettings);
4192 connect(focusFramesSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &Focus::syncSettings);
4193 connect(focusDetectionCombo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::activated), this,
4194 &Ekos::Focus::syncSettings);
4195 }
4196
initPlots()4197 void Focus::initPlots()
4198 {
4199 connect(clearDataB, &QPushButton::clicked, this, &Ekos::Focus::clearDataPoints);
4200
4201 profileDialog = new QDialog(this);
4202 profileDialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
4203 QVBoxLayout *profileLayout = new QVBoxLayout(profileDialog);
4204 profileDialog->setWindowTitle(i18nc("@title:window", "Relative Profile"));
4205 profilePlot = new FocusProfilePlot(profileDialog);
4206
4207 profileLayout->addWidget(profilePlot);
4208 profileDialog->setLayout(profileLayout);
4209 profileDialog->resize(400, 300);
4210
4211 connect(relativeProfileB, &QPushButton::clicked, profileDialog, &QDialog::show);
4212 connect(this, &Ekos::Focus::newHFR, [this](double currentHFR, int pos)
4213 {
4214 Q_UNUSED(pos) profilePlot->drawProfilePlot(currentHFR);
4215 });
4216 }
4217
initConnections()4218 void Focus::initConnections()
4219 {
4220 // How long do we wait until the user select a star?
4221 waitStarSelectTimer.setInterval(AUTO_STAR_TIMEOUT);
4222 connect(&waitStarSelectTimer, &QTimer::timeout, this, &Ekos::Focus::checkAutoStarTimeout);
4223 connect(liveVideoB, &QPushButton::clicked, this, &Ekos::Focus::toggleVideo);
4224
4225 // Show FITS Image in a new window
4226 showFITSViewerB->setIcon(QIcon::fromTheme("kstars_fitsviewer"));
4227 showFITSViewerB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
4228 connect(showFITSViewerB, &QPushButton::clicked, this, &Ekos::Focus::showFITSViewer);
4229
4230 // Toggle FITS View to full screen
4231 toggleFullScreenB->setIcon(QIcon::fromTheme("view-fullscreen"));
4232 toggleFullScreenB->setShortcut(Qt::Key_F4);
4233 toggleFullScreenB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
4234 connect(toggleFullScreenB, &QPushButton::clicked, this, &Ekos::Focus::toggleFocusingWidgetFullScreen);
4235
4236 // delayed capturing for waiting the scope to settle
4237 captureTimer.setSingleShot(true);
4238 connect(&captureTimer, &QTimer::timeout, this, [&]()
4239 {
4240 capture();
4241 });
4242
4243 // How long do we wait until an exposure times out and needs a retry?
4244 captureTimeout.setSingleShot(true);
4245 connect(&captureTimeout, &QTimer::timeout, this, &Ekos::Focus::processCaptureTimeout);
4246
4247 // Start/Stop focus
4248 connect(startFocusB, &QPushButton::clicked, this, &Ekos::Focus::start);
4249 connect(stopFocusB, &QPushButton::clicked, this, &Ekos::Focus::abort);
4250
4251 // Focus IN/OUT
4252 connect(focusOutB, &QPushButton::clicked, [&]()
4253 {
4254 focusOut();
4255 });
4256 connect(focusInB, &QPushButton::clicked, [&]()
4257 {
4258 focusIn();
4259 });
4260
4261 // Capture a single frame
4262 connect(captureB, &QPushButton::clicked, this, &Ekos::Focus::capture);
4263 // Start continuous capture
4264 connect(startLoopB, &QPushButton::clicked, this, &Ekos::Focus::startFraming);
4265 // Use a subframe when capturing
4266 connect(useSubFrame, &QCheckBox::toggled, this, &Ekos::Focus::toggleSubframe);
4267 // Reset frame dimensions to default
4268 connect(resetFrameB, &QPushButton::clicked, this, &Ekos::Focus::resetFrame);
4269 // Sync setting if full field setting is toggled.
4270 connect(useFullField, &QCheckBox::toggled, [&](bool toggled)
4271 {
4272 fullFieldInnerRing->setEnabled(toggled);
4273 fullFieldOuterRing->setEnabled(toggled);
4274 if (toggled)
4275 {
4276 useSubFrame->setChecked(false);
4277 useAutoStar->setChecked(false);
4278 }
4279 else
4280 {
4281 // Disable the overlay
4282 focusView->setStarFilterRange(0, 1);
4283 }
4284 });
4285
4286
4287 // Sync settings if the CCD selection is updated.
4288 connect(CCDCaptureCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &Ekos::Focus::checkCCD);
4289 // Sync settings if the Focuser selection is updated.
4290 connect(focuserCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &Ekos::Focus::checkFocuser);
4291 // Sync settings if the filter selection is updated.
4292 connect(FilterDevicesCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &Ekos::Focus::checkFilter);
4293 // Sync settings if the temperature source selection is updated.
4294 connect(temperatureSourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
4295 &Ekos::Focus::checkTemperatureSource);
4296
4297 // Set focuser absolute position
4298 connect(startGotoB, &QPushButton::clicked, this, &Ekos::Focus::setAbsoluteFocusTicks);
4299 connect(stopGotoB, &QPushButton::clicked, [this]()
4300 {
4301 if (currentFocuser)
4302 currentFocuser->stop();
4303 });
4304 // Update the focuser box size used to enclose a star
4305 connect(focusBoxSize, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &Ekos::Focus::updateBoxSize);
4306
4307 // Update the focuser star detection if the detection algorithm selection changes.
4308 connect(focusDetectionCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, [&](int index)
4309 {
4310 focusDetection = static_cast<StarAlgorithm>(index);
4311 thresholdSpin->setEnabled(focusDetection == ALGORITHM_THRESHOLD);
4312 multiRowAverageSpin->setEnabled(focusDetection == ALGORITHM_BAHTINOV);
4313 if (focusDetection == ALGORITHM_BAHTINOV)
4314 {
4315 // In case of Bahtinov mask uncheck auto select star
4316 useAutoStar->setChecked(false);
4317 focusBoxSize->setMaximum(512);
4318 }
4319 else
4320 {
4321 // When not using Bathinov mask, limit box size to 256 and make sure value stays within range.
4322 if (Options::focusBoxSize() > 256)
4323 {
4324 Options::setFocusBoxSize(32);
4325 // Focus box size changed, update control
4326 focusBoxSize->setValue(Options::focusBoxSize());
4327 }
4328 focusBoxSize->setMaximum(256);
4329 }
4330 useAutoStar->setEnabled(focusDetection != ALGORITHM_BAHTINOV);
4331 });
4332
4333 // Update the focuser solution algorithm if the selection changes.
4334 connect(focusAlgorithmCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, [&](int index)
4335 {
4336 setFocusAlgorithm(static_cast<FocusAlgorithm>(index));
4337 });
4338
4339 // Reset star center on auto star check toggle
4340 connect(useAutoStar, &QCheckBox::toggled, this, [&](bool enabled)
4341 {
4342 if (enabled)
4343 {
4344 starCenter = QVector3D();
4345 starSelected = false;
4346 focusView->setTrackingBox(QRect());
4347 }
4348 });
4349 }
4350
setFocusAlgorithm(FocusAlgorithm algorithm)4351 void Focus::setFocusAlgorithm(FocusAlgorithm algorithm)
4352 {
4353 focusAlgorithm = algorithm;
4354 switch(algorithm)
4355 {
4356 case FOCUS_ITERATIVE:
4357 initialFocusOutStepsIN->setEnabled(false); // Out step multiple
4358 maxTravelIN->setEnabled(true); // Max Travel
4359 stepIN->setEnabled(true); // Initial Step Size
4360 maxSingleStepIN->setEnabled(true); // Max Step Size
4361 break;
4362
4363 case FOCUS_POLYNOMIAL:
4364 initialFocusOutStepsIN->setEnabled(false); // Out step multiple
4365 maxTravelIN->setEnabled(true); // Max Travel
4366 stepIN->setEnabled(true); // Initial Step Size
4367 maxSingleStepIN->setEnabled(true); // Max Step Size
4368 break;
4369
4370 case FOCUS_LINEAR:
4371 initialFocusOutStepsIN->setEnabled(true); // Out step multiple
4372 maxTravelIN->setEnabled(true); // Max Travel
4373 stepIN->setEnabled(true); // Initial Step Size
4374 maxSingleStepIN->setEnabled(false); // Max Step Size
4375 break;
4376 }
4377 }
4378
initView()4379 void Focus::initView()
4380 {
4381 focusView = new FITSView(focusingWidget, FITS_FOCUS);
4382 focusView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
4383 focusView->setBaseSize(focusingWidget->size());
4384 focusView->createFloatingToolBar();
4385 QVBoxLayout *vlayout = new QVBoxLayout();
4386 vlayout->addWidget(focusView);
4387 focusingWidget->setLayout(vlayout);
4388 connect(focusView, &FITSView::trackingStarSelected, this, &Ekos::Focus::focusStarSelected, Qt::UniqueConnection);
4389 focusView->setStarsEnabled(true);
4390 focusView->setStarsHFREnabled(true);
4391 }
4392
4393 ///////////////////////////////////////////////////////////////////////////////////////////
4394 ///
4395 ///////////////////////////////////////////////////////////////////////////////////////////
getSettings() const4396 QJsonObject Focus::getSettings() const
4397 {
4398 QJsonObject settings;
4399
4400 settings.insert("camera", CCDCaptureCombo->currentText());
4401 settings.insert("focuser", focuserCombo->currentText());
4402 settings.insert("fw", FilterDevicesCombo->currentText());
4403 settings.insert("filter", FilterPosCombo->currentText());
4404 settings.insert("exp", exposureIN->value());
4405 settings.insert("bin", qMax(1, binningCombo->currentIndex() + 1));
4406 settings.insert("gain", gainIN->value());
4407 settings.insert("iso", ISOCombo->currentIndex());
4408 return settings;
4409 }
4410
4411 ///////////////////////////////////////////////////////////////////////////////////////////
4412 ///
4413 ///////////////////////////////////////////////////////////////////////////////////////////
setSettings(const QJsonObject & settings)4414 void Focus::setSettings(const QJsonObject &settings)
4415 {
4416 static bool init = false;
4417
4418 // Camera
4419 if (syncControl(settings, "camera", CCDCaptureCombo) || init == false)
4420 checkCCD();
4421 // Focuser
4422 if (syncControl(settings, "focuser", focuserCombo) || init == false)
4423 checkFocuser();
4424 // Filter Wheel
4425 if (syncControl(settings, "fw", FilterDevicesCombo) || init == false)
4426 checkFilter();
4427 // Filter
4428 syncControl(settings, "filter", FilterPosCombo);
4429 Options::setLockAlignFilterIndex(FilterPosCombo->currentIndex());
4430 // Exposure
4431 syncControl(settings, "exp", exposureIN);
4432 // Binning
4433 const int bin = settings["bin"].toInt(binningCombo->currentIndex() + 1) - 1;
4434 if (bin != binningCombo->currentIndex())
4435 binningCombo->setCurrentIndex(bin);
4436
4437 // Gain
4438 if (currentCCD->hasGain())
4439 syncControl(settings, "gain", gainIN);
4440 // ISO
4441 if (ISOCombo->count() > 1)
4442 {
4443 const int iso = settings["iso"].toInt(ISOCombo->currentIndex());
4444 if (iso != ISOCombo->currentIndex())
4445 ISOCombo->setCurrentIndex(iso);
4446 }
4447
4448 init = true;
4449 }
4450
4451
4452 ///////////////////////////////////////////////////////////////////////////////////////////
4453 ///
4454 ///////////////////////////////////////////////////////////////////////////////////////////
getPrimarySettings() const4455 QJsonObject Focus::getPrimarySettings() const
4456 {
4457 QJsonObject settings;
4458
4459 settings.insert("autostar", useAutoStar->isChecked());
4460 settings.insert("dark", darkFrameCheck->isChecked());
4461 settings.insert("subframe", useSubFrame->isChecked());
4462 settings.insert("box", focusBoxSize->value());
4463 settings.insert("fullfield", useFullField->isChecked());
4464 settings.insert("inner", fullFieldInnerRing->value());
4465 settings.insert("outer", fullFieldOuterRing->value());
4466 settings.insert("suspend", suspendGuideCheck->isChecked());
4467 settings.insert("guide_settle", FocusSettleTime->value());
4468
4469 return settings;
4470 }
4471
4472 ///////////////////////////////////////////////////////////////////////////////////////////
4473 ///
4474 ///////////////////////////////////////////////////////////////////////////////////////////
setPrimarySettings(const QJsonObject & settings)4475 void Focus::setPrimarySettings(const QJsonObject &settings)
4476 {
4477 syncControl(settings, "autostar", useAutoStar);
4478 syncControl(settings, "dark", darkFrameCheck);
4479 syncControl(settings, "subframe", useSubFrame);
4480 syncControl(settings, "box", focusBoxSize);
4481 syncControl(settings, "fullfield", useFullField);
4482 syncControl(settings, "inner", fullFieldInnerRing);
4483 syncControl(settings, "outer", fullFieldOuterRing);
4484 syncControl(settings, "suspend", suspendGuideCheck);
4485 syncControl(settings, "guide_settle", FocusSettleTime);
4486
4487 }
4488
4489 ///////////////////////////////////////////////////////////////////////////////////////////
4490 ///
4491 ///////////////////////////////////////////////////////////////////////////////////////////
getProcessSettings() const4492 QJsonObject Focus::getProcessSettings() const
4493 {
4494 QJsonObject settings;
4495
4496 settings.insert("detection", focusDetectionCombo->currentText());
4497 settings.insert("algorithm", focusAlgorithmCombo->currentText());
4498 settings.insert("sep", focusOptionsProfiles->currentText());
4499 settings.insert("threshold", thresholdSpin->value());
4500 settings.insert("tolerance", toleranceIN->value());
4501 settings.insert("average", focusFramesSpin->value());
4502 settings.insert("rows", multiRowAverageSpin->value());
4503 settings.insert("kernel", gaussianKernelSizeSpin->value());
4504 settings.insert("sigma", gaussianSigmaSpin->value());
4505
4506 return settings;
4507 }
4508
4509 ///////////////////////////////////////////////////////////////////////////////////////////
4510 ///
4511 ///////////////////////////////////////////////////////////////////////////////////////////
setProcessSettings(const QJsonObject & settings)4512 void Focus::setProcessSettings(const QJsonObject &settings)
4513 {
4514 syncControl(settings, "detection", focusDetectionCombo);
4515 syncControl(settings, "algorithm", focusAlgorithmCombo);
4516 syncControl(settings, "sep", focusOptionsProfiles);
4517 syncControl(settings, "threshold", thresholdSpin);
4518 syncControl(settings, "tolerance", toleranceIN);
4519 syncControl(settings, "average", focusFramesSpin);
4520 syncControl(settings, "rows", multiRowAverageSpin);
4521 syncControl(settings, "kernel", gaussianKernelSizeSpin);
4522 syncControl(settings, "sigma", gaussianSigmaSpin);
4523 }
4524
4525 ///////////////////////////////////////////////////////////////////////////////////////////
4526 ///
4527 ///////////////////////////////////////////////////////////////////////////////////////////
getMechanicsSettings() const4528 QJsonObject Focus::getMechanicsSettings() const
4529 {
4530 QJsonObject settings;
4531
4532 settings.insert("step", stepIN->value());
4533 settings.insert("travel", maxTravelIN->value());
4534 settings.insert("maxstep", maxSingleStepIN->value());
4535 settings.insert("backlash", focusBacklashSpin->value());
4536 settings.insert("settle", FocusSettleTime->value());
4537 settings.insert("out", initialFocusOutStepsIN->value());
4538
4539 return settings;
4540 }
4541
4542 ///////////////////////////////////////////////////////////////////////////////////////////
4543 ///
4544 ///////////////////////////////////////////////////////////////////////////////////////////
setMechanicsSettings(const QJsonObject & settings)4545 void Focus::setMechanicsSettings(const QJsonObject &settings)
4546 {
4547 syncControl(settings, "step", stepIN);
4548 syncControl(settings, "travel", maxTravelIN);
4549 syncControl(settings, "maxstep", maxSingleStepIN);
4550 syncControl(settings, "backlash", focusBacklashSpin);
4551 syncControl(settings, "settle", FocusSettleTime);
4552 syncControl(settings, "out", initialFocusOutStepsIN);
4553 }
4554
4555 ///////////////////////////////////////////////////////////////////////////////////////////
4556 ///
4557 ///////////////////////////////////////////////////////////////////////////////////////////
syncControl(const QJsonObject & settings,const QString & key,QWidget * widget)4558 bool Focus::syncControl(const QJsonObject &settings, const QString &key, QWidget * widget)
4559 {
4560 QSpinBox *pSB = nullptr;
4561 QDoubleSpinBox *pDSB = nullptr;
4562 QCheckBox *pCB = nullptr;
4563 QComboBox *pComboBox = nullptr;
4564
4565 if ((pSB = qobject_cast<QSpinBox *>(widget)))
4566 {
4567 const int value = settings[key].toInt(pSB->value());
4568 if (value != pSB->value())
4569 {
4570 pSB->setValue(value);
4571 return true;
4572 }
4573 }
4574 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
4575 {
4576 const double value = settings[key].toDouble(pDSB->value());
4577 if (value != pDSB->value())
4578 {
4579 pDSB->setValue(value);
4580 return true;
4581 }
4582 }
4583 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
4584 {
4585 const bool value = settings[key].toBool(pCB->isChecked());
4586 if (value != pCB->isChecked())
4587 {
4588 pCB->setChecked(value);
4589 return true;
4590 }
4591 }
4592 // ONLY FOR STRINGS, not INDEX
4593 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
4594 {
4595 const QString value = settings[key].toString(pComboBox->currentText());
4596 if (value != pComboBox->currentText())
4597 {
4598 pComboBox->setCurrentText(value);
4599 return true;
4600 }
4601 }
4602
4603 return false;
4604 };
4605
4606 }
4607