Kstars

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

KDE's Doxygen guidelines are available online.